From 8889949ad3851bfbf36ff5b73128286d67c88201 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sun, 7 Sep 2025 11:26:10 +0300 Subject: tiding up --- docs/configuration.md | 33 +- docs/coverage.html | 513 +++- docs/coverage.out | 7749 +++++++++++++++++++++++++++++++++++++++---------- docs/usage.md | 8 +- 4 files changed, 6715 insertions(+), 1588 deletions(-) (limited to 'docs') diff --git a/docs/configuration.md b/docs/configuration.md index 3fbb1dc..dc4adbd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -30,23 +30,30 @@ API keys: Selecting a provider - Sectioned: set `[provider] name = "openai" | "copilot" | "ollama"`. -- Flat: set `provider = "openai" | "copilot" | "ollama"`. - If omitted, Hexai defaults to `openai`. -Provider-specific options - -- See [config.toml.example](../config.toml.example) for the per-provider tables and defaults. - -Notes: +Notes on Ollama: - Ensure the model is available locally (e.g., `ollama pull qwen3-coder:30b-a3b-q4_K_M`). - Alternatively, run Ollama in OpenAI‑compatible mode and use the OpenAI provider with `openai_base_url` pointed at your local endpoint. -LSP completion tuning - -- See the [completion] section in [config.toml.example](../config.toml.example). - -Temperature behavior - -- Defaults and recommended ranges are commented inline in [config.toml.example](../config.toml.example) under [general] and provider tables. +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"` +- 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. + - 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. + - `--outfile` Write output to the given file instead of stdout (truncates/creates). + - `--tmux` force tmux-pane mode. + - `--no-tmux` disable tmux mode even if available. + - `--tmux-target` tmux target pane/window (advanced). + - `--tmux-split v|h` split orientation (default: `v`). + - `--tmux-percent N` split size percentage (default: `33`). + - `--ui-child` internal; used by the parent process when spawning inside tmux. diff --git a/docs/coverage.html b/docs/coverage.html index 6b80630..2003a0d 100644 --- a/docs/coverage.html +++ b/docs/coverage.html @@ -59,19 +59,19 @@ - + - + - + - + - + @@ -119,6 +119,8 @@ + +
@@ -198,18 +200,240 @@ func main() { import ( "context" + "flag" "fmt" + "io" "os" + "path/filepath" + "time" "codeberg.org/snonux/hexai/internal/hexaiaction" + "codeberg.org/snonux/hexai/internal/tmux" + "golang.org/x/term" ) func main() { - if err := hexaiaction.Run(context.Background(), os.Stdin, os.Stdout, os.Stderr); err != nil { + infile := flag.String("infile", "", "Read input from this file instead of stdin") + outfile := flag.String("outfile", "", "Write output to this file instead of stdout") + // Tmux/UI flags + 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") + tmuxPercent := flag.Int("tmux-percent", 33, "tmux split size percentage (1-100)") + flag.Parse() + + // Child mode: run TUI and write atomically to -outfile + if *uiChild { + if err := runChild(*infile, *outfile); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + return + } + + // Parent mode: decide inline vs tmux + if shouldRunInTmux(*forceTmux, *noTmux) { + if err := runInTmuxParent(*tmuxTarget, *tmuxSplit, *tmuxPercent); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + return + } + + // Inline path: only if we have a TTY for UI; otherwise echo input + if isTTY(os.Stdout.Fd()) && isTTY(os.Stdin.Fd()) { + in, out, closeIn, closeOut, err := openIO(*infile, *outfile) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + defer closeIn() + defer closeOut() + if err := hexaiactionRun(context.Background(), in, out, os.Stderr); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + return + } + + // Fallback: no TTY and tmux not available; echo input to output + if err := echoThrough(*infile, *outfile); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } + +// 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) + out := io.Writer(os.Stdout) + closeIn := func() {} + 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) + } + 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) + } + out = f + closeOut = func() { _ = f.Close() } + } + return in, out, closeIn, closeOut, nil +} + +// runChild runs the interactive flow and writes the final output atomically to outfile. +var hexaiactionRun = hexaiaction.Run + +func runChild(infile, outfile string) error { + if outfile == "" { + // No atomic handoff needed; just run normally to stdout + in, out, closeIn, closeOut, err := openIO(infile, "") + if err != nil { + return err + } + defer closeIn() + defer closeOut() + return hexaiactionRun(context.Background(), in, out, os.Stderr) + } + tmp := outfile + ".tmp" + in, out, closeIn, closeOut, err := openIO(infile, tmp) + if err != nil { + return err + } + defer closeIn() + if err := hexaiactionRun(context.Background(), in, out, os.Stderr); err != nil { + // On error, try to echo input to tmp to avoid blocking + closeOut() + if copyErr := echoThrough(infile, tmp); copyErr != nil { + return fmt.Errorf("hexai-action child: %v; echo failed: %v", err, copyErr) + } + } else { + closeOut() + } + return os.Rename(tmp, outfile) +} + +var isTTYFn = isTTY +var tmuxAvailableFn = tmux.Available +var splitRunFn = tmux.SplitRun +var osExecutableFn = os.Executable + +func shouldRunInTmux(forceTmux, noTmux bool) bool { + if noTmux { + return false + } + if forceTmux { + return true + } + // Auto: prefer tmux when stdio are not TTYs (Helix :pipe scenario) + if !(isTTYFn(os.Stdin.Fd()) && isTTYFn(os.Stdout.Fd())) && tmuxAvailableFn() { + return true + } + return false +} + +func isTTY(fd uintptr) bool { return term.IsTerminal(int(fd)) } + +func runInTmuxParent(target, split string, percent int) error { + // Prepare temp files + dir, err := os.MkdirTemp("", "hexai-action-") + if err != nil { + return err + } + defer func() { _ = os.RemoveAll(dir) }() + inPath := filepath.Join(dir, "input.txt") + outPath := filepath.Join(dir, "reply.txt") + // Read stdin and persist to inPath + if err := persistStdin(inPath); err != nil { + return err + } + // Build child argv + exe, err := osExecutableFn() + if err != nil { + return err + } + argv := []string{exe, "-ui-child", "-infile", inPath, "-outfile", outPath} + // Spawn tmux split + opts := tmux.SplitOpts{Target: target, Vertical: split != "h", Percent: percent} + if err := splitRunFn(opts, argv); err != nil { + return err + } + // Wait for outfile to appear + if err := waitForFile(outPath, 60*time.Second); err != nil { + return err + } + // Print to stdout + return catFileToStdout(outPath) +} + +func persistStdin(path string) error { + f, err := os.Create(path) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + if _, err := io.Copy(f, os.Stdin); err != nil { + return err + } + return f.Sync() +} + +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") + } + time.Sleep(200 * time.Millisecond) + } +} + +func catFileToStdout(path string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + _, err = io.Copy(os.Stdout, f) + return err +} + +func echoThrough(infile, outfile string) error { + // Read from infile or stdin and write to outfile or stdout + var in io.Reader = os.Stdin + var out io.Writer = os.Stdout + if infile != "" { + f, err := os.Open(infile) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + in = f + } + if outfile != "" { + f, err := os.Create(outfile) + if err != nil { + return err + } + defer func() { _ = f.Close() }() + out = f + } + _, err := io.Copy(out, in) + return err +} @@ -2736,14 +2960,14 @@ type Config struct { // NewFromConfig creates an LLM client using only the supplied configuration. // The OpenAI API key is supplied separately and may be read from the environment // by the caller; other environment-based configuration is not used. -func NewFromConfig(cfg Config, openAIAPIKey, copilotAPIKey string) (Client, error) { +func NewFromConfig(cfg Config, openAIAPIKey, copilotAPIKey string) (Client, error) { p := strings.ToLower(strings.TrimSpace(cfg.Provider)) - if p == "" { + if p == "" { p = "openai" } - switch p { - case "openai": - if strings.TrimSpace(openAIAPIKey) == "" { + switch p { + case "openai": + if strings.TrimSpace(openAIAPIKey) == "" { return nil, errors.New("missing OPENAI_API_KEY for provider openai") } // Set coding-friendly default temperature if none provided @@ -2792,7 +3016,7 @@ import ( ) // NewClientFromApp builds an llm.Client using app config and environment keys. -func NewClientFromApp(cfg appconfig.App) (llm.Client, error) { +func NewClientFromApp(cfg appconfig.App) (llm.Client, error) { llmCfg := llm.Config{ Provider: cfg.Provider, OpenAIBaseURL: cfg.OpenAIBaseURL, @@ -2806,14 +3030,14 @@ func NewClientFromApp(cfg appconfig.App) (llm.Client, error) { + if strings.TrimSpace(oaKey) == "" { oaKey = os.Getenv("OPENAI_API_KEY") } - cpKey := os.Getenv("HEXAI_COPILOT_API_KEY") - if strings.TrimSpace(cpKey) == "" { + cpKey := os.Getenv("HEXAI_COPILOT_API_KEY") + if strings.TrimSpace(cpKey) == "" { cpKey = os.Getenv("COPILOT_API_KEY") } - return llm.NewFromConfig(llmCfg, oaKey, cpKey) + return llm.NewFromConfig(llmCfg, oaKey, cpKey) } @@ -5752,26 +5976,26 @@ func RenderTemplate(t string, vars map[string]string) string { +func StripCodeFences(s string) string { t := strings.TrimSpace(s) if t == "" { return t } - lines := strings.Split(t, "\n") + lines := strings.Split(t, "\n") start := 0 for start < len(lines) && strings.TrimSpace(lines[start]) == "" { start++ } - end := len(lines) - 1 + end := len(lines) - 1 for end >= 0 && strings.TrimSpace(lines[end]) == "" { end-- } - if start >= len(lines) || end < 0 || start > end { + if start >= len(lines) || end < 0 || start > end { return t } - first := strings.TrimSpace(lines[start]) + first := strings.TrimSpace(lines[start]) last := strings.TrimSpace(lines[end]) - if strings.HasPrefix(first, "```") && last == "```" && end > start { + if strings.HasPrefix(first, "```") && last == "```" && end > start { inner := strings.Join(lines[start+1:end], "\n") return inner } @@ -5849,6 +6073,93 @@ func FindStrictInlineTag(line string) (text string, left, right int, ok bool) return "", -1, -1, false } + + +
diff --git a/docs/coverage.out b/docs/coverage.out index 36d1f38..59e9205 100644 --- a/docs/coverage.out +++ b/docs/coverage.out @@ -1,6 +1,4 @@ mode: atomic -codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:11.13,12.94 1 0 -codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:12.94,15.3 2 0 codeberg.org/snonux/hexai/cmd/hexai/main.go:14.13,17.18 3 0 codeberg.org/snonux/hexai/cmd/hexai/main.go:17.18,20.3 2 0 codeberg.org/snonux/hexai/cmd/hexai/main.go:22.2,22.104 1 0 @@ -9,8 +7,104 @@ codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go:13.13,17.18 4 1 codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go:17.18,20.3 2 1 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/internal/hexai-action/main.go:11.13,12.94 1 0 -codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:12.94,15.3 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:17.13,30.14 10 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:30.14,31.53 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:31.53,34.4 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:35.3,35.9 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:39.2,39.42 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:39.42,40.80 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:40.80,43.4 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:44.3,44.9 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:48.2,48.51 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:48.51,50.17 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:50.17,53.4 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:54.3,56.88 3 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:56.88,59.10 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:60.9,60.15 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:64.2,64.55 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:64.55,67.3 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:71.83,74.20 3 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:74.21,74.22 0 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:75.2,75.21 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:75.22,75.23 0 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:77.2,77.32 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:77.32,79.17 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:79.17,80.28 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:80.29,80.30 0 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:80.40,80.41 0 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:82.3,83.20 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:83.20,83.37 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:85.2,85.33 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:85.33,87.17 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:87.17,88.28 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:88.29,88.30 0 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:88.40,88.41 0 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:90.3,91.21 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:91.21,91.38 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:93.2,93.40 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:99.45,100.19 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:100.19,103.17 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:103.17,105.4 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:106.3,108.72 3 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:110.2,112.16 3 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:112.16,114.3 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:115.2,116.84 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:116.84,119.64 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:119.64,121.10 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:122.8,124.3 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:125.2,125.32 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:133.51,134.12 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:134.12,136.3 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:137.2,137.15 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:137.15,139.3 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:141.5,141.82 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:141.82,143.6 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:144.2,144.14 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:147.29,147.64 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:149.63,152.16 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:152.16,154.3 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:155.2,155.15 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:155.15,155.40 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:156.2,159.45 3 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:159.45,161.3 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:163.2,164.16 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:164.16,166.3 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:167.2,170.47 3 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:170.47,172.3 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:174.2,174.61 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:174.61,176.3 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:178.2,178.33 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:181.38,183.16 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:183.16,185.3 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:186.2,186.15 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:186.15,186.32 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:187.2,187.48 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:187.48,189.3 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:190.2,190.17 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:193.60,195.6 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:195.6,196.42 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:196.42,198.4 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:199.3,199.33 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:199.33,201.4 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:202.3,202.37 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:206.41,208.16 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:208.16,210.3 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:211.2,211.15 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:211.15,211.32 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:212.2,213.12 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:216.48,220.18 3 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:220.18,222.17 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:222.17,224.4 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:225.3,225.16 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:225.16,225.33 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:226.3,226.9 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:228.2,228.19 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:228.19,230.17 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:230.17,232.4 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:233.3,233.16 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:233.16,233.33 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:234.3,234.10 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:236.2,237.12 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 @@ -1617,319 +1711,3914 @@ codeberg.org/snonux/hexai/internal/textutil/textutil.go:106.49,106.59 1 0 codeberg.org/snonux/hexai/internal/textutil/textutil.go:107.17,108.32 2 0 codeberg.org/snonux/hexai/internal/textutil/textutil.go:108.32,108.64 1 0 codeberg.org/snonux/hexai/internal/textutil/textutil.go:112.5,112.29 1 0 -codeberg.org/snonux/hexai/cmd/hexai/main.go:14.13,17.18 3 0 -codeberg.org/snonux/hexai/cmd/hexai/main.go:17.18,20.3 2 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:11.23,11.60 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:17.23,17.72 2 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:20.23,20.76 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:31.52,32.20 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:32.20,34.3 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:35.2,36.19 2 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:36.19,38.3 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:38.8,40.3 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:41.2,41.45 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:41.45,43.3 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:44.2,44.42 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:44.42,46.3 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:48.2,51.19 4 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:56.38,58.25 2 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:58.25,59.14 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:59.14,61.12 2 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:63.3,63.20 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:63.20,65.12 2 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:69.3,70.33 2 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:72.2,72.31 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:76.32,77.30 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:77.30,79.141 2 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:79.141,80.12 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:82.3,82.15 1 0 +codeberg.org/snonux/hexai/internal/tmux/tmux.go:84.2,84.13 1 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-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/internal/hexai-action/main.go:11.13,12.94 1 0 -codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:12.94,15.3 2 0 -codeberg.org/snonux/hexai/internal/appconfig/config.go:89.29,136.2 2 7 -codeberg.org/snonux/hexai/internal/appconfig/config.go:140.35,142.19 2 6 -codeberg.org/snonux/hexai/internal/appconfig/config.go:142.19,144.3 1 1 -codeberg.org/snonux/hexai/internal/appconfig/config.go:146.2,147.16 2 5 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:17.13,30.14 10 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:30.14,31.53 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:31.53,34.4 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:35.3,35.9 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:39.2,39.42 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:39.42,40.80 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:40.80,43.4 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:44.3,44.9 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:48.2,48.51 1 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:48.51,50.17 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:50.17,53.4 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:54.3,56.88 3 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:56.88,59.10 2 0 +codeberg.org/snonux/hexai/cmd/internal/hexai-action/main.go:60.9,60.15 1 0 +codeberg.org/snonux