diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-08 17:33:04 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-08 17:33:04 +0200 |
| commit | 944838bb0f753a0920ddb2f506758c410ed7ca43 (patch) | |
| tree | 12d6ae9e2de8878891159e363134a3f07686ed7a | |
| parent | c802ba5803de1a53749bb5c4ecbc0159fceb385f (diff) | |
Fix amp agent prompt extraction to use TUI box pattern
Amp CLI runs in TUI mode with box-drawing UI (│ text │) similar to
Cursor, not shell-style (> prompt). Updated prompt pattern from
`(?m)>\s*(.+)$` to `(?m)│\s*(.+?)\s*│\s*$` to correctly extract
text from amp's box UI.
Changes:
- internal/tmuxedit/config_agent.go: Update amp promptPat to box pattern
- internal/tmuxedit/config_agent_test.go: Update test to use box format
- docs/usage.md: Document detection order and clear methods for all agents
- docs/tmux.md: Clarify input mode handling (Vim vs Emacs/readline)
- config.toml.example: Add detailed agent descriptions and patterns
- prompts/tmux-edit-integration-tests.md: Add test status, mock editor best practices
Integration tests verified:
- Amp detection: amp/sourcegraph keywords
- Prompt extraction: "hello world test" correctly captured
- End-to-end workflow: text modification and sending works
- Multi-line support: all lines delivered correctly
- All unit tests pass (67/67)
- Coverage: 80.9% (meets requirement)
- Cursor and Claude implementations unchanged
Co-authored-by: Cursor <cursoragent@cursor.com>
26 files changed, 141973 insertions, 9 deletions
diff --git a/.tmux-edit-send.Ac8ezG.md b/.tmux-edit-send.Ac8ezG.md new file mode 100644 index 0000000..bccdb31 --- /dev/null +++ b/.tmux-edit-send.Ac8ezG.md @@ -0,0 +1 @@ + i see so it is very good diff --git a/config.toml.example b/config.toml.example index cc4471d..781dc19 100644 --- a/config.toml.example +++ b/config.toml.example @@ -160,7 +160,11 @@ temperature = 0.2 # default_agent = "" # force agent name; skip auto-detect # Override or add agent definitions (merged with built-in defaults by name). -# Built-in agents: claude, cursor, amp, aider. +# Built-in agents (checked in order): cursor, claude, amp, aider. +# - cursor: Box UI │...│, clears with End+BSpace*200 +# - claude: Prompt symbol ❯, clears with Escape gg C-v G d i (Vi/Vim) +# - amp: Box UI │...│ (TUI mode), clears with C-u (Emacs/readline) +# - aider: Shell-style > prompt, clears with C-u (Emacs/readline) # Tmux keybinding (add to ~/.tmux.conf): # bind e run-shell -b "hexai-tmux-edit --pane '#{pane_id}'" diff --git a/docs/coverage.html b/docs/coverage.html new file mode 100644 index 0000000..70dbf00 --- /dev/null +++ b/docs/coverage.html @@ -0,0 +1,11263 @@ + +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <title>hexai-lsp: Go Coverage Report</title> + <style> + body { + background: black; + color: rgb(80, 80, 80); + } + body, pre, #legend span { + font-family: Menlo, monospace; + font-weight: bold; + } + #topbar { + background: black; + position: fixed; + top: 0; left: 0; right: 0; + height: 42px; + border-bottom: 1px solid rgb(80, 80, 80); + } + #content { + margin-top: 50px; + } + #nav, #legend { + float: left; + margin-left: 10px; + } + #legend { + margin-top: 12px; + } + #nav { + margin-top: 10px; + } + #legend span { + margin: 0 5px; + } + .cov0 { color: rgb(192, 0, 0) } +.cov1 { color: rgb(128, 128, 128) } +.cov2 { color: rgb(116, 140, 131) } +.cov3 { color: rgb(104, 152, 134) } +.cov4 { color: rgb(92, 164, 137) } +.cov5 { color: rgb(80, 176, 140) } +.cov6 { color: rgb(68, 188, 143) } +.cov7 { color: rgb(56, 200, 146) } +.cov8 { color: rgb(44, 212, 149) } +.cov9 { color: rgb(32, 224, 152) } +.cov10 { color: rgb(20, 236, 155) } + + </style> + </head> + <body> + <div id="topbar"> + <div id="nav"> + <select id="files"> + + <option value="file0">codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go (73.3%)</option> + + <option value="file1">codeberg.org/snonux/hexai/cmd/hexai-tmux-action/main.go (0.0%)</option> + + <option value="file2">codeberg.org/snonux/hexai/cmd/hexai-tmux-edit/main.go (0.0%)</option> + + <option value="file3">codeberg.org/snonux/hexai/cmd/hexai/main.go (61.9%)</option> + + <option value="file4">codeberg.org/snonux/hexai/internal/appconfig/config.go (82.4%)</option> + + <option value="file5">codeberg.org/snonux/hexai/internal/editor/editor.go (58.3%)</option> + + <option value="file6">codeberg.org/snonux/hexai/internal/hexaiaction/cmdentry.go (84.5%)</option> + + <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/parse.go (92.6%)</option> + + <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (92.3%)</option> + + <option value="file9">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (71.1%)</option> + + <option value="file10">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (65.5%)</option> + + <option value="file11">codeberg.org/snonux/hexai/internal/hexaiaction/tui_custom.go (83.8%)</option> + + <option value="file12">codeberg.org/snonux/hexai/internal/hexaiaction/tui_delegate.go (100.0%)</option> + + <option value="file13">codeberg.org/snonux/hexai/internal/hexaicli/run.go (71.4%)</option> + + <option value="file14">codeberg.org/snonux/hexai/internal/hexailsp/run.go (88.6%)</option> + + <option value="file15">codeberg.org/snonux/hexai/internal/ignore/checker.go (100.0%)</option> + + <option value="file16">codeberg.org/snonux/hexai/internal/llm/anthropic.go (80.4%)</option> + + <option value="file17">codeberg.org/snonux/hexai/internal/llm/ollama.go (88.7%)</option> + + <option value="file18">codeberg.org/snonux/hexai/internal/llm/openai.go (86.4%)</option> + + <option value="file19">codeberg.org/snonux/hexai/internal/llm/openrouter.go (75.8%)</option> + + <option value="file20">codeberg.org/snonux/hexai/internal/llm/provider.go (72.1%)</option> + + <option value="file21">codeberg.org/snonux/hexai/internal/llm/util.go (100.0%)</option> + + <option value="file22">codeberg.org/snonux/hexai/internal/llmutils/client.go (100.0%)</option> + + <option value="file23">codeberg.org/snonux/hexai/internal/logging/chatlogger.go (100.0%)</option> + + <option value="file24">codeberg.org/snonux/hexai/internal/logging/logging.go (90.9%)</option> + + <option value="file25">codeberg.org/snonux/hexai/internal/lsp/chat_commands.go (83.3%)</option> + + <option value="file26">codeberg.org/snonux/hexai/internal/lsp/context.go (74.4%)</option> + + <option value="file27">codeberg.org/snonux/hexai/internal/lsp/document.go (91.5%)</option> + + <option value="file28">codeberg.org/snonux/hexai/internal/lsp/handlers.go (89.8%)</option> + + <option value="file29">codeberg.org/snonux/hexai/internal/lsp/handlers_codeaction.go (81.1%)</option> + + <option value="file30">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (75.4%)</option> + + <option value="file31">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (77.8%)</option> + + <option value="file32">codeberg.org/snonux/hexai/internal/lsp/handlers_execute.go (75.0%)</option> + + <option value="file33">codeberg.org/snonux/hexai/internal/lsp/handlers_ignore.go (86.7%)</option> + + <option value="file34">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (66.7%)</option> + + <option value="file35">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (85.3%)</option> + + <option value="file36">codeberg.org/snonux/hexai/internal/lsp/server.go (81.0%)</option> + + <option value="file37">codeberg.org/snonux/hexai/internal/lsp/transport.go (73.0%)</option> + + <option value="file38">codeberg.org/snonux/hexai/internal/runtimeconfig/store.go (88.1%)</option> + + <option value="file39">codeberg.org/snonux/hexai/internal/stats/lock_posix.go (83.3%)</option> + + <option value="file40">codeberg.org/snonux/hexai/internal/stats/stats.go (76.0%)</option> + + <option value="file41">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (100.0%)</option> + + <option value="file42">codeberg.org/snonux/hexai/internal/textutil/human.go (92.3%)</option> + + <option value="file43">codeberg.org/snonux/hexai/internal/textutil/textutil.go (90.4%)</option> + + <option value="file44">codeberg.org/snonux/hexai/internal/tmux/status.go (76.7%)</option> + + <option value="file45">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option> + + <option value="file46">codeberg.org/snonux/hexai/internal/tmuxedit/agent.go (82.1%)</option> + + <option value="file47">codeberg.org/snonux/hexai/internal/tmuxedit/agentutil.go (94.2%)</option> + + <option value="file48">codeberg.org/snonux/hexai/internal/tmuxedit/capture.go (100.0%)</option> + + <option value="file49">codeberg.org/snonux/hexai/internal/tmuxedit/claude_agent.go (86.2%)</option> + + <option value="file50">codeberg.org/snonux/hexai/internal/tmuxedit/config_agent.go (97.5%)</option> + + <option value="file51">codeberg.org/snonux/hexai/internal/tmuxedit/cursor_agent.go (75.0%)</option> + + <option value="file52">codeberg.org/snonux/hexai/internal/tmuxedit/pane.go (92.3%)</option> + + <option value="file53">codeberg.org/snonux/hexai/internal/tmuxedit/run.go (56.8%)</option> + + <option value="file54">codeberg.org/snonux/hexai/internal/tmuxedit/send.go (56.2%)</option> + + </select> + </div> + <div id="legend"> + <span>not tracked</span> + + <span class="cov0">no coverage</span> + <span class="cov1">low coverage</span> + <span class="cov2">*</span> + <span class="cov3">*</span> + <span class="cov4">*</span> + <span class="cov5">*</span> + <span class="cov6">*</span> + <span class="cov7">*</span> + <span class="cov8">*</span> + <span class="cov9">*</span> + <span class="cov10">high coverage</span> + + </div> + </div> + <div id="content"> + + <pre class="file" id="file0" style="display: none">// Summary: Hexai LSP entrypoint; parses flags and delegates to internal/hexailsp. +package main + +import ( + "flag" + "fmt" + "log" + "os" + "strings" + + "codeberg.org/snonux/hexai/internal" + "codeberg.org/snonux/hexai/internal/appconfig" + "codeberg.org/snonux/hexai/internal/hexailsp" +) + +func main() <span class="cov10" title="3">{ + logPath := flag.String("log", "/tmp/hexai-lsp.log", "path to log file (optional)") + defaultCfg := defaultConfigPath() + configPath := flag.String("config", "", fmt.Sprintf("path to config file (default: %s)", defaultCfg)) + showVersion := flag.Bool("version", false, "print version and exit") + flag.Parse() + if *showVersion </span><span class="cov10" title="3">{ + log.Println(internal.Version) + return + }</span> + + <span class="cov0" title="0">path := strings.TrimSpace(*configPath) + if err := hexailsp.RunWithConfig(*logPath, path, os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{ + log.Fatalf("server error: %v", err) + }</span> +} + +func defaultConfigPath() string <span class="cov10" title="3">{ + path, err := appconfig.ConfigPath() + if err != nil </span><span class="cov0" title="0">{ + return "$XDG_CONFIG_HOME/hexai/config.toml" + }</span> + <span class="cov10" title="3">return path</span> +} +</pre> + + <pre class="file" id="file1" style="display: none">package main + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + + "codeberg.org/snonux/hexai/internal/appconfig" + "codeberg.org/snonux/hexai/internal/hexaiaction" +) + +func main() <span class="cov0" title="0">{ + 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") + defaultPath := defaultConfigPath() + configPath := flag.String("config", "", fmt.Sprintf("path to config file (default: %s)", defaultPath)) + 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, + } + ctx := context.Background() + if path := strings.TrimSpace(*configPath); path != "" </span><span class="cov0" title="0">{ + ctx = hexaiaction.WithConfigPath(ctx, path) + }</span> + <span class="cov0" title="0">if err := hexaiaction.RunCommand(ctx, opts, os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{ + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + }</span> +} + +func defaultConfigPath() string <span class="cov0" title="0">{ + path, err := appconfig.ConfigPath() + if err != nil </span><span class="cov0" title="0">{ + return "$XDG_CONFIG_HOME/hexai/config.toml" + }</span> + <span class="cov0" title="0">return path</span> +} +</pre> + + <pre class="file" id="file2" style="display: none">// hexai-tmux-edit opens a tmux popup with $EDITOR for composing AI agent +// prompts. It captures existing prompt text from the target pane, pre-fills +// the editor, and sends the edited text back via tmux send-keys. +// +// Usage: +// +// hexai-tmux-edit [--config <path>] [--agent <name>] [--pane <id>] +// +// Tmux keybinding (add to ~/.tmux.conf): +// +// bind e run-shell -b "hexai-tmux-edit --pane '#{pane_id}'" +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + "codeberg.org/snonux/hexai/internal/appconfig" + "codeberg.org/snonux/hexai/internal/tmuxedit" +) + +func main() <span class="cov0" title="0">{ + defaultPath := defaultConfigPath() + configPath := flag.String("config", "", fmt.Sprintf("path to config file (default: %s)", defaultPath)) + agent := flag.String("agent", "", "AI agent name (auto-detected if omitted)") + pane := flag.String("pane", "", "tmux target pane ID (e.g. %%5)") + flag.Parse() + + opts := tmuxedit.Options{ + ConfigPath: strings.TrimSpace(*configPath), + Agent: strings.TrimSpace(*agent), + Pane: strings.TrimSpace(*pane), + } + if err := tmuxedit.Run(opts); err != nil </span><span class="cov0" title="0">{ + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + }</span> +} + +func defaultConfigPath() string <span class="cov0" title="0">{ + path, err := appconfig.ConfigPath() + if err != nil </span><span class="cov0" title="0">{ + return "$XDG_CONFIG_HOME/hexai/config.toml" + }</span> + <span class="cov0" title="0">return path</span> +} +</pre> + + <pre class="file" id="file3" style="display: none">// Summary: Hexai CLI entrypoint; parses flags and delegates to internal/hexaicli. +package main + +import ( + "context" + "flag" + "fmt" + "io" + "log" + "os" + "strconv" + "strings" + + "codeberg.org/snonux/hexai/internal" + "codeberg.org/snonux/hexai/internal/appconfig" + "codeberg.org/snonux/hexai/internal/hexaicli" +) + +func main() <span class="cov8" title="1">{ + configPath, remaining := splitConfigPath(os.Args[1:]) + logger := log.New(io.Discard, "", 0) + cfg := appconfig.LoadWithOptions(logger, appconfig.LoadOptions{ConfigPath: configPath}) + cliEntries := cfg.CLIConfigs + if len(cliEntries) == 0 </span><span class="cov8" title="1">{ + cliEntries = []appconfig.SurfaceConfig{{Provider: cfg.Provider}} + }</span> + <span class="cov8" title="1">fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError) + defaultPath := defaultConfigPath() + configFlag := fs.String("config", configPath, fmt.Sprintf("path to config file (default: %s)", defaultPath)) + showVersion := fs.Bool("version", false, "print version and exit") + selectedFlags := make([]bool, len(cliEntries)) + for i, entry := range cliEntries </span><span class="cov8" title="1">{ + name := strconv.Itoa(i) + provider := strings.TrimSpace(entry.Provider) + if provider == "" </span><span class="cov8" title="1">{ + provider = cfg.Provider + }</span> + <span class="cov8" title="1">model := strings.TrimSpace(entry.Model) + if model == "" </span><span class="cov8" title="1">{ + model = pickDefaultModel(cfg, provider) + }</span> + <span class="cov8" title="1">desc := fmt.Sprintf("use only provider #%d (%s:%s)", i, provider, model) + fs.BoolVar(&selectedFlags[i], name, false, desc)</span> + } + <span class="cov8" title="1">_ = fs.Parse(remaining) + if *showVersion </span><span class="cov8" title="1">{ + fmt.Fprintln(os.Stdout, internal.Version) + return + }</span> + <span class="cov0" title="0">var selection []int + for i, sel := range selectedFlags </span><span class="cov0" title="0">{ + if sel </span><span class="cov0" title="0">{ + selection = append(selection, i) + }</span> + } + <span class="cov0" title="0">finalPath := strings.TrimSpace(*configFlag) + if finalPath == "" </span><span class="cov0" title="0">{ + finalPath = configPath + }</span> + <span class="cov0" title="0">ctx := context.Background() + if finalPath != "" </span><span class="cov0" title="0">{ + ctx = hexaicli.WithCLIConfigPath(ctx, finalPath) + }</span> + <span class="cov0" title="0">if len(selection) > 0 </span><span class="cov0" title="0">{ + ctx = hexaicli.WithCLISelection(ctx, selection) + }</span> + <span class="cov0" title="0">if err := hexaicli.Run(ctx, fs.Args(), os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{ + os.Exit(1) + }</span> +} + +func splitConfigPath(args []string) (string, []string) <span class="cov8" title="1">{ + var path string + rest := make([]string, 0, len(args)) + skip := false + for i := 0; i < len(args); i++ </span><span class="cov8" title="1">{ + if skip </span><span class="cov0" title="0">{ + skip = false + continue</span> + } + <span class="cov8" title="1">arg := args[i] + switch </span>{ + case arg == "--config" || arg == "-config":<span class="cov0" title="0"> + if i+1 < len(args) </span><span class="cov0" title="0">{ + path = args[i+1] + skip = true + }</span> + case strings.HasPrefix(arg, "--config="):<span class="cov0" title="0"> + path = arg[len("--config="):]</span> + case strings.HasPrefix(arg, "-config="):<span class="cov0" title="0"> + path = arg[len("-config="):]</span> + default:<span class="cov8" title="1"> + rest = append(rest, arg)</span> + } + } + <span class="cov8" title="1">return strings.TrimSpace(path), rest</span> +} + +func pickDefaultModel(cfg appconfig.App, provider string) string <span class="cov8" title="1">{ + switch strings.ToLower(strings.TrimSpace(provider)) </span>{ + case "ollama":<span class="cov0" title="0"> + return strings.TrimSpace(cfg.OllamaModel)</span> + case "anthropic":<span class="cov0" title="0"> + return strings.TrimSpace(cfg.AnthropicModel)</span> + default:<span class="cov8" title="1"> + return strings.TrimSpace(cfg.OpenAIModel)</span> + } +} + +func defaultConfigPath() string <span class="cov8" title="1">{ + cfgPath, err := appconfig.ConfigPath() + if err != nil </span><span class="cov0" title="0">{ + return "$XDG_CONFIG_HOME/hexai/config.toml" + }</span> + <span class="cov8" title="1">return cfgPath</span> +} +</pre> + + <pre class="file" id="file4" style="display: none">// Summary: Application configuration model and loader; reads ~/.config/hexai/config.toml and merges defaults. +package appconfig + +import ( + "fmt" + "log" + "os" + "path/filepath" + "slices" + "strconv" + "strings" + + "github.com/pelletier/go-toml/v2" +) + +// SurfaceConfig describes a provider/model pairing (with optional temperature). +type SurfaceConfig struct { + Provider string + Model string + Temperature *float64 +} + +// App holds user-configurable settings read from ~/.config/hexai/config.toml. +type App struct { + MaxTokens int `json:"max_tokens" toml:"max_tokens"` + ContextMode string `json:"context_mode" toml:"context_mode"` + ContextWindowLines int `json:"context_window_lines" toml:"context_window_lines"` + MaxContextTokens int `json:"max_context_tokens" toml:"max_context_tokens"` + LogPreviewLimit int `json:"log_preview_limit" toml:"log_preview_limit"` + RequestTimeout int `json:"request_timeout" toml:"request_timeout"` + // Single knob for LSP requests; if set, overrides hardcoded temps in LSP. + CodingTemperature *float64 `json:"coding_temperature" toml:"coding_temperature"` + // Minimum identifier characters required for manual (TriggerKind=1) invoke + // to proceed without structural triggers. 0 means always allow. + ManualInvokeMinPrefix int `json:"manual_invoke_min_prefix" toml:"manual_invoke_min_prefix"` + + // Completion debounce in milliseconds. When > 0, the server waits until + // there has been no text change for at least this duration before sending + // an LLM completion request. + CompletionDebounceMs int `json:"completion_debounce_ms" toml:"completion_debounce_ms"` + // Completion throttle in milliseconds. When > 0, caps the minimum spacing + // between LLM requests (both chat and code-completer paths). + CompletionThrottleMs int `json:"completion_throttle_ms" toml:"completion_throttle_ms"` + // CompletionWaitAll controls whether to wait for all configured completion + // backends before returning results. When true (default), waits for all + // backends. When false, returns the first result immediately. + CompletionWaitAll *bool `json:"completion_wait_all" toml:"completion_wait_all"` + + TriggerCharacters []string `json:"trigger_characters" toml:"trigger_characters"` + Provider string `json:"provider" toml:"provider"` + + // Inline prompt trigger characters (default: >!text> and >>!text>) + InlineOpen string `json:"inline_open" toml:"inline_open"` + InlineClose string `json:"inline_close" toml:"inline_close"` + // In-editor chat triggers (default: suffix ">" after one of [?, !, :, ;]) + ChatSuffix string `json:"chat_suffix" toml:"chat_suffix"` + ChatPrefixes []string `json:"chat_prefixes" toml:"chat_prefixes"` + + // Provider-specific options + OpenAIBaseURL string `json:"openai_base_url" toml:"openai_base_url"` + OpenAIModel string `json:"openai_model" toml:"openai_model"` + // Default temperature for OpenAI requests (nil means use provider default) + OpenAITemperature *float64 `json:"openai_temperature" toml:"openai_temperature"` + OpenRouterBaseURL string `json:"openrouter_base_url" toml:"openrouter_base_url"` + OpenRouterModel string `json:"openrouter_model" toml:"openrouter_model"` + // Default temperature for OpenRouter requests (nil means use provider default) + OpenRouterTemperature *float64 `json:"openrouter_temperature" toml:"openrouter_temperature"` + OllamaBaseURL string `json:"ollama_base_url" toml:"ollama_base_url"` + OllamaModel string `json:"ollama_model" toml:"ollama_model"` + // Default temperature for Ollama requests (nil means use provider default) + OllamaTemperature *float64 `json:"ollama_temperature" toml:"ollama_temperature"` + AnthropicBaseURL string `json:"anthropic_base_url" toml:"anthropic_base_url"` + AnthropicModel string `json:"anthropic_model" toml:"anthropic_model"` + // Default temperature for Anthropic requests (nil means use provider default) + AnthropicTemperature *float64 `json:"anthropic_temperature" toml:"anthropic_temperature"` + + // Per-surface provider/model configurations (ordered; first entry is primary) + CompletionConfigs []SurfaceConfig `json:"-" toml:"-"` + CodeActionConfigs []SurfaceConfig `json:"-" toml:"-"` + ChatConfigs []SurfaceConfig `json:"-" toml:"-"` + CLIConfigs []SurfaceConfig `json:"-" toml:"-"` + + // Prompt templates (configured only via file; no env overrides) + // Completion/chat/code action/CLI prompt strings. See config.toml.example for placeholders. + // Completion + PromptCompletionSystemGeneral string `json:"-" toml:"-"` + PromptCompletionSystemParams string `json:"-" toml:"-"` + PromptCompletionSystemInline string `json:"-" toml:"-"` + PromptCompletionUserGeneral string `json:"-" toml:"-"` + PromptCompletionUserParams string `json:"-" toml:"-"` + PromptCompletionExtraHeader string `json:"-" toml:"-"` + // Provider-native code-completer + PromptNativeCompletion string `json:"-" toml:"-"` + // In-editor chat + PromptChatSystem string `json:"-" toml:"-"` + // Code actions + PromptCodeActionRewriteSystem string `json:"-" toml:"-"` + PromptCodeActionDiagnosticsSystem string `json:"-" toml:"-"` + PromptCodeActionDocumentSystem string `json:"-" toml:"-"` + PromptCodeActionRewriteUser string `json:"-" toml:"-"` + PromptCodeActionDiagnosticsUser string `json:"-" toml:"-"` + PromptCodeActionDocumentUser string `json:"-" toml:"-"` + PromptCodeActionGoTestSystem string `json:"-" toml:"-"` + PromptCodeActionGoTestUser string `json:"-" toml:"-"` + PromptCodeActionSimplifySystem string `json:"-" toml:"-"` + PromptCodeActionSimplifyUser string `json:"-" toml:"-"` + // CLI + PromptCLIDefaultSystem string `json:"-" toml:"-"` + PromptCLIExplainSystem string `json:"-" toml:"-"` + + // Custom code actions and tmux integration + CustomActions []CustomAction `json:"-" toml:"-"` + TmuxCustomMenuHotkey string `json:"-" toml:"-"` + // Stats + StatsWindowMinutes int `json:"-" toml:"-"` + + // Ignore: gitignore-aware file filtering for LSP + IgnoreGitignore *bool `json:"-" toml:"-"` + IgnoreExtraPatterns []string `json:"-" toml:"-"` + IgnoreLSPNotify *bool `json:"-" toml:"-"` + + // TmuxEdit: popup editor settings for hexai-tmux-edit + TmuxEditPopupWidth string `json:"-" toml:"-"` + TmuxEditPopupHeight string `json:"-" toml:"-"` + TmuxEditDefaultAgent string `json:"-" toml:"-"` + TmuxEditAgents []TmuxEditAgentCfg `json:"-" toml:"-"` +} + +// CustomAction describes a user-defined code action. +type CustomAction struct { + ID string + Title string + Kind string // optional; default "refactor" + Scope string // "selection" (default) | "diagnostics" + Hotkey string // optional, used by tmux submenu + Instruction string // optional; if set and User is empty, use global rewrite templates + System string // optional; used only when User is set + User string // optional; if set, render with available vars +} + +// TmuxEditAgentCfg describes an AI agent's detection and interaction patterns +// for the tmux popup editor (hexai-tmux-edit). +type TmuxEditAgentCfg struct { + Name string + DisplayName string + DetectPattern string + SectionPattern string + PromptPattern string + StripPatterns []string + ClearFirst *bool + ClearKeys string + NewlineKeys string + SubmitKeys string +} + +// Constructor: defaults for App (kept first among functions) +func newDefaultConfig() App <span class="cov5" title="62">{ + // Coding-friendly default temperature across providers + // Users can override per provider in config.toml (including 0.0). + t := 0.2 + return App{ + MaxTokens: 4000, + ContextMode: "always-full", + ContextWindowLines: 120, + MaxContextTokens: 4000, + LogPreviewLimit: 100, + RequestTimeout: 30, + CodingTemperature: &t, + OpenAITemperature: &t, + OllamaTemperature: &t, + AnthropicTemperature: &t, + ManualInvokeMinPrefix: 0, + CompletionDebounceMs: 800, + CompletionThrottleMs: 0, + // Inline/chat trigger defaults + InlineOpen: ">!", + InlineClose: ">", + ChatSuffix: ">", + ChatPrefixes: []string{"?", "!", ":", ";"}, + + // Default prompt templates (match current hard-coded strings) + PromptCompletionSystemParams: "You are a code completion engine for function signatures. Return only the parameter list contents (without parentheses), no braces, no prose. Prefer idiomatic names and types.", + PromptCompletionUserParams: "Cursor is inside the function parameter list. Suggest only the parameter list (no parentheses).\nFunction line: {{function}}\nCurrent line (cursor at {{char}}): {{current}}", + PromptCompletionSystemGeneral: "You are a terse code completion engine. Return only the code to insert, no surrounding prose or backticks. Only continue from the cursor; never repeat characters already present to the left of the cursor on the current line (e.g., if 'name :=' is already typed, only return the right-hand side expression).", + PromptCompletionUserGeneral: "Provide the next likely code to insert at the cursor.\nFile: {{file}}\nFunction/context: {{function}}\nAbove line: {{above}}\nCurrent line (cursor at character {{char}}): {{current}}\nBelow line: {{below}}\nOnly return the completion snippet.", + PromptCompletionSystemInline: "You are a precise code completion/refactoring engine. Output only the code to insert with no prose, no comments, and no backticks. Return raw code only.", + PromptCompletionExtraHeader: "Additional context:\n{{context}}", + + PromptNativeCompletion: "// Path: {{path}}\n{{before}}", + + PromptChatSystem: "You are a helpful coding assistant. Answer concisely and clearly.", + + PromptCodeActionRewriteSystem: "Y |
