Use the Twin command line
Inspect recent sessions and channels, read artifacts, and query your Twin from a terminal.
The twin command gives people and coding agents a small, predictable way to
work with the Twin. Its working surface is sessions, channels, query, artifacts, and feedback;
login, logout, status, organisations, and help support those commands.
Install and learn the interface
The CLI is published as a public GitHub Release. Use the installation on Use the Twin CLI, then verify:
command -v twin
twin help
twin help --json
twin help queryInstall it globally for the current user, not with npx or as a project
dependency. command -v twin proves that the command is on PATH and usable
outside a repository. The download does not require a GitHub account or token.
If the install succeeds but that check fails, add npm's user-level global binary
directory to the persistent shell PATH, open a new terminal, and restart Codex
or Claude Code. Do not use sudo or install a similarly named package.
The JSON help is an agent-readable capability manifest. It describes the two query modes, accepted flags, authority boundary, and normal workflow.
Installation does not grant Twin access
The CLI is public, but twin login still maps the signed-in user to their
approved organisation and channels.
Sign in
twin loginThe command opens the protected browser sign-in and stores the resulting credential in macOS Keychain or Linux Secret Service. Customers do not need SSH, Tailscale, an API key, a copied token, or tenant and organisation IDs.
The final browser redirect to http://127.0.0.1:<random-port> is intentional.
It is a temporary loopback callback that returns a one-time OAuth code to the
CLI process on the same computer. Its completion page shows Twin connected
only after the verified credential has been saved. Twin requests continue
through the shared HTTPS gateway; no customer server or Twin data is exposed
on localhost.
Check or switch organisation
twin status --json
twin organisations --json
twin organisations --use <organisation-id-or-exact-name>The CLI lists every mapped organisation available to the signed-in identity and
marks the current one. Selecting another organisation exchanges the existing
session for new organisation-scoped access and refresh credentials, so the user
does not repeat browser login. An agent should state the current organisation,
ask the user before choosing among several, and confirm the switch with twin status --json before listing channels or querying.
Discover and read sessions
twin sessions --limit 50 --json
twin sessions --session <session-id> --jsonLists are ordered by most recent activity first and include title, ID, canonical owner ID, status, and timestamps. A session read contains bounded user and assistant turns, with cursors for older turns. It does not expose internal prompts, tools, events, routing, models, skills, or reasoning.
Discover channels
twin channels
twin channels --json
twin channels --channel <channel-id-or-slug> --json
twin channels --channel <channel-id-or-slug> --views --json
twin channels --channel <channel-id-or-slug> --view <view-id-or-slug> --data --json
twin channels --channel <channel-id-or-slug> --view <view-id-or-slug> --data --widget <widget-id> --jsonThe list contains only channels available to the signed-in user. Inspection
returns each channel's Data Sets and queryable field keys. The JSON and
compatibility flags retain objectType, objectTypeId, and --object-type so
existing clients do not break. Products may
govern the data behind a channel, but they are not part of the CLI contract.
All Table, Board, Calendar, Timeline, Dashboard, and Pivot reads use the same
canonical saved-View endpoint as the web app. Record renderers return bounded
rows; Dashboards return widgets; Pivot tables return canonical cells. Selecting
one Dashboard widget keeps agent output small. Channel and saved-View references
accept either the returned ID or slug. Data Sets and fields use the exact
inspected ID and key. The public response does not include tenant metadata,
dataset IDs, source relations, or other internal plumbing.
Create channel structure
Channel, Data Set, field, and saved-View creation require twin.write and
explicit local approval. Supply each payload as a JSON file, then use the exact
IDs returned by the preceding operation:
twin channels --create-channel channel.json --allow-write --json
twin channels --channel <channel-id-or-slug> \
--create-object-type object-type.json --allow-write --json
twin channels --channel <channel-id-or-slug> \
--object-type <object-type-id> --create-field field.json --allow-write --json
twin channels --channel <channel-id-or-slug> \
--create-view view.json --allow-write --jsonThe compatibility commands still call a Data Set an object type. Creating one uses its supplied name for the initial Table View. Explicit saved-View creation continues to require its own name.
The server validates field types, select options, and complete saved-view definitions before publishing them. See Use the Twin CLI for payload examples.
Follow channel activity
Read the mixed channel feed in bounded pages, inspect a thread, or publish an approved update:
twin channels --channel <channel-id> --feed --limit 10 --json
twin channels --channel <channel-id> --feed --cursor <next-cursor> --limit 10 --json
twin channels --channel <channel-id> --members --json
twin channels --channel <channel-id> --replies <post-id> --json
twin channels --channel <channel-id> --thread session:<session-id> --json
twin channels --channel <channel-id> \
--post "Weekly close is ready for review." --allow-write --json
twin channels --channel <channel-id> \
--thread artifact:<artifact-id> --comment "@Alex Please review." \
--mention-user-ids <user-id> --allow-write --json
twin channels --channel <channel-id> \
--thread session:<session-id> --reaction eyes --allow-write --json--members returns active human members explicitly attached to the channel and
their canonical mention IDs. A shared channel can also be visible to
organisation users who are not explicit members. Threads are flat and may be
attached to posts, sessions, artifacts, or files. Comments can be edited or
deleted by their author; reactions may target the thread root or one comment.
Use eyes as an explicit seen acknowledgement and check for handled—reading
alone does not mark an item seen.
The server assigns the authenticated author and interface attribution. Agents should keep posts and comments complete and concise and must not publish internal reasoning or noisy tool-by-tool progress.
Read and update records and saved Views
Channel writes are separate from normal queries. They require a credential
with twin.write and a second, local confirmation through --allow-write.
Users who authenticated before this scope was introduced should run twin login again.
Inspect the channel before building a record file:
{
"objectTypeId": "client",
"title": "Acme Ltd",
"values": { "name": "Acme Ltd", "status": "active" }
}twin channels --channel <clients-channel-id> \
--add-record client.json --allow-write --jsonThe server validates the Data Set and fields and emits the same canonical record and audit event as the web app. Source-backed Data Sets are read-only through this command; create the row in the source system.
Read an existing record to obtain its current values and version:
twin channels --channel <channel-id> --record <record-id> --jsonThen supply only the fields that should change together with that version:
{
"expectedVersion": 3,
"values": { "owner": "Henry", "nextAction": "Schedule review" }
}twin channels --channel <channel-id> --record <record-id> \
--update-record record-patch.json --allow-write --jsonThe server merges supplied value keys, validates them against the channel
schema, and increments the record version. A stale expectedVersion returns a
conflict. For source-backed rows, only MLX overlay-owned fields can be changed;
source-owned fields remain read-only.
Use --create-view to Save as a new named View without changing the current
one. To Save an existing View, first inspect it and retain its
currentVersionId. Put that value in expectedCurrentVersionId alongside the
complete replacement definition, then run:
twin channels --channel <channel-id> --view <view-id> \
--update-view view-update.json --allow-write --jsonMLX publishes a new view version. A stale expected version fails with a conflict instead of overwriting somebody else's change.
Read, create, or update artifacts
twin artifacts --session <session-id> --json
twin artifacts --artifact <artifact-id>
twin artifacts --artifact <artifact-id> --version <version-id> --jsonSession artifact lists and content reads use the same visibility checks as the app. MLX Markdown and CSV can be streamed; public metadata excludes internal event and tool provenance.
Store approved complete Markdown or CSV without a model query:
twin artifacts --kind markdown --title "Weekly brief" --file brief.md \
--channel <channel-id> --client-app codex --allow-write --jsonRevise it only against the version returned by creation or inspection:
twin artifacts --artifact <artifact-id> --expected-version <version-id> \
--title "Revised weekly brief" --file brief.md \
--client-app codex --allow-write --jsonBoth commands are idempotent. A stale version fails instead of overwriting a
concurrent change. --client-app controls visible client attribution only;
the signed-in user remains the author and authority.
Ask the Twin
twin query --question "What changed this week?"
twin query --channel <channel-id> --question "Summarise the open risks"
twin query --channel <channel-id> --question "Summarise the open risks" --streamNatural-language queries create a private session through the same canonical router as the web app. The command waits for the run and prints the answer plus its query, task, and session IDs.
Stream status to an agent
By default, a session query writes nothing until it reaches a terminal state.
Add --stream when another agent or program needs incremental status:
{"type":"query.submitting"}
{"type":"query.queued","queryId":"query_123","taskId":"task_123","sessionId":"session_123"}
{"type":"query.running","queryId":"query_123","taskId":"task_123","sessionId":"session_123"}
{"type":"query.completed","query":{"id":"query_123","status":"completed","answer":"..."}}The command immediately flushes query.submitting, then one JSON line for each
server status transition. Intermediate events contain only stable identifiers
to keep third-party agent context small. The terminal query.completed event
contains the complete public result, including the answer and permitted channel
links.
Deterministic requests move directly from submitting to completed. A timeout,
abort, or transport failure emits query.error and the command still exits
nonzero.
This is lifecycle streaming over the existing query polling contract. It does not expose model tokens, private reasoning, prompts, SQL, or internal run events.
Run a deterministic query
When an agent already knows the exact result shape, it can supply a typed intent instead of asking the router to interpret prose:
{
"mode": "deterministic",
"channelId": "invoices",
"objectTypeId": "invoice",
"operation": "aggregate",
"groupBy": ["month"],
"metrics": [
{ "alias": "revenue", "function": "sum", "field": "amount" }
],
"filters": [
{ "field": "status", "operator": "eq", "value": "paid" }
],
"orderBy": [{ "field": "month", "direction": "asc" }],
"limit": 24
}twin query --intent-file query.json --jsonDeterministic intents support records, aggregate, and saved view reads.
View reads use channelId, viewId, and optional widgetIds. For record and
aggregate intents, the server checks
channel access, validates every field against the channel schema, injects
organisation scope, and compiles a parameterised query. Raw SQL and internal
relation or product names are not accepted.
Aggregate functions are count_rows, count_distinct, sum, average,
minimum, and maximum. count_rows omits field; all other functions
require an inspected field key. twin help --json contains exact examples,
filter operators, and limits for every deterministic intent.
Failures preserve the server problem detail and validation paths. With
--json, the CLI writes a stable { "error": { ... } } object to standard
error so another agent can explain the failure without reducing it to only
Bad Request.
Submit user feedback
twin feedback \
--query <query-id> \
--description "The answer used the wrong reporting period."Use --description-file <path> for longer feedback. Attaching a routed query
lets the existing session feedback system retain its run provenance and
metrics. General feedback can omit --query.
An agent should submit feedback only when the end user provided or confirmed it. The acknowledgement returns a feedback ID but does not expose internal run context.
Useful query options
| Option | Purpose |
|---|---|
--question-file <path> | Read the natural-language question from a file |
--channel <id-or-slug> | Scope a routed session to one channel |
--view <id-or-slug> | Scope a routed session to one channel view |
--intent-file <path> | Run a typed deterministic records, aggregate, or saved-view query |
--timeout-ms <number> | Change how long the command waits |
--stream | Emit public lifecycle updates as newline-delimited JSON |
--json | Return structured output for another agent or program |
When --stream and --json are both present, the NDJSON stream is the output
contract; the terminal result is not printed a second time.
The CLI records cli as interface metadata. REST and MCP record their own
interface in the same way. This is audit metadata only; it does not select a
different router, grant access, or change the result policy.
On failure or timeout, retain the IDs printed by the CLI. They let an administrator inspect the governed run without exposing tokens, SQL, source rows, or unrelated sessions.
Sign out
twin logoutThis removes the saved credential from the local credential store.