Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions internal/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"os/exec"
"time"

"github.com/CoreyRDean/intent/internal/config"
"github.com/CoreyRDean/intent/internal/state"
Expand Down Expand Up @@ -107,6 +108,12 @@ func lookupKnown(c *config.Config, key string) string {
return c.UpdateChannel
case "auto_update":
return fmt.Sprintf("%t", c.AutoUpdate)
case "daemon.enabled", "daemon_enabled":
return fmt.Sprintf("%t", c.DaemonEnabled)
case "daemon.idle_unload_after", "daemon_idle_unload_after":
return c.DaemonIdleUnloadAfter.String()
case "cache.enabled", "cache_enabled":
return fmt.Sprintf("%t", c.CacheEnabled)
}
return ""
}
Expand All @@ -123,5 +130,13 @@ func setKnown(c *config.Config, key, value string) {
c.UpdateChannel = value
case "auto_update":
c.AutoUpdate = value == "true" || value == "yes"
case "daemon.enabled", "daemon_enabled":
c.DaemonEnabled = value == "true" || value == "yes"
case "daemon.idle_unload_after", "daemon_idle_unload_after":
if d, err := time.ParseDuration(value); err == nil {
c.DaemonIdleUnloadAfter = d
}
case "cache.enabled", "cache_enabled":
c.CacheEnabled = value == "true" || value == "yes"
}
}
35 changes: 35 additions & 0 deletions internal/cli/smoke_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,41 @@ func TestConfigRoundTrip(t *testing.T) {
}
}

func TestConfigRoundTripSectionedKnownKey(t *testing.T) {
stateDir := t.TempDir()
cacheDir := t.TempDir()
baseEnv := []string{
"HOME=" + os.Getenv("HOME"),
"PATH=" + os.Getenv("PATH"),
"INTENT_STATE_DIR=" + stateDir,
"INTENT_CACHE_DIR=" + cacheDir,
}

cmd1 := exec.Command(testBinary, "config", "set", "daemon.enabled", "false")
cmd1.Env = baseEnv
if out, err := cmd1.CombinedOutput(); err != nil {
t.Fatalf("config set daemon.enabled: %v\n%s", err, out)
}

cmd2 := exec.Command(testBinary, "config", "get", "daemon.enabled")
cmd2.Env = baseEnv
out, err := cmd2.Output()
if err != nil {
t.Fatalf("config get daemon.enabled: %v", err)
}
if got := strings.TrimSpace(string(out)); got != "false" {
t.Fatalf("config get daemon.enabled: got %q, want %q", got, "false")
}

cfgBody, err := os.ReadFile(filepath.Join(stateDir, "intent", "config.toml"))
if err != nil {
t.Fatalf("read config file: %v", err)
}
if !strings.Contains(string(cfgBody), "daemon.enabled = false") {
t.Fatalf("config file missing spec-style daemon.enabled key:\n%s", string(cfgBody))
}
}

func TestConfigSetRejectsRemoteDaemonHost(t *testing.T) {
stateDir := t.TempDir()
cacheDir := t.TempDir()
Expand Down
18 changes: 10 additions & 8 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,13 @@ func read(path string) (*Config, error) {
c.UpdateChannel = v
case "auto_update":
c.AutoUpdate = parseBool(v)
case "daemon_enabled":
case "daemon_enabled", "daemon.enabled":
c.DaemonEnabled = parseBool(v)
case "daemon_idle_unload_after":
case "daemon_idle_unload_after", "daemon.idle_unload_after":
if d, err := time.ParseDuration(v); err == nil {
c.DaemonIdleUnloadAfter = d
}
case "cache_enabled":
case "cache_enabled", "cache.enabled":
c.CacheEnabled = parseBool(v)
}
}
Expand Down Expand Up @@ -165,15 +165,17 @@ func Write(path string, c *Config) error {
fmt.Fprintf(w, "timeout = %q\n", c.Timeout.String())
fmt.Fprintf(w, "update_channel = %q\n", c.UpdateChannel)
fmt.Fprintf(w, "auto_update = %t\n", c.AutoUpdate)
fmt.Fprintf(w, "daemon_enabled = %t\n", c.DaemonEnabled)
fmt.Fprintf(w, "daemon_idle_unload_after = %q\n", c.DaemonIdleUnloadAfter.String())
fmt.Fprintf(w, "cache_enabled = %t\n", c.CacheEnabled)
fmt.Fprintf(w, "daemon.enabled = %t\n", c.DaemonEnabled)
fmt.Fprintf(w, "daemon.idle_unload_after = %q\n", c.DaemonIdleUnloadAfter.String())
fmt.Fprintf(w, "cache.enabled = %t\n", c.CacheEnabled)
// Persist unknown raw keys that are not covered by the known struct fields.
knownFields := map[string]bool{
"backend": true, "model": true, "auto_run": true, "sandbox": true,
"max_tool_steps": true, "timeout": true, "update_channel": true,
"auto_update": true, "daemon_enabled": true,
"daemon_idle_unload_after": true, "cache_enabled": true,
"auto_update": true,
"daemon_enabled": true, "daemon.enabled": true,
"daemon_idle_unload_after": true, "daemon.idle_unload_after": true,
"cache_enabled": true, "cache.enabled": true,
}
for k, v := range c.Raw {
if !knownFields[k] {
Expand Down
77 changes: 77 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
)
Expand All @@ -19,6 +20,8 @@ base_url = "https://example.test/v1"
model = "gpt-4.1-mini"

[daemon]
enabled = false
idle_unload_after = "45m"
host = "127.0.0.1"
port = "18080"

Expand Down Expand Up @@ -53,4 +56,78 @@ enabled = true
if got := cfg.Raw["daemon.port"]; got != "18080" {
t.Fatalf("daemon.port=%q want %q", got, "18080")
}
if cfg.DaemonEnabled {
t.Fatalf("daemon enabled=%t want false", cfg.DaemonEnabled)
}
if cfg.DaemonIdleUnloadAfter != 45*time.Minute {
t.Fatalf("daemon idle unload after=%s want %s", cfg.DaemonIdleUnloadAfter, 45*time.Minute)
}
if !cfg.CacheEnabled {
t.Fatal("cache enabled=false want true")
}
}

func TestReadSupportsLegacyFlatAliases(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.toml")
if err := os.WriteFile(path, []byte(`
daemon_enabled = false
daemon_idle_unload_after = "15m"
cache_enabled = false
`), 0o600); err != nil {
t.Fatalf("write config: %v", err)
}

cfg, err := read(path)
if err != nil {
t.Fatalf("read config: %v", err)
}
if cfg.DaemonEnabled {
t.Fatalf("daemon enabled=%t want false", cfg.DaemonEnabled)
}
if cfg.DaemonIdleUnloadAfter != 15*time.Minute {
t.Fatalf("daemon idle unload after=%s want %s", cfg.DaemonIdleUnloadAfter, 15*time.Minute)
}
if cfg.CacheEnabled {
t.Fatal("cache enabled=true want false")
}
}

func TestWriteUsesSpecStyleDaemonAndCacheKeys(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.toml")
cfg := Defaults()
cfg.DaemonEnabled = false
cfg.DaemonIdleUnloadAfter = 45 * time.Minute
cfg.CacheEnabled = false
cfg.Raw["daemon.host"] = "127.0.0.1"
cfg.Raw["backends.openai.base_url"] = "https://example.test/v1"

if err := Write(path, cfg); err != nil {
t.Fatalf("write config: %v", err)
}

body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read config: %v", err)
}
got := string(body)
for _, want := range []string{
`daemon.enabled = false`,
`daemon.idle_unload_after = "45m0s"`,
`cache.enabled = false`,
`daemon.host = "127.0.0.1"`,
`backends.openai.base_url = "https://example.test/v1"`,
} {
if !strings.Contains(got, want) {
t.Fatalf("config missing %q:\n%s", want, got)
}
}
for _, unwanted := range []string{
"daemon_enabled =",
"daemon_idle_unload_after =",
"cache_enabled =",
} {
if strings.Contains(got, unwanted) {
t.Fatalf("config unexpectedly contained legacy key %q:\n%s", unwanted, got)
}
}
}
Loading