Agentic coding in your terminal — powered by Axon models by MatterAI.
OrbCode CLI is a standalone terminal port of the Orbital extension: the same Axon models, the same native tool schemas, the same MatterAI auth backend — rebuilt from scratch as an interactive TUI with streaming chat, live thinking display, tool activity rows, edit/command approvals, and todo tracking.
- Install
- Updating / relinking
- Usage
- Authentication
- Models
- The TUI
- Slash commands
- Keyboard shortcuts
- Approvals & safety
- Headless mode
- Configuration
- Architecture
- Tools
- Agent loop
- Development
- Tests
- Troubleshooting
Requires Node.js >= 20.
npm install
npm run build
npm link # exposes the global `orbcode` commandThen, from any project directory:
orbcodenpm link creates a symlink to this repo, so after pulling changes you only
need to rebuild — no relink required:
npm run build # the linked `orbcode` command picks this up immediatelyRelink only when the package name or bin entry changes (e.g. the package was
renamed orbitalcode → orbcode):
npm unlink -g orbitalcode # remove a stale link under the old name (once)
npm linkThe version reported by orbcode --version is read from package.json at
runtime — bumping the version there is all that's needed.
orbcode start an interactive session in the current directory
orbcode "<prompt>" start an interactive session with an initial prompt
orbcode login sign in to MatterAI (browser device flow)
orbcode -p "<prompt>" run a single prompt non-interactively, print only the final response
orbcode -p "…" --yolo non-interactive with edits/commands auto-approved
orbcode --model <id> use a specific model for this run (also -m)
orbcode --resume <id> resume a previous session by id (also -r)
orbcode --version print version
orbcode --help show help
The TUI always takes over the full terminal screen on launch (prior shell output stays in scrollback).
The directory you launch from becomes the workspace directory: the default target for file operations and commands, and the file listing the model sees in its environment details.
Browser-based device flow with polling (no copy/paste needed):
orbcode login(or/loginin the TUI) callsPOST /orbcode/auth/starton the MatterAI backend, which issues a one-time 48-hex device code (10-minute lifetime, stored in redis).- The CLI opens
https://app.matterai.so/orbital?loginType=orbcode&devicecode=<code>in your browser:- Already signed in → the webapp shows the Authorize OrbCode CLI dialog immediately.
- Not signed in → you're redirected to sign-in first. The
devicecodequery param is preserved through the OAuth state (Google/Microsoft) and the email/password path, so the authorize dialog appears right after sign-in.
- Clicking Authorize binds your session token to the device code
(
POST /orbcode/auth/authorize). - Meanwhile the CLI polls
GET /orbcode/auth/poll?devicecode=…(every 3s by default, bounded by the code's lifetime). The token is handed out exactly once — the redis key is deleted on first successful poll. - The CLI verifies the token against the profile endpoint and saves it.
Fallbacks & overrides:
- settings.json key: set
apiKeyin~/.orbcode/settings.json(or a project's.orbcode/settings.json) to skip login. The login screen itself is browser-redirect only. - Env token: set
ORBCODE_TOKENto skip login entirely (takes precedence over everything). - Dev endpoints:
ORBCODE_BACKEND_URL(defaulthttps://api.matterai.so) andORBCODE_APP_URL(defaulthttps://app.matterai.so) override where the device flow points — useful against a local backend/webapp. - Tokens are MatterAI JWTs. A token whose payload has
env: "development"automatically routes API calls tohttp://localhost:3000, matching the extension's behavior.
Sign out with /logout (removes the saved token).
The two Axon models are built in; /model opens a scroll-and-select picker
(/model <id> still selects directly). Additional models can be declared via
customModels in settings.json. The choice persists across sessions.
| id | context | max output | pricing |
|---|---|---|---|
axon-code-2-5-pro |
400k | 64k | $2/M in · $6/M out |
axon-code-2-5-mini |
400k | 64k | free |
axon-code-2-5-pro is the default. Both support native JSON tool calls and
image input. Cost comes from the API's usage chunks (is_byok-aware) and is
shown in the status bar.
___ _ ____ _
/ _ \ _ __ | |__ / ___| ___ __| | ___
| | | || '__|| '_ \ | | / _ \ / _` | / _ \
| |_| || | | |_) || |___ | (_) || (_| || __/
\___/ |_| |_.__/ \____| \___/ \__,_| \___|
- Streaming responses rendered as markdown (headers, lists, code fences, inline code, links) via a lightweight ANSI renderer.
- Thinking: reasoning streams live under
✦ Thinking…(last few lines, dimmed) and collapses to✦ Thought for Nswhen done.ctrl+otoggles expanded thinking for subsequent turns. Reasoning arrives from the API asreasoning/reasoning_contentdeltas or inline<think>…</think>blocks — all are routed to the thinking display. - Tool rows: each tool call shows a formatted name ("Read File", "Execute
Command"…), one-line summary (file path, command, query…), live "running"
state, then
✓/✗with a short result preview. - Edit diffs: file-modifying tools render a real diff — stats header ("Added 2 lines, removed 1 line"), line-number gutter, red/green backgrounds — both in the approval prompt (before anything is written) and in the finished tool row.
- Tasks: the model maintains a checklist via
update_todo_list; it renders as a compact Tasks panel (□pending /◧in progress /■done). - @-references: type
@in the input to fuzzy-search workspace files; ↑/↓ to choose, enter/tab inserts the top/selected match into the prompt. - Followup questions:
ask_followup_questionrenders a selectable menu (arrow keys, number quick-pick, or free-text answer). - Completion:
attempt_completionrenders a bordered "✔ Task completed" card with the result. - Status bar: approval mode (
⏵⏵ accept edits on, shift+tab to cycle), busy state, model name, context token usage, and session cost. - Input box: top/bottom rule borders with a
❯prompt, history (↑/↓), cursor movement (←/→, ctrl+a/e), kill line (ctrl+u), multi-char paste (a trailing newline submits), and a slash-command autocomplete menu when the line starts with/. Every menu in the CLI (slash commands, @-files, model picker, session picker, followups) is navigable with ↑/↓ and selectable with enter; a partial command like/mod+ enter runs the highlighted match.
| command | action |
|---|---|
/help |
list commands |
/model |
scrollable model picker (/model pro / /model mini / full id selects directly) |
/clear |
clear the screen only, like the terminal's clear — the conversation and context continue |
/new |
start a fresh conversation/session with a clean slate |
/resume |
pick a previous session for this directory and continue it (screen is cleared, conversation replayed) |
/analytics |
open the MatterAI analytics dashboard (app.matterai.so/orbital) in the browser |
/compact |
summarize the conversation and replace history with the summary |
/tasks |
print the current task list |
/status |
version, model, account, gateway, context usage, cost, approval modes |
/cost |
show session cost and fetch account balance |
/init |
analyze the codebase and create/improve AGENTS.md |
/login |
start the browser sign-in flow |
/logout |
remove the saved token |
/version |
print the CLI version |
/exit |
quit |
| key | action |
|---|---|
Esc |
interrupt the running turn (or cancel login polling / close a menu) |
Ctrl+C |
quit |
Ctrl+O |
toggle thinking display for the whole transcript (past turns included) |
Shift+Tab |
cycle approval mode: ask → accept edits → auto-approve |
↑ / ↓ |
input history, or navigate any open menu |
Ctrl+A / Ctrl+E |
start / end of line |
Ctrl+U |
clear the input line |
Read-only tools (read/list/search/web/todos) run without prompting. Mutating tools prompt first:
- File edits (
file_edit,multi_file_edit,file_write) — prompt shows the target;yallow once,ndeny,aallow for the rest of the session. - Commands (
execute_command) — prompt shows the exact command line. The model classifies commands with anisDangerousflag; dangerous commands (deletes, force-pushes, system changes…) can never be auto-approved — noaoption, and--yolo/session-approval don't apply.
A denial is reported back to the model as "The user denied this operation." so it can adjust course rather than fail.
orbcode -p "explain the build pipeline in this repo"
orbcode -p "fix the lint errors" --yoloPrints only the final content to stdout (the completion result, or the last
assistant message) — no tool activity, no intermediate text. Errors go to
stderr. Without --yolo, edit/command approvals are auto-denied (read-only
analysis). Followup questions are auto-answered with "proceed with best
judgment".
Two kinds of files under ~/.orbcode/:
config.json— state written by the app itself (login token, chosen model, approval defaults). Created on first save, mode 0600.settings.json— user-managed configuration, Claude-Code style. Created automatically as an empty{}on first run so it's easy to find. A project-level.orbcode/settings.jsonin the working directory layers on top of the user-level file.
{
"apiKey": "<token used instead of logging in>",
"baseUrl": "https://my-gateway.example.com/v1",
"model": "my-custom-model",
"autoApproveEdits": false,
"autoApproveSafeCommands": false,
"customModels": [
{
"id": "my-custom-model",
"name": "My Custom Model",
"contextWindow": 128000,
"maxOutputTokens": 32000,
"inputPrice": 0.000001,
"outputPrice": 0.000002
}
],
"env": { "MY_VAR": "value" }
}All keys are optional. customModels entries appear in the /model picker
alongside the built-in Axon models; baseUrl points the chat client at any
OpenAI-compatible gateway; env is applied to the process at startup.
Precedence: env vars > project settings.json > user settings.json >
config.json.
Sessions are stored in ~/.orbcode/sessions/<id>.json and power /resume
and --resume <id>.
| env var | effect |
|---|---|
ORBCODE_TOKEN |
auth token (overrides everything) |
ORBCODE_API_KEY |
same as apiKey in settings.json |
ORBCODE_BASE_URL |
same as baseUrl in settings.json |
ORBCODE_MODEL |
model override (what --model sets internally) |
ORBCODE_CONFIG_DIR |
config directory (default ~/.orbcode) |
ORBCODE_BACKEND_URL |
device-auth backend (default https://api.matterai.so) |
ORBCODE_APP_URL |
webapp for the authorize page (default https://app.matterai.so) |
autoApproveEdits / autoApproveSafeCommands set the session defaults for the
approval prompts (dangerous commands still always prompt); shift+tab cycles
them at runtime.
src/
index.tsx entry: arg parsing, interactive vs -p (headless) mode
branding.ts product name, logo, colors; VERSION read from package.json
headless.ts non-interactive -p runner
config/settings.ts load/save ~/.orbcode/config.json
auth/auth.ts device flow (start/poll), JWT→backend-URL mapping,
profile/balance fetch, token verification
api/
models.ts the two Axon models (ported from the extension registry)
client.ts OpenAI-compatible streaming client → api2.matterai.so/v1/web
stream.ts chunk model: text / reasoning / native_tool_calls / usage
headers.ts X-AxonCode-Version, X-AxonCode-TaskId, X-AXON-REPO, …
prompts/system.ts system prompt: agent roleDefinition + tool guide (ported
verbatim from the extension) + CLI system-info section
tools/
schemas/ native-tools JSON schemas, copied verbatim from the extension
executors/ CLI implementations (fs, child_process, search, web)
index.ts dispatch, approval classification, call summaries
core/
agent.ts the agent loop (see below)
events.ts AgentEvent model consumed by the UI
ui/
App.tsx main Ink app: static finalized rows + dynamic streaming area
LoginView.tsx device-flow login screen with paste fallback
components/ Header, InputBox, rows, ApprovalPrompt, FollowupPrompt,
Spinner, StatusBar
markdown.ts markdown → ANSI renderer
Streaming faithfully ports the extension's handler quirks: cumulative
content dedup (some backends re-send full content), <think> blocks routed to
reasoning, both reasoning and reasoning_content delta fields, tool-call
fragments accumulated by index (id/name in the first delta, argument chunks in
the rest), and cost taken from the final usage chunk.
Requests carry the extension-compatible headers (X-Title,
X-AxonCode-Version, User-Agent: orbcode-cli/<version>, per-task
X-AxonCode-TaskId, and X-AXON-REPO set from the git remote or folder name).
Task titles: after the first turn, the backend-generated task title is
fetched once per task from /axoncode/meta/<taskId> (with retries, like the
extension). It shows in the status bar, is written into the session file (so
/resume lists real titles), and becomes the terminal window title:
<title> (orbcode).
Usage data: /status and /cost fetch /axoncode/profile and show the
plan, usage percentage (used/remaining), remaining reviews, and the credits
reset date — the same data as the extension's profile view.
Active in the CLI (schemas byte-identical to the extension's native-tools):
| tool | executor notes |
|---|---|
read_file |
line-numbered output (LINE|content, 6-char pad), 1000-line cap, offset/limit |
file_edit |
single replacement; unique-match enforcement; replace_all; empty old_string = whole file |
multi_file_edit |
batched edits grouped per file, per-edit OK/FAILED results |
file_write |
creates parent dirs, full-content writes |
list_files |
optional recursive, ignores node_modules/.git/build dirs, 800-entry cap |
search_files |
JS regex search with glob file_pattern (picomatch), 300-match cap, binary skip |
execute_command |
user's shell, 120s timeout, 30k output cap, optional cwd |
web_search / web_fetch |
proxied through the MatterAI backend with your token |
update_todo_list |
drives the TUI todo panel |
ask_followup_question |
interactive menu in the TUI |
attempt_completion |
ends the turn with a completion card |
Present in tools/schemas/ but inactive (need IDE services): codebase_search,
lsp, list_code_definition_names, use_skill, check_past_chat_memories,
browser_action, generate_image, new_task, switch_mode,
fetch_instructions, run_slash_command.
Per user message (core/agent.ts):
- The first message is prefixed with
<environment_details>(workspace file listing, git branch/status, time); every user message is wrapped in<user_query>tags, matching the extension's prompt contract. - Stream a completion (system prompt + history + tool schemas, temperature 0.2). Text/reasoning deltas are forwarded to the UI as they arrive; tool-call fragments are accumulated.
- If the model made tool calls: each one is summarized, approval is requested
when required, the executor runs, and the result is appended as a
role: "tool"message.ask_followup_questionblocks on the user's answer;attempt_completionends the turn. - Repeat (max 50 steps per turn) until a plain text response or completion.
Esc aborts the in-flight request via AbortController; the interruption is
recorded in the conversation as a <system_reminder> so the model knows.
npm run dev # run from source (tsx)
npm run build # compile to dist/
npm run typecheck # tsc --noEmitSource-of-truth rule: behavior is ported from the Orbital extension repo
(tool schemas under src/core/prompts/tools/native-tools, prompts in
src/core/prompts/, models in src/api/providers/kilocode-models.ts) — keep
schemas byte-identical rather than editing them here.
Backend/web pieces of the device-auth flow live in:
gravity-console-backend→src/controller/orbcodeAuthController.ts(+ OAuth state inrouter.ts, callbacks inauthController.ts)gravity-console-webapp→ authorize dialog insrc/App.js, sign-in q-p preservation insrc/layouts/authentication/sign-up/index.js
node test-ui.mjs # in-process TUI test with a fake TTY
node test-device-auth.mjs # device-auth polling flow against a local mocktest-ui.mjsdrives the real App (ink-testing-library technique): header, slash menu,/help,/modelswitching, message submission, and a live round-trip to the API gateway — the bundled fake token yields a clean 401 error row. Self-contained: writes its own config fixture to/tmp/orbcode-test-config.test-device-auth.mjsspins up a local HTTP mock of the backend endpoints and verifies: code issuance, pending polls, authorization, one-time token pickup, and expiry semantics.
orbcode: command not found— runnpm linkin this repo; checknpm prefix -g's bin dir is on your PATH.--versionshows an old version — rebuild (npm run build); the linked command runsdist/, and the version is read frompackage.jsonat runtime.- Stale link after the rename —
npm unlink -g orbitalcode && npm link. - Login times out — the device code lives 10 minutes; press Enter to retry,
or paste a token manually. Against a local stack, set
ORBCODE_BACKEND_URLandORBCODE_APP_URL. - 401 on chat — token expired:
/logoutthen/login. - Keyboard input does nothing — OrbCode needs a real TTY (raw mode); it
won't accept piped stdin. Use
-pfor non-interactive runs.