Skip to content

CLI — full command reference

Every command spendguard exposes. New here? Start with the 60-second quickstart — spendguard scan needs none of this.

CLI — full command reference

# enforce / control
spendguard status | on | off                 # kill switch (persistent flag)
spendguard doctor [--live]                   # is the gate ENFORCING here? + keys · subscription lanes · bulk-lane REACHABILITY · cached leak verdict w/ age (--live = full pull)
spendguard install-hook --venv <path>        # gate every process in ANOTHER venv/repo (--uninstall to remove; alias: gate-venv)
spendguard install-hook --user [--python P]  # gate a python's per-USER site (system-python bypass; PEP668-safe, no pip)
spendguard install-rule [--global|--project DIR]  # drop the spendguard rule into CLAUDE.md → every AI chat wires it in
spendguard install-skills                    # deploy the 5 slash-commands (/spend, /spendguard-{reconcile,learn,prompts,close})
spendguard install-receipts --host claude-code|codex   # surface the always-on tally in a host (statusline + per-turn + compaction hooks)
spendguard install-mcp [--remove]            # register `spendguard mcp` in ~/.claude.json → 9 MCP tools in every repo (model-advisor + spend/compaction)
spendguard mcp                               # stdio MCP server: model-advisor + read-only spend/compaction tools (point any MCP client here)
spendguard coverage                          # which LLM-calling VENVS aren't gated (ungated realtime spend sources)
spendguard gate-coverage                     # per-INTERPRETER gate check across EVERY python on the machine (3.11/3.14/…)
spendguard remote onstart|verify|sync        # enforce the gate on remote/distributed compute (vast.ai / any SSH host)
# in code, fail-closed:  import spendguard; spendguard.require()   # refuses to run if NOT actually gated

# teams / orgs (client seam → future server repo, llmspendguard.com)
spendguard saas [status|ping|link|push|pull|sync|reconcile|audit|crosscheck|commands]
#   status/ping — connection · link — device-link (approve in browser → verified email = contributor)
#   sync [--if-due] — roll-up push on cadence · push [--dry] — force now · pull — fetch pooled learnings
#   reconcile / audit / crosscheck — reconcile local ledger to provider truth, completeness audit, local↔server row diff (all free)
#   commands — drain + run server-enqueued work (reconcile / re-tag).  Opt-in; private until you enable it.

# run a WHOLE job set (the whole-job contract — hand it the set + a goal, it plans & runs) — see docs/WHOLE-JOB.md
spendguard submit-jobs <jobs.jsonl>                 # PLAN + estimate only ($0): the per-intent method + cost, no spend
spendguard submit-jobs <jobs.jsonl> --execute --budget 5.00 [--urgency auto|realtime|batch] [--quality]
#   --execute REQUIRES --budget (the estimate-first cap: refuses before spending over it, or if a group can't be priced)
#   --urgency auto (true-$ planner picks batch vs now) | realtime (sync) | batch (~half-price Batch API where eligible)
#   --quality — run each intent at best-value (cheapest model whose measured quality holds)
spendguard submit-jobs --collect <run….pending.jsonl>   # settle a prior run's async Batch-API handles (durable)
#   jobs.jsonl = one job/line: {prompt, intent[, id, system, schema]}. A STRICT schema auto-routes to a path that
#   can enforce it. Also the spendguard_run_jobs MCP tool (spend-safe: no budget → plan-only).

# see the money
spendguard receipt [--json|--line]                    # running today/7d/month tally; auto-emitted after every flow
spendguard report [--alert-threshold 150] [--email]   # daily/weekly/monthly + ledger-leak alert + top learnings
spendguard reconcile openai|anthropic [--by-day]      # actual billed batch spend from the provider
spendguard reconcile all                              # UNIFIED view: every source (LLM+GPU) via one account-anchored loop
spendguard reconcile-ledger [--since DATE]            # local gate ledger vs provider billing → find LEAKS (aliases: ledger-sync, leaks)
spendguard trust                                      # provider billing vs recorded — the daily double-count guard (alias: trust-check)
spendguard truth [--push]                             # per-day provider-truth totals (owner connection only) → the org statement's yardstick
spendguard close [--month YYYY-MM] [--csv] [--account]  # monthly close, client view; --account = shared-account axis (truth is account-wide)
spendguard calls [--intent X]                # per-intent cost + good% + $/good (opt-in corpus)
spendguard prompts [--intent X] [--json]     # prompt-efficiency lint: boilerplate/context/truncation/model-mix, ranked by $ at stake
spendguard realized [--intent X] [--sync]    # MEASURED before/after $/call around insight adoptions (no counterfactuals); --sync → guarded
spendguard estimate --items N --from-sample f.jsonl --packs 1,30 [--label X]   # --label adds the LEARNED correction
spendguard calibrate predict --label X --n N --model M [--transport batch] [--in-tokens T] [--out-max T]
                                             # LEARNED estimator: your captured history corrects the naive $
spendguard calibrate show | pair | backtest  # what's learned + confidence · join predictions↔actuals · MAPE vs naive
spendguard maxtokens <sig> [current_max]     # data-driven max_tokens bound for a call-class (p99×1.5 — measured, not guessed)
spendguard tokens show | calibrate [--dry-run] [--json]   # per-provider TEXT token factors: real o200k BPE base × an o200k→native
                                             # multiplier CHOSEN AGENTICALLY from call_io stats (one small meta call; --dry-run = $0
                                             # stats + estimate). Fixes chars/4 anthropic/gemini/glm estimates; per-call use is $0.
spendguard pricing | providers               # canonical price table · configured providers→models
spendguard cross-check | check-prices | sync-prices | refresh-prices   # OpenRouter drift · freshness · LiteLLM sync · refresh
spendguard audit [--ci]                       # fail if a script hardcodes a price ≠ the table
spendguard compare --prompt "..." --models a,b,c --show   # one prompt across providers → cost + latency + output

# plan / decide  (the briefing + advisor loop)
spendguard brief --task "..."                 # "what we need to do" → pre-filled confirm-or-correct plan
spendguard advise [--intent X] [--plan M]     # deterministic per-intent ranking by $/good (no spend)
spendguard backtest --as-of DATE              # replay advise as of a past date
spendguard optimize --intent X [--plan M]     # caged LLM recommendation (cheapest config that holds quality)
spendguard mine                               # caged: synthesize confidence-scored insights + graph from the evidence
spendguard reconstruct                        # caged: judge recovered call I/O → real good% / $/good
spendguard review                             # caged: practice audit (was the usage SMART, not just what it cost)
spendguard models [show <model>]              # per-model learnings, auto-applied (reasoning/cache/tokens)
spendguard insights list|export|import        # living insights; opt-in scrubbed collective learning

# prove / run cheaper  (estimate-first, caged by caps.meta)
spendguard experiment --intent X --model M... [--semantic embed|rubric] [--run]   # A/B cost↓ + same-output, graduated
spendguard promote --intent X --model M [--input chunk.jsonl] [--batch] [--run]    # run the winner + KEEP output
spendguard cache-audit | cache-test --script f.py [--run]   # prompt-caching: find + prove savings
spendguard cascade --ladder cheap,…,strong --intent X [--prompt …] --run           # cheap→verify→escalate
spendguard cache-stats | dedup --input f.jsonl --out u.jsonl | dedup-populate      # response cache + batch dedup
spendguard bakeoff X --candidates v:m,v:m [--efforts minimal,low,medium,high] [--sample N] [--requirement-aware [--adjudicator v:m]] [--run]  # measure a SLATE per (model[,effort]); records for advise; estimate unless --run
spendguard effort-titrate X [--model v:m] [--efforts …] [--sample N] [--requirement-aware [--adjudicator v:m]] [--run]   # learn the CHEAPEST reasoning effort that HOLDS quality, per (intent,model); estimate unless --run
#   --requirement-aware: judge each output against the PROMPT'S OWN extracted requirements, two-tier (cheap screen → opus adjudicator on the unsure calls) — the sharpest "did it meet the spec" ruler.
spendguard savings [--json]                   # the THIRD axis: what spendguard SAVED (measured + counterfactual, by source) — kept SEPARATE from real-$/est-value, never summed
spendguard reliability [--run] [--remediate] [--notify] [--json]   # sweep every $0 lane + metered provider for reachability; each probe wall-clock-bounded so a hung endpoint fails fast (--run = tiny pings)
#   --remediate: for each DOWN resource, the exact FIX (issue/fix/command — which login/quota/API) decided AGENTICALLY + cached (only a NEW failure costs). e.g. claude-code down → `claude auth login`.
#   --notify: fire a macOS notification on any red (for a scheduled/headless run). The result is CACHED, so a down lane also surfaces in the receipt EVERY turn — and a lane that fails mid-use surfaces INSTANTLY (event-driven) + auto-clears on recovery.
#   Schedule it: a launchd agent running `reliability --run --remediate --notify` daily = a standing lane-health watch (see docs/SUBSCRIPTION_VALUE.md).
# Make spendguard PICK the model+effort for you on a REAL call (not just advise): the API takes reasoning="best-value" —
#   spendguard.adapters.call(prompt, reasoning="best-value", intent="X")  → cheapest (model,effort) whose quality holds for X; books the saving vs the counterfactual.

# OpenAI Batch-API job  (the gated, human-run halves — you supply TASKS, spendguard builds each request; Batch = 50% of realtime)
spendguard batch-submit --tasks t.jsonl --model M [--system … | --system-file f] [--max-out N] [--cap $] [--avg-out T] [--dry-run]
#   tasks .jsonl = one {"custom_id","content"} per line; spendguard builds the per-model envelope (max_tokens vs
#   max_completion_tokens + a verifiably-accepted reasoning_effort, output floored so reasoning can't empty the reply)
#   from models.py — you NEVER hand-roll a body, so a model-wrong param can't reach the API (the 250/250 HTTP-400 bug).
#   custom_id is preserved VERBATIM on each result line (your mapping key). --dry-run = $0 estimate + cap check (keeps the
#   built envelope to inspect). --cap refuses if the projected $ exceeds it. (legacy: --jsonl reqs.jsonl submits a
#   PRE-BUILT request file — then you own each body's per-model params.)
spendguard batch-fetch --batch-id ID --out out.jsonl [--force]   # poll; on completion download output (+ any .errors so failures stay visible) — never clobbers a prior --out (renames it .bak_<ts>)

# subscription lanes  (run heavy work $0 on flat-fee plans — the atomic lane→metered pair)
spendguard lanes                              # lane status (which plans are ready) — add --usage for per-plan quota bars
spendguard lanes --balance                    # per-plan UTILISATION this month (🔥 hot vs 💤 idle) = est-value ÷ plan fee
spendguard lanes --catalog                    # each lane's provider · reasoning quirk · use-names · $/1M (the source of truth)
spendguard lanes --economics                  # measured token caps · $/token · plan-fee-at-risk this window
spendguard lanes --fallback                   # lane→metered ID equivalence: a down/exhausted plan degrades to the paid API, never strands
spendguard lanes --reasoning-map              # lane→metered REASONING map: SAME model, EQUAL-or-GREATER effort, availability ✓/?/✗, per (lane,model,level)
spendguard tiers [--probe]                    # bulk-lane routing GROUPS (advisor.tiers): is each group's models priced + does a lane serve it; --probe = live $0 REACHABILITY check that each lane CLI ACCEPTS its declared model (a rejected id reads 🔴 = silently meters)
spendguard tiers set <group> <model…>         # declare a capability group (refuses an unpriced model at declaration); then `lanes set-model <lane> <model>` maps a lane to one of them
spendguard lanes --delegate "<task>"          # offload ONE task to the cheapest viable idle lane ($0; billed fallback flagged)
spendguard lanes --bulk <intent> [--file t.txt|--jsonl] [--tier G] [--lanes a,b,c] [--estimate] [--checkpoint c.jsonl] [--out r.jsonl]   # fan a LIST across all idle lanes (durable, chunked); --estimate = $0 preview
spendguard lanes --bakeoff X "<task>" | --estimate   # seed the learned cross-lane router (--estimate = $0 judge-cost preview)
spendguard lanes --propose X <primary> | --confirm X <sub>   # a judge PROPOSES acceptable substitutes (PENDING) → you confirm once
#   PIN semantics: a pin is a PROVIDER + a reasoning FLOOR. Work rides the $0 lane; a miss falls to that SAME provider's metered
#   API at EQUAL-or-GREATER reasoning (src/spendguard/reasoning_equivalence.py) — never a different vendor, never less reasoning.
#   Programmatic fan: lane_balance.bulk_delegate(tasks, intent, model_for=…|tier=…, on_miss="batch"|"error"|"api", hedge_ms=…, strategy="auto") — see docs/GATED_BULK_LANES.md

# work-done attribution (org → team × project), all sources
spendguard claude-code [show|sync|ingest|overflow|invoices|attribute|overage|context|conversations|compact|classify|work|story]   # mine ~/.claude (alias: cc)
#   ingest → per-turn rows into spend_events (source=claude-code, kind=est_chat, billed=0 = est-value) · overflow|billing-state → calc overage windows from observable signals
#   invoices → ingest the REAL Anthropic charges as ground truth · attribute → reconcile invoice overage → conversations
#   overage → factual per-PROVIDER overage tally (invoice TRUTH + un-invoiced ESTIMATE, never double-counted; general across lanes)
#   context → sustained-large-context $/turn compaction view · conversations|convs → per-conversation view, labeled by sidebar title
#   compact [--tailor] → the effective /compact command (--tailor = agentic, conversation-specific advisor) · show|sync|classify|work|story as before
spendguard codex [show|sync|...]             # mine ~/.codex sessions → Codex est-value (channel=codex, billed=false)
spendguard chat [test|show|discover|classify|loop|work|story|sync|status|accept]   # claude.ai chat adapter (OPT-IN, on-device, macOS)
spendguard resources [show|snapshot|sync|discover]   # vast.ai GPU → org/team/project (discover [--agentic] recovers destroyed boxes)
#   more GPU clouds: RunPod / Modal / Lambda adapters ride `reconcile all` when their key is set (docs/PROVIDERS.md §GPU)
spendguard accounting                        # match actual provider USAGE → project via the conversations that ran each batch
spendguard signal                            # per project·intent·model efficiency signal (cost+quality+waste+reco) → server
spendguard workdone                          # work-done CONTEXT for spend (git + batch intents) → server (alias: work)
spendguard tag                               # re-assign a project tag (fix cwd-fallback mistags)

# cold start / corpus
spendguard bootstrap [--repo] [--transcripts]   # mine ALL history → corpus + insights (free, then estimate)
spendguard fetch-io [--cap 50]                  # recover real prompt+output from providers (free)
spendguard callio-status [--intent a,b] [--json]  # replay corpus fill per (intent, model): sampled · replay(=truncated0, what a sweep samples) · live · judged, + is live-capture ON — consult BEFORE effort-titrate/bakeoff (free)
#   Build the corpus for realtime/lane-only intents: SPENDGUARD_CAPTURE_LIVE=1 (callio.capture_live) → adapters.call records each served workload call WHOLE into call_io as it runs (opt-in, bounded, privacy-gated).
spendguard backfill [--intent-map …]            # seed corpus + graph from the batch ledgers (free)
spendguard mine-history {intents,graph,git} [--apply]   # reconstruct intents/edges from the repo (free; alias: history)
spendguard mine-conv {index,synth} [--run]      # mine session transcripts for the cost playbook (alias: conv)
spendguard validate                             # re-check learnings vs the current corpus (lifecycle)

# setup
spendguard init | config                        # guided setup / show resolved config
spendguard schedule [--daily] [--remove]        # install the OS-native scheduler (launchd/cron/schtasks)

The workflow it's built around

brief (pre-filled plan) → experiment (prove the cheapest config that holds quality, graduated) → promote (run it + keep the output) → the gate enforces caps → reconcile-ledger (catch leaks vs provider billing) → report (daily email: totals + leak alert + top learnings) → validate (learnings stay true as data grows) → those learnings feed the next brief.

Gate another repo

The gate auto-installs per venv via a sitecustomize.py hook. To gate another project:

spendguard install-hook --venv /path/to/that-repo/.venv     # pip-installs spendguard + writes the hook
Then every process in that venv is gated (kill switch: GATE_DISABLE=1 or spendguard off). Until a repo is gated, its provider spend shows up in reconcile-ledger as a leak (billed but ungoverned).

Enforce the gate on remote / distributed compute (vast.ai, any SSH host)

The gate only governs the interpreter it's loaded in — a freshly-spun-up box's python3 is ungated until it's provisioned, so remote LLM scripts can spend silently. Make it structural — gate at provision, verify before spend, sync before teardown:

spendguard remote onstart                              # boot snippet → bake into the instance onstart (gates every python3)
spendguard remote verify --ssh "ssh -p PORT root@HOST -i KEY"   # FAIL-CLOSED: exit≠0 if the box isn't ENFORCING → abort the launch
spendguard remote sync   --ssh "ssh -p PORT root@HOST -i KEY" --project manga2anime   # roll the box ledger up to the org (idempotent)
On the box itself, an LLM script should also import spendguard; spendguard.require() (fail-closed in-process). Then an ungated box can't spend: provisioning gates it, verify refuses to launch if it didn't, require() aborts the script, and sync attributes the spend before the ephemeral box is destroyed.

Always-on spend tally (inline receipts)

After every gated flow — a with spendguard.context(intent=…): … block, a batch submit at the gate, or a CLI command — spendguard prints a compact receipt so what it tracked is visible the moment it happens:

spendguard ▸ loinc-typing · 42 calls · in 1.2M / out 300.0K · est $2.10 → actual $1.87 (−11%)
             actual-$ (billed): today $81 · 7d $421 · month $2,015
             est-value (plan, not billed) (as of 2026-06-23): today $1.4k · 7d $8.6k · month $20.2k
The two axes are always kept separate and never summed: actual-$ is money billed (the gate ledger, reconciles to provider truth); est-value is coding-agent usage value — Claude Code + claude.ai + Codex (what it would cost at API rates — covered by your plan), stamped per-source so they sum. It's per-FLOW (not per-call), costs nothing (a local read, no LLM, no admin key), and the verbosity is receipts.level / SPENDGUARD_RECEIPTS = off | footer | flow | verbose (default flow). Check it any time:
spendguard receipt            # the two-line tally   ·   --line = one compact line   ·   --json = machine-readable

Surface it in your Claude Code chat — one command (idempotent; backs up + can --remove):

spendguard install-receipts --host claude-code      # statusLine footer + per-turn notice + compaction-preservation hooks
It registers four guarded hook protocols in ~/.claude/settings.json: receipt --statusline (always-on footer: cwd · model · ctx% · tally, which also carries a compaction nudge when a session's context is large enough that compacting would pay off), receipt --stop-hook (a systemMessage line each turn), receipt --precompact-hook (a PreCompact hook that injects context-preservation guidance — cutting the measured ~19% auto-compaction loss — and records the event), and receipt --sessionstart-hook (a SessionStart matcher=compact hook that records the post-compaction context so the real compaction ratio is measured). A hook can never block or break a turn. Restart Claude Code to apply.

Other hosts (Codex, editors, menubar). Codex has no in-chat hook, but spendguard still TRACKS it (spendguard codex show → channel=codex, billed=false). To surface the tally anywhere, point a sink at a file and render that: receipts.sinks / SPENDGUARD_RECEIPTS_SINK = stderr (default) | stdout | file:<path> (comma-separated). e.g. spendguard config set receipts.sinks 'stderr,file:~/.spendguard/receipt.log', then tail -f ~/.spendguard/receipt.log in a pane.