From cead3ebde8f3aee0ef8677158d37f4d04c6629dc Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 8 Sep 2025 09:50:38 +0300 Subject: 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 --- internal/hexaiaction/run.go | 131 ++++++++++++++++++++++---------------------- 1 file changed, 66 insertions(+), 65 deletions(-) (limited to 'internal/hexaiaction/run.go') diff --git a/internal/hexaiaction/run.go b/internal/hexaiaction/run.go index c8cfcac..4958642 100644 --- a/internal/hexaiaction/run.go +++ b/internal/hexaiaction/run.go @@ -1,63 +1,64 @@ package hexaiaction import ( - "context" - "fmt" - "io" - "log" - "strings" + "context" + "fmt" + "io" + "log" + "strings" - "codeberg.org/snonux/hexai/internal/appconfig" - "codeberg.org/snonux/hexai/internal/editor" - "codeberg.org/snonux/hexai/internal/logging" - "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/llmutils" + "codeberg.org/snonux/hexai/internal/logging" + "codeberg.org/snonux/hexai/internal/tmux" ) // Run executes the hexai-tmux-action command flow. // seams for testability -var chooseActionFn = RunTUI -var newClientFromApp = llmutils.NewClientFromApp +var ( + chooseActionFn = RunTUI + 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) - 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) + logger := log.New(stderr, "hexai-tmux-action ", log.LstdFlags|log.Lmsgprefix) + cfg := appconfig.Load(logger) + cli, err := newClientFromApp(cfg) if err != nil { - fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: failed to read input"+logging.AnsiReset) + fmt.Fprintf(stderr, logging.AnsiBase+"hexai-tmux-action: LLM disabled: %v"+logging.AnsiReset+"\n", err) + return err + } + _ = tmux.SetStatus(tmux.FormatLLMStartStatus(cli.Name(), 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) return err } if strings.TrimSpace(parts.Selection) == "" { - return fmt.Errorf("hexai-tmux-action: no input provided on stdin") + return fmt.Errorf("hexai-tmux-action: no input provided on stdin") + } + kind, err := chooseActionFn() + if err != nil { + return err + } + out, err := executeAction(ctx, kind, parts, cfg, client, stderr) + if err != nil { + return err } - kind, err := chooseActionFn() - if err != nil { - return err - } - out, err := executeAction(ctx, kind, parts, cfg, client, stderr) - if err != nil { - return err - } - io.WriteString(stdout, out) - _ = tmux.SetStatus("✅ " + cli.DefaultModel()) - return nil + io.WriteString(stdout, out) + return nil } func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (string, error) { - switch kind { - case ActionSkip: - return parts.Selection, nil - case ActionRewrite: + switch kind { + case ActionSkip: + return parts.Selection, nil + case ActionRewrite: instr, cleaned := ExtractInstruction(parts.Selection) if strings.TrimSpace(instr) == "" { - fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: no inline instruction found; echoing input"+logging.AnsiReset) + fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: no inline instruction found; echoing input"+logging.AnsiReset) return parts.Selection, nil } cctx, cancel := timeout10s(ctx) @@ -67,31 +68,31 @@ func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg a cctx, cancel := timeout10s(ctx) defer cancel() return runDiagnostics(cctx, cfg, client, parts.Diagnostics, parts.Selection) - case ActionDocument: - cctx, cancel := timeout10s(ctx) - defer cancel() - return runDocument(cctx, cfg, client, parts.Selection) - case ActionGoTest: - cctx, cancel := timeout8s(ctx) - defer cancel() - return runGoTest(cctx, cfg, client, parts.Selection) - case ActionSimplify: - cctx, cancel := timeout10s(ctx) - defer cancel() - return runSimplify(cctx, cfg, client, parts.Selection) - case ActionCustom: - cctx, cancel := timeout10s(ctx) - defer cancel() - // Open editor for free-form instruction - prompt, err := editor.OpenTempAndEdit(nil) - if err != nil || strings.TrimSpace(prompt) == "" { - fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: custom prompt canceled or empty; echoing input"+logging.AnsiReset) - return parts.Selection, nil - } - return runRewrite(cctx, cfg, client, prompt, parts.Selection) - default: - return parts.Selection, nil - } + case ActionDocument: + cctx, cancel := timeout10s(ctx) + defer cancel() + return runDocument(cctx, cfg, client, parts.Selection) + case ActionGoTest: + cctx, cancel := timeout8s(ctx) + defer cancel() + return runGoTest(cctx, cfg, client, parts.Selection) + case ActionSimplify: + cctx, cancel := timeout10s(ctx) + defer cancel() + return runSimplify(cctx, cfg, client, parts.Selection) + case ActionCustom: + cctx, cancel := timeout10s(ctx) + defer cancel() + // Open editor for free-form instruction + prompt, err := editor.OpenTempAndEdit(nil) + if err != nil || strings.TrimSpace(prompt) == "" { + fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: custom prompt canceled or empty; echoing input"+logging.AnsiReset) + return parts.Selection, nil + } + return runRewrite(cctx, cfg, client, prompt, parts.Selection) + default: + return parts.Selection, nil + } } // client construction is shared via internal/llmutils -- cgit v1.2.3