@cotal-ai/connector-opencode 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +3 -3
- package/dist/plugin.bundle.js +189 -35
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -136,7 +136,7 @@ import { isConcreteChannel as isConcreteChannel3, channelInAllow as channelInAll
|
|
|
136
136
|
|
|
137
137
|
// ../connector-core/dist/docs-bundle.generated.js
|
|
138
138
|
var DOCS_BUNDLE = {
|
|
139
|
-
"version": "0.
|
|
139
|
+
"version": "0.16.0",
|
|
140
140
|
"generatedFrom": "docs/*.md + SPEC.md + spec/cotal.schema.json",
|
|
141
141
|
"pages": [
|
|
142
142
|
{
|
|
@@ -207,7 +207,7 @@ var DOCS_BUNDLE = {
|
|
|
207
207
|
"title": "`cotal` CLI reference",
|
|
208
208
|
"kind": "Reference: describes the TypeScript reference implementation (the `cotal` CLI), not the wire contract.",
|
|
209
209
|
"summary": "cotal is the operator command line for the reference implementation: bring a mesh up, mint identities, launch agents, watch what they do, and tear it all down.",
|
|
210
|
-
"body": "# `cotal` CLI reference\n\n> **Reference**: describes the TypeScript reference implementation (the `cotal` CLI), not the wire contract. \xB7 **For:** operators \xB7 **Wire contract:** [SPEC](../SPEC.md)\n\n`cotal` is the operator command line for the reference implementation: bring a mesh up, mint\nidentities, launch agents, watch what they do, and tear it all down. It is a thin client over the\nwire contract: the normative subjects and schemas live in the [SPEC](../SPEC.md); this page is\nlookup material for the commands, not a walkthrough; if you are new, start with\n[Getting started](getting-started.md).\n\n## Running it\n\n```bash\nnpm install -g cotal-ai # puts `cotal` on your PATH (needs Node 22+)\ncotal --help # every command, grouped\ncotal --version # cotal-ai version + each installed extension's (also `cotal -v`)\ncotal <command> --help # one command's flags and usage\n```\n\n`npx cotal-ai <command>` runs it without a global install; in a dev clone, `pnpm cotal <command>`\nruns it through `tsx` with no build step. Bare `cotal` prints help. Every command generates its own\n`--help`, usage, and shell completion from its declared flags.\n\nCommands come from the surfaces the binary composes: the base mesh CLI, the manager\n(`supervise`), and the delivery daemon (`deliver`), plus any operator-installed extensions.\n`cotal ext add <npm-package>` installs any registry providers a package contributes: commands,\nruntimes, and local process lifecycle descriptors. The `web` dashboard and optional manager\nruntimes ship this way.\n\n## Commands\n\n| Area | Command | Purpose |\n|---|---|---|\n| Set up & lifecycle | [`setup`](#setup) | Guided, configure-only setup (installs, seeds personas; launches nothing) |\n| Set up & lifecycle | [`update`](#update) | Reconcile first-party extensions and check or opt into a coherent CLI upgrade |\n| Set up & lifecycle | [`up`](#up) | Start a local mesh (nats-server + JetStream), or boot a whole manifest with `-f` |\n| Set up & lifecycle | [`down`](#down) | Stop the whole stack, selected registered components, or a manifest deploy |\n| Set up & lifecycle | [`backup`](#backup-and-restore) | Create an offline full-space or registry-only artifact from a preserved cut |\n| Set up & lifecycle | [`clean`](#clean) | Configurable cleanup: purge history (live), or wipe the local store / identity (stopped) |\n| Set up & lifecycle | [`meshes`](#meshes-use-status) | List the running meshes on this machine |\n| Set up & lifecycle | [`use`](#meshes-use-status) | Set the default mesh a bare `cotal spawn` joins |\n| Set up & lifecycle | [`status`](#meshes-use-status) | Read-only diagnostics for setup, processes, and the selected mesh |\n| Agents & personas | [`spawn`](#spawn) | Launch an agent from a persona (foreground, or `--detach` via the manager) |\n| Agents & personas | [`models`](#models) | List connector model catalogs and variants from the manager |\n| Agents & personas | [`ps`](#ps-stop-attach) | List managed agents and their mesh status |\n| Agents & personas | [`stop`](#ps-stop-attach) | Ask the manager to stop a managed agent |\n| Agents & personas | [`attach`](#ps-stop-attach) | Stream and drive a managed agent's terminal (pty runtime) |\n| Agents & personas | [`personas`](#personas) | List, show, edit, create, or remove local personas |\n| Agents & personas | [`supervise`](#supervise) | Run a manager daemon (the agent supervisor / control plane) |\n| Agents & personas | [`runtimes`](#runtimes) | List the agent runtimes the manager can spawn through and whether each is reachable |\n| Messaging & watching | [`endpoints`](#endpoints) | List every endpoint in the live presence roster, including infrastructure |\n| Messaging & watching | [`send`](#send) | Send one message, then exit: DM a peer, post a channel, or ask a role |\n| Messaging & watching | [`channels`](#channels) | Inspect or set the channel registry |\n| Messaging & watching | [`history`](#history) | Clear retained message history |\n| Messaging & watching | [`console`](#console) | Live protocol view for a space (TUI, or `--plain` line stream) |\n| Messaging & watching | [`web`](#web) | Browser dashboard (installed as the `@cotal-ai/web` extension) |\n| Auth & meshes | [`mint`](#mint) | Mint a creds file for a space (static auth mode) |\n| Auth & meshes | [`login`](#login-logout) | Sign in to a per-user-auth mesh's IdP (once per machine) |\n| Auth & meshes | [`logout`](#login-logout) | Revoke the IdP session and clear the cached login |\n| Auth & meshes | [`actor`](#actor) | Manage a user-auth space's actor ledger (grant / revoke / list) |\n| Auth & meshes | [`doctor`](#doctor) | Credential-health diagnosis and repair (`doctor auth`) |\n| Auth & meshes | [`join`](#join) | Join a space as your own presence (interactive) |\n| Manifest | [`topology`](#manifest-deploys) | Validate and view a mesh manifest's access graph (read-only) |\n| Extensions & misc | [`ext`](#ext) | Install / remove operator CLI extensions |\n| Extensions & misc | [`completion`](#completion) | Print or install shell completion |\n| Extensions & misc | [`feedback`](#feedback) | Send feedback to the Cotal developers |\n| Extensions & misc | [`deliver`](#server-daemons) | Run the server-side Plane-3 delivery daemon |\n| Extensions & misc | [`feedback-intake`](#server-daemons) | Run a self-hosted feedback intake server |\n\nThe manifest modes of `up`, `spawn`, and `down` (`-f <cotal.yaml>`) plus `topology` are covered\ntogether under [Manifest deploys](#manifest-deploys).\n\n## setup\n\n```bash\ncotal setup [--full] [--demo] [--yes]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--full` | off | Redo the full guided flow (implies `--demo`) |\n| `--demo` | off | Also seed the guided expert team (`david`, `sven`, `me`) |\n| `--yes`, `-y` | off | Non-interactive accept-all (for agents / CI) |\n\nGuided setup is **configure-only**: it checks prerequisites, installs the Claude Code plugin, and\nseeds persona files, and it launches nothing (no mesh, no web, no manager). First run gets the\nnarrated flow; later runs print a status card. By default it seeds one `default` persona; the\n`david`/`sven`/`me` team is opt-in via `--demo`. See [Getting started](getting-started.md) and, for\nmaintainers, [setup internals](setup-internals.md).\n\n## update\n\n```bash\ncotal update [--self]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--self` | off | If a newer release exists, install that exact validated `cotal-ai` version globally and reconcile through the newly installed binary |\n\nWithout `--self`, `update` keeps the installed first-party surfaces coherent with the running\nbinary: it force-reconciles the four built-in connectors, then reinstalls other `@cotal-ai/*`\noperator extensions at the binary's exact version. Each extension runs in an isolated child, so one\nfailure cannot poison later replays. It then checks npm; a newer binary is an informational notice\nwith `cotal update --self` as the next command, not an automatic install.\n\nWith `--self`, the npm check happens first. When a newer release exists, Cotal installs the exact\nversion it validated, resolves and verifies that package in npm's global root, then launches that\nbinary to reconcile connectors and first-party extensions to the new generation. An npx or dev-clone\ninvocation therefore installs and continues through a separate global copy; it never claims the\nalready-running process changed. If the binary is current, `--self` performs the normal local\nreconcile without reinstalling it.\n\nThird-party extensions are listed with their installed version and recorded spec but are not\nauto-updated in v1. Floating third-party updates require `@cotal-ai/*` peer-range validation and are\na future follow-up. A failed connector/extension install, npm metadata check, or requested global\ninstall is reported and makes the command exit nonzero. Independent extension attempts continue so\nthe output includes every failure; an unavailable npm registry does not undo a completed local\nreconcile, but the command still exits nonzero because it could not establish that the install is\ncurrent.\n\n## up\n\n```bash\ncotal up [--detach] [--open] [--space <s>] [--server <url>] [--channels <path>] [--runtime <name>]\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\ncotal up -f <cotal.yaml> [--dry-run] [--runtime <name>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--server <url>` | auto (free local port) | Listen URL override |\n| `--host <host>` | \u2014 | Bind host override. With no `--server`, the broker URL is derived from it, so `--host <addr>` alone is enough to make a mesh reachable at that address; a `--host`/`--server` pair naming different addresses is refused. A wildcard bind (`0.0.0.0`, `::`) keeps a dialable loopback URL. Recorded on the mesh and reused by every later manager launch, so a repair or resume keeps remote [`attach`](#ps-stop-attach) working |\n| `--space <s>` | the folder's name | Space name |\n| `--store-dir <dir>` | \u2014 | JetStream store directory |\n| `--channels <path>` | `.cotal/channels.json` if present | Channel-registry seed file (JSON). An explicit path that is missing is an error |\n| `--restore <dir>` | \u2014 | Restore a completed offline backup before exposing the normal listener |\n| `--restore-only registry` | artifact selection | Restore only the registry component |\n| `--accept-missing-source` | off | Explicit disaster consent when the inode-bound preserved source is absent |\n| `--open` | off (auth) | Unauthenticated dev mesh: no JWT, no ACLs |\n| `--user-auth` | off | Per-user auth: people `cotal login`; connects are authorized against the actor ledger |\n| `--idp <url>` | \u2014 | With `--user-auth`: the IdP auth base URL to pin on first enable |\n| `--detach` | off | Run in the background (stop with `cotal down`) |\n| `--file <cotal.yaml>`, `-f` | \u2014 | Launch a whole mesh from a manifest |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing |\n| `--runtime <name>` | `pty` (or the manifest's, with `-f`) | Agent runtime for the mesh manager (`pty` built in; others are installed extensions, explicit-only). Resolved + probed before the broker starts; an uninstalled/unreachable runtime fails loud. With `-f`, overrides the manifest's runtime |\n\n`cotal up` boots a local nats-server with JetStream and, in auth mode (the default), JWT auth and\nper-agent ACLs; `--detach` records the mesh so `cotal spawn` from any directory can find it. With no\n`--server`, it auto-selects a free port if the default address is taken; an explicit `--server`\nstays fail-loud on collision. `--detach` also brings up the control plane (delivery daemon in auth\nmode, then the manager). The `-f` form is a [manifest deploy](#manifest-deploys); see\n[Run a mesh](run-a-mesh.md).\n\n`--user-auth --idp <url>` starts the space's auth service alongside the broker (the NATS\nauth callout plus the loopback token exchange); it is torn down with `cotal down`, and a\nre-run of `cotal up` heals a dead service on a running broker. `--user-auth` and `--open`\ncontradict each other and are refused loudly; a running broker cannot change auth mode\nwithout a `cotal down` first. See [identity & auth](identity-and-auth.md).\n\n## down\n\n```bash\ncotal down\ncotal down --preserve-state [--store-dir <dir>]\ncotal down manager [delivery auth web nats ...]\ncotal down web [--space <name>]\ncotal down -f <cotal.yaml> | --run <id> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--file <cotal.yaml>`, `-f` | \u2014 | Tear down this manifest's deploy |\n| `--run <id>` | \u2014 | Tear down one `spawn -f` run by id |\n| `--space <name>` | current mesh | With components: the mesh whose target-addressed components (e.g. `web`) to stop |\n| `--dry-run` | off | Print the manifest teardown or selected components, mutate nothing |\n| `--preserve-state` | off | Bare whole stack only: fence the manager, retain principals and durable state, stop and prove the stack down, then publish `ready` |\n| `--store-dir <dir>` | `.cotal/nats` | With `--preserve-state`: the actual store path (required for a custom store) |\n\nBare `cotal down` stops the whole local stack in dependency order. Positional component names stop\nonly those self-registered local processes; for example, `cotal down manager` leaves delivery and\nthe broker running, and `cotal down web` is available when the web extension is installed. A\ncomponent that starts target-resolved (the web dashboard) is stopped the same way: `cotal down web`\nresolves the mesh exactly like `cotal web` (registry current mesh first, `--space` to name one), so\nit works from any directory; the other components always stop under the folder you run it in. The\n`-f` / `--run` forms tear down a [manifest deploy](#manifest-deploys) without stopping the whole mesh\nand cannot be combined with component names. Stopping `nats` alone is refused while an unselected\nregistered daemon is still live; include those components or use bare `cotal down`.\n\nNormal `down` remains destructive at the logical identity/durable layer. `--preserve-state` is a\ndifferent maintenance transition: it suppresses leave/deprovision cleanup, persists the manager's\nsame-principal resume inventory, stops the entire stack without removing run/auth artifacts, and\npublishes a stable inode-bound cut only after every recorded process is proven stopped and the exact\nrecorded NATS endpoint is unreachable. A missing or stale broker pidfile never counts as stopped. The\nattempt is bound durably before the manager is fenced, the resume document and attempt-bound\n`cut-intent` are fsynced before manager commit, and the manager's commitment itself is journaled\n(`cut-committed`) before any process stops. A retry after a crash at any of those boundaries reuses\nthe exact recorded attempt and finishes the remaining stop and endpoint proofs idempotently, without\nneeding the (by then intentionally dead) manager. A partial cut never publishes `ready`. It cannot\nbe combined with component names, manifest teardown, or `--dry-run`.\n\n## clean\n\n```bash\ncotal clean <history|store|all> --force\ncotal clean restore-attempt --attempt <id> --force\ncotal clean restore-fallback --attempt <id> --force\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | `history`: target mesh |\n| `--dms` | off | `history`: also clear DM history |\n| `--store-dir <dir>` | `.cotal/nats` | `store`/`all`: JetStream store directory |\n| `--force` | \u2014 | Required: destructive, no prompting |\n| `--attempt <id>` | \u2014 | `restore-attempt`: exact stale pre-commit attempt; `restore-fallback`: matching healthy committed restore |\n\nOne configurable cleanup verb; every target requires `--force`.\n\n- `history` purges the retained message backlog on the **running** broker (channels, plus DMs\n with `--dms`). The same operation as [`history clear`](#history), which stays as an alias.\n- `store` deletes the **stopped** mesh's JetStream store (`.cotal/nats`): streams, durable\n consumers, and messages. This is the reset for stale on-disk broker state, e.g. durables\n minted by an older, incompatible Cotal generation surviving a `down`/`up` cycle.\n- `all` is `store` plus the space identity (`.cotal/auth`), the local creds and markers tied to\n it, any crash residue a normal `down` would have swept (stale pidfiles, `run/`), and the mesh's\n registry entry; the next `cotal up` mints a fresh identity.\n\n`history` needs the mesh up; `store` and `all` refuse while any recorded mesh process is still\nalive or any same-root recorded broker endpoint remains reachable (run `cotal down` first). They\nalso refuse outright on a root that holds accounts for several spaces: the store and the broker\ntrust record are shared by every space on the broker, so both targets would take out all of them\nand no `--space` can narrow that. `down`, `backup` and `up --restore` refuse there for the same\nreason. `cotal status` lists the tenants on such a root. Personas\n(`.cotal/agents`) and logs are never touched. A custom\nstore location is not recorded anywhere, so `--store-dir` must repeat whatever the mesh was\nlaunched with. Custom cleanup targets must contain either the Cotal store-generation marker or a\nreal `jetstream/` store directory; filesystem roots, project roots, and Cotal auth/maintenance trees\nare always refused.\n\n`store` and `all` also refuse every maintenance journal state. After a healthy committed restore,\n`restore-fallback` is the only supported way to remove the recorded unchanged old-store inode; it\nnever deletes the active target, requires both the exact attempt id and `--force`, and retires the\ncompleted restore journal so a later `down --preserve-state` can start a new backup cycle.\n\n## backup and restore\n\n```bash\ncotal down --preserve-state [--store-dir <dir>]\ncotal backup create <dir> [--only full|registry] [--store-dir <dir>]\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\n```\n\nBackup is offline-only. It requires the stable `ready` record from `down --preserve-state`, an exact\nstore match, no live recorded process, and an unreachable exact endpoint from the recorded cut.\nThat endpoint is probed immediately before cloning, so a live broker with a missing or stale pidfile\nis still refused. It claims the cut, reflink/copies the stopped source to a\nprivate attempt clone, and opens only that clone on a random loopback bootstrap broker with an\nindependent parent/deadline watchdog. It validates the canonical stream and pull-consumer inventory,\nwrites native snapshots with consumers excluded, and stores conservative contiguous ACK-floor\ncheckpoints separately. The original store is never opened by the backup broker, and the stack is\nnot restarted implicitly. Artifact destinations must not overlap the preserved source or maintenance\nattempt tree. Restore artifacts and targets likewise cannot nest inside or contain each other, the\npreserved source, or the maintenance attempt tree.\n\n`full` is the default and indivisible: channel registry, CHAT/DM/TASK/INBOX/DLV, ACL, MEMBERS, and\nvalidated durable checkpoints. `registry` is the sole partial artifact. Presence, derived membership\nfeed, leases, native ephemeral/history consumers, credentials, keys, tokens, owner secrets, and actor\nledger files are excluded. Artifacts are exclusively created `0700`; snapshot/checkpoint files and\nthe manifest are `0600`; `manifest.json` is written last with exact sizes and SHA-256 values. The\ndirectory is trusted operator input: hashes detect corruption, not malicious rewriting.\n\nRestore validates and stages the exact allowlisted artifact bytes before moving or creating a store.\nIt requires the same space and existing trust state. The whole pre-commit window holds a journaled\nliveness claim (coordinator, watchdogs, brokers, absolute deadline): ordinary `up` and a repeated\n`up --restore` refuse while the claim is live, and a stale attempt is recovered only after the\ndeadline has elapsed and every recorded owner is proven dead \u2014 automatically by a retried\n`up --restore`, or explicitly with `cotal clean restore-attempt --attempt <id> --force`. Nothing\never rolls back a live attempt. A registry-only artifact restores as registry-only whether or not\n`--restore-only registry` is passed; omitted infrastructure is always created and the exact\npost-restore stream inventory is asserted before commit intent. Ordinary `up` from a preserved cut\nresumes only the exact recorded source store and runtime; a contradicting `--store-dir` or\n`--runtime` fails in preflight. Authenticated restores validate the complete\nspace trust bundle before staging, including nkeys, seed matches, JWTs, signers, and space binding;\nfull restores commit to the validated operator, system-account, data-account, and active-signer root\nchain in addition to the static/user authority fingerprint. The composed commitment is revalidated\nimmediately before store mutation and never includes secret seeds. Restore never creates fresh auth.\nSame-path restores atomically retain the old\nsource at the journaled fallback path; alternate targets retain it in place; a missing canonical\nsource needs explicit `--accept-missing-source`. Quarantine and target restores use current canonical\nconfigs on isolated random-loopback brokers, never expose native snapshot consumers, and publish a\ncommit-intent immediately before the normal listener starts. Archive bytes never instantiate the real\ntarget: after quarantine validation, every stream is re-snapshotted from the validated quarantine\nstate into attempt-owned sanitized files, and the target is restored solely from those. Before that boundary, failure rolls back\nthe attempt-owned target; after it, ambiguity preserves both stores and records forward-repair\nrecourse. The cooperative maintenance lock excludes Cotal commands, not arbitrary raw NATS processes.\n\nBootstrap brokers in every auth mode \u2014 including open \u2014 mount the store under a local account with\nrandom operation-specific logins only, each carrying the exact per-phase subject permission matrix;\nnormal static credentials and user-auth sentinel/bearer connections are rejected, and no auth\nservice or callout starts. Open mode differs only in its account label, never in authority. Inventory, each stream snapshot,\nrestore initiation, exact upload id, validation, and each checkpoint recreation use separate exact\nauthorities. Every checkpoint carries the source stream's message/first/last sequence state and must\nmatch its snapshot record before mutation; core then derives and validates the only allowed start\npolicy. TASK is not a CLI exception: the same core checkpoint API recreates its canonical `DeliverAll`\nWorkQueue durable because acknowledged tasks are absent from retention and NATS forbids a\nstart-sequence policy there. Registry-only restore creates every omitted canonical stream and transient\nbucket on the isolated target before the normal listener is exposed. It deliberately does not resume\nretained agents or recreate their DM/DLV/TASK/ACL state; their identity material stays retained and\nstopped rather than being reprovisioned into a partial restore.\n\nAfter listener readiness, the manager starts attempt-bound, validates retained credentials/tokens\nwithout granting or reprovisioning, and resumes the exact persisted principals under cleanup\nsuppression. Registry-only restore uses the same flow with an empty agent set. `commitResume` is an\nidempotent validation barrier only: success must be `awaitingFinalize` with an attempt-bound 64-hex\ncommit token and does not release suppression. Under the workspace lock, the CLI first fsyncs that\nexact evidence as `manager-committed` (restore) or `resume-committed` (ordinary resume), then calls\ntoken-bound `finalizeResume`; only an `active` response for the exact token releases suppression. The\nCLI records the same token in finalization evidence before a restore becomes `active`, or before an\nordinary resume retires and consumes the marker. Re-entry from either committed state skips the prior\nidempotent activation/commit phases, retries finalization with the durable token, and finishes the\nworkspace transition. Failure before finalization preserves the committed state and cleanup\nsuppression; it is not rewritten through a degraded transition. Re-entry between any two earlier\nboundaries reuses the same attempt and may retry the idempotent phases without deleting retained state. A missing or\nchanged per-agent dependency is a named fail-closed result; the journal becomes degraded and remains\navailable for forward repair. A retry from `resume-intent`,\n`resume-active`, or `resume-degraded` reuses the same attempt and inventory after the prior listener is\nproven stopped. Every normal restore listener has an unguessable attempt-bound NATS server name. The\nCLI fsyncs its exact name/nonce, canonical endpoint, process owner, and generation-bound target identity\nimmediately after spawn. Re-entry accepts a surviving listener only when its INFO server name, live PID\nrecord, endpoint, and target identity all match that proof; degraded restore repair then moves through\nthe guarded workspace transition only after manager commit. If an uncommitted bound owner is provably\ndead, recovery retires that exact proof under the maintenance lock and binds a fresh listener for the\nsame attempt, endpoint, and target with a new nonce and server name. A live foreign/mismatched listener\nor ambiguous owner is preserved and refused, never adopted by reachability alone. A reconstructed\ncommit/degraded attempt without either the exact bound proof or a durable dead-listener replacement\nrecord fails closed even when the recorded port is free. A later ordinary startup may pass an `active`\nrestore only when its details prove manager commit and its exact recorded listener is dead.\n\n## meshes, use, status\n\n```bash\ncotal meshes\ncotal use <space>\ncotal status [--space <s>] [--server <url>]\n```\n\n`meshes` lists the running meshes on this machine; a `*` marks the `current` default a bare\n`cotal spawn` joins. `use <space>` sets that default; the selection applies from every directory,\nincluding inside another mesh's project. `status` is a read-only report: machine prerequisites\n(starting with the installed `cotal-ai` version), the installed extensions and their versions, this\nfolder's `.cotal/`, the recorded meshes, and a live snapshot of the selected mesh (roster, channels,\nmembership feed). `status` takes only `--space` / `--server` to pick the mesh to inspect; it starts\nnothing.\n\n## spawn\n\n```bash\ncotal spawn [<persona>] [--detach] [--name <n>] [--agent <a>] [--model <m>] [--variant <v>] [--prompt <text>] [--cwd <dir>]\ncotal spawn -f <cotal.yaml> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | resolved mesh | Target space |\n| `--server <url>` | registry entry | Broker URL override |\n| `--creds <path>` | \u2014 | Control-caller creds for an off-registry manager (`--detach` only) |\n| `--name <n>` | persona's `name:` | Presence-name override (does not choose the persona) |\n| `--config <persona-or-path>` | \u2014 | Persona catalog name or file path; wins over the positional |\n| `--agent <a>` | `COTAL_DEFAULT_AGENT`, else `claude` | Connector type (`claude`, `opencode`, `hermes`, \u2026) |\n| `--role <r>` | persona's `role:` | Role override |\n| `--model <m>` | persona's `model:` | Model override |\n| `--variant <v>` | persona's `variant:` | Model variant override (connector-defined; e.g. OpenCode reasoning tiers) |\n| `--cwd <dir>` | this cwd | Working directory to root the agent at |\n| `--prompt <text>` | \u2014 | Initial prompt auto-submitted at start |\n| `--resume <id>` | \u2014 | Fork an existing session id into the mesh (claude only) |\n| `--transcript` / `--no-transcript` | off | Mirror the session transcript to `tr-<name>` |\n| `--share-tools <sel>` | none | Share named operator MCP servers with the agent |\n| `--subscribe <a,b>` | persona's | Channel read-set override |\n| `--allow-subscribe <a,b>` | = subscribe | Read-ACL override |\n| `--allow-publish <a,b>` | deny | Post-ACL override |\n| `--detach`, `-d` | off | Launch via the manager into a detached PTY (reattach with `cotal attach`) |\n| `--file <cotal.yaml>`, `-f` | \u2014 | Deploy a manifest onto the running mesh |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing |\n| `--allow-stale <a,b>` | \u2014 | With `-f`: waive named stale agents (apply-only) |\n| `--runtime <name>` | manifest's | With `-f`: override the manifest's runtime |\n\nThe persona (`--config` > positional > `COTAL_DEFAULT_PERSONA` > `default`) is loaded from the\ntarget mesh's `.cotal/agents/`; the launch flags override the file. Foreground runs the agent\nattached to your terminal; `--detach` hands the launch to the running manager. Both modes get the\ndurable backstop on a mesh that runs the delivery daemon; `--live-only` skips it for a foreground\nspawn (messages posted while it is disconnected are then not replayed). A foreground exit retires\nthe agent's creds and broker footprint, like a manager despawn. See\n[Connect Claude Code](connect-claude.md) and [Agent files](agent-files.md); `-f` is a\n[manifest deploy](#manifest-deploys). (`cotal start` was merged into `cotal spawn --detach`.)\n\n## models\n\n```bash\ncotal models [--agent <connector>] [--refresh]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--agent <connector>` | all registered connectors | Connector whose catalog to list |\n| `--refresh` | off | Ask the connector to refresh its provider cache |\n\nAsks the running manager for each connector's model catalog (model ids plus their variants)\nfor connectors that expose one (OpenCode today; a connector without a catalog says so). Pick a\nresult with `cotal spawn --model <provider/model> --variant <v>`.\n\n## endpoints\n\n```bash\ncotal endpoints [--space <s>] [--server <url>] [--creds <path>]\n```\n\nLists the mesh presence roster: agents, the manager, and any other protocol endpoint, with each\nendpoint's role, kind, status, and current activity. Unlike `ps`, this is a read-only presence view;\nit is not limited to child processes owned by the manager.\n\n## ps, stop, attach\n\n```bash\ncotal ps [--space <s>]\ncotal stop --name <n> [--space <s>]\ncotal attach --name <n> [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--name <n>` | \u2014 | Managed agent to stop / attach (required) |\n\nThese are operator clients over the running manager's control plane. `ps` lists managed agents with\ntheir mesh status (`starting\u2026` / `working` / `waiting` / `offline`); on a user-auth mesh it also\nrenders each managed agent's last credential-refresh outcome, fail-closed. `attach` streams and\ndrives an agent's terminal on the `pty` runtime; detach with the escape key (Ctrl-] by default; see\n[`COTAL_DETACH_KEY`](config.md)). `stop` and `attach` need a running manager to talk to. On a\nstatic mesh they are cross-agent admin operations. On a user-auth mesh, your own agents (any agent\nunder your owner) need only the `spawn` scope; another owner's agent needs `admin` on your ledger\nrow ([identity & auth](identity-and-auth.md)). Launch detached agents with\n[`spawn --detach`](#spawn).\n\n`attach` streams over the manager's own HTTP/WS face rather than the mesh. That endpoint binds\n**loopback by default**, so nothing is exposed by accident; `cotal up --host <addr>` passes its bind\naddress down, which is what lets you attach to an agent whose manager runs on another machine. A\nbare `cotal supervise` and an embedded manager stay machine-local. Set it directly with\n`supervise --console-host <host>`.\n\nThat address is **recorded on the mesh** and carried forward, because it is a decision rather than\nsomething later commands can work out for themselves (a broker dial address is not a manager bind\naddress). Every later manager launch for the same mesh reuses it \u2014 a same-root `cotal up` repair,\nadopting a preserved or restored listener, a `spawn -f` manifest deploy \u2014 so a manager replacement\ndoes not quietly move a reachable attach face back to loopback. Passing `--host` again overrides it,\nso you can widen or narrow exposure whenever you like; a mesh that never asked stays loopback-only\nand records nothing.\n\nBecause that face carries terminal read and write for every managed agent, it is credentialed in two\ntiers. A mesh caller receives a **ticket** bound to the single agent the manager just authorized,\nsingle-use and short-lived, so one authorized attach can never be re-pointed at someone else's\nagent. The **console token** is the operator's own, reaches every agent, and is printed only to the\nmanager's output. The roster, the live feed, and the PTY stream all answer `401` without one; the\nstatic console shell is served openly, since it describes no agent.\n\n## personas\n\n```bash\ncotal personas list [-v] [--running]\ncotal personas show <name>\ncotal personas edit <name>\ncotal personas new <name> (--prompt <t> | --from <f>) [--role <r>] [--model <m>]\ncotal personas rm <name> --force\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh's persona catalog |\n| `--role <r>` | \u2014 | `new`: the persona's role |\n| `--model <m>` | \u2014 | `new`: the persona's model |\n| `--prompt <t>` | \u2014 | `new`: the persona's prompt text |\n| `--from <f>` | \u2014 | `new`: seed the prompt from a file |\n| `--verbose`, `-v` | off | `list`: include role / model / description |\n| `--running` | off | `list`: mark personas live on the mesh |\n| `--force` | \u2014 | `rm`: required, delete without prompting |\n\nPersonas are the local agent files under `.cotal/agents/` that `cotal spawn` launches. See\n[Agent files](agent-files.md) for the file format.\n\n## supervise\n\n```bash\ncotal supervise [--runtime <name>] [--space <s>] [--server <url>] [--spawn <names>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | this folder's auth space | Space to supervise |\n| `--server <url>` | the local mesh | Broker URL |\n| `--runtime <name>` | `pty` | Agent runtime (`pty` built in; extension runtimes are explicit-only) |\n| `--console-port <n>` | \u2014 | Protocol-console port |\n| `--console-host <host>` | loopback | Bind host for the console + attach endpoint. Loopback keeps it machine-local; `cotal up` passes the address it bound the broker to, which is what lets `cotal attach` reach this manager from another machine |\n| `--roster <file>` | \u2014 | Declarative roster to boot at startup |\n| `--launch <spec>` | \u2014 | Resolved manifest launch spec (from `up -f` / `spawn -f`) |\n| `--spawn <names>` | \u2014 | Comma-separated personas to pre-spawn at startup |\n\nThe manager is the agent supervisor and control plane: it answers `spawn --detach`, `stop`, `ps`,\n`attach`, and the `cotal_*` manager tools. `cotal up --detach` starts one for you; run `supervise`\ndirectly to recover a dead manager or drive a custom runtime. Default runtime is `pty`; install an\noptional provider first (`cotal ext add @cotal-ai/orca`, `@cotal-ai/tmux`, or `@cotal-ai/cmux`) and\nselect it explicitly. A missing provider or app fails loudly; there is no fallback. See [Deploy](deploy.md).\n\n## runtimes\n\n```bash\ncotal runtimes\n```\n\nLists every agent runtime the manager can spawn through: the built-in `pty`, the official providers\n(`orca`, `tmux`, `cmux`), and any custom provider installed via `cotal ext add`. Each installed\nprovider is probed so you can see what is actually reachable on this machine before selecting it:\n\n```\npty built in\norca installed \xB7 reachable @cotal-ai/orca\ntmux available \xB7 cotal ext add @cotal-ai/tmux\ncmux available \xB7 cotal ext add @cotal-ai/cmux\n```\n\n`installed \xB7 reachable` / `unreachable` is the provider's own `available()` probe; `available` means\nit is a known runtime you can add with the shown command. Selecting an unknown or uninstalled runtime\nvia `up`/`spawn --runtime <name>` fails loud and, for a known one, points at the exact `cotal ext add`\npackage \u2014 there is no silent fallback to `pty`.\n\n## send\n\n```bash\ncotal send dm <agent> \"<text>\" [--space <s>] [--server <url>] [--creds <path>]\ncotal send msg <channel> \"<text>\"\ncotal send ask <role> \"<text>\"\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and (off-registry) which credential |\n\nOne-shot messaging: connect, send a single direct message (`dm`), channel post (`msg`), or role\nask/anycast (`ask`), then exit. For a running conversation, agents use the mesh tools instead\n([MCP tools](mcp-tools.md)).\n\n## channels\n\n```bash\ncotal channels list\ncotal channels set <name> [--replay | --no-replay] [--window <n>] [--desc <s>] [--instructions <s>]\ncotal channels default --replay | --no-replay\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--replay` / `--no-replay` | \u2014 | `set`/`default`: replay history to new joiners, or not |\n| `--window <n>` | \u2014 | `set`: replay window size |\n| `--desc <s>` | \u2014 | `set`: one-line channel description |\n| `--instructions <s>` | \u2014 | `set`: instructions shown to joiners |\n\nInspects and edits the channel registry: replay policy, description, and joiner instructions. ACL\nsemantics (who may read or post) are set at mint / provision time, not here; see\n[Channels and permissions](channels-and-permissions.md). On a user-auth mesh, `list` rides your\nown login as is; `set` and `default` edit the registry over a short-lived\nchannel-writer view, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\n\n\n## history\n\n```bash\ncotal history clear --force [--dms] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--dms` | off | Also clear DM history |\n| `--force` | \u2014 | Required: clear without prompting |\n\nPurges retained channel history; `--dms` extends it to direct-message history. An alias of\n[`clean history`](#clean). On a user-auth mesh the purge rides a short-lived purger view over\nyour login, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\n\n## console\n\n```bash\ncotal console [--plain] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to watch |\n| `--plain` | off | Line stream instead of the TUI |\n\nA live protocol view for a space: a lazygit-style TUI, or a plain line stream on `--plain`. On a\nuser-auth mesh it rides the read-only admin view over your login, which needs ledger scope\n`admin`. See [Watch a mesh](watch-a-mesh.md).\n\n## web\n\n```bash\ncotal web [--detach] [--port <n>] [--no-open] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to serve |\n| `--port <n>` | `7799` | HTTP port |\n| `--detach` | off | Run in the background; stop with `cotal down web` or bare `cotal down` |\n| `--no-open` | off | Don't open the browser |\n\nThe browser observability dashboard: presence, channels, and a live feed. It is **not** part of\n`cotal up`: it ships inside `cotal-ai` as the `@cotal-ai/web` extension, seeded automatically on first\nrun (like the built-in connectors) so it always matches your CLI version. It self-registers `cotal web`\ninto this surface and serves\n`http://cotal.localhost:7799` (loopback; `*.localhost` resolves in Chrome/Firefox/Edge; Safari may\nneed `http://127.0.0.1:7799`). On a user-auth mesh the dashboard rides the read-only admin view\nover your login, and a channel purge asks for its own channel-purger view per click; both need\nledger scope `admin`. Detached mode re-execs the current Cotal installation, writes diagnostics to\nthe mesh root's `.cotal/web.log`, and reports success only after the HTTP server answers. It requires\na recorded mesh root, but can be launched from any directory once `cotal up` has recorded the mesh.\nSee [Watch a mesh](watch-a-mesh.md).\n\n## mint\n\n```bash\ncotal mint <name> [--profile <agent|observer|admin>] [--out <path>] [--signer]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--profile <agent\\|observer\\|admin>` | `agent` | Credential profile |\n| `--out <path>` | `.cotal/auth/creds/<name>.creds` | Output path |\n| `--signer` | off | Emit a stripped account-signing file instead |\n| `--force` | off | With `--signer`: overwrite an existing file |\n| `--allow-subscribe <a,b>` | profile default | Read-ACL override |\n| `--allow-publish <a,b>` | profile default | Post-ACL override |\n\nMints a NATS creds file for a space in **static** auth mode, scoped to a profile and (optionally)\nexplicit read/post ACLs. `--signer` emits an account-signing file for delegating minting to another\nhost. A per-user-auth space refuses `mint`: agents there join under a logged-in user\n([`login`](#login-logout) + [`actor grant`](#actor)), never via a handed-out creds file. See\n[Identity and auth](identity-and-auth.md).\n\n## login, logout\n\n```bash\ncotal login --idp <auth base URL> [--client-id <id>]\ncotal logout --idp <auth base URL>\n```\n\nSigns you in to a per-user-auth mesh's IdP (device code flow) and caches the session; run it\nonce per machine. It prints your IdP subject, the id the operator grants against. After a\nlogin, every command on that mesh works under your identity: each connect takes a fresh IdP\nproof, exchanges it locally for a short-lived bearer, and is authorized against the actor\nledger at connect time. `logout` revokes the IdP session and clears the cache. See\n[identity & auth](identity-and-auth.md).\n\n## actor\n\n```bash\ncotal actor grant <actor> --sub <IdP subject> [--scope a,b] [--allow-subscribe a,b] [--allow-publish a,b] [--role <r>] [--label <l>]\ncotal actor revoke <actor> (--sub <IdP subject> | --owner <u_\u2026>)\ncotal actor list\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | the folder's | Space whose ledger to manage |\n| `--sub <subject>` | \u2014 | The IdP subject (shown by `cotal login`) the actor belongs to |\n| `--owner <u_\u2026>` | \u2014 | The derived owner token (alternative to `--sub`) |\n| `--scope <a,b>` | `spawn,role:default` | Capability scope (`''` = none; `spawn` = may run agents, `role:<r>` = may delegate role r, `admin` = cross-agent control) |\n| `--allow-subscribe <a,b>` | `>` (all channels) | Channel read ACL; the user's envelope, their agents can never read beyond it |\n| `--allow-publish <a,b>` | `>` (all channels) | Channel post ACL; also the envelope for their agents' posting |\n| `--role <r>` | \u2014 | Role (scopes the task-queue consumer) |\n| `--label <l>` | \u2014 | Display label for `actor list` (never the IdP subject) |\n\nThe actor ledger is the single authorization source of a user-auth space: no row, no access.\nA bare `grant` is the **full** envelope (all channels, may spawn); the flags narrow it. A\nre-grant **replaces** the row, so to add a capability, re-grant with it added to the current\nscope (`cotal actor list` shows what a row holds). `revoke` denies the next exchange and the\nnext connect with no restart, and evicts the principal's live connections. Managed-agent rows\n(written by the spawn path) live in a disjoint row space this command never touches. See\n[identity & auth](identity-and-auth.md).\n\n## doctor\n\n```bash\ncotal doctor auth [--fix]\n```\n\nCredential-health diagnosis and repair for this folder's mesh: renders every managed\ncredential as healthy / near-expiry / expired and ends in `healthy` or the exact next\ncommand; `--fix` applies the repairs it can. The one surface every stale-credential error\npoints at.\n\n## join\n\n```bash\ncotal join --space <s> --name <n> [--role <r>] [--channel <c>]\ncotal join --link <url> | --token <t>\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and which credential |\n| `--name <n>` | \u2014 | Your presence name |\n| `--role <r>` | \u2014 | Your role |\n| `--channel <c>` | \u2014 | Channel to join |\n| `--kind <k>` | `agent` | Endpoint kind |\n| `--link <url>` | \u2014 | Join link (`cotal://\u2026`) |\n| `--token <t>` | \u2014 | Join token |\n| `--lifecycle-uid <uid>` | \u2014 | Required with `--creds`: the lifecycle UID minted alongside the credential (`COTAL_LIFECYCLE_UID` works too). A credential's durable grants name exact lifecycle-keyed resources, so `join` refuses to invent one |\n| `--tls` | off | Connect over TLS |\n\nAn interactive presence: join a space under your own name and role, without launching an agent\nharness. A `--link` or `--token` supplies the where and the auth in one value. See\n[Spaces](spaces.md) and [Identity and auth](identity-and-auth.md).\n\n## Manifest deploys\n\nA `cotal.yaml` manifest declares a whole mesh (channels, personas, roles, and ACLs) in one file.\nThree commands consume it, plus a read-only validator:\n\n```bash\ncotal up -f cotal.yaml # boot a fresh mesh from the manifest\ncotal spawn -f cotal.yaml # deploy the manifest additively onto a running mesh\ncotal down -f cotal.yaml # tear that deploy down (or --run <id> for one run)\ncotal topology view -f cotal.yaml # validate + view the access graph, change nothing\n```\n\n`up -f` and `spawn -f` differ in target: `up -f` brings up a new broker and applies the manifest;\n`spawn -f` requires an already-reachable mesh and applies additively (ownership-scoped). On a\nuser-auth mesh, `spawn -f` deploys over your own login (the deployer view, gated on ledger scope\n`spawn`): the manifest's agents land under your owner, a manifest claiming another owner is\nrefused, and seeding new channels additionally needs scope `admin`. Both take\n`--dry-run` to print the plan without mutating anything. `topology` validates the manifest and\nrenders its channel / role / ACL graph. See [Define a team](define-a-team.md) and the\n[manifest reference](manifest.md).\n\n## ext\n\n```bash\ncotal ext # same as `list`\ncotal ext add <npm-package>\ncotal ext remove <name>\ncotal ext list\ncotal ext root # print just the install prefix (scriptable)\ncotal ext seed [--repair|--reset|--force]\n```\n\nOperator-installed extensions: `add` installs an npm package into a cotal-owned prefix and records\nevery registry provider it contributes. Commands appear in help, completion, and dispatch; runtime\nproviders are lazy-loaded by commands such as `supervise`; local process providers participate in\n`status` and selective `down`. `remove` and `list` manage them. The `@cotal-ai/web` dashboard is the\ncanonical command/process example. Installed packages and their location are described in\n[config](config.md).\n\nBare `cotal ext` lists the inventory, headed by the install prefix. That prefix is a cotal-owned npm\nroot kept **separate** from npm's own global tree, so these packages never show up in `npm list -g` \u2014\n`cotal ext` (or the Extensions section of `cotal status`) is the canonical inventory. `cotal ext root`\nprints only the path, for scripts. The versions shown are the manifest pin recorded at add time.\n\nRemoving an extension that owns a running local process is refused with the mesh root and its\n`cotal down <component>` command; stop it first so uninstalling the package never strands a process\nwhose lifecycle provider is gone.\n\n### Built-in connectors are seeded extensions\n\nThe first-party agent connectors (`claude`, `opencode`, `codex`, `hermes`, `pi`) are not compiled into\nthe binary. They are seeded on first run through the **same** `ext add` path a third party uses, and\nappear in `cotal ext list` like any other extension. So you can remove one you do not want\n(`cotal ext remove @cotal-ai/connector-hermes`), and a deliberately-removed connector STAYS removed\nacross upgrades. `cotal ext add <your-package>` adds a third-party connector the same way. The web\ndashboard (`@cotal-ai/web`, providing `command:web`) is a fifth built-in seeded on the same path.\n\n`cotal ext seed` is the maintenance entry for that seeding (it runs automatically on the first real\ncommand of each boot, so you rarely call it):\n\n| Flag | Meaning |\n|---|---|\n| (none) | Reconcile: seed any never-seeded built-in, refresh a seeded one whose version the binary bumped, leave a removed one removed. A no-op once current. |\n| `--repair` | Recover after an interrupted seed or a lost authority (rebuilds the interrupted connector; restores the removed-vs-never-seeded record from its durable backup). |\n| `--reset` | Discard the record and re-seed all six built-ins (the five connectors plus the web dashboard). **Resurrects any you removed.** Rebuilds cleanly over corrupt seed state. |\n| `--force` | Re-seed the built-ins even when the version stamp is current or a downgrade. |\n\nThe default connector for a bare `cotal spawn` (no `--agent`) is `claude`; set `COTAL_DEFAULT_AGENT`\n(e.g. `opencode`) to change it. An `--agent` naming a removed connector fails loud with the exact\n`cotal ext add` to restore it. Set `COTAL_SKIP_CONNECTOR_SEED=1` to turn off the automatic first-run\nseed/refresh entirely (for a controlled or offline setup that manages connectors by hand); `cotal ext\nseed` still runs on request.\n\n## completion\n\n```bash\ncotal completion <bash|zsh|fish|powershell> # print a stub to eval / source\ncotal completion install [shell] # install it persistently\n```\n\nPrints or installs shell completion. Completion candidates come from each command's declared flags\nand, where useful, live mesh state (spaces, personas, managed agents) resolved offline.\n\n## feedback\n\n```bash\ncotal feedback \"<summary>\" [--type <t>] [--email <e>] [--details <text>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--type <t>` | \u2014 | `bug` \\| `idea` \\| `friction` \\| `praise` \\| `other` |\n| `--details <text>` | \u2014 | Longer free-form details |\n| `--severity <s>` | \u2014 | `low` \\| `medium` \\| `high` |\n| `--area <a>` | \u2014 | The part of Cotal this concerns |\n| `--email <e>` | git email | Contact email (required on the keyless public path) |\n| `--name <n>` | \u2014 | Your name (optional) |\n| `--url <url>` | keyed / public intake | Intake URL override |\n| `--key <k>` | `COTAL_FEEDBACK_KEY` | Feedback key |\n\nSends feedback to the Cotal developers. With a key (`--key` / `COTAL_FEEDBACK_KEY`) it routes to the\nkeyed beta intake; without one it goes to the public `cotal.ai` intake and requires a contact email\n(`--email` / `COTAL_FEEDBACK_EMAIL`, else your git email). Run a self-hosted intake with\n[`feedback-intake`](#server-daemons).\n\n## Server daemons\n\nTwo long-lived infra roles ship with the CLI. They are not part of everyday operation; the delivery\ndaemon comes up automatically with `cotal up --detach` in auth mode.\n\n```bash\ncotal deliver --space <s> [--server <url>] [--creds <file>]\ncotal auth-service --space <s> --server <url> [--port <n>]\ncotal feedback-intake --keys <keys.json> [--port <n>] [--creds <file>]\n```\n\n`auth-service` runs a user-auth space's identity plane (the NATS auth callout plus the\nloopback token exchange and JWKS); `cotal up --user-auth` starts and supervises it for you,\nso you run it directly only to recover one by hand.\n\n`deliver` runs the server-side Plane-3 delivery daemon: the durable backstop and membership/ACL\nauthority. It is auth-mode-only and single-instance (`--shard`/`--shards` accept only `N=1`);\n`--dev-mint` mints a scoped cred from the local signer for standalone dev. See the\n[delivery daemon](delivery-daemon.md). `feedback-intake` runs a self-hosted feedback server\n(requires `--keys` and a scoped `--creds`), announcing submissions into a space channel; flags\ninclude `--host`/`--port`, `--store`, `--space`/`--channel`, `--max-bytes`, and `--rate-limit`.\n\n## Plumbing\n\n`cotal __complete <words\u2026>` is the internal entry the shell-completion stubs call to emit candidates\nfor the current command line; you never run it directly. `cotal agent-bearer` is machine-facing\nplumbing on user-auth meshes: spawned agents exec it to print a fresh short-lived bearer from their\nspawn-time secret; you never run it directly either. (`cotal start` is a removed tombstone: it\nerrors and points you to `cotal spawn --detach`.)\n"
|
|
210
|
+
"body": "# `cotal` CLI reference\n\n> **Reference**: describes the TypeScript reference implementation (the `cotal` CLI), not the wire contract. \xB7 **For:** operators \xB7 **Wire contract:** [SPEC](../SPEC.md)\n\n`cotal` is the operator command line for the reference implementation: bring a mesh up, mint\nidentities, launch agents, watch what they do, and tear it all down. It is a thin client over the\nwire contract: the normative subjects and schemas live in the [SPEC](../SPEC.md); this page is\nlookup material for the commands, not a walkthrough; if you are new, start with\n[Getting started](getting-started.md).\n\n## Running it\n\n```bash\nnpm install -g cotal-ai # puts `cotal` on your PATH (needs Node 22+)\ncotal --help # every command, grouped\ncotal --version # cotal-ai version + each installed extension's (also `cotal -v`)\ncotal <command> --help # one command's flags and usage\n```\n\n`npx cotal-ai <command>` runs it without a global install; in a dev clone, `pnpm cotal <command>`\nruns it through `tsx` with no build step. Bare `cotal` prints help. Every command generates its own\n`--help`, usage, and shell completion from its declared flags.\n\nCommands come from the surfaces the binary composes: the base mesh CLI, the manager\n(`supervise`), and the delivery daemon (`deliver`), plus any operator-installed extensions.\n`cotal ext add <npm-package>` installs any registry providers a package contributes: commands,\nruntimes, and local process lifecycle descriptors. The `web` dashboard and optional manager\nruntimes ship this way.\n\n## Commands\n\n| Area | Command | Purpose |\n|---|---|---|\n| Set up & lifecycle | [`setup`](#setup) | Guided, configure-only setup (installs, seeds personas; launches nothing) |\n| Set up & lifecycle | [`update`](#update) | Reconcile first-party extensions and check or opt into a coherent CLI upgrade |\n| Set up & lifecycle | [`up`](#up) | Start a local mesh (nats-server + JetStream), or boot a whole manifest with `-f` |\n| Set up & lifecycle | [`down`](#down) | Stop the whole stack, selected registered components, or a manifest deploy |\n| Set up & lifecycle | [`backup`](#backup-and-restore) | Create an offline full-space or registry-only artifact from a preserved cut |\n| Set up & lifecycle | [`clean`](#clean) | Configurable cleanup: purge history (live), or wipe the local store / identity (stopped) |\n| Set up & lifecycle | [`meshes`](#meshes-use-status) | List the running meshes on this machine |\n| Set up & lifecycle | [`use`](#meshes-use-status) | Set the default mesh a bare `cotal spawn` joins |\n| Set up & lifecycle | [`status`](#meshes-use-status) | Read-only diagnostics for setup, processes, and the selected mesh |\n| Agents & personas | [`spawn`](#spawn) | Launch an agent from a persona (foreground, or `--detach` via the manager) |\n| Agents & personas | [`models`](#models) | List connector model catalogs and variants from the manager |\n| Agents & personas | [`ps`](#ps-stop-attach) | List managed agents and their mesh status |\n| Agents & personas | [`stop`](#ps-stop-attach) | Ask the manager to stop a managed agent |\n| Agents & personas | [`attach`](#ps-stop-attach) | Stream and drive a managed agent's terminal (pty runtime) |\n| Agents & personas | [`personas`](#personas) | List, show, edit, create, or remove local personas |\n| Agents & personas | [`supervise`](#supervise) | Run a manager daemon (the agent supervisor / control plane) |\n| Agents & personas | [`runtimes`](#runtimes) | List the agent runtimes the manager can spawn through and whether each is reachable |\n| Messaging & watching | [`endpoints`](#endpoints) | List every endpoint in the live presence roster, including infrastructure |\n| Messaging & watching | [`send`](#send) | Send one message, then exit: DM a peer, post a channel, or ask a role |\n| Messaging & watching | [`channels`](#channels) | Inspect or set the channel registry |\n| Messaging & watching | [`history`](#history) | Clear retained message history |\n| Messaging & watching | [`console`](#console) | Live protocol view for a space (TUI, or `--plain` line stream) |\n| Messaging & watching | [`web`](#web) | Browser dashboard (installed as the `@cotal-ai/web` extension) |\n| Auth & meshes | [`mint`](#mint) | Mint a creds file for a space (static auth mode) |\n| Auth & meshes | [`login`](#login-logout) | Sign in to a per-user-auth mesh's IdP (once per machine) |\n| Auth & meshes | [`logout`](#login-logout) | Revoke the IdP session and clear the cached login |\n| Auth & meshes | [`actor`](#actor) | Manage a user-auth space's actor ledger (grant / revoke / list) |\n| Auth & meshes | [`doctor`](#doctor) | Credential-health diagnosis and repair (`doctor auth`) |\n| Auth & meshes | [`join`](#join) | Join a space as your own presence (interactive) |\n| Manifest | [`topology`](#manifest-deploys) | Validate and view a mesh manifest's access graph (read-only) |\n| Extensions & misc | [`ext`](#ext) | Install / remove operator CLI extensions |\n| Extensions & misc | [`completion`](#completion) | Print or install shell completion |\n| Extensions & misc | [`feedback`](#feedback) | Send feedback to the Cotal developers |\n| Extensions & misc | [`deliver`](#server-daemons) | Run the server-side Plane-3 delivery daemon |\n| Extensions & misc | [`feedback-intake`](#server-daemons) | Run a self-hosted feedback intake server |\n\nThe manifest modes of `up`, `spawn`, and `down` (`-f <cotal.yaml>`) plus `topology` are covered\ntogether under [Manifest deploys](#manifest-deploys).\n\n## setup\n\n```bash\ncotal setup [--full] [--demo] [--yes]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--full` | off | Redo the full guided flow (implies `--demo`) |\n| `--demo` | off | Also seed the guided expert team (`david`, `sven`, `me`) |\n| `--yes`, `-y` | off | Non-interactive accept-all (for agents / CI) |\n\nGuided setup is **configure-only**: it checks prerequisites, installs the Claude Code plugin, and\nseeds persona files, and it launches nothing (no mesh, no web, no manager). First run gets the\nnarrated flow; later runs print a status card. By default it seeds one `default` persona; the\n`david`/`sven`/`me` team is opt-in via `--demo`. See [Getting started](getting-started.md) and, for\nmaintainers, [setup internals](setup-internals.md).\n\n## update\n\n```bash\ncotal update [--self]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--self` | off | If a newer release exists, install that exact validated `cotal-ai` version globally and reconcile through the newly installed binary |\n\nWithout `--self`, `update` keeps the installed first-party surfaces coherent with the running\nbinary: it force-reconciles the four built-in connectors, then reinstalls other `@cotal-ai/*`\noperator extensions at the binary's exact version. Each extension runs in an isolated child, so one\nfailure cannot poison later replays. It then checks npm; a newer binary is an informational notice\nwith `cotal update --self` as the next command, not an automatic install.\n\nWith `--self`, the npm check happens first. When a newer release exists, Cotal installs the exact\nversion it validated, resolves and verifies that package in npm's global root, then launches that\nbinary to reconcile connectors and first-party extensions to the new generation. An npx or dev-clone\ninvocation therefore installs and continues through a separate global copy; it never claims the\nalready-running process changed. If the binary is current, `--self` performs the normal local\nreconcile without reinstalling it.\n\nThird-party extensions are listed with their installed version and recorded spec but are not\nauto-updated in v1. Floating third-party updates require `@cotal-ai/*` peer-range validation and are\na future follow-up. A failed connector/extension install, npm metadata check, or requested global\ninstall is reported and makes the command exit nonzero. Independent extension attempts continue so\nthe output includes every failure; an unavailable npm registry does not undo a completed local\nreconcile, but the command still exits nonzero because it could not establish that the install is\ncurrent.\n\n## up\n\n```bash\ncotal up [--detach] [--open] [--space <s>] [--server <url>] [--channels <path>] [--runtime <name>]\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\ncotal up -f <cotal.yaml> [--dry-run] [--runtime <name>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--server <url>` | auto (free local port) | Listen URL override |\n| `--host <host>` | \u2014 | Bind host override. With no `--server`, the broker URL is derived from it, so `--host <addr>` alone is enough to make a mesh reachable at that address; a `--host`/`--server` pair naming different addresses is refused. A wildcard bind (`0.0.0.0`, `::`) keeps a dialable loopback URL. Recorded on the mesh and reused by every later manager launch, so a repair or resume keeps remote [`attach`](#ps-stop-attach) working |\n| `--space <s>` | the folder's name | Space name |\n| `--store-dir <dir>` | \u2014 | JetStream store directory |\n| `--channels <path>` | `.cotal/channels.json` if present | Channel-registry seed file (JSON). An explicit path that is missing is an error |\n| `--restore <dir>` | \u2014 | Restore a completed offline backup before exposing the normal listener |\n| `--restore-only registry` | artifact selection | Restore only the registry component |\n| `--accept-missing-source` | off | Explicit disaster consent when the inode-bound preserved source is absent |\n| `--open` | off (auth) | Unauthenticated dev mesh: no JWT, no ACLs |\n| `--user-auth` | off | Per-user auth: people `cotal login`; connects are authorized against the actor ledger |\n| `--idp <url>` | \u2014 | With `--user-auth`: the IdP auth base URL to pin on first enable |\n| `--detach` | off | Run in the background (stop with `cotal down`) |\n| `--file <cotal.yaml>`, `-f` | \u2014 | Launch a whole mesh from a manifest |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing |\n| `--runtime <name>` | `pty` (or the manifest's, with `-f`) | Agent runtime for the mesh manager (`pty` built in; others are installed extensions, explicit-only). Resolved + probed before the broker starts; an uninstalled/unreachable runtime fails loud. With `-f`, overrides the manifest's runtime |\n\n`cotal up` boots a local nats-server with JetStream and, in auth mode (the default), JWT auth and\nper-agent ACLs; `--detach` records the mesh so `cotal spawn` from any directory can find it. With no\n`--server`, it auto-selects a free port if the default address is taken; an explicit `--server`\nstays fail-loud on collision. `--detach` also brings up the control plane (delivery daemon in auth\nmode, then the manager). The `-f` form is a [manifest deploy](#manifest-deploys); see\n[Run a mesh](run-a-mesh.md).\n\n`--user-auth --idp <url>` starts the space's auth service alongside the broker (the NATS\nauth callout plus the loopback token exchange); it is torn down with `cotal down`, and a\nre-run of `cotal up` heals a dead service on a running broker. `--user-auth` and `--open`\ncontradict each other and are refused loudly; a running broker cannot change auth mode\nwithout a `cotal down` first. See [identity & auth](identity-and-auth.md).\n\n## down\n\n```bash\ncotal down\ncotal down --preserve-state [--store-dir <dir>]\ncotal down manager [delivery auth web nats ...]\ncotal down web [--space <name>]\ncotal down -f <cotal.yaml> | --run <id> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--file <cotal.yaml>`, `-f` | \u2014 | Tear down this manifest's deploy |\n| `--run <id>` | \u2014 | Tear down one `spawn -f` run by id |\n| `--space <name>` | current mesh | With components: the mesh whose target-addressed components (e.g. `web`) to stop |\n| `--dry-run` | off | Print the manifest teardown or selected components, mutate nothing |\n| `--preserve-state` | off | Bare whole stack only: fence the manager, retain principals and durable state, stop and prove the stack down, then publish `ready` |\n| `--store-dir <dir>` | `.cotal/nats` | With `--preserve-state`: the actual store path (required for a custom store) |\n\nBare `cotal down` stops the whole local stack in dependency order. Positional component names stop\nonly those self-registered local processes; for example, `cotal down manager` leaves delivery and\nthe broker running, and `cotal down web` is available when the web extension is installed. A\ncomponent that starts target-resolved (the web dashboard) is stopped the same way: `cotal down web`\nresolves the mesh exactly like `cotal web` (registry current mesh first, `--space` to name one), so\nit works from any directory; the other components always stop under the folder you run it in. The\n`-f` / `--run` forms tear down a [manifest deploy](#manifest-deploys) without stopping the whole mesh\nand cannot be combined with component names. Stopping `nats` alone is refused while an unselected\nregistered daemon is still live; include those components or use bare `cotal down`.\n\nNormal `down` remains destructive at the logical identity/durable layer. `--preserve-state` is a\ndifferent maintenance transition: it suppresses leave/deprovision cleanup, persists the manager's\nsame-principal resume inventory, stops the entire stack without removing run/auth artifacts, and\npublishes a stable inode-bound cut only after every recorded process is proven stopped and the exact\nrecorded NATS endpoint is unreachable. A missing or stale broker pidfile never counts as stopped. The\nattempt is bound durably before the manager is fenced, the resume document and attempt-bound\n`cut-intent` are fsynced before manager commit, and the manager's commitment itself is journaled\n(`cut-committed`) before any process stops. A retry after a crash at any of those boundaries reuses\nthe exact recorded attempt and finishes the remaining stop and endpoint proofs idempotently, without\nneeding the (by then intentionally dead) manager. A partial cut never publishes `ready`. It cannot\nbe combined with component names, manifest teardown, or `--dry-run`.\n\n## clean\n\n```bash\ncotal clean <history|store|all> --force\ncotal clean restore-attempt --attempt <id> --force\ncotal clean restore-fallback --attempt <id> --force\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | `history`: target mesh |\n| `--dms` | off | `history`: also clear DM history |\n| `--store-dir <dir>` | `.cotal/nats` | `store`/`all`: JetStream store directory |\n| `--force` | \u2014 | Required: destructive, no prompting |\n| `--attempt <id>` | \u2014 | `restore-attempt`: exact stale pre-commit attempt; `restore-fallback`: matching healthy committed restore |\n\nOne configurable cleanup verb; every target requires `--force`.\n\n- `history` purges the retained message backlog on the **running** broker (channels, plus DMs\n with `--dms`). The same operation as [`history clear`](#history), which stays as an alias.\n- `store` deletes the **stopped** mesh's JetStream store (`.cotal/nats`): streams, durable\n consumers, and messages. This is the reset for stale on-disk broker state, e.g. durables\n minted by an older, incompatible Cotal generation surviving a `down`/`up` cycle.\n- `all` is `store` plus the space identity (`.cotal/auth`), the local creds and markers tied to\n it, any crash residue a normal `down` would have swept (stale pidfiles, `run/`), and the mesh's\n registry entry; the next `cotal up` mints a fresh identity.\n\n`history` needs the mesh up; `store` and `all` refuse while any recorded mesh process is still\nalive or any same-root recorded broker endpoint remains reachable (run `cotal down` first). They\nalso refuse outright on a root that holds accounts for several spaces: the store and the broker\ntrust record are shared by every space on the broker, so both targets would take out all of them\nand no `--space` can narrow that. `down`, `backup` and `up --restore` refuse there for the same\nreason. `cotal status` lists the tenants on such a root. Personas\n(`.cotal/agents`) and logs are never touched. A custom\nstore location is not recorded anywhere, so `--store-dir` must repeat whatever the mesh was\nlaunched with. Custom cleanup targets must contain either the Cotal store-generation marker or a\nreal `jetstream/` store directory; filesystem roots, project roots, and Cotal auth/maintenance trees\nare always refused.\n\n`store` and `all` also refuse every maintenance journal state. After a healthy committed restore,\n`restore-fallback` is the only supported way to remove the recorded unchanged old-store inode; it\nnever deletes the active target, requires both the exact attempt id and `--force`, and retires the\ncompleted restore journal so a later `down --preserve-state` can start a new backup cycle.\n\n## backup and restore\n\n```bash\ncotal down --preserve-state [--store-dir <dir>]\ncotal backup create <dir> [--only full|registry] [--store-dir <dir>]\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\n```\n\nBackup is offline-only. It requires the stable `ready` record from `down --preserve-state`, an exact\nstore match, no live recorded process, and an unreachable exact endpoint from the recorded cut.\nThat endpoint is probed immediately before cloning, so a live broker with a missing or stale pidfile\nis still refused. It claims the cut, reflink/copies the stopped source to a\nprivate attempt clone, and opens only that clone on a random loopback bootstrap broker with an\nindependent parent/deadline watchdog. It validates the canonical stream and pull-consumer inventory,\nwrites native snapshots with consumers excluded, and stores conservative contiguous ACK-floor\ncheckpoints separately. The original store is never opened by the backup broker, and the stack is\nnot restarted implicitly. Artifact destinations must not overlap the preserved source or maintenance\nattempt tree. Restore artifacts and targets likewise cannot nest inside or contain each other, the\npreserved source, or the maintenance attempt tree.\n\n`full` is the default and indivisible: channel registry, CHAT/DM/TASK/INBOX/DLV, ACL, MEMBERS, and\nvalidated durable checkpoints. `registry` is the sole partial artifact. Presence, derived membership\nfeed, leases, native ephemeral/history consumers, credentials, keys, tokens, owner secrets, and actor\nledger files are excluded. Artifacts are exclusively created `0700`; snapshot/checkpoint files and\nthe manifest are `0600`; `manifest.json` is written last with exact sizes and SHA-256 values. The\ndirectory is trusted operator input: hashes detect corruption, not malicious rewriting.\n\nRestore validates and stages the exact allowlisted artifact bytes before moving or creating a store.\nIt requires the same space and existing trust state. The whole pre-commit window holds a journaled\nliveness claim (coordinator, watchdogs, brokers, absolute deadline): ordinary `up` and a repeated\n`up --restore` refuse while the claim is live, and a stale attempt is recovered only after the\ndeadline has elapsed and every recorded owner is proven dead \u2014 automatically by a retried\n`up --restore`, or explicitly with `cotal clean restore-attempt --attempt <id> --force`. Nothing\never rolls back a live attempt. A registry-only artifact restores as registry-only whether or not\n`--restore-only registry` is passed; omitted infrastructure is always created and the exact\npost-restore stream inventory is asserted before commit intent. Ordinary `up` from a preserved cut\nresumes only the exact recorded source store and runtime; a contradicting `--store-dir` or\n`--runtime` fails in preflight. Authenticated restores validate the complete\nspace trust bundle before staging, including nkeys, seed matches, JWTs, signers, and space binding;\nfull restores commit to the validated operator, system-account, data-account, and active-signer root\nchain in addition to the static/user authority fingerprint. The composed commitment is revalidated\nimmediately before store mutation and never includes secret seeds. Restore never creates fresh auth.\nSame-path restores atomically retain the old\nsource at the journaled fallback path; alternate targets retain it in place; a missing canonical\nsource needs explicit `--accept-missing-source`. Quarantine and target restores use current canonical\nconfigs on isolated random-loopback brokers, never expose native snapshot consumers, and publish a\ncommit-intent immediately before the normal listener starts. Archive bytes never instantiate the real\ntarget: after quarantine validation, every stream is re-snapshotted from the validated quarantine\nstate into attempt-owned sanitized files, and the target is restored solely from those. Before that boundary, failure rolls back\nthe attempt-owned target; after it, ambiguity preserves both stores and records forward-repair\nrecourse. The cooperative maintenance lock excludes Cotal commands, not arbitrary raw NATS processes.\n\nBootstrap brokers in every auth mode \u2014 including open \u2014 mount the store under a local account with\nrandom operation-specific logins only, each carrying the exact per-phase subject permission matrix;\nnormal static credentials and user-auth sentinel/bearer connections are rejected, and no auth\nservice or callout starts. Open mode differs only in its account label, never in authority. Inventory, each stream snapshot,\nrestore initiation, exact upload id, validation, and each checkpoint recreation use separate exact\nauthorities. Every checkpoint carries the source stream's message/first/last sequence state and must\nmatch its snapshot record before mutation; core then derives and validates the only allowed start\npolicy. TASK is not a CLI exception: the same core checkpoint API recreates its canonical `DeliverAll`\nWorkQueue durable because acknowledged tasks are absent from retention and NATS forbids a\nstart-sequence policy there. Registry-only restore creates every omitted canonical stream and transient\nbucket on the isolated target before the normal listener is exposed. It deliberately does not resume\nretained agents or recreate their DM/DLV/TASK/ACL state; their identity material stays retained and\nstopped rather than being reprovisioned into a partial restore.\n\nAfter listener readiness, the manager starts attempt-bound, validates retained credentials/tokens\nwithout granting or reprovisioning, and resumes the exact persisted principals under cleanup\nsuppression. Registry-only restore uses the same flow with an empty agent set. `commitResume` is an\nidempotent validation barrier only: success must be `awaitingFinalize` with an attempt-bound 64-hex\ncommit token and does not release suppression. Under the workspace lock, the CLI first fsyncs that\nexact evidence as `manager-committed` (restore) or `resume-committed` (ordinary resume), then calls\ntoken-bound `finalizeResume`; only an `active` response for the exact token releases suppression. The\nCLI records the same token in finalization evidence before a restore becomes `active`, or before an\nordinary resume retires and consumes the marker. Re-entry from either committed state skips the prior\nidempotent activation/commit phases, retries finalization with the durable token, and finishes the\nworkspace transition. Failure before finalization preserves the committed state and cleanup\nsuppression; it is not rewritten through a degraded transition. Re-entry between any two earlier\nboundaries reuses the same attempt and may retry the idempotent phases without deleting retained state. A missing or\nchanged per-agent dependency is a named fail-closed result; the journal becomes degraded and remains\navailable for forward repair. A retry from `resume-intent`,\n`resume-active`, or `resume-degraded` reuses the same attempt and inventory after the prior listener is\nproven stopped. Every normal restore listener has an unguessable attempt-bound NATS server name. The\nCLI fsyncs its exact name/nonce, canonical endpoint, process owner, and generation-bound target identity\nimmediately after spawn. Re-entry accepts a surviving listener only when its INFO server name, live PID\nrecord, endpoint, and target identity all match that proof; degraded restore repair then moves through\nthe guarded workspace transition only after manager commit. If an uncommitted bound owner is provably\ndead, recovery retires that exact proof under the maintenance lock and binds a fresh listener for the\nsame attempt, endpoint, and target with a new nonce and server name. A live foreign/mismatched listener\nor ambiguous owner is preserved and refused, never adopted by reachability alone. A reconstructed\ncommit/degraded attempt without either the exact bound proof or a durable dead-listener replacement\nrecord fails closed even when the recorded port is free. A later ordinary startup may pass an `active`\nrestore only when its details prove manager commit and its exact recorded listener is dead.\n\n## meshes, use, status\n\n```bash\ncotal meshes\ncotal meshes add # guided, on a terminal\ncotal meshes add <space> --server <url> [--root <dir>] [--mode auth|open] [--force]\ncotal meshes rm <space> [<space> \u2026] [--force]\ncotal use <space>\ncotal status [--space <s>] [--server <url>]\n```\n\n`meshes` lists the meshes this machine knows; a `*` marks the `current` default a bare\n`cotal spawn` joins.\n\nRun on a terminal with the space or `--server` missing, **`meshes add` is guided**: it asks for the\none thing that cannot be derived (the broker URL), probes it, and tells you what answered - open or\nrequiring credentials. It then offers the spaces your `--root` already holds credentials for, states\nthe mode as a fact about that broker rather than asking, and shows the exact record before writing\nanything. A broker that does not answer, or a space name already registered, becomes a choice rather\nthan an error. Anything you pass on the command line is taken as given and not asked again. Without\na terminal - a script, an agent, CI - nothing prompts and the flag form's errors stand\n(`COTAL_NO_PROMPT=1` forces that too).\n\n`cotal up` and `cotal down` maintain their own records. `meshes add` registers a mesh they cannot\nspeak for: one running on another machine, a shared broker, a hosted space. `--root` is the folder\nwhose `.cotal/auth` holds that mesh's credentials and whose `.cotal/agents` holds its personas\n(default: the project you run it in) \u2014 the registry stores that path, never a secret. `--mode`\ndefaults to `auth` when the root holds the space's account record and to `open` otherwise; a\nuser-auth space cannot be registered by hand, because its IdP pins are trust that only\n`cotal up --user-auth` establishes. The broker is probed before anything is recorded, so a wrong\naddress, or credentials that mesh will not accept, fails here instead of at the first `spawn`;\n`--force` records without verifying (and replaces an existing record).\n\n`meshes rm` drops records \u2014 it never stops a mesh. For a mesh running on this machine `cotal down`\nis the right verb, and `rm` says so unless you pass `--force`. A record you added by hand is only\nremoved by something that names it \u2014 `meshes rm`, or an `add --force` replacement \u2014 or by a\n`cotal up` that actually starts the broker for that same space, server and root, which becomes that\nmesh and so takes the record over (a `cotal up` for that space anywhere else refuses instead).\nNothing that merely *infers* a record is stale touches it: an\nunreachable broker is listed `offline` and stays, and `cotal down` / `cotal clean all` leave it\nalone even when it shares a root with the project they are tearing down, because nothing on this\nmachine could write it back.\n\n`use <space>` sets that default; the selection applies from every directory,\nincluding inside another mesh's project. `status` is a read-only report: machine prerequisites\n(starting with the installed `cotal-ai` version), the installed extensions and their versions, this\nfolder's `.cotal/`, the recorded meshes, and a live snapshot of the selected mesh (roster, channels,\nmembership feed). `status` takes only `--space` / `--server` to pick the mesh to inspect; it starts\nnothing.\n\n## spawn\n\n```bash\ncotal spawn [<persona>] [--detach] [--name <n>] [--agent <a>] [--model <m>] [--variant <v>] [--prompt <text>] [--cwd <dir>]\ncotal spawn -f <cotal.yaml> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | resolved mesh | Target space |\n| `--server <url>` | registry entry | Broker URL override |\n| `--creds <path>` | \u2014 | Control-caller creds for an off-registry manager (`--detach` only) |\n| `--name <n>` | persona's `name:` | Presence-name override (does not choose the persona) |\n| `--config <persona-or-path>` | \u2014 | Persona catalog name or file path; wins over the positional |\n| `--agent <a>` | `COTAL_DEFAULT_AGENT`, else `claude` | Connector type (`claude`, `opencode`, `hermes`, \u2026) |\n| `--role <r>` | persona's `role:` | Role override |\n| `--model <m>` | persona's `model:` | Model override |\n| `--variant <v>` | persona's `variant:` | Model variant override (connector-defined; e.g. OpenCode reasoning tiers) |\n| `--cwd <dir>` | this cwd | Working directory to root the agent at |\n| `--prompt <text>` | \u2014 | Initial prompt auto-submitted at start |\n| `--resume <id>` | \u2014 | Fork an existing session id into the mesh (claude only) |\n| `--transcript` / `--no-transcript` | off | Mirror the session transcript to `tr-<name>` |\n| `--share-tools <sel>` | none | Share named operator MCP servers with the agent |\n| `--subscribe <a,b>` | persona's | Channel read-set override |\n| `--allow-subscribe <a,b>` | = subscribe | Read-ACL override |\n| `--allow-publish <a,b>` | deny | Post-ACL override |\n| `--detach`, `-d` | off | Launch via the manager into a detached PTY (reattach with `cotal attach`) |\n| `--file <cotal.yaml>`, `-f` | \u2014 | Deploy a manifest onto the running mesh |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing |\n| `--allow-stale <a,b>` | \u2014 | With `-f`: waive named stale agents (apply-only) |\n| `--runtime <name>` | manifest's | With `-f`: override the manifest's runtime |\n\nThe persona (`--config` > positional > `COTAL_DEFAULT_PERSONA` > `default`) is loaded from the\ntarget mesh's `.cotal/agents/`; the launch flags override the file. Foreground runs the agent\nattached to your terminal; `--detach` hands the launch to the running manager. Both modes get the\ndurable backstop on a mesh that runs the delivery daemon; `--live-only` skips it for a foreground\nspawn (messages posted while it is disconnected are then not replayed). A foreground exit retires\nthe agent's creds and broker footprint, like a manager despawn. See\n[Connect Claude Code](connect-claude.md) and [Agent files](agent-files.md); `-f` is a\n[manifest deploy](#manifest-deploys). (`cotal start` was merged into `cotal spawn --detach`.)\n\n## models\n\n```bash\ncotal models [--agent <connector>] [--refresh]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--agent <connector>` | all registered connectors | Connector whose catalog to list |\n| `--refresh` | off | Ask the connector to refresh its provider cache |\n\nAsks the running manager for each connector's model catalog (model ids plus their variants)\nfor connectors that expose one (OpenCode today; a connector without a catalog says so). Pick a\nresult with `cotal spawn --model <provider/model> --variant <v>`.\n\n## endpoints\n\n```bash\ncotal endpoints [--space <s>] [--server <url>] [--creds <path>]\n```\n\nLists the mesh presence roster: agents, the manager, and any other protocol endpoint, with each\nendpoint's role, kind, status, and current activity. Unlike `ps`, this is a read-only presence view;\nit is not limited to child processes owned by the manager.\n\n## ps, stop, attach\n\n```bash\ncotal ps [--space <s>]\ncotal stop --name <n> [--space <s>]\ncotal attach --name <n> [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--name <n>` | \u2014 | Managed agent to stop / attach (required) |\n\nThese are operator clients over the running manager's control plane. `ps` lists managed agents with\ntheir mesh status (`starting\u2026` / `working` / `waiting` / `offline`); on a user-auth mesh it also\nrenders each managed agent's last credential-refresh outcome, fail-closed. `attach` streams and\ndrives an agent's terminal on the `pty` runtime; detach with the escape key (Ctrl-] by default; see\n[`COTAL_DETACH_KEY`](config.md)). `stop` and `attach` need a running manager to talk to. On a\nstatic mesh they are cross-agent admin operations. On a user-auth mesh, your own agents (any agent\nunder your owner) need only the `spawn` scope; another owner's agent needs `admin` on your ledger\nrow ([identity & auth](identity-and-auth.md)). Launch detached agents with\n[`spawn --detach`](#spawn).\n\n`attach` streams over the manager's own HTTP/WS face rather than the mesh. That endpoint binds\n**loopback by default**, so nothing is exposed by accident; `cotal up --host <addr>` passes its bind\naddress down, which is what lets you attach to an agent whose manager runs on another machine. A\nbare `cotal supervise` and an embedded manager stay machine-local. Set it directly with\n`supervise --console-host <host>`.\n\nThat address is **recorded on the mesh** and carried forward, because it is a decision rather than\nsomething later commands can work out for themselves (a broker dial address is not a manager bind\naddress). Every later manager launch for the same mesh reuses it \u2014 a same-root `cotal up` repair,\nadopting a preserved or restored listener, a `spawn -f` manifest deploy \u2014 so a manager replacement\ndoes not quietly move a reachable attach face back to loopback. Passing `--host` again overrides it,\nso you can widen or narrow exposure whenever you like; a mesh that never asked stays loopback-only\nand records nothing.\n\nBecause that face carries terminal read and write for every managed agent, it is credentialed in two\ntiers. A mesh caller receives a **ticket** bound to the single agent the manager just authorized,\nsingle-use and short-lived, so one authorized attach can never be re-pointed at someone else's\nagent. The **console token** is the operator's own, reaches every agent, and is printed only to the\nmanager's output. The roster, the live feed, and the PTY stream all answer `401` without one; the\nstatic console shell is served openly, since it describes no agent.\n\n## personas\n\n```bash\ncotal personas list [-v] [--running]\ncotal personas show <name>\ncotal personas edit <name>\ncotal personas new <name> (--prompt <t> | --from <f>) [--role <r>] [--model <m>]\ncotal personas rm <name> --force\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh's persona catalog |\n| `--role <r>` | \u2014 | `new`: the persona's role |\n| `--model <m>` | \u2014 | `new`: the persona's model |\n| `--prompt <t>` | \u2014 | `new`: the persona's prompt text |\n| `--from <f>` | \u2014 | `new`: seed the prompt from a file |\n| `--verbose`, `-v` | off | `list`: include role / model / description |\n| `--running` | off | `list`: mark personas live on the mesh |\n| `--force` | \u2014 | `rm`: required, delete without prompting |\n\nPersonas are the local agent files under `.cotal/agents/` that `cotal spawn` launches. See\n[Agent files](agent-files.md) for the file format.\n\n## supervise\n\n```bash\ncotal supervise [--runtime <name>] [--space <s>] [--server <url>] [--spawn <names>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | this folder's auth space | Space to supervise |\n| `--server <url>` | the local mesh | Broker URL |\n| `--runtime <name>` | `pty` | Agent runtime (`pty` built in; extension runtimes are explicit-only) |\n| `--console-port <n>` | \u2014 | Protocol-console port |\n| `--console-host <host>` | loopback | Bind host for the console + attach endpoint. Loopback keeps it machine-local; `cotal up` passes the address it bound the broker to, which is what lets `cotal attach` reach this manager from another machine |\n| `--roster <file>` | \u2014 | Declarative roster to boot at startup |\n| `--launch <spec>` | \u2014 | Resolved manifest launch spec (from `up -f` / `spawn -f`) |\n| `--spawn <names>` | \u2014 | Comma-separated personas to pre-spawn at startup |\n\nThe manager is the agent supervisor and control plane: it answers `spawn --detach`, `stop`, `ps`,\n`attach`, and the `cotal_*` manager tools. `cotal up --detach` starts one for you; run `supervise`\ndirectly to recover a dead manager or drive a custom runtime. Default runtime is `pty`; install an\noptional provider first (`cotal ext add @cotal-ai/orca`, `@cotal-ai/tmux`, or `@cotal-ai/cmux`) and\nselect it explicitly. A missing provider or app fails loudly; there is no fallback. See [Deploy](deploy.md).\n\n## runtimes\n\n```bash\ncotal runtimes\n```\n\nLists every agent runtime the manager can spawn through: the built-in `pty`, the official providers\n(`orca`, `tmux`, `cmux`), and any custom provider installed via `cotal ext add`. Each installed\nprovider is probed so you can see what is actually reachable on this machine before selecting it:\n\n```\npty built in\norca installed \xB7 reachable @cotal-ai/orca\ntmux available \xB7 cotal ext add @cotal-ai/tmux\ncmux available \xB7 cotal ext add @cotal-ai/cmux\n```\n\n`installed \xB7 reachable` / `unreachable` is the provider's own `available()` probe; `available` means\nit is a known runtime you can add with the shown command. Selecting an unknown or uninstalled runtime\nvia `up`/`spawn --runtime <name>` fails loud and, for a known one, points at the exact `cotal ext add`\npackage \u2014 there is no silent fallback to `pty`.\n\n## send\n\n```bash\ncotal send dm <agent> \"<text>\" [--space <s>] [--server <url>] [--creds <path>]\ncotal send msg <channel> \"<text>\"\ncotal send ask <role> \"<text>\"\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and (off-registry) which credential |\n\nOne-shot messaging: connect, send a single direct message (`dm`), channel post (`msg`), or role\nask/anycast (`ask`), then exit. For a running conversation, agents use the mesh tools instead\n([MCP tools](mcp-tools.md)).\n\n## channels\n\n```bash\ncotal channels list\ncotal channels set <name> [--replay | --no-replay] [--window <n>] [--desc <s>] [--instructions <s>]\ncotal channels default --replay | --no-replay\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--replay` / `--no-replay` | \u2014 | `set`/`default`: replay history to new joiners, or not |\n| `--window <n>` | \u2014 | `set`: replay window size |\n| `--desc <s>` | \u2014 | `set`: one-line channel description |\n| `--instructions <s>` | \u2014 | `set`: instructions shown to joiners |\n\nInspects and edits the channel registry: replay policy, description, and joiner instructions. ACL\nsemantics (who may read or post) are set at mint / provision time, not here; see\n[Channels and permissions](channels-and-permissions.md). On a user-auth mesh, `list` rides your\nown login as is; `set` and `default` edit the registry over a short-lived\nchannel-writer view, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\n\n\n## history\n\n```bash\ncotal history clear --force [--dms] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--dms` | off | Also clear DM history |\n| `--force` | \u2014 | Required: clear without prompting |\n\nPurges retained channel history; `--dms` extends it to direct-message history. An alias of\n[`clean history`](#clean). On a user-auth mesh the purge rides a short-lived purger view over\nyour login, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\n\n## console\n\n```bash\ncotal console [--plain] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to watch |\n| `--plain` | off | Line stream instead of the TUI |\n\nA live protocol view for a space: a lazygit-style TUI, or a plain line stream on `--plain`. On a\nuser-auth mesh it rides the read-only admin view over your login, which needs ledger scope\n`admin`. See [Watch a mesh](watch-a-mesh.md).\n\n## web\n\n```bash\ncotal web [--detach] [--port <n>] [--no-open] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to serve |\n| `--port <n>` | `7799` | HTTP port |\n| `--detach` | off | Run in the background; stop with `cotal down web` or bare `cotal down` |\n| `--no-open` | off | Don't open the browser |\n\nThe browser observability dashboard: presence, channels, and a live feed. It is **not** part of\n`cotal up`: it ships inside `cotal-ai` as the `@cotal-ai/web` extension, seeded automatically on first\nrun (like the built-in connectors) so it always matches your CLI version. It self-registers `cotal web`\ninto this surface and serves\n`http://cotal.localhost:7799` (loopback; `*.localhost` resolves in Chrome/Firefox/Edge; Safari may\nneed `http://127.0.0.1:7799`). On a user-auth mesh the dashboard rides the read-only admin view\nover your login, and a channel purge asks for its own channel-purger view per click; both need\nledger scope `admin`. Detached mode re-execs the current Cotal installation, writes diagnostics to\nthe mesh root's `.cotal/web.log`, and reports success only after the HTTP server answers. It requires\na recorded mesh root, but can be launched from any directory once `cotal up` has recorded the mesh.\nSee [Watch a mesh](watch-a-mesh.md).\n\n## mint\n\n```bash\ncotal mint <name> [--profile <agent|observer|admin>] [--out <path>] [--signer]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--profile <agent\\|observer\\|admin>` | `agent` | Credential profile |\n| `--out <path>` | `.cotal/auth/creds/<name>.creds` | Output path |\n| `--signer` | off | Emit a stripped account-signing file instead |\n| `--force` | off | With `--signer`: overwrite an existing file |\n| `--allow-subscribe <a,b>` | profile default | Read-ACL override |\n| `--allow-publish <a,b>` | profile default | Post-ACL override |\n\nMints a NATS creds file for a space in **static** auth mode, scoped to a profile and (optionally)\nexplicit read/post ACLs. `--signer` emits an account-signing file for delegating minting to another\nhost. A per-user-auth space refuses `mint`: agents there join under a logged-in user\n([`login`](#login-logout) + [`actor grant`](#actor)), never via a handed-out creds file. See\n[Identity and auth](identity-and-auth.md).\n\n## login, logout\n\n```bash\ncotal login --idp <auth base URL> [--client-id <id>]\ncotal logout --idp <auth base URL>\n```\n\nSigns you in to a per-user-auth mesh's IdP (device code flow) and caches the session; run it\nonce per machine. It prints your IdP subject, the id the operator grants against. After a\nlogin, every command on that mesh works under your identity: each connect takes a fresh IdP\nproof, exchanges it locally for a short-lived bearer, and is authorized against the actor\nledger at connect time. `logout` revokes the IdP session and clears the cache. See\n[identity & auth](identity-and-auth.md).\n\n## actor\n\n```bash\ncotal actor grant <actor> --sub <IdP subject> [--scope a,b] [--allow-subscribe a,b] [--allow-publish a,b] [--role <r>] [--label <l>]\ncotal actor revoke <actor> (--sub <IdP subject> | --owner <u_\u2026>)\ncotal actor list\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | the folder's | Space whose ledger to manage |\n| `--sub <subject>` | \u2014 | The IdP subject (shown by `cotal login`) the actor belongs to |\n| `--owner <u_\u2026>` | \u2014 | The derived owner token (alternative to `--sub`) |\n| `--scope <a,b>` | `spawn,role:default` | Capability scope (`''` = none; `spawn` = may run agents, `role:<r>` = may delegate role r, `admin` = cross-agent control) |\n| `--allow-subscribe <a,b>` | `>` (all channels) | Channel read ACL; the user's envelope, their agents can never read beyond it |\n| `--allow-publish <a,b>` | `>` (all channels) | Channel post ACL; also the envelope for their agents' posting |\n| `--role <r>` | \u2014 | Role (scopes the task-queue consumer) |\n| `--label <l>` | \u2014 | Display label for `actor list` (never the IdP subject) |\n\nThe actor ledger is the single authorization source of a user-auth space: no row, no access.\nA bare `grant` is the **full** envelope (all channels, may spawn); the flags narrow it. A\nre-grant **replaces** the row, so to add a capability, re-grant with it added to the current\nscope (`cotal actor list` shows what a row holds). `revoke` denies the next exchange and the\nnext connect with no restart, and evicts the principal's live connections. Managed-agent rows\n(written by the spawn path) live in a disjoint row space this command never touches. See\n[identity & auth](identity-and-auth.md).\n\n## doctor\n\n```bash\ncotal doctor auth [--fix]\n```\n\nCredential-health diagnosis and repair for this folder's mesh: renders every managed\ncredential as healthy / near-expiry / expired and ends in `healthy` or the exact next\ncommand; `--fix` applies the repairs it can. The one surface every stale-credential error\npoints at.\n\n## join\n\n```bash\ncotal join --space <s> --name <n> [--role <r>] [--channel <c>]\ncotal join --link <url> | --token <t>\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and which credential |\n| `--name <n>` | \u2014 | Your presence name |\n| `--role <r>` | \u2014 | Your role |\n| `--channel <c>` | \u2014 | Channel to join |\n| `--kind <k>` | `agent` | Endpoint kind |\n| `--link <url>` | \u2014 | Join link (`cotal://\u2026`) |\n| `--token <t>` | \u2014 | Join token |\n| `--lifecycle-uid <uid>` | \u2014 | Required with `--creds`: the lifecycle UID minted alongside the credential (`COTAL_LIFECYCLE_UID` works too). A credential's durable grants name exact lifecycle-keyed resources, so `join` refuses to invent one |\n| `--tls` | off | Connect over TLS |\n\nAn interactive presence: join a space under your own name and role, without launching an agent\nharness. A `--link` or `--token` supplies the where and the auth in one value. See\n[Spaces](spaces.md) and [Identity and auth](identity-and-auth.md).\n\n## Manifest deploys\n\nA `cotal.yaml` manifest declares a whole mesh (channels, personas, roles, and ACLs) in one file.\nThree commands consume it, plus a read-only validator:\n\n```bash\ncotal up -f cotal.yaml # boot a fresh mesh from the manifest\ncotal spawn -f cotal.yaml # deploy the manifest additively onto a running mesh\ncotal down -f cotal.yaml # tear that deploy down (or --run <id> for one run)\ncotal topology view -f cotal.yaml # validate + view the access graph, change nothing\n```\n\n`up -f` and `spawn -f` differ in target: `up -f` brings up a new broker and applies the manifest;\n`spawn -f` requires an already-reachable mesh and applies additively (ownership-scoped). On a\nuser-auth mesh, `spawn -f` deploys over your own login (the deployer view, gated on ledger scope\n`spawn`): the manifest's agents land under your owner, a manifest claiming another owner is\nrefused, and seeding new channels additionally needs scope `admin`. Both take\n`--dry-run` to print the plan without mutating anything. `topology` validates the manifest and\nrenders its channel / role / ACL graph. See [Define a team](define-a-team.md) and the\n[manifest reference](manifest.md).\n\n## ext\n\n```bash\ncotal ext # same as `list`\ncotal ext add <npm-package>\ncotal ext remove <name>\ncotal ext list\ncotal ext root # print just the install prefix (scriptable)\ncotal ext seed [--repair|--reset|--force]\n```\n\nOperator-installed extensions: `add` installs an npm package into a cotal-owned prefix and records\nevery registry provider it contributes. Commands appear in help, completion, and dispatch; runtime\nproviders are lazy-loaded by commands such as `supervise`; local process providers participate in\n`status` and selective `down`. `remove` and `list` manage them. The `@cotal-ai/web` dashboard is the\ncanonical command/process example. Installed packages and their location are described in\n[config](config.md).\n\nBare `cotal ext` lists the inventory, headed by the install prefix. That prefix is a cotal-owned npm\nroot kept **separate** from npm's own global tree, so these packages never show up in `npm list -g` \u2014\n`cotal ext` (or the Extensions section of `cotal status`) is the canonical inventory. `cotal ext root`\nprints only the path, for scripts. The versions shown are the manifest pin recorded at add time.\n\nRemoving an extension that owns a running local process is refused with the mesh root and its\n`cotal down <component>` command; stop it first so uninstalling the package never strands a process\nwhose lifecycle provider is gone.\n\n### Built-in connectors are seeded extensions\n\nThe first-party agent connectors (`claude`, `opencode`, `codex`, `hermes`, `pi`) are not compiled into\nthe binary. They are seeded on first run through the **same** `ext add` path a third party uses, and\nappear in `cotal ext list` like any other extension. So you can remove one you do not want\n(`cotal ext remove @cotal-ai/connector-hermes`), and a deliberately-removed connector STAYS removed\nacross upgrades. `cotal ext add <your-package>` adds a third-party connector the same way. The web\ndashboard (`@cotal-ai/web`, providing `command:web`) is a fifth built-in seeded on the same path.\n\n`cotal ext seed` is the maintenance entry for that seeding (it runs automatically on the first real\ncommand of each boot, so you rarely call it):\n\n| Flag | Meaning |\n|---|---|\n| (none) | Reconcile: seed any never-seeded built-in, refresh a seeded one whose version the binary bumped, leave a removed one removed. A no-op once current. |\n| `--repair` | Recover after an interrupted seed or a lost authority (rebuilds the interrupted connector; restores the removed-vs-never-seeded record from its durable backup). |\n| `--reset` | Discard the record and re-seed all six built-ins (the five connectors plus the web dashboard). **Resurrects any you removed.** Rebuilds cleanly over corrupt seed state. |\n| `--force` | Re-seed the built-ins even when the version stamp is current or a downgrade. |\n\nThe default connector for a bare `cotal spawn` (no `--agent`) is `claude`; set `COTAL_DEFAULT_AGENT`\n(e.g. `opencode`) to change it. An `--agent` naming a removed connector fails loud with the exact\n`cotal ext add` to restore it. Set `COTAL_SKIP_CONNECTOR_SEED=1` to turn off the automatic first-run\nseed/refresh entirely (for a controlled or offline setup that manages connectors by hand); `cotal ext\nseed` still runs on request.\n\n## completion\n\n```bash\ncotal completion <bash|zsh|fish|powershell> # print a stub to eval / source\ncotal completion install [shell] # install it persistently\n```\n\nPrints or installs shell completion. Completion candidates come from each command's declared flags\nand, where useful, live mesh state (spaces, personas, managed agents) resolved offline.\n\n## feedback\n\n```bash\ncotal feedback \"<summary>\" [--type <t>] [--email <e>] [--details <text>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--type <t>` | \u2014 | `bug` \\| `idea` \\| `friction` \\| `praise` \\| `other` |\n| `--details <text>` | \u2014 | Longer free-form details |\n| `--severity <s>` | \u2014 | `low` \\| `medium` \\| `high` |\n| `--area <a>` | \u2014 | The part of Cotal this concerns |\n| `--email <e>` | git email | Contact email (required on the keyless public path) |\n| `--name <n>` | \u2014 | Your name (optional) |\n| `--url <url>` | keyed / public intake | Intake URL override |\n| `--key <k>` | `COTAL_FEEDBACK_KEY` | Feedback key |\n\nSends feedback to the Cotal developers. With a key (`--key` / `COTAL_FEEDBACK_KEY`) it routes to the\nkeyed beta intake; without one it goes to the public `cotal.ai` intake and requires a contact email\n(`--email` / `COTAL_FEEDBACK_EMAIL`, else your git email). Run a self-hosted intake with\n[`feedback-intake`](#server-daemons).\n\n## Server daemons\n\nTwo long-lived infra roles ship with the CLI. They are not part of everyday operation; the delivery\ndaemon comes up automatically with `cotal up --detach` in auth mode.\n\n```bash\ncotal deliver --space <s> [--server <url>] [--creds <file>]\ncotal auth-service --space <s> --server <url> [--port <n>]\ncotal feedback-intake --keys <keys.json> [--port <n>] [--creds <file>]\n```\n\n`auth-service` runs a user-auth space's identity plane (the NATS auth callout plus the\nloopback token exchange and JWKS); `cotal up --user-auth` starts and supervises it for you,\nso you run it directly only to recover one by hand.\n\n`deliver` runs the server-side Plane-3 delivery daemon: the durable backstop and membership/ACL\nauthority. It is auth-mode-only and single-instance (`--shard`/`--shards` accept only `N=1`);\n`--dev-mint` mints a scoped cred from the local signer for standalone dev. See the\n[delivery daemon](delivery-daemon.md). `feedback-intake` runs a self-hosted feedback server\n(requires `--keys` and a scoped `--creds`), announcing submissions into a space channel; flags\ninclude `--host`/`--port`, `--store`, `--space`/`--channel`, `--max-bytes`, and `--rate-limit`.\n\n## Plumbing\n\n`cotal __complete <words\u2026>` is the internal entry the shell-completion stubs call to emit candidates\nfor the current command line; you never run it directly. `cotal agent-bearer` is machine-facing\nplumbing on user-auth meshes: spawned agents exec it to print a fresh short-lived bearer from their\nspawn-time secret; you never run it directly either. (`cotal start` is a removed tombstone: it\nerrors and points you to `cotal spawn --detach`.)\n"
|
|
211
211
|
},
|
|
212
212
|
{
|
|
213
213
|
"slug": "config",
|
|
@@ -340,7 +340,7 @@ var DOCS_BUNDLE = {
|
|
|
340
340
|
"title": "Run a mesh",
|
|
341
341
|
"kind": "Guide (informative)",
|
|
342
342
|
"summary": "Day-to-day operation of a local mesh: what cotal up actually runs, how spawning resolves personas, harnesses, and models, how to reach a mesh from any directory, and the operator-only maintenance v\u2026",
|
|
343
|
-
"body": "# Run a mesh\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\nDay-to-day operation of a local mesh: what `cotal up` actually runs, how spawning\nresolves personas, harnesses, and models, how to reach a mesh from any directory, and the\noperator-only maintenance verbs. Every command's full flag set is in the\n[CLI reference](cli.md).\n\n## The stack\n\n`cotal up` brings up the whole local stack and bare `cotal down` stops it:\n\n- **Broker**: a local `nats-server` (logs to `.cotal/nats.log`).\n- **Delivery daemon**: the durable backstop, auth mode only\n ([what it does](delivery-daemon.md)).\n- **Manager**: a detached supervisor answering the control plane, so\n `cotal spawn --detach` and the `cotal_spawn` tool work right after `up`.\n\nThree modes:\n\n- **Default (static auth).** JWT-authed, on by default: sender authenticity and per-agent\n ACLs, enforced by the broker ([how](identity-and-auth.md)).\n- **`--user-auth --idp <url>`.** Per-user auth: people `cotal login` once, the operator\n grants their agents on the actor ledger, and every connect is authorized live against\n that grant. Starts the space's auth service alongside the broker\n ([how](identity-and-auth.md)).\n- **`--open`.** An unauthenticated, live-only dev mesh (no auth, no delivery daemon). For\n quick local experiments.\n\nAll bind **loopback** by default. `--host 0.0.0.0` widens the bind independently of the\nauth mode, so \"network-reachable\" never silently means \"unauthenticated\". With no explicit\n`--server`, `cotal up` auto-selects a free local port when the default address is already\nheld by another project; an explicit `--server` fails loud on collision.\n\n`cotal status` prints the detailed setup, process, registry, and live mesh status;\n`cotal setup` (after the first run) prints the compact card.\n\nStop one part without tearing down the mesh by naming its registered component: `cotal down\nmanager`, `cotal down delivery`, or `cotal down web`. Component names from installed extensions\njoin the same surface; `cotal down` with no names retains whole-stack behavior.\n\n## Spawning agents\n\n```bash\ncotal spawn # foreground: your default agent, in this terminal\ncotal spawn reviewer --detach # supervised: the manager runs it in a PTY\ncotal attach --name reviewer # watch/type into a detached agent (Ctrl-] detaches)\ncotal ps # what the manager is running\ncotal stop --name reviewer # stop one\n```\n\nHow a spawn resolves:\n\n- **Persona.** A bare `cotal spawn` uses `.cotal/agents/default.md`; a positional name\n picks `.cotal/agents/<name>.md`; `--config` takes an explicit ref or path. Set\n `COTAL_DEFAULT_PERSONA=<name-or-path>` to change the fallback. Fields and format:\n [agent files](agent-files.md).\n- **Harness.** Claude by default; `--agent opencode` / `--agent hermes` / `--agent pi` per\n spawn, or `COTAL_DEFAULT_AGENT` to change the default. Compared in\n [Connectors](connectors.md); per-connector guides:\n [Claude](connect-claude.md) \xB7 [OpenCode](connect-opencode.md) \xB7\n [Hermes](connect-hermes.md) \xB7 [pi](connect-pi.md).\n- **Model.** `--model` overrides the persona file's `model:` (Claude: `opus` / `sonnet` or\n a full id; OpenCode: `provider/model`). Connectors that expose a catalog report it via\n `cotal models --agent opencode`: model ids plus available variants; pick one with\n `--model provider/model --variant high`.\n- **Tools.** A spawned agent gets only the cotal tools by default; share your own MCP\n servers deliberately with `--share-tools` ([config](config.md)).\n- **Launch options.** `--opt key=value` (repeatable) passes a native harness flag straight\n through; a persona or manifest `launchOptions:` mapping does the same declaratively (a\n `--opt` wins per key). It is a **raw passthrough**, with no allow/deny list: Claude renders\n each as `--key value` (a bare `--key` for an empty value), OpenCode merges them into its\n agent config, and Hermes has no option surface so it fails loud. The trust boundary is the\n `spawn` capability itself, not the flag set, so granting `spawn` is host-launch authority\n ([security](security.md)). A key must be a plain flag name; malformed or prototype-polluting\n keys are refused.\n\nDetach from an attached PTY with **Ctrl-]** (the agent keeps running); rebind it with\n`COTAL_DETACH_KEY=ctrl-<char>` when it clashes with a keybinding inside the agent's TUI.\n\n**Runtimes.** The manager spawns into a **pty** it owns by default. Optional runtimes are installed\nthrough the extension surface, for example `cotal ext add @cotal-ai/orca`, then selected with\n`--runtime orca` (similarly `@cotal-ai/tmux` and `@cotal-ai/cmux`). They put teammates in native\nterminal surfaces rather than manager-owned PTYs. Runtime names are open-ended and resolved from\nthe registry; a missing provider or app throws, never silently falls back\n([architecture](architecture.md)).\n\n## From any directory: the mesh registry\n\n`cotal up` records each running mesh in a machine-local registry\n(`~/.cotal/meshes/space.<key>.json`, named by a case-safe hex encoding of the space: broker URL, the project root holding its creds and\npersonas, and its mode). So a bare `cotal spawn <persona>` from *any* directory joins the\nrunning mesh with the right credentials instead of mistaking the cwd for a space:\n\n- `cotal use <name>` sets the default from every directory, including inside another mesh's\n project. `--space <name>` overrides it for one command.\n- With no live selected default, a project with its own `.cotal/` resolves to that project's\n mesh; otherwise one running mesh is used automatically and several are an error.\n- `cotal meshes` lists them (a `*` marks the default); `cotal down` removes the entry.\n\nThe registry stores a *path*, never a secret; trust material stays in each project's\n`.cotal/auth`. If the mesh is down or won't take your creds, spawn fails with one\nsentence, never a raw NATS trace.\n\n## Watching\n\n`cotal console` is the terminal view (TUI on a real terminal, plain line stream when\npiped); `cotal web` is the browser dashboard. Both are read-only observers; the\nwalkthrough is [Watch a mesh](watch-a-mesh.md).\n\n## History\n\nRetained history is operator-owned. `cotal clean history --force` purges a space's\nretained channel history; `--dms` also purges DMs (`cotal history clear` is an alias).\nIt is deliberately **not** an agent tool: agents cannot wipe the record\n([identity & auth](identity-and-auth.md)). For a **stopped** mesh, `cotal clean store\n--force` deletes the on-disk JetStream store outright, and `cotal clean all --force`\nalso resets the space identity ([CLI reference](cli.md#clean)).\n\n## Offline backup\n\nFor a coherent durable cut, preserve the whole stack first, then create the artifact while it stays\ndown:\n\n```bash\ncotal down --preserve-state\ncotal backup create ./space-backup # full by default\n# later: deliberately resume the unchanged source\ncotal up --detach\n# or, from another preserved cut, restore before the normal listener opens\ncotal up --restore ./space-backup --detach\n```\n\nUse `--store-dir` on both preservation and backup for a custom JetStream store. `registry` is the\nonly partial selection (`backup create ... --only registry`; `up --restore ... --restore-only\nregistry`). Backup never stops or restarts a mesh implicitly, never opens the original store, and\ndoes not contain credentials or trust secrets. Backup/restore in every auth mode \u2014 open included \u2014\nuses isolated, operation-specific maintenance logins; normal agent credentials cannot enter that\nlistener. Full\nrestore requires the same space and exact current local trust continuity, recreates conservative\nconsumer checkpoints bound to their snapshot stream sequence state, and resumes retained agents under\ntheir original principals. The trust commitment includes the cryptographically validated full\noperator/system/data-account root chain as well as static/user authority state. A registry-only\nrestore completes canonical empty infrastructure but leaves retained agents stopped because their\nDM/DLV/TASK/ACL state is outside that selection. Authenticated restore validates the complete space\ntrust bundle before staging or changing the preserved store. Interrupted ordinary resume retries the\nsame durable attempt after its prior listener is stopped. Restore re-entry can recover a surviving normal listener\nonly when its attempt nonce, NATS server name, process owner, endpoint, and target-store identity all\nmatch the fsynced proof. A provably dead uncommitted owner is retired under lock and replaced with a\nfresh attempt-bound listener; an occupied foreign listener or ambiguous owner is never adopted. The\nmanager commit validates while retained cleanup is still suppressed; the CLI durably records its\nattempt-bound 64-hex token in `manager-committed` / `resume-committed` before `finalizeResume` can\nrelease suppression. A retry from either committed state goes straight to exact-token finalization;\nfailure preserves the committed gate and retained cleanup suppression. Missing commit evidence,\ninterrupted finalization, a live recorded endpoint despite missing pidfiles, or ambiguous proof fails closed. See the [CLI\nbackup and restore contract](cli.md#backup-and-restore) for artifact, checkpoint, fallback,\ndisaster-consent, and degraded-recovery details.\n\n## Personas from the CLI\n\n`cotal personas` manages the local catalog offline: `list` (`--running` overlays live\nmarkers), `show <name>`, `edit <name>` (re-validates on save), `new <name>`, `rm <name>\n--force`. The runtime counterpart is the `cotal_persona` tool, which goes over the wire\nwith the manager's ownership checks. Fields: [agent files](agent-files.md).\n\n## When something looks absent\n\nPermission denials are **loud, never silent**: an over-tight ACL shows up as a logged\ndenial on the endpoint, not as a peer that mysteriously looks absent. Check\n`.cotal/manager.log`, `.cotal/delivery.log`, and `.cotal/nats.log`; `cotal status` shows\nwhat is actually running. The access rules are collected in\n[Channels & permissions](channels-and-permissions.md).\n"
|
|
343
|
+
"body": "# Run a mesh\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\nDay-to-day operation of a local mesh: what `cotal up` actually runs, how spawning\nresolves personas, harnesses, and models, how to reach a mesh from any directory, and the\noperator-only maintenance verbs. Every command's full flag set is in the\n[CLI reference](cli.md).\n\n## The stack\n\n`cotal up` brings up the whole local stack and bare `cotal down` stops it:\n\n- **Broker**: a local `nats-server` (logs to `.cotal/nats.log`).\n- **Delivery daemon**: the durable backstop, auth mode only\n ([what it does](delivery-daemon.md)).\n- **Manager**: a detached supervisor answering the control plane, so\n `cotal spawn --detach` and the `cotal_spawn` tool work right after `up`.\n\nThree modes:\n\n- **Default (static auth).** JWT-authed, on by default: sender authenticity and per-agent\n ACLs, enforced by the broker ([how](identity-and-auth.md)).\n- **`--user-auth --idp <url>`.** Per-user auth: people `cotal login` once, the operator\n grants their agents on the actor ledger, and every connect is authorized live against\n that grant. Starts the space's auth service alongside the broker\n ([how](identity-and-auth.md)).\n- **`--open`.** An unauthenticated, live-only dev mesh (no auth, no delivery daemon). For\n quick local experiments.\n\nAll bind **loopback** by default. `--host 0.0.0.0` widens the bind independently of the\nauth mode, so \"network-reachable\" never silently means \"unauthenticated\". With no explicit\n`--server`, `cotal up` auto-selects a free local port when the default address is already\nheld by another project; an explicit `--server` fails loud on collision.\n\n`cotal status` prints the detailed setup, process, registry, and live mesh status;\n`cotal setup` (after the first run) prints the compact card.\n\nStop one part without tearing down the mesh by naming its registered component: `cotal down\nmanager`, `cotal down delivery`, or `cotal down web`. Component names from installed extensions\njoin the same surface; `cotal down` with no names retains whole-stack behavior.\n\n## Spawning agents\n\n```bash\ncotal spawn # foreground: your default agent, in this terminal\ncotal spawn reviewer --detach # supervised: the manager runs it in a PTY\ncotal attach --name reviewer # watch/type into a detached agent (Ctrl-] detaches)\ncotal ps # what the manager is running\ncotal stop --name reviewer # stop one\n```\n\nHow a spawn resolves:\n\n- **Persona.** A bare `cotal spawn` uses `.cotal/agents/default.md`; a positional name\n picks `.cotal/agents/<name>.md`; `--config` takes an explicit ref or path. Set\n `COTAL_DEFAULT_PERSONA=<name-or-path>` to change the fallback. Fields and format:\n [agent files](agent-files.md).\n- **Harness.** Claude by default; `--agent opencode` / `--agent hermes` / `--agent pi` per\n spawn, or `COTAL_DEFAULT_AGENT` to change the default. Compared in\n [Connectors](connectors.md); per-connector guides:\n [Claude](connect-claude.md) \xB7 [OpenCode](connect-opencode.md) \xB7\n [Hermes](connect-hermes.md) \xB7 [pi](connect-pi.md).\n- **Model.** `--model` overrides the persona file's `model:` (Claude: `opus` / `sonnet` or\n a full id; OpenCode: `provider/model`). Connectors that expose a catalog report it via\n `cotal models --agent opencode`: model ids plus available variants; pick one with\n `--model provider/model --variant high`.\n- **Tools.** A spawned agent gets only the cotal tools by default; share your own MCP\n servers deliberately with `--share-tools` ([config](config.md)).\n- **Launch options.** `--opt key=value` (repeatable) passes a native harness flag straight\n through; a persona or manifest `launchOptions:` mapping does the same declaratively (a\n `--opt` wins per key). It is a **raw passthrough**, with no allow/deny list: Claude renders\n each as `--key value` (a bare `--key` for an empty value), OpenCode merges them into its\n agent config, and Hermes has no option surface so it fails loud. The trust boundary is the\n `spawn` capability itself, not the flag set, so granting `spawn` is host-launch authority\n ([security](security.md)). A key must be a plain flag name; malformed or prototype-polluting\n keys are refused.\n\nDetach from an attached PTY with **Ctrl-]** (the agent keeps running); rebind it with\n`COTAL_DETACH_KEY=ctrl-<char>` when it clashes with a keybinding inside the agent's TUI.\n\n**Runtimes.** The manager spawns into a **pty** it owns by default. Optional runtimes are installed\nthrough the extension surface, for example `cotal ext add @cotal-ai/orca`, then selected with\n`--runtime orca` (similarly `@cotal-ai/tmux` and `@cotal-ai/cmux`). They put teammates in native\nterminal surfaces rather than manager-owned PTYs. Runtime names are open-ended and resolved from\nthe registry; a missing provider or app throws, never silently falls back\n([architecture](architecture.md)).\n\n## From any directory: the mesh registry\n\n`cotal up` records each running mesh in a machine-local registry\n(`~/.cotal/meshes/space.<key>.json`, named by a case-safe hex encoding of the space: broker URL, the project root holding its creds and\npersonas, and its mode). So a bare `cotal spawn <persona>` from *any* directory joins the\nrunning mesh with the right credentials instead of mistaking the cwd for a space:\n\n- `cotal use <name>` sets the default from every directory, including inside another mesh's\n project. `--space <name>` overrides it for one command.\n- With no live selected default, a project with its own `.cotal/` resolves to that project's\n mesh; otherwise one running mesh is used automatically and several are an error.\n- `cotal meshes` lists them (a `*` marks the default); `cotal down` removes the entry.\n\nThe registry stores a *path*, never a secret; trust material stays in each project's\n`.cotal/auth`. If the mesh is down or won't take your creds, spawn fails with one\nsentence, never a raw NATS trace.\n\n### Meshes you did not start here\n\nA mesh running on another machine has no `cotal up` on this one, so register it by hand:\n\n```bash\ncotal meshes add # guided: asks for the broker, probes it, offers what it finds\ncotal meshes add optiplex --server nats://10.0.0.5:4222 --root ~/meshes/optiplex\ncotal meshes rm optiplex\n```\n\nOn a terminal, a bare `cotal meshes add` walks you through it: it probes the broker you name and\nreports whether it is open or requires credentials, offers the spaces the folder already holds\ncredentials for, and shows the record before writing it. Scripts and agents keep the flag form -\nwithout a terminal nothing prompts.\n\n`--root` is the local folder holding that mesh's `.cotal/auth` (credentials you minted where it\nruns) and `.cotal/agents` (its personas); the mode is inferred from what that folder holds. The\nbroker is probed before the record is written, so a bad address or a credential that mesh will not\naccept fails at registration rather than at your first `spawn` (`--force` records it without verifying \u2014\nuseful when the mesh is simply down right now).\n\nRecords added this way are removed only by something that names them. A mesh this machine started\ncan be dropped on a hunch \u2014 a failed liveness probe, a `cotal down` in its project \u2014 because\n`cotal up` writes the record straight back. One you registered by hand cannot be reconstructed, so\nnothing removes it by inference: an unreachable broker is shown as `offline` in `cotal meshes`, and\n`cotal down` / `cotal clean all` leave it alone even when `--root` pointed at the project they are\ntearing down. A `cotal up` for that space refuses outright (naming `cotal meshes rm`) unless it is\nthat same endpoint: finding a broker already answering there is a refresh that starts nothing and\nleaves the record's provenance alone, while actually starting the broker for that space, server and\nroot makes this machine the one running it, so the record becomes an ordinary local one that\n`cotal down` clears. `cotal meshes rm` drops it and re-registering with `--force` replaces it. `rm`\nonly forgets a mesh \u2014 to stop one running here, use `cotal down`.\n\n## Watching\n\n`cotal console` is the terminal view (TUI on a real terminal, plain line stream when\npiped); `cotal web` is the browser dashboard. Both are read-only observers; the\nwalkthrough is [Watch a mesh](watch-a-mesh.md).\n\n## History\n\nRetained history is operator-owned. `cotal clean history --force` purges a space's\nretained channel history; `--dms` also purges DMs (`cotal history clear` is an alias).\nIt is deliberately **not** an agent tool: agents cannot wipe the record\n([identity & auth](identity-and-auth.md)). For a **stopped** mesh, `cotal clean store\n--force` deletes the on-disk JetStream store outright, and `cotal clean all --force`\nalso resets the space identity ([CLI reference](cli.md#clean)).\n\n## Offline backup\n\nFor a coherent durable cut, preserve the whole stack first, then create the artifact while it stays\ndown:\n\n```bash\ncotal down --preserve-state\ncotal backup create ./space-backup # full by default\n# later: deliberately resume the unchanged source\ncotal up --detach\n# or, from another preserved cut, restore before the normal listener opens\ncotal up --restore ./space-backup --detach\n```\n\nUse `--store-dir` on both preservation and backup for a custom JetStream store. `registry` is the\nonly partial selection (`backup create ... --only registry`; `up --restore ... --restore-only\nregistry`). Backup never stops or restarts a mesh implicitly, never opens the original store, and\ndoes not contain credentials or trust secrets. Backup/restore in every auth mode \u2014 open included \u2014\nuses isolated, operation-specific maintenance logins; normal agent credentials cannot enter that\nlistener. Full\nrestore requires the same space and exact current local trust continuity, recreates conservative\nconsumer checkpoints bound to their snapshot stream sequence state, and resumes retained agents under\ntheir original principals. The trust commitment includes the cryptographically validated full\noperator/system/data-account root chain as well as static/user authority state. A registry-only\nrestore completes canonical empty infrastructure but leaves retained agents stopped because their\nDM/DLV/TASK/ACL state is outside that selection. Authenticated restore validates the complete space\ntrust bundle before staging or changing the preserved store. Interrupted ordinary resume retries the\nsame durable attempt after its prior listener is stopped. Restore re-entry can recover a surviving normal listener\nonly when its attempt nonce, NATS server name, process owner, endpoint, and target-store identity all\nmatch the fsynced proof. A provably dead uncommitted owner is retired under lock and replaced with a\nfresh attempt-bound listener; an occupied foreign listener or ambiguous owner is never adopted. The\nmanager commit validates while retained cleanup is still suppressed; the CLI durably records its\nattempt-bound 64-hex token in `manager-committed` / `resume-committed` before `finalizeResume` can\nrelease suppression. A retry from either committed state goes straight to exact-token finalization;\nfailure preserves the committed gate and retained cleanup suppression. Missing commit evidence,\ninterrupted finalization, a live recorded endpoint despite missing pidfiles, or ambiguous proof fails closed. See the [CLI\nbackup and restore contract](cli.md#backup-and-restore) for artifact, checkpoint, fallback,\ndisaster-consent, and degraded-recovery details.\n\n## Personas from the CLI\n\n`cotal personas` manages the local catalog offline: `list` (`--running` overlays live\nmarkers), `show <name>`, `edit <name>` (re-validates on save), `new <name>`, `rm <name>\n--force`. The runtime counterpart is the `cotal_persona` tool, which goes over the wire\nwith the manager's ownership checks. Fields: [agent files](agent-files.md).\n\n## When something looks absent\n\nPermission denials are **loud, never silent**: an over-tight ACL shows up as a logged\ndenial on the endpoint, not as a peer that mysteriously looks absent. Check\n`.cotal/manager.log`, `.cotal/delivery.log`, and `.cotal/nats.log`; `cotal status` shows\nwhat is actually running. The access rules are collected in\n[Channels & permissions](channels-and-permissions.md).\n"
|
|
344
344
|
},
|
|
345
345
|
{
|
|
346
346
|
"slug": "security",
|
package/dist/plugin.bundle.js
CHANGED
|
@@ -13365,9 +13365,9 @@ var require_kv = __commonJS({
|
|
|
13365
13365
|
return Promise.reject(new Error(`kv is only supported on servers ${min} or better`));
|
|
13366
13366
|
}
|
|
13367
13367
|
if (opts.bindOnly) {
|
|
13368
|
-
return
|
|
13368
|
+
return Bucket2.bind(this.js, name, opts);
|
|
13369
13369
|
}
|
|
13370
|
-
return
|
|
13370
|
+
return Bucket2.create(this.js, name, opts);
|
|
13371
13371
|
}
|
|
13372
13372
|
/**
|
|
13373
13373
|
* Lists all available KVs
|
|
@@ -13397,7 +13397,7 @@ var require_kv = __commonJS({
|
|
|
13397
13397
|
}
|
|
13398
13398
|
};
|
|
13399
13399
|
exports.Kvm = Kvm10;
|
|
13400
|
-
var
|
|
13400
|
+
var Bucket2 = class _Bucket {
|
|
13401
13401
|
js;
|
|
13402
13402
|
jsm;
|
|
13403
13403
|
stream;
|
|
@@ -13982,7 +13982,7 @@ var require_kv = __commonJS({
|
|
|
13982
13982
|
return new KvStatusImpl(si, cluster);
|
|
13983
13983
|
}
|
|
13984
13984
|
};
|
|
13985
|
-
exports.Bucket =
|
|
13985
|
+
exports.Bucket = Bucket2;
|
|
13986
13986
|
var KvStatusImpl = class {
|
|
13987
13987
|
si;
|
|
13988
13988
|
cluster;
|
|
@@ -24716,6 +24716,67 @@ var import_kv3 = __toESM(require_mod4(), 1);
|
|
|
24716
24716
|
|
|
24717
24717
|
// ../../packages/core/dist/acls.js
|
|
24718
24718
|
var import_kv2 = __toESM(require_mod4(), 1);
|
|
24719
|
+
|
|
24720
|
+
// ../../packages/core/dist/kv-scan.js
|
|
24721
|
+
var import_internal = __toESM(require_internal_mod3(), 1);
|
|
24722
|
+
var IncompleteKvScan = class extends Error {
|
|
24723
|
+
bucket;
|
|
24724
|
+
received;
|
|
24725
|
+
expected;
|
|
24726
|
+
constructor(bucket, received, expected) {
|
|
24727
|
+
super(`reading ${bucket} ended after ${received} of ~${expected} entries without reaching the end of the bucket - the connection or consumer died mid-pass. Refusing to return a partial view (a short or empty result here is indistinguishable from a real answer). Retry; if it persists, the link to the broker is dropping.`);
|
|
24728
|
+
this.bucket = bucket;
|
|
24729
|
+
this.received = received;
|
|
24730
|
+
this.expected = expected;
|
|
24731
|
+
this.name = "IncompleteKvScan";
|
|
24732
|
+
}
|
|
24733
|
+
};
|
|
24734
|
+
async function liveKvEntries(kv, filter) {
|
|
24735
|
+
if (!(kv instanceof import_internal.Bucket))
|
|
24736
|
+
throw new Error(`liveKvEntries needs the @nats-io/kv Bucket implementation to bind its own consumer (got ${kv?.constructor?.name ?? typeof kv}). Refusing to fall back to history(), whose hidden bind-time pending count is exactly the ambiguity this exists to remove.`);
|
|
24737
|
+
const bucket = kv;
|
|
24738
|
+
const cc = bucket._buildCC(filter ?? ">", import_internal.KvWatchInclude.AllHistory, { headers_only: false });
|
|
24739
|
+
const oc = await bucket.js.consumers.getPushConsumer(bucket.stream, cc);
|
|
24740
|
+
const latest = /* @__PURE__ */ new Map();
|
|
24741
|
+
let received = 0;
|
|
24742
|
+
let sawTerminal = false;
|
|
24743
|
+
let bucketName = bucket.bucket;
|
|
24744
|
+
let expected = 0;
|
|
24745
|
+
try {
|
|
24746
|
+
expected = (await oc.info(true)).num_pending;
|
|
24747
|
+
if (expected === 0)
|
|
24748
|
+
return [];
|
|
24749
|
+
const iter = await oc.consume();
|
|
24750
|
+
try {
|
|
24751
|
+
for await (const m of iter) {
|
|
24752
|
+
const e = bucket.jmToWatchEntry(m, false);
|
|
24753
|
+
received++;
|
|
24754
|
+
bucketName = e.bucket;
|
|
24755
|
+
const prior = latest.get(e.key);
|
|
24756
|
+
if (prior === void 0 || e.revision >= prior.revision)
|
|
24757
|
+
latest.set(e.key, e);
|
|
24758
|
+
if (m.info.pending === 0) {
|
|
24759
|
+
sawTerminal = true;
|
|
24760
|
+
break;
|
|
24761
|
+
}
|
|
24762
|
+
}
|
|
24763
|
+
} finally {
|
|
24764
|
+
iter.stop();
|
|
24765
|
+
}
|
|
24766
|
+
} finally {
|
|
24767
|
+
await oc.delete().catch(() => {
|
|
24768
|
+
});
|
|
24769
|
+
}
|
|
24770
|
+
if (!sawTerminal)
|
|
24771
|
+
throw new IncompleteKvScan(bucketName, received, expected);
|
|
24772
|
+
const out = [];
|
|
24773
|
+
for (const e of latest.values())
|
|
24774
|
+
if (e.operation !== "DEL" && e.operation !== "PURGE")
|
|
24775
|
+
out.push(e);
|
|
24776
|
+
return out;
|
|
24777
|
+
}
|
|
24778
|
+
|
|
24779
|
+
// ../../packages/core/dist/acls.js
|
|
24719
24780
|
async function openAclRegistry(nc, space, opts = {}) {
|
|
24720
24781
|
const kvm = new import_kv2.Kvm(nc);
|
|
24721
24782
|
return opts.create ? kvm.create(aclBucket(space)) : kvm.open(aclBucket(space));
|
|
@@ -24757,13 +24818,19 @@ async function readAclForAlias(kv, principal) {
|
|
|
24757
24818
|
}
|
|
24758
24819
|
async function enumerateLiveAclRows(kv, principal) {
|
|
24759
24820
|
const live = [];
|
|
24760
|
-
for
|
|
24761
|
-
const parsed = parseLifecycleSubjectKey(key);
|
|
24821
|
+
for (const e of await liveKvEntries(kv, aclAliasFilter(principal))) {
|
|
24822
|
+
const parsed = parseLifecycleSubjectKey(e.key);
|
|
24762
24823
|
if (!parsed || `${parsed.owner}.${parsed.actor}` !== principal)
|
|
24763
24824
|
continue;
|
|
24764
|
-
|
|
24765
|
-
|
|
24766
|
-
|
|
24825
|
+
let record2;
|
|
24826
|
+
try {
|
|
24827
|
+
record2 = e.json();
|
|
24828
|
+
} catch {
|
|
24829
|
+
continue;
|
|
24830
|
+
}
|
|
24831
|
+
if (!Array.isArray(record2.allowSubscribe))
|
|
24832
|
+
continue;
|
|
24833
|
+
live.push({ lifecycleUid: parsed.lifecycleUid, record: record2, revision: e.revision });
|
|
24767
24834
|
}
|
|
24768
24835
|
return live;
|
|
24769
24836
|
}
|
|
@@ -25074,17 +25141,18 @@ async function activateMember(kv, channel, owner, lifecycleUid, expectedGenerati
|
|
|
25074
25141
|
}
|
|
25075
25142
|
async function listMembers(kv, filter = {}) {
|
|
25076
25143
|
const out = [];
|
|
25077
|
-
for
|
|
25078
|
-
const parsed = parseMemberKey(key);
|
|
25144
|
+
for (const e of await liveKvEntries(kv)) {
|
|
25145
|
+
const parsed = parseMemberKey(e.key);
|
|
25079
25146
|
if (!parsed)
|
|
25080
25147
|
continue;
|
|
25081
25148
|
if (filter.channel !== void 0 && parsed.channel !== filter.channel)
|
|
25082
25149
|
continue;
|
|
25083
25150
|
if (filter.owner !== void 0 && parsed.principal !== filter.owner)
|
|
25084
25151
|
continue;
|
|
25085
|
-
|
|
25086
|
-
|
|
25087
|
-
|
|
25152
|
+
try {
|
|
25153
|
+
out.push(e.json());
|
|
25154
|
+
} catch {
|
|
25155
|
+
}
|
|
25088
25156
|
}
|
|
25089
25157
|
return out;
|
|
25090
25158
|
}
|
|
@@ -26338,11 +26406,8 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
26338
26406
|
const kv = await this.membershipFeedRegistry();
|
|
26339
26407
|
const members = [];
|
|
26340
26408
|
let asOf;
|
|
26341
|
-
for
|
|
26342
|
-
|
|
26343
|
-
if (!e || e.operation === "DEL" || e.operation === "PURGE")
|
|
26344
|
-
continue;
|
|
26345
|
-
if (key === MEMBERSHIP_FEED_KEY) {
|
|
26409
|
+
for (const e of await liveKvEntries(kv)) {
|
|
26410
|
+
if (e.key === MEMBERSHIP_FEED_KEY) {
|
|
26346
26411
|
try {
|
|
26347
26412
|
asOf = e.json().observedAt;
|
|
26348
26413
|
} catch {
|
|
@@ -26351,7 +26416,7 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
26351
26416
|
}
|
|
26352
26417
|
try {
|
|
26353
26418
|
const rec = e.json();
|
|
26354
|
-
members.push({ id: key, live: rec.live ?? [], durable: rec.durable ?? [], observedAt: rec.observedAt });
|
|
26419
|
+
members.push({ id: e.key, live: rec.live ?? [], durable: rec.durable ?? [], observedAt: rec.observedAt });
|
|
26355
26420
|
} catch {
|
|
26356
26421
|
}
|
|
26357
26422
|
}
|
|
@@ -26380,30 +26445,119 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
26380
26445
|
async dmHistory(opts) {
|
|
26381
26446
|
return this.streamHistory(dmStream(this.space), `${spacePrefix(this.space)}.inst.>`, opts?.limit ?? 100);
|
|
26382
26447
|
}
|
|
26383
|
-
/**
|
|
26384
|
-
*
|
|
26385
|
-
*
|
|
26386
|
-
*
|
|
26387
|
-
|
|
26448
|
+
/**
|
|
26449
|
+
* The `limit` MOST RECENT messages matching `subject`, oldest-first within the page.
|
|
26450
|
+
*
|
|
26451
|
+
* **This used to return the OLDEST N.** `js.consumers.get(stream, {...})` builds an ORDERED
|
|
26452
|
+
* consumer, which defaults to `DeliverPolicy.StartSequence` with `opt_start_seq: 1` — the very
|
|
26453
|
+
* beginning of the stream. Capping the fetch at `limit` therefore took the first N messages ever
|
|
26454
|
+
* sent, while this method is documented as "recent" and every caller (the dashboard feed, the
|
|
26455
|
+
* agent-facing history tools) presents the result as the latest. Confirmed live against a
|
|
26456
|
+
* 123-message channel: `limit=10` returned the ten oldest, not the ten newest.
|
|
26457
|
+
*
|
|
26458
|
+
* Fixing it by draining from the start and keeping the tail would be correct and ruinous: it
|
|
26459
|
+
* transfers the entire backlog to show one screen. Instead, find the newest matching sequence and
|
|
26460
|
+
* consume a WINDOW ending there, widening geometrically until the window holds `limit` matches.
|
|
26461
|
+
* A filtered subject's sequences are non-contiguous (other channels interleave in the same
|
|
26462
|
+
* stream), so the window cannot be computed arithmetically. A FAILED attempt holds fewer than a
|
|
26463
|
+
* page by definition, so wasted transfer stays page-sized and geometric growth keeps the number of
|
|
26464
|
+
* attempts logarithmic. The one unbounded case is named in the body: a channel whose matches are
|
|
26465
|
+
* all old and sparse walks back to the start of the stream and reads its whole retained set.
|
|
26466
|
+
*
|
|
26467
|
+
* `before` pages toward the past: pass the `seq` of the oldest message you already have.
|
|
26468
|
+
*/
|
|
26469
|
+
async streamHistory(stream, subject, limit, before) {
|
|
26388
26470
|
if (!this.nc)
|
|
26389
26471
|
throw new Error("endpoint not started");
|
|
26472
|
+
if (limit <= 0)
|
|
26473
|
+
return [];
|
|
26390
26474
|
const js = (0, import_jetstream11.jetstream)(this.nc);
|
|
26391
|
-
const msgs = [];
|
|
26392
26475
|
try {
|
|
26393
|
-
const
|
|
26394
|
-
|
|
26476
|
+
const ceiling = before !== void 0 ? before - 1 : await this.lastMatchingSeq(js, stream, subject);
|
|
26477
|
+
if (ceiling < 1)
|
|
26478
|
+
return [];
|
|
26479
|
+
let span = Math.max(limit * 4, 64);
|
|
26480
|
+
for (; ; ) {
|
|
26481
|
+
const start = Math.max(1, ceiling - span + 1);
|
|
26482
|
+
const page = await this.drainWindow(js, stream, subject, start, ceiling);
|
|
26483
|
+
if (page.length >= limit || start === 1)
|
|
26484
|
+
return page.slice(-limit);
|
|
26485
|
+
span *= 4;
|
|
26486
|
+
}
|
|
26487
|
+
} catch (e) {
|
|
26488
|
+
const msg = String(e?.message ?? "");
|
|
26489
|
+
if (/stream not found/i.test(msg) || e?.code === 404)
|
|
26490
|
+
return [];
|
|
26491
|
+
if (isPermissionDenied(e) && stream === dmStream(this.space))
|
|
26492
|
+
return [];
|
|
26493
|
+
throw e;
|
|
26494
|
+
}
|
|
26495
|
+
}
|
|
26496
|
+
/** The newest stream sequence matching `subject`, or 0 when the subject has no messages.
|
|
26497
|
+
*
|
|
26498
|
+
* One ordered consumer at `DeliverPolicy.Last` with this subject's filter: its `num_pending`
|
|
26499
|
+
* (available from the create, before anything is delivered) is 0 for an empty subject, and
|
|
26500
|
+
* otherwise one message carries the sequence. Same CREATE/INFO/NEXT/DELETE surface `drainWindow`
|
|
26501
|
+
* already uses, so no broker authority is added. */
|
|
26502
|
+
async lastMatchingSeq(js, stream, subject) {
|
|
26503
|
+
const consumer = await js.consumers.get(stream, {
|
|
26504
|
+
filter_subjects: [subject],
|
|
26505
|
+
deliver_policy: import_jetstream11.DeliverPolicy.Last
|
|
26506
|
+
});
|
|
26507
|
+
try {
|
|
26508
|
+
if ((await consumer.info(true)).num_pending === 0)
|
|
26509
|
+
return 0;
|
|
26510
|
+
const iter = await consumer.fetch({ max_messages: 1 });
|
|
26511
|
+
for await (const m of iter)
|
|
26512
|
+
return m.seq;
|
|
26513
|
+
throw new Error(`history: the broker reported messages on ${subject} but delivered none - the read was cut short, not empty`);
|
|
26514
|
+
} finally {
|
|
26515
|
+
await consumer.delete().catch(() => {
|
|
26516
|
+
});
|
|
26517
|
+
}
|
|
26518
|
+
}
|
|
26519
|
+
/** Drain every message matching `subject` with sequence in `[start, ceiling]`, oldest-first.
|
|
26520
|
+
* One ephemeral ordered consumer, one batched pull — `AckPolicy.None`, so no per-message ack
|
|
26521
|
+
* round trip. Fetches exactly the pending count so it returns as soon as the window is
|
|
26522
|
+
* delivered rather than blocking for the pull's full expiry. */
|
|
26523
|
+
async drainWindow(js, stream, subject, start, ceiling) {
|
|
26524
|
+
const out = [];
|
|
26525
|
+
const consumer = await js.consumers.get(stream, { filter_subjects: [subject], opt_start_seq: start });
|
|
26526
|
+
try {
|
|
26527
|
+
const pending = (await consumer.info(true)).num_pending;
|
|
26395
26528
|
if (pending === 0)
|
|
26396
|
-
return
|
|
26529
|
+
return out;
|
|
26397
26530
|
const iter = await consumer.fetch({ max_messages: pending });
|
|
26531
|
+
let delivered = 0;
|
|
26532
|
+
let complete = false;
|
|
26398
26533
|
for await (const m of iter) {
|
|
26534
|
+
delivered++;
|
|
26535
|
+
if (m.seq >= ceiling) {
|
|
26536
|
+
if (m.seq === ceiling) {
|
|
26537
|
+
try {
|
|
26538
|
+
out.push(m.json());
|
|
26539
|
+
} catch {
|
|
26540
|
+
}
|
|
26541
|
+
}
|
|
26542
|
+
complete = true;
|
|
26543
|
+
break;
|
|
26544
|
+
}
|
|
26399
26545
|
try {
|
|
26400
|
-
|
|
26546
|
+
out.push(m.json());
|
|
26401
26547
|
} catch {
|
|
26402
26548
|
}
|
|
26549
|
+
if (delivered >= pending) {
|
|
26550
|
+
complete = true;
|
|
26551
|
+
break;
|
|
26552
|
+
}
|
|
26403
26553
|
}
|
|
26404
|
-
|
|
26554
|
+
if (!complete)
|
|
26555
|
+
throw new Error(`history: read ${delivered} of ${pending} messages on ${subject} before the stream ended early - the window was cut short, not empty`);
|
|
26556
|
+
return out;
|
|
26557
|
+
} finally {
|
|
26558
|
+
await consumer.delete().catch(() => {
|
|
26559
|
+
});
|
|
26405
26560
|
}
|
|
26406
|
-
return msgs;
|
|
26407
26561
|
}
|
|
26408
26562
|
// ---- internals -----------------------------------------------------------
|
|
26409
26563
|
/**
|
|
@@ -43558,7 +43712,7 @@ config(en_default());
|
|
|
43558
43712
|
|
|
43559
43713
|
// ../connector-core/dist/docs-bundle.generated.js
|
|
43560
43714
|
var DOCS_BUNDLE = {
|
|
43561
|
-
"version": "0.
|
|
43715
|
+
"version": "0.16.0",
|
|
43562
43716
|
"generatedFrom": "docs/*.md + SPEC.md + spec/cotal.schema.json",
|
|
43563
43717
|
"pages": [
|
|
43564
43718
|
{
|
|
@@ -43629,7 +43783,7 @@ var DOCS_BUNDLE = {
|
|
|
43629
43783
|
"title": "`cotal` CLI reference",
|
|
43630
43784
|
"kind": "Reference: describes the TypeScript reference implementation (the `cotal` CLI), not the wire contract.",
|
|
43631
43785
|
"summary": "cotal is the operator command line for the reference implementation: bring a mesh up, mint identities, launch agents, watch what they do, and tear it all down.",
|
|
43632
|
-
"body": "# `cotal` CLI reference\n\n> **Reference**: describes the TypeScript reference implementation (the `cotal` CLI), not the wire contract. \xB7 **For:** operators \xB7 **Wire contract:** [SPEC](../SPEC.md)\n\n`cotal` is the operator command line for the reference implementation: bring a mesh up, mint\nidentities, launch agents, watch what they do, and tear it all down. It is a thin client over the\nwire contract: the normative subjects and schemas live in the [SPEC](../SPEC.md); this page is\nlookup material for the commands, not a walkthrough; if you are new, start with\n[Getting started](getting-started.md).\n\n## Running it\n\n```bash\nnpm install -g cotal-ai # puts `cotal` on your PATH (needs Node 22+)\ncotal --help # every command, grouped\ncotal --version # cotal-ai version + each installed extension's (also `cotal -v`)\ncotal <command> --help # one command's flags and usage\n```\n\n`npx cotal-ai <command>` runs it without a global install; in a dev clone, `pnpm cotal <command>`\nruns it through `tsx` with no build step. Bare `cotal` prints help. Every command generates its own\n`--help`, usage, and shell completion from its declared flags.\n\nCommands come from the surfaces the binary composes: the base mesh CLI, the manager\n(`supervise`), and the delivery daemon (`deliver`), plus any operator-installed extensions.\n`cotal ext add <npm-package>` installs any registry providers a package contributes: commands,\nruntimes, and local process lifecycle descriptors. The `web` dashboard and optional manager\nruntimes ship this way.\n\n## Commands\n\n| Area | Command | Purpose |\n|---|---|---|\n| Set up & lifecycle | [`setup`](#setup) | Guided, configure-only setup (installs, seeds personas; launches nothing) |\n| Set up & lifecycle | [`update`](#update) | Reconcile first-party extensions and check or opt into a coherent CLI upgrade |\n| Set up & lifecycle | [`up`](#up) | Start a local mesh (nats-server + JetStream), or boot a whole manifest with `-f` |\n| Set up & lifecycle | [`down`](#down) | Stop the whole stack, selected registered components, or a manifest deploy |\n| Set up & lifecycle | [`backup`](#backup-and-restore) | Create an offline full-space or registry-only artifact from a preserved cut |\n| Set up & lifecycle | [`clean`](#clean) | Configurable cleanup: purge history (live), or wipe the local store / identity (stopped) |\n| Set up & lifecycle | [`meshes`](#meshes-use-status) | List the running meshes on this machine |\n| Set up & lifecycle | [`use`](#meshes-use-status) | Set the default mesh a bare `cotal spawn` joins |\n| Set up & lifecycle | [`status`](#meshes-use-status) | Read-only diagnostics for setup, processes, and the selected mesh |\n| Agents & personas | [`spawn`](#spawn) | Launch an agent from a persona (foreground, or `--detach` via the manager) |\n| Agents & personas | [`models`](#models) | List connector model catalogs and variants from the manager |\n| Agents & personas | [`ps`](#ps-stop-attach) | List managed agents and their mesh status |\n| Agents & personas | [`stop`](#ps-stop-attach) | Ask the manager to stop a managed agent |\n| Agents & personas | [`attach`](#ps-stop-attach) | Stream and drive a managed agent's terminal (pty runtime) |\n| Agents & personas | [`personas`](#personas) | List, show, edit, create, or remove local personas |\n| Agents & personas | [`supervise`](#supervise) | Run a manager daemon (the agent supervisor / control plane) |\n| Agents & personas | [`runtimes`](#runtimes) | List the agent runtimes the manager can spawn through and whether each is reachable |\n| Messaging & watching | [`endpoints`](#endpoints) | List every endpoint in the live presence roster, including infrastructure |\n| Messaging & watching | [`send`](#send) | Send one message, then exit: DM a peer, post a channel, or ask a role |\n| Messaging & watching | [`channels`](#channels) | Inspect or set the channel registry |\n| Messaging & watching | [`history`](#history) | Clear retained message history |\n| Messaging & watching | [`console`](#console) | Live protocol view for a space (TUI, or `--plain` line stream) |\n| Messaging & watching | [`web`](#web) | Browser dashboard (installed as the `@cotal-ai/web` extension) |\n| Auth & meshes | [`mint`](#mint) | Mint a creds file for a space (static auth mode) |\n| Auth & meshes | [`login`](#login-logout) | Sign in to a per-user-auth mesh's IdP (once per machine) |\n| Auth & meshes | [`logout`](#login-logout) | Revoke the IdP session and clear the cached login |\n| Auth & meshes | [`actor`](#actor) | Manage a user-auth space's actor ledger (grant / revoke / list) |\n| Auth & meshes | [`doctor`](#doctor) | Credential-health diagnosis and repair (`doctor auth`) |\n| Auth & meshes | [`join`](#join) | Join a space as your own presence (interactive) |\n| Manifest | [`topology`](#manifest-deploys) | Validate and view a mesh manifest's access graph (read-only) |\n| Extensions & misc | [`ext`](#ext) | Install / remove operator CLI extensions |\n| Extensions & misc | [`completion`](#completion) | Print or install shell completion |\n| Extensions & misc | [`feedback`](#feedback) | Send feedback to the Cotal developers |\n| Extensions & misc | [`deliver`](#server-daemons) | Run the server-side Plane-3 delivery daemon |\n| Extensions & misc | [`feedback-intake`](#server-daemons) | Run a self-hosted feedback intake server |\n\nThe manifest modes of `up`, `spawn`, and `down` (`-f <cotal.yaml>`) plus `topology` are covered\ntogether under [Manifest deploys](#manifest-deploys).\n\n## setup\n\n```bash\ncotal setup [--full] [--demo] [--yes]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--full` | off | Redo the full guided flow (implies `--demo`) |\n| `--demo` | off | Also seed the guided expert team (`david`, `sven`, `me`) |\n| `--yes`, `-y` | off | Non-interactive accept-all (for agents / CI) |\n\nGuided setup is **configure-only**: it checks prerequisites, installs the Claude Code plugin, and\nseeds persona files, and it launches nothing (no mesh, no web, no manager). First run gets the\nnarrated flow; later runs print a status card. By default it seeds one `default` persona; the\n`david`/`sven`/`me` team is opt-in via `--demo`. See [Getting started](getting-started.md) and, for\nmaintainers, [setup internals](setup-internals.md).\n\n## update\n\n```bash\ncotal update [--self]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--self` | off | If a newer release exists, install that exact validated `cotal-ai` version globally and reconcile through the newly installed binary |\n\nWithout `--self`, `update` keeps the installed first-party surfaces coherent with the running\nbinary: it force-reconciles the four built-in connectors, then reinstalls other `@cotal-ai/*`\noperator extensions at the binary's exact version. Each extension runs in an isolated child, so one\nfailure cannot poison later replays. It then checks npm; a newer binary is an informational notice\nwith `cotal update --self` as the next command, not an automatic install.\n\nWith `--self`, the npm check happens first. When a newer release exists, Cotal installs the exact\nversion it validated, resolves and verifies that package in npm's global root, then launches that\nbinary to reconcile connectors and first-party extensions to the new generation. An npx or dev-clone\ninvocation therefore installs and continues through a separate global copy; it never claims the\nalready-running process changed. If the binary is current, `--self` performs the normal local\nreconcile without reinstalling it.\n\nThird-party extensions are listed with their installed version and recorded spec but are not\nauto-updated in v1. Floating third-party updates require `@cotal-ai/*` peer-range validation and are\na future follow-up. A failed connector/extension install, npm metadata check, or requested global\ninstall is reported and makes the command exit nonzero. Independent extension attempts continue so\nthe output includes every failure; an unavailable npm registry does not undo a completed local\nreconcile, but the command still exits nonzero because it could not establish that the install is\ncurrent.\n\n## up\n\n```bash\ncotal up [--detach] [--open] [--space <s>] [--server <url>] [--channels <path>] [--runtime <name>]\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\ncotal up -f <cotal.yaml> [--dry-run] [--runtime <name>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--server <url>` | auto (free local port) | Listen URL override |\n| `--host <host>` | \u2014 | Bind host override. With no `--server`, the broker URL is derived from it, so `--host <addr>` alone is enough to make a mesh reachable at that address; a `--host`/`--server` pair naming different addresses is refused. A wildcard bind (`0.0.0.0`, `::`) keeps a dialable loopback URL. Recorded on the mesh and reused by every later manager launch, so a repair or resume keeps remote [`attach`](#ps-stop-attach) working |\n| `--space <s>` | the folder's name | Space name |\n| `--store-dir <dir>` | \u2014 | JetStream store directory |\n| `--channels <path>` | `.cotal/channels.json` if present | Channel-registry seed file (JSON). An explicit path that is missing is an error |\n| `--restore <dir>` | \u2014 | Restore a completed offline backup before exposing the normal listener |\n| `--restore-only registry` | artifact selection | Restore only the registry component |\n| `--accept-missing-source` | off | Explicit disaster consent when the inode-bound preserved source is absent |\n| `--open` | off (auth) | Unauthenticated dev mesh: no JWT, no ACLs |\n| `--user-auth` | off | Per-user auth: people `cotal login`; connects are authorized against the actor ledger |\n| `--idp <url>` | \u2014 | With `--user-auth`: the IdP auth base URL to pin on first enable |\n| `--detach` | off | Run in the background (stop with `cotal down`) |\n| `--file <cotal.yaml>`, `-f` | \u2014 | Launch a whole mesh from a manifest |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing |\n| `--runtime <name>` | `pty` (or the manifest's, with `-f`) | Agent runtime for the mesh manager (`pty` built in; others are installed extensions, explicit-only). Resolved + probed before the broker starts; an uninstalled/unreachable runtime fails loud. With `-f`, overrides the manifest's runtime |\n\n`cotal up` boots a local nats-server with JetStream and, in auth mode (the default), JWT auth and\nper-agent ACLs; `--detach` records the mesh so `cotal spawn` from any directory can find it. With no\n`--server`, it auto-selects a free port if the default address is taken; an explicit `--server`\nstays fail-loud on collision. `--detach` also brings up the control plane (delivery daemon in auth\nmode, then the manager). The `-f` form is a [manifest deploy](#manifest-deploys); see\n[Run a mesh](run-a-mesh.md).\n\n`--user-auth --idp <url>` starts the space's auth service alongside the broker (the NATS\nauth callout plus the loopback token exchange); it is torn down with `cotal down`, and a\nre-run of `cotal up` heals a dead service on a running broker. `--user-auth` and `--open`\ncontradict each other and are refused loudly; a running broker cannot change auth mode\nwithout a `cotal down` first. See [identity & auth](identity-and-auth.md).\n\n## down\n\n```bash\ncotal down\ncotal down --preserve-state [--store-dir <dir>]\ncotal down manager [delivery auth web nats ...]\ncotal down web [--space <name>]\ncotal down -f <cotal.yaml> | --run <id> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--file <cotal.yaml>`, `-f` | \u2014 | Tear down this manifest's deploy |\n| `--run <id>` | \u2014 | Tear down one `spawn -f` run by id |\n| `--space <name>` | current mesh | With components: the mesh whose target-addressed components (e.g. `web`) to stop |\n| `--dry-run` | off | Print the manifest teardown or selected components, mutate nothing |\n| `--preserve-state` | off | Bare whole stack only: fence the manager, retain principals and durable state, stop and prove the stack down, then publish `ready` |\n| `--store-dir <dir>` | `.cotal/nats` | With `--preserve-state`: the actual store path (required for a custom store) |\n\nBare `cotal down` stops the whole local stack in dependency order. Positional component names stop\nonly those self-registered local processes; for example, `cotal down manager` leaves delivery and\nthe broker running, and `cotal down web` is available when the web extension is installed. A\ncomponent that starts target-resolved (the web dashboard) is stopped the same way: `cotal down web`\nresolves the mesh exactly like `cotal web` (registry current mesh first, `--space` to name one), so\nit works from any directory; the other components always stop under the folder you run it in. The\n`-f` / `--run` forms tear down a [manifest deploy](#manifest-deploys) without stopping the whole mesh\nand cannot be combined with component names. Stopping `nats` alone is refused while an unselected\nregistered daemon is still live; include those components or use bare `cotal down`.\n\nNormal `down` remains destructive at the logical identity/durable layer. `--preserve-state` is a\ndifferent maintenance transition: it suppresses leave/deprovision cleanup, persists the manager's\nsame-principal resume inventory, stops the entire stack without removing run/auth artifacts, and\npublishes a stable inode-bound cut only after every recorded process is proven stopped and the exact\nrecorded NATS endpoint is unreachable. A missing or stale broker pidfile never counts as stopped. The\nattempt is bound durably before the manager is fenced, the resume document and attempt-bound\n`cut-intent` are fsynced before manager commit, and the manager's commitment itself is journaled\n(`cut-committed`) before any process stops. A retry after a crash at any of those boundaries reuses\nthe exact recorded attempt and finishes the remaining stop and endpoint proofs idempotently, without\nneeding the (by then intentionally dead) manager. A partial cut never publishes `ready`. It cannot\nbe combined with component names, manifest teardown, or `--dry-run`.\n\n## clean\n\n```bash\ncotal clean <history|store|all> --force\ncotal clean restore-attempt --attempt <id> --force\ncotal clean restore-fallback --attempt <id> --force\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | `history`: target mesh |\n| `--dms` | off | `history`: also clear DM history |\n| `--store-dir <dir>` | `.cotal/nats` | `store`/`all`: JetStream store directory |\n| `--force` | \u2014 | Required: destructive, no prompting |\n| `--attempt <id>` | \u2014 | `restore-attempt`: exact stale pre-commit attempt; `restore-fallback`: matching healthy committed restore |\n\nOne configurable cleanup verb; every target requires `--force`.\n\n- `history` purges the retained message backlog on the **running** broker (channels, plus DMs\n with `--dms`). The same operation as [`history clear`](#history), which stays as an alias.\n- `store` deletes the **stopped** mesh's JetStream store (`.cotal/nats`): streams, durable\n consumers, and messages. This is the reset for stale on-disk broker state, e.g. durables\n minted by an older, incompatible Cotal generation surviving a `down`/`up` cycle.\n- `all` is `store` plus the space identity (`.cotal/auth`), the local creds and markers tied to\n it, any crash residue a normal `down` would have swept (stale pidfiles, `run/`), and the mesh's\n registry entry; the next `cotal up` mints a fresh identity.\n\n`history` needs the mesh up; `store` and `all` refuse while any recorded mesh process is still\nalive or any same-root recorded broker endpoint remains reachable (run `cotal down` first). They\nalso refuse outright on a root that holds accounts for several spaces: the store and the broker\ntrust record are shared by every space on the broker, so both targets would take out all of them\nand no `--space` can narrow that. `down`, `backup` and `up --restore` refuse there for the same\nreason. `cotal status` lists the tenants on such a root. Personas\n(`.cotal/agents`) and logs are never touched. A custom\nstore location is not recorded anywhere, so `--store-dir` must repeat whatever the mesh was\nlaunched with. Custom cleanup targets must contain either the Cotal store-generation marker or a\nreal `jetstream/` store directory; filesystem roots, project roots, and Cotal auth/maintenance trees\nare always refused.\n\n`store` and `all` also refuse every maintenance journal state. After a healthy committed restore,\n`restore-fallback` is the only supported way to remove the recorded unchanged old-store inode; it\nnever deletes the active target, requires both the exact attempt id and `--force`, and retires the\ncompleted restore journal so a later `down --preserve-state` can start a new backup cycle.\n\n## backup and restore\n\n```bash\ncotal down --preserve-state [--store-dir <dir>]\ncotal backup create <dir> [--only full|registry] [--store-dir <dir>]\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\n```\n\nBackup is offline-only. It requires the stable `ready` record from `down --preserve-state`, an exact\nstore match, no live recorded process, and an unreachable exact endpoint from the recorded cut.\nThat endpoint is probed immediately before cloning, so a live broker with a missing or stale pidfile\nis still refused. It claims the cut, reflink/copies the stopped source to a\nprivate attempt clone, and opens only that clone on a random loopback bootstrap broker with an\nindependent parent/deadline watchdog. It validates the canonical stream and pull-consumer inventory,\nwrites native snapshots with consumers excluded, and stores conservative contiguous ACK-floor\ncheckpoints separately. The original store is never opened by the backup broker, and the stack is\nnot restarted implicitly. Artifact destinations must not overlap the preserved source or maintenance\nattempt tree. Restore artifacts and targets likewise cannot nest inside or contain each other, the\npreserved source, or the maintenance attempt tree.\n\n`full` is the default and indivisible: channel registry, CHAT/DM/TASK/INBOX/DLV, ACL, MEMBERS, and\nvalidated durable checkpoints. `registry` is the sole partial artifact. Presence, derived membership\nfeed, leases, native ephemeral/history consumers, credentials, keys, tokens, owner secrets, and actor\nledger files are excluded. Artifacts are exclusively created `0700`; snapshot/checkpoint files and\nthe manifest are `0600`; `manifest.json` is written last with exact sizes and SHA-256 values. The\ndirectory is trusted operator input: hashes detect corruption, not malicious rewriting.\n\nRestore validates and stages the exact allowlisted artifact bytes before moving or creating a store.\nIt requires the same space and existing trust state. The whole pre-commit window holds a journaled\nliveness claim (coordinator, watchdogs, brokers, absolute deadline): ordinary `up` and a repeated\n`up --restore` refuse while the claim is live, and a stale attempt is recovered only after the\ndeadline has elapsed and every recorded owner is proven dead \u2014 automatically by a retried\n`up --restore`, or explicitly with `cotal clean restore-attempt --attempt <id> --force`. Nothing\never rolls back a live attempt. A registry-only artifact restores as registry-only whether or not\n`--restore-only registry` is passed; omitted infrastructure is always created and the exact\npost-restore stream inventory is asserted before commit intent. Ordinary `up` from a preserved cut\nresumes only the exact recorded source store and runtime; a contradicting `--store-dir` or\n`--runtime` fails in preflight. Authenticated restores validate the complete\nspace trust bundle before staging, including nkeys, seed matches, JWTs, signers, and space binding;\nfull restores commit to the validated operator, system-account, data-account, and active-signer root\nchain in addition to the static/user authority fingerprint. The composed commitment is revalidated\nimmediately before store mutation and never includes secret seeds. Restore never creates fresh auth.\nSame-path restores atomically retain the old\nsource at the journaled fallback path; alternate targets retain it in place; a missing canonical\nsource needs explicit `--accept-missing-source`. Quarantine and target restores use current canonical\nconfigs on isolated random-loopback brokers, never expose native snapshot consumers, and publish a\ncommit-intent immediately before the normal listener starts. Archive bytes never instantiate the real\ntarget: after quarantine validation, every stream is re-snapshotted from the validated quarantine\nstate into attempt-owned sanitized files, and the target is restored solely from those. Before that boundary, failure rolls back\nthe attempt-owned target; after it, ambiguity preserves both stores and records forward-repair\nrecourse. The cooperative maintenance lock excludes Cotal commands, not arbitrary raw NATS processes.\n\nBootstrap brokers in every auth mode \u2014 including open \u2014 mount the store under a local account with\nrandom operation-specific logins only, each carrying the exact per-phase subject permission matrix;\nnormal static credentials and user-auth sentinel/bearer connections are rejected, and no auth\nservice or callout starts. Open mode differs only in its account label, never in authority. Inventory, each stream snapshot,\nrestore initiation, exact upload id, validation, and each checkpoint recreation use separate exact\nauthorities. Every checkpoint carries the source stream's message/first/last sequence state and must\nmatch its snapshot record before mutation; core then derives and validates the only allowed start\npolicy. TASK is not a CLI exception: the same core checkpoint API recreates its canonical `DeliverAll`\nWorkQueue durable because acknowledged tasks are absent from retention and NATS forbids a\nstart-sequence policy there. Registry-only restore creates every omitted canonical stream and transient\nbucket on the isolated target before the normal listener is exposed. It deliberately does not resume\nretained agents or recreate their DM/DLV/TASK/ACL state; their identity material stays retained and\nstopped rather than being reprovisioned into a partial restore.\n\nAfter listener readiness, the manager starts attempt-bound, validates retained credentials/tokens\nwithout granting or reprovisioning, and resumes the exact persisted principals under cleanup\nsuppression. Registry-only restore uses the same flow with an empty agent set. `commitResume` is an\nidempotent validation barrier only: success must be `awaitingFinalize` with an attempt-bound 64-hex\ncommit token and does not release suppression. Under the workspace lock, the CLI first fsyncs that\nexact evidence as `manager-committed` (restore) or `resume-committed` (ordinary resume), then calls\ntoken-bound `finalizeResume`; only an `active` response for the exact token releases suppression. The\nCLI records the same token in finalization evidence before a restore becomes `active`, or before an\nordinary resume retires and consumes the marker. Re-entry from either committed state skips the prior\nidempotent activation/commit phases, retries finalization with the durable token, and finishes the\nworkspace transition. Failure before finalization preserves the committed state and cleanup\nsuppression; it is not rewritten through a degraded transition. Re-entry between any two earlier\nboundaries reuses the same attempt and may retry the idempotent phases without deleting retained state. A missing or\nchanged per-agent dependency is a named fail-closed result; the journal becomes degraded and remains\navailable for forward repair. A retry from `resume-intent`,\n`resume-active`, or `resume-degraded` reuses the same attempt and inventory after the prior listener is\nproven stopped. Every normal restore listener has an unguessable attempt-bound NATS server name. The\nCLI fsyncs its exact name/nonce, canonical endpoint, process owner, and generation-bound target identity\nimmediately after spawn. Re-entry accepts a surviving listener only when its INFO server name, live PID\nrecord, endpoint, and target identity all match that proof; degraded restore repair then moves through\nthe guarded workspace transition only after manager commit. If an uncommitted bound owner is provably\ndead, recovery retires that exact proof under the maintenance lock and binds a fresh listener for the\nsame attempt, endpoint, and target with a new nonce and server name. A live foreign/mismatched listener\nor ambiguous owner is preserved and refused, never adopted by reachability alone. A reconstructed\ncommit/degraded attempt without either the exact bound proof or a durable dead-listener replacement\nrecord fails closed even when the recorded port is free. A later ordinary startup may pass an `active`\nrestore only when its details prove manager commit and its exact recorded listener is dead.\n\n## meshes, use, status\n\n```bash\ncotal meshes\ncotal use <space>\ncotal status [--space <s>] [--server <url>]\n```\n\n`meshes` lists the running meshes on this machine; a `*` marks the `current` default a bare\n`cotal spawn` joins. `use <space>` sets that default; the selection applies from every directory,\nincluding inside another mesh's project. `status` is a read-only report: machine prerequisites\n(starting with the installed `cotal-ai` version), the installed extensions and their versions, this\nfolder's `.cotal/`, the recorded meshes, and a live snapshot of the selected mesh (roster, channels,\nmembership feed). `status` takes only `--space` / `--server` to pick the mesh to inspect; it starts\nnothing.\n\n## spawn\n\n```bash\ncotal spawn [<persona>] [--detach] [--name <n>] [--agent <a>] [--model <m>] [--variant <v>] [--prompt <text>] [--cwd <dir>]\ncotal spawn -f <cotal.yaml> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | resolved mesh | Target space |\n| `--server <url>` | registry entry | Broker URL override |\n| `--creds <path>` | \u2014 | Control-caller creds for an off-registry manager (`--detach` only) |\n| `--name <n>` | persona's `name:` | Presence-name override (does not choose the persona) |\n| `--config <persona-or-path>` | \u2014 | Persona catalog name or file path; wins over the positional |\n| `--agent <a>` | `COTAL_DEFAULT_AGENT`, else `claude` | Connector type (`claude`, `opencode`, `hermes`, \u2026) |\n| `--role <r>` | persona's `role:` | Role override |\n| `--model <m>` | persona's `model:` | Model override |\n| `--variant <v>` | persona's `variant:` | Model variant override (connector-defined; e.g. OpenCode reasoning tiers) |\n| `--cwd <dir>` | this cwd | Working directory to root the agent at |\n| `--prompt <text>` | \u2014 | Initial prompt auto-submitted at start |\n| `--resume <id>` | \u2014 | Fork an existing session id into the mesh (claude only) |\n| `--transcript` / `--no-transcript` | off | Mirror the session transcript to `tr-<name>` |\n| `--share-tools <sel>` | none | Share named operator MCP servers with the agent |\n| `--subscribe <a,b>` | persona's | Channel read-set override |\n| `--allow-subscribe <a,b>` | = subscribe | Read-ACL override |\n| `--allow-publish <a,b>` | deny | Post-ACL override |\n| `--detach`, `-d` | off | Launch via the manager into a detached PTY (reattach with `cotal attach`) |\n| `--file <cotal.yaml>`, `-f` | \u2014 | Deploy a manifest onto the running mesh |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing |\n| `--allow-stale <a,b>` | \u2014 | With `-f`: waive named stale agents (apply-only) |\n| `--runtime <name>` | manifest's | With `-f`: override the manifest's runtime |\n\nThe persona (`--config` > positional > `COTAL_DEFAULT_PERSONA` > `default`) is loaded from the\ntarget mesh's `.cotal/agents/`; the launch flags override the file. Foreground runs the agent\nattached to your terminal; `--detach` hands the launch to the running manager. Both modes get the\ndurable backstop on a mesh that runs the delivery daemon; `--live-only` skips it for a foreground\nspawn (messages posted while it is disconnected are then not replayed). A foreground exit retires\nthe agent's creds and broker footprint, like a manager despawn. See\n[Connect Claude Code](connect-claude.md) and [Agent files](agent-files.md); `-f` is a\n[manifest deploy](#manifest-deploys). (`cotal start` was merged into `cotal spawn --detach`.)\n\n## models\n\n```bash\ncotal models [--agent <connector>] [--refresh]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--agent <connector>` | all registered connectors | Connector whose catalog to list |\n| `--refresh` | off | Ask the connector to refresh its provider cache |\n\nAsks the running manager for each connector's model catalog (model ids plus their variants)\nfor connectors that expose one (OpenCode today; a connector without a catalog says so). Pick a\nresult with `cotal spawn --model <provider/model> --variant <v>`.\n\n## endpoints\n\n```bash\ncotal endpoints [--space <s>] [--server <url>] [--creds <path>]\n```\n\nLists the mesh presence roster: agents, the manager, and any other protocol endpoint, with each\nendpoint's role, kind, status, and current activity. Unlike `ps`, this is a read-only presence view;\nit is not limited to child processes owned by the manager.\n\n## ps, stop, attach\n\n```bash\ncotal ps [--space <s>]\ncotal stop --name <n> [--space <s>]\ncotal attach --name <n> [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--name <n>` | \u2014 | Managed agent to stop / attach (required) |\n\nThese are operator clients over the running manager's control plane. `ps` lists managed agents with\ntheir mesh status (`starting\u2026` / `working` / `waiting` / `offline`); on a user-auth mesh it also\nrenders each managed agent's last credential-refresh outcome, fail-closed. `attach` streams and\ndrives an agent's terminal on the `pty` runtime; detach with the escape key (Ctrl-] by default; see\n[`COTAL_DETACH_KEY`](config.md)). `stop` and `attach` need a running manager to talk to. On a\nstatic mesh they are cross-agent admin operations. On a user-auth mesh, your own agents (any agent\nunder your owner) need only the `spawn` scope; another owner's agent needs `admin` on your ledger\nrow ([identity & auth](identity-and-auth.md)). Launch detached agents with\n[`spawn --detach`](#spawn).\n\n`attach` streams over the manager's own HTTP/WS face rather than the mesh. That endpoint binds\n**loopback by default**, so nothing is exposed by accident; `cotal up --host <addr>` passes its bind\naddress down, which is what lets you attach to an agent whose manager runs on another machine. A\nbare `cotal supervise` and an embedded manager stay machine-local. Set it directly with\n`supervise --console-host <host>`.\n\nThat address is **recorded on the mesh** and carried forward, because it is a decision rather than\nsomething later commands can work out for themselves (a broker dial address is not a manager bind\naddress). Every later manager launch for the same mesh reuses it \u2014 a same-root `cotal up` repair,\nadopting a preserved or restored listener, a `spawn -f` manifest deploy \u2014 so a manager replacement\ndoes not quietly move a reachable attach face back to loopback. Passing `--host` again overrides it,\nso you can widen or narrow exposure whenever you like; a mesh that never asked stays loopback-only\nand records nothing.\n\nBecause that face carries terminal read and write for every managed agent, it is credentialed in two\ntiers. A mesh caller receives a **ticket** bound to the single agent the manager just authorized,\nsingle-use and short-lived, so one authorized attach can never be re-pointed at someone else's\nagent. The **console token** is the operator's own, reaches every agent, and is printed only to the\nmanager's output. The roster, the live feed, and the PTY stream all answer `401` without one; the\nstatic console shell is served openly, since it describes no agent.\n\n## personas\n\n```bash\ncotal personas list [-v] [--running]\ncotal personas show <name>\ncotal personas edit <name>\ncotal personas new <name> (--prompt <t> | --from <f>) [--role <r>] [--model <m>]\ncotal personas rm <name> --force\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh's persona catalog |\n| `--role <r>` | \u2014 | `new`: the persona's role |\n| `--model <m>` | \u2014 | `new`: the persona's model |\n| `--prompt <t>` | \u2014 | `new`: the persona's prompt text |\n| `--from <f>` | \u2014 | `new`: seed the prompt from a file |\n| `--verbose`, `-v` | off | `list`: include role / model / description |\n| `--running` | off | `list`: mark personas live on the mesh |\n| `--force` | \u2014 | `rm`: required, delete without prompting |\n\nPersonas are the local agent files under `.cotal/agents/` that `cotal spawn` launches. See\n[Agent files](agent-files.md) for the file format.\n\n## supervise\n\n```bash\ncotal supervise [--runtime <name>] [--space <s>] [--server <url>] [--spawn <names>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | this folder's auth space | Space to supervise |\n| `--server <url>` | the local mesh | Broker URL |\n| `--runtime <name>` | `pty` | Agent runtime (`pty` built in; extension runtimes are explicit-only) |\n| `--console-port <n>` | \u2014 | Protocol-console port |\n| `--console-host <host>` | loopback | Bind host for the console + attach endpoint. Loopback keeps it machine-local; `cotal up` passes the address it bound the broker to, which is what lets `cotal attach` reach this manager from another machine |\n| `--roster <file>` | \u2014 | Declarative roster to boot at startup |\n| `--launch <spec>` | \u2014 | Resolved manifest launch spec (from `up -f` / `spawn -f`) |\n| `--spawn <names>` | \u2014 | Comma-separated personas to pre-spawn at startup |\n\nThe manager is the agent supervisor and control plane: it answers `spawn --detach`, `stop`, `ps`,\n`attach`, and the `cotal_*` manager tools. `cotal up --detach` starts one for you; run `supervise`\ndirectly to recover a dead manager or drive a custom runtime. Default runtime is `pty`; install an\noptional provider first (`cotal ext add @cotal-ai/orca`, `@cotal-ai/tmux`, or `@cotal-ai/cmux`) and\nselect it explicitly. A missing provider or app fails loudly; there is no fallback. See [Deploy](deploy.md).\n\n## runtimes\n\n```bash\ncotal runtimes\n```\n\nLists every agent runtime the manager can spawn through: the built-in `pty`, the official providers\n(`orca`, `tmux`, `cmux`), and any custom provider installed via `cotal ext add`. Each installed\nprovider is probed so you can see what is actually reachable on this machine before selecting it:\n\n```\npty built in\norca installed \xB7 reachable @cotal-ai/orca\ntmux available \xB7 cotal ext add @cotal-ai/tmux\ncmux available \xB7 cotal ext add @cotal-ai/cmux\n```\n\n`installed \xB7 reachable` / `unreachable` is the provider's own `available()` probe; `available` means\nit is a known runtime you can add with the shown command. Selecting an unknown or uninstalled runtime\nvia `up`/`spawn --runtime <name>` fails loud and, for a known one, points at the exact `cotal ext add`\npackage \u2014 there is no silent fallback to `pty`.\n\n## send\n\n```bash\ncotal send dm <agent> \"<text>\" [--space <s>] [--server <url>] [--creds <path>]\ncotal send msg <channel> \"<text>\"\ncotal send ask <role> \"<text>\"\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and (off-registry) which credential |\n\nOne-shot messaging: connect, send a single direct message (`dm`), channel post (`msg`), or role\nask/anycast (`ask`), then exit. For a running conversation, agents use the mesh tools instead\n([MCP tools](mcp-tools.md)).\n\n## channels\n\n```bash\ncotal channels list\ncotal channels set <name> [--replay | --no-replay] [--window <n>] [--desc <s>] [--instructions <s>]\ncotal channels default --replay | --no-replay\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--replay` / `--no-replay` | \u2014 | `set`/`default`: replay history to new joiners, or not |\n| `--window <n>` | \u2014 | `set`: replay window size |\n| `--desc <s>` | \u2014 | `set`: one-line channel description |\n| `--instructions <s>` | \u2014 | `set`: instructions shown to joiners |\n\nInspects and edits the channel registry: replay policy, description, and joiner instructions. ACL\nsemantics (who may read or post) are set at mint / provision time, not here; see\n[Channels and permissions](channels-and-permissions.md). On a user-auth mesh, `list` rides your\nown login as is; `set` and `default` edit the registry over a short-lived\nchannel-writer view, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\n\n\n## history\n\n```bash\ncotal history clear --force [--dms] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--dms` | off | Also clear DM history |\n| `--force` | \u2014 | Required: clear without prompting |\n\nPurges retained channel history; `--dms` extends it to direct-message history. An alias of\n[`clean history`](#clean). On a user-auth mesh the purge rides a short-lived purger view over\nyour login, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\n\n## console\n\n```bash\ncotal console [--plain] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to watch |\n| `--plain` | off | Line stream instead of the TUI |\n\nA live protocol view for a space: a lazygit-style TUI, or a plain line stream on `--plain`. On a\nuser-auth mesh it rides the read-only admin view over your login, which needs ledger scope\n`admin`. See [Watch a mesh](watch-a-mesh.md).\n\n## web\n\n```bash\ncotal web [--detach] [--port <n>] [--no-open] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to serve |\n| `--port <n>` | `7799` | HTTP port |\n| `--detach` | off | Run in the background; stop with `cotal down web` or bare `cotal down` |\n| `--no-open` | off | Don't open the browser |\n\nThe browser observability dashboard: presence, channels, and a live feed. It is **not** part of\n`cotal up`: it ships inside `cotal-ai` as the `@cotal-ai/web` extension, seeded automatically on first\nrun (like the built-in connectors) so it always matches your CLI version. It self-registers `cotal web`\ninto this surface and serves\n`http://cotal.localhost:7799` (loopback; `*.localhost` resolves in Chrome/Firefox/Edge; Safari may\nneed `http://127.0.0.1:7799`). On a user-auth mesh the dashboard rides the read-only admin view\nover your login, and a channel purge asks for its own channel-purger view per click; both need\nledger scope `admin`. Detached mode re-execs the current Cotal installation, writes diagnostics to\nthe mesh root's `.cotal/web.log`, and reports success only after the HTTP server answers. It requires\na recorded mesh root, but can be launched from any directory once `cotal up` has recorded the mesh.\nSee [Watch a mesh](watch-a-mesh.md).\n\n## mint\n\n```bash\ncotal mint <name> [--profile <agent|observer|admin>] [--out <path>] [--signer]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--profile <agent\\|observer\\|admin>` | `agent` | Credential profile |\n| `--out <path>` | `.cotal/auth/creds/<name>.creds` | Output path |\n| `--signer` | off | Emit a stripped account-signing file instead |\n| `--force` | off | With `--signer`: overwrite an existing file |\n| `--allow-subscribe <a,b>` | profile default | Read-ACL override |\n| `--allow-publish <a,b>` | profile default | Post-ACL override |\n\nMints a NATS creds file for a space in **static** auth mode, scoped to a profile and (optionally)\nexplicit read/post ACLs. `--signer` emits an account-signing file for delegating minting to another\nhost. A per-user-auth space refuses `mint`: agents there join under a logged-in user\n([`login`](#login-logout) + [`actor grant`](#actor)), never via a handed-out creds file. See\n[Identity and auth](identity-and-auth.md).\n\n## login, logout\n\n```bash\ncotal login --idp <auth base URL> [--client-id <id>]\ncotal logout --idp <auth base URL>\n```\n\nSigns you in to a per-user-auth mesh's IdP (device code flow) and caches the session; run it\nonce per machine. It prints your IdP subject, the id the operator grants against. After a\nlogin, every command on that mesh works under your identity: each connect takes a fresh IdP\nproof, exchanges it locally for a short-lived bearer, and is authorized against the actor\nledger at connect time. `logout` revokes the IdP session and clears the cache. See\n[identity & auth](identity-and-auth.md).\n\n## actor\n\n```bash\ncotal actor grant <actor> --sub <IdP subject> [--scope a,b] [--allow-subscribe a,b] [--allow-publish a,b] [--role <r>] [--label <l>]\ncotal actor revoke <actor> (--sub <IdP subject> | --owner <u_\u2026>)\ncotal actor list\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | the folder's | Space whose ledger to manage |\n| `--sub <subject>` | \u2014 | The IdP subject (shown by `cotal login`) the actor belongs to |\n| `--owner <u_\u2026>` | \u2014 | The derived owner token (alternative to `--sub`) |\n| `--scope <a,b>` | `spawn,role:default` | Capability scope (`''` = none; `spawn` = may run agents, `role:<r>` = may delegate role r, `admin` = cross-agent control) |\n| `--allow-subscribe <a,b>` | `>` (all channels) | Channel read ACL; the user's envelope, their agents can never read beyond it |\n| `--allow-publish <a,b>` | `>` (all channels) | Channel post ACL; also the envelope for their agents' posting |\n| `--role <r>` | \u2014 | Role (scopes the task-queue consumer) |\n| `--label <l>` | \u2014 | Display label for `actor list` (never the IdP subject) |\n\nThe actor ledger is the single authorization source of a user-auth space: no row, no access.\nA bare `grant` is the **full** envelope (all channels, may spawn); the flags narrow it. A\nre-grant **replaces** the row, so to add a capability, re-grant with it added to the current\nscope (`cotal actor list` shows what a row holds). `revoke` denies the next exchange and the\nnext connect with no restart, and evicts the principal's live connections. Managed-agent rows\n(written by the spawn path) live in a disjoint row space this command never touches. See\n[identity & auth](identity-and-auth.md).\n\n## doctor\n\n```bash\ncotal doctor auth [--fix]\n```\n\nCredential-health diagnosis and repair for this folder's mesh: renders every managed\ncredential as healthy / near-expiry / expired and ends in `healthy` or the exact next\ncommand; `--fix` applies the repairs it can. The one surface every stale-credential error\npoints at.\n\n## join\n\n```bash\ncotal join --space <s> --name <n> [--role <r>] [--channel <c>]\ncotal join --link <url> | --token <t>\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and which credential |\n| `--name <n>` | \u2014 | Your presence name |\n| `--role <r>` | \u2014 | Your role |\n| `--channel <c>` | \u2014 | Channel to join |\n| `--kind <k>` | `agent` | Endpoint kind |\n| `--link <url>` | \u2014 | Join link (`cotal://\u2026`) |\n| `--token <t>` | \u2014 | Join token |\n| `--lifecycle-uid <uid>` | \u2014 | Required with `--creds`: the lifecycle UID minted alongside the credential (`COTAL_LIFECYCLE_UID` works too). A credential's durable grants name exact lifecycle-keyed resources, so `join` refuses to invent one |\n| `--tls` | off | Connect over TLS |\n\nAn interactive presence: join a space under your own name and role, without launching an agent\nharness. A `--link` or `--token` supplies the where and the auth in one value. See\n[Spaces](spaces.md) and [Identity and auth](identity-and-auth.md).\n\n## Manifest deploys\n\nA `cotal.yaml` manifest declares a whole mesh (channels, personas, roles, and ACLs) in one file.\nThree commands consume it, plus a read-only validator:\n\n```bash\ncotal up -f cotal.yaml # boot a fresh mesh from the manifest\ncotal spawn -f cotal.yaml # deploy the manifest additively onto a running mesh\ncotal down -f cotal.yaml # tear that deploy down (or --run <id> for one run)\ncotal topology view -f cotal.yaml # validate + view the access graph, change nothing\n```\n\n`up -f` and `spawn -f` differ in target: `up -f` brings up a new broker and applies the manifest;\n`spawn -f` requires an already-reachable mesh and applies additively (ownership-scoped). On a\nuser-auth mesh, `spawn -f` deploys over your own login (the deployer view, gated on ledger scope\n`spawn`): the manifest's agents land under your owner, a manifest claiming another owner is\nrefused, and seeding new channels additionally needs scope `admin`. Both take\n`--dry-run` to print the plan without mutating anything. `topology` validates the manifest and\nrenders its channel / role / ACL graph. See [Define a team](define-a-team.md) and the\n[manifest reference](manifest.md).\n\n## ext\n\n```bash\ncotal ext # same as `list`\ncotal ext add <npm-package>\ncotal ext remove <name>\ncotal ext list\ncotal ext root # print just the install prefix (scriptable)\ncotal ext seed [--repair|--reset|--force]\n```\n\nOperator-installed extensions: `add` installs an npm package into a cotal-owned prefix and records\nevery registry provider it contributes. Commands appear in help, completion, and dispatch; runtime\nproviders are lazy-loaded by commands such as `supervise`; local process providers participate in\n`status` and selective `down`. `remove` and `list` manage them. The `@cotal-ai/web` dashboard is the\ncanonical command/process example. Installed packages and their location are described in\n[config](config.md).\n\nBare `cotal ext` lists the inventory, headed by the install prefix. That prefix is a cotal-owned npm\nroot kept **separate** from npm's own global tree, so these packages never show up in `npm list -g` \u2014\n`cotal ext` (or the Extensions section of `cotal status`) is the canonical inventory. `cotal ext root`\nprints only the path, for scripts. The versions shown are the manifest pin recorded at add time.\n\nRemoving an extension that owns a running local process is refused with the mesh root and its\n`cotal down <component>` command; stop it first so uninstalling the package never strands a process\nwhose lifecycle provider is gone.\n\n### Built-in connectors are seeded extensions\n\nThe first-party agent connectors (`claude`, `opencode`, `codex`, `hermes`, `pi`) are not compiled into\nthe binary. They are seeded on first run through the **same** `ext add` path a third party uses, and\nappear in `cotal ext list` like any other extension. So you can remove one you do not want\n(`cotal ext remove @cotal-ai/connector-hermes`), and a deliberately-removed connector STAYS removed\nacross upgrades. `cotal ext add <your-package>` adds a third-party connector the same way. The web\ndashboard (`@cotal-ai/web`, providing `command:web`) is a fifth built-in seeded on the same path.\n\n`cotal ext seed` is the maintenance entry for that seeding (it runs automatically on the first real\ncommand of each boot, so you rarely call it):\n\n| Flag | Meaning |\n|---|---|\n| (none) | Reconcile: seed any never-seeded built-in, refresh a seeded one whose version the binary bumped, leave a removed one removed. A no-op once current. |\n| `--repair` | Recover after an interrupted seed or a lost authority (rebuilds the interrupted connector; restores the removed-vs-never-seeded record from its durable backup). |\n| `--reset` | Discard the record and re-seed all six built-ins (the five connectors plus the web dashboard). **Resurrects any you removed.** Rebuilds cleanly over corrupt seed state. |\n| `--force` | Re-seed the built-ins even when the version stamp is current or a downgrade. |\n\nThe default connector for a bare `cotal spawn` (no `--agent`) is `claude`; set `COTAL_DEFAULT_AGENT`\n(e.g. `opencode`) to change it. An `--agent` naming a removed connector fails loud with the exact\n`cotal ext add` to restore it. Set `COTAL_SKIP_CONNECTOR_SEED=1` to turn off the automatic first-run\nseed/refresh entirely (for a controlled or offline setup that manages connectors by hand); `cotal ext\nseed` still runs on request.\n\n## completion\n\n```bash\ncotal completion <bash|zsh|fish|powershell> # print a stub to eval / source\ncotal completion install [shell] # install it persistently\n```\n\nPrints or installs shell completion. Completion candidates come from each command's declared flags\nand, where useful, live mesh state (spaces, personas, managed agents) resolved offline.\n\n## feedback\n\n```bash\ncotal feedback \"<summary>\" [--type <t>] [--email <e>] [--details <text>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--type <t>` | \u2014 | `bug` \\| `idea` \\| `friction` \\| `praise` \\| `other` |\n| `--details <text>` | \u2014 | Longer free-form details |\n| `--severity <s>` | \u2014 | `low` \\| `medium` \\| `high` |\n| `--area <a>` | \u2014 | The part of Cotal this concerns |\n| `--email <e>` | git email | Contact email (required on the keyless public path) |\n| `--name <n>` | \u2014 | Your name (optional) |\n| `--url <url>` | keyed / public intake | Intake URL override |\n| `--key <k>` | `COTAL_FEEDBACK_KEY` | Feedback key |\n\nSends feedback to the Cotal developers. With a key (`--key` / `COTAL_FEEDBACK_KEY`) it routes to the\nkeyed beta intake; without one it goes to the public `cotal.ai` intake and requires a contact email\n(`--email` / `COTAL_FEEDBACK_EMAIL`, else your git email). Run a self-hosted intake with\n[`feedback-intake`](#server-daemons).\n\n## Server daemons\n\nTwo long-lived infra roles ship with the CLI. They are not part of everyday operation; the delivery\ndaemon comes up automatically with `cotal up --detach` in auth mode.\n\n```bash\ncotal deliver --space <s> [--server <url>] [--creds <file>]\ncotal auth-service --space <s> --server <url> [--port <n>]\ncotal feedback-intake --keys <keys.json> [--port <n>] [--creds <file>]\n```\n\n`auth-service` runs a user-auth space's identity plane (the NATS auth callout plus the\nloopback token exchange and JWKS); `cotal up --user-auth` starts and supervises it for you,\nso you run it directly only to recover one by hand.\n\n`deliver` runs the server-side Plane-3 delivery daemon: the durable backstop and membership/ACL\nauthority. It is auth-mode-only and single-instance (`--shard`/`--shards` accept only `N=1`);\n`--dev-mint` mints a scoped cred from the local signer for standalone dev. See the\n[delivery daemon](delivery-daemon.md). `feedback-intake` runs a self-hosted feedback server\n(requires `--keys` and a scoped `--creds`), announcing submissions into a space channel; flags\ninclude `--host`/`--port`, `--store`, `--space`/`--channel`, `--max-bytes`, and `--rate-limit`.\n\n## Plumbing\n\n`cotal __complete <words\u2026>` is the internal entry the shell-completion stubs call to emit candidates\nfor the current command line; you never run it directly. `cotal agent-bearer` is machine-facing\nplumbing on user-auth meshes: spawned agents exec it to print a fresh short-lived bearer from their\nspawn-time secret; you never run it directly either. (`cotal start` is a removed tombstone: it\nerrors and points you to `cotal spawn --detach`.)\n"
|
|
43786
|
+
"body": "# `cotal` CLI reference\n\n> **Reference**: describes the TypeScript reference implementation (the `cotal` CLI), not the wire contract. \xB7 **For:** operators \xB7 **Wire contract:** [SPEC](../SPEC.md)\n\n`cotal` is the operator command line for the reference implementation: bring a mesh up, mint\nidentities, launch agents, watch what they do, and tear it all down. It is a thin client over the\nwire contract: the normative subjects and schemas live in the [SPEC](../SPEC.md); this page is\nlookup material for the commands, not a walkthrough; if you are new, start with\n[Getting started](getting-started.md).\n\n## Running it\n\n```bash\nnpm install -g cotal-ai # puts `cotal` on your PATH (needs Node 22+)\ncotal --help # every command, grouped\ncotal --version # cotal-ai version + each installed extension's (also `cotal -v`)\ncotal <command> --help # one command's flags and usage\n```\n\n`npx cotal-ai <command>` runs it without a global install; in a dev clone, `pnpm cotal <command>`\nruns it through `tsx` with no build step. Bare `cotal` prints help. Every command generates its own\n`--help`, usage, and shell completion from its declared flags.\n\nCommands come from the surfaces the binary composes: the base mesh CLI, the manager\n(`supervise`), and the delivery daemon (`deliver`), plus any operator-installed extensions.\n`cotal ext add <npm-package>` installs any registry providers a package contributes: commands,\nruntimes, and local process lifecycle descriptors. The `web` dashboard and optional manager\nruntimes ship this way.\n\n## Commands\n\n| Area | Command | Purpose |\n|---|---|---|\n| Set up & lifecycle | [`setup`](#setup) | Guided, configure-only setup (installs, seeds personas; launches nothing) |\n| Set up & lifecycle | [`update`](#update) | Reconcile first-party extensions and check or opt into a coherent CLI upgrade |\n| Set up & lifecycle | [`up`](#up) | Start a local mesh (nats-server + JetStream), or boot a whole manifest with `-f` |\n| Set up & lifecycle | [`down`](#down) | Stop the whole stack, selected registered components, or a manifest deploy |\n| Set up & lifecycle | [`backup`](#backup-and-restore) | Create an offline full-space or registry-only artifact from a preserved cut |\n| Set up & lifecycle | [`clean`](#clean) | Configurable cleanup: purge history (live), or wipe the local store / identity (stopped) |\n| Set up & lifecycle | [`meshes`](#meshes-use-status) | List the running meshes on this machine |\n| Set up & lifecycle | [`use`](#meshes-use-status) | Set the default mesh a bare `cotal spawn` joins |\n| Set up & lifecycle | [`status`](#meshes-use-status) | Read-only diagnostics for setup, processes, and the selected mesh |\n| Agents & personas | [`spawn`](#spawn) | Launch an agent from a persona (foreground, or `--detach` via the manager) |\n| Agents & personas | [`models`](#models) | List connector model catalogs and variants from the manager |\n| Agents & personas | [`ps`](#ps-stop-attach) | List managed agents and their mesh status |\n| Agents & personas | [`stop`](#ps-stop-attach) | Ask the manager to stop a managed agent |\n| Agents & personas | [`attach`](#ps-stop-attach) | Stream and drive a managed agent's terminal (pty runtime) |\n| Agents & personas | [`personas`](#personas) | List, show, edit, create, or remove local personas |\n| Agents & personas | [`supervise`](#supervise) | Run a manager daemon (the agent supervisor / control plane) |\n| Agents & personas | [`runtimes`](#runtimes) | List the agent runtimes the manager can spawn through and whether each is reachable |\n| Messaging & watching | [`endpoints`](#endpoints) | List every endpoint in the live presence roster, including infrastructure |\n| Messaging & watching | [`send`](#send) | Send one message, then exit: DM a peer, post a channel, or ask a role |\n| Messaging & watching | [`channels`](#channels) | Inspect or set the channel registry |\n| Messaging & watching | [`history`](#history) | Clear retained message history |\n| Messaging & watching | [`console`](#console) | Live protocol view for a space (TUI, or `--plain` line stream) |\n| Messaging & watching | [`web`](#web) | Browser dashboard (installed as the `@cotal-ai/web` extension) |\n| Auth & meshes | [`mint`](#mint) | Mint a creds file for a space (static auth mode) |\n| Auth & meshes | [`login`](#login-logout) | Sign in to a per-user-auth mesh's IdP (once per machine) |\n| Auth & meshes | [`logout`](#login-logout) | Revoke the IdP session and clear the cached login |\n| Auth & meshes | [`actor`](#actor) | Manage a user-auth space's actor ledger (grant / revoke / list) |\n| Auth & meshes | [`doctor`](#doctor) | Credential-health diagnosis and repair (`doctor auth`) |\n| Auth & meshes | [`join`](#join) | Join a space as your own presence (interactive) |\n| Manifest | [`topology`](#manifest-deploys) | Validate and view a mesh manifest's access graph (read-only) |\n| Extensions & misc | [`ext`](#ext) | Install / remove operator CLI extensions |\n| Extensions & misc | [`completion`](#completion) | Print or install shell completion |\n| Extensions & misc | [`feedback`](#feedback) | Send feedback to the Cotal developers |\n| Extensions & misc | [`deliver`](#server-daemons) | Run the server-side Plane-3 delivery daemon |\n| Extensions & misc | [`feedback-intake`](#server-daemons) | Run a self-hosted feedback intake server |\n\nThe manifest modes of `up`, `spawn`, and `down` (`-f <cotal.yaml>`) plus `topology` are covered\ntogether under [Manifest deploys](#manifest-deploys).\n\n## setup\n\n```bash\ncotal setup [--full] [--demo] [--yes]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--full` | off | Redo the full guided flow (implies `--demo`) |\n| `--demo` | off | Also seed the guided expert team (`david`, `sven`, `me`) |\n| `--yes`, `-y` | off | Non-interactive accept-all (for agents / CI) |\n\nGuided setup is **configure-only**: it checks prerequisites, installs the Claude Code plugin, and\nseeds persona files, and it launches nothing (no mesh, no web, no manager). First run gets the\nnarrated flow; later runs print a status card. By default it seeds one `default` persona; the\n`david`/`sven`/`me` team is opt-in via `--demo`. See [Getting started](getting-started.md) and, for\nmaintainers, [setup internals](setup-internals.md).\n\n## update\n\n```bash\ncotal update [--self]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--self` | off | If a newer release exists, install that exact validated `cotal-ai` version globally and reconcile through the newly installed binary |\n\nWithout `--self`, `update` keeps the installed first-party surfaces coherent with the running\nbinary: it force-reconciles the four built-in connectors, then reinstalls other `@cotal-ai/*`\noperator extensions at the binary's exact version. Each extension runs in an isolated child, so one\nfailure cannot poison later replays. It then checks npm; a newer binary is an informational notice\nwith `cotal update --self` as the next command, not an automatic install.\n\nWith `--self`, the npm check happens first. When a newer release exists, Cotal installs the exact\nversion it validated, resolves and verifies that package in npm's global root, then launches that\nbinary to reconcile connectors and first-party extensions to the new generation. An npx or dev-clone\ninvocation therefore installs and continues through a separate global copy; it never claims the\nalready-running process changed. If the binary is current, `--self` performs the normal local\nreconcile without reinstalling it.\n\nThird-party extensions are listed with their installed version and recorded spec but are not\nauto-updated in v1. Floating third-party updates require `@cotal-ai/*` peer-range validation and are\na future follow-up. A failed connector/extension install, npm metadata check, or requested global\ninstall is reported and makes the command exit nonzero. Independent extension attempts continue so\nthe output includes every failure; an unavailable npm registry does not undo a completed local\nreconcile, but the command still exits nonzero because it could not establish that the install is\ncurrent.\n\n## up\n\n```bash\ncotal up [--detach] [--open] [--space <s>] [--server <url>] [--channels <path>] [--runtime <name>]\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\ncotal up -f <cotal.yaml> [--dry-run] [--runtime <name>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--server <url>` | auto (free local port) | Listen URL override |\n| `--host <host>` | \u2014 | Bind host override. With no `--server`, the broker URL is derived from it, so `--host <addr>` alone is enough to make a mesh reachable at that address; a `--host`/`--server` pair naming different addresses is refused. A wildcard bind (`0.0.0.0`, `::`) keeps a dialable loopback URL. Recorded on the mesh and reused by every later manager launch, so a repair or resume keeps remote [`attach`](#ps-stop-attach) working |\n| `--space <s>` | the folder's name | Space name |\n| `--store-dir <dir>` | \u2014 | JetStream store directory |\n| `--channels <path>` | `.cotal/channels.json` if present | Channel-registry seed file (JSON). An explicit path that is missing is an error |\n| `--restore <dir>` | \u2014 | Restore a completed offline backup before exposing the normal listener |\n| `--restore-only registry` | artifact selection | Restore only the registry component |\n| `--accept-missing-source` | off | Explicit disaster consent when the inode-bound preserved source is absent |\n| `--open` | off (auth) | Unauthenticated dev mesh: no JWT, no ACLs |\n| `--user-auth` | off | Per-user auth: people `cotal login`; connects are authorized against the actor ledger |\n| `--idp <url>` | \u2014 | With `--user-auth`: the IdP auth base URL to pin on first enable |\n| `--detach` | off | Run in the background (stop with `cotal down`) |\n| `--file <cotal.yaml>`, `-f` | \u2014 | Launch a whole mesh from a manifest |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing |\n| `--runtime <name>` | `pty` (or the manifest's, with `-f`) | Agent runtime for the mesh manager (`pty` built in; others are installed extensions, explicit-only). Resolved + probed before the broker starts; an uninstalled/unreachable runtime fails loud. With `-f`, overrides the manifest's runtime |\n\n`cotal up` boots a local nats-server with JetStream and, in auth mode (the default), JWT auth and\nper-agent ACLs; `--detach` records the mesh so `cotal spawn` from any directory can find it. With no\n`--server`, it auto-selects a free port if the default address is taken; an explicit `--server`\nstays fail-loud on collision. `--detach` also brings up the control plane (delivery daemon in auth\nmode, then the manager). The `-f` form is a [manifest deploy](#manifest-deploys); see\n[Run a mesh](run-a-mesh.md).\n\n`--user-auth --idp <url>` starts the space's auth service alongside the broker (the NATS\nauth callout plus the loopback token exchange); it is torn down with `cotal down`, and a\nre-run of `cotal up` heals a dead service on a running broker. `--user-auth` and `--open`\ncontradict each other and are refused loudly; a running broker cannot change auth mode\nwithout a `cotal down` first. See [identity & auth](identity-and-auth.md).\n\n## down\n\n```bash\ncotal down\ncotal down --preserve-state [--store-dir <dir>]\ncotal down manager [delivery auth web nats ...]\ncotal down web [--space <name>]\ncotal down -f <cotal.yaml> | --run <id> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--file <cotal.yaml>`, `-f` | \u2014 | Tear down this manifest's deploy |\n| `--run <id>` | \u2014 | Tear down one `spawn -f` run by id |\n| `--space <name>` | current mesh | With components: the mesh whose target-addressed components (e.g. `web`) to stop |\n| `--dry-run` | off | Print the manifest teardown or selected components, mutate nothing |\n| `--preserve-state` | off | Bare whole stack only: fence the manager, retain principals and durable state, stop and prove the stack down, then publish `ready` |\n| `--store-dir <dir>` | `.cotal/nats` | With `--preserve-state`: the actual store path (required for a custom store) |\n\nBare `cotal down` stops the whole local stack in dependency order. Positional component names stop\nonly those self-registered local processes; for example, `cotal down manager` leaves delivery and\nthe broker running, and `cotal down web` is available when the web extension is installed. A\ncomponent that starts target-resolved (the web dashboard) is stopped the same way: `cotal down web`\nresolves the mesh exactly like `cotal web` (registry current mesh first, `--space` to name one), so\nit works from any directory; the other components always stop under the folder you run it in. The\n`-f` / `--run` forms tear down a [manifest deploy](#manifest-deploys) without stopping the whole mesh\nand cannot be combined with component names. Stopping `nats` alone is refused while an unselected\nregistered daemon is still live; include those components or use bare `cotal down`.\n\nNormal `down` remains destructive at the logical identity/durable layer. `--preserve-state` is a\ndifferent maintenance transition: it suppresses leave/deprovision cleanup, persists the manager's\nsame-principal resume inventory, stops the entire stack without removing run/auth artifacts, and\npublishes a stable inode-bound cut only after every recorded process is proven stopped and the exact\nrecorded NATS endpoint is unreachable. A missing or stale broker pidfile never counts as stopped. The\nattempt is bound durably before the manager is fenced, the resume document and attempt-bound\n`cut-intent` are fsynced before manager commit, and the manager's commitment itself is journaled\n(`cut-committed`) before any process stops. A retry after a crash at any of those boundaries reuses\nthe exact recorded attempt and finishes the remaining stop and endpoint proofs idempotently, without\nneeding the (by then intentionally dead) manager. A partial cut never publishes `ready`. It cannot\nbe combined with component names, manifest teardown, or `--dry-run`.\n\n## clean\n\n```bash\ncotal clean <history|store|all> --force\ncotal clean restore-attempt --attempt <id> --force\ncotal clean restore-fallback --attempt <id> --force\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | `history`: target mesh |\n| `--dms` | off | `history`: also clear DM history |\n| `--store-dir <dir>` | `.cotal/nats` | `store`/`all`: JetStream store directory |\n| `--force` | \u2014 | Required: destructive, no prompting |\n| `--attempt <id>` | \u2014 | `restore-attempt`: exact stale pre-commit attempt; `restore-fallback`: matching healthy committed restore |\n\nOne configurable cleanup verb; every target requires `--force`.\n\n- `history` purges the retained message backlog on the **running** broker (channels, plus DMs\n with `--dms`). The same operation as [`history clear`](#history), which stays as an alias.\n- `store` deletes the **stopped** mesh's JetStream store (`.cotal/nats`): streams, durable\n consumers, and messages. This is the reset for stale on-disk broker state, e.g. durables\n minted by an older, incompatible Cotal generation surviving a `down`/`up` cycle.\n- `all` is `store` plus the space identity (`.cotal/auth`), the local creds and markers tied to\n it, any crash residue a normal `down` would have swept (stale pidfiles, `run/`), and the mesh's\n registry entry; the next `cotal up` mints a fresh identity.\n\n`history` needs the mesh up; `store` and `all` refuse while any recorded mesh process is still\nalive or any same-root recorded broker endpoint remains reachable (run `cotal down` first). They\nalso refuse outright on a root that holds accounts for several spaces: the store and the broker\ntrust record are shared by every space on the broker, so both targets would take out all of them\nand no `--space` can narrow that. `down`, `backup` and `up --restore` refuse there for the same\nreason. `cotal status` lists the tenants on such a root. Personas\n(`.cotal/agents`) and logs are never touched. A custom\nstore location is not recorded anywhere, so `--store-dir` must repeat whatever the mesh was\nlaunched with. Custom cleanup targets must contain either the Cotal store-generation marker or a\nreal `jetstream/` store directory; filesystem roots, project roots, and Cotal auth/maintenance trees\nare always refused.\n\n`store` and `all` also refuse every maintenance journal state. After a healthy committed restore,\n`restore-fallback` is the only supported way to remove the recorded unchanged old-store inode; it\nnever deletes the active target, requires both the exact attempt id and `--force`, and retires the\ncompleted restore journal so a later `down --preserve-state` can start a new backup cycle.\n\n## backup and restore\n\n```bash\ncotal down --preserve-state [--store-dir <dir>]\ncotal backup create <dir> [--only full|registry] [--store-dir <dir>]\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\n```\n\nBackup is offline-only. It requires the stable `ready` record from `down --preserve-state`, an exact\nstore match, no live recorded process, and an unreachable exact endpoint from the recorded cut.\nThat endpoint is probed immediately before cloning, so a live broker with a missing or stale pidfile\nis still refused. It claims the cut, reflink/copies the stopped source to a\nprivate attempt clone, and opens only that clone on a random loopback bootstrap broker with an\nindependent parent/deadline watchdog. It validates the canonical stream and pull-consumer inventory,\nwrites native snapshots with consumers excluded, and stores conservative contiguous ACK-floor\ncheckpoints separately. The original store is never opened by the backup broker, and the stack is\nnot restarted implicitly. Artifact destinations must not overlap the preserved source or maintenance\nattempt tree. Restore artifacts and targets likewise cannot nest inside or contain each other, the\npreserved source, or the maintenance attempt tree.\n\n`full` is the default and indivisible: channel registry, CHAT/DM/TASK/INBOX/DLV, ACL, MEMBERS, and\nvalidated durable checkpoints. `registry` is the sole partial artifact. Presence, derived membership\nfeed, leases, native ephemeral/history consumers, credentials, keys, tokens, owner secrets, and actor\nledger files are excluded. Artifacts are exclusively created `0700`; snapshot/checkpoint files and\nthe manifest are `0600`; `manifest.json` is written last with exact sizes and SHA-256 values. The\ndirectory is trusted operator input: hashes detect corruption, not malicious rewriting.\n\nRestore validates and stages the exact allowlisted artifact bytes before moving or creating a store.\nIt requires the same space and existing trust state. The whole pre-commit window holds a journaled\nliveness claim (coordinator, watchdogs, brokers, absolute deadline): ordinary `up` and a repeated\n`up --restore` refuse while the claim is live, and a stale attempt is recovered only after the\ndeadline has elapsed and every recorded owner is proven dead \u2014 automatically by a retried\n`up --restore`, or explicitly with `cotal clean restore-attempt --attempt <id> --force`. Nothing\never rolls back a live attempt. A registry-only artifact restores as registry-only whether or not\n`--restore-only registry` is passed; omitted infrastructure is always created and the exact\npost-restore stream inventory is asserted before commit intent. Ordinary `up` from a preserved cut\nresumes only the exact recorded source store and runtime; a contradicting `--store-dir` or\n`--runtime` fails in preflight. Authenticated restores validate the complete\nspace trust bundle before staging, including nkeys, seed matches, JWTs, signers, and space binding;\nfull restores commit to the validated operator, system-account, data-account, and active-signer root\nchain in addition to the static/user authority fingerprint. The composed commitment is revalidated\nimmediately before store mutation and never includes secret seeds. Restore never creates fresh auth.\nSame-path restores atomically retain the old\nsource at the journaled fallback path; alternate targets retain it in place; a missing canonical\nsource needs explicit `--accept-missing-source`. Quarantine and target restores use current canonical\nconfigs on isolated random-loopback brokers, never expose native snapshot consumers, and publish a\ncommit-intent immediately before the normal listener starts. Archive bytes never instantiate the real\ntarget: after quarantine validation, every stream is re-snapshotted from the validated quarantine\nstate into attempt-owned sanitized files, and the target is restored solely from those. Before that boundary, failure rolls back\nthe attempt-owned target; after it, ambiguity preserves both stores and records forward-repair\nrecourse. The cooperative maintenance lock excludes Cotal commands, not arbitrary raw NATS processes.\n\nBootstrap brokers in every auth mode \u2014 including open \u2014 mount the store under a local account with\nrandom operation-specific logins only, each carrying the exact per-phase subject permission matrix;\nnormal static credentials and user-auth sentinel/bearer connections are rejected, and no auth\nservice or callout starts. Open mode differs only in its account label, never in authority. Inventory, each stream snapshot,\nrestore initiation, exact upload id, validation, and each checkpoint recreation use separate exact\nauthorities. Every checkpoint carries the source stream's message/first/last sequence state and must\nmatch its snapshot record before mutation; core then derives and validates the only allowed start\npolicy. TASK is not a CLI exception: the same core checkpoint API recreates its canonical `DeliverAll`\nWorkQueue durable because acknowledged tasks are absent from retention and NATS forbids a\nstart-sequence policy there. Registry-only restore creates every omitted canonical stream and transient\nbucket on the isolated target before the normal listener is exposed. It deliberately does not resume\nretained agents or recreate their DM/DLV/TASK/ACL state; their identity material stays retained and\nstopped rather than being reprovisioned into a partial restore.\n\nAfter listener readiness, the manager starts attempt-bound, validates retained credentials/tokens\nwithout granting or reprovisioning, and resumes the exact persisted principals under cleanup\nsuppression. Registry-only restore uses the same flow with an empty agent set. `commitResume` is an\nidempotent validation barrier only: success must be `awaitingFinalize` with an attempt-bound 64-hex\ncommit token and does not release suppression. Under the workspace lock, the CLI first fsyncs that\nexact evidence as `manager-committed` (restore) or `resume-committed` (ordinary resume), then calls\ntoken-bound `finalizeResume`; only an `active` response for the exact token releases suppression. The\nCLI records the same token in finalization evidence before a restore becomes `active`, or before an\nordinary resume retires and consumes the marker. Re-entry from either committed state skips the prior\nidempotent activation/commit phases, retries finalization with the durable token, and finishes the\nworkspace transition. Failure before finalization preserves the committed state and cleanup\nsuppression; it is not rewritten through a degraded transition. Re-entry between any two earlier\nboundaries reuses the same attempt and may retry the idempotent phases without deleting retained state. A missing or\nchanged per-agent dependency is a named fail-closed result; the journal becomes degraded and remains\navailable for forward repair. A retry from `resume-intent`,\n`resume-active`, or `resume-degraded` reuses the same attempt and inventory after the prior listener is\nproven stopped. Every normal restore listener has an unguessable attempt-bound NATS server name. The\nCLI fsyncs its exact name/nonce, canonical endpoint, process owner, and generation-bound target identity\nimmediately after spawn. Re-entry accepts a surviving listener only when its INFO server name, live PID\nrecord, endpoint, and target identity all match that proof; degraded restore repair then moves through\nthe guarded workspace transition only after manager commit. If an uncommitted bound owner is provably\ndead, recovery retires that exact proof under the maintenance lock and binds a fresh listener for the\nsame attempt, endpoint, and target with a new nonce and server name. A live foreign/mismatched listener\nor ambiguous owner is preserved and refused, never adopted by reachability alone. A reconstructed\ncommit/degraded attempt without either the exact bound proof or a durable dead-listener replacement\nrecord fails closed even when the recorded port is free. A later ordinary startup may pass an `active`\nrestore only when its details prove manager commit and its exact recorded listener is dead.\n\n## meshes, use, status\n\n```bash\ncotal meshes\ncotal meshes add # guided, on a terminal\ncotal meshes add <space> --server <url> [--root <dir>] [--mode auth|open] [--force]\ncotal meshes rm <space> [<space> \u2026] [--force]\ncotal use <space>\ncotal status [--space <s>] [--server <url>]\n```\n\n`meshes` lists the meshes this machine knows; a `*` marks the `current` default a bare\n`cotal spawn` joins.\n\nRun on a terminal with the space or `--server` missing, **`meshes add` is guided**: it asks for the\none thing that cannot be derived (the broker URL), probes it, and tells you what answered - open or\nrequiring credentials. It then offers the spaces your `--root` already holds credentials for, states\nthe mode as a fact about that broker rather than asking, and shows the exact record before writing\nanything. A broker that does not answer, or a space name already registered, becomes a choice rather\nthan an error. Anything you pass on the command line is taken as given and not asked again. Without\na terminal - a script, an agent, CI - nothing prompts and the flag form's errors stand\n(`COTAL_NO_PROMPT=1` forces that too).\n\n`cotal up` and `cotal down` maintain their own records. `meshes add` registers a mesh they cannot\nspeak for: one running on another machine, a shared broker, a hosted space. `--root` is the folder\nwhose `.cotal/auth` holds that mesh's credentials and whose `.cotal/agents` holds its personas\n(default: the project you run it in) \u2014 the registry stores that path, never a secret. `--mode`\ndefaults to `auth` when the root holds the space's account record and to `open` otherwise; a\nuser-auth space cannot be registered by hand, because its IdP pins are trust that only\n`cotal up --user-auth` establishes. The broker is probed before anything is recorded, so a wrong\naddress, or credentials that mesh will not accept, fails here instead of at the first `spawn`;\n`--force` records without verifying (and replaces an existing record).\n\n`meshes rm` drops records \u2014 it never stops a mesh. For a mesh running on this machine `cotal down`\nis the right verb, and `rm` says so unless you pass `--force`. A record you added by hand is only\nremoved by something that names it \u2014 `meshes rm`, or an `add --force` replacement \u2014 or by a\n`cotal up` that actually starts the broker for that same space, server and root, which becomes that\nmesh and so takes the record over (a `cotal up` for that space anywhere else refuses instead).\nNothing that merely *infers* a record is stale touches it: an\nunreachable broker is listed `offline` and stays, and `cotal down` / `cotal clean all` leave it\nalone even when it shares a root with the project they are tearing down, because nothing on this\nmachine could write it back.\n\n`use <space>` sets that default; the selection applies from every directory,\nincluding inside another mesh's project. `status` is a read-only report: machine prerequisites\n(starting with the installed `cotal-ai` version), the installed extensions and their versions, this\nfolder's `.cotal/`, the recorded meshes, and a live snapshot of the selected mesh (roster, channels,\nmembership feed). `status` takes only `--space` / `--server` to pick the mesh to inspect; it starts\nnothing.\n\n## spawn\n\n```bash\ncotal spawn [<persona>] [--detach] [--name <n>] [--agent <a>] [--model <m>] [--variant <v>] [--prompt <text>] [--cwd <dir>]\ncotal spawn -f <cotal.yaml> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | resolved mesh | Target space |\n| `--server <url>` | registry entry | Broker URL override |\n| `--creds <path>` | \u2014 | Control-caller creds for an off-registry manager (`--detach` only) |\n| `--name <n>` | persona's `name:` | Presence-name override (does not choose the persona) |\n| `--config <persona-or-path>` | \u2014 | Persona catalog name or file path; wins over the positional |\n| `--agent <a>` | `COTAL_DEFAULT_AGENT`, else `claude` | Connector type (`claude`, `opencode`, `hermes`, \u2026) |\n| `--role <r>` | persona's `role:` | Role override |\n| `--model <m>` | persona's `model:` | Model override |\n| `--variant <v>` | persona's `variant:` | Model variant override (connector-defined; e.g. OpenCode reasoning tiers) |\n| `--cwd <dir>` | this cwd | Working directory to root the agent at |\n| `--prompt <text>` | \u2014 | Initial prompt auto-submitted at start |\n| `--resume <id>` | \u2014 | Fork an existing session id into the mesh (claude only) |\n| `--transcript` / `--no-transcript` | off | Mirror the session transcript to `tr-<name>` |\n| `--share-tools <sel>` | none | Share named operator MCP servers with the agent |\n| `--subscribe <a,b>` | persona's | Channel read-set override |\n| `--allow-subscribe <a,b>` | = subscribe | Read-ACL override |\n| `--allow-publish <a,b>` | deny | Post-ACL override |\n| `--detach`, `-d` | off | Launch via the manager into a detached PTY (reattach with `cotal attach`) |\n| `--file <cotal.yaml>`, `-f` | \u2014 | Deploy a manifest onto the running mesh |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing |\n| `--allow-stale <a,b>` | \u2014 | With `-f`: waive named stale agents (apply-only) |\n| `--runtime <name>` | manifest's | With `-f`: override the manifest's runtime |\n\nThe persona (`--config` > positional > `COTAL_DEFAULT_PERSONA` > `default`) is loaded from the\ntarget mesh's `.cotal/agents/`; the launch flags override the file. Foreground runs the agent\nattached to your terminal; `--detach` hands the launch to the running manager. Both modes get the\ndurable backstop on a mesh that runs the delivery daemon; `--live-only` skips it for a foreground\nspawn (messages posted while it is disconnected are then not replayed). A foreground exit retires\nthe agent's creds and broker footprint, like a manager despawn. See\n[Connect Claude Code](connect-claude.md) and [Agent files](agent-files.md); `-f` is a\n[manifest deploy](#manifest-deploys). (`cotal start` was merged into `cotal spawn --detach`.)\n\n## models\n\n```bash\ncotal models [--agent <connector>] [--refresh]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--agent <connector>` | all registered connectors | Connector whose catalog to list |\n| `--refresh` | off | Ask the connector to refresh its provider cache |\n\nAsks the running manager for each connector's model catalog (model ids plus their variants)\nfor connectors that expose one (OpenCode today; a connector without a catalog says so). Pick a\nresult with `cotal spawn --model <provider/model> --variant <v>`.\n\n## endpoints\n\n```bash\ncotal endpoints [--space <s>] [--server <url>] [--creds <path>]\n```\n\nLists the mesh presence roster: agents, the manager, and any other protocol endpoint, with each\nendpoint's role, kind, status, and current activity. Unlike `ps`, this is a read-only presence view;\nit is not limited to child processes owned by the manager.\n\n## ps, stop, attach\n\n```bash\ncotal ps [--space <s>]\ncotal stop --name <n> [--space <s>]\ncotal attach --name <n> [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--name <n>` | \u2014 | Managed agent to stop / attach (required) |\n\nThese are operator clients over the running manager's control plane. `ps` lists managed agents with\ntheir mesh status (`starting\u2026` / `working` / `waiting` / `offline`); on a user-auth mesh it also\nrenders each managed agent's last credential-refresh outcome, fail-closed. `attach` streams and\ndrives an agent's terminal on the `pty` runtime; detach with the escape key (Ctrl-] by default; see\n[`COTAL_DETACH_KEY`](config.md)). `stop` and `attach` need a running manager to talk to. On a\nstatic mesh they are cross-agent admin operations. On a user-auth mesh, your own agents (any agent\nunder your owner) need only the `spawn` scope; another owner's agent needs `admin` on your ledger\nrow ([identity & auth](identity-and-auth.md)). Launch detached agents with\n[`spawn --detach`](#spawn).\n\n`attach` streams over the manager's own HTTP/WS face rather than the mesh. That endpoint binds\n**loopback by default**, so nothing is exposed by accident; `cotal up --host <addr>` passes its bind\naddress down, which is what lets you attach to an agent whose manager runs on another machine. A\nbare `cotal supervise` and an embedded manager stay machine-local. Set it directly with\n`supervise --console-host <host>`.\n\nThat address is **recorded on the mesh** and carried forward, because it is a decision rather than\nsomething later commands can work out for themselves (a broker dial address is not a manager bind\naddress). Every later manager launch for the same mesh reuses it \u2014 a same-root `cotal up` repair,\nadopting a preserved or restored listener, a `spawn -f` manifest deploy \u2014 so a manager replacement\ndoes not quietly move a reachable attach face back to loopback. Passing `--host` again overrides it,\nso you can widen or narrow exposure whenever you like; a mesh that never asked stays loopback-only\nand records nothing.\n\nBecause that face carries terminal read and write for every managed agent, it is credentialed in two\ntiers. A mesh caller receives a **ticket** bound to the single agent the manager just authorized,\nsingle-use and short-lived, so one authorized attach can never be re-pointed at someone else's\nagent. The **console token** is the operator's own, reaches every agent, and is printed only to the\nmanager's output. The roster, the live feed, and the PTY stream all answer `401` without one; the\nstatic console shell is served openly, since it describes no agent.\n\n## personas\n\n```bash\ncotal personas list [-v] [--running]\ncotal personas show <name>\ncotal personas edit <name>\ncotal personas new <name> (--prompt <t> | --from <f>) [--role <r>] [--model <m>]\ncotal personas rm <name> --force\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh's persona catalog |\n| `--role <r>` | \u2014 | `new`: the persona's role |\n| `--model <m>` | \u2014 | `new`: the persona's model |\n| `--prompt <t>` | \u2014 | `new`: the persona's prompt text |\n| `--from <f>` | \u2014 | `new`: seed the prompt from a file |\n| `--verbose`, `-v` | off | `list`: include role / model / description |\n| `--running` | off | `list`: mark personas live on the mesh |\n| `--force` | \u2014 | `rm`: required, delete without prompting |\n\nPersonas are the local agent files under `.cotal/agents/` that `cotal spawn` launches. See\n[Agent files](agent-files.md) for the file format.\n\n## supervise\n\n```bash\ncotal supervise [--runtime <name>] [--space <s>] [--server <url>] [--spawn <names>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | this folder's auth space | Space to supervise |\n| `--server <url>` | the local mesh | Broker URL |\n| `--runtime <name>` | `pty` | Agent runtime (`pty` built in; extension runtimes are explicit-only) |\n| `--console-port <n>` | \u2014 | Protocol-console port |\n| `--console-host <host>` | loopback | Bind host for the console + attach endpoint. Loopback keeps it machine-local; `cotal up` passes the address it bound the broker to, which is what lets `cotal attach` reach this manager from another machine |\n| `--roster <file>` | \u2014 | Declarative roster to boot at startup |\n| `--launch <spec>` | \u2014 | Resolved manifest launch spec (from `up -f` / `spawn -f`) |\n| `--spawn <names>` | \u2014 | Comma-separated personas to pre-spawn at startup |\n\nThe manager is the agent supervisor and control plane: it answers `spawn --detach`, `stop`, `ps`,\n`attach`, and the `cotal_*` manager tools. `cotal up --detach` starts one for you; run `supervise`\ndirectly to recover a dead manager or drive a custom runtime. Default runtime is `pty`; install an\noptional provider first (`cotal ext add @cotal-ai/orca`, `@cotal-ai/tmux`, or `@cotal-ai/cmux`) and\nselect it explicitly. A missing provider or app fails loudly; there is no fallback. See [Deploy](deploy.md).\n\n## runtimes\n\n```bash\ncotal runtimes\n```\n\nLists every agent runtime the manager can spawn through: the built-in `pty`, the official providers\n(`orca`, `tmux`, `cmux`), and any custom provider installed via `cotal ext add`. Each installed\nprovider is probed so you can see what is actually reachable on this machine before selecting it:\n\n```\npty built in\norca installed \xB7 reachable @cotal-ai/orca\ntmux available \xB7 cotal ext add @cotal-ai/tmux\ncmux available \xB7 cotal ext add @cotal-ai/cmux\n```\n\n`installed \xB7 reachable` / `unreachable` is the provider's own `available()` probe; `available` means\nit is a known runtime you can add with the shown command. Selecting an unknown or uninstalled runtime\nvia `up`/`spawn --runtime <name>` fails loud and, for a known one, points at the exact `cotal ext add`\npackage \u2014 there is no silent fallback to `pty`.\n\n## send\n\n```bash\ncotal send dm <agent> \"<text>\" [--space <s>] [--server <url>] [--creds <path>]\ncotal send msg <channel> \"<text>\"\ncotal send ask <role> \"<text>\"\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and (off-registry) which credential |\n\nOne-shot messaging: connect, send a single direct message (`dm`), channel post (`msg`), or role\nask/anycast (`ask`), then exit. For a running conversation, agents use the mesh tools instead\n([MCP tools](mcp-tools.md)).\n\n## channels\n\n```bash\ncotal channels list\ncotal channels set <name> [--replay | --no-replay] [--window <n>] [--desc <s>] [--instructions <s>]\ncotal channels default --replay | --no-replay\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--replay` / `--no-replay` | \u2014 | `set`/`default`: replay history to new joiners, or not |\n| `--window <n>` | \u2014 | `set`: replay window size |\n| `--desc <s>` | \u2014 | `set`: one-line channel description |\n| `--instructions <s>` | \u2014 | `set`: instructions shown to joiners |\n\nInspects and edits the channel registry: replay policy, description, and joiner instructions. ACL\nsemantics (who may read or post) are set at mint / provision time, not here; see\n[Channels and permissions](channels-and-permissions.md). On a user-auth mesh, `list` rides your\nown login as is; `set` and `default` edit the registry over a short-lived\nchannel-writer view, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\n\n\n## history\n\n```bash\ncotal history clear --force [--dms] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--dms` | off | Also clear DM history |\n| `--force` | \u2014 | Required: clear without prompting |\n\nPurges retained channel history; `--dms` extends it to direct-message history. An alias of\n[`clean history`](#clean). On a user-auth mesh the purge rides a short-lived purger view over\nyour login, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\n\n## console\n\n```bash\ncotal console [--plain] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to watch |\n| `--plain` | off | Line stream instead of the TUI |\n\nA live protocol view for a space: a lazygit-style TUI, or a plain line stream on `--plain`. On a\nuser-auth mesh it rides the read-only admin view over your login, which needs ledger scope\n`admin`. See [Watch a mesh](watch-a-mesh.md).\n\n## web\n\n```bash\ncotal web [--detach] [--port <n>] [--no-open] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to serve |\n| `--port <n>` | `7799` | HTTP port |\n| `--detach` | off | Run in the background; stop with `cotal down web` or bare `cotal down` |\n| `--no-open` | off | Don't open the browser |\n\nThe browser observability dashboard: presence, channels, and a live feed. It is **not** part of\n`cotal up`: it ships inside `cotal-ai` as the `@cotal-ai/web` extension, seeded automatically on first\nrun (like the built-in connectors) so it always matches your CLI version. It self-registers `cotal web`\ninto this surface and serves\n`http://cotal.localhost:7799` (loopback; `*.localhost` resolves in Chrome/Firefox/Edge; Safari may\nneed `http://127.0.0.1:7799`). On a user-auth mesh the dashboard rides the read-only admin view\nover your login, and a channel purge asks for its own channel-purger view per click; both need\nledger scope `admin`. Detached mode re-execs the current Cotal installation, writes diagnostics to\nthe mesh root's `.cotal/web.log`, and reports success only after the HTTP server answers. It requires\na recorded mesh root, but can be launched from any directory once `cotal up` has recorded the mesh.\nSee [Watch a mesh](watch-a-mesh.md).\n\n## mint\n\n```bash\ncotal mint <name> [--profile <agent|observer|admin>] [--out <path>] [--signer]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--profile <agent\\|observer\\|admin>` | `agent` | Credential profile |\n| `--out <path>` | `.cotal/auth/creds/<name>.creds` | Output path |\n| `--signer` | off | Emit a stripped account-signing file instead |\n| `--force` | off | With `--signer`: overwrite an existing file |\n| `--allow-subscribe <a,b>` | profile default | Read-ACL override |\n| `--allow-publish <a,b>` | profile default | Post-ACL override |\n\nMints a NATS creds file for a space in **static** auth mode, scoped to a profile and (optionally)\nexplicit read/post ACLs. `--signer` emits an account-signing file for delegating minting to another\nhost. A per-user-auth space refuses `mint`: agents there join under a logged-in user\n([`login`](#login-logout) + [`actor grant`](#actor)), never via a handed-out creds file. See\n[Identity and auth](identity-and-auth.md).\n\n## login, logout\n\n```bash\ncotal login --idp <auth base URL> [--client-id <id>]\ncotal logout --idp <auth base URL>\n```\n\nSigns you in to a per-user-auth mesh's IdP (device code flow) and caches the session; run it\nonce per machine. It prints your IdP subject, the id the operator grants against. After a\nlogin, every command on that mesh works under your identity: each connect takes a fresh IdP\nproof, exchanges it locally for a short-lived bearer, and is authorized against the actor\nledger at connect time. `logout` revokes the IdP session and clears the cache. See\n[identity & auth](identity-and-auth.md).\n\n## actor\n\n```bash\ncotal actor grant <actor> --sub <IdP subject> [--scope a,b] [--allow-subscribe a,b] [--allow-publish a,b] [--role <r>] [--label <l>]\ncotal actor revoke <actor> (--sub <IdP subject> | --owner <u_\u2026>)\ncotal actor list\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | the folder's | Space whose ledger to manage |\n| `--sub <subject>` | \u2014 | The IdP subject (shown by `cotal login`) the actor belongs to |\n| `--owner <u_\u2026>` | \u2014 | The derived owner token (alternative to `--sub`) |\n| `--scope <a,b>` | `spawn,role:default` | Capability scope (`''` = none; `spawn` = may run agents, `role:<r>` = may delegate role r, `admin` = cross-agent control) |\n| `--allow-subscribe <a,b>` | `>` (all channels) | Channel read ACL; the user's envelope, their agents can never read beyond it |\n| `--allow-publish <a,b>` | `>` (all channels) | Channel post ACL; also the envelope for their agents' posting |\n| `--role <r>` | \u2014 | Role (scopes the task-queue consumer) |\n| `--label <l>` | \u2014 | Display label for `actor list` (never the IdP subject) |\n\nThe actor ledger is the single authorization source of a user-auth space: no row, no access.\nA bare `grant` is the **full** envelope (all channels, may spawn); the flags narrow it. A\nre-grant **replaces** the row, so to add a capability, re-grant with it added to the current\nscope (`cotal actor list` shows what a row holds). `revoke` denies the next exchange and the\nnext connect with no restart, and evicts the principal's live connections. Managed-agent rows\n(written by the spawn path) live in a disjoint row space this command never touches. See\n[identity & auth](identity-and-auth.md).\n\n## doctor\n\n```bash\ncotal doctor auth [--fix]\n```\n\nCredential-health diagnosis and repair for this folder's mesh: renders every managed\ncredential as healthy / near-expiry / expired and ends in `healthy` or the exact next\ncommand; `--fix` applies the repairs it can. The one surface every stale-credential error\npoints at.\n\n## join\n\n```bash\ncotal join --space <s> --name <n> [--role <r>] [--channel <c>]\ncotal join --link <url> | --token <t>\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and which credential |\n| `--name <n>` | \u2014 | Your presence name |\n| `--role <r>` | \u2014 | Your role |\n| `--channel <c>` | \u2014 | Channel to join |\n| `--kind <k>` | `agent` | Endpoint kind |\n| `--link <url>` | \u2014 | Join link (`cotal://\u2026`) |\n| `--token <t>` | \u2014 | Join token |\n| `--lifecycle-uid <uid>` | \u2014 | Required with `--creds`: the lifecycle UID minted alongside the credential (`COTAL_LIFECYCLE_UID` works too). A credential's durable grants name exact lifecycle-keyed resources, so `join` refuses to invent one |\n| `--tls` | off | Connect over TLS |\n\nAn interactive presence: join a space under your own name and role, without launching an agent\nharness. A `--link` or `--token` supplies the where and the auth in one value. See\n[Spaces](spaces.md) and [Identity and auth](identity-and-auth.md).\n\n## Manifest deploys\n\nA `cotal.yaml` manifest declares a whole mesh (channels, personas, roles, and ACLs) in one file.\nThree commands consume it, plus a read-only validator:\n\n```bash\ncotal up -f cotal.yaml # boot a fresh mesh from the manifest\ncotal spawn -f cotal.yaml # deploy the manifest additively onto a running mesh\ncotal down -f cotal.yaml # tear that deploy down (or --run <id> for one run)\ncotal topology view -f cotal.yaml # validate + view the access graph, change nothing\n```\n\n`up -f` and `spawn -f` differ in target: `up -f` brings up a new broker and applies the manifest;\n`spawn -f` requires an already-reachable mesh and applies additively (ownership-scoped). On a\nuser-auth mesh, `spawn -f` deploys over your own login (the deployer view, gated on ledger scope\n`spawn`): the manifest's agents land under your owner, a manifest claiming another owner is\nrefused, and seeding new channels additionally needs scope `admin`. Both take\n`--dry-run` to print the plan without mutating anything. `topology` validates the manifest and\nrenders its channel / role / ACL graph. See [Define a team](define-a-team.md) and the\n[manifest reference](manifest.md).\n\n## ext\n\n```bash\ncotal ext # same as `list`\ncotal ext add <npm-package>\ncotal ext remove <name>\ncotal ext list\ncotal ext root # print just the install prefix (scriptable)\ncotal ext seed [--repair|--reset|--force]\n```\n\nOperator-installed extensions: `add` installs an npm package into a cotal-owned prefix and records\nevery registry provider it contributes. Commands appear in help, completion, and dispatch; runtime\nproviders are lazy-loaded by commands such as `supervise`; local process providers participate in\n`status` and selective `down`. `remove` and `list` manage them. The `@cotal-ai/web` dashboard is the\ncanonical command/process example. Installed packages and their location are described in\n[config](config.md).\n\nBare `cotal ext` lists the inventory, headed by the install prefix. That prefix is a cotal-owned npm\nroot kept **separate** from npm's own global tree, so these packages never show up in `npm list -g` \u2014\n`cotal ext` (or the Extensions section of `cotal status`) is the canonical inventory. `cotal ext root`\nprints only the path, for scripts. The versions shown are the manifest pin recorded at add time.\n\nRemoving an extension that owns a running local process is refused with the mesh root and its\n`cotal down <component>` command; stop it first so uninstalling the package never strands a process\nwhose lifecycle provider is gone.\n\n### Built-in connectors are seeded extensions\n\nThe first-party agent connectors (`claude`, `opencode`, `codex`, `hermes`, `pi`) are not compiled into\nthe binary. They are seeded on first run through the **same** `ext add` path a third party uses, and\nappear in `cotal ext list` like any other extension. So you can remove one you do not want\n(`cotal ext remove @cotal-ai/connector-hermes`), and a deliberately-removed connector STAYS removed\nacross upgrades. `cotal ext add <your-package>` adds a third-party connector the same way. The web\ndashboard (`@cotal-ai/web`, providing `command:web`) is a fifth built-in seeded on the same path.\n\n`cotal ext seed` is the maintenance entry for that seeding (it runs automatically on the first real\ncommand of each boot, so you rarely call it):\n\n| Flag | Meaning |\n|---|---|\n| (none) | Reconcile: seed any never-seeded built-in, refresh a seeded one whose version the binary bumped, leave a removed one removed. A no-op once current. |\n| `--repair` | Recover after an interrupted seed or a lost authority (rebuilds the interrupted connector; restores the removed-vs-never-seeded record from its durable backup). |\n| `--reset` | Discard the record and re-seed all six built-ins (the five connectors plus the web dashboard). **Resurrects any you removed.** Rebuilds cleanly over corrupt seed state. |\n| `--force` | Re-seed the built-ins even when the version stamp is current or a downgrade. |\n\nThe default connector for a bare `cotal spawn` (no `--agent`) is `claude`; set `COTAL_DEFAULT_AGENT`\n(e.g. `opencode`) to change it. An `--agent` naming a removed connector fails loud with the exact\n`cotal ext add` to restore it. Set `COTAL_SKIP_CONNECTOR_SEED=1` to turn off the automatic first-run\nseed/refresh entirely (for a controlled or offline setup that manages connectors by hand); `cotal ext\nseed` still runs on request.\n\n## completion\n\n```bash\ncotal completion <bash|zsh|fish|powershell> # print a stub to eval / source\ncotal completion install [shell] # install it persistently\n```\n\nPrints or installs shell completion. Completion candidates come from each command's declared flags\nand, where useful, live mesh state (spaces, personas, managed agents) resolved offline.\n\n## feedback\n\n```bash\ncotal feedback \"<summary>\" [--type <t>] [--email <e>] [--details <text>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--type <t>` | \u2014 | `bug` \\| `idea` \\| `friction` \\| `praise` \\| `other` |\n| `--details <text>` | \u2014 | Longer free-form details |\n| `--severity <s>` | \u2014 | `low` \\| `medium` \\| `high` |\n| `--area <a>` | \u2014 | The part of Cotal this concerns |\n| `--email <e>` | git email | Contact email (required on the keyless public path) |\n| `--name <n>` | \u2014 | Your name (optional) |\n| `--url <url>` | keyed / public intake | Intake URL override |\n| `--key <k>` | `COTAL_FEEDBACK_KEY` | Feedback key |\n\nSends feedback to the Cotal developers. With a key (`--key` / `COTAL_FEEDBACK_KEY`) it routes to the\nkeyed beta intake; without one it goes to the public `cotal.ai` intake and requires a contact email\n(`--email` / `COTAL_FEEDBACK_EMAIL`, else your git email). Run a self-hosted intake with\n[`feedback-intake`](#server-daemons).\n\n## Server daemons\n\nTwo long-lived infra roles ship with the CLI. They are not part of everyday operation; the delivery\ndaemon comes up automatically with `cotal up --detach` in auth mode.\n\n```bash\ncotal deliver --space <s> [--server <url>] [--creds <file>]\ncotal auth-service --space <s> --server <url> [--port <n>]\ncotal feedback-intake --keys <keys.json> [--port <n>] [--creds <file>]\n```\n\n`auth-service` runs a user-auth space's identity plane (the NATS auth callout plus the\nloopback token exchange and JWKS); `cotal up --user-auth` starts and supervises it for you,\nso you run it directly only to recover one by hand.\n\n`deliver` runs the server-side Plane-3 delivery daemon: the durable backstop and membership/ACL\nauthority. It is auth-mode-only and single-instance (`--shard`/`--shards` accept only `N=1`);\n`--dev-mint` mints a scoped cred from the local signer for standalone dev. See the\n[delivery daemon](delivery-daemon.md). `feedback-intake` runs a self-hosted feedback server\n(requires `--keys` and a scoped `--creds`), announcing submissions into a space channel; flags\ninclude `--host`/`--port`, `--store`, `--space`/`--channel`, `--max-bytes`, and `--rate-limit`.\n\n## Plumbing\n\n`cotal __complete <words\u2026>` is the internal entry the shell-completion stubs call to emit candidates\nfor the current command line; you never run it directly. `cotal agent-bearer` is machine-facing\nplumbing on user-auth meshes: spawned agents exec it to print a fresh short-lived bearer from their\nspawn-time secret; you never run it directly either. (`cotal start` is a removed tombstone: it\nerrors and points you to `cotal spawn --detach`.)\n"
|
|
43633
43787
|
},
|
|
43634
43788
|
{
|
|
43635
43789
|
"slug": "config",
|
|
@@ -43762,7 +43916,7 @@ var DOCS_BUNDLE = {
|
|
|
43762
43916
|
"title": "Run a mesh",
|
|
43763
43917
|
"kind": "Guide (informative)",
|
|
43764
43918
|
"summary": "Day-to-day operation of a local mesh: what cotal up actually runs, how spawning resolves personas, harnesses, and models, how to reach a mesh from any directory, and the operator-only maintenance v\u2026",
|
|
43765
|
-
"body": "# Run a mesh\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\nDay-to-day operation of a local mesh: what `cotal up` actually runs, how spawning\nresolves personas, harnesses, and models, how to reach a mesh from any directory, and the\noperator-only maintenance verbs. Every command's full flag set is in the\n[CLI reference](cli.md).\n\n## The stack\n\n`cotal up` brings up the whole local stack and bare `cotal down` stops it:\n\n- **Broker**: a local `nats-server` (logs to `.cotal/nats.log`).\n- **Delivery daemon**: the durable backstop, auth mode only\n ([what it does](delivery-daemon.md)).\n- **Manager**: a detached supervisor answering the control plane, so\n `cotal spawn --detach` and the `cotal_spawn` tool work right after `up`.\n\nThree modes:\n\n- **Default (static auth).** JWT-authed, on by default: sender authenticity and per-agent\n ACLs, enforced by the broker ([how](identity-and-auth.md)).\n- **`--user-auth --idp <url>`.** Per-user auth: people `cotal login` once, the operator\n grants their agents on the actor ledger, and every connect is authorized live against\n that grant. Starts the space's auth service alongside the broker\n ([how](identity-and-auth.md)).\n- **`--open`.** An unauthenticated, live-only dev mesh (no auth, no delivery daemon). For\n quick local experiments.\n\nAll bind **loopback** by default. `--host 0.0.0.0` widens the bind independently of the\nauth mode, so \"network-reachable\" never silently means \"unauthenticated\". With no explicit\n`--server`, `cotal up` auto-selects a free local port when the default address is already\nheld by another project; an explicit `--server` fails loud on collision.\n\n`cotal status` prints the detailed setup, process, registry, and live mesh status;\n`cotal setup` (after the first run) prints the compact card.\n\nStop one part without tearing down the mesh by naming its registered component: `cotal down\nmanager`, `cotal down delivery`, or `cotal down web`. Component names from installed extensions\njoin the same surface; `cotal down` with no names retains whole-stack behavior.\n\n## Spawning agents\n\n```bash\ncotal spawn # foreground: your default agent, in this terminal\ncotal spawn reviewer --detach # supervised: the manager runs it in a PTY\ncotal attach --name reviewer # watch/type into a detached agent (Ctrl-] detaches)\ncotal ps # what the manager is running\ncotal stop --name reviewer # stop one\n```\n\nHow a spawn resolves:\n\n- **Persona.** A bare `cotal spawn` uses `.cotal/agents/default.md`; a positional name\n picks `.cotal/agents/<name>.md`; `--config` takes an explicit ref or path. Set\n `COTAL_DEFAULT_PERSONA=<name-or-path>` to change the fallback. Fields and format:\n [agent files](agent-files.md).\n- **Harness.** Claude by default; `--agent opencode` / `--agent hermes` / `--agent pi` per\n spawn, or `COTAL_DEFAULT_AGENT` to change the default. Compared in\n [Connectors](connectors.md); per-connector guides:\n [Claude](connect-claude.md) \xB7 [OpenCode](connect-opencode.md) \xB7\n [Hermes](connect-hermes.md) \xB7 [pi](connect-pi.md).\n- **Model.** `--model` overrides the persona file's `model:` (Claude: `opus` / `sonnet` or\n a full id; OpenCode: `provider/model`). Connectors that expose a catalog report it via\n `cotal models --agent opencode`: model ids plus available variants; pick one with\n `--model provider/model --variant high`.\n- **Tools.** A spawned agent gets only the cotal tools by default; share your own MCP\n servers deliberately with `--share-tools` ([config](config.md)).\n- **Launch options.** `--opt key=value` (repeatable) passes a native harness flag straight\n through; a persona or manifest `launchOptions:` mapping does the same declaratively (a\n `--opt` wins per key). It is a **raw passthrough**, with no allow/deny list: Claude renders\n each as `--key value` (a bare `--key` for an empty value), OpenCode merges them into its\n agent config, and Hermes has no option surface so it fails loud. The trust boundary is the\n `spawn` capability itself, not the flag set, so granting `spawn` is host-launch authority\n ([security](security.md)). A key must be a plain flag name; malformed or prototype-polluting\n keys are refused.\n\nDetach from an attached PTY with **Ctrl-]** (the agent keeps running); rebind it with\n`COTAL_DETACH_KEY=ctrl-<char>` when it clashes with a keybinding inside the agent's TUI.\n\n**Runtimes.** The manager spawns into a **pty** it owns by default. Optional runtimes are installed\nthrough the extension surface, for example `cotal ext add @cotal-ai/orca`, then selected with\n`--runtime orca` (similarly `@cotal-ai/tmux` and `@cotal-ai/cmux`). They put teammates in native\nterminal surfaces rather than manager-owned PTYs. Runtime names are open-ended and resolved from\nthe registry; a missing provider or app throws, never silently falls back\n([architecture](architecture.md)).\n\n## From any directory: the mesh registry\n\n`cotal up` records each running mesh in a machine-local registry\n(`~/.cotal/meshes/space.<key>.json`, named by a case-safe hex encoding of the space: broker URL, the project root holding its creds and\npersonas, and its mode). So a bare `cotal spawn <persona>` from *any* directory joins the\nrunning mesh with the right credentials instead of mistaking the cwd for a space:\n\n- `cotal use <name>` sets the default from every directory, including inside another mesh's\n project. `--space <name>` overrides it for one command.\n- With no live selected default, a project with its own `.cotal/` resolves to that project's\n mesh; otherwise one running mesh is used automatically and several are an error.\n- `cotal meshes` lists them (a `*` marks the default); `cotal down` removes the entry.\n\nThe registry stores a *path*, never a secret; trust material stays in each project's\n`.cotal/auth`. If the mesh is down or won't take your creds, spawn fails with one\nsentence, never a raw NATS trace.\n\n## Watching\n\n`cotal console` is the terminal view (TUI on a real terminal, plain line stream when\npiped); `cotal web` is the browser dashboard. Both are read-only observers; the\nwalkthrough is [Watch a mesh](watch-a-mesh.md).\n\n## History\n\nRetained history is operator-owned. `cotal clean history --force` purges a space's\nretained channel history; `--dms` also purges DMs (`cotal history clear` is an alias).\nIt is deliberately **not** an agent tool: agents cannot wipe the record\n([identity & auth](identity-and-auth.md)). For a **stopped** mesh, `cotal clean store\n--force` deletes the on-disk JetStream store outright, and `cotal clean all --force`\nalso resets the space identity ([CLI reference](cli.md#clean)).\n\n## Offline backup\n\nFor a coherent durable cut, preserve the whole stack first, then create the artifact while it stays\ndown:\n\n```bash\ncotal down --preserve-state\ncotal backup create ./space-backup # full by default\n# later: deliberately resume the unchanged source\ncotal up --detach\n# or, from another preserved cut, restore before the normal listener opens\ncotal up --restore ./space-backup --detach\n```\n\nUse `--store-dir` on both preservation and backup for a custom JetStream store. `registry` is the\nonly partial selection (`backup create ... --only registry`; `up --restore ... --restore-only\nregistry`). Backup never stops or restarts a mesh implicitly, never opens the original store, and\ndoes not contain credentials or trust secrets. Backup/restore in every auth mode \u2014 open included \u2014\nuses isolated, operation-specific maintenance logins; normal agent credentials cannot enter that\nlistener. Full\nrestore requires the same space and exact current local trust continuity, recreates conservative\nconsumer checkpoints bound to their snapshot stream sequence state, and resumes retained agents under\ntheir original principals. The trust commitment includes the cryptographically validated full\noperator/system/data-account root chain as well as static/user authority state. A registry-only\nrestore completes canonical empty infrastructure but leaves retained agents stopped because their\nDM/DLV/TASK/ACL state is outside that selection. Authenticated restore validates the complete space\ntrust bundle before staging or changing the preserved store. Interrupted ordinary resume retries the\nsame durable attempt after its prior listener is stopped. Restore re-entry can recover a surviving normal listener\nonly when its attempt nonce, NATS server name, process owner, endpoint, and target-store identity all\nmatch the fsynced proof. A provably dead uncommitted owner is retired under lock and replaced with a\nfresh attempt-bound listener; an occupied foreign listener or ambiguous owner is never adopted. The\nmanager commit validates while retained cleanup is still suppressed; the CLI durably records its\nattempt-bound 64-hex token in `manager-committed` / `resume-committed` before `finalizeResume` can\nrelease suppression. A retry from either committed state goes straight to exact-token finalization;\nfailure preserves the committed gate and retained cleanup suppression. Missing commit evidence,\ninterrupted finalization, a live recorded endpoint despite missing pidfiles, or ambiguous proof fails closed. See the [CLI\nbackup and restore contract](cli.md#backup-and-restore) for artifact, checkpoint, fallback,\ndisaster-consent, and degraded-recovery details.\n\n## Personas from the CLI\n\n`cotal personas` manages the local catalog offline: `list` (`--running` overlays live\nmarkers), `show <name>`, `edit <name>` (re-validates on save), `new <name>`, `rm <name>\n--force`. The runtime counterpart is the `cotal_persona` tool, which goes over the wire\nwith the manager's ownership checks. Fields: [agent files](agent-files.md).\n\n## When something looks absent\n\nPermission denials are **loud, never silent**: an over-tight ACL shows up as a logged\ndenial on the endpoint, not as a peer that mysteriously looks absent. Check\n`.cotal/manager.log`, `.cotal/delivery.log`, and `.cotal/nats.log`; `cotal status` shows\nwhat is actually running. The access rules are collected in\n[Channels & permissions](channels-and-permissions.md).\n"
|
|
43919
|
+
"body": "# Run a mesh\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\nDay-to-day operation of a local mesh: what `cotal up` actually runs, how spawning\nresolves personas, harnesses, and models, how to reach a mesh from any directory, and the\noperator-only maintenance verbs. Every command's full flag set is in the\n[CLI reference](cli.md).\n\n## The stack\n\n`cotal up` brings up the whole local stack and bare `cotal down` stops it:\n\n- **Broker**: a local `nats-server` (logs to `.cotal/nats.log`).\n- **Delivery daemon**: the durable backstop, auth mode only\n ([what it does](delivery-daemon.md)).\n- **Manager**: a detached supervisor answering the control plane, so\n `cotal spawn --detach` and the `cotal_spawn` tool work right after `up`.\n\nThree modes:\n\n- **Default (static auth).** JWT-authed, on by default: sender authenticity and per-agent\n ACLs, enforced by the broker ([how](identity-and-auth.md)).\n- **`--user-auth --idp <url>`.** Per-user auth: people `cotal login` once, the operator\n grants their agents on the actor ledger, and every connect is authorized live against\n that grant. Starts the space's auth service alongside the broker\n ([how](identity-and-auth.md)).\n- **`--open`.** An unauthenticated, live-only dev mesh (no auth, no delivery daemon). For\n quick local experiments.\n\nAll bind **loopback** by default. `--host 0.0.0.0` widens the bind independently of the\nauth mode, so \"network-reachable\" never silently means \"unauthenticated\". With no explicit\n`--server`, `cotal up` auto-selects a free local port when the default address is already\nheld by another project; an explicit `--server` fails loud on collision.\n\n`cotal status` prints the detailed setup, process, registry, and live mesh status;\n`cotal setup` (after the first run) prints the compact card.\n\nStop one part without tearing down the mesh by naming its registered component: `cotal down\nmanager`, `cotal down delivery`, or `cotal down web`. Component names from installed extensions\njoin the same surface; `cotal down` with no names retains whole-stack behavior.\n\n## Spawning agents\n\n```bash\ncotal spawn # foreground: your default agent, in this terminal\ncotal spawn reviewer --detach # supervised: the manager runs it in a PTY\ncotal attach --name reviewer # watch/type into a detached agent (Ctrl-] detaches)\ncotal ps # what the manager is running\ncotal stop --name reviewer # stop one\n```\n\nHow a spawn resolves:\n\n- **Persona.** A bare `cotal spawn` uses `.cotal/agents/default.md`; a positional name\n picks `.cotal/agents/<name>.md`; `--config` takes an explicit ref or path. Set\n `COTAL_DEFAULT_PERSONA=<name-or-path>` to change the fallback. Fields and format:\n [agent files](agent-files.md).\n- **Harness.** Claude by default; `--agent opencode` / `--agent hermes` / `--agent pi` per\n spawn, or `COTAL_DEFAULT_AGENT` to change the default. Compared in\n [Connectors](connectors.md); per-connector guides:\n [Claude](connect-claude.md) \xB7 [OpenCode](connect-opencode.md) \xB7\n [Hermes](connect-hermes.md) \xB7 [pi](connect-pi.md).\n- **Model.** `--model` overrides the persona file's `model:` (Claude: `opus` / `sonnet` or\n a full id; OpenCode: `provider/model`). Connectors that expose a catalog report it via\n `cotal models --agent opencode`: model ids plus available variants; pick one with\n `--model provider/model --variant high`.\n- **Tools.** A spawned agent gets only the cotal tools by default; share your own MCP\n servers deliberately with `--share-tools` ([config](config.md)).\n- **Launch options.** `--opt key=value` (repeatable) passes a native harness flag straight\n through; a persona or manifest `launchOptions:` mapping does the same declaratively (a\n `--opt` wins per key). It is a **raw passthrough**, with no allow/deny list: Claude renders\n each as `--key value` (a bare `--key` for an empty value), OpenCode merges them into its\n agent config, and Hermes has no option surface so it fails loud. The trust boundary is the\n `spawn` capability itself, not the flag set, so granting `spawn` is host-launch authority\n ([security](security.md)). A key must be a plain flag name; malformed or prototype-polluting\n keys are refused.\n\nDetach from an attached PTY with **Ctrl-]** (the agent keeps running); rebind it with\n`COTAL_DETACH_KEY=ctrl-<char>` when it clashes with a keybinding inside the agent's TUI.\n\n**Runtimes.** The manager spawns into a **pty** it owns by default. Optional runtimes are installed\nthrough the extension surface, for example `cotal ext add @cotal-ai/orca`, then selected with\n`--runtime orca` (similarly `@cotal-ai/tmux` and `@cotal-ai/cmux`). They put teammates in native\nterminal surfaces rather than manager-owned PTYs. Runtime names are open-ended and resolved from\nthe registry; a missing provider or app throws, never silently falls back\n([architecture](architecture.md)).\n\n## From any directory: the mesh registry\n\n`cotal up` records each running mesh in a machine-local registry\n(`~/.cotal/meshes/space.<key>.json`, named by a case-safe hex encoding of the space: broker URL, the project root holding its creds and\npersonas, and its mode). So a bare `cotal spawn <persona>` from *any* directory joins the\nrunning mesh with the right credentials instead of mistaking the cwd for a space:\n\n- `cotal use <name>` sets the default from every directory, including inside another mesh's\n project. `--space <name>` overrides it for one command.\n- With no live selected default, a project with its own `.cotal/` resolves to that project's\n mesh; otherwise one running mesh is used automatically and several are an error.\n- `cotal meshes` lists them (a `*` marks the default); `cotal down` removes the entry.\n\nThe registry stores a *path*, never a secret; trust material stays in each project's\n`.cotal/auth`. If the mesh is down or won't take your creds, spawn fails with one\nsentence, never a raw NATS trace.\n\n### Meshes you did not start here\n\nA mesh running on another machine has no `cotal up` on this one, so register it by hand:\n\n```bash\ncotal meshes add # guided: asks for the broker, probes it, offers what it finds\ncotal meshes add optiplex --server nats://10.0.0.5:4222 --root ~/meshes/optiplex\ncotal meshes rm optiplex\n```\n\nOn a terminal, a bare `cotal meshes add` walks you through it: it probes the broker you name and\nreports whether it is open or requires credentials, offers the spaces the folder already holds\ncredentials for, and shows the record before writing it. Scripts and agents keep the flag form -\nwithout a terminal nothing prompts.\n\n`--root` is the local folder holding that mesh's `.cotal/auth` (credentials you minted where it\nruns) and `.cotal/agents` (its personas); the mode is inferred from what that folder holds. The\nbroker is probed before the record is written, so a bad address or a credential that mesh will not\naccept fails at registration rather than at your first `spawn` (`--force` records it without verifying \u2014\nuseful when the mesh is simply down right now).\n\nRecords added this way are removed only by something that names them. A mesh this machine started\ncan be dropped on a hunch \u2014 a failed liveness probe, a `cotal down` in its project \u2014 because\n`cotal up` writes the record straight back. One you registered by hand cannot be reconstructed, so\nnothing removes it by inference: an unreachable broker is shown as `offline` in `cotal meshes`, and\n`cotal down` / `cotal clean all` leave it alone even when `--root` pointed at the project they are\ntearing down. A `cotal up` for that space refuses outright (naming `cotal meshes rm`) unless it is\nthat same endpoint: finding a broker already answering there is a refresh that starts nothing and\nleaves the record's provenance alone, while actually starting the broker for that space, server and\nroot makes this machine the one running it, so the record becomes an ordinary local one that\n`cotal down` clears. `cotal meshes rm` drops it and re-registering with `--force` replaces it. `rm`\nonly forgets a mesh \u2014 to stop one running here, use `cotal down`.\n\n## Watching\n\n`cotal console` is the terminal view (TUI on a real terminal, plain line stream when\npiped); `cotal web` is the browser dashboard. Both are read-only observers; the\nwalkthrough is [Watch a mesh](watch-a-mesh.md).\n\n## History\n\nRetained history is operator-owned. `cotal clean history --force` purges a space's\nretained channel history; `--dms` also purges DMs (`cotal history clear` is an alias).\nIt is deliberately **not** an agent tool: agents cannot wipe the record\n([identity & auth](identity-and-auth.md)). For a **stopped** mesh, `cotal clean store\n--force` deletes the on-disk JetStream store outright, and `cotal clean all --force`\nalso resets the space identity ([CLI reference](cli.md#clean)).\n\n## Offline backup\n\nFor a coherent durable cut, preserve the whole stack first, then create the artifact while it stays\ndown:\n\n```bash\ncotal down --preserve-state\ncotal backup create ./space-backup # full by default\n# later: deliberately resume the unchanged source\ncotal up --detach\n# or, from another preserved cut, restore before the normal listener opens\ncotal up --restore ./space-backup --detach\n```\n\nUse `--store-dir` on both preservation and backup for a custom JetStream store. `registry` is the\nonly partial selection (`backup create ... --only registry`; `up --restore ... --restore-only\nregistry`). Backup never stops or restarts a mesh implicitly, never opens the original store, and\ndoes not contain credentials or trust secrets. Backup/restore in every auth mode \u2014 open included \u2014\nuses isolated, operation-specific maintenance logins; normal agent credentials cannot enter that\nlistener. Full\nrestore requires the same space and exact current local trust continuity, recreates conservative\nconsumer checkpoints bound to their snapshot stream sequence state, and resumes retained agents under\ntheir original principals. The trust commitment includes the cryptographically validated full\noperator/system/data-account root chain as well as static/user authority state. A registry-only\nrestore completes canonical empty infrastructure but leaves retained agents stopped because their\nDM/DLV/TASK/ACL state is outside that selection. Authenticated restore validates the complete space\ntrust bundle before staging or changing the preserved store. Interrupted ordinary resume retries the\nsame durable attempt after its prior listener is stopped. Restore re-entry can recover a surviving normal listener\nonly when its attempt nonce, NATS server name, process owner, endpoint, and target-store identity all\nmatch the fsynced proof. A provably dead uncommitted owner is retired under lock and replaced with a\nfresh attempt-bound listener; an occupied foreign listener or ambiguous owner is never adopted. The\nmanager commit validates while retained cleanup is still suppressed; the CLI durably records its\nattempt-bound 64-hex token in `manager-committed` / `resume-committed` before `finalizeResume` can\nrelease suppression. A retry from either committed state goes straight to exact-token finalization;\nfailure preserves the committed gate and retained cleanup suppression. Missing commit evidence,\ninterrupted finalization, a live recorded endpoint despite missing pidfiles, or ambiguous proof fails closed. See the [CLI\nbackup and restore contract](cli.md#backup-and-restore) for artifact, checkpoint, fallback,\ndisaster-consent, and degraded-recovery details.\n\n## Personas from the CLI\n\n`cotal personas` manages the local catalog offline: `list` (`--running` overlays live\nmarkers), `show <name>`, `edit <name>` (re-validates on save), `new <name>`, `rm <name>\n--force`. The runtime counterpart is the `cotal_persona` tool, which goes over the wire\nwith the manager's ownership checks. Fields: [agent files](agent-files.md).\n\n## When something looks absent\n\nPermission denials are **loud, never silent**: an over-tight ACL shows up as a logged\ndenial on the endpoint, not as a peer that mysteriously looks absent. Check\n`.cotal/manager.log`, `.cotal/delivery.log`, and `.cotal/nats.log`; `cotal status` shows\nwhat is actually running. The access rules are collected in\n[Channels & permissions](channels-and-permissions.md).\n"
|
|
43766
43920
|
},
|
|
43767
43921
|
{
|
|
43768
43922
|
"slug": "security",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cotal-ai/connector-opencode",
|
|
3
3
|
"description": "Cotal connector for OpenCode: a native in-process plugin that joins a session to the mesh.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.16.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"@opencode-ai/plugin": "^1.16.2",
|
|
25
25
|
"esbuild": "^0.28.0",
|
|
26
26
|
"tsx": "^4.22.4",
|
|
27
|
-
"@cotal-ai/core": "0.
|
|
28
|
-
"@cotal-ai/connector-core": "0.
|
|
27
|
+
"@cotal-ai/core": "0.16.0",
|
|
28
|
+
"@cotal-ai/connector-core": "0.16.0"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
"dist"
|