summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-07 21:10:15 +0300
committerPaul Buetow <paul@buetow.org>2025-09-07 21:10:15 +0300
commitb39f233bf65c4a4b1d8d7a813b21d8477699ffae (patch)
tree185069f91a513194545822e60276ab38a2f060c4
parentfb15616d595e0bf9f734992ceae529001e1f4677 (diff)
feat(tmux): add status line updates via @hexai_status; wire into CLI, LSP stats, and tmux-action
-rw-r--r--PROJECTSTATUS.md3
-rw-r--r--internal/hexaiaction/run.go18
-rw-r--r--internal/hexaicli/run.go16
-rw-r--r--internal/lsp/handlers_utils.go7
-rw-r--r--internal/tmux/status.go28
5 files changed, 58 insertions, 14 deletions
diff --git a/PROJECTSTATUS.md b/PROJECTSTATUS.md
index 8709378..41a634a 100644
--- a/PROJECTSTATUS.md
+++ b/PROJECTSTATUS.md
@@ -4,8 +4,11 @@ This documents shows future items and in progress items. Already completed ones
## 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!
diff --git a/internal/hexaiaction/run.go b/internal/hexaiaction/run.go
index b11457c..c8cfcac 100644
--- a/internal/hexaiaction/run.go
+++ b/internal/hexaiaction/run.go
@@ -11,6 +11,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.
@@ -21,11 +22,13 @@ var newClientFromApp = llmutils.NewClientFromApp
func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error {
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 {
fmt.Fprintf(stderr, logging.AnsiBase+"hexai-tmux-action: LLM disabled: %v"+logging.AnsiReset+"\n", err)
return err
}
+ _ = tmux.SetStatus("hexai action ready " + cli.DefaultModel())
+ var client chatDoer = cli
parts, err := ParseInput(stdin)
if err != nil {
fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: failed to read input"+logging.AnsiReset)
@@ -38,12 +41,13 @@ func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error {
if err != nil {
return err
}
- out, err := executeAction(ctx, kind, parts, cfg, client, stderr)
- if err != nil {
- return err
- }
- io.WriteString(stdout, out)
- return nil
+ out, err := executeAction(ctx, kind, parts, cfg, client, stderr)
+ if err != nil {
+ return err
+ }
+ io.WriteString(stdout, out)
+ _ = tmux.SetStatus("✅ " + cli.DefaultModel())
+ return nil
}
func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (string, error) {
diff --git a/internal/hexaicli/run.go b/internal/hexaicli/run.go
index cdcc3ac..18d4289 100644
--- a/internal/hexaicli/run.go
+++ b/internal/hexaicli/run.go
@@ -17,6 +17,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.
@@ -123,8 +124,10 @@ func buildMessagesFromConfig(cfg appconfig.App, input string) []llm.Message {
// 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 {
- 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 {
var b strings.Builder
if err := s.ChatStream(ctx, msgs, func(chunk string) {
@@ -142,10 +145,11 @@ func runChat(ctx context.Context, client llm.Client, msgs []llm.Message, input s
output = txt
fmt.Fprint(out, output)
}
- 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
+ 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
}
// printProviderInfo writes the provider/model line to stderr.
diff --git a/internal/lsp/handlers_utils.go b/internal/lsp/handlers_utils.go
index 015e9c1..7f116cd 100644
--- a/internal/lsp/handlers_utils.go
+++ b/internal/lsp/handlers_utils.go
@@ -8,6 +8,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 '>') used by free helpers below.
@@ -60,7 +61,11 @@ func (s *Server) logLLMStats() {
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)
+ 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 {
+ _ = tmx.SetStatus("LLM:" + s.llmClient.DefaultModel())
+ }
}
// Completion prompt builders and filters
diff --git a/internal/tmux/status.go b/internal/tmux/status.go
new file mode 100644
index 0000000..4e1f9e4
--- /dev/null
+++ b/internal/tmux/status.go
@@ -0,0 +1,28 @@
+package tmux
+
+import (
+ "os"
+ "os/exec"
+ "strings"
+)
+
+// Enabled reports whether tmux status updates are enabled via env (default: on).
+func Enabled() bool {
+ v := strings.TrimSpace(os.Getenv("HEXAI_TMUX_STATUS"))
+ if v == "" { return true }
+ v = strings.ToLower(v)
+ return v == "1" || v == "true" || v == "yes" || v == "on"
+}
+
+// SetUserOption sets a global tmux user option like @hexai_status to value.
+func SetUserOption(key, value string) error {
+ if !Enabled() || !HasBinary() || !InSession() { return nil }
+ k := strings.TrimPrefix(strings.TrimSpace(key), "@")
+ if k == "" { return nil }
+ // Use set-option -g so it appears for all windows
+ return exec.Command("tmux", "set-option", "-g", "@"+k, value).Run()
+}
+
+// SetStatus is a convenience for setting @hexai_status.
+func SetStatus(value string) error { return SetUserOption("hexai_status", value) }
+