Config
Data directories, config.json v1, .spirit/, and environment variables.
Shared user data follows the build quality, not the operating system's application-data directory.
Data directory
Override with SPIRIT_DATA_DIR. The server can also use SPIRIT_SERVER_DATA_DIR (wins over the former).
| Build | macOS / Linux | Windows |
|---|---|---|
| Stable | ~/.spirit | %USERPROFILE%\.spirit |
| Dev | ~/.spirit-dev | %USERPROFILE%\.spirit-dev |
Breaking change: old platform data directories are not read or migrated automatically. Explicit data-directory overrides remain supported. Electron caches, browser storage, and window state remain in the private platform application directory.
Typical files:
{spiritDataDir}/
config.json
mcp.json
hooks.json
rule.md
skills/
chats/
plans/
extensions/
automations/
dreams/
server.token
server/instances/CLI binaries use SPIRIT_HOME. On Unix, the Stable install and shared data may have the same parent directory; bin/ and cli/ remain installation files. The installer setting does not select the shared-data directory.
config.json v1
schemaVersion must be 1. Any other value is rejected — delete the old file and reconnect a provider. There is no migration from older shapes.
Core fields:
providers— connected providers: endpoint, transport, credentials, modelsactiveModelimageGenerationModel/videoGenerationModel/secondaryLanguageModelagents— agent toggles such asagents.providerTools.enabledpermission— permission allowlist rules
activeModel and the other model slots are model refs: { "providerId": "…", "name": "…" }, where providerId is a providers[].id and name is one of its models[].name.
Provider entries
Each providers[] entry is a fully declarative connection — what the connect wizard writes is what the runtime reads. The runtime never re-derives apiBase from the provider's identity; site, plan, and cloud choices are baked into the final URL at connect time.
Connection-level fields:
id(required) — unique entry id. Model refs and the internal keyring account point at itlabel— optional display nameapiBase(required) — the endpoint, the single source of truthtransportKind— wire protocol:openai-compatible(default) /open-responses/anthropic/bedrocksdk— full npm package name of the bundled SDK adapter (for example@ai-sdk/openai-compatible). Absent = the transport default; an unknown package name is rejected at loadheaders— literal extra request headers merged into every outbound requestbody— literal fields merged into every request body. An escape hatch for odd wire quirks — prefer a structured declaration where one existsproviderTools— tools the provider exposes (web search, code interpreter). See Provider ToolsfileUpload— the upload channel for local files that should not go inline (path/url,purpose,idPath,refTemplate,maxBytes). Absent = media go inlineimageApi/videoApi— API shape names for image / video generation; absent = no such generationoptions— transport parameters; the legal key set depends ontransportKind×sdk(awsRegionfor bedrock,project/locationfor@ai-sdk/google-vertex,auth/storedState/applyPatchswitches on open-responses). Unknown keys are rejectedmodels(required array, may be empty)
Model-level fields — capabilities, contextLength, outputLimit, reasoningEffort, supportedReasoningEfforts, reasoningMode, supportedReasoningModes, supportsThinkingType, supportsThinkingSwitch, thinkingSwitch, serviceTiers, serviceTier — may also be declared flat on the provider entry as defaults inherited by every model; a model's own declaration wins. Inside models[], everything except name is optional, and absent means undeclared, not a default.
capabilitiestokens:languageModel,image,audio,video,imageGeneration,videoGenerationoutputLimitis the output token cap, separate fromcontextLengthsupportedReasoningEfforts/supportedReasoningModes/serviceTiersare the sole picker and validation sources — an out-of-list selection is a load-time error, never silently clampedthinkingEnabledis your thinking on/off state (not a capability); it is model-level only and requiressupportsThinkingSwitchdeclared
Key channels
Keys do not have to live in this file — the connect wizard writes them to the keyring by default. Config may instead declare a channel, resolved in this order:
apiKey— plaintext. Not recommended; an escape hatch onlyapiKeyEnv— the name of an environment variable to read the key fromapiKeyKeyring—{ "service": "…", "account": "…" }, an entry in an external keyring- Internal keyring — the implicit fallback written by the wizard (account
provider::{id})
Composite credentials (Bedrock IAM, Vertex service accounts) use a credentials slot map instead of the flat keys:
{
"credentials": {
"accessKeyId": { "env": "AWS_ACCESS_KEY_ID" },
"secretAccessKey": { "keyring": { "service": "…", "account": "…" } }
}
}Slot names are protocol fields — apiKey, accessKeyId / secretAccessKey (Bedrock IAM / SigV4), clientEmail / privateKey (Vertex) — and unknown slots are rejected. Each slot resolves value → env → keyring → internal keyring (provider::{id}::{slot}; the apiKey slot is plain provider::{id}). Flat keys and credentials.apiKey cannot coexist in one entry.
agents.providerTools
agents.providerTools.enabled gates all provider-declared tools at once (renamed from agents.webSearch.enabled; the semantics did not change: opt-out, on by default). Set to false, no provider tool entries or body switches reach requests.
Catalog refresh and reconnect
Routine catalog refreshes rewrite the model declaration fields the catalog owns (capabilities, supportedReasoningEfforts, supportedReasoningModes, serviceTiers, …) — model facts come from the catalog as the single source of truth, so manual edits to those fields are overwritten on the next refresh. Provider-level materializations (apiBase, sdk, providerTools, fileUpload, options, imageApi / videoApi) change only when you reconnect the provider. Reconnect resets the wizard-owned keys (edits you made to them are overwritten) and preserves everything else: label, credential channels, headers / body, flat model defaults, and models you added by hand.
permission
permission pins down what individual shell commands and file paths may do, on top of the session approval level. Keys are tool names (shell, read_file); values map wildcard patterns to allow, ask, or deny.
{
"permission": {
"shell": {
"git *": "allow",
"rm -rf *": "deny"
}
}
}deny is absolute — no approval level, hook, or session memory overrides it. Pattern syntax, path semantics, and the full action list are in Approvals. Verify rules without running anything: spirit permission check --shell "git status".
The old tool-permissions.json (written by the "Always Trust" button) is deprecated: the code no longer reads or writes it, and there is no automatic migration. Leftover files are inert — delete them manually.
Workspace .spirit/
| Path | Role |
|---|---|
.spirit/rule.md | Workspace rule |
.spirit/skills/ | Workspace skills |
.spirit/mcp.json | Workspace MCP |
.spirit/hooks.json | Workspace hooks |
Also used: {workspace}/AGENTS.md, {workspace}/.agents/skills/, {workspace}/plans/.
Environment variables
| Variable | Role |
|---|---|
SPIRIT_DATA_DIR | Data directory |
SPIRIT_SERVER_DATA_DIR | Server data directory (wins) |
SPIRIT_ACP_DATA_DIR / SPIRIT_ACP_WORKSPACE | ACP |
SPIRIT_HOME / SPIRIT_VERSION / SPIRIT_DOWNLOAD_HOST | CLI installer |
SPIRIT_API_KEY / SPIRIT_API_BASE | CLI process overrides |
SPIRIT_WEB_HOST / SPIRIT_WEB_PORT / SPIRIT_DESKTOP_WEB_HOST | Desktop Web Host |
SPIRIT_TERMINAL_SHELL | Desktop PTY shell |