diff options
| author | Paul Buetow <paul@buetow.org> | 2025-09-08 09:50:38 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-09-08 09:50:38 +0300 |
| commit | cead3ebde8f3aee0ef8677158d37f4d04c6629dc (patch) | |
| tree | eadf4928c13e4f1fd782e8e0955116a24cef1d27 /internal/hexaicli | |
| parent | 29b0da31acf02816ee9e8f1d5a1b9a0ad5993593 (diff) | |
tmux: colored LLM status with provider + stats; add start heartbeat for LSP/CLI/TUI; theme support via HEXAI_TMUX_STATUS_THEME and HEXAI_TMUX_STATUS_FG/BG; docs: update tmux options and add Helix+tmux quickstart
Diffstat (limited to 'internal/hexaicli')
| -rw-r--r-- | internal/hexaicli/run.go | 87 |
1 files changed, 49 insertions, 38 deletions
diff --git a/internal/hexaicli/run.go b/internal/hexaicli/run.go index 18d4289..984bc85 100644 --- a/internal/hexaicli/run.go +++ b/internal/hexaicli/run.go @@ -3,44 +3,44 @@ package hexaicli import ( - "bufio" - "context" - "fmt" - "io" - "log" - "os" - "strings" - "time" + "bufio" + "context" + "fmt" + "io" + "log" + "os" + "strings" + "time" - "codeberg.org/snonux/hexai/internal/appconfig" - "codeberg.org/snonux/hexai/internal/editor" - "codeberg.org/snonux/hexai/internal/logging" - "codeberg.org/snonux/hexai/internal/llm" - "codeberg.org/snonux/hexai/internal/llmutils" - "codeberg.org/snonux/hexai/internal/tmux" + "codeberg.org/snonux/hexai/internal/appconfig" + "codeberg.org/snonux/hexai/internal/editor" + "codeberg.org/snonux/hexai/internal/llm" + "codeberg.org/snonux/hexai/internal/llmutils" + "codeberg.org/snonux/hexai/internal/logging" + "codeberg.org/snonux/hexai/internal/tmux" ) // Run executes the Hexai CLI behavior given arguments and I/O streams. // It assumes flags have already been parsed by the caller. func Run(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) error { - // Load configuration with a logger so file-based config is respected. - logger := log.New(stderr, "hexai ", log.LstdFlags|log.Lmsgprefix) - cfg := appconfig.Load(logger) - client, err := newClientFromApp(cfg) + // Load configuration with a logger so file-based config is respected. + logger := log.New(stderr, "hexai ", log.LstdFlags|log.Lmsgprefix) + cfg := appconfig.Load(logger) + client, err := newClientFromApp(cfg) if err != nil { fmt.Fprintf(stderr, logging.AnsiBase+"hexai: LLM disabled: %v"+logging.AnsiReset+"\n", err) return err } - // No args: open editor to capture a prompt, then combine with stdin as usual. - if len(args) == 0 { - if prompt, eerr := editor.OpenTempAndEdit(nil); eerr == nil && strings.TrimSpace(prompt) != "" { - args = []string{prompt} - } else { - // If editor fails or empty, continue; readInput will likely error if no stdin either. - } - } - // Inline the flow here to use configured CLI prompts. - input, rerr := readInput(stdin, args) + // No args: open editor to capture a prompt, then combine with stdin as usual. + if len(args) == 0 { + if prompt, eerr := editor.OpenTempAndEdit(nil); eerr == nil && strings.TrimSpace(prompt) != "" { + args = []string{prompt} + } else { + // If editor fails or empty, continue; readInput will likely error if no stdin either. + } + } + // Inline the flow here to use configured CLI prompts. + input, rerr := readInput(stdin, args) if rerr != nil { fmt.Fprintln(stderr, logging.AnsiBase+rerr.Error()+logging.AnsiReset) return rerr @@ -124,10 +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() - // Best-effort tmux status update - _ = tmux.SetStatus("⏳ " + client.Name() + ":" + client.DefaultModel()) - var output string + start := time.Now() + // Best-effort tmux status update (colored start heartbeat) + _ = tmux.SetStatus(tmux.FormatLLMStartStatus(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) { @@ -145,16 +145,27 @@ 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)) - _ = tmux.SetStatus("✅ " + client.DefaultModel() + " " + dur.Round(time.Millisecond).String()) - return nil + dur := time.Since(start) + // Compute simple stats for tmux heartbeat + sent := 0 + for _, m := range msgs { + sent += len(m.Content) + } + recv := len(output) + mins := dur.Minutes() + if mins <= 0 { + mins = 0.001 + } + rpm := float64(1) / mins + 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), sent, recv) + _ = tmux.SetStatus(tmux.FormatLLMStatsStatusColored(client.Name(), client.DefaultModel(), 1, rpm, int64(sent), int64(recv))) + return nil } // printProviderInfo writes the provider/model line to stderr. func printProviderInfo(errw io.Writer, client llm.Client) { - fmt.Fprintf(errw, logging.AnsiBase+"provider=%s model=%s"+logging.AnsiReset+"\n", client.Name(), client.DefaultModel()) + fmt.Fprintf(errw, logging.AnsiBase+"provider=%s model=%s"+logging.AnsiReset+"\n", client.Name(), client.DefaultModel()) } // newClientFromConfig is kept for tests; delegates to llmutils. |
