CLI
Manage your 2kw.ai schemas, prompts, extractions, and more — straight from the terminal.
Overview
The 2kw.ai CLI gives you full access to the platform without leaving your terminal. Define schemas, run extractions, convert documents, chat with AI models, and manage your entire workflow from the command line.
The package installs one program under three names: 2kw, backbone and bb. They are the same command, so bb schemas list and backbone schemas list do what 2kw schemas list does. The examples on this page use 2kw, the name the CLI itself prints in its hints.
Installation
Install the CLI globally via npm:
# Latest stable release
npm install -g @2kw/ai
# Pre-release (dev channel)
npm install -g @2kw/ai@dev
The CLI will notify you when a newer version is available.
Verify the installation:
2kw --version
Updating
Update to the latest version:
npm update -g @2kw/ai
The CLI automatically checks for newer versions and will notify you when an update is available.
Authentication
Sign in through the browser
2kw auth login
This prints a code, opens your browser and signs you in as a member of your organization. Credentials are stored locally in ~/.config/backbone/config.json, and each command mints a short-lived organization token from them.
Or use an API key
2kw auth login --api-key sk_...
Create the key first on the API Keys page in the sidebar. A key is what CI and other unattended runs want; --manual prompts for one instead of taking it on the command line.
Verify
2kw auth status
This validates your credentials against the API and shows available models.
You can also authenticate via environment variables or CLI flags:
| Method | Example |
|---|---|
| Environment variables | AI_2KW_API_KEY=sk_... AI_2KW_BASE_URL=https://... |
| CLI flags | --api-key sk_... --base-url https://... |
| Local config file | .backbone JSON file in the current directory |
Either credential drives almost every command. The exception is connectors public-hosts: approving a host your agents may connect to is a person's decision, so those three commands take the browser sign-in only and refuse an API key whatever role it carries.
Commands
Global Options
| Flag | Description |
|---|---|
--api-key <key> | Override the API key |
--base-url <url> | Override the base URL |
--json | Output raw JSON instead of formatted tables |
--no-color | Disable colored output |
Schemas
Define and manage extraction schemas within your organization.
2kw schemas list
2kw schemas get <schemaId>
2kw schemas create -n "Invoice Schema"
2kw schemas update <schemaId> -n "Updated Name"
2kw schemas delete <schemaId>
Schema Versions
2kw schemas versions list --schema <schemaId>
2kw schemas versions create --schema <schemaId> -c '{"type":"object","properties":{}}'
2kw schemas versions get <versionId> --schema <schemaId>
2kw schemas versions activate <versionId> --schema <schemaId>
Schema Labels
2kw schemas labels list --schema <schemaId>
2kw schemas labels create --schema <schemaId> -n "production" --version-id <versionId>
2kw schemas labels update "production" --schema <schemaId> --version-id <versionId>
2kw schemas labels delete "production" --schema <schemaId>
Schema Testing
# Validate a JSON Schema definition
2kw schemas validate <schemaId> -c '{"type":"object"}'
# Test extraction against sample text
2kw schemas test <schemaId> -t "John Doe, john@example.com" -m gpt-5.1
# Resolve active or labeled schema
2kw schemas resolve <schemaId>
2kw schemas resolve <schemaId> -l production
Prompts
Version-controlled prompt templates with variables and labels.
2kw prompts list
2kw prompts get <promptId>
2kw prompts create -n "My Prompt"
2kw prompts update <promptId>
2kw prompts delete <promptId>
# Compile template with variables
2kw prompts compile <promptId> --vars '{"name":"World"}'
# Test prompt against a model
2kw prompts test <promptId> -m gpt-5.1
Prompts also support versions and labels sub-commands with the same syntax as schemas.
Extractions
Extract structured data from text, files, or images.
# Extract from text
2kw extractions create --schema <schemaId> -m gpt-5.1 --text "John Doe, john@example.com"
# Extract from a file
2kw extractions create --schema <schemaId> -m gpt-5.1 --file ./invoice.pdf
# Extract from images
2kw extractions create --schema <schemaId> -m gpt-5.1 --images ./page1.png ./page2.png
# Async extraction
2kw extractions create --schema <schemaId> -m gpt-5.1 --text "..." --async
# List and manage
2kw extractions list --status COMPLETED
2kw extractions get <extractionId>
2kw extractions rerun <extractionId>
# Estimate token usage
2kw extractions estimate --schema <schemaId> -m gpt-5.1 --text "..."
Document Conversion
Convert documents (PDF, DOCX, images, etc.) to Markdown, text, HTML, or JSON.
# Convert local files
2kw convert file ./document.pdf --format md
2kw convert file ./scan.png --pipeline vlm --format md
# Convert from URL
2kw convert url https://example.com/doc.pdf --format text
# Async conversion
2kw convert file ./large.pdf --format md --async
2kw convert status <taskId>
2kw convert status <taskId> --wait # Block until done
2kw convert result <taskId>
AI Gateway
Chat with AI models, run agents through the Responses endpoint, create embeddings, and list available providers.
# Chat completion
2kw ai chat -m gpt-5.1 --message "Explain recursion in one sentence"
2kw ai chat -m gpt-5.1 --message "Hello" --system "You are a pirate" --temperature 0.9
# Responses endpoint — direct model call or a stored agent
2kw ai respond "Summarize this ticket" --model gpt-5.1
2kw ai respond "Summarize this ticket" --agent support-bot --conversation conv_123
echo "Summarize this ticket" | 2kw ai respond --agent support-bot
# With --agent, --model picks another model from the agent's list for this request
2kw ai respond "Summarize this ticket" --agent support-bot@prod --model gpt-4.1-mini
# List available models
2kw ai models
Agents
Run agents and manage them from a file. Commands to list, create, version and label agents also exist (2kw agents --help).
# Run an agent on a task (name or id, optionally @label)
2kw agents run invoice-checker "Check invoice INV-204"
2kw agents run invoice-checker@prod --input-file task.txt --json
2kw agents run invoice-checker "And the next one?" --conversation conv_123
2kw agents run invoice-checker@prod "Check invoice INV-204" --model openai/gpt-4o
2kw agents run invoice-checker@prod "Check invoice INV-204" --mode plan # look, don't change anything
# See which agents are published, without their configuration
2kw agents catalog --search invoice
# See which skills an agent's latest published version binds
2kw agents skills --agent <agent-id>
# Create an agent or a version with an ordered model list; the first model is the default
2kw agents create --name invoice-checker --models azure-openai/gpt-4.1,openai/gpt-4o
2kw agents versions create --agent <agentId> -m azure-openai/gpt-4.1 -m openai/gpt-4o
# Resume a run that paused for approval: decide every pending approval in one call,
# naming the agent as the run did, with the same @label and #model
2kw agents decide invoice-checker@prod --response resp_91 --approve-all
2kw agents decide invoice-checker@prod --response resp_91 --reject-all --reason "wrong PO"
2kw agents decide invoice-checker@prod --response resp_91 --approve apreq_1 --reject apreq_2
2kw agents decide invoice-checker@prod --response resp_91 --approve-all --mode ask # leave plan mode and approve
# Answer a run that paused on a client tool call (exit code 4): run the tool yourself,
# then send its result, or say it failed, together with any pending approvals
2kw agents decide invoice-checker@prod --response resp_92 --output c9=@result.json
2kw agents decide invoice-checker@prod --response resp_92 --fail c9='not available in this client'
# What the policy gate would do to each tool call, without running the agent
2kw agents policy invoice-checker
2kw agents policy invoice-checker --version <versionId> --tool create_note --installation <installationId>
2kw agents policy invoice-checker --mode plan # as a conversation in plan mode would be gated
# Configure from agent.yaml
2kw agents init invoice-checker -o agent.yaml
2kw agents schema > agent.schema.json
2kw agents apply -f agent.yaml --dry-run
2kw agents apply -f agent.yaml --label prod
2kw agents export invoice-checker@prod -o prod.yaml
agents list returns the full configuration and needs VIEWER or above. agents catalog is the chat-facing read: published agents only, as id, name and description, with no instructions, tools, options, HITL policy or model list. agents skills lists the skills an agent's latest published version binds, as name, description, version, ref and source plugin; an agent with no published version lists none. These two are the agent commands a chat-only USER key may run.
An agent carries an ordered model list. agents create, agents update and agents versions create take it as --models a,b or as a repeated --model; a single --model still sets one model. agents list and agents versions list show the models column. run --model <model> (or ai respond --agent … --model <model>) runs one request on another model from the list by sending <agent>#<model>; the server refuses a model that is not on the list.
decide continues on the label and model it is given, so pass the same @label and #model the run used (invoice-checker@prod#openai/gpt-4o after run invoice-checker@prod --model openai/gpt-4o); the run's next command already carries both. Without #model the continuation runs on the default model. --reason is recorded on every decision in the call, approvals included.
--mode plan|ask|auto on run and decide sets the conversation mode: plan is read-only, ask makes every call that needs approval wait for you, auto is the operator's policy as written. The mode is stored on the conversation. Without --mode nothing is sent and the conversation keeps its mode. An interactive run sends its --mode with every approval round. Any other value is a usage error (exit code 2).
agents policy shows the effective tool policy of a version (the latest unless --version names one): for each tool the verdict, the composed action, the policy class, where the class came from and the matched rules. --tool answers for one tool, --installation resolves that installation's module tool catalog, and --mode plan|ask|auto answers as a conversation in that mode would be gated; a mode that changed the answer shows as conversation_mode.<mode> in the matched rules (see Conversation Mode). An invalid --mode is a usage error (exit code 2) and sends nothing.
run asks for each approval and continues only when stdin and stderr are both terminals and none of --json, --raw or --no-input is set. Otherwise it stops at the first pause; --json prints the result as a run envelope:
| Field | Meaning |
|---|---|
status | completed, requires_approval, requires_tool_output or incomplete |
mode | The conversation mode the run ran under: plan, ask, auto, or null when none is set |
agent, version | The agent and version that ran |
text | The agent's answer so far |
responseId, conversationId | Pass to decide --response and run --conversation |
toolCalls | Tools that ran, each with its status |
pendingApprovals | Calls waiting for a decision: approvalId, tool, arguments, policyClass, reason, the approver's reason when a request was escalated (null otherwise), and preview, an object or null: set only for the built-in SkillsApply, with the same shape as the preview of a backbone:approval_request (see Editing Skills in Chat) |
pendingToolCalls | Calls only a client application can answer. A connector's mcp__<label>__connect call is never listed here |
pendingConnections | Connectors the user has to act on in the chat app before the run can use them: serverLabel, host, reason (connect, allow or reconnect) and, when an allow is asked again, destinations |
pendingInputs | Questions a connector asked the user, answered in the chat app only: inputRequestId, serverLabel, tool, mode (form, url, mixed or unknown) and answerUrl, the conversation to answer in. Never the question itself |
incompleteReason | Why an incomplete run stopped |
next | When a connector asked the user a question, only the chat link to answer it in, whatever else waits; otherwise a ready decide command, with the run's @label, when the run paused for approval; for a client tool call, a decide command with one --output <callId>=@<file> per call (plus --approve-all when approvals wait too) and, when a connector pause is open as well, what to connect first; for a connector pause alone, what to connect where and the agents run … --continue command to run afterwards; for a pause the CLI cannot decode, where to answer it |
| Exit code | Meaning |
|---|---|
0 | Completed |
1 | API or network error, or an unknown or missing flag |
2 | Invalid usage or invalid agent.yaml |
3 | Paused for approval |
4 | Paused for client tool output, for a connector the user has to connect in the chat app, or for a connector's question the user answers in the chat app |
5 | Incomplete, for example at the tool-iteration limit |
An agent tool with no executor on the platform (any tool in agent.yaml that is not one of the built-in or function types) is answered by the caller: the run pauses with status requires_tool_output and exit code 4, and lists each call under pendingToolCalls with its tool, callId and arguments. The CLI never runs the tool. You run it, then answer the pause with decide: --output <callId>=<value> sends the result, --fail <callId>=<message> reports that the tool failed or cannot run here, and the platform marks that tool call as an error. Both flags repeat, one per call. The value is literal text; @<path> reads a file and @- reads stdin (once per call), and either is sent verbatim, never parsed as JSON or re-encoded. Literal text that starts with @ has to go through a file. Only the file you name is read, and its whole content is sent to the agent and stored with the run. Every released call and every pending approval of the response must be answered in the same decide: approvals are decided with the usual flags beside --output, and a call that leaves one open exits with code 2 before anything is sent. Answer within one hour of the pause; a later result is dropped and the agent is told the tool timed out. The CLI does not ask for tool output at a terminal: it prints each call and the command to run, for example:
Paused: the agent waits for client-side tool output:
- open_dialog (c9)
{"id":1}
Answer with:
2kw agents decide invoice-checker@prod --response resp_92 --output 'c9=@c9.out'
Write the result to the file the command names, or change the path, and run it. With --json the same command is in next.
A run that needs a connector that requires a sign-in the key's owner has not connected, or has not allowed this agent to use, pauses with status requires_tool_output and exit code 4, like a client tool call, and lists the connector under pendingConnections. The CLI cannot sign in for anyone. It prints what to do and the command that continues the run, for example:
Connect erp (erp.example.com) in https://chat.2kw.ai/connectors, then run:
2kw agents run invoice-checker@prod --continue resp_…
With --json, next holds the same on one line. Connect or allow on that page, then run the printed command as it stands. It names the agent with the run's @label and #model, and --continue sends the response id as previous_response_id. --continue needs no input: without an argument, --input-file or piped stdin it continues with an empty turn, and the platform checks the connection again on that turn. Input you do give is sent as a new user message. Do not continue it with --conversation alone; the platform refuses that for a connector pause. When the same response also waits for a client tool call, next names the decide command for that call instead of agents run --continue: connect first, then answer the call, and the platform re-checks the connection on that continuation.
A connector can ask the user a question in the middle of a call: pick one of two matching customers, fill in a missing field, or confirm a step on the service's own page. The run pauses with status requires_tool_output and exit code 4 and lists each question under pendingInputs. Only the user who started the run can answer, and only in the chat app; the CLI never shows the question and has no command to answer it. It prints the connectors that ask and the link, for example:
Paused: the agent needs your answer in chat:
- erp: create_invoice (form)
erp needs your input: answer in chat at https://chat.2kw.ai/c/conv_…. The run continues there.
Open the link signed in as that user; the run continues in the chat app once they answer. A question outranks every other pause: when the same response also waits for an approval or a connection, next still names the chat link only, and the answer there resolves the rest.
A run can also pause with requires_tool_output on something the CLI does not decode, for example a connector call waiting for approval (mcp_approval_request). The CLI prints Paused on something this CLI cannot show:, and next names the item types it cannot answer and says the response stays paused: answer it in the chat app, or continue it by its id from a client that sends previous_response_id.
The chat app's address comes from the API base URL: https://api.2kw.ai points to https://chat.2kw.ai, and https://api-dev.2kw.ai to https://chat-dev.2kw.ai. A base URL the CLI does not know points to https://chat.2kw.ai. Set AI_2KW_CHAT_URL to the chat app's origin when yours is elsewhere.
agent.yaml maps onto an agent version: name, description, model or models, instructions, tools, hitlPolicy, options, skills. models is the ordered list, first entry the default; give model alone for one model, and when both are present model must equal the first entry. apply finds the agent by name, creates it if missing, and publishes a new version only when the configuration changed. It refuses --label latest, which moves on its own. ${NAME} placeholders, where NAME is upper-case letters, digits and _, are read from the environment at apply time in any value; a missing variable stops apply with exit code 2. Write $${ for a literal ${. Values resolved into a secret field, or from a variable whose name contains SECRET, TOKEN, PASSWORD, PASSWD, PASS, API_KEY, KEY, PRIVATE, CREDENTIAL or AUTH, are masked in the output.
export writes YAML, or JSON with --json. Webhook secrets become placeholders named after the tool, such as ${AGENT_CRM_SYNC_SECRET} for a tool crm-sync, unless you pass --include-secrets, and tool entries that have no effect at run time are dropped with a note. The schema is published at docs.2kw.ai/schemas/agent.v1.json.
Transcription
Transcribe audio files to text.
2kw transcribe ./meeting.mp3 -m openai/whisper-1
2kw transcribe ./audio.wav -m openai/whisper-1 --language en --format srt
Supported formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, webm.
Providers
Manage BYOK (Bring Your Own Key) AI providers. Provider types, their settings and the API are on Providers & BYOK.
2kw providers list
2kw providers get <id>
2kw providers create -n "My OpenAI" --type openai --provider-api-key sk-...
2kw providers update <id> --name "Production OpenAI"
2kw providers delete <id>
2kw providers models # List all models across providers
Connectors
Manage the public MCP hosts your organization's agents may reach. See Connectors (MCP servers) for what a connector is.
2kw connectors public-hosts list
2kw connectors public-hosts add mcp.example.com
2kw connectors public-hosts add mcp.example.com --port 8443
2kw connectors public-hosts remove <id>
These three need the browser sign-in from Authentication; an API key is refused. The member, admin and owner roles may list the hosts, which the console's agent editor offers when an author adds a connector; add and remove need an admin or owner role. Approving a host approves every path and every account on it, and a withdrawal takes effect on the next request.
Installations
Register and manage the deployments of your application that embed an agent. See Embeddable Surface for what an installation is and how pairing works.
# List and read
2kw installations list --status ACTIVE --agent-id <agentId>
2kw installations get <id>
# Register an installation bound to one agent; prints the first pairing string
2kw installations create -n "ERP production" --agent-id <agentId> -d "Main ERP instance"
# Rename, describe, or switch it off
2kw installations update <id> -n "ERP prod" --status DISABLED
# A fresh pairing code, valid ten minutes, keeping the registered keys
2kw installations pairing-code <id>
# Replace the whole list of origins allowed to frame the panel
2kw installations origins replace <id> --origins https://erp.example.com https://intranet.example.com
# Verification keys
2kw installations keys list --installation <id>
2kw installations keys create --installation <id> --kid key-2026-10 --public-key <base64>
2kw installations keys delete key-2026-09 --installation <id>
# The public embed configuration the panel reads
2kw installations embed-config <id> --agent <agentId>
list, get and keys list need the viewer role or above; every command that changes an installation needs an admin or owner role. embed-config reads the unauthenticated endpoint the panel itself calls. --status takes PENDING, ACTIVE or DISABLED. There is no delete: update --status DISABLED switches an installation off and voids an outstanding pairing code. update changes only the fields you pass; the rest keep their current values. origins replace replaces the list, so pass every origin you want to keep. It needs at least one origin; to revoke every origin, send an empty list to PUT /v1/installations/{id}/origins. Rotate keys in the order the Origins and keys section gives: create the successor, switch signing, then delete the predecessor.
Relays
Manage MCP relays: containers you run inside your own network that dial out to 2kw.ai so agents can reach MCP servers there. Every relay command needs an admin or owner role.
# Create a relay; prints its id, a single-use enrolment token and a docker run snippet
2kw relays create --name "Office Berlin"
# List relays with their status and whether they are online; show one with its inventory
2kw relays list
2kw relays get <id>
2kw relays rename <id> --name "Office Munich"
2kw relays disable <id>
2kw relays enable <id>
# A new enrolment token; redeeming it replaces the relay's key
2kw relays re-enrol <id>
# Revoke a relay for good
2kw relays delete <id>
The enrolment token is shown once and is valid for 24 hours. create is the only command that prints a new relay's id; keep it, every other command takes it. Run the printed docker run command on a host inside your network, with the relay.yaml it names beside it. disable stops the relay from getting new work and voids a token that has not been redeemed; enable brings it back. re-enrol works on a pending or active relay, not on a disabled one. delete cannot be undone. A deployment that has not enabled relays answers create and re-enrol with 404 and MCP relays are not enabled on this installation.
Not usable by connectors yet
A relay can be created, enrolled and monitored, but an agent's connectors cannot reach an MCP server through it yet; see Not in this release.
Analytics
View organization usage analytics.
2kw analytics summary
2kw analytics time-series --group-by week --start 2025-01-01 --end 2025-12-31
2kw analytics schemas --limit 10
2kw analytics providers
2kw analytics errors
2kw analytics status
API Documentation
Browse the backend's OpenAPI documentation by section.
2kw docs sections # List available doc sections
2kw docs get <section> # Fetch docs for a specific section
Billing
Check subscription tier and usage limits.
2kw billing tier # Current tier
2kw billing tiers # All available tiers
2kw billing limits # Detailed usage and limits
2kw billing check schema # Can I create another schema?
Configuration
The CLI resolves configuration in this order (highest priority first):
- CLI flags —
--api-key,--base-url - Environment variables —
AI_2KW_API_KEY,AI_2KW_BASE_URL(legacy:BACKBONE_*) - Local
.backbonefile — JSON file in the current working directory - Active context — stored in
~/.config/backbone/config.json
AI_2KW_CHAT_URL is read from the environment only. It sets the chat app's origin that a connector pause points to (see Agents); without it, the CLI derives the origin from the base URL.
Manage the config store directly:
2kw config set <key> <value>
2kw config get <key>
2kw config list
Contexts
For users working across multiple organizations or environments, the CLI supports named contexts. Each context stores its own base URL and API key — making it easy to switch between organizations (e.g. different clients or teams) or environments (production, staging, local dev).
# Create a new context
2kw context create acme-corp --base-url https://api.2kw.ai --api-key sk_acme_...
# List all contexts (* marks the active one)
2kw context list
# Switch to a different context
2kw context use acme-corp
# Show the active context
2kw context current
# Rename or delete a context
2kw context rename acme-corp acme
2kw context delete acme
Default behavior
If you only use one organization, you never need to think about contexts. 2kw auth login stores credentials in a default context automatically.
What's Next?
Check out the MCP Server to give AI coding assistants direct access to 2kw.ai, or learn about API Keys to manage your authentication tokens.