Documentation
¶
Overview ¶
Package wizard implements `yottacode setup` — the first-run setup flow. It collects provider profiles, default models, API keys, and a few session-stable knobs (router), then writes ~/.yottacode/config.toml plus ~/.yottacode/.env (mode 0600).
The wizard is a standalone Bubbletea program. It is independent of the chat TUI and can be invoked from a fresh terminal (`yottacode setup`), from inside the chat TUI (`/setup`, suspends), or auto-triggered when no config.toml exists at startup.
API keys never live in config.toml. Every Provider entry names an env-var via api_key_env; values land in ~/.yottacode/.env (chmod 0600) when the user enters them interactively. Already-set environment values are detected and not duplicated to disk.
Index ¶
- Variables
- func CatalogIdentity(profileName string) string
- func Decode(path string) (config.Config, error)
- func DetectEmbeddingModels(installed []string) []string
- func Encode(cfg config.Config) (string, error)
- func FreeFormModelPlaceholder(name string) string
- func OllamaPull(ctx context.Context, baseURL, model string) error
- func Run(ctx context.Context, opts Options) error
- type CatalogEntry
- type EmbeddingModelInfo
- type EnvSnapshot
- type OllamaProbeResult
- type Options
- type Plan
- type PlanProvider
- type ValidationResult
- type ValidationStatus
- type WriteOptions
- type WriteOutcome
Constants ¶
This section is empty.
Variables ¶
var Catalog = []CatalogEntry{
{
Name: "openai-auth",
Kind: "openai-auth",
BaseURL: "https://chatgpt.com/backend-api/codex",
APIKeyEnv: "",
Note: "OpenAI via ChatGPT login (browser OAuth)",
},
{
Name: "copilot-auth",
Kind: "copilot",
BaseURL: "https://api.githubcopilot.com",
APIKeyEnv: "",
Note: "GitHub Copilot (device code OAuth)",
},
{
Name: "anthropic",
Kind: "anthropic",
BaseURL: "https://api.anthropic.com",
APIKeyEnv: "ANTHROPIC_API_KEY",
Note: "Claude (Anthropic) — model list from internal/catalog (refresh via cmd/yotta-models)",
},
{
Name: "openai",
Kind: "openai",
BaseURL: "https://api.openai.com/v1",
APIKeyEnv: "OPENAI_API_KEY",
Note: "GPT + o-series (OpenAI) — model list from internal/catalog",
},
{
Name: "gemini",
Kind: "gemini",
BaseURL: "https://generativelanguage.googleapis.com",
APIKeyEnv: "GEMINI_API_KEY",
Note: "Gemini (Google) — model list from internal/catalog",
},
{
Name: "xai",
Kind: "xai",
BaseURL: "https://api.x.ai/v1",
APIKeyEnv: "XAI_API_KEY",
Note: "Grok (xAI) — model list from internal/catalog",
},
{
Name: "nvidia-nim",
Kind: "openai-compatible",
BaseURL: "https://integrate.api.nvidia.com/v1",
APIKeyEnv: "NVIDIA_API_KEY",
Note: "NVIDIA NIM — OpenAI-compatible endpoint at build.nvidia.com",
},
{
Name: "ollama",
Kind: "ollama",
BaseURL: "http://localhost:11434/v1",
APIKeyEnv: "",
Note: "local models via Ollama (auto-probed on localhost:11434)",
},
{
Name: "custom",
Kind: "openai-compatible",
BaseURL: "",
APIKeyEnv: "",
Note: "Custom OpenAI-compatible endpoint (vLLM, Llama Stack, Groq, Fireworks, OpenRouter, Together, ...)",
},
}
Catalog is the ordered list of providers offered in the wizard. Order matters: it's the screen order in the multi-select. Most-loved providers go first; "custom" lands at the bottom for power users.
var KnownEmbeddingModels = []EmbeddingModelInfo{
{Name: "nomic-embed-text", Desc: "recommended", Size: "~270 MB"},
{Name: "all-minilm", Desc: "lightweight", Size: "~45 MB"},
}
KnownEmbeddingModels lists the Ollama embedding models the wizard recognizes. Order is preference order: the first match in a probe result wins auto-detection.
Functions ¶
func CatalogIdentity ¶
CatalogIdentity returns the catalog entry name a profile was added from, or "" if no match. Renderers use this to display "nvidia-nim" for both "nvidia-nim" and "nvidia-nim-2" profiles instead of the generic "openai-compatible" kind. The lookup is:
- Exact match on the profile name (covers the first-add case where Name == catalog entry name verbatim).
- Strip a trailing "-<digits>" suffix (the uniqueProviderName output, e.g. "nvidia-nim-2" → "nvidia-nim") and re-look-up.
Returns "" when the profile name doesn't trace back to any catalog entry — typically a user-renamed profile or a free-form name that never came through the picker. Callers fall back to the kind in that case.
func Decode ¶
Decode parses an existing config.toml at path so the merge step can preserve the user's tunables (context, retrieval) across reruns. A missing file returns an empty default config and no error — that's the first-run case.
func DetectEmbeddingModels ¶ added in v0.3.0
DetectEmbeddingModels returns which known embedding models are already installed, given a list of Ollama model names from /api/tags. Returns them in preference order (nomic-embed-text first).
func Encode ¶
Encode marshals a config.Config back to TOML using the encoder. Used only by the merge path (when we want the encoder's section rendering for tunables); RenderTOML is preferred for [active] and [[providers]] because order matters there.
func FreeFormModelPlaceholder ¶
FreeFormModelPlaceholder picks the model-tag hint the textinput shows for free-form providers. The default tag shape varies by vendor — Anthropic uses "claude-<family>-<version>", OpenAI uses flat "gpt-X" / "o-N", Google uses "gemini-<gen>-<size>", Ollama uses "<family>:<size>", NIM uses "<org>/<model>". A single shared example would mislead more than half. The strings are recent shapes, not endorsements — the Note for each provider points to the live docs so users get current model names without us curating a stale list. Exported so internal/tui/provider_picker.go can reuse the same hints in the /provider Add form.
func OllamaPull ¶ added in v0.3.0
OllamaPull sends POST /api/pull to the Ollama server and blocks until the pull completes or the context expires. Returns nil on success.
func Run ¶
Run is the top-level entry point. It dispatches to the interactive Bubbletea program or the non-interactive resolver based on opts, then writes (or prints) the resulting Plan.
The non-interactive path is also exercised by `--print-only` — even an interactive `yottacode setup --print-only` skips the UI and just reports what defaults would produce, since printing is its own use case (CI bootstrap, "what would this do" inspection).
Types ¶
type CatalogEntry ¶
type CatalogEntry struct {
// Name is the user-facing label that appears in the wizard and
// becomes [[providers]].name in config.toml. Used by /provider use.
Name string
// Kind is the adapter family. Must be one of
// internal/config.ValidKinds (anthropic | openai | gemini | xai | ollama
// | openai-compatible). Drives both adapter dispatch and whether
// the wizard's stepConfigure pulls a curated model list from
// internal/catalog (anthropic/openai/gemini/xai) or falls back to
// free-form text input (ollama, openai-compatible).
Kind string
// BaseURL is the API endpoint. For Anthropic / Gemini we route by
// model prefix as well, so the base URL is informational; for
// OpenAI-compatible endpoints the URL is the routing decision.
BaseURL string
// APIKeyEnv is the env-var name that holds the bearer token.
// Empty for Ollama (local, no key). The wizard prompts for the
// value when the variable is not already set in the environment.
APIKeyEnv string
// Note is a one-line hint shown next to the provider in the
// multi-select. Keep short — anything longer than 50 chars wraps
// awkwardly on narrow terminals.
Note string
}
CatalogEntry describes one provider yottacode knows about out-of-the-box. It maps 1:1 onto a future config.Provider — name + kind + base URL + the env var to read the API key from. The curated *model* list (which models exist for this provider) used to live here too; it now lives in internal/catalog/catalog.gen.json and is consumed lazily by the wizard's stepConfigure picker and the /model TUI overlay. CatalogEntry is just the provider directory.
The catalog is a static Go map: changes ship with the binary. Network-fetched catalogs are tempting (always up-to-date!) but bring "first-run requires internet" failure modes, license/ToS questions per provider, and a freshness cliff when our own URL goes down. A few lines of Go is the right answer.
func FindCatalogEntry ¶
func FindCatalogEntry(name string) *CatalogEntry
FindCatalogEntry returns the catalog entry with the given name, or nil. Used by flag-driven non-interactive paths to translate `--provider anthropic` into a CatalogEntry.
type EmbeddingModelInfo ¶ added in v0.3.0
EmbeddingModelInfo describes a known Ollama embedding model that the wizard can offer to pull for semantic memory search.
type EnvSnapshot ¶
type EnvSnapshot struct {
// Present maps env-var name → true for every key in os.Environ
// that matches a known catalog entry's APIKeyEnv. Used to skip
// the key-prompt step and to render "key present in env" badges.
Present map[string]bool
// SuggestedProviders is the set of catalog entry names whose key
// is in Present, plus "ollama" if the local probe responded.
// Order follows Catalog so the UI is deterministic.
SuggestedProviders []string
}
EnvSnapshot is what the wizard learns from the live OS environment before any prompt: which provider keys are already exported, and a suggestion list so the multi-select can pre-check those rows.
func SnapshotEnv ¶
func SnapshotEnv() EnvSnapshot
SnapshotEnv inspects os.Environ for every catalog entry's APIKeyEnv and builds an EnvSnapshot. Pure read, no side effects.
type OllamaProbeResult ¶
type OllamaProbeResult struct {
Reachable bool
BaseURL string // the URL probed
Models []string // models reported by /api/tags, if any
Err error // last error encountered (for diagnostics)
}
OllamaProbeResult is the output of probing a local Ollama server.
func ProbeOllama ¶
func ProbeOllama(ctx context.Context, base string) OllamaProbeResult
ProbeOllama hits <base>/api/tags with a short timeout and returns the installed model names. The default base URL is http://localhost:11434 (or $OLLAMA_HOST). The wizard uses this result to (a) auto-suggest the Ollama provider and (b) populate the model picker for it.
Failure is silent — Ollama might just not be running. The wizard shows the catalog's free-form text input in that case.
type Options ¶
type Options struct {
// Force replaces an existing config.toml (after backing up).
// Without this flag, the wizard merges into the existing file.
Force bool
// PrintOnly renders the would-be config.toml + .env plan to
// out and returns without writing anything.
PrintOnly bool
// SkipValidation suppresses the per-key validation pings.
SkipValidation bool
// NonInteractive accepts defaults and never prompts. Required
// fields that can't be resolved from flags + env + catalog
// produce an error rather than a prompt.
NonInteractive bool
// FromEnv pre-enables every catalog entry whose API key is in
// the live environment, with the catalog's default model.
// Useful in CI / dotfile bootstrap. Composes with -y.
FromEnv bool
// Providers is the comma-separated list passed via --provider.
// Empty means "let the wizard ask."
Providers []string
// Active is the "<provider>" or "<provider>:<model>" form passed
// via --active.
Active string
// EnableRouter / RouterPolicy / NoRouter mirror the flags. The
// EnableRouter+NoRouter pair is enforced mutually-exclusive at
// the cobra layer.
EnableRouter bool
RouterPolicy string
NoRouter bool
// ConfigPath / EnvPath override defaults (~/.yottacode/...).
ConfigPath string
EnvPath string
// SkipEnvWrite prevents .env from being written. The wizard
// still records api_key_env names on each provider so the
// runtime knows where to look.
SkipEnvWrite bool
// Out is where stdout-style messages go: "wrote /path", the
// print-only TOML body, the post-write hint. nil → os.Stdout.
Out io.Writer
}
Options is what the cobra command translates flags into. Both the interactive and non-interactive paths receive the same struct; the interactive program reads from it for pre-fills (so re-running with `--provider anthropic` skips the picker), and the non-interactive path treats it as the complete answer set.
type Plan ¶
type Plan struct {
// Providers is the ordered list of enabled providers. Order
// matters: it's the candidate order for fallback-chain routing
// and the display order in /provider list.
Providers []PlanProvider
// Active picks the session-default provider:model. ActiveProvider
// must match one of Providers[].Name; ActiveModel must be present
// in that provider's Models.
ActiveProvider string
ActiveModel string
// EnableRouter and Router* control the [router] config block.
EnableRouter bool
RouterPolicy string // "fallback-chain" | "cheap-first"
RouterCandList []string // each "<provider>" or "<provider>:<model>"
// EnvKeys maps env-var name → secret value the user entered
// during the wizard. Already-set environment values are NOT in
// this map: we never copy a live key from os.Environ to .env.
// Keys here are written to ~/.yottacode/.env (chmod 0600).
EnvKeys map[string]string
// SkipEnvWrite, when true, suppresses the .env write entirely.
// The wizard still records api_key_env names on each provider so
// the runtime knows where to look — values are expected to come
// from the live shell or a secrets manager.
SkipEnvWrite bool
// RetrievalStrategy is the retrieval scoring strategy set by the
// wizard. Empty means "wizard didn't touch retrieval; preserve
// disk." Non-empty overrides [retrieval].strategy in config.toml.
RetrievalStrategy string
// EmbeddingModel is the Ollama embedding model chosen during
// setup. Empty means "wizard didn't set one; preserve disk."
// Non-empty overrides [retrieval].embedding_model.
EmbeddingModel string
}
Plan is the wizard's output: every decision, ready to be written. The wizard's interactive Bubbletea program builds this struct over its steps; the non-interactive path (`yottacode setup -y …`) builds the same struct directly from CLI flags + env. Either way, the downstream writers consume Plan exclusively — the steps are not privileged.
func (Plan) RenderEnv ¶
RenderEnv renders the .env file contents. Keys are emitted in alphabetical order so the file diffs stably across runs. Returns "" when there's nothing to write (no keys collected, or SkipEnvWrite=true), so callers can skip the write entirely.
func (Plan) RenderTOML ¶
RenderTOML renders the Plan into the canonical config.toml shape we want to write. Used by `--print-only` for a stdout dump and by the review screen for the diff view.
We hand-render the [[providers]] / [[providers.models]] sections in a fixed key order rather than relying on BurntSushi's encoder so the output is stable and reads top-to-bottom in the same order it was configured. The encoder's emit order is implementation-defined and changes between releases, which would surprise users running diffs.
func (Plan) ToConfig ¶
ToConfig converts the Plan into a config.Config with [[providers]], [active], and (optionally) [router] populated. Other sections — context, retrieval — are left at their zero values; the caller merges this into an existing config so those fields preserve what the user already had on disk.
type PlanProvider ¶
type PlanProvider struct {
Name string
Kind string
BaseURL string
APIKeyEnv string
DefaultModel string
// EnvAlreadySet is true when the wizard saw the API key already
// present in os.Environ. Purely informational — it's how the
// review screen says "key present in env" instead of "saved to
// .env."
EnvAlreadySet bool
}
PlanProvider mirrors a config.Provider but holds only the fields the wizard actually sets. The Models field (curated per-provider model list) was removed when internal/catalog/catalog.gen.json became the source of truth for the cloud-provider model list — the picker reads the catalog at /model time, the wizard only authors provider directory metadata + default_model.
type ValidationResult ¶
type ValidationResult struct {
Status ValidationStatus
HTTPStatus int // 0 when no response (DNS / timeout)
Detail string // short message — "ok", "401 Unauthorized", "timeout", …
}
ValidationResult bundles the outcome of one provider+key validation.
func ValidateKey ¶
func ValidateKey(ctx context.Context, e CatalogEntry, key string) ValidationResult
ValidateKey runs a 3-second-bounded HEAD/GET against a known "list-models" endpoint for the given catalog entry. The response is not parsed; we only care whether the key was accepted (2xx/3xx) or rejected (401/403). DNS / network / timeout failures land as ValidationUnknown — we don't pretend a flaky network is a bad key.
Per-kind endpoint:
- anthropic → GET https://api.anthropic.com/v1/models (header x-api-key + anthropic-version)
- openai → GET https://api.openai.com/v1/models
- openai-compatible → GET <base>/models
- gemini → GET https://generativelanguage.googleapis.com/v1/models?key=…
- ollama → never validated (no key)
type ValidationStatus ¶
type ValidationStatus int
ValidationStatus reports whether a provided API key works against the provider's models endpoint. The wizard surfaces this inline next to the key input as a glyph (✓ / ✗ / ⏳) — informational only; validation never blocks the wizard.
const ( // ValidationUnknown means we haven't probed yet, or probing was // skipped (e.g. --skip-validation, no key, no internet). ValidationUnknown ValidationStatus = iota ValidationOK ValidationFailed )
type WriteOptions ¶
type WriteOptions struct {
// ConfigPath defaults to ~/.yottacode/config.toml. Override for
// `--config /custom/path.toml` and tests.
ConfigPath string
// EnvPath defaults to ~/.yottacode/.env. Override for
// `--env-file` and tests.
EnvPath string
// Force=true backs up an existing config.toml to
// `<path>.bak-<unix-ts>` and replaces it. With Force=false the
// writer attempts a structural merge (existing tunables
// preserved; providers union'd by name).
Force bool
// SkipEnvWrite skips the .env step entirely. Mirrors
// Plan.SkipEnvWrite — supplied separately so the CLI flag wins
// independently of the Plan content.
SkipEnvWrite bool
}
WriteOptions controls where the wizard writes and whether it backs up. Defaults aim at the standard locations.
type WriteOutcome ¶
type WriteOutcome struct {
ConfigPath string
EnvPath string
BackupPath string // empty when no backup was made
WroteConfig bool
WroteEnv bool
WroteBackup bool
}
WriteOutcome reports what the writer actually did, so callers can surface the right "wrote /path" lines and the right backup hint.
func Apply ¶
func Apply(plan Plan, opts WriteOptions) (WriteOutcome, error)
Apply writes the Plan to disk per opts and returns a WriteOutcome. The function is the single entry point used by both the interactive wizard's "write" step and the non-interactive `yottacode setup -y` path. All writes are atomic (tmp+rename) and the .env permission is chmod'd to 0600 before rename.