From eb2ae84ff7e0c8eac583d17cb2854d4ec801d35c Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 26 Apr 2026 23:38:29 -0700 Subject: [PATCH] feat: add configure command --- internal/cli/app.go | 52 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/internal/cli/app.go b/internal/cli/app.go index b44f0fa..2ab2094 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -92,7 +92,9 @@ func (a *App) Run(ctx context.Context, args []string) error { return a.runRuns(ctx, rest[1:]) case "search": return a.runSearch(ctx, rest[1:]) - case "configure", "refresh", "summarize", "key-summaries", "embed", "cluster", "cluster-experiment", "clusters", "durable-clusters", "cluster-detail", "cluster-explain", "neighbors", "close-thread", "close-cluster", "exclude-cluster-member", "include-cluster-member", "set-cluster-canonical", "merge-clusters", "split-cluster", "export-sync", "import-sync", "validate-sync", "portable-size", "sync-status", "optimize", "tui", "completion": + case "configure": + return a.runConfigure(rest[1:]) + case "refresh", "summarize", "key-summaries", "embed", "cluster", "cluster-experiment", "clusters", "durable-clusters", "cluster-detail", "cluster-explain", "neighbors", "close-thread", "close-cluster", "exclude-cluster-member", "include-cluster-member", "set-cluster-canonical", "merge-clusters", "split-cluster", "export-sync", "import-sync", "validate-sync", "portable-size", "sync-status", "optimize", "tui", "completion": _ = ctx return notImplemented(rest[0]) default: @@ -100,6 +102,54 @@ func (a *App) Run(ctx context.Context, args []string) error { } } +func (a *App) runConfigure(args []string) error { + fs := flag.NewFlagSet("configure", flag.ContinueOnError) + fs.SetOutput(io.Discard) + summaryModel := fs.String("summary-model", "", "summary model") + embedModel := fs.String("embed-model", "", "embedding model") + embeddingBasis := fs.String("embedding-basis", "", "embedding basis") + jsonOut := fs.Bool("json", false, "write JSON output") + if err := fs.Parse(args); err != nil { + return usageErr(err) + } + a.applyCommandJSON(*jsonOut) + + cfg, err := config.Load(a.configPath) + configExists := true + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + return err + } + configExists = false + cfg = config.Default() + } + updated := false + if strings.TrimSpace(*summaryModel) != "" { + cfg.OpenAI.SummaryModel = strings.TrimSpace(*summaryModel) + updated = true + } + if strings.TrimSpace(*embedModel) != "" { + cfg.OpenAI.EmbedModel = strings.TrimSpace(*embedModel) + updated = true + } + if strings.TrimSpace(*embeddingBasis) != "" { + cfg.EmbeddingBasis = strings.TrimSpace(*embeddingBasis) + updated = true + } + if updated || !configExists { + if err := config.Save(a.configPath, cfg); err != nil { + return err + } + } + return a.writeOutput("configure", map[string]any{ + "config_path": config.ResolvePath(a.configPath), + "updated": updated || !configExists, + "summary_model": cfg.OpenAI.SummaryModel, + "embed_model": cfg.OpenAI.EmbedModel, + "embedding_basis": cfg.EmbeddingBasis, + }, true) +} + func (a *App) runSearch(ctx context.Context, args []string) error { fs := flag.NewFlagSet("search", flag.ContinueOnError) fs.SetOutput(io.Discard)