Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Chaotic CLI

Command-line interface for the Chaotic issue tracker.

Installation

cd cli
pip install -e .

Configuration

Set the API URL (defaults to http://localhost:24267):

chaotic config set-url https://your-api-server.com

Authentication

Sign up / Login

chaotic auth signup
chaotic auth login

API Keys (for scripts/automation)

chaotic auth keys list
chaotic auth keys create
chaotic auth keys revoke <key-id>
chaotic auth set-key ck_your_api_key_here
chaotic auth clear-key

Check current user

chaotic auth whoami
chaotic me                     # Shortcut for 'auth whoami'

Status

Check current context (user, team, project):

chaotic status

JSON output

Most commands accept a --json flag — the CLI's machine-output mode, for scripts and agent harnesses. It works both before and after the subcommand name:

chaotic --json issue list          # equivalent
chaotic issue list --json          # equivalent

Under --json:

  • stdout carries exactly one JSON value — the result of the command (the created/updated/affected entity, a list, or {"error": ...} on failure). Nothing else is written to stdout.
  • Everything else — status lines, progress messages, tables, panels — goes to stderr. A caller doing data = json.loads(subprocess.check_output([...])) never has to worry about human-readable chatter breaking the parse.
  • Mutation and state-transition commands (issue create, issue move, issue block, doc update, sprint add, etc.) emit the created/affected entity's id as part of the JSON payload, so a caller doesn't need a follow-up issue show --json just to learn what happened.
  • Confirmation-gated commands (issue delete, doc delete, etc.) never prompt under --json — a machine consumer can't answer a TTY prompt. Pass --yes (chaotic --yes issue delete ... --json) to proceed; without it they emit {"error": ...} and exit 2 instead of hanging on (or leaking) a prompt.
  • This holds even for errors Click raises before your command runs — an invalid choice value (--status bogus), a missing required argument, an unknown flag all produce {"error": ...} on stdout (exit 2), with the usage text on stderr.

Run chaotic <command> --help to check whether a given command supports --json — it's listed in the flag help text, not hidden.

chaotic await has its own richer JSON contract (a normalized event schema, not just "whatever the entity looks like") — see the Await section below.

Exit codes

Code Meaning
0 Success
1 Runtime/API error (network failure, 4xx/5xx from the server, a ClickException raised by command logic)
2 Usage error — bad flags/arguments (Click's own UsageError/BadParameter, e.g. an invalid --status value)

This holds under --json too: a usage error still exits 2 (with {"error": ...} on stdout), not collapsed to 1. chaotic await extends this with its own additional codes (124 timeout, 130 SIGINT, 143 SIGTERM) — see the Await exit-code table below, which is more specific than this general contract.

Long-text values from a file or stdin

Every long-text flag in the CLI (--description, --body, --content, --comment, --note, --prompt) accepts curl-style file references:

# Read body from a file
chaotic doc create --project PRJ --title "Report" --body @./report.md

# Read body from stdin (useful with heredocs — no tmp file needed)
chaotic doc create --project PRJ --title "Report" --body - <<'EOF'
# Long doc

Body content with `backticks`, $vars, em-dashes — all fine.
EOF

# Literal "@" prefix (escape with @@)
chaotic issue comment CHT-11 --note @@alice

Resolution rules:

Value Behavior
- Read stdin to EOF
@/path/to/file Read text from file (UTF-8); error if missing
@@<rest> Literal @<rest> (escape; one level only)
anything else Literal value (existing behavior)

Stdin can only be consumed once per command; if multiple flags pass -, the second sees EOF.

Teams

chaotic team list              # List your teams
chaotic team use <team-id>     # Set current team
chaotic team show              # Show current team details
chaotic team create            # Create a new team
chaotic team members           # List team members
chaotic team invite            # Invite a member
chaotic team accept-invite     # Accept an invitation

Projects

chaotic project list           # List projects in current team
chaotic project use <project-id>  # Set current project
chaotic project show           # Show current project details
chaotic project create         # Create a new project
chaotic project update         # Update current project

Issues

Listing and viewing

chaotic issue list                        # List issues in current project
chaotic issue list --status in_progress   # Filter by status
chaotic issue list --priority high        # Filter by priority
chaotic issue mine                        # List issues assigned to me
chaotic issue mine --status in_progress   # Filter my issues by status
chaotic issue search "search term"        # Search issues across the team
chaotic issue show CHT-123                # Show issue details
chaotic issue view CHT-123                # Alias for 'show'
chaotic issue open CHT-123                # Open issue in browser

Ready to start (CHT-1245)

chaotic issue ready answers "what can I start right now" -- the agent's first shell-out, before touching issue list's filters. It's Beads' bd-ready equivalent: open (backlog/todo), unblocked (no unresolved blocks relation), unassigned by default, priority-sorted (urgent first, then oldest-first within a tier). Epics are excluded -- they're containers for work, not startable work; their sub-issues surface individually.

chaotic issue ready --json                 # unassigned, unblocked, open work
chaotic issue ready --mine                 # your own assigned-but-not-started backlog
chaotic issue ready --include-assigned     # widen: include everyone's claims too
chaotic issue ready --all-projects         # team-wide instead of current project
chaotic issue ready --limit 5

--mine and --include-assigned are mutually exclusive. See docs/agents.md for the full agent operating loop (readystart → work → complete) and how this interacts with claim leases below.

Claiming and completing

chaotic issue start CHT-123                # assign to self + move to in_progress
chaotic issue start CHT-123 --lease 4h     # override the claim-lease duration (CHT-1246)
chaotic issue claim CHT-123                # same thing (start is an alias for claim)
chaotic issue close CHT-123                # mark done (alias: issue complete)
chaotic issue wontfix CHT-123              # cancel (alias: issue cancel)

start/claim acquire a claim lease -- a server-side expiry (default ~2h, --lease overrides). Re-running start/claim while you still hold the ticket extends (heartbeats) the lease instead of erroring. Claiming a ticket someone else already holds under a valid lease fails with an "already claimed by X" error (already_claimed on the wire, exit 1) -- concurrent claims are serialized server-side, so of two racing issue starts exactly one wins. If the lease expires while the issue is still in_progress, the next read or list of that issue lazily releases it back to todo/unassigned and logs a lease_expired activity -- no cron, no silent wedge. See docs/agents.md for the full semantics.

Creating issues

chaotic issue create --title "Bug fix"
chaotic issue create --title "Bug fix" --project CHT     # Specify project by key
chaotic issue create --title "Sub-task" --parent CHT-123  # Create sub-issue
chaotic issue create --title "Feature" --priority high --status todo

Updating issues

chaotic issue update CHT-123 --status done
chaotic issue update CHT-123 --priority urgent
chaotic issue update CHT-123 --assignee user-id
chaotic issue update CHT-123 --estimate 5
chaotic issue move CHT-123 in_progress      # Quick status change
chaotic issue assign CHT-123 me             # Assign to yourself
chaotic issue assign CHT-123 agent-bot      # Assign to an agent by name/ID
chaotic issue assign CHT-123                # Unassign

Comments

chaotic issue comment CHT-123 "This is a comment"

Sub-issues and Relations

chaotic issue sub-issues CHT-123           # List sub-issues
chaotic issue relations CHT-123            # Show issue relations
chaotic issue block CHT-1 CHT-2            # CHT-1 blocks CHT-2
chaotic issue block CHT-1 CHT-2 --type duplicates  # Mark as duplicate
chaotic issue block CHT-1 CHT-2 --type relates_to  # Related issues
chaotic issue relations CHT-1              # Find the relation ID to remove
chaotic issue unblock CHT-1 <relation-id>  # Remove relation (relation ID, not an issue identifier)

Deleting

chaotic issue delete CHT-123

Sprints

chaotic sprint list            # List sprints in current project
chaotic sprint current         # Get or create the current sprint
chaotic sprint close <id>      # Close the current sprint
chaotic sprint add CHT-1 CHT-2 # Add issues to the current sprint
chaotic sprint remove CHT-1    # Remove an issue from its sprint

There is no sprint delete command — sprints aren't deletable at any layer (no client method, no backend route). If you need this, file a ticket rather than trying the command; it will error with "No such command".

Labels

chaotic label list             # List labels in current team
chaotic label create           # Create a new label
chaotic label delete <id>      # Delete a label

Documents

chaotic doc list               # List documents in current team
chaotic doc show <id>          # Show document content
chaotic doc create             # Create a new document
chaotic doc update <id>        # Update a document
chaotic doc delete <id>        # Delete a document

Await

chaotic await blocks until something happens in the scope you're watching, then exits. It's the agent-harness primitive for "park this process until a human — or another agent — does something I care about."

The command hangs on stdin/stdout, exits 0 on the first matching activity, exits 124 on timeout. The event that caused the wake is emitted on stdout. Under --json, stdout contains exactly one JSON object followed by \n; all human-readable output (errors, banners) goes to stderr.

Subcommands

chaotic await mirrors the top-level chaotic groups. Singular subcommands take a positional ID and wait on that specific entity; plural subcommands take collection filters and wait on any matching entity.

chaotic await issue CHT-1334                  # Specific issue
chaotic await issues --project CHT            # Any issue in project CHT
chaotic await doc D-91                        # Specific document
chaotic await docs                            # Any document in current team
chaotic await project CHT                     # Any activity in a project
chaotic await project                         # Current project
chaotic await sprint CHT-S24                  # Specific sprint
chaotic await sprint                          # Current sprint
chaotic await team platform                   # Any activity on a team
chaotic await team                            # Current team
chaotic await ritual code-review              # Specific ritual's next attestation
chaotic await ritual code-review --ticket CHT-1334   # Ticket-scoped ritual
chaotic await rituals                         # Any ritual attestation in current project

await project wakes on issue activity, document activity, and comments on either, within the project. await team wakes on anything team-wide. Collection filters on plurals (issues, docs) are currently limited to --project. Other filters (--sprint, --assignee, --author, --status, …) are not yet wired through; combine --type with --until for client-side narrowing in the meantime.

await sprint MVP limitation: await sprint [ID] does NOT filter to just the sprint — the backend feed doesn't carry per-sprint scope, and the CLI doesn't fetch each event's parent issue to check its sprint_id. It currently wakes on any activity in the sprint's parent project. To narrow to sprint-specific signals, combine with --type moved_to_sprint,removed_from_sprint, or with --until checking the event's sprint_name field. True sprint scoping will land when the backend gains a sprint-aware activity endpoint.

Scope resolution

Each subcommand inherits the auth/team/project requirement of its corresponding chaotic command. The CLI errors (exit 2) with a clear message when a required scope is missing.

Subcommand Required context Notes
await issue ID auth Issue identifier is globally unique
await issues current project Mirrors issue list. Pass --project to override.
await doc ID current team Doc IDs are team-local
await docs current team Mirrors doc list
await project [ID] current team (ID optional) Defaults to current project
await sprint [ID] current project when ID omitted With ID, the sprint is looked up and its parent project becomes the scope. MVP scopes to that project, not to the sprint itself. See note below.
await team [ID] auth (ID optional) Defaults to current team
await ritual NAME current project Ritual names are per-project. Pass --ticket to also wake on intent_* events.
await rituals current project Wakes on ritual events project-wide, including intent lifecycle.

intent_opened / intent_cleared / intent_canceled events fire at the ticket level — one intent covers every claim-blocking (or close-blocking) ritual on the issue at once, so the event doesn't carry a single ritual name. Under await ritual NAME, intent events are only matched when --ticket is also given (the wait is then bound to a specific issue; the type filter does the rest). Without --ticket, intent events are silently skipped from await ritual NAME. Use await issue ID --type intent_opened,intent_cleared,intent_canceled or await rituals if you want to wake on intents regardless of ritual name.

Flags

-t, --type TYPES    Comma-separated activity types to wake on. Logical
                    tokens, mapped internally to the right backend enum
                    per entity type:
                      commented, status_changed, priority_changed,
                      assigned, unassigned, labeled, unlabeled,
                      moved_to_sprint, removed_from_sprint,
                      attested, approved, intent_opened,
                      intent_cleared, intent_canceled, lease_expired,
                      created, updated, deleted, any
                    Default: any. These tokens are a stable CLI contract;
                    they do not change if backend enum names are renamed.
                    For cross-entity scopes (project, team) a token
                    matches the equivalent activity across both issues
                    and documents.
--include-self      Wake on activity authored by the current auth
                    principal (compared on user_id). Default: excluded —
                    an agent that spawns `await` as a background
                    subprocess and keeps working should not wake itself
                    on its own ongoing activity. Note: if multiple agents
                    share one principal (e.g. a team bot), this filter
                    hides all of their activity, not just the caller's.
                    Exception: `lease_expired` events are never filtered
                    as self — their user_id is the *former* lease holder
                    by attribution (the release is a system action), and
                    the headline use is an agent awaiting its own lease's
                    expiry.
--timeout DURATION  Give up after DURATION. Accepted forms: integer
                    seconds (`30`), or suffixed units, combinable
                    (`30s`, `5m`, `8h`, `1h30m`; whitespace between
                    parts is fine). Zero is rejected — omit the flag
                    for no timeout. Default: no timeout. Exits 124 on
                    expiry.
--json              Emit the event as a single JSON object on stdout.
                    See "JSON output contract" below.
--until CMD         Shell predicate. When a candidate event would match,
                    CMD is executed via `sh -c CMD` with the event JSON
                    piped to its stdin. Exit 0 wakes; any non-zero exit
                    keeps polling. See "--until contract" below.

Exit codes

Code Meaning
0 Matching event received
1 Error (see "Error handling and retries")
2 Usage error (invalid ID up front, missing required scope, etc.)
124 --timeout expired with no matching event
130 Interrupted (SIGINT)
143 Terminated (SIGTERM) — clean exit with a stderr message, never a partial JSON object on stdout

Error handling and retries

Transient failures during a wait are retried silently with exponential backoff (1s, 2s, 4s, 8s, capped at 30s), retrying indefinitely while --timeout (if any) has not expired. Transient errors include:

  • Connection errors and timeouts
  • HTTP 429 (rate limit)
  • HTTP 5xx (server error)

Non-transient failures exit 1 with a message on stderr:

  • HTTP 401/403 (auth/permission — won't self-heal without intervention)
  • HTTP 404 on the initial target lookup (e.g. await issue CHT-9999999)
  • HTTP 404 after a successful wait begins (target deleted mid-wait)
  • --until predicate failed to execute (missing binary, not executable)

JSON output contract

Under --json, stdout contains exactly one JSON object followed by a single \n. All other output (errors, warnings, retry messages) routes to stderr, so harnesses can safely json.loads(stdout) without stripping.

All fields shown below are always present. Fields that don't apply to the event type (e.g. document_id on an issue activity) are present with a null value, not omitted — jq -e '.document_id' will see the key. The rendered (non-JSON) output is unstable and intended for human use only; do not parse it.

{
  "id": "act_48213",
  "activity_type": "commented",
  "created_at": "2026-04-19T14:22:07Z",
  "user_id": "u_42",
  "user_name": "ali",
  "user_email": "ali@example.com",
  "issue_id": "iss_9f...",
  "issue_identifier": "CHT-1334",
  "issue_title": "Polling await command",
  "document_id": null,
  "document_title": null,
  "document_icon": null,
  "field_name": null,
  "old_value": null,
  "new_value": null,
  "sprint_name": null
}

This shape is the CLI-stable contract, enforced by the CLI itself: the documented keys are filled in (with null) even if the backend omits them. Fields the backend adds beyond this list pass through — harnesses should ignore unknown keys. The same normalized shape is what --until predicates receive on stdin.

--until contract

  • Invocation: sh -c CMD (POSIX /bin/sh). Shell quoting rules apply, which is why --until 'jq -e ".new_value == \"in_review\""' parses correctly.
  • Event delivery: the candidate event is piped to the predicate's stdin as a single JSON object followed by \n. Event data is never interpolated into CMD — adversarial values in event fields (issue_title, old_value, etc.) cannot inject shell commands.
  • Predicate output: stdout and stderr of the predicate are discarded. This preserves await's own stdout contract.
  • Exit code semantics:
    • 0 → wake: await prints the event and exits 0.
    • Any other non-zero → reject: await keeps polling.
    • 126 (not executable) or 127 (command not found) are treated specially: await exits 1 with a clear error rather than silently looping forever on a broken predicate.
  • Timing: the predicate runs synchronously on every candidate event. Keep it fast and side-effect-free. A single run is killed after 30 seconds and treated like a broken predicate (await exits 1 with a clear error) — a wedged predicate would otherwise hang the wait forever, out of reach of --timeout.

Usage patterns

Agent parks on its current ticket. Process blocks until a human comments or changes status; then the harness resumes its next turn.

chaotic await issue CHT-1334 --type commented,status_changed --timeout 8h

Agent waits for a ritual attestation before proceeding. The literal "agent yields, human gates" pattern — agent finishes work, parks until the gating ritual is attested.

chaotic await ritual code-review --ticket CHT-1334 --timeout 24h --json
# Event JSON payload includes the attestation note; harness reads it to
# decide next step (merge, address feedback, etc).

On-call agent watching a project. Each wake is the trigger for one agent turn; the harness re-enters await when the turn is over.

# In the harness (pseudo-code):
while true; do
  event_json=$(chaotic await issues --project CHT --type commented --json) \
    && handle_event "$event_json"
done

Narrow wake condition via --until. Wake only when the ticket transitions to review, not on any status change. The predicate runs in a cheap subprocess per candidate; the agent itself never sees the non-matching events.

chaotic await issue CHT-1334 --type status_changed --json \
  --until 'jq -e ".new_value == \"in_review\""'

Interactive human use. Pass --include-self so you see your own edits in the feed.

chaotic await sprint --include-self

Notes for harness authors

  • await is single-shot by design. There is no --follow / tail mode; loop by re-invoking await from the harness.
  • The watermark (created_at threshold) is set internally at command start. Events that landed before the command started are not replayed. Harnesses that crash mid-wait lose events between death and restart.
  • --until predicates should be fast and side-effect-free — they run synchronously on every candidate event.
  • Concurrent await invocations are supported. No client-side coordination is required; each process maintains its own watermark.

MCP server

chaotic mcp runs an MCP (Model Context Protocol) server over stdio, exposing a curated set of chaotic operations as tools -- so any MCP-speaking harness (Claude Code, etc.) gets native chaotic tools instead of shelling out to this CLI.

It's a thin adapter over the same Client this CLI uses. There is no separate MCP login step: it inherits whatever chaotic status reports (profile, team, project, credentials) via the usual CHAOTIC_PROFILE / CHAOTIC_HOME / config.json resolution.

Add it to Claude Code

claude mcp add chaotic -- chaotic mcp

To pin a specific profile, pass the flag through:

claude mcp add chaotic -- chaotic --profile myprofile mcp

Generic MCP client config

{
  "mcpServers": {
    "chaotic": {
      "command": "chaotic",
      "args": ["mcp"]
    }
  }
}

Tools

Tool Equivalent to
issue_list issue list (with all_projects for team-wide)
issue_view issue show
issue_create issue create
issue_update issue update + issue assign (status/priority/estimate/assignee/title/description; assignee: "unassigned" clears)
issue_comment issue comment
issue_start issue start
doc_list doc list
doc_view doc show
doc_create doc create
activity_recent activity

Every tool returns a JSON object. Failures come back as {"error": "..."} (the same shape as this CLI's --json error contract) rather than an MCP protocol-level error -- a bad identifier or missing team/project context is data for the caller to read, not a crash.

No destructive tools (delete) are exposed in v1 -- those need a human in the loop. An issue_ready tool (open/unblocked/unclaimed work query) is expected once CHT-1245 lands; it isn't included here yet.

Status Values

  • backlog - Not yet planned
  • todo - Planned for work
  • in_progress - Currently being worked on
  • in_review - Awaiting review
  • done - Completed
  • canceled - Canceled

Priority Values

  • no_priority - No priority set
  • low - Low priority
  • medium - Medium priority
  • high - High priority
  • urgent - Urgent, needs immediate attention

Relation Types

  • blocks - Issue blocks another issue
  • relates_to - Issues are related
  • duplicates - Issue is a duplicate of another

Download files

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

Source Distribution

chaotic_cli-0.1.0a16.tar.gz (114.7 kB view details)

Uploaded Source

Built Distribution

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

chaotic_cli-0.1.0a16-py3-none-any.whl (131.3 kB view details)

Uploaded Python 3

File details

Details for the file chaotic_cli-0.1.0a16.tar.gz.

File metadata

  • Download URL: chaotic_cli-0.1.0a16.tar.gz
  • Upload date:
  • Size: 114.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.6

File hashes

Hashes for chaotic_cli-0.1.0a16.tar.gz
Algorithm Hash digest
SHA256 a04a067d5b6c422c1e4093943330c0b9629f0c512419a3f85c48220e9da1a3eb
MD5 90dc68b7b4aad5332b919ba3c2955cf1
BLAKE2b-256 d9f4faebfce0e4e94297be5b68f027e1812c8f56f38b89eacefa41c3c13df6c9

See more details on using hashes here.

File details

Details for the file chaotic_cli-0.1.0a16-py3-none-any.whl.

File metadata

  • Download URL: chaotic_cli-0.1.0a16-py3-none-any.whl
  • Upload date:
  • Size: 131.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.6

File hashes

Hashes for chaotic_cli-0.1.0a16-py3-none-any.whl
Algorithm Hash digest
SHA256 8e382ec458439f06e70b1ce6b190a92085521a35a7001d46e59fd68abfe2f70a
MD5 a98ba762f1106b2800c1167db4fb1206
BLAKE2b-256 fa6aaa072e29c061139b241146a492df10af07c6a89e273727c8fcaf2ac87c6a

See more details on using hashes here.

Supported by

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