SPAVN docs v0.2.0 GitHub

The complete SPAVN reference

One Rust binary, no async runtime, three direct dependencies. Everything below is taken from the shipped source: 11 builtin tools, 37 slash commands, every config key, and the six gates that stand between a model and your filesystem. Press / or ⌘K to search.

v0.2.0 11 tools 37 commands 6 gates JSON-RPC 2.0 3 direct deps MIT
01 · Start

Overview#

SPAVN is a terminal coding agent: you describe a change, it reads the repo, edits files, runs commands and reports back. It runs as a single native binary with a synchronous TUI — there is no Node, no Electron, no plugin tree, and no daemon.

The design bets

BetWhat it means in the code
Gates live in code, never in the promptAccess, phase, path, egress and MCP checks are if statements in guard.rs / tools.rs. A model cannot talk its way past them.
No hidden network peersThe binary talks to the endpoint you configure and to processes you name in mcpServers. Tested by a source scan, not a promise.
Config over hardcodeSearch backend, providers, hosts, tools and the update endpoint all come from ~/.spavn/config.json. No vendor host ships in the binary.
One binary, boring depsserde, serde_json, ureq. No async runtime; threads only where a child process would otherwise block.

Autonomous harness intelligence (v0.2.0)

LayerModuleHow it works
Zero-Stop Verification Gatesrc/turn/mod.rsAutomatically runs cargo check --quiet, tsc --noEmit, python3 -m py_compile, or go vet when a turn modifies source files, feeding compiler diagnostics back into the turn loop before stopping.
Signature & Arity-Aware Symbol Mapsrc/tools/search.rsExtracts exact function, method, struct, and type signatures with 1-based line numbers across modified and neighboring files ([SPAVN LIVE PROJECT SIGNATURES]) so models never hallucinate parameter counts.
Fuzzy Self-Heal edit Enginesrc/tools/edit.rsRecovers from intra-line whitespace/indentation drift (fuzzy_find_unique_line_block), auto-expands off-by-N line ranges (±25 lines), and supports multi-hunk edits[] plus cross-file atomic files[] transactions.
Stale-File Guard (FS_STALE_VERSION)src/tools/edit.rsTracks per-file SystemTime snapshots on read and refuses edit / write calls when a file was modified externally since it was last read.
Canonical Repeat-Tool Breakersrc/turn/mod.rsRecursively canonicalizes JSON tool arguments (sorted object keys) and injects [SPAVN REPEAT-TOOL BREAKER] after 2 identical read-only calls to break infinite loops.
Turn-Aware Schema & Budget Compactionsrc/tools/schema.rs, src/session/compactor.rsTrims tool schema verbosity after Turn 0 and scales context pruning to the active model's context_window.
Transactional /undo Stacksrc/tools/edit.rsSnapshots files before every write / edit for instant single or multi-step rollback via /undo [N].

At a glance

Version0.2.0 · MIT
PlatformsmacOS (arm64, x86_64) · Linux (x86_64, arm64) · Windows (x86_64)
Rust1.80+ (uses LazyLock)
State~/.spavn · mode 0700, secrets 0600
External toolsripgrep for the search tool · ssh for remote hosts
Bridgespavn --rpc headless NDJSON JSON-RPC 2.0 stdio server for spavn-desktop & IDEs
TUISynchronous, alternate scroll region, no flicker, no cursor ghost
02 · Start

Install#

Five channels. Pick one; they all install the same binary.

ChannelCommandUpdate with
Install scriptcurl -fsSL https://spavn.dev/install.sh | shre-run it
Homebrewbrew install tugayoktayokay/tap/spavnbrew upgrade spavn
Cargo (git)cargo install --git https://github.com/tugayoktayokay/spavn-clire-run it
Prebuilt archiveGitHub Releases + .sha256 sidecardownload again
From sourcegit clone … && cargo build --releasegit pull

Install script

curl -fsSL https://spavn.dev/install.sh | sh

# custom target directory
SPAVN_INSTALL_DIR=/usr/local/bin sh install.sh

# build from a local binary instead of downloading
SPAVN_LOCAL_BIN=./target/release/spavn sh install.sh

Lands in $HOME/.local/bin/spavn plus the spvn alias, then executes the binary once to prove execve works. Staging uses a fresh inode and an ad-hoc signature so macOS amfid does not kill the result.

Homebrew

brew install tugayoktayokay/tap/spavn
brew upgrade spavn        # later

# Homebrew 6+ trusts third-party taps explicitly:
brew trust tugayoktayokay/tap

The formula lives in tugayoktayokay/homebrew-tap and is generated, never hand-edited: packaging/homebrew/sync-tap.sh <version> <tap-checkout> pulls each .sha256 sidecar and rewrites the URLs.

Cargo

cargo install --git https://github.com/tugayoktayokay/spavn-cli   # tracks main
cargo install spavn                                             # once published
Note cargo install produces only spavn. The spvn alias comes from the install script and the Homebrew formula.

Verify a download

gh release download v0.2.0 --pattern '*aarch64-apple-darwin*'
shasum -a 256 -c ./*.sha256
03 · Start

Quickstart#

spavn                            # interactive TUI in the current directory
spavn "explain the spill module" # one-shot prompt, still interactive
spavn -p "fix the failing test" -q   # headless: print and exit
spavn --resume                   # session picker
spavn --help                     # every flag, path and env var

Flags

FlagEffect
-m, --modelModel key, e.g. sonnet (claude-sonnet-5-5), opus (claude-opus-5-5), ollama:qwen2.5-coder
-a, --accesssafe · auto · sovereign
-P, --phaseplan (read-only) · code
-H, --hostTarget host from hosts[]
-s, --sessionNamed session file
-c, --continueResume the most recent session
-r, --resumeInteractive session picker
-i, --interactiveForce the TUI even with a positional prompt
-p, --promptExplicit prompt (same as positional)
-t, --thinkShow the reasoning stream
-q, --quietHeadless: no HUD
-j, --jsonMachine-readable output
--rpcHeadless JSON-RPC 2.0 stdio bridge (prompt, abort, set_phase, set_model, get_state)
-V, --versionPrint version, OS and arch
-h, --helpFull reference

First five minutes

  1. spavn — the TUI opens with a dock: model, access mode, phase, host, context use.
  2. /byok — paste a provider key (stored in ~/.spavn/secrets.env, 0600) or start a local model instead.
  3. /model — pick from local → BYOK → catalog → free tier.
  4. /access auto — read-only calls run freely, mutations ask.
  5. /plan then describe the change; /code when you are ready to write.
Keys never touch config.json config.json holds endpoints and key names; the material lives in secrets.env, which the path policy treats as secret material in every mode.
04 · Start

Models & routing#

One picker, four sources, resolved in this order: local → BYOK credits → provider catalog → free tier. Zero vendor lock-in: bring your own local GPU server or API key.

Local models

# Ollama ships as a provider group at http://127.0.0.1:11434/v1 — zero config
spavn --model ollama:qwen2.5-coder "refactor the spill module"

# Other OpenAI-compatible servers: point discovery at the /v1 root once
#   LM Studio   http://localhost:1234/v1
#   llama.cpp   http://localhost:8080/v1
export SPAVN_LOCAL_MODELS_URL=http://localhost:8080/v1
export SPAVN_LOCAL_BASE_URL=http://localhost:8080/v1

Your own gateway

"providers": [
  {
    "id": "my-gw",
    "title": "My gateway",
    "base_url": "https://gw.example.test/v1",
    "api_key_env": "MY_GW_KEY",
    "models": [{ "key": "fast", "model_id": "gw-fast-1", "desc": "cheap", "context_window": 32768 }]
  }
]

Set byokCatalog: false to drop the built-in catalog entirely and drive the picker from providers alone.

/bench and /fleet

/bench runs one prompt across every candidate and reports TTFT, tokens/sec and whether the tool-call format actually parsed — JSON, NO TOOL CALL, or a malformed call. /fleet probes every hosts[] entry over ssh -o ConnectTimeout=3 and prints round-trip latency plus the host's uptime line.

05 · Reference

Slash commands#

37 entries, all with aliases. /help prints the same set in the TUI; / opens the menu.

CommandAliasesEffect
/model/m /models /localModel picker: local → BYOK → catalog → free
/byok/b /keys /login /provider /providers /connect /authKey setup, balance, provider login
/bench—Tournament across candidate models
/fleet/fHost panel with latency probes
/quota/qQuota badge / pool from configured scripts
/host/hTarget host selection
/access/asafe 🔒 · auto ⚡ · sovereign 🔥
/plan—Lock to the read-only design phase
/code/actUnlock writes
/role—auto · coder · reviewer · security · test · docs · refactor
/think—Toggle the reasoning stream
/recap—Idle recap; on / off / toggle
/todo/tasksLive task list; interactive arrow-key /todo done picker, clear
/queue/qlist /qeditInspect / edit queued prompts
/queue clear/qclear /qcancelDrop the queue and any steers
/queue pop/qpopPop the last queued prompt
/new/nFresh session
/resume/s /sessionsSession picker
/fork—Branch the current session
/compact/cCompact context
/clear—Clear the transcript
/reload/r /rbRebuild and re-exec, session preserved
/update/upgrade /updCheck for a newer release; prints the exact upgrade command
/exit/quit /x :qLeave
/stop/cancelInterrupt a running turn
/diff/dWorking-tree diff
/commit/cmCommit helper
/undo/uRevert the last N edits via transactional UndoStack (/undo [N])
/janitor/jDead code / stray artifact sweep
/audit—HMAC-chained action log, filterable by file
/blame—Attribution for a path
/pin/p /pinsContext pins: add / list / remove
/goal/g /goalsSession goals
/wiki/wProject wiki view
/skill/s /skillsSkill discovery and listing
/lang/language /dil /en /trUI language
/help—This list

Keyboard

KeysAction
EnterSend
Shift+EnterNewline
↑ ↓History / menu navigation
TabComplete a slash command or @file
Ctrl+CInterrupt the running turn
Ctrl+DExit
EscClose the menu / cancel the picker

Piping into a busy session

busy_input_mode decides what happens when you type during a turn: queue (default) defers to the next turn, steer interrupts and redirects the model mid-flight.

06 · Reference

Tools#

Eleven builtins, each a fixed contract in tool_definitions(). Anything else comes from an mcpServers entry.

ToolPurposeGate
readFile, directory, SQLite, PDF or image; disjoint ranges (src/x.rs:97-112,1066-1076), records mtime snapshotpath policy
editFuzzy self-heal old_text → new_text, auto line-window expansion, multi-hunk edits[], cross-file atomic files[], stale-file guardwrite gate
writeCreate or overwrite with stale-file guard (FS_STALE_VERSION); <host>:/path for remotewrite gate
bashLocal or remote; cwd, timeout_sec, background (PID + log path)command classifier
searchRegex content search via ripgreppath policy
globFile listing; skips node_modules, target, .git unless hiddenpath policy
memoryRecall or record decisions; local store or an external binaryread-only tier
skillLoad domain instructions from ~/.spavn/skills, ~/.claude/skillsread-only tier
webSearch or fetch a URL as Markdown; redirects re-validated per hopegress classifier
subagentDelegate a read-only investigation with its own context windowcannot mutate
lspDefinitions, references, symbols; also the generic MCP callread-only tier

Points worth knowing

  • Oversized output spills, it is not truncated away. bash keeps 8 MiB per stream in ~/.spavn/tool-output/ (newest 40 kept, 0600) and tells the model the path instead of silently dropping bytes.
  • read beats bash cat. Range syntax avoids re-reading a whole file and never spawns a shell.
  • subagent cannot write. Investigate in parallel, then apply the change yourself.
  • web has no built-in endpoint. Search stays off until you point webSearchUrl or webSearchServer at a backend.
07 · Reference

MCP servers#

The extension path. There is no builtin desktop-control tool and no hardcoded path to one — the catalog is read from your config, so nothing assumes a vendor or an install location.

{
  "mcpServers": {
    "memory":  { "command": "noospire-memory", "args": ["--stdio"], "readOnly": true },
    "desktop": { "command": "open-computer-use", "args": ["mcp"], "readOnly": false },
    "remote":  { "url": "https://bridge.example.test/mcp", "readOnly": true }
  }
}
// the model reaches any of them through the generic MCP call
{ "op": "mcp", "server": "desktop", "tool": "click", "arguments": { "x": 640, "y": 320 } }

Rules

  • Every configured server name is listed in the lsp tool description, so the model finds them without a second prompt.
  • Servers not in mcpServers are unreachable.
  • readOnly: true declares no side effects; without it, calls pass the mutation gate.
  • In PLAN phase a mutating MCP call is refused outright — no prompt, no override.
  • Stdio servers get a scrubbed environment: only what env declares survives.
  • url selects a streamable-HTTP server instead of a subprocess.
Environment is not inherited A stdio server inherits nothing, including PATH, unless you list it in env. That is deliberate: a tool cannot read your shell secrets by accident.
08 · Reference

Config#

~/.spavn/config.json. Every key is optional; an absent file is a working default. Both camelCase and snake_case spellings parse.

{
  "default_model": "sonnet",
  "busy_input_mode": "queue",
  "language": "en",
  "byokCatalog": true,
  "updateCheck": true,
  "updateCheckIntervalHours": 24,
  "providers": [],
  "mcpServers": {},
  "hosts": [{ "key": "buildbox", "ssh_target": "deploy@10.0.0.5", "badge": "BB", "sensitive": true }]
}

General

KeyMeaning
default_modelModel key used at startup
fallback_cloud_modelUsed when the primary is unreachable
busy_input_modesteer interrupts mid-turn, queue defers
languagetr · en
byokCatalogtrue merges the built-in catalog; false runs the list from providers only
updateCheckOne background release check per interval
updateCheckIntervalHoursHours between checks, minimum 1, default 24
updateCheckUrlEndpoint answering with tag_name; unset derives from the packaged repo, empty disables

Web / search backends

KeyMeaning
webSearchServer webSearchToolRun web search through that mcpServers entry
webFetchToolTool used for page fetch on that server; unset keeps the built-in fetch path
webSearchUrlHTTP MCP endpoint for web. Empty or unset means no HTTP backend
webSearchHttpTool webFetchHttpToolTool names your endpoint serves, if they differ
webSearchApiKeyHeader webSearchApiKeyEnvHeader and env var carrying the endpoint's key
No vendor host ships in the binary Search is off until you point webSearchUrl or webSearchServer somewhere. The refusal message names both routes, so an unconfigured web teaches instead of failing dumbly.

Local discovery and paths

KeyMeaning
local_discovery_urlOpenAI-compatible /v1 URL probed for local models
local_base_urlPins the base URL for local / local:<id> keys
protected_paths allowed_pathsPath policy inputs, ~ expanded
quota_quick_script quota_full_scriptCommands whose output becomes the quota badge / picker pool
memory_recall_bin memory_remember_binExternal recall bridge instead of the local store

Remote hosts

KeyMeaning
hosts[].keyAlias used as host in tool calls
hosts[].ssh_targetssh destination; local means "never remote"
hosts[].sensitiveRequire approval in auto; remote hosts are sensitive even without the flag

read, write, bash, glob and search all accept <host>:/path, so one investigation can span machines.

MCP servers

KeyMeaning
mcpServers[].command argsStdio server; ~ expanded, environment scrubbed
mcpServers[].envThe only variables the child inherits
mcpServers[].urlStreamable-HTTP server instead of a subprocess
mcpServers[].readOnlyDeclares no side effects; without it, calls pass the mutation gate
09 · Reference

Environment variables#

VariableEffect
SPAVN_HOMEConfig and state root instead of ~/.spavn
SPAVN_MODELDefault model key
SPAVN_LANGInitial UI language (tr / en)
SPAVN_SHELLShell used for bash tool invocations
SPAVN_BASE_URL SPAVN_API_KEYFallback endpoint when no provider resolves
SPAVN_FORCE_ANTHROPICForce the Anthropic wire format
SPAVN_LOCAL_MODELS_URL SPAVN_LOCAL_BASE_URLOverride local discovery / local base URL
SPAVN_WEB_SEARCH_URLHTTP MCP endpoint for web when webSearchUrl is unset
SPAVN_UPDATE_CHECK0 disables the release check
SPAVN_INSTALL_DIR SPAVN_LOCAL_BINInstall script target directory / local binary to install
OPENAI_API_KEY ANTHROPIC_API_KEY GEMINI_API_KEY GROQ_API_KEY DEEPSEEK_API_KEY XAI_API_KEY OPENROUTER_API_KEYProvider credentials
10 · Behaviour

Security gates#

Access modes

ModeBehaviour
🔒 safeEvery tool call asks for confirmation
⚡ autoRead-only calls run freely; mutations and non-trivial shell commands ask
🔥 sovereignNothing asks — except the categories that always prompt
sovereign is not a licence to exfiltrate Path policy, egress classification and MCP read-only checks stay active in every mode.

The six gates, in order

1AccessMode below the call's risk class is refusedguard.rs
2Phasewrite/edit/bash and mutating MCP calls refused in PLANtools.rs
3Pathprotected_paths, secret material, outside allowed_pathsguard.rs
4EgressPrivate / loopback / link-local targets, re-checked on every redirect hopguard.rs
5MCPMutating MCP call without readOnly: truetools.rs
6AuditRecords nothing away — chains every action by HMACspavn_engine.rs
No TTY, no approval Approval prompts are answered by a real terminal only. Under a non-interactive shell there is nobody to ask, so the answer is deny — a piped stdin at EOF is not consent.

Phases

/plan is a read-only planning mode enforced in code: the model can read, search and think, but write, edit, bash and mutating MCP calls are refused. /code unlocks them. Prompt text cannot talk its way out of the phase.

What is on disk

~/.spavn/                 0700
├── config.json           providers, paths, MCP servers
├── secrets.env           0600 · flagged as secret material by the path policy
├── PROFILE.md            user profile injected into the prompt
├── sessions/             transcripts
├── skills/               on-demand domain instructions
├── tool-output/          spilled tool output, newest 40 kept
└── audit.key             HMAC key for the audit chain
11 · Behaviour

Context & memory#

Four layers, increasing specificity. Everything is a file you can read and edit.

LayerLocationLoaded
Repo rulesAGENTS.md in the working directoryalways
Profile~/.spavn/PROFILE.mdalways; written back by memory
Skills~/.spavn/skills, ~/.claude/skillson demand via skill
Pinsper project slugalways, until removed with /pin
Wiki~/.spavnon /wiki

External recall

memory can delegate to your own binaries via memory_recall_bin and memory_remember_bin instead of the local store. The local store is the default, not the only path.

Compaction

/compact rewrites the transcript into a shorter form and keeps going; /recap controls the idle summary line. Context-window usage is shown in the dock as a bar, sourced from the model's declared window.

12 · Behaviour

Architecture#

Modules

src/
├── main.rs           CLI parsing, TUI & --rpc entry point
├── lib.rs            module surface
├── rpc.rs            headless JSON-RPC 2.0 stdio bridge
├── router/           model resolution, catalog, OAuth, SSE stream watchdog
├── turn/             agentic turn loop, Zero-Stop compiler gate, repeat-tool breaker
├── tools/            schema, fuzzy self-heal edit, stale-file guard, live signature map, shell, MCP
├── guard.rs          access, path, shell AST risk and egress enforcement
├── hud/              dock, input, keys, menus, pickers, panels, tool cards
├── slash_commands.rs command dispatch and /help
├── session/          JSONL persistence, budget-relative compactor, fork
├── spavn_engine.rs   audit chain, secret scrubbing, janitor, goals, pins
├── memory.rs         recall / remember bridge & repo map builder
├── skills.rs         skill discovery and injection
├── update.rs         release check and upgrade advice
├── spill.rs          oversized-output spill files
├── markdown.rs       terminal Markdown rendering
├── state.rs          shared signals between TUI and agent
├── http.rs           the only socket code
├── ansi.rs           escape sequences & OSC-52 clipboard
└── config.rs         config loading, defaults, provider catalog (Claude 5.5, etc.)

Invariants

  1. Gates are code, never prompt text. Only guard.rs can permit.
  2. No implicit peers. The binary talks to the endpoint you configured and to processes you named in mcpServers.
  3. One socket layer. All network I/O goes through http.rs, which classifies every host before dialling.

Threads

No async runtime. The render loop is synchronous; threads exist only where a child process would otherwise block: the pipe drainer that caps large output, the background-job waiter, bounded subagent fan-out, and the one-shot update check. A long tool call still blocks the render loop; interruption is by signal.

The request lifecycle

  1. Prompt is assembled from AGENTS.md, profile, skills and pins.
  2. Router resolves model key → endpoint.
  3. Egress is classified and secrets are scrubbed before the body leaves.
  4. Tokens stream into the transcript; tool calls arrive as structured JSON.
  5. Each tool call walks the six gates; a mutation may prompt.
  6. Result returns to the model; the dock and tool cards update.
13 · Behaviour

Updates#

There is no auto-updater and no daemon. The binary compares its own version against the newest published tag and tells you the command to run.

  • Background check — once per updateCheckIntervalHours (default 24), a single GET. Nothing is downloaded and nothing is executed.
  • Banner notice — when a newer release is known, the welcome output carries a box with both versions and the upgrade command.
  • Mid-session notice — if the check finishes later, the notice is parked and printed once the prompt is idle. It is never shown twice.
  • /update — check on demand and print the exact command.
/update

# the command it chooses depends on how this binary was installed
#   brew  →  brew upgrade spavn
#   cargo →  cargo install spavn --locked --force
#   curl  →  curl -fsSL https://spavn.dev/install.sh | sh

Turning it off

// ~/.spavn/config.json
{ "updateCheck": false }

# or per invocation
SPAVN_UPDATE_CHECK=0 spavn

/update says the check is disabled rather than falsely reporting "up to date".

Release channels

One tag drives everything: the GitHub archives with SHA-256 sidecars, the Homebrew formula (generated by packaging/homebrew/sync-tap.sh) and the crates.io publish. Nothing is rebuilt per channel.

14 · Help

Troubleshooting#

SymptomCauseFix
⚠️ NO TOOL CALLThe model cannot produce the tool format/bench it, then pick another model
search errors outripgrep missingbrew install ripgrep / apt install ripgrep
web refuses to searchNo backend configuredSet webSearchUrl or webSearchServer
Remote host shows 🔴ssh target unreachable/host, then check ssh_target
Approval never appearsNo TTY — the answer is deny by designRun from a real terminal
Writes refusedStill in PLAN phase/code
Everything asks in autoCommand classified as mutating, or the host is sensitiveExpected; use /access sovereign for speed
brew refuses the tapHomebrew 6+ requires explicit trustbrew trust tugayoktayokay/tap
brew install says Xcode is too outdatedHomebrew aborts any install on an old XcodeUpdate Xcode, or point DEVELOPER_DIR at the Command Line Tools
Binary killed on macOS after replacing itStale code signature in the kernel cacheUse the install script: it stages a fresh inode and re-signs

Health checks

spavn --version          # version, OS, arch
spavn --help             # flags, paths, env vars
/audit                   # what actually ran, HMAC-chained
/fleet                   # host reachability and latency
/local                   # local model discovery
15 · Help

FAQ#

Does it phone home?

No telemetry and no analytics. Two outbound calls exist: the provider endpoint you configured, and — if you leave it enabled — one release check per interval against updateCheckUrl. Both are auditable in http.rs.

Why is web search off by default?

Because shipping a default host would send your queries to a third party nobody chose. Point webSearchUrl at your own bridge, or name an MCP server with webSearchServer. Fetching a URL you gave it works without any config.

Can I use it fully offline?

Yes, with a local model: spavn --model ollama:qwen2.5-coder, or any OpenAI-compatible server via SPAVN_LOCAL_BASE_URL. Set updateCheck: false and nothing leaves the machine.

How is this different from a chat wrapper?

The gates. Access mode, phase, path policy, egress classification, MCP read-only checks and an HMAC-chained audit log are enforced in Rust, not requested in a prompt. A model that decides to write to ~/.ssh in PLAN phase is refused.

Does it work over SSH on a remote box?

Add the host to hosts[] and pass <host>:/path to read, write, bash, glob or search. Remote hosts are treated as sensitive automatically.

Where are my API keys stored?

~/.spavn/secrets.env, mode 0600. config.json stores key names only. The path policy classifies secrets.env as secret material, so no tool can read it back out.

What happens if I pipe input into it?

Without a TTY there is nobody to approve anything, so the answer is deny. Headless use (-p … -q) is designed for read-only or pre-approved work.

How do I add desktop control?

Through MCP, not a builtin. Register the server in mcpServers and call it via the lsp tool's mcp operation. Nothing assumes a vendor or a path.

Why is there no async runtime?

A synchronous render loop is simpler to reason about and starts in under a millisecond. Threads are used only where a child process would block: pipe draining, background jobs, subagents, and the update check.

Can I ship it inside a company?

MIT licensed. Pin a version, mirror the archives, and set updateCheckUrl to your own release feed — or turn the check off entirely. Provider endpoints and key env names are config, so internal gateways work without patching.

16 · Help

Contributing#

Build and gate

cargo build --release
cargo fmt --all --check
cargo clippy --all-targets
cargo test

# the ignored test needs a live MCP server from your own config
cargo test -- --ignored

Rules that keep the project intact

  • No new runtime dependency without a strong reason, and no async runtime.
  • Every gate is enforced in code, never only in the prompt.
  • New behaviour needs a test that fails without it.
  • User-facing changes update README.md and CHANGELOG.md.

Release order

cargo fmt --all --check && cargo clippy --all-targets && cargo test
cargo publish --dry-run

git commit -am "chore(release): v0.1.1" && git tag v0.1.1 && git push --tags
# CI builds the matrix and attaches SHA-256 sidecars

cargo publish                                                  # needs a token
sh packaging/homebrew/sync-tap.sh 0.1.1 /path/to/homebrew-tap   # or let CI do it
Publish after the tag cargo publish runs last so the crate's repository field and the README images point at a release that already exists.