diff options
| author | Paul Buetow <paul@buetow.org> | 2025-09-07 14:29:35 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-09-07 14:29:35 +0300 |
| commit | 23482b5d8da5c67da1fc501ddbafdd123be3972c (patch) | |
| tree | 452dc7c418055ebb79a88a303e50d2dbc1877f09 /internal/hexaiaction | |
| parent | 76f388f9759cdc15cb1eba985cd87cde1906208b (diff) | |
feat: rename hexai-action -> hexai-tmux-action; remove --tmux/--no-tmux; tmux-only flow; update docs and Magefile
Diffstat (limited to 'internal/hexaiaction')
| -rw-r--r-- | internal/hexaiaction/cmdentry.go | 41 | ||||
| -rw-r--r-- | internal/hexaiaction/cmdentry_runcommand_test.go | 22 | ||||
| -rw-r--r-- | internal/hexaiaction/cmdentry_test.go | 23 | ||||
| -rw-r--r-- | internal/hexaiaction/run.go | 12 | ||||
| -rw-r--r-- | internal/hexaiaction/types.go | 2 |
5 files changed, 25 insertions, 75 deletions
diff --git a/internal/hexaiaction/cmdentry.go b/internal/hexaiaction/cmdentry.go index 1947390..cf72495 100644 --- a/internal/hexaiaction/cmdentry.go +++ b/internal/hexaiaction/cmdentry.go @@ -12,52 +12,32 @@ import ( "golang.org/x/term" ) -// Options configures the command-line orchestration for hexai-action. +// Options configures the command-line orchestration for hexai-tmux-action. type Options struct { Infile string Outfile string - ForceTmux bool - NoTmux bool UIChild bool TmuxTarget string TmuxSplit string // "v" or "h" TmuxPercent int // 1-100 } -// RunCommand is the CLI orchestrator used by cmd/hexai-action. It decides whether -// to run inline, in a tmux split pane, or in child mode; then delegates to Run. +// RunCommand is the CLI orchestrator used by cmd/hexai-tmux-action. It runs in tmux +// split-pane mode by default, or child mode when -ui-child is set. func RunCommand(ctx context.Context, opts Options, stdin io.Reader, stdout, stderr io.Writer) error { if opts.UIChild { return runChild(ctx, opts.Infile, opts.Outfile, stdout, stderr) } - if shouldRunInTmux(opts.ForceTmux, opts.NoTmux) { - return runInTmuxParent(stdin, stdout, opts.TmuxTarget, opts.TmuxSplit, opts.TmuxPercent) - } - // Inline path: only if we have a TTY for UI; otherwise echo input - if isTTYFn(os.Stdout.Fd()) && isTTYFn(os.Stdin.Fd()) { - in, out, closeIn, closeOut, err := openIO(opts.Infile, opts.Outfile) - if err != nil { return err } - defer closeIn(); defer closeOut() - return Run(ctx, in, out, stderr) - } - // Fallback: echo - return echoThrough(opts.Infile, opts.Outfile, stdin, stdout) + // Always use tmux path + return runInTmuxParent(stdin, stdout, opts.TmuxTarget, opts.TmuxSplit, opts.TmuxPercent) } // seams for unit tests var isTTYFn = func(fd uintptr) bool { return term.IsTerminal(int(fd)) } -var tmuxAvailableFn = tmux.Available var splitRunFn = tmux.SplitRun var osExecutableFn = os.Executable var runFn = Run -func shouldRunInTmux(forceTmux, noTmux bool) bool { - if noTmux { return false } - if forceTmux { return true } - if !(isTTYFn(os.Stdin.Fd()) && isTTYFn(os.Stdout.Fd())) && tmuxAvailableFn() { return true } - return false -} - // openIO returns readers/writers for infile/outfile flags with deferred closers. func openIO(infile, outfile string) (io.Reader, io.Writer, func(), func(), error) { in := io.Reader(os.Stdin) @@ -66,13 +46,13 @@ func openIO(infile, outfile string) (io.Reader, io.Writer, func(), func(), error closeOut := func() {} if path := infile; path != "" { f, err := os.Open(path) - if err != nil { return nil, nil, func(){}, func(){}, fmt.Errorf("hexai-action: cannot open infile: %w", err) } + if err != nil { return nil, nil, func(){}, func(){}, fmt.Errorf("hexai-tmux-action: cannot open infile: %w", err) } in = f closeIn = func() { _ = f.Close() } } if path := outfile; path != "" { f, err := os.Create(path) - if err != nil { return nil, nil, func(){}, func(){}, fmt.Errorf("hexai-action: cannot open outfile: %w", err) } + if err != nil { return nil, nil, func(){}, func(){}, fmt.Errorf("hexai-tmux-action: cannot open outfile: %w", err) } out = f closeOut = func() { _ = f.Close() } } @@ -99,7 +79,7 @@ func runChild(ctx context.Context, infile, outfile string, stdout, stderr io.Wri if err := runFn(ctx, in, out, stderr); err != nil { closeOut() if copyErr := echoThrough(infile, tmp, os.Stdin, stdout); copyErr != nil { - return fmt.Errorf("hexai-action child: %v; echo failed: %v", err, copyErr) + return fmt.Errorf("hexai-tmux-action child: %v; echo failed: %v", err, copyErr) } } else { closeOut() @@ -108,7 +88,7 @@ func runChild(ctx context.Context, infile, outfile string, stdout, stderr io.Wri } func runInTmuxParent(stdin io.Reader, stdout io.Writer, target, split string, percent int) error { - dir, err := os.MkdirTemp("", "hexai-action-") + dir, err := os.MkdirTemp("", "hexai-tmux-action-") if err != nil { return err } defer func() { _ = os.RemoveAll(dir) }() inPath := filepath.Join(dir, "input.txt") @@ -135,7 +115,7 @@ func waitForFile(path string, timeout time.Duration) error { deadline := time.Now().Add(timeout) for { if _, err := os.Stat(path); err == nil { return nil } - if time.Now().After(deadline) { return fmt.Errorf("hexai-action: timeout waiting for reply file") } + if time.Now().After(deadline) { return fmt.Errorf("hexai-tmux-action: timeout waiting for reply file") } time.Sleep(200 * time.Millisecond) } } @@ -148,6 +128,7 @@ func catFileTo(w io.Writer, path string) error { return err } +// echoThrough no longer used in tmux-only flow, but kept for potential reuse. func echoThrough(infile, outfile string, stdin io.Reader, stdout io.Writer) error { var in io.Reader = stdin var out io.Writer = stdout diff --git a/internal/hexaiaction/cmdentry_runcommand_test.go b/internal/hexaiaction/cmdentry_runcommand_test.go index 7c8aa5c..092e43b 100644 --- a/internal/hexaiaction/cmdentry_runcommand_test.go +++ b/internal/hexaiaction/cmdentry_runcommand_test.go @@ -6,7 +6,6 @@ import ( "io" "os" "path/filepath" - "strings" "testing" "codeberg.org/snonux/hexai/internal/tmux" @@ -30,12 +29,10 @@ func TestRunCommand_UIChild(t *testing.T) { func TestRunCommand_Tmux(t *testing.T) { oldTTY := isTTYFn - oldAvail := tmuxAvailableFn oldExec := osExecutableFn oldSplit := splitRunFn isTTYFn = func(_ uintptr) bool { return false } - tmuxAvailableFn = func() bool { return true } - osExecutableFn = func() (string, error) { return "/bin/hexai-action", nil } + osExecutableFn = func() (string, error) { return "/bin/hexai-tmux-action", nil } splitRunFn = func(_ tmux.SplitOpts, argv []string) error { for i := 0; i < len(argv)-1; i++ { if argv[i] == "-outfile" && i+1 < len(argv) { @@ -45,9 +42,9 @@ func TestRunCommand_Tmux(t *testing.T) { } return nil } - defer func(){ isTTYFn = oldTTY; tmuxAvailableFn = oldAvail; osExecutableFn = oldExec; splitRunFn = oldSplit }() + defer func(){ isTTYFn = oldTTY; osExecutableFn = oldExec; splitRunFn = oldSplit }() var out bytes.Buffer - if err := RunCommand(context.Background(), Options{ForceTmux: true}, bytes.NewBufferString("X"), &out, io.Discard); err != nil { + if err := RunCommand(context.Background(), Options{}, bytes.NewBufferString("X"), &out, io.Discard); err != nil { t.Fatalf("RunCommand tmux: %v", err) } if out.String() != "OUT" { t.Fatalf("stdout: %q", out.String()) } @@ -56,15 +53,4 @@ func TestRunCommand_Tmux(t *testing.T) { // Inline TTY path is exercised implicitly via other helpers; testing it directly // would require TTY simulation which is brittle in unit tests. -func TestRunCommand_FallbackEcho(t *testing.T) { - oldTTY := isTTYFn - oldAvail := tmuxAvailableFn - isTTYFn = func(_ uintptr) bool { return false } - tmuxAvailableFn = func() bool { return false } - defer func(){ isTTYFn = oldTTY; tmuxAvailableFn = oldAvail }() - var out bytes.Buffer - if err := RunCommand(context.Background(), Options{NoTmux: true}, bytes.NewBufferString("Z"), &out, io.Discard); err != nil { - t.Fatalf("RunCommand fallback: %v", err) - } - if strings.TrimSpace(out.String()) != "Z" { t.Fatalf("stdout: %q", out.String()) } -} +// Fallback echo path removed in tmux-only flow. diff --git a/internal/hexaiaction/cmdentry_test.go b/internal/hexaiaction/cmdentry_test.go index 8525f7d..de8b5dd 100644 --- a/internal/hexaiaction/cmdentry_test.go +++ b/internal/hexaiaction/cmdentry_test.go @@ -13,24 +13,7 @@ import ( "codeberg.org/snonux/hexai/internal/tmux" ) -func TestShouldRunInTmux_Preferences(t *testing.T) { - if shouldRunInTmux(false, true) { t.Fatal("expected false when no-tmux is set") } - if !shouldRunInTmux(true, false) { t.Fatal("expected true when -tmux is set") } -} - -func TestShouldRunInTmux_Auto(t *testing.T) { - oldIsTTY := isTTYFn - oldAvail := tmuxAvailableFn - t.Cleanup(func() { isTTYFn = oldIsTTY; tmuxAvailableFn = oldAvail }) - isTTYFn = func(_ uintptr) bool { return false } - tmuxAvailableFn = func() bool { return true } - if !shouldRunInTmux(false, false) { t.Fatal("expected true when not TTY and tmux available") } - isTTYFn = func(_ uintptr) bool { return true } - if shouldRunInTmux(false, false) { t.Fatal("expected false when TTY present") } - isTTYFn = func(_ uintptr) bool { return false } - tmuxAvailableFn = func() bool { return false } - if shouldRunInTmux(false, false) { t.Fatal("expected false when tmux unavailable") } -} +// tmux-only flow: decision helpers removed. func TestPersistStdin_WritesFile(t *testing.T) { dir := t.TempDir() @@ -78,7 +61,7 @@ func TestRunInTmuxParent_Stubbed(t *testing.T) { rout, wout, _ := os.Pipe() oldExec := osExecutableFn oldSplit := splitRunFn - osExecutableFn = func() (string, error) { return "/bin/hexai-action", nil } + osExecutableFn = func() (string, error) { return "/bin/hexai-tmux-action", nil } splitRunFn = func(opts tmux.SplitOpts, argv []string) error { for i := 0; i < len(argv)-1; i++ { if argv[i] == "-outfile" && i+1 < len(argv) { @@ -106,7 +89,7 @@ func TestRunInTmuxParent_ExecutableError(t *testing.T) { func TestRunInTmuxParent_SplitError(t *testing.T) { oldExec := osExecutableFn - osExecutableFn = func() (string, error) { return "/bin/hexai-action", nil } + osExecutableFn = func() (string, error) { return "/bin/hexai-tmux-action", nil } oldSplit := splitRunFn splitRunFn = func(_ tmux.SplitOpts, _ []string) error { return fmt.Errorf("split failed") } t.Cleanup(func() { osExecutableFn = oldExec; splitRunFn = oldSplit }) diff --git a/internal/hexaiaction/run.go b/internal/hexaiaction/run.go index 609dad1..a8d4243 100644 --- a/internal/hexaiaction/run.go +++ b/internal/hexaiaction/run.go @@ -12,26 +12,26 @@ import ( "codeberg.org/snonux/hexai/internal/llmutils" ) -// Run executes the hexai-action command flow. +// Run executes the hexai-tmux-action command flow. // seams for testability var chooseActionFn = RunTUI var newClientFromApp = llmutils.NewClientFromApp func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error { - logger := log.New(stderr, "hexai-action ", log.LstdFlags|log.Lmsgprefix) + logger := log.New(stderr, "hexai-tmux-action ", log.LstdFlags|log.Lmsgprefix) cfg := appconfig.Load(logger) client, err := newClientFromApp(cfg) if err != nil { - fmt.Fprintf(stderr, logging.AnsiBase+"hexai-action: LLM disabled: %v"+logging.AnsiReset+"\n", err) + fmt.Fprintf(stderr, logging.AnsiBase+"hexai-tmux-action: LLM disabled: %v"+logging.AnsiReset+"\n", err) return err } parts, err := ParseInput(stdin) if err != nil { - fmt.Fprintln(stderr, logging.AnsiBase+"hexai-action: failed to read input"+logging.AnsiReset) + fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: failed to read input"+logging.AnsiReset) return err } if strings.TrimSpace(parts.Selection) == "" { - return fmt.Errorf("hexai-action: no input provided on stdin") + return fmt.Errorf("hexai-tmux-action: no input provided on stdin") } kind, err := chooseActionFn() if err != nil { @@ -52,7 +52,7 @@ func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg a case ActionRewrite: instr, cleaned := ExtractInstruction(parts.Selection) if strings.TrimSpace(instr) == "" { - fmt.Fprintln(stderr, logging.AnsiBase+"hexai-action: no inline instruction found; echoing input"+logging.AnsiReset) + fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: no inline instruction found; echoing input"+logging.AnsiReset) return parts.Selection, nil } cctx, cancel := timeout10s(ctx) diff --git a/internal/hexaiaction/types.go b/internal/hexaiaction/types.go index 5e01cfc..2dc918f 100644 --- a/internal/hexaiaction/types.go +++ b/internal/hexaiaction/types.go @@ -1,6 +1,6 @@ package hexaiaction -// Summary: Core types and constants for hexai-action. +// Summary: Core types and constants for hexai-tmux-action. type ActionKind string |
