Metadata-Version: 2.4
Name: curio-decision
Version: 0.1.0
Summary: Terminal interface to the Curio Decision Record backend (sibling to the MCP server).
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: pytest; extra == "dev"

# `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):

```bash
curl -fsSL https://raw.githubusercontent.com/ujjalsharma100/curio-decision-record/main/cli/install.sh | bash
```

Override the branch (default `main`):

```bash
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)

```bash
./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:

```bash
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](#backend-contract-shared-with-the-mcp-server) for how tokens and scopes work.

```bash
# 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**:

```bash
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:

```bash
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**):

```bash
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:

```bash
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/whoami` → `project_id_scope` → `GET /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`.

```bash
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

```bash
pip install -e "./cli[dev]"
cd cli && python3 -m pytest tests/ -q
```

The live read-only integration check runs only when opted in:

```bash
CURIO_DECISION_CLI_INTEGRATION=1 python3 -m pytest cli/tests/test_readonly.py::test_live_health_reachable
```
