diff options
| author | Paul Buetow <paul@buetow.org> | 2025-09-07 14:32:48 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-09-07 14:32:48 +0300 |
| commit | 90e586831c0351fb5808ef5c1eca0692178731c9 (patch) | |
| tree | 28b5edd41ec468ae34dc7f05eb3b994e07b02d71 | |
| parent | 23482b5d8da5c67da1fc501ddbafdd123be3972c (diff) | |
tui: hide title in hexai-tmux-action menu to save space
| -rw-r--r-- | TODO.md | 121 | ||||
| -rw-r--r-- | docs/coverage.html | 172 | ||||
| -rw-r--r-- | docs/coverage.out | 5379 | ||||
| -rw-r--r-- | internal/hexaiaction/tui.go | 2 |
4 files changed, 2681 insertions, 2993 deletions
@@ -1,121 +0,0 @@ -Comprehensive plan: integrate Helix + tmux flow into hexai-tmux-action - -Summary of current setup -- Helix keybinding pipes selection to the tmux action command. -- The tmux action writes stdin to a temp file, opens a tmux split-pane, runs `hexai-tmux-action -infile <input> -outfile <reply>.tmp`, then atomically renames to `<reply>` and prints the reply back to Helix. -- This works but requires shell scripts and out-of-band temp files. - -Goal -- Helix should call hexai-tmux-action directly: `:pipe hexai-tmux-action`. -- hexai-tmux-action itself handles: reading stdin; presenting TUI in a tmux pane; executing the chosen action; printing the result to stdout for Helix to apply. -- Consolidate all tmux logic in a reusable `internal/tmux` package for future features. - -Proposed CLI/UX -- Default (auto): - - If running in an interactive TTY, run TUI inline and print the result to stdout. - - If stdin/stdout are pipes (Helix `:pipe`) and a tmux session is available, spawn a temporary tmux pane to render the TUI, then return the final output on stdout. - - If tmux is not available and no TTY is present, fall back to a sensible non-interactive mode: echo input or use instruction-based rewrite if an inline instruction is detected. -- Flags (public): - - `-tmux` (bool, default: auto): force enabling tmux-pane mode. - - `-no-tmux` (bool): force disabling tmux-pane mode even if available. - - `-infile`, `-outfile`: keep for compatibility/testing, but not needed for Helix. -- Flags (internal/private; hidden in help): - - `-ui-child` (bool): internal child mode that assumes `-infile` and `-outfile` and runs the interactive TUI on the attached TTY, writing final output to outfile. - - `-tmux-target` (string, optional): tmux target pane/window (advanced users). - - `-tmux-split` (enum: `v|h`, default `v`): split orientation. - -High-level design -1) IO orchestration (parent process) - - Determine whether to show TUI inline or via tmux based on TTY detection and `-tmux`/`-no-tmux`. - - Inline path: run current `hexaiaction.Run(ctx, in, out, err)` (unchanged behavior) and exit. - - Tmux path: write stdin to a secure temp file, spawn a tmux split-pane that executes a `hexai-tmux-action -ui-child -infile <in> -outfile <out>.tmp`, wait for completion (by process exit and/or file rename), then print `<out>` to stdout. - -2) Child/TUI execution (`-ui-child`) - - Read from `-infile`, parse input (diagnostics + selection), construct LLM client, show Bubble Tea menu, run selected action, write result to `-outfile.tmp`, fsync, rename to `-outfile`. - - On error, write a human-readable message to stderr and a minimal fallback to outfile (e.g., echo selection) to avoid blocking Helix. - -3) Tmux integration package (`internal/tmux`) - - Responsibilities: - - Detect availability: binary present and inside a tmux session (`$TMUX` set) or a viable target. - - Run commands in a new split pane and return control immediately or after completion. - - Small helpers for file-based rendezvous (optional), e.g., `WaitForFile(path, timeout)`. - - Minimal API (initial): - - `func Available() bool` - - `type SplitOpts struct { Target string; Vertical bool; Percent int }` - - `func SplitRun(opts SplitOpts, argv []string) error` — runs `tmux split-window ... <argv>` and returns once tmux has launched the child process. - - `func HasBinary() bool` and `func InSession() bool` (if we want finer checks). - - Implementation details: - - Shell out to `tmux` (no lib dep). Build command like: `tmux split-window -v -p 33 "<cmd>"`. - - Quote/escape argv safely. Prefer `exec.Command` for the child in a shell wrapper, or join argv for `tmux`’s command string. - - Avoid writing to `~/.hx-*`; use `os.CreateTemp("", "hexai-tmux-action-*" )` under `$TMPDIR`. - -4) hexaiaction refactor (internal package) - - Separate concerns to keep functions small/testable: - - New function `ChooseAction(ctx, stdin, stderr) (ActionKind, InputParts, error)` that parses input and (conditionally) runs TUI. - - Existing action runners remain unchanged (rewrite, document, diagnostics, gotest). - - Keep `Run` as a thin orchestrator that assumes interactive mode; the parent (cmd) decides inline vs tmux/child. - - Ensure unit-testable seams: parse, instruction extraction, action execution already have tests; add tests for new branching logic where possible without tmux. - -5) Robustness and UX details - - TTY detection: use `golang.org/x/term` or a small `isatty` helper to decide inline vs tmux. - - Timeouts: child actions already use short timeouts; parent wait for outfile should have a reasonable deadline (e.g., 60s) to avoid hanging Helix. - - Atomic writes: write to `outfile.tmp`, `Sync`, then `Rename` for a clear completion signal. - - Cleanup: always remove temp files (defer and signal handling for SIGINT/SIGTERM). - - Logging: log to stderr with clear `hexai-tmux-action` prefixes; keep stdout clean for Helix’s `:pipe`. - -Helix configuration after change -- Replace the current keybinding pipeline with a single call: - - `C-a = ":pipe hexai-tmux-action"` - -Migration plan -1) Implement tmux package and integrate auto-mode in `cmd/hexai-tmux-action/main.go`. -2) Keep legacy flags (`-infile`, `-outfile`) for compatibility and tests. -3) Update README and docs to show new Helix keybinding and describe flags. -4) Mark shell scripts (`llminputs/ai`, `llminputs/hx.hexai-action-prompt`) as deprecated in repo notes; retain them temporarily. -5) After a stabilization period, remove the scripts (or move to `scripts/legacy/`). - -Testing plan -- Unit tests: - - `internal/tmux`: mock `exec.Command` via a small command-runner interface; verify command assembly and availability checks. - - `internal/hexaiaction`: tests for tmux split orchestration and child flow. - - hexaiaction: existing tests continue to pass; add tests for non-interactive fallback behavior when no TTY. -- Integration tests (manual or scripted): - - Run under tmux: verify a pane opens, TUI choice is applied, stdout contains result. - - Run outside tmux: verify inline TUI works in terminal; verify fallback behavior under `:pipe` without tmux. - - Coverage target: ensure at least 85% unit test coverage for all new code paths added in this integration (verify with `go test -cover ./...` and per-package `-coverprofile`). - -Edge cases and mitigations -- No tmux, no TTY: skip TUI; if inline instruction detected (`;...;`, `// ...`, etc.), run rewrite; else echo selection. -- tmux available but spawning fails: warn on stderr and fall back to non-interactive mode. -- Large inputs: spill to temp files; ensure temp dir has space; surface errors clearly. -- Windows: tmux mode auto-disables; inline mode continues to work. - -Implementation steps (incremental) -1) Add `internal/tmux` with `Available`, `SplitRun`, and helpers. -2) Wire flags (`-ui-child`, `-tmux-target`, `-tmux-split`, `-tmux-percent`). -3) Parent flow: detect mode, manage temp files, spawn child via tmux when selected, wait/print result. -4) Child flow: reuse existing `hexaiaction.Run` to keep logic centralized; ensure outfile atomic write. -5) Docs: update README with new Helix config and flags. -6) Optional: add `-tmux-target`/`-tmux-split` for power users. - -Notes on code organization -- Place all tmux-related code under `internal/tmux` and keep functions well under 50 lines. -- Keep command entrypoint (`cmd/hexai-tmux-action/main.go`) small and focused on wiring/mode selection. -- Avoid duplication across `hexaiaction` and `tmux`; IO/file and action logic remain in `hexaiaction`. - -Outcome -- One-step Helix integration (`:pipe hexai-tmux-action`). -- No helper scripts required; cross-platform friendly with graceful fallbacks. -- Reusable tmux utilities for future features. - -Progress -- [x] Add `internal/tmux` with `Available`, `SplitRun`, quoting helpers. -- [x] Wire flags in tmux action: `-ui-child`, `-tmux-target`, `-tmux-split`, `-tmux-percent`. -- [x] Parent tmux orchestration: write stdin to temp, split tmux, wait for outfile, print to stdout. -- [x] Child mode: atomic `outfile.tmp` write and rename, with error echo fallback. -- [x] Unit tests for `internal/tmux` and tmux orchestration in action (validate locally; target ≥85% coverage for new code). -- [x] Update README/docs for new Helix keybinding and flags. -- [ ] Delete legacy helper scripts (`llminputs/ai`, `llminputs/hx.hexai-action-prompt`) when ready; no deprecation notice. -- [x] Ran coverage locally. Notes: - - `mage coverage` now passes (HTML at docs/coverage.html). Total cross-package coverage ≈ 84%. - - New package `internal/tmux` is ≥85% covered. The action entrypoint package sits ~69% overall; newly added helper paths are covered (openIO, runChild, runInTmuxParent, etc.). The `main()` remains intentionally untested. diff --git a/docs/coverage.html b/docs/coverage.html index 90eae60..fb5d655 100644 --- a/docs/coverage.html +++ b/docs/coverage.html @@ -3,7 +3,7 @@ <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> - <title>hexai-action: Go Coverage Report</title> + <title>hexai-lsp: Go Coverage Report</title> <style> body { background: black; @@ -55,15 +55,15 @@ <div id="nav"> <select id="files"> - <option value="file0">codeberg.org/snonux/hexai/cmd/hexai-action/main.go (0.0%)</option> + <option value="file0">codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go (75.0%)</option> - <option value="file1">codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go (75.0%)</option> + <option value="file1">codeberg.org/snonux/hexai/cmd/hexai-tmux-action/main.go (0.0%)</option> <option value="file2">codeberg.org/snonux/hexai/cmd/hexai/main.go (71.4%)</option> <option value="file3">codeberg.org/snonux/hexai/internal/appconfig/config.go (91.6%)</option> - <option value="file4">codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go (81.5%)</option> + <option value="file4">codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go (84.5%)</option> <option value="file5">codeberg.org/snonux/hexai/internal/hexaiaction/parse.go (92.6%)</option> @@ -144,7 +144,34 @@ </div> <div id="content"> - <pre class="file" id="file0" style="display: none">package main + <pre class="file" id="file0" style="display: none">// Summary: Hexai LSP entrypoint; parses flags and delegates to internal/hexailsp. +package main + +import ( + "flag" + "log" + "os" + + "codeberg.org/snonux/hexai/internal" + "codeberg.org/snonux/hexai/internal/hexailsp" +) + +func main() <span class="cov8" title="1">{ + logPath := flag.String("log", "/tmp/hexai-lsp.log", "path to log file (optional)") + showVersion := flag.Bool("version", false, "print version and exit") + flag.Parse() + if *showVersion </span><span class="cov8" title="1">{ + log.Println(internal.Version) + return + }</span> + + <span class="cov0" title="0">if err := hexailsp.Run(*logPath, os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{ + log.Fatalf("server error: %v", err) + }</span> +} +</pre> + + <pre class="file" id="file1" style="display: none">package main import ( "context" @@ -158,8 +185,6 @@ import ( func main() <span class="cov0" title="0">{ infile := flag.String("infile", "", "Read input from this file instead of stdin") outfile := flag.String("outfile", "", "Write output to this file instead of stdout") - forceTmux := flag.Bool("tmux", false, "Force running the UI in a tmux split-pane (auto if not set)") - noTmux := flag.Bool("no-tmux", false, "Disable tmux mode even if available") uiChild := flag.Bool("ui-child", false, "INTERNAL: run interactive UI and write to -outfile atomically") tmuxTarget := flag.String("tmux-target", "", "tmux split target (advanced)") tmuxSplit := flag.String("tmux-split", "v", "tmux split orientation: v or h") @@ -168,8 +193,7 @@ func main() <span class="cov0" title="0">{ opts := hexaiaction.Options{ Infile: *infile, Outfile: *outfile, - ForceTmux: *forceTmux, NoTmux: *noTmux, UIChild: *uiChild, - TmuxTarget: *tmuxTarget, TmuxSplit: *tmuxSplit, TmuxPercent: *tmuxPercent, + UIChild: *uiChild, TmuxTarget: *tmuxTarget, TmuxSplit: *tmuxSplit, TmuxPercent: *tmuxPercent, } if err := hexaiaction.RunCommand(context.Background(), opts, os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{ fmt.Fprintln(os.Stderr, err) @@ -179,33 +203,6 @@ func main() <span class="cov0" title="0">{ </pre> - <pre class="file" id="file1" style="display: none">// Summary: Hexai LSP entrypoint; parses flags and delegates to internal/hexailsp. -package main - -import ( - "flag" - "log" - "os" - - "codeberg.org/snonux/hexai/internal" - "codeberg.org/snonux/hexai/internal/hexailsp" -) - -func main() <span class="cov8" title="1">{ - logPath := flag.String("log", "/tmp/hexai-lsp.log", "path to log file (optional)") - showVersion := flag.Bool("version", false, "print version and exit") - flag.Parse() - if *showVersion </span><span class="cov8" title="1">{ - log.Println(internal.Version) - return - }</span> - - <span class="cov0" title="0">if err := hexailsp.Run(*logPath, os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{ - log.Fatalf("server error: %v", err) - }</span> -} -</pre> - <pre class="file" id="file2" style="display: none">// Summary: Hexai CLI entrypoint; parses flags and delegates to internal/hexaicli. package main @@ -1076,75 +1073,55 @@ 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. -func RunCommand(ctx context.Context, opts Options, stdin io.Reader, stdout, stderr io.Writer) error <span class="cov6" title="3">{ +// 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 <span class="cov4" title="2">{ if opts.UIChild </span><span class="cov1" title="1">{ return runChild(ctx, opts.Infile, opts.Outfile, stdout, stderr) }</span> - <span class="cov4" title="2">if shouldRunInTmux(opts.ForceTmux, opts.NoTmux) </span><span class="cov1" title="1">{ - return runInTmuxParent(stdin, stdout, opts.TmuxTarget, opts.TmuxSplit, opts.TmuxPercent) - }</span> - // Inline path: only if we have a TTY for UI; otherwise echo input - <span class="cov1" title="1">if isTTYFn(os.Stdout.Fd()) && isTTYFn(os.Stdin.Fd()) </span><span class="cov0" title="0">{ - in, out, closeIn, closeOut, err := openIO(opts.Infile, opts.Outfile) - if err != nil </span><span class="cov0" title="0">{ return err }</span> - <span class="cov0" title="0">defer closeIn(); defer closeOut() - return Run(ctx, in, out, stderr)</span> - } - // Fallback: echo - <span class="cov1" title="1">return echoThrough(opts.Infile, opts.Outfile, stdin, stdout)</span> + // Always use tmux path + <span class="cov1" title="1">return runInTmuxParent(stdin, stdout, opts.TmuxTarget, opts.TmuxSplit, opts.TmuxPercent)</span> } // seams for unit tests var isTTYFn = func(fd uintptr) bool <span class="cov0" title="0">{ return term.IsTerminal(int(fd)) }</span> -var tmuxAvailableFn = tmux.Available var splitRunFn = tmux.SplitRun var osExecutableFn = os.Executable var runFn = Run -func shouldRunInTmux(forceTmux, noTmux bool) bool <span class="cov10" title="7">{ - if noTmux </span><span class="cov4" title="2">{ return false }</span> - <span class="cov8" title="5">if forceTmux </span><span class="cov4" title="2">{ return true }</span> - <span class="cov6" title="3">if !(isTTYFn(os.Stdin.Fd()) && isTTYFn(os.Stdout.Fd())) && tmuxAvailableFn() </span><span class="cov1" title="1">{ return true }</span> - <span class="cov4" title="2">return false</span> -} - // openIO returns readers/writers for infile/outfile flags with deferred closers. -func openIO(infile, outfile string) (io.Reader, io.Writer, func(), func(), error) <span class="cov6" title="3">{ +func openIO(infile, outfile string) (io.Reader, io.Writer, func(), func(), error) <span class="cov7" title="3">{ in := io.Reader(os.Stdin) out := io.Writer(os.Stdout) closeIn := func() </span>{<span class="cov0" title="0">}</span> - <span class="cov6" title="3">closeOut := func() </span>{<span class="cov0" title="0">}</span> - <span class="cov6" title="3">if path := infile; path != "" </span><span class="cov6" title="3">{ + <span class="cov7" title="3">closeOut := func() </span>{<span class="cov0" title="0">}</span> + <span class="cov7" title="3">if path := infile; path != "" </span><span class="cov7" title="3">{ f, err := os.Open(path) - if err != nil </span><span class="cov0" title="0">{ return nil, nil, func()</span>{<span class="cov0" title="0">}</span>, func(){<span class="cov0" title="0">}</span>, fmt.Errorf("hexai-action: cannot open infile: %w", err) } - <span class="cov6" title="3">in = f - closeIn = func() </span><span class="cov6" title="3">{ _ = f.Close() }</span> + if err != nil </span><span class="cov0" title="0">{ return nil, nil, func()</span>{<span class="cov0" title="0">}</span>, func(){<span class="cov0" title="0">}</span>, fmt.Errorf("hexai-tmux-action: cannot open infile: %w", err) } + <span class="cov7" title="3">in = f + closeIn = func() </span><span class="cov7" title="3">{ _ = f.Close() }</span> } - <span class="cov6" title="3">if path := outfile; path != "" </span><span class="cov6" title="3">{ + <span class="cov7" title="3">if path := outfile; path != "" </span><span class="cov7" title="3">{ f, err := os.Create(path) - if err != nil </span><span class="cov0" title="0">{ return nil, nil, func()</span>{<span class="cov0" title="0">}</span>, func(){<span class="cov0" title="0">}</span>, fmt.Errorf("hexai-action: cannot open outfile: %w", err) } - <span class="cov6" title="3">out = f - closeOut = func() </span><span class="cov6" title="3">{ _ = f.Close() }</span> + if err != nil </span><span class="cov0" title="0">{ return nil, nil, func()</span>{<span class="cov0" title="0">}</span>, func(){<span class="cov0" title="0">}</span>, fmt.Errorf("hexai-tmux-action: cannot open outfile: %w", err) } + <span class="cov7" title="3">out = f + closeOut = func() </span><span class="cov7" title="3">{ _ = f.Close() }</span> } - <span class="cov6" title="3">return in, out, closeIn, closeOut, nil</span> + <span class="cov7" title="3">return in, out, closeIn, closeOut, nil</span> } // runChild runs the interactive flow and writes the final output atomically when outfile is set. -func runChild(ctx context.Context, infile, outfile string, stdout, stderr io.Writer) error <span class="cov6" title="3">{ +func runChild(ctx context.Context, infile, outfile string, stdout, stderr io.Writer) error <span class="cov7" title="3">{ if outfile == "" </span><span class="cov1" title="1">{ // No atomic handoff needed; just run normally to provided stdout var in io.Reader = os.Stdin @@ -1163,7 +1140,7 @@ func runChild(ctx context.Context, infile, outfile string, stdout, stderr io.Wri if err := runFn(ctx, in, out, stderr); err != nil </span><span class="cov0" title="0">{ closeOut() if copyErr := echoThrough(infile, tmp, os.Stdin, stdout); copyErr != nil </span><span class="cov0" title="0">{ - 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) }</span> } else<span class="cov4" title="2"> { closeOut() @@ -1171,35 +1148,35 @@ func runChild(ctx context.Context, infile, outfile string, stdout, stderr io.Wri <span class="cov4" title="2">return os.Rename(tmp, outfile)</span> } -func runInTmuxParent(stdin io.Reader, stdout io.Writer, target, split string, percent int) error <span class="cov7" title="4">{ - dir, err := os.MkdirTemp("", "hexai-action-") +func runInTmuxParent(stdin io.Reader, stdout io.Writer, target, split string, percent int) error <span class="cov8" title="4">{ + dir, err := os.MkdirTemp("", "hexai-tmux-action-") if err != nil </span><span class="cov0" title="0">{ return err }</span> - <span class="cov7" title="4">defer func() </span><span class="cov7" title="4">{ _ = os.RemoveAll(dir) }</span>() - <span class="cov7" title="4">inPath := filepath.Join(dir, "input.txt") + <span class="cov8" title="4">defer func() </span><span class="cov8" title="4">{ _ = os.RemoveAll(dir) }</span>() + <span class="cov8" title="4">inPath := filepath.Join(dir, "input.txt") outPath := filepath.Join(dir, "reply.txt") if err := persistStdin(inPath, stdin); err != nil </span><span class="cov0" title="0">{ return err }</span> - <span class="cov7" title="4">exe, err := osExecutableFn() + <span class="cov8" title="4">exe, err := osExecutableFn() if err != nil </span><span class="cov1" title="1">{ return err }</span> - <span class="cov6" title="3">argv := []string{exe, "-ui-child", "-infile", inPath, "-outfile", outPath} + <span class="cov7" title="3">argv := []string{exe, "-ui-child", "-infile", inPath, "-outfile", outPath} opts := tmux.SplitOpts{Target: target, Vertical: split != "h", Percent: percent} if err := splitRunFn(opts, argv); err != nil </span><span class="cov1" title="1">{ return err }</span> <span class="cov4" title="2">if err := waitForFile(outPath, 60*time.Second); err != nil </span><span class="cov0" title="0">{ return err }</span> <span class="cov4" title="2">return catFileTo(stdout, outPath)</span> } -func persistStdin(path string, stdin io.Reader) error <span class="cov8" title="5">{ +func persistStdin(path string, stdin io.Reader) error <span class="cov10" title="5">{ f, err := os.Create(path) if err != nil </span><span class="cov0" title="0">{ return err }</span> - <span class="cov8" title="5">defer func() </span><span class="cov8" title="5">{ _ = f.Close() }</span>() - <span class="cov8" title="5">if _, err := io.Copy(f, stdin); err != nil </span><span class="cov0" title="0">{ return err }</span> - <span class="cov8" title="5">return f.Sync()</span> + <span class="cov10" title="5">defer func() </span><span class="cov10" title="5">{ _ = f.Close() }</span>() + <span class="cov10" title="5">if _, err := io.Copy(f, stdin); err != nil </span><span class="cov0" title="0">{ return err }</span> + <span class="cov10" title="5">return f.Sync()</span> } -func waitForFile(path string, timeout time.Duration) error <span class="cov6" title="3">{ +func waitForFile(path string, timeout time.Duration) error <span class="cov7" title="3">{ deadline := time.Now().Add(timeout) - for </span><span class="cov7" title="4">{ + for </span><span class="cov8" title="4">{ if _, err := os.Stat(path); err == nil </span><span class="cov4" title="2">{ return nil }</span> - <span class="cov4" title="2">if time.Now().After(deadline) </span><span class="cov1" title="1">{ return fmt.Errorf("hexai-action: timeout waiting for reply file") }</span> + <span class="cov4" title="2">if time.Now().After(deadline) </span><span class="cov1" title="1">{ return fmt.Errorf("hexai-tmux-action: timeout waiting for reply file") }</span> <span class="cov1" title="1">time.Sleep(200 * time.Millisecond)</span> } } @@ -1212,7 +1189,8 @@ func catFileTo(w io.Writer, path string) error <span class="cov4" title="2">{ return err</span> } -func echoThrough(infile, outfile string, stdin io.Reader, stdout io.Writer) error <span class="cov6" title="3">{ +// 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 <span class="cov4" title="2">{ var in io.Reader = stdin var out io.Writer = stdout if infile != "" </span><span class="cov1" title="1">{ @@ -1221,13 +1199,13 @@ func echoThrough(infile, outfile string, stdin io.Reader, stdout io.Writer) erro <span class="cov1" title="1">defer func() </span><span class="cov1" title="1">{ _ = f.Close() }</span>() <span class="cov1" title="1">in = f</span> } - <span class="cov6" title="3">if outfile != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="2">if outfile != "" </span><span class="cov1" title="1">{ f, err := os.Create(outfile) if err != nil </span><span class="cov0" title="0">{ return err }</span> <span class="cov1" title="1">defer func() </span><span class="cov1" title="1">{ _ = f.Close() }</span>() <span class="cov1" title="1">out = f</span> } - <span class="cov6" title="3">_, err := io.Copy(out, in) + <span class="cov4" title="2">_, err := io.Copy(out, in) return err</span> } </pre> @@ -1410,26 +1388,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 <span class="cov6" title="3">{ - 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 </span><span class="cov1" title="1">{ - 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 }</span> <span class="cov4" title="2">parts, err := ParseInput(stdin) if err != nil </span><span class="cov0" title="0">{ - 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 }</span> <span class="cov4" title="2">if strings.TrimSpace(parts.Selection) == "" </span><span class="cov0" title="0">{ - return fmt.Errorf("hexai-action: no input provided on stdin") + return fmt.Errorf("hexai-tmux-action: no input provided on stdin") }</span> <span class="cov4" title="2">kind, err := chooseActionFn() if err != nil </span><span class="cov0" title="0">{ @@ -1450,7 +1428,7 @@ func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg a case ActionRewrite:<span class="cov4" title="2"> instr, cleaned := ExtractInstruction(parts.Selection) if strings.TrimSpace(instr) == "" </span><span class="cov0" title="0">{ - 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 }</span> <span class="cov4" title="2">cctx, cancel := timeout10s(ctx) diff --git a/docs/coverage.out b/docs/coverage.out index e1b4e9b..40ded60 100644 --- a/docs/coverage.out +++ b/docs/coverage.out @@ -1,16 +1,14 @@ mode: atomic -codeberg.org/snonux/hexai/cmd/hexai-action/main.go:12.13,28.110 11 0 -codeberg.org/snonux/hexai/cmd/hexai-action/main.go:28.110,31.6 2 0 codeberg.org/snonux/hexai/cmd/hexai/main.go:14.13,17.18 3 1 codeberg.org/snonux/hexai/cmd/hexai/main.go:17.18,20.3 2 1 codeberg.org/snonux/hexai/cmd/hexai/main.go:22.2,22.104 1 0 codeberg.org/snonux/hexai/cmd/hexai/main.go:22.104,24.3 1 0 -codeberg.org/snonux/hexai/cmd/hexai-action/main.go:12.13,28.110 11 0 -codeberg.org/snonux/hexai/cmd/hexai-action/main.go:28.110,31.6 2 0 codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go:13.13,17.18 4 0 codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go:17.18,20.3 2 0 codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go:22.2,22.79 1 0 codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go:22.79,24.3 1 0 +codeberg.org/snonux/hexai/cmd/hexai-tmux-action/main.go:12.13,25.110 9 0 +codeberg.org/snonux/hexai/cmd/hexai-tmux-action/main.go:25.110,28.6 2 0 codeberg.org/snonux/hexai/internal/appconfig/config.go:89.29,136.2 2 0 codeberg.org/snonux/hexai/internal/appconfig/config.go:140.35,142.19 2 0 codeberg.org/snonux/hexai/internal/appconfig/config.go:142.19,144.3 1 0 @@ -275,101 +273,88 @@ codeberg.org/snonux/hexai/internal/appconfig/config.go:818.61,821.3 2 0 codeberg.org/snonux/hexai/internal/appconfig/config.go:823.2,823.10 1 0 codeberg.org/snonux/hexai/internal/appconfig/config.go:823.10,825.3 1 0 codeberg.org/snonux/hexai/internal/appconfig/config.go:826.2,826.13 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:29.101,30.21 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:30.21,32.6 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:33.5,33.53 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:33.53,35.6 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:37.5,37.58 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:37.58,39.23 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:39.23,39.37 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:40.9,41.41 3 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:44.5,44.65 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:48.37,48.72 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:54.51,55.15 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:55.15,55.31 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:56.5,56.18 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:56.18,56.33 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:57.5,57.82 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:57.82,57.97 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:58.5,58.17 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:62.83,65.23 3 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:65.24,65.25 0 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:66.5,66.24 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:66.25,66.26 0 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:67.5,67.35 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:67.35,69.23 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:69.23,69.48 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:69.49,69.50 0 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:69.59,69.60 0 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:70.9,71.26 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:71.26,71.43 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:73.5,73.36 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:73.36,75.23 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:75.23,75.48 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:75.49,75.50 0 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:75.59,75.60 0 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:76.9,77.27 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:77.27,77.44 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:79.5,79.43 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:83.92,84.22 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:84.22,87.25 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:87.25,89.27 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:89.27,89.41 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:90.13,90.25 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:90.25,90.42 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:91.13,91.19 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:93.9,93.46 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:95.5,97.19 3 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:97.19,97.33 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:98.5,99.55 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:99.55,101.82 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:101.82,103.10 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:104.11,106.6 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:107.5,107.35 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:110.98,112.19 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:112.19,112.33 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:113.5,113.18 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:113.18,113.43 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:114.5,116.55 3 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:116.55,116.69 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:117.5,118.19 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:118.19,118.33 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:119.5,121.50 3 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:121.50,121.64 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:122.5,122.64 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:122.64,122.78 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:123.5,123.38 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:126.55,128.19 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:128.19,128.33 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:129.5,129.18 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:129.18,129.35 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:130.5,130.48 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:130.48,130.62 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:131.5,131.20 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:134.60,136.9 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:136.9,137.48 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:137.48,137.62 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:138.9,138.39 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:138.39,138.108 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:139.9,139.43 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:143.48,145.19 2 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:145.19,145.33 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:146.5,146.18 1 0 -codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:146.18,146.35 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:27.101,28.21 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:28.21,30.6 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:32.5,32.93 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:36.37,36.72 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:42.83,45.23 3 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:45.24,45.25 0 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:46.5,46.24 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:46.25,46.26 0 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:47.5,47.35 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:47.35,49.23 2 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:49.23,49.48 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:49.49,49.50 0 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:49.59,49.60 0 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:50.9,51.26 2 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:51.26,51.43 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:53.5,53.36 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:53.36,55.23 2 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:55.23,55.48 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:55.49,55.50 0 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:55.59,55.60 0 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:56.9,57.27 2 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:57.27,57.44 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:59.5,59.43 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:63.92,64.22 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:64.22,67.25 2 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:67.25,69.27 2 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:69.27,69.41 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:70.13,70.25 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:70.25,70.42 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:71.13,71.19 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:73.9,73.46 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:75.5,77.19 3 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:77.19,77.33 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:78.5,79.55 2 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:79.55,81.82 2 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:81.82,83.10 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:84.11,86.6 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:87.5,87.35 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:90.98,92.19 2 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:92.19,92.33 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:93.5,93.18 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:93.18,93.43 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:94.5,96.55 3 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:96.55,96.69 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:97.5,98.19 2 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:98.19,98.33 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:99.5,101.50 3 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:101.50,101.64 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:102.5,102.64 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:102.64,102.78 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:103.5,103.38 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:106.55,108.19 2 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:108.19,108.33 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:109.5,109.18 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:109.18,109.35 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:110.5,110.48 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:110.48,110.62 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:111.5,111.20 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:114.60,116.9 2 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:116.9,117.48 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:117.48,117.62 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:118.9,118.39 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:118.39,118.113 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:119.9,119.43 1 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:123.48,125.19 2 0 +codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go:12 |
