Skip to main content

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) — no requests, 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.6 config/whoami introspection · 6.7 doctor · 6.8 curl + install.sh distribution · 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, not curio-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:

  1. CLI flag — --service-url, --project, --token (or --token-env NAME, read from the env var without persisting)
  2. Env — CURIO_DECISION_SERVICE_URL, CURIO_PROJECT_NAME, CURIO_DECISION_TOKEN
  3. Project .curio-decision/config.json
  4. Global ~/.curio-decision/config.json
  5. Fallback:
    • service_url → built-in default http://localhost:5000 (matches the MCP server's default backend URL)
    • project_name → the token's own scope via GET /api/v1/auth/whoamiproject_id_scopeGET /api/projects/<id> → name (the exact MCP fallback, so a project-scoped token "just works")
    • token → none; standalone backend then returns 401 (the CLI prints a hint), while a backend in CURIO_AUTH_MODE=none works 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 backend tokens table. The CLI forwards them verbatim as Authorization: Bearer cd_….
  • A token carries org_id, an optional project_id scope, and scopes (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 by X-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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

curio_decision-0.1.0.tar.gz (49.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

curio_decision-0.1.0-py3-none-any.whl (50.3 kB view details)

Uploaded Python 3

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

Hashes for curio_decision-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1c58b9fa2a685ba5808a71b7c98e711e0102498c07a44ba98e124b90faf48126
MD5 032efafad8bec40d553a400e29371b52
BLAKE2b-256 9340fcde81ba43ab58c33b87d73561fac4adea0973142450315ac728c7082b5e

See more details on using hashes here.

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

Hashes for curio_decision-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 147bcc089bc28ad5c69e6a6b41b3e2ee05013e27fc95727516c440b776dcf3b6
MD5 4fa36b70684dbe304df3021204088016
BLAKE2b-256 b1696b2960bf67fd02e16ab55e586c13c4e4220dc0b34ca92a083266bd99553c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page