summaryrefslogtreecommitdiff
path: root/internal/hexaiaction
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-07 14:29:35 +0300
committerPaul Buetow <paul@buetow.org>2025-09-07 14:29:35 +0300
commit23482b5d8da5c67da1fc501ddbafdd123be3972c (patch)
tree452dc7c418055ebb79a88a303e50d2dbc1877f09 /internal/hexaiaction
parent76f388f9759cdc15cb1eba985cd87cde1906208b (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.go41
-rw-r--r--internal/hexaiaction/cmdentry_runcommand_test.go22
-rw-r--r--internal/hexaiaction/cmdentry_test.go23
-rw-r--r--internal/hexaiaction/run.go12
-rw-r--r--internal/hexaiaction/types.go2
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