summaryrefslogtreecommitdiff
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
parent76f388f9759cdc15cb1eba985cd87cde1906208b (diff)
feat: rename hexai-action -> hexai-tmux-action; remove --tmux/--no-tmux; tmux-only flow; update docs and Magefile
-rw-r--r--Magefile.go26
-rw-r--r--PROJECTSTATUS.md2
-rw-r--r--README.md9
-rw-r--r--TODO.md35
-rw-r--r--cmd/hexai-tmux-action/main.go (renamed from cmd/hexai-action/main.go)5
-rw-r--r--docs/configuration.md6
-rw-r--r--docs/go-unit-tests.md27
-rw-r--r--docs/source-structure.md79
-rw-r--r--docs/testing.md30
-rw-r--r--docs/usage.md8
-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
15 files changed, 68 insertions, 259 deletions
diff --git a/Magefile.go b/Magefile.go
index 16ff7c0..5c5be0b 100644
--- a/Magefile.go
+++ b/Magefile.go
@@ -17,16 +17,16 @@ import (
)
var (
- Default = Build // Default target: build all binaries.
+ Default = Build // Default target: build all binaries.
coverageThreshold float64 = 85
coveragePrinted = make(chan struct{}, 1)
)
-// Build builds the Hexai LSP and CLI binaries.
+// Build builds binaries.
func Build() error {
- mg.Deps(BuildHexaiLSP, BuildHexaiCLI, BuildHexaiAction)
- printCoverage()
- return nil
+ mg.Deps(BuildHexaiLSP, BuildHexaiCLI, BuildHexaiTmuxAction)
+ printCoverage()
+ return nil
}
// BuildHexaiLSP builds the LSP server binary.
@@ -41,10 +41,10 @@ func BuildHexaiCLI() error {
return sh.RunV("go", "build", "-o", "hexai", "cmd/hexai/main.go")
}
-// BuildHexaiAction builds the hexai-action TUI binary.
-func BuildHexaiAction() error {
+// BuildHexaiTmuxAction builds the hexai-tmux-action TUI binary.
+func BuildHexaiTmuxAction() error {
printCoverage()
- return sh.RunV("go", "build", "-o", "hexai-action", "cmd/hexai-action/main.go")
+ return sh.RunV("go", "build", "-o", "hexai-tmux-action", "cmd/hexai-tmux-action/main.go")
}
// Dev runs tests, vet, lint, then builds with race for both binaries.
@@ -57,7 +57,7 @@ func Dev() error {
if err := sh.RunV("go", "build", "-race", "-o", "hexai", "cmd/hexai/main.go"); err != nil {
return err
}
- return sh.RunV("go", "build", "-race", "-o", "hexai-action", "cmd/hexai-action/main.go")
+ return sh.RunV("go", "build", "-race", "-o", "hexai-tmux-action", "cmd/hexai-tmux-action/main.go")
}
// Run launches the LSP server via go run (useful during development).
@@ -97,14 +97,14 @@ func Install() error {
if err := sh.RunV("cp", "-v", "./hexai", bin+"/"); err != nil {
return err
}
- return sh.RunV("cp", "-v", "./hexai-action", bin+"/")
+ return sh.RunV("cp", "-v", "./hexai-tmux-action", bin+"/")
}
-// RunAction runs the hexai-action TUI via go run (reads stdin).
-func RunAction() error {
+// RunTmuxAction runs the hexai-tmux-action TUI via go run (reads stdin).
+func RunTmuxAction() error {
printCoverage()
mg.Deps(Dev)
- return sh.RunV("go", "run", "cmd/hexai-action/main.go")
+ return sh.RunV("go", "run", "cmd/hexai-tmux-action/main.go")
}
// printCoverage prints a warning if an existing coverage profile shows total < coverateThreshold.
diff --git a/PROJECTSTATUS.md b/PROJECTSTATUS.md
index 18b0278..a5687fa 100644
--- a/PROJECTSTATUS.md
+++ b/PROJECTSTATUS.md
@@ -15,7 +15,7 @@ Or maybe
```
[keys.normal]
-C-p = ":sh hexai-action"
+C-p = ":sh hexai-tmux-action"
```
And then generate a menu with all the code actions hexai-lsp knows of and include hotkeys for each menu item! Also print out a notice that this is a work-around due to limitations in Helix's current LSP UI.
diff --git a/README.md b/README.md
index be68bec..dc5b4cf 100644
--- a/README.md
+++ b/README.md
@@ -12,21 +12,20 @@ It has got improved capabilities for Go code understanding (for example, create
* LSP Code actions
* LSP in-editor chat with the LLM
* Stand-alone command line tool for LLM interaction
-* TUI code-action runner (`hexai-action`) with Bubble Tea
+* TUI code-action runner (`hexai-tmux-action`) with Bubble Tea
* Support for OpenAI, GitHub Copilot, and Ollama
## Documentation
* [Configuration guide](docs/configuration.md)
* [Usage examples](docs/usage.md)
-* [Source structure](docs/source-structure.md)
## Build and tasks
Hexai uses Mage for developer tasks. Install Mage, then run targets like build, dev, test, and install.
- Install Mage: `go install github.com/magefile/mage@latest`
-- Build binaries: `mage build` (produces `hexai`, `hexai-lsp`, and `hexai-action`)
+- Build binaries: `mage build` (produces `hexai`, `hexai-lsp`, and `hexai-tmux-action`)
- Dev build (+ tests, vet, lint): `mage dev`
- Run tests: `mage test`
- Run tests with coverage: `go test ./... -cover`
@@ -43,5 +42,5 @@ Either use the Mage method as mentioned above, or install directly with:
- CLI: `go install codeberg.org/snonux/hexai/cmd/hexai@latest`
- LSP: `go install codeberg.org/snonux/hexai/cmd/hexai-lsp@latest`
-- Action runner: `go install codeberg.org/snonux/hexai/cmd/hexai-action@latest`
-Install: `mage install` (copies `hexai-action` to `GOPATH/bin` together with other binaries)
+- Action runner: `go install codeberg.org/snonux/hexai/cmd/hexai-tmux-action@latest`
+Install: `mage install` (copies `hexai-tmux-action` to `GOPATH/bin` together with other binaries)
diff --git a/TODO.md b/TODO.md
index 7f3a839..276009f 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,13 +1,13 @@
-Comprehensive plan: integrate Helix + tmux flow into hexai-action
+Comprehensive plan: integrate Helix + tmux flow into hexai-tmux-action
Summary of current setup
-- Helix keybinding pipes selection to `ai`, which dispatches to `hx.hexai-action-prompt` for hexai-action mode.
-- `hx.hexai-action-prompt` writes stdin to `~/.hx-action-input`, opens a tmux split-pane, runs `hexai-action -infile <input> -outfile <reply>.tmp`, then atomically renames to `<reply>` and prints the reply back to Helix.
+- 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-action directly: `:pipe hexai-action`.
-- hexai-action itself handles: reading stdin; presenting TUI in a tmux pane (when needed); executing the chosen action; printing the result to stdout for Helix to apply.
+- 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
@@ -28,7 +28,7 @@ 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-action -ui-child -infile <in> -outfile <out>.tmp`, wait for completion (by process exit and/or file rename), then print `<out>` to stdout.
+ - 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`.
@@ -47,7 +47,7 @@ High-level design
- 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-action-*" )` under `$TMPDIR`.
+ - Avoid writing to `~/.hx-*`; use `os.CreateTemp("", "hexai-tmux-action-*" )` under `$TMPDIR`.
4) hexaiaction refactor (internal package)
- Separate concerns to keep functions small/testable:
@@ -61,15 +61,14 @@ High-level design
- 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-action` prefixes; keep stdout clean for Helix’s `:pipe`.
+ - 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-action"`
-- Optional: users can force tmux pane with `:pipe hexai-action -tmux` or disable with `:pipe hexai-action -no-tmux` if auto-detection does not fit their setup.
+ - `C-a = ":pipe hexai-tmux-action"`
Migration plan
-1) Implement tmux package and integrate auto-mode in `cmd/hexai-action/main.go`.
+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.
@@ -78,7 +77,7 @@ Migration plan
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 parent decision logic (TTY vs pipe; tmux available vs not) using injectable detectors.
+ - `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.
@@ -93,7 +92,7 @@ Edge cases and mitigations
Implementation steps (incremental)
1) Add `internal/tmux` with `Available`, `SplitRun`, and helpers.
-2) Add TTY detection helper to `internal/hexaiaction` and wire flags (`-tmux`, `-no-tmux`, `-ui-child`).
+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.
@@ -101,22 +100,22 @@ Implementation steps (incremental)
Notes on code organization
- Place all tmux-related code under `internal/tmux` and keep functions well under 50 lines.
-- Keep command entrypoint (`cmd/hexai-action/main.go`) small and focused on wiring/mode selection.
+- 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-action`).
+- 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 `hexai-action`: `-tmux`, `-no-tmux`, `-ui-child`, `-tmux-target`, `-tmux-split`, `-tmux-percent`.
+- [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 decision logic in `hexai-action` (validate locally; target ≥85% coverage for new code).
+- [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 `hexai-action` entrypoint package sits ~69% overall; newly added helper paths are covered (openIO, runChild, runInTmuxParent, echoThrough, waitForFile, etc.). The inline TTY UI path and `main()` remain intentionally untested.
+ - 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/cmd/hexai-action/main.go b/cmd/hexai-tmux-action/main.go
index b796cbd..02cfe09 100644
--- a/cmd/hexai-action/main.go
+++ b/cmd/hexai-tmux-action/main.go
@@ -12,8 +12,6 @@ import (
func main() {
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")
@@ -22,8 +20,7 @@ func main() {
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 {
fmt.Fprintln(os.Stderr, err)
diff --git a/docs/configuration.md b/docs/configuration.md
index dc4adbd..09ff519 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -42,11 +42,11 @@ Hexai Action (TUI) configuration
This is mostly useful when Helix runs in a [tmux](https://tmux.github.io/) session!
-- Helix integration (recommended): bind a key to pipe the current selection to `hexai-action` and replace it with the output.
- - Example: `C-a = ":pipe hexai-action"`
+- Helix integration (recommended): bind a key to pipe the current selection to `hexai-tmux-action` and replace it with the output.
+ - Example: `C-a = ":pipe hexai-tmux-action"`
- Default behavior:
- Inline TUI when run in a real terminal (TTY).
- - When invoked via Helix `:pipe` and a tmux session is available, `hexai-action` opens a split pane to render the menu and returns the result on stdout for Helix to apply.
+ - When invoked via Helix `:pipe`, `hexai-tmux-action` opens a split pane to render the menu and returns the result on stdout for Helix to apply.
- If no TTY and no tmux are available, it falls back to echoing the input.
- Flags:
- `--infile` Read input from the given file instead of stdin.
diff --git a/docs/go-unit-tests.md b/docs/go-unit-tests.md
deleted file mode 100644
index 1be9fd0..0000000
--- a/docs/go-unit-tests.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Go unit tests via code action
-
-Hexai can generate Go unit tests for the function at your cursor.
-
-- Scope: Available only for Go source files ending with `.go` (not `_test.go`).
-- Trigger: Use your editor's code actions on the current selection/position and pick "Implement unit test".
-
-What happens
-
-- Function detection: Hexai finds the nearest `func` definition above the cursor and captures the function body by balancing braces.
-- Test generation:
- - If an LLM provider is configured, Hexai asks it to generate one or more `Test*` functions using the `testing` package. The provider must return only the test function code (no package/import lines).
- - If no provider is configured or the request fails, Hexai inserts a small stub test `Test<Name>` with a TODO.
-- File handling and navigation:
- - If `<file>_test.go` exists, the test function is appended to the end of that file.
- - If it does not exist, Hexai creates it, writing `package <pkg>` (inferred from the source file) and `import "testing"`, followed by the generated test function(s).
- - After applying the edit, Hexai asks the editor to focus the test file and place the cursor at the start of the newly added test function.
-
-Notes and limitations
-
-- Imports on append: when appending to an existing test file, Hexai assumes `testing` is available. If not, add `import "testing"` to the test file and re-run `go test`.
-- Method names: for methods with receivers, test names default to `TestMethod` (stub fallback). Future improvement may generate `TestType_Method` automatically.
-- Formatting: run `go fmt ./...` or your editor's formatter to normalize whitespace if needed.
-
-Examples
-
-In Helix, position the cursor inside a function and invoke code actions; choose "Implement unit test". Hexai will create or update `<file>_test.go` accordingly.
diff --git a/docs/source-structure.md b/docs/source-structure.md
deleted file mode 100644
index 0c7f56f..0000000
--- a/docs/source-structure.md
+++ /dev/null
@@ -1,79 +0,0 @@
-# Source code structure
-
-This document provides a high‑level map of the Hexai source layout and how the
-main packages relate to each other.
-
-## Diagram
-
-```mermaid
-graph TD
- %% Entrypoints
- A[cmd/hexai\nCLI entrypoint] --> B[internal/hexaicli\nCLI runner]
- C[cmd/hexai-lsp\nLSP entrypoint] --> D[internal/hexailsp\nLSP runner]
-
- %% Shared/internal packages
- subgraph internal/
- I[appconfig\nLoad config from file/env]
- L[llm\nClient + providers]
- G[logging\nBound logger + helpers]
- S[lsp\nJSON-RPC, server, handlers]
- end
-
- %% Relationships
- B --> I
- B --> L
- B --> G
-
- D --> I
- D --> L
- D --> G
- D --> S
-
- S --> L
- S --> G
-
- %% LLM providers
- subgraph internal/llm
- P1[openai.go]
- P2[ollama.go]
- P3[copilot.go]
- end
- L --> P1
- L --> P2
- L --> P3
-
- %% Version info
- V[internal/version.go\nVersion string] --> A
- V --> C
-```
-
-## Module overview
-
-- cmd/hexai: CLI binary that parses flags, prints version via `internal.Version`,
- and delegates to `internal/hexaicli.Run`.
-- cmd/hexai-lsp: LSP server binary that parses flags, prints version, and calls
- `internal/hexailsp.Run` (stdio JSON‑RPC server).
-- internal/hexaicli: CLI flow — reads stdin/args, loads config, builds an LLM
- client, constructs messages, and runs a single chat request (streaming when
- supported).
-- internal/hexailsp: LSP orchestration — binds logging, loads config, builds the
- LLM client, constructs `internal/lsp.ServerOptions`, and runs the server.
-- internal/lsp: Minimal LSP over stdio — document store, JSON‑RPC handlers
- (initialize, completion, code action, etc.), context building, and a small
- completion cache.
-- internal/llm: Provider‑agnostic client interface plus concrete providers for
- OpenAI, GitHub Copilot, and Ollama, including streaming support where
- available.
-- internal/appconfig: Loads user configuration from file and environment, shared
- by both CLI and LSP paths.
-- internal/logging: Central logger binding and small helpers for consistent,
- readable logs and chat summaries.
-- internal/version.go: Single place for the version string used by both
- binaries.
-
-## Typical flows
-
-- CLI: `cmd/hexai` → `internal/hexaicli` → `internal/appconfig` → `internal/llm`
- (providers) → print output and a short summary line.
-- LSP: `cmd/hexai-lsp` → `internal/hexailsp` → `internal/lsp.Server` →
- request handlers → `internal/llm` for completions/actions.
diff --git a/docs/testing.md b/docs/testing.md
deleted file mode 100644
index 86d88b1..0000000
--- a/docs/testing.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Testing Guide
-
-This repository includes a growing test suite designed to be realistic and robust.
-
-Key patterns:
-
-- Table‑driven tests: consolidate repetitive scenarios into concise tables (see `internal/lsp/*_table_test.go`).
-- Shared fixtures: use `internal/testutil/fixtures.go` for multi‑line docblocks, chat replies, function suggestions, and markdown fences.
-- Provider mocks: use `httptest.Server` and/or custom `http.RoundTripper` to simulate OpenAI/Copilot/Ollama responses, including success, stream (SSE), and error cases.
-- E2E LSP tests: capture JSON‑RPC frames from the in‑memory server (`captureResponse`, `captureRequest`) and validate code actions, resolves, and chat edits.
-
-Suggested additions:
-
-- Expand table‑driven coverage for completion edit computations and label/filter selection.
-- Add more negative tests (malformed SSE/JSON payloads) to assert robust error handling.
-
-## Running Tests
-
-- Full suite with coverage:
- - `HEXAI_TEST_SKIP_NET=1 go test ./... -cover`
- - The `HEXAI_TEST_SKIP_NET=1` env var disables any tests that require network access, keeping runs deterministic in CI/sandboxes.
-
-- Package-specific runs:
- - `HEXAI_TEST_SKIP_NET=1 go test ./internal/hexaiaction -cover`
- - `HEXAI_TEST_SKIP_NET=1 go test ./internal/hexaiaction -cover`
-
-Notes
-
-- Some environments restrict writes to the Go build cache; if you see cache permission errors, re-run in a less-restricted shell or allow the command to write to the cache.
-- Always format Go code before committing: `gofumpt -w .`
diff --git a/docs/usage.md b/docs/usage.md
index 05d6c55..fb65596 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -105,7 +105,7 @@ hexai 'install ripgrep on macOS and explain'
## Hexai Action (TUI)
-`hexai-action` runs code actions over a selection or diagnostics+selection piped from stdin, or read from a file.
+`hexai-tmux-action` runs code actions over a selection or diagnostics+selection piped from stdin, or read from a file.
- Choose an action with arrow keys, `j/k`, `g/G`, Enter, or hotkeys `[s] [r] [c] [t]`.
- Output is written to stdout by default, or to a file via `--outfile`.
@@ -122,11 +122,11 @@ Examples
```sh
# From stdin
-cat input.go | hexai-action
+cat input.go | hexai-tmux-action
# From file to file
-hexai-action --infile input.go --outfile output.go
+hexai-tmux-action --infile input.go --outfile output.go
# Using shell redirection
-hexai-action < input.go > output.go
+hexai-tmux-action < input.go > output.go
```
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