diff options
| author | Paul Buetow <paul@buetow.org> | 2025-09-07 21:10:15 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-09-07 21:10:15 +0300 |
| commit | b39f233bf65c4a4b1d8d7a813b21d8477699ffae (patch) | |
| tree | 185069f91a513194545822e60276ab38a2f060c4 | |
| parent | fb15616d595e0bf9f734992ceae529001e1f4677 (diff) | |
feat(tmux): add status line updates via @hexai_status; wire into CLI, LSP stats, and tmux-action
| -rw-r--r-- | PROJECTSTATUS.md | 3 | ||||
| -rw-r--r-- | internal/hexaiaction/run.go | 18 | ||||
| -rw-r--r-- | internal/hexaicli/run.go | 16 | ||||
| -rw-r--r-- | internal/lsp/handlers_utils.go | 7 | ||||
| -rw-r--r-- | internal/tmux/status.go | 28 |
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) } + |
