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:
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)
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
receipts.level / SPENDGUARD_RECEIPTS =
off | footer | flow | verbose (default flow). Check it any time:
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
~/.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.