TwinDocs
Develop with your Twin

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 query

Install 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 login

The 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> --json

Lists 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> --json

The 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 --json

The 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:

client.json
{
  "objectTypeId": "client",
  "title": "Acme Ltd",
  "values": { "name": "Acme Ltd", "status": "active" }
}
twin channels --channel <clients-channel-id> \
  --add-record client.json --allow-write --json

The 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> --json

Then supply only the fields that should change together with that version:

record-patch.json
{
  "expectedVersion": 3,
  "values": { "owner": "Henry", "nextAction": "Schedule review" }
}
twin channels --channel <channel-id> --record <record-id> \
  --update-record record-patch.json --allow-write --json

The 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 --json

MLX 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> --json

Session 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 --json

Revise 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 --json

Both 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" --stream

Natural-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:

NDJSON status stream
{"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:

query.json
{
  "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 --json

Deterministic 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

OptionPurpose
--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
--streamEmit public lifecycle updates as newline-delimited JSON
--jsonReturn 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 logout

This removes the saved credential from the local credential store.

On this page