summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-11-02 23:42:15 +0200
committerPaul Buetow <paul@buetow.org>2025-11-02 23:42:15 +0200
commit35e1de6f975088ade5dbf0af533fe6fdac8fcc94 (patch)
treec9fc9b6ad86cc10a777b3f510c3c4b2d62c41ebd /docs
parentc60d5703d25b7d76d1da2f368b0632f08a161644 (diff)
some linter fixes
Diffstat (limited to 'docs')
-rw-r--r--docs/coverage.html3593
-rw-r--r--docs/coverage.out52019
2 files changed, 33971 insertions, 21641 deletions
diff --git a/docs/coverage.html b/docs/coverage.html
index 36775ce..4526ad1 100644
--- a/docs/coverage.html
+++ b/docs/coverage.html
@@ -55,13 +55,13 @@
<div id="nav">
<select id="files">
- <option value="file0">codeberg.org/snonux/hexai/cmd/hexai-lsp/main.go (75.0%)</option>
+ <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/main.go (71.4%)</option>
+ <option value="file2">codeberg.org/snonux/hexai/cmd/hexai/main.go (61.9%)</option>
- <option value="file3">codeberg.org/snonux/hexai/internal/appconfig/config.go (88.9%)</option>
+ <option value="file3">codeberg.org/snonux/hexai/internal/appconfig/config.go (82.5%)</option>
<option value="file4">codeberg.org/snonux/hexai/internal/editor/editor.go (58.3%)</option>
@@ -69,9 +69,9 @@
<option value="file6">codeberg.org/snonux/hexai/internal/hexaiaction/parse.go (92.6%)</option>
- <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (92.0%)</option>
+ <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (92.3%)</option>
- <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (76.8%)</option>
+ <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (71.1%)</option>
<option value="file9">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (65.5%)</option>
@@ -79,65 +79,67 @@
<option value="file11">codeberg.org/snonux/hexai/internal/hexaiaction/tui_delegate.go (100.0%)</option>
- <option value="file12">codeberg.org/snonux/hexai/internal/hexaicli/run.go (90.0%)</option>
+ <option value="file12">codeberg.org/snonux/hexai/internal/hexaicli/run.go (72.3%)</option>
- <option value="file13">codeberg.org/snonux/hexai/internal/hexailsp/run.go (90.8%)</option>
+ <option value="file13">codeberg.org/snonux/hexai/internal/hexailsp/run.go (88.9%)</option>
<option value="file14">codeberg.org/snonux/hexai/internal/llm/copilot.go (82.4%)</option>
<option value="file15">codeberg.org/snonux/hexai/internal/llm/ollama.go (89.8%)</option>
- <option value="file16">codeberg.org/snonux/hexai/internal/llm/openai.go (86.4%)</option>
+ <option value="file16">codeberg.org/snonux/hexai/internal/llm/openai.go (87.1%)</option>
- <option value="file17">codeberg.org/snonux/hexai/internal/llm/provider.go (100.0%)</option>
+ <option value="file17">codeberg.org/snonux/hexai/internal/llm/openrouter.go (76.2%)</option>
- <option value="file18">codeberg.org/snonux/hexai/internal/llm/util.go (100.0%)</option>
+ <option value="file18">codeberg.org/snonux/hexai/internal/llm/provider.go (86.0%)</option>
- <option value="file19">codeberg.org/snonux/hexai/internal/llmutils/client.go (100.0%)</option>
+ <option value="file19">codeberg.org/snonux/hexai/internal/llm/util.go (100.0%)</option>
- <option value="file20">codeberg.org/snonux/hexai/internal/logging/chatlogger.go (100.0%)</option>
+ <option value="file20">codeberg.org/snonux/hexai/internal/llmutils/client.go (100.0%)</option>
- <option value="file21">codeberg.org/snonux/hexai/internal/logging/logging.go (90.9%)</option>
+ <option value="file21">codeberg.org/snonux/hexai/internal/logging/chatlogger.go (100.0%)</option>
- <option value="file22">codeberg.org/snonux/hexai/internal/lsp/chat_commands.go (72.2%)</option>
+ <option value="file22">codeberg.org/snonux/hexai/internal/logging/logging.go (90.9%)</option>
- <option value="file23">codeberg.org/snonux/hexai/internal/lsp/context.go (74.4%)</option>
+ <option value="file23">codeberg.org/snonux/hexai/internal/lsp/chat_commands.go (83.3%)</option>
- <option value="file24">codeberg.org/snonux/hexai/internal/lsp/document.go (91.5%)</option>
+ <option value="file24">codeberg.org/snonux/hexai/internal/lsp/context.go (74.4%)</option>
- <option value="file25">codeberg.org/snonux/hexai/internal/lsp/handlers.go (92.2%)</option>
+ <option value="file25">codeberg.org/snonux/hexai/internal/lsp/document.go (91.5%)</option>
- <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers_codeaction.go (84.1%)</option>
+ <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers.go (89.8%)</option>
- <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (88.8%)</option>
+ <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_codeaction.go (82.0%)</option>
- <option value="file28">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (77.7%)</option>
+ <option value="file28">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (79.0%)</option>
- <option value="file29">codeberg.org/snonux/hexai/internal/lsp/handlers_execute.go (75.0%)</option>
+ <option value="file29">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (78.1%)</option>
- <option value="file30">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (66.7%)</option>
+ <option value="file30">codeberg.org/snonux/hexai/internal/lsp/handlers_execute.go (75.0%)</option>
- <option value="file31">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (90.2%)</option>
+ <option value="file31">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (66.7%)</option>
- <option value="file32">codeberg.org/snonux/hexai/internal/lsp/server.go (86.8%)</option>
+ <option value="file32">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (85.3%)</option>
- <option value="file33">codeberg.org/snonux/hexai/internal/lsp/transport.go (73.0%)</option>
+ <option value="file33">codeberg.org/snonux/hexai/internal/lsp/server.go (82.1%)</option>
- <option value="file34">codeberg.org/snonux/hexai/internal/runtimeconfig/store.go (87.1%)</option>
+ <option value="file34">codeberg.org/snonux/hexai/internal/lsp/transport.go (73.0%)</option>
- <option value="file35">codeberg.org/snonux/hexai/internal/stats/lock_posix.go (83.3%)</option>
+ <option value="file35">codeberg.org/snonux/hexai/internal/runtimeconfig/store.go (88.1%)</option>
- <option value="file36">codeberg.org/snonux/hexai/internal/stats/stats.go (75.8%)</option>
+ <option value="file36">codeberg.org/snonux/hexai/internal/stats/lock_posix.go (83.3%)</option>
- <option value="file37">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (100.0%)</option>
+ <option value="file37">codeberg.org/snonux/hexai/internal/stats/stats.go (75.8%)</option>
- <option value="file38">codeberg.org/snonux/hexai/internal/textutil/human.go (92.3%)</option>
+ <option value="file38">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (100.0%)</option>
- <option value="file39">codeberg.org/snonux/hexai/internal/textutil/textutil.go (90.4%)</option>
+ <option value="file39">codeberg.org/snonux/hexai/internal/textutil/human.go (92.3%)</option>
- <option value="file40">codeberg.org/snonux/hexai/internal/tmux/status.go (76.7%)</option>
+ <option value="file40">codeberg.org/snonux/hexai/internal/textutil/textutil.go (90.4%)</option>
- <option value="file41">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option>
+ <option value="file41">codeberg.org/snonux/hexai/internal/tmux/status.go (76.7%)</option>
+
+ <option value="file42">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option>
</select>
</div>
@@ -165,15 +167,20 @@ 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="cov8" title="1">{
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="cov8" title="1">{
@@ -181,10 +188,19 @@ func main() <span class="cov8" title="1">{
return
}</span>
- <span class="cov0" title="0">if err := hexailsp.Run(*logPath, os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{
+ <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="cov8" title="1">{
+ path, 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 path</span>
+}
</pre>
<pre class="file" id="file1" style="display: none">package main
@@ -194,7 +210,9 @@ import (
"flag"
"fmt"
"os"
+ "strings"
+ "codeberg.org/snonux/hexai/internal/appconfig"
"codeberg.org/snonux/hexai/internal/hexaiaction"
)
@@ -202,6 +220,8 @@ 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)")
@@ -211,11 +231,23 @@ func main() <span class="cov0" title="0">{
Infile: *infile, Outfile: *outfile,
UIChild: *uiChild, TmuxTarget: *tmuxTarget, TmuxSplit: *tmuxSplit, TmuxPercent: *tmuxPercent,
}
- if err := hexaiaction.RunCommand(context.Background(), opts, os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{
+ 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">// Summary: Hexai CLI entrypoint; parses flags and delegates to internal/hexaicli.
@@ -225,24 +257,115 @@ 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">{
- showVersion := flag.Bool("version", false, "print version and exit")
- flag.Parse()
+ 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(&amp;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">if err := hexaicli.Run(context.Background(), flag.Args(), os.Stdin, os.Stdout, os.Stderr); err != nil </span><span class="cov0" title="0">{
+ <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) &gt; 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 &lt; 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 &lt; 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 "copilot":<span class="cov0" title="0">
+ return strings.TrimSpace(cfg.CopilotModel)</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="file3" style="display: none">// Summary: Application configuration model and loader; reads ~/.config/hexai/config.toml and merges defaults.
@@ -260,6 +383,13 @@ import (
"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"`
@@ -284,7 +414,7 @@ type App struct {
TriggerCharacters []string `json:"trigger_characters" toml:"trigger_characters"`
Provider string `json:"provider" toml:"provider"`
- // Inline prompt trigger characters (default: &gt;text&gt; and &gt;&gt;text&gt;)
+ // Inline prompt trigger characters (default: &gt;!text&gt; and &gt;&gt;!text&gt;)
InlineOpen string `json:"inline_open" toml:"inline_open"`
InlineClose string `json:"inline_close" toml:"inline_close"`
// In-editor chat triggers (default: suffix "&gt;" after one of [?, !, :, ;])
@@ -296,8 +426,12 @@ type App struct {
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"`
- OllamaBaseURL string `json:"ollama_base_url" toml:"ollama_base_url"`
- OllamaModel string `json:"ollama_model" toml:"ollama_model"`
+ 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"`
CopilotBaseURL string `json:"copilot_base_url" toml:"copilot_base_url"`
@@ -305,6 +439,12 @@ type App struct {
// Default temperature for Copilot requests (nil means use provider default)
CopilotTemperature *float64 `json:"copilot_temperature" toml:"copilot_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
@@ -353,7 +493,7 @@ type CustomAction struct {
}
// Constructor: defaults for App (kept first among functions)
-func newDefaultConfig() App <span class="cov6" title="49">{
+func newDefaultConfig() App <span class="cov5" title="51">{
// Coding-friendly default temperature across providers
// Users can override per provider in config.toml (including 0.0).
t := 0.2
@@ -371,7 +511,7 @@ func newDefaultConfig() App <span class="cov6" title="49">{
CompletionDebounceMs: 800,
CompletionThrottleMs: 0,
// Inline/chat trigger defaults
- InlineOpen: "&gt;",
+ InlineOpen: "&gt;!",
InlineClose: "&gt;",
ChatSuffix: "&gt;",
ChatPrefixes: []string{"?", "!", ":", ";"},
@@ -409,40 +549,45 @@ func newDefaultConfig() App <span class="cov6" title="49">{
// Load reads configuration from a file and merges with defaults.
// It respects the XDG Base Directory Specification.
-func Load(logger *log.Logger) App <span class="cov6" title="44">{ return LoadWithOptions(logger, LoadOptions{}) }</span>
+func Load(logger *log.Logger) App <span class="cov5" title="35">{ return LoadWithOptions(logger, LoadOptions{}) }</span>
// LoadOptions tune how configuration is loaded at runtime.
type LoadOptions struct {
// IgnoreEnv skips applying environment overrides when true.
- IgnoreEnv bool
+ IgnoreEnv bool
+ ConfigPath string
}
// LoadWithOptions reads configuration and applies the requested loading options.
-func LoadWithOptions(logger *log.Logger, opts LoadOptions) App <span class="cov6" title="48">{
+func LoadWithOptions(logger *log.Logger, opts LoadOptions) App <span class="cov5" title="50">{
cfg := newDefaultConfig()
if logger == nil </span><span class="cov4" title="13">{
return cfg // Return defaults if no logger is provided (e.g. in tests)
}</span>
- <span class="cov5" title="35">configPath, err := getConfigPath()
- if err != nil </span><span class="cov0" title="0">{
- logger.Printf("%v", err)
- // Even if config path cannot be resolved, keep defaults and optionally apply env overrides below.
- }</span> else<span class="cov5" title="35"> {
- if fileCfg, err := loadFromFile(configPath, logger); err == nil &amp;&amp; fileCfg != nil </span><span class="cov5" title="30">{
+ <span class="cov5" title="37">configPath := strings.TrimSpace(opts.ConfigPath)
+ if configPath != "" </span><span class="cov0" title="0">{
+ if fileCfg, err := loadFromFile(configPath, logger); err == nil &amp;&amp; fileCfg != nil </span><span class="cov0" title="0">{
+ cfg.mergeWith(fileCfg)
+ }</span> else<span class="cov0" title="0"> if err != nil </span><span class="cov0" title="0">{
+ logger.Printf("cannot open config file %s: %v", configPath, err)
+ }</span>
+ } else<span class="cov5" title="37"> {
+ path, err := getConfigPath()
+ if err != nil </span><span class="cov0" title="0">{
+ logger.Printf("%v", err)
+ }</span> else<span class="cov5" title="37"> if fileCfg, err := loadFromFile(path, logger); err == nil &amp;&amp; fileCfg != nil </span><span class="cov4" title="21">{
cfg.mergeWith(fileCfg)
}</span>
- // When the config file is missing or invalid, we keep defaults and still
- // apply any environment overrides below (unless disabled).
}
- <span class="cov5" title="35">if !opts.IgnoreEnv </span><span class="cov5" title="31">{
+ <span class="cov5" title="37">if !opts.IgnoreEnv </span><span class="cov5" title="33">{
// Environment overrides (take precedence over file)
- if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov3" title="8">{
+ if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov3" title="9">{
cfg.mergeWith(envCfg)
}</span>
}
- <span class="cov5" title="35">return cfg</span>
+ <span class="cov5" title="37">return cfg</span>
}
// Private helpers
@@ -457,6 +602,7 @@ type fileConfig struct {
Chat sectionChat `toml:"chat"`
Provider sectionProvider `toml:"provider"`
OpenAI sectionOpenAI `toml:"openai"`
+ OpenRouter sectionOpenRouter `toml:"openrouter"`
Copilot sectionCopilot `toml:"copilot"`
Ollama sectionOllama `toml:"ollama"`
Prompts sectionPrompts `toml:"prompts"`
@@ -511,16 +657,16 @@ type sectionOpenAI struct {
Presets map[string]string `toml:"presets"`
}
-func (s sectionOpenAI) isZero() bool <span class="cov5" title="30">{
+func (s sectionOpenAI) isZero() bool <span class="cov4" title="23">{
return strings.TrimSpace(s.Model) == "" &amp;&amp; strings.TrimSpace(s.BaseURL) == "" &amp;&amp; s.Temperature == nil &amp;&amp; len(s.Presets) == 0
}</span>
-func (s sectionOpenAI) resolvedModel() string <span class="cov4" title="14">{
+func (s sectionOpenAI) resolvedModel() string <span class="cov3" title="6">{
model := strings.TrimSpace(s.Model)
if model == "" </span><span class="cov0" title="0">{
return ""
}</span>
- <span class="cov4" title="14">if len(s.Presets) == 0 </span><span class="cov4" title="13">{
+ <span class="cov3" title="6">if len(s.Presets) == 0 </span><span class="cov3" title="5">{
return model
}</span>
<span class="cov1" title="1">if mapped := strings.TrimSpace(s.Presets[model]); mapped != "" </span><span class="cov1" title="1">{
@@ -537,6 +683,12 @@ func (s sectionOpenAI) resolvedModel() string <span class="cov4" title="14">{
<span class="cov0" title="0">return model</span>
}
+type sectionOpenRouter struct {
+ Model string `toml:"model"`
+ BaseURL string `toml:"base_url"`
+ Temperature *float64 `toml:"temperature"`
+}
+
type sectionCopilot struct {
Model string `toml:"model"`
BaseURL string `toml:"base_url"`
@@ -609,11 +761,11 @@ type sectionTmux struct {
CustomMenuHotkey string `toml:"custom_menu_hotkey"`
}
-func (fc *fileConfig) toApp() App <span class="cov5" title="30">{
+func (fc *fileConfig) toApp() App <span class="cov4" title="23">{
out := App{}
// Merge section: general
- if (fc.General != sectionGeneral{}) || fc.General.CodingTemperature != nil </span><span class="cov4" title="11">{
+ if (fc.General != sectionGeneral{}) || fc.General.CodingTemperature != nil </span><span class="cov4" title="12">{
tmp := App{
MaxTokens: fc.General.MaxTokens,
ContextMode: fc.General.ContextMode,
@@ -625,13 +777,13 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="30">{
}</span>
// logging
- <span class="cov5" title="30">if (fc.Logging != sectionLogging{}) </span><span class="cov1" title="1">{
+ <span class="cov4" title="23">if (fc.Logging != sectionLogging{}) </span><span class="cov1" title="1">{
tmp := App{LogPreviewLimit: fc.Logging.LogPreviewLimit}
out.mergeBasics(&amp;tmp)
}</span>
// completion
- <span class="cov5" title="30">if (fc.Completion != sectionCompletion{}) </span><span class="cov2" title="3">{
+ <span class="cov4" title="23">if (fc.Completion != sectionCompletion{}) </span><span class="cov2" title="4">{
tmp := App{
CompletionDebounceMs: fc.Completion.CompletionDebounceMs,
CompletionThrottleMs: fc.Completion.CompletionThrottleMs,
@@ -641,31 +793,31 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="30">{
}</span>
// triggers
- <span class="cov5" title="30">if len(fc.Triggers.TriggerCharacters) &gt; 0 </span><span class="cov2" title="3">{
+ <span class="cov4" title="23">if len(fc.Triggers.TriggerCharacters) &gt; 0 </span><span class="cov2" title="4">{
tmp := App{TriggerCharacters: fc.Triggers.TriggerCharacters}
out.mergeBasics(&amp;tmp)
}</span>
// inline
- <span class="cov5" title="30">if (fc.Inline != sectionInline{}) </span><span class="cov4" title="11">{
+ <span class="cov4" title="23">if (fc.Inline != sectionInline{}) </span><span class="cov1" title="1">{
tmp := App{InlineOpen: fc.Inline.InlineOpen, InlineClose: fc.Inline.InlineClose}
out.mergeBasics(&amp;tmp)
}</span>
// chat
- <span class="cov5" title="30">if strings.TrimSpace(fc.Chat.ChatSuffix) != "" || len(fc.Chat.ChatPrefixes) &gt; 0 </span><span class="cov1" title="1">{
+ <span class="cov4" title="23">if strings.TrimSpace(fc.Chat.ChatSuffix) != "" || len(fc.Chat.ChatPrefixes) &gt; 0 </span><span class="cov1" title="1">{
tmp := App{ChatSuffix: fc.Chat.ChatSuffix, ChatPrefixes: fc.Chat.ChatPrefixes}
out.mergeBasics(&amp;tmp)
}</span>
// provider
- <span class="cov5" title="30">if strings.TrimSpace(fc.Provider.Name) != "" </span><span class="cov2" title="4">{
+ <span class="cov4" title="23">if strings.TrimSpace(fc.Provider.Name) != "" </span><span class="cov3" title="6">{
tmp := App{Provider: fc.Provider.Name}
out.mergeBasics(&amp;tmp)
}</span>
// openai
- <span class="cov5" title="30">if !fc.OpenAI.isZero() || fc.OpenAI.Temperature != nil </span><span class="cov4" title="14">{
+ <span class="cov4" title="23">if !fc.OpenAI.isZero() || fc.OpenAI.Temperature != nil </span><span class="cov3" title="6">{
tmp := App{
OpenAIBaseURL: fc.OpenAI.BaseURL,
OpenAIModel: fc.OpenAI.resolvedModel(),
@@ -674,8 +826,18 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="30">{
out.mergeProviderFields(&amp;tmp)
}</span>
+ // openrouter
+ <span class="cov4" title="23">if (fc.OpenRouter != sectionOpenRouter{}) || fc.OpenRouter.Temperature != nil </span><span class="cov0" title="0">{
+ tmp := App{
+ OpenRouterBaseURL: fc.OpenRouter.BaseURL,
+ OpenRouterModel: fc.OpenRouter.Model,
+ OpenRouterTemperature: fc.OpenRouter.Temperature,
+ }
+ out.mergeProviderFields(&amp;tmp)
+ }</span>
+
// copilot
- <span class="cov5" title="30">if (fc.Copilot != sectionCopilot{}) || fc.Copilot.Temperature != nil </span><span class="cov2" title="3">{
+ <span class="cov4" title="23">if (fc.Copilot != sectionCopilot{}) || fc.Copilot.Temperature != nil </span><span class="cov2" title="4">{
tmp := App{
CopilotBaseURL: fc.Copilot.BaseURL,
CopilotModel: fc.Copilot.Model,
@@ -685,7 +847,7 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="30">{
}</span>
// ollama
- <span class="cov5" title="30">if (fc.Ollama != sectionOllama{}) || fc.Ollama.Temperature != nil </span><span class="cov2" title="3">{
+ <span class="cov4" title="23">if (fc.Ollama != sectionOllama{}) || fc.Ollama.Temperature != nil </span><span class="cov2" title="4">{
tmp := App{
OllamaBaseURL: fc.Ollama.BaseURL,
OllamaModel: fc.Ollama.Model,
@@ -696,7 +858,7 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="30">{
// prompts
// completion
- <span class="cov5" title="30">if (fc.Prompts.Completion != sectionPromptsCompletion{}) </span><span class="cov1" title="1">{
+ <span class="cov4" title="23">if (fc.Prompts.Completion != sectionPromptsCompletion{}) </span><span class="cov1" title="1">{
if strings.TrimSpace(fc.Prompts.Completion.SystemGeneral) != "" </span><span class="cov1" title="1">{
out.PromptCompletionSystemGeneral = fc.Prompts.Completion.SystemGeneral
}</span>
@@ -717,11 +879,11 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="30">{
}</span>
}
// chat
- <span class="cov5" title="30">if strings.TrimSpace(fc.Prompts.Chat.System) != "" </span><span class="cov1" title="1">{
+ <span class="cov4" title="23">if strings.TrimSpace(fc.Prompts.Chat.System) != "" </span><span class="cov1" title="1">{
out.PromptChatSystem = fc.Prompts.Chat.System
}</span>
// code action
- <span class="cov5" title="30">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" ||
+ <span class="cov4" title="23">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" ||
strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsSystem) != "" ||
strings.TrimSpace(fc.Prompts.CodeAction.DocumentSystem) != "" ||
strings.TrimSpace(fc.Prompts.CodeAction.RewriteUser) != "" ||
@@ -731,39 +893,39 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="30">{
strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" ||
strings.TrimSpace(fc.Prompts.CodeAction.SimplifySystem) != "" ||
strings.TrimSpace(fc.Prompts.CodeAction.SimplifyUser) != "" ||
- len(fc.Prompts.CodeAction.Custom) &gt; 0 </span><span class="cov4" title="17">{
+ len(fc.Prompts.CodeAction.Custom) &gt; 0 </span><span class="cov3" title="7">{
if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionRewriteSystem = fc.Prompts.CodeAction.RewriteSystem
}</span>
- <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsSystem) != "" </span><span class="cov1" title="1">{
+ <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsSystem) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionDiagnosticsSystem = fc.Prompts.CodeAction.DiagnosticsSystem
}</span>
- <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentSystem) != "" </span><span class="cov1" title="1">{
+ <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentSystem) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionDocumentSystem = fc.Prompts.CodeAction.DocumentSystem
}</span>
- <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteUser) != "" </span><span class="cov1" title="1">{
+ <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteUser) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionRewriteUser = fc.Prompts.CodeAction.RewriteUser
}</span>
- <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsUser) != "" </span><span class="cov1" title="1">{
+ <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsUser) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionDiagnosticsUser = fc.Prompts.CodeAction.DiagnosticsUser
}</span>
- <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentUser) != "" </span><span class="cov1" title="1">{
+ <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentUser) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionDocumentUser = fc.Prompts.CodeAction.DocumentUser
}</span>
- <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestSystem) != "" </span><span class="cov1" title="1">{
+ <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestSystem) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionGoTestSystem = fc.Prompts.CodeAction.GoTestSystem
}</span>
- <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" </span><span class="cov1" title="1">{
+ <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" </span><span class="cov1" title="1">{
out.PromptCodeActionGoTestUser = fc.Prompts.CodeAction.GoTestUser
}</span>
- <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifySystem) != "" </span><span class="cov0" title="0">{
+ <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifySystem) != "" </span><span class="cov0" title="0">{
out.PromptCodeActionSimplifySystem = fc.Prompts.CodeAction.SimplifySystem
}</span>
- <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifyUser) != "" </span><span class="cov0" title="0">{
+ <span class="cov3" title="7">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifyUser) != "" </span><span class="cov0" title="0">{
out.PromptCodeActionSimplifyUser = fc.Prompts.CodeAction.SimplifyUser
}</span>
- <span class="cov4" title="17">if len(fc.Prompts.CodeAction.Custom) &gt; 0 </span><span class="cov4" title="16">{
- for _, ca := range fc.Prompts.CodeAction.Custom </span><span class="cov5" title="30">{
+ <span class="cov3" title="7">if len(fc.Prompts.CodeAction.Custom) &gt; 0 </span><span class="cov3" title="6">{
+ for _, ca := range fc.Prompts.CodeAction.Custom </span><span class="cov3" title="10">{
out.CustomActions = append(out.CustomActions, CustomAction{
ID: strings.TrimSpace(ca.ID),
Title: strings.TrimSpace(ca.Title),
@@ -778,7 +940,7 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="30">{
}
}
// cli
- <span class="cov5" title="30">if (fc.Prompts.CLI != sectionPrompt