partiri-cli 0.2.0

Partiri CLI — Deploy and manage services on Partiri Cloud
# partiri CLI — agent guide

This document is shipped inside the `partiri` binary and printed by `partiri llm guide`.
It tells an LLM agent (Claude Code, Codex, custom MCP client, etc.) everything needed to drive
the CLI without reading source.

## 1. Quickstart

```sh
partiri auth --key "$PARTIRI_KEY"     # one-time setup
partiri -j llm doctor                 # confirm environment is sane
partiri -j llm context | jq '.data'   # one call → workspaces, projects, regions, pods, secrets, services
# ... pick UUIDs from the tree, write/edit .partiri.jsonc ...
partiri -j validate --remote          # gate before create
partiri -j -y service create
partiri -j -y service deploy
```

- `-j` switches to JSON output.
- `-y` skips confirmation prompts on destructive operations (`deploy`, `kill`, `pause`, `unpause`).
- Prompts are auto-skipped when stdin is not a TTY (every Bash-tool invocation, every CI run).

## 2. I/O contract

- **stdout**: exactly one structured result per invocation, terminated by `\n`.
- **stderr**: spinners, progress, prompts, warnings — and the error JSON document on failure.
- Every JSON document carries `"schema_version": "1"`.

Envelopes:

```jsonc
// list
{ "schema_version": "1", "data": [ { ... } ] }
// single resource
{ "schema_version": "1", "data": { ... } }
// successful mutation
{ "schema_version": "1", "ok": true, "message": "…", "data": { … } }
// error (stderr, exit 1)
{
  "schema_version": "1",
  "ok": false,
  "error": {
    "code": "401",                            // HTTP status as string OR literal: validation/auth/network/config/cancelled/conflict/missing_dependency
    "message": "Unauthorized",
    "hint": "Run 'partiri auth' to update your API key.",
    "likely_causes": ["…"],
    "suggested_commands": ["partiri auth --key <K>", "partiri llm doctor"]
  }
}
```

Exit codes: `0` success, `1` any error, `2` user cancellation (Ctrl-C / inquire abort).

When something goes wrong, **read `error.suggested_commands` first** — that's the next thing to run.

## 3. The `.partiri.jsonc` schema

```jsonc
{
  "id": null,                           // string|null. Set by 'partiri service create'; do not edit by hand.
  "deploy_tag": null,                   // string|null. Set by the deploy job once it succeeds; refresh with 'partiri service pull'. Needed for logs/metrics.
  "fk_workspace": "uuid",               // string. Required.
  "fk_project": "uuid",                 // string. Required.
  "service": {
    "name": "max-16-chars",             // string ≤16. Required.
    "deploy_type": "webservice",        // "webservice" | "static" | "private-service". Required.
    "runtime": "node",                  // "node" | "rust" | "python" | "go" | "ruby" | "elixir" | "php" | "jvm" | "dotnet" | "cpp" | "static" | "registry". Required.
    "root_path": ".",                   // string. Required (defaults to ".").
    "repository_url": "https://…",      // string. XOR with registry_url.
    "repository_branch": "main",        // string. Required when repository_url is set.
    "registry_url": null,               // string|null. XOR with repository_url.
    "registry_repository_url": null,    // string|null. Required when registry_url is set.
    "fk_service_secret": null,          // string|null UUID. Required for private repo/registry.
    "build_path": null,                 // string|null. Output dir of the build step.
    "build_command": "npm run build",   // string|null. Required for repo source on non-static runtimes.
    "pre_deploy_command": null,         // string|null. Runs before each deploy (e.g. migrations).
    "run_command": "node ./dist/server", // string|null. Required for webservice / private-service.
    "fk_region": "uuid",                // string. Required.
    "fk_pod": "uuid",                   // string. Required.
    "health_check_path": "/health",     // string|null. Path or absolute URL. Probed by `validate --remote` only when absolute.
    "maintenance_mode": false,
    "active": true,
    "env": [ { "key": "NODE_ENV", "value": "production" } ]
  }
}
```

Mutual exclusions and required-field rules are encoded in `partiri llm schema --json` (machine-readable).

## 4. Discovery commands

| Need | Single-call shortcut | Per-resource |
|---|---|---|
| Everything | `partiri -j llm context` ||
| Workspaces | `partiri -j workspaces list` ||
| Projects || `partiri -j projects list --workspace <UUID>` |
| Regions || `partiri -j regions list --workspace <UUID>` |
| Pods || `partiri -j pods list --workspace <UUID>` |
| Services || `partiri -j services list --project <UUID>` |

`partiri llm context` is the single most useful command for any multi-resource decision — it
fans out the per-workspace requests in parallel and returns a fully nested tree.

## 5. Workflow recipes

### Create a new service from scratch

```sh
partiri auth --key "$KEY"
partiri init --template                 # writes .partiri.jsonc with commented examples
# (edit .partiri.jsonc — fill in fk_workspace / fk_project / fk_region / fk_pod and the service.* block)
partiri -j llm context | jq '.data'     # to find UUIDs
partiri -j validate --remote
partiri -j -y service create
partiri -j -y service deploy
```

### Adopt an existing service into a fresh checkout

```sh
partiri auth --key "$KEY"
partiri service pull                    # interactive; or write .partiri.jsonc by hand with id set
partiri -j -y service deploy
```

### Deploy across many directories (the "8 services" scenario)

```sh
partiri -j llm context | jq '.data' > /tmp/ctx.json
for dir in examples/*; do
  ( cd "$dir" \
    && partiri init --template \
    && jq -r '...' /tmp/ctx.json | apply_to .partiri.jsonc \
    && partiri -j validate --remote \
    && partiri -j -y service create \
    && partiri -j -y service deploy )
done
```

### Move a service to a different region/pod

```sh
partiri -j service link --region <UUID> --pod <UUID>
partiri -j -y service push
partiri -j -y service deploy
```

### Wire up a private repo

```sh
partiri -j llm context | jq '.data.workspaces[0].repository_secrets'
partiri -j -y service token --secret <UUID>
partiri -j validate --remote      # should now succeed
```

## 6. Error code catalog

Same content as `partiri llm errors --json`:

| Code | Meaning | Typical fix |
|---|---|---|
| `400` | Bad request — config values out of range / wrong type | `partiri validate` |
| `401` | Unauthorized — API key missing/expired/revoked | `partiri auth --key <K>` |
| `402` | Insufficient workspace balance | Top up at https://partiri.cloud/settings/billing |
| `403` | Permission denied or workspace limit reached | `partiri llm whoami` |
| `404` | Resource not found | `partiri llm context` to see real UUIDs |
| `409` | Conflicting operation in progress | `partiri service jobs` |
| `422` | Invalid request data / schema mismatch | `partiri validate --remote` |
| `429` | Rate limited | wait and retry |
| `500-599` | Server error | retry; if persistent, contact support |
| `auth` | No API key configured | `partiri auth --key <K>` |
| `validation` | Local config validation failed | `partiri llm next` |
| `network` | API host unreachable | `partiri llm doctor` |
| `config` | Bad `.partiri.jsonc` content | `partiri validate` |
| `cancelled` | User aborted (Ctrl-C / inquire cancel) | exit 2 |
| `missing_dependency` | A required predecessor wasn't done | `partiri llm next` |

## 7. Common pitfalls

- **`service.name` must be ≤16 characters.** Validated locally; the API also rejects longer names.
- **`fk_region` and `fk_pod` must come from the same workspace.** Cross-workspace UUIDs return 404.
- **`repository_url` XOR `registry_url`.** Setting both errors out at `validate`.
- **Private repositories and registries require `fk_service_secret`.** Without it, `validate --remote` fails on the source-reachability check; create the secret in the Partiri dashboard, list it via `partiri llm context`, then set it with `partiri service token --secret <UUID>`.
- **`health_check_path` accepts either a path or an absolute URL.** Only absolute URLs are probed by `validate --remote`; relative paths are deferred to runtime.
- **`deploy_tag` is set by the deploy job once it succeeds — not synchronously by `service deploy`.** The deploy is async, so the tag may still be empty right after the POST returns. `service deploy` does a best-effort refresh; if the job is still in progress, run `partiri llm next` (which inspects job status) or `partiri service pull` to refresh later. Required for `partiri service logs` and metrics.
- **`init --template` refuses to overwrite an existing `.partiri.jsonc`.** Delete the file manually first (or pull the existing service).

## 8. Glossary

- **Workspace** — billing/ownership boundary. A user belongs to one or more workspaces.
- **Project** — a logical grouping of services within a workspace, with an environment label (`dev`/`staging`/`prod`).
- **Service** — the deployable unit. One service per `.partiri.jsonc` per directory.
- **Region** — geographic location. Pods live in regions.
- **Pod** — a sized compute slot (CPU + RAM + replicas). Pick a pod that matches your service's needs.
- **deploy_tag** — the immutable tag of the most recent successful deploy. Used to fetch logs/metrics for that exact build.
- **fk_*** — foreign-key fields in `.partiri.jsonc` pointing at other resources by UUID.