CLI

cymatics: every API operation from your terminal, plus browser login, a local simulator, scenario control and webhook forwarding. Built for people and agents alike.

Install

$npm install -g @cymos/cli

Requires Node.js 20.19 or later. Check it with cymatics --version.

Log in

cymatics login opens the dashboard in your browser. Confirm there and the CLI receives a new key on a one-time local callback. It checks the key with GET /v1/project before saving it to profiles.json, readable only by you. In CI, pipe an existing key to --key-stdin so it never appears in your shell history.

terminal
$ cymatics login               # confirm in the browser
$ cymatics login --live        # a live key instead
$ cymatics login --no-browser  # print the URL instead
$ printf '%s' "$KEY" | cymatics login --key-stdin --profile ci

Profiles and modes

Test mode is the default; --live selects live credentials. The key comes from, in order, CYMO_TEST_API_KEY or CYMO_LIVE_API_KEY, then CYMO_API_KEY, then the selected profile. The API decides a key's project and mode, and the CLI rejects responses from the other mode.

terminal
$ cymatics auth status            # verify the key online
$ cymatics auth status --offline  # which key, no request
$ cymatics projects current       # the key's project
$ cymatics profiles list          # never shows the keys
$ cymatics profiles use staging   # the new default
$ cymatics logout --profile staging

Quickstart

terminal
$ cymatics login
$ cymatics vitals get --room room_bedroom --json
$ cymatics vitals stream --room room_bedroom --max-events 3

Commands

Commands are cymatics <group> <action>, generated from the API contract, so they always match the API reference. Parameters are flags in kebab case, such as --room-id or its alias --room. A single path parameter can be passed positionally. cymatics api runs any operation by its ID.

terminal
$ cymatics rooms get --room room_bedroom --json
$ cymatics results list --room room_bedroom --limit 100 --paginate
$ cymatics webhooks create --input webhook.json \
    --idempotency-key "$(uuidgen)"
$ cymatics api get-room --room room_bedroom --dry-run
cymatics projectget
cymatics capabilitieslist
cymatics modelslist, get
cymatics sensing-profileslist
cymatics retention-profileslist
cymatics coordinate-frameslist
cymatics skeleton-definitionslist
cymatics roomslist, create, get, update, archive, zones get, zones replace, calibration get, calibration replace, processing-config get, processing-config replace, stream
cymatics deviceslist, get, update, health, sensing-config get, sensing-config replace, observations list, raw-stream
cymatics sceneslist, create, get, update, delete
cymatics vitalsget, history
cymatics presenceget
cymatics movementget
cymatics stressget
cymatics skeletonget
cymatics classificationsget
cymatics resultslist
cymatics peopleresults list, stream
cymatics sleepsessions list, summary get
cymatics activitysummary get
cymatics analyseslist, get
cymatics eventslist
cymatics webhookslist, create, get, update, delete, test, deliveries list
cymatics scenarioslist, run
cymatics scenario-runsget, control
cymatics raw-dataexports create, exports get, exports download
cymatics usageget
cymatics request-logslist

Flags and output

Results go to stdout as JSON, the same envelope as the API; streams and listen print one NDJSON line per message. A failure prints one {"error": {"code", "message"}} object on stderr and nothing on stdout.

--test, --livedefault --testChoose the mode's credentials. Both at once is an error.
--profile <name>Use a saved profile instead of the default one.
--jsonPrint the response envelope as JSON on stdout.
--input <file|->The JSON request body, from a file or stdin. Invalid input fails before any request.
--dry-runValidate and print the request, with the key redacted, without sending it.
--paginatePrint every page, one envelope each, until the cursor stops advancing.
--max-events <n>, --duration-ms <ms>Bound a stream or listen; it then exits 0.
--agentBound a stream to ten events or ten seconds, whichever comes first.
--read-onlyRefuse any command that writes. The API still enforces the key's scopes.
--idempotency-key <key>Make a write safe to retry: the API replays the first response.
--timeout-ms <ms>Per-request timeout.
--output-file <path>, --max-bytes <n>Write binary data to a new file, up to a size limit.
--base-url <url>default https://api.cymatics.techThe API origin: HTTPS, or HTTP on loopback.

Streams and downloads

Stream commands run until Ctrl-C unless you bound them with --max-events or --duration-ms. Binary data is only ever written to a new --output-file.

terminal
$ cymatics rooms stream --room room_bedroom --max-events 5
$ cymatics vitals stream --room room_bedroom --duration-ms 10000
$ cymatics raw-data exports download EXPORT_ID \
    --output-file capture.bin --max-bytes 1048576

Local simulator

cymatics mock start runs the real API gateway on 127.0.0.1 with the test simulator, in memory and offline. It has built-in keys: cym_test_local with every scope, cym_test_readonly with results:read, and cym_test_other for a second project.

terminal
$ cymatics mock start --port 8787

# in another terminal
$ export CYMO_API_URL=http://127.0.0.1:8787
$ export CYMO_TEST_API_KEY=cym_test_local
$ cymatics rooms get --room room_bedroom --json

Scenarios

Start a preset or custom timeline with scenarios run, then pause, resume or advance it with scenario-runs control. The run's rooms answer every read and stream. See Scenarios for the presets.

terminal
$ echo '{"scenario_id":"bed_exit","seed":42}' | cymatics scenarios run --input -
$ echo '{"action":"pause"}' | cymatics scenario-runs control RUN_ID --input -
$ echo '{"action":"advance","duration_ms":6000}' \
    | cymatics scenario-runs control RUN_ID --input -
$ cymatics events list --room ROOM_ID --json

Forward webhooks

listen forwards your project's new events to a local URL (localhost, 127.0.0.1 or [::1]) with the production body and headers, signed with a fresh whsec_ secret it prints first. Verify them with verifyWebhookSignature exactly as in production. Deliveries add Cymo-Forwarded: cli, and --events or --webhook-id filter the types.

terminal
$ cymatics listen --forward-to http://localhost:3000/api/cymo-webhook
$ cymatics listen --forward-to http://localhost:3000/hook \
    --events bed.exited,fall.detected
$ cymatics listen --forward-to http://localhost:3000/hook \
    --webhook-id wh_123 --max-events 10

For AI agents

Agents in a terminal can discover everything offline, without a key. agent guide returns the recommended workflow and agent schema every command's JSON Schema with a readOnlyHint. Combine --dry-run, --read-only and --agent to keep an agent's calls safe and bounded. For agents that speak MCP, use the MCP server.

terminal
$ cymatics agent guide                 # workflow, exit codes
$ cymatics agent schema --group rooms  # JSON Schemas
$ cymatics schema get-room             # one operation
$ cymatics rooms stream --room room_bedroom --agent

Exit codes

0Success, including a stream or listen that ended at its bound or on Ctrl-C.
1API or response error, such as resource_not_found, live_unavailable or an invalid response.
2Invalid arguments or input, an unknown command, an invalid origin or configuration file.
3Credentials or permissions: a missing, invalid, expired or revoked key, 401 or 403, a mode mismatch, or a cancelled or failed login.
4Network: connection failure, timeout, 503 service_unavailable or a login timeout.
5429: rate_limit_exceeded after the SDK's automatic retries (with retry_after_ms), or quota_exceeded.

quota_exceeded is never retried: the message says how to raise the key's live-hour cap in the dashboard.

Environment variables

CYMO_TEST_API_KEY, CYMO_LIVE_API_KEYThe key for that mode. Checked first.
CYMO_API_KEYA key for whichever mode is selected, checked after the mode's own variable and before saved profiles.
CYMO_API_URLdefault https://api.cymatics.techThe API origin, like --base-url. Point it at cymatics mock start to work offline.
CYMO_SITE_URLdefault https://cymatics.techThe website that browser login opens, like --site-url.
CYMO_CONFIG_DIRdefault ~/.config/cymaticsWhere profiles.json and config.json live. $XDG_CONFIG_HOME is honoured.
CYMO_NO_UPDATE_CHECK1 to disableSkip the daily background check for a newer release. It never runs in CI or when stdout isn't a terminal.