Terminal interface to the Curio Decision Record backend (sibling to the MCP server).
Project description
curio-decision CLI
Stdlib-only terminal client for the curio-decision-record Flask backend. It is a sibling of the MCP server: same opaque bearer token, same org isolation, same per-project scope, and the same project-resolution order. All auth and isolation live in the backend — the CLI only forwards a token, it never mints or inspects one.
- stdlib-only (
urllib,argparse,json) — norequests,httpx, or Click. - The CLI is optional. A project can be operated entirely through the MCP server with the CLI never installed; equally, the CLI runs with zero config files when given flags/env alone.
- Status: M0 Phase 6 complete — 6.1 skeleton · 6.2 settings cascade + whoami project fallback · 6.3
init· 6.4 read-only commands · 6.5 mutating commands · 6.6config/whoamiintrospection · 6.7doctor· 6.8 curl +install.shdistribution · 6.9 packaging + docs (this file).
Installation
1. curl from main (recommended)
Installs under ~/.curio-decision/ (same parent directory as the global config file), wires ~/.curio-decision/bin into your shell rc, and copies only the curio_decision package (stdlib-only — no pip/venv step):
curl -fsSL https://raw.githubusercontent.com/ujjalsharma100/curio-decision-record/main/cli/install.sh | bash
Override the branch (default main):
CURIO_DECISION_INSTALL_BRANCH=my-feature \
curl -fsSL https://raw.githubusercontent.com/ujjalsharma100/curio-decision-record/main/cli/install.sh | bash
After install, curio-decision, cd-decision, and cdr are on PATH (restart your shell, or export PATH="${HOME}/.curio-decision/bin:${PATH}").
2. Local ./cli/install.sh (from a checkout)
./cli/install.sh # bash; run from the monorepo checkout
3. pip install ./cli (optional)
For contributors, or anyone who prefers a packaged install into a venv:
pip install ./cli # or, editable + dev tools:
pip install -e "./cli[dev]"
curio-decision --version
python -m build (from cli/) produces an sdist + wheel; pip install dist/curio_decision-*.whl works in a clean venv. The package version is single-sourced from curio_decision.__version__. PyPI publication is deferred post-M0 (consistent with the agent-trace distribution decision); curl / install.sh is the documented default.
Requires Python 3.9+.
Five-minute walk-through
This assumes a backend running locally (default http://localhost:5000) in standalone auth mode, and a token minted for your org (cd_…). See Backend contract for how tokens and scopes work.
# 0. Hold the token in an env var (never paste it inline into shell history).
export CD_TOKEN="cd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# 1. Configure — persist service URL + project once (also registers the project with --create).
curio-decision --service-url http://localhost:5000 --token "$CD_TOKEN" --project payments init --create
# 2. Create a decision (the container for competing record options).
curio-decision decision create "Primary datastore for payments"
# 3. Propose a record against that decision.
curio-decision propose "Primary datastore for payments" \
--description "Managed Postgres on RDS" \
--field rationale="Relational integrity + team familiarity" \
--field tradeoffs="Higher cost than self-hosting"
# 4. Accept the proposed record (conflicting records auto-deprecate server-side).
curio-decision accept "Primary datastore for payments" "Managed Postgres on RDS" \
--reason "Approved at architecture review"
# 5. Implement it once it ships.
curio-decision implement "Primary datastore for payments" "Managed Postgres on RDS"
# Inspect along the way:
curio-decision list --status accepted
curio-decision show "Primary datastore for payments"
curio-decision history "Primary datastore for payments"
After step 1, the resolved service_url + project_name live in ./.curio-decision/config.json, so steps 2–5 need no flags. The same values are exactly what an MCP client reads from CURIO_DECISION_SERVICE_URL / CURIO_PROJECT_NAME, so you can point Cursor or Claude Code at the same project with no re-setup.
Zero-config (flag-only) form
init is purely a convenience. Every setting can be supplied per-invocation, so the CLI works with no config files on disk:
curio-decision --service-url http://localhost:5000 --token "$CD_TOKEN" --project payments list
# read the token from a named env var without persisting it anywhere:
curio-decision --service-url http://localhost:5000 --token-env CD_TOKEN --project payments list
Argparse note: global flags (
--service-url,--project,--token,--token-env,--json) must appear before the subcommand (curio-decision --project payments list, notcurio-decision list --project payments).
Command surface
Read-only:
curio-decision project list # GET /api/projects (caller's org)
curio-decision --project payments project show # GET /api/mcp/projects/<name>
curio-decision project use payments # write project_name to config
curio-decision --project payments list [--status accepted] # decisions in the project
curio-decision --project payments show "ADR-1" # decision + its records
curio-decision --project payments record show "ADR-1" "Option A"
curio-decision --project payments history "ADR-1"
curio-decision --project payments versions "ADR-1" "Option A"
curio-decision --project payments diff "ADR-1" "Option A" --from-version=1 --to-version=current
curio-decision --project payments relationships "ADR-1" ["Option A"]
Mutating (end-user surface only — no admin or token-minting command ships in the CLI):
curio-decision project create payments
curio-decision decision create "ADR-1"
curio-decision propose "ADR-1" --description "Option A" --field rationale=… # --from-file <json> or TTY prompts also work
curio-decision update "ADR-1" "Option A" --field context=… # version auto-increments server-side
curio-decision accept|implement|reject|deprecate "ADR-1" "Option A" [--reason …]
curio-decision relate "ADR-1" "Option A" --type=supersedes "ADR-1" "Option B" [--decision-level]
curio-decision unrelate …
curio-decision revert "ADR-1" "Option A" --to-version=1 [--reason …]
Each mutating verb also has a noun-scoped alias (record propose, record accept, decision create, relationship relate, version revert, …); both forms share one set of argparse builders. Bad input never crashes raw — the CLI prints a one-line human error (or {"error": …} under --json) and exits non-zero.
Workflow commands + introspection:
curio-decision commands install [--tool cursor|claude] # copy bundled curio-*.md into .cursor/ / .claude/commands
curio-decision config show [--resolved] # effective values + which source won (+ full ladder)
curio-decision config paths # the two config files, existence, per-key merge
curio-decision config set <key> <value> [--global] # write one of service_url|project_name|token
curio-decision whoami # GET /api/v1/auth/whoami (org, scopes, project scope, auth_mode)
curio-decision doctor # resolution-aware health checks
--json is a root flag honoured by every command.
Configuration & resolution
The CLI resolves exactly three settings per invocation — service_url, project_name, token — through one inspectable cascade. There is one config schema (those three keys) stored in up to two places:
| Tier | Path | Wins over |
|---|---|---|
| Project | .curio-decision/config.json (found by walking up from cwd) |
global, per key |
| Global | ~/.curio-decision/config.json |
defaults |
Resolution order (highest precedence first), identical for all three keys:
- CLI flag —
--service-url,--project,--token(or--token-env NAME, read from the env var without persisting) - Env —
CURIO_DECISION_SERVICE_URL,CURIO_PROJECT_NAME,CURIO_DECISION_TOKEN - Project
.curio-decision/config.json - Global
~/.curio-decision/config.json - Fallback:
service_url→ built-in defaulthttp://localhost:5000(matches the MCP server's default backend URL)project_name→ the token's own scope viaGET /api/v1/auth/whoami→project_id_scope→GET /api/projects/<id>→ name (the exact MCP fallback, so a project-scoped token "just works")token→ none;standalonebackend then returns 401 (the CLI prints a hint), while a backend inCURIO_AUTH_MODE=noneworks token-less
Because a project config file can hold a token, init / config set add .curio-decision/ to the repo's .gitignore and warn if writing a token into a tracked file. Use curio-decision config show --resolved to see the full ordered candidate list and which rung supplied each value.
Backend contract (shared with the MCP server)
The CLI and the MCP server share no Python code — what they share is this backend contract. Documented once here so both interfaces stay in lockstep.
Token model
- Opaque bearer tokens, prefix
cd_, stored hashed in the backendtokenstable. The CLI forwards them verbatim asAuthorization: Bearer cd_…. - A token carries
org_id, an optionalproject_idscope, andscopes(read/write). - Auth modes (
CURIO_AUTH_MODE):standalone(default — bearer tokens),gateway(trusts an upstream gateway secret),none(auth disabled, default org only). - Tokens are minted by admin endpoints (
POST/GET/DELETE /api/v1/tokens, gated byX-Admin-Secret). The CLI deliberately exposes none of these — minting is an operator task.
Project resolution (mirrors mcp_server/.../context.py::resolve_project)
Explicit project wins, then the token's scope: CLI/settings project_name → (MCP only: X-Curio-Project header) → env CURIO_PROJECT_NAME → token project_id_scope via whoami. The CLI sends the resolved name in the URL path of the name-based endpoints; X-Curio-Project is the MCP wire equivalent — so both interfaces target identically. (Backend resolution matches curio_decision_mcp/context.py::resolve_project.)
| Purpose | Method + path |
|---|---|
| Health / schema version | GET /health |
| Token identity / scope | GET /api/v1/auth/whoami |
| Projects (list / create) | GET · POST /api/projects |
| Project metadata (name route) | GET /api/mcp/projects/<name> |
| Decisions (list / create) | GET · POST /api/mcp/projects/<name>/decisions |
| Decision (show) | GET /api/mcp/projects/<name>/decisions/<title> |
| Records (list / create) | GET · POST .../decisions/<title>/records |
| Record (show / update) | GET · PUT .../records/<description> |
| Record status change | PATCH .../records/<description>/status |
| Implementation history | GET .../decisions/<title>/implementation-history |
| Versions / diff / revert | GET /api/records/<id>/versions · /diff · POST /api/records/<id>/revert |
| Relationships | GET/POST /api/records/<id>/relationships, /api/decisions/<id>/decision-relationships |
Name/title routes (/api/mcp/...) are the end-user surface; id-based routes (/api/records/<id>/...) are used only after resolving a name → id. 403 / 404 from the backend are surfaced with actionable hints and never leak whether another org's project exists.
CLI vs MCP — when to use which
Both are thin clients over the same backend, token, and project scope; pick by context, not capability:
curio-decision CLI |
MCP server | |
|---|---|---|
| Who drives it | a human at a terminal / scripts / CI | a coding agent (Cursor, Claude Code, …) |
| Transport | sync urllib, stdlib-only |
async httpx over MCP |
| Workflow prompts | installs the bundled curio-*.md commands (which drive the MCP tools) |
exposes the workflow prompts/tools directly |
| Surface | end-user CRUD + config introspection + doctor |
end-user CRUD only (no minting, no cross-project listing) |
| Config source | flags / env / .curio-decision/config.json |
env (CURIO_DECISION_SERVICE_URL, CURIO_PROJECT_NAME) + headers |
curio-decision commands install copies the bundled workflow markdown into .cursor/commands/ / .claude/commands/; those commands invoke MCP tools, so they need an MCP client bound to the same project — doctor flags this.
init
init writes resolved settings to ./.curio-decision/config.json (default) or ~/.curio-decision/config.json (--global). Values come from the cascade above; a resolved project_name is required unless a project-scoped token supplies it via whoami.
curio-decision --service-url http://localhost:5000 --token "$CD_TOKEN" --project payments init
curio-decision --token "$CD_TOKEN" --project payments init --create # POST /api/projects first
curio-decision --token "$CD_TOKEN" --project payments init --global # user-level config only
For project-local writes, a .gitignore entry for .curio-decision/ is ensured when a git repo root is detected (the file may carry a token).
Bundled templates
Markdown workflow prompts live under curio_decision/templates/commands/ (copies of the repo's .cursor/commands/curio-*.md) and ship with the package. commands install materializes them into a workspace.
Development & tests
pip install -e "./cli[dev]"
cd cli && python3 -m pytest tests/ -q
The live read-only integration check runs only when opted in:
CURIO_DECISION_CLI_INTEGRATION=1 python3 -m pytest cli/tests/test_readonly.py::test_live_health_reachable
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file curio_decision-0.1.0.tar.gz.
File metadata
- Download URL: curio_decision-0.1.0.tar.gz
- Upload date:
- Size: 49.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c58b9fa2a685ba5808a71b7c98e711e0102498c07a44ba98e124b90faf48126
|
|
| MD5 |
032efafad8bec40d553a400e29371b52
|
|
| BLAKE2b-256 |
9340fcde81ba43ab58c33b87d73561fac4adea0973142450315ac728c7082b5e
|
File details
Details for the file curio_decision-0.1.0-py3-none-any.whl.
File metadata
- Download URL: curio_decision-0.1.0-py3-none-any.whl
- Upload date:
- Size: 50.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
147bcc089bc28ad5c69e6a6b41b3e2ee05013e27fc95727516c440b776dcf3b6
|
|
| MD5 |
4fa36b70684dbe304df3021204088016
|
|
| BLAKE2b-256 |
b1696b2960bf67fd02e16ab55e586c13c4e4220dc0b34ca92a083266bd99553c
|