summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-08 12:02:40 +0300
committerPaul Buetow <paul@buetow.org>2025-09-08 12:02:40 +0300
commit75cf6abd55bfb60324fc47cf91eac08dbb8b87b4 (patch)
tree6ef90d8014fe4d9a757d3f7e95bf736b70e4c685
parent0dcf347c3fbc6e4ffb7e46294f5dd92dbbcd98ef (diff)
docs: move tmux documentation to its own file
-rw-r--r--Magefile.go22
-rw-r--r--PROJECTSTATUS.md8
-rw-r--r--README.md26
-rw-r--r--cmd/hexai-lsp/main_test.go31
-rw-r--r--cmd/hexai-tmux-action/main.go41
-rw-r--r--cmd/hexai/main_test.go32
-rw-r--r--docs/configuration.md19
-rw-r--r--docs/coverage.html89
-rw-r--r--docs/coverage.out10580
-rw-r--r--docs/helix-tmux-quickstart.md68
-rw-r--r--docs/tmux.md109
-rw-r--r--internal/appconfig/config.go84
-rw-r--r--internal/appconfig/config_test.go38
-rw-r--r--internal/editor/editor.go102
-rw-r--r--internal/editor/editor_test.go63
-rw-r--r--internal/hexaiaction/cmdentry.go252
-rw-r--r--internal/hexaiaction/cmdentry_runcommand_test.go87
-rw-r--r--internal/hexaiaction/cmdentry_test.go250
-rw-r--r--internal/hexaiaction/custom_action_test.go59
-rw-r--r--internal/hexaiaction/parse.go8
-rw-r--r--internal/hexaiaction/parse_test.go2
-rw-r--r--internal/hexaiaction/prompts_more_test.go23
-rw-r--r--internal/hexaiaction/run_more_test.go35
-rw-r--r--internal/hexaiaction/run_seam_test.go56
-rw-r--r--internal/hexaiaction/run_test.go70
-rw-r--r--internal/hexaiaction/tui.go114
-rw-r--r--internal/hexaiaction/tui_delegate.go36
-rw-r--r--internal/hexaiaction/tui_delegate_test.go42
-rw-r--r--internal/hexaiaction/tui_test.go74
-rw-r--r--internal/hexaiaction/types.go14
-rw-r--r--internal/hexaicli/editor_integration_test.go83
-rw-r--r--internal/hexaicli/run_more_test.go51
-rw-r--r--internal/hexailsp/run_more_test.go63
-rw-r--r--internal/llm/provider_more2_test.go13
-rw-r--r--internal/llmutils/client.go51
-rw-r--r--internal/llmutils/client_test.go33
-rw-r--r--internal/logging/logging_test.go33
-rw-r--r--internal/lsp/codeaction_gotest_int_test.go37
-rw-r--r--internal/lsp/coverage_add_test.go156
-rw-r--r--internal/lsp/handlers.go112
-rw-r--r--internal/lsp/handlers_codeaction.go110
-rw-r--r--internal/lsp/handlers_completion.go9
-rw-r--r--internal/lsp/handlers_document.go2
-rw-r--r--internal/lsp/handlers_init_more_test.go15
-rw-r--r--internal/lsp/server.go28
-rw-r--r--internal/testutil/fixtures_test.go9
-rw-r--r--internal/textutil/textutil.go192
-rw-r--r--internal/textutil/textutil_test.go130
-rw-r--r--internal/tmux/tmux.go18
-rw-r--r--internal/tmux/tmux_test.go135
50 files changed, 7029 insertions, 6685 deletions
diff --git a/Magefile.go b/Magefile.go
index 5c5be0b..b297a9d 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 binaries.
func Build() error {
- mg.Deps(BuildHexaiLSP, BuildHexaiCLI, BuildHexaiTmuxAction)
- printCoverage()
- return nil
+ mg.Deps(BuildHexaiLSP, BuildHexaiCLI, BuildHexaiTmuxAction)
+ printCoverage()
+ return nil
}
// BuildHexaiLSP builds the LSP server binary.
@@ -43,8 +43,8 @@ func BuildHexaiCLI() error {
// BuildHexaiTmuxAction builds the hexai-tmux-action TUI binary.
func BuildHexaiTmuxAction() error {
- printCoverage()
- return sh.RunV("go", "build", "-o", "hexai-tmux-action", "cmd/hexai-tmux-action/main.go")
+ printCoverage()
+ 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-tmux-action", "cmd/hexai-tmux-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-tmux-action", bin+"/")
+ return sh.RunV("cp", "-v", "./hexai-tmux-action", bin+"/")
}
// 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-tmux-action/main.go")
+ printCoverage()
+ mg.Deps(Dev)
+ 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 41a634a..9991645 100644
--- a/PROJECTSTATUS.md
+++ b/PROJECTSTATUS.md
@@ -1,19 +1,13 @@
# Project status
-This documents shows future items and in progress items. Already completed ones are removed from this document as we go.
+This document shows future items and items in progress. Already completed ones are deleted from this document as updates occur.
## Features
-* [ ] tmux or helix status line updates with LLM progress/stats?
-* [/] EDITOR support for custom action in hexai-tmux-action
- * Verify documentation is correct
-* [/] EDITOR support for hexai when no args given
- * Verify documentation is correct
* [ ] In-editor chat triggers should be context aware of the current file, buffer and function!
* [ ] Kagi FastGPT for in-editor search
- Think about an in-editor chat trigger, maybe with S> for search!
* [ ] Test whethe GitHub Copilot support actually works now, and if not, fix it!
-* [ ] Fish and zsh and bash shell integration for command completion? Have access to the current shell history (N last commands), current directory content, and current directory name.
* [ ] Be able to re-configure the temperature in-editor
* [ ] Be able to switch LLMs.
diff --git a/README.md b/README.md
index 32ce84f..a2b1836 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ It has got improved capabilities for Go code understanding (for example, create
* [Configuration guide](docs/configuration.md)
* [Usage examples](docs/usage.md)
-* [Helix + tmux quickstart](docs/helix-tmux-quickstart.md)
+* [Helix + tmux quickstart](docs/tmux.md)
## Build and tasks
@@ -53,26 +53,4 @@ Either use the Mage method as mentioned above, or install directly with:
## Tmux Status Line
-Hexai can surface live progress in tmux's status line via a user option. Add this to your `~/.tmux.conf`:
-
-```
-set -g status-right '#{@hexai_status} #[fg=colour8]| %H:%M'
-```
-
-- Note: `colour8` is typically “bright black” (a dim grey) in many themes.
- If it’s low-contrast on your background, change it (e.g., `colour7` or `white`).
-
-- CLI updates `@hexai_status` at start (⏳ provider:model) and on completion with compact stats (↑sent, ↓recv, rpm, reqs).
-- LSP emits an initial heartbeat on client initialize and periodic compact stats (provider, model, rpm, reqs, bytes).
-- The TUI action runner sets a ready heartbeat and a completion heartbeat with stats.
-- Toggle with `HEXAI_TMUX_STATUS=0` to disable (enabled by default).
-
-The status segment supports simple theming:
-
-- Preset themes:
- - `HEXAI_TMUX_STATUS_THEME=white-on-purple` (white fg on purple/magenta bg)
- - `HEXAI_TMUX_STATUS_THEME=black-on-yellow` (black fg on yellow bg)
-- Explicit colors: set any tmux color names or 256-color codes
- - `HEXAI_TMUX_STATUS_FG=white`
- - `HEXAI_TMUX_STATUS_BG=magenta` (or `colour5`, etc.)
-- If the segment is truncated, widen it: `set -g status-right-length 120`
+See the [tmux integration guide](docs/tmux.md) for details on configuring the status line.
diff --git a/cmd/hexai-lsp/main_test.go b/cmd/hexai-lsp/main_test.go
index 987bfb4..387b640 100644
--- a/cmd/hexai-lsp/main_test.go
+++ b/cmd/hexai-lsp/main_test.go
@@ -1,23 +1,22 @@
package main
import (
- "bytes"
- "log"
- "os"
- "testing"
+ "bytes"
+ "log"
+ "os"
+ "testing"
)
func TestMain_Version(t *testing.T) {
- oldArgs := os.Args
- defer func() { os.Args = oldArgs }()
- os.Args = []string{"hexai-lsp", "-version"}
- var buf bytes.Buffer
- old := log.Writer()
- log.SetOutput(&buf)
- defer log.SetOutput(old)
- main()
- if buf.Len() == 0 {
- t.Fatalf("expected version log")
- }
+ oldArgs := os.Args
+ defer func() { os.Args = oldArgs }()
+ os.Args = []string{"hexai-lsp", "-version"}
+ var buf bytes.Buffer
+ old := log.Writer()
+ log.SetOutput(&buf)
+ defer log.SetOutput(old)
+ main()
+ if buf.Len() == 0 {
+ t.Fatalf("expected version log")
+ }
}
-
diff --git a/cmd/hexai-tmux-action/main.go b/cmd/hexai-tmux-action/main.go
index 02cfe09..2d8793b 100644
--- a/cmd/hexai-tmux-action/main.go
+++ b/cmd/hexai-tmux-action/main.go
@@ -1,30 +1,29 @@
package main
import (
- "context"
- "flag"
- "fmt"
- "os"
+ "context"
+ "flag"
+ "fmt"
+ "os"
- "codeberg.org/snonux/hexai/internal/hexaiaction"
+ "codeberg.org/snonux/hexai/internal/hexaiaction"
)
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")
- 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()
+ infile := flag.String("infile", "", "Read input from this file instead of stdin")
+ outfile := flag.String("outfile", "", "Write output to this file instead of stdout")
+ 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()
- opts := hexaiaction.Options{
- Infile: *infile, Outfile: *outfile,
- 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)
- os.Exit(1)
- }
+ opts := hexaiaction.Options{
+ Infile: *infile, Outfile: *outfile,
+ 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)
+ os.Exit(1)
+ }
}
-
diff --git a/cmd/hexai/main_test.go b/cmd/hexai/main_test.go
index beb684b..70c844f 100644
--- a/cmd/hexai/main_test.go
+++ b/cmd/hexai/main_test.go
@@ -1,23 +1,23 @@
package main
import (
- "io"
- "os"
- "testing"
+ "io"
+ "os"
+ "testing"
)
func TestMain_Version(t *testing.T) {
- oldArgs := os.Args
- defer func() { os.Args = oldArgs }()
- os.Args = []string{"hexai", "-version"}
- r, w, _ := os.Pipe()
- old := os.Stdout
- os.Stdout = w
- defer func() { os.Stdout = old }()
- main()
- w.Close()
- b, _ := io.ReadAll(r)
- if len(b) == 0 {
- t.Fatalf("expected version output")
- }
+ oldArgs := os.Args
+ defer func() { os.Args = oldArgs }()
+ os.Args = []string{"hexai", "-version"}
+ r, w, _ := os.Pipe()
+ old := os.Stdout
+ os.Stdout = w
+ defer func() { os.Stdout = old }()
+ main()
+ w.Close()
+ b, _ := io.ReadAll(r)
+ if len(b) == 0 {
+ t.Fatalf("expected version output")
+ }
}
diff --git a/docs/configuration.md b/docs/configuration.md
index 95b65de..0b9e204 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -64,24 +64,7 @@ Editor integration
Tmux status line
-- Hexai can update a tmux user option during LLM activity. Add this to your `~/.tmux.conf` to display it:
-
- - `set -g status-right '#{@hexai_status} #[fg=colour8]| %H:%M'`
-
-- Status content is updated best‑effort at key moments:
- - CLI: start (⏳ provider:model) and completion with compact stats (↑sent, ↓recv, rpm, reqs)
- - LSP: initial heartbeat on client initialize, and periodic compact stats (provider, model, rpm, reqs, bytes)
- - TUI action runner: ready (provider:model) and completion with compact stats
-
-- Toggle via environment:
- - Enable (default): unset or `HEXAI_TMUX_STATUS=1`
- - Disable: `HEXAI_TMUX_STATUS=0`
-
-- Theme and colors:
- - Preset: `HEXAI_TMUX_STATUS_THEME=white-on-purple` (white fg on purple/magenta bg)
- - Explicit: `HEXAI_TMUX_STATUS_FG=<color>`, `HEXAI_TMUX_STATUS_BG=<color>` (e.g., `white`, `magenta`, `colour5`)
- - Colors use tmux’s `fg`/`bg` names; both methods wrap the entire `@hexai_status` segment.
- - If truncated, increase width: `set -g status-right-length 120`
+See the [tmux integration guide](docs/tmux.md) for details on configuring the status line.
Code action prompts
diff --git a/docs/coverage.html b/docs/coverage.html
index 8560a58..059834b 100644
--- a/docs/coverage.html
+++ b/docs/coverage.html
@@ -71,13 +71,13 @@
<option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (85.0%)</option>
- <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (67.3%)</option>
+ <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (69.2%)</option>
<option value="file9">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (65.5%)</option>
<option value="file10">codeberg.org/snonux/hexai/internal/hexaiaction/tui_delegate.go (100.0%)</option>
- <option value="file11">codeberg.org/snonux/hexai/internal/hexaicli/run.go (88.4%)</option>
+ <option value="file11">codeberg.org/snonux/hexai/internal/hexaicli/run.go (88.7%)</option>
<option value="file12">codeberg.org/snonux/hexai/internal/hexailsp/run.go (92.5%)</option>
@@ -113,7 +113,7 @@
<option value="file28">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (66.7%)</option>
- <option value="file29">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (89.0%)</option>
+ <option value="file29">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (89.1%)</option>
<option value="file30">codeberg.org/snonux/hexai/internal/lsp/server.go (82.6%)</option>
@@ -123,7 +123,9 @@
<option value="file33">codeberg.org/snonux/hexai/internal/textutil/textutil.go (89.0%)</option>
- <option value="file34">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option>
+ <option value="file34">codeberg.org/snonux/hexai/internal/tmux/status.go (66.7%)</option>
+
+ <option value="file35">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option>
</select>
</div>
@@ -1485,6 +1487,7 @@ import (
"codeberg.org/snonux/hexai/internal/editor"
"codeberg.org/snonux/hexai/internal/logging"
"codeberg.org/snonux/hexai/internal/llmutils"
+ "codeberg.org/snonux/hexai/internal/tmux"
)
// Run executes the hexai-tmux-action command flow.
@@ -1495,12 +1498,14 @@ var newClientFromApp = llmutils.NewClientFromApp
func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error <span class="cov7" title="4">{
logger := log.New(stderr, "hexai-tmux-action ", log.LstdFlags|log.Lmsgprefix)
cfg := appconfig.Load(logger)
- client, err := newClientFromApp(cfg)
+ cli, err := newClientFromApp(cfg)
if err != nil </span><span class="cov1" title="1">{
fmt.Fprintf(stderr, logging.AnsiBase+"hexai-tmux-action: LLM disabled: %v"+logging.AnsiReset+"\n", err)
return err
}</span>
- <span class="cov6" title="3">parts, err := ParseInput(stdin)
+ <span class="cov6" title="3">_ = tmux.SetStatus("hexai action ready " + cli.DefaultModel())
+ var client chatDoer = cli
+ parts, err := ParseInput(stdin)
if err != nil </span><span class="cov0" title="0">{
fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: failed to read input"+logging.AnsiReset)
return err
@@ -1512,12 +1517,13 @@ func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error <
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
- <span class="cov6" title="3">out, err := executeAction(ctx, kind, parts, cfg, client, stderr)
- if err != nil </span><span class="cov0" title="0">{
- return err
- }</span>
- <span class="cov6" title="3">io.WriteString(stdout, out)
- return nil</span>
+ <span class="cov6" title="3">out, err := executeAction(ctx, kind, parts, cfg, client, stderr)
+ if err != nil </span><span class="cov0" title="0">{
+ return err
+ }</span>
+ <span class="cov6" title="3">io.WriteString(stdout, out)
+ _ = tmux.SetStatus("✅ " + cli.DefaultModel())
+ return nil</span>
}
func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (string, error) <span class="cov10" title="7">{
@@ -1745,6 +1751,7 @@ import (
"codeberg.org/snonux/hexai/internal/logging"
"codeberg.org/snonux/hexai/internal/llm"
"codeberg.org/snonux/hexai/internal/llmutils"
+ "codeberg.org/snonux/hexai/internal/tmux"
)
// Run executes the Hexai CLI behavior given arguments and I/O streams.
@@ -1851,8 +1858,10 @@ func buildMessagesFromConfig(cfg appconfig.App, input string) []llm.Message <spa
// runChat executes the chat request, handling streaming and summary output.
func runChat(ctx context.Context, client llm.Client, msgs []llm.Message, input string, out io.Writer, errw io.Writer) error <span class="cov10" title="7">{
- start := time.Now()
- var output string
+ start := time.Now()
+ // Best-effort tmux status update
+ _ = tmux.SetStatus("⏳ " + client.Name() + ":" + client.DefaultModel())
+ var output string
if s, ok := client.(llm.Streamer); ok </span><span class="cov4" title="2">{
var b strings.Builder
if err := s.ChatStream(ctx, msgs, func(chunk string) </span><span class="cov8" title="5">{
@@ -1870,10 +1879,11 @@ func runChat(ctx context.Context, client llm.Client, msgs []llm.Message, input s
<span class="cov6" title="3">output = txt
fmt.Fprint(out, output)</span>
}
- <span class="cov8" title="5">dur := time.Since(start)
- fmt.Fprintf(errw, "\n"+logging.AnsiBase+"done provider=%s model=%s time=%s in_bytes=%d out_bytes=%d"+logging.AnsiReset+"\n",
- client.Name(), client.DefaultModel(), dur.Round(time.Millisecond), len(input), len(output))
- return nil</span>
+ <span class="cov8" title="5">dur := time.Since(start)
+ fmt.Fprintf(errw, "\n"+logging.AnsiBase+"done provider=%s model=%s time=%s in_bytes=%d out_bytes=%d"+logging.AnsiReset+"\n",
+ client.Name(), client.DefaultModel(), dur.Round(time.Millisecond), len(input), len(output))
+ _ = tmux.SetStatus("✅ " + client.DefaultModel() + " " + dur.Round(time.Millisecond).String())
+ return nil</span>
}
// printProviderInfo writes the provider/model line to stderr.
@@ -5260,6 +5270,7 @@ import (
"codeberg.org/snonux/hexai/internal/llm"
"codeberg.org/snonux/hexai/internal/logging"
"codeberg.org/snonux/hexai/internal/textutil"
+ tmx "codeberg.org/snonux/hexai/internal/tmux"
)
// Configurable inline trigger characters (default to '&gt;') used by free helpers below.
@@ -5312,7 +5323,11 @@ func (s *Server) logLLMStats() <span class="cov5" title="8">{
<span class="cov5" title="8">rpm := float64(reqs) / mins
sentPerMin := float64(sentTot) / mins
recvPerMin := float64(recvTot) / mins
- logging.Logf("lsp ", "llm stats reqs=%d avg_sent=%d avg_recv=%d sent_total=%d recv_total=%d rpm=%.2f sent_per_min=%.0f recv_per_min=%.0f", reqs, avgSent, avgRecv, sentTot, recvTot, rpm, sentPerMin, recvPerMin)</span>
+ logging.Logf("lsp ", "llm stats reqs=%d avg_sent=%d avg_recv=%d sent_total=%d recv_total=%d rpm=%.2f sent_per_min=%.0f recv_per_min=%.0f", reqs, avgSent, avgRecv, sentTot, recvTot, rpm, sentPerMin, recvPerMin)
+ // Best-effort tmux status update
+ if s.llmClient != nil </span><span class="cov4" title="7">{
+ _ = tmx.SetStatus("LLM:" + s.llmClient.DefaultModel())
+ }</span>
}
// Completion prompt builders and filters
@@ -6197,6 +6212,36 @@ func FindStrictInlineTag(line string) (text string, left, right int, ok bool) <s
import (
"os"
"os/exec"
+ "strings"
+)
+
+// Enabled reports whether tmux status updates are enabled via env (default: on).
+func Enabled() bool <span class="cov10" title="25">{
+ v := strings.TrimSpace(os.Getenv("HEXAI_TMUX_STATUS"))
+ if v == "" </span><span class="cov10" title="25">{ return true }</span>
+ <span class="cov0" title="0">v = strings.ToLower(v)
+ return v == "1" || v == "true" || v == "yes" || v == "on"</span>
+}
+
+// SetUserOption sets a global tmux user option like @hexai_status to value.
+func SetUserOption(key, value string) error <span class="cov10" title="25">{
+ if !Enabled() || !HasBinary() || !InSession() </span><span class="cov0" title="0">{ return nil }</span>
+ <span class="cov10" title="25">k := strings.TrimPrefix(strings.TrimSpace(key), "@")
+ if k == "" </span><span class="cov0" title="0">{ return nil }</span>
+ // Use set-option -g so it appears for all windows
+ <span class="cov10" title="25">return exec.Command("tmux", "set-option", "-g", "@"+k, value).Run()</span>
+}
+
+// SetStatus is a convenience for setting @hexai_status.
+func SetStatus(value string) error <span class="cov10" title="25">{ return SetUserOption("hexai_status", value) }</span>
+
+</pre>
+
+ <pre class="file" id="file35" style="display: none">package tmux
+
+import (
+ "os"
+ "os/exec"
"strconv"
"strings"
)
@@ -6208,10 +6253,10 @@ func Available() bool <span class="cov2" title="2">{ return HasBinary() &amp;&am
var lookPath = exec.LookPath
var command = exec.Command
-func HasBinary() bool <span class="cov4" title="4">{ _, err := lookPath("tmux"); return err == nil }</span>
+func HasBinary() bool <span class="cov10" title="29">{ _, err := lookPath("tmux"); return err == nil }</span>
// InSession reports whether we seem to be running inside a tmux session.
-func InSession() bool <span class="cov4" title="3">{ return strings.TrimSpace(os.Getenv("TMUX")) != "" }</span>
+func InSession() bool <span class="cov9" title="28">{ return strings.TrimSpace(os.Getenv("TMUX")) != "" }</span>
// SplitOpts controls how a new pane is created for running a command.
type SplitOpts struct {
@@ -6268,7 +6313,7 @@ func shellJoin(argv []string) string <span class="cov1" title="1">{
// isSafeBare returns true if a contains only safe characters for bare words.
func isSafeBare(s string) bool <span class="cov4" title="4">{
- for i := 0; i &lt; len(s); i++ </span><span class="cov10" title="27">{
+ for i := 0; i &lt; len(s); i++ </span><span class="cov9" title="27">{
b := s[i]
if (b &gt;= 'a' &amp;&amp; b &lt;= 'z') || (b &gt;= 'A' &amp;&amp; b &lt;= 'Z') || (b &gt;= '0' &amp;&amp; b &lt;= '9') || b == '-' || b == '_' || b == '.' || b == '/' || b == ':' </span><span class="cov9" title="25">{
continue</span>
diff --git a/docs/coverage.out b/docs/coverage.out
index 29c09e0..45ffbdd 100644
--- a/docs/coverage.out
+++ b/docs/coverage.out
@@ -425,30 +425,30 @@ codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go:84.37,86.6 1 0
codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go:87.5,87.16 1 0
codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go:91.79,93.2 1 0
codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go:95.78,97.2 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:21.80,25.19 4 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:25.19,28.6 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:29.5,30.16 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:30.16,33.3 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:34.2,34.46 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:34.46,36.3 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:37.5,38.19 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:38.19,40.6 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:41.2,42.16 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:42.16,44.3 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:45.2,46.12 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:49.146,50.17 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:51.21,52.36 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:53.24,55.37 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:55.37,58.4 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:59.3,61.55 3 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:62.25,65.79 3 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:66.25,69.63 3 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:70.23,73.61 3 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:74.25,77.63 3 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:78.23,83.58 4 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:83.58,86.10 2 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:87.9,87.70 1 0
-codeberg.org/snonux/hexai/internal/hexaiaction/run.go:88.13,89.36 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/run.go:22.80,26.19 4 0
+codeberg.org/snonux/hexai/internal/hexaiaction/run.go:26.19,29.6 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/run.go:30.5,33.16 4 0
+codeberg.org/snonux/hexai/internal/hexaiaction/run.go:33.16,36.3 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/run.go:37.2,37.46 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/run.go:37.46,39.3 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/run.go:40.5,41.19 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/run.go:41.19,43.6 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/run.go:44.5,45.19 2 0
+codeberg.org/snonux/hexai/internal/hexaiaction/run.go:45.19,47.6 1 0
+codeberg.org/snonux/hexai/internal/hexaiaction/run.go:48.5,50.15 3 0
+codeberg.org/snonu