@robota-sdk/agent-cli 3.0.0-beta.83 → 3.0.0-beta.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +229 -1180
  3. package/dist/node/bin.js +22 -22
  4. package/dist/node/bounded-output-Bgx2Cgkx-28em7iHq.js +2 -0
  5. package/dist/node/bounded-output-Bgx2Cgkx-28em7iHq.js.map +1 -0
  6. package/dist/node/bounded-output-Bgx2Cgkx-C4ucXwLR.js +2 -0
  7. package/dist/node/cli-core-DjVnz1-_.js +2891 -0
  8. package/dist/node/command-executor-d6DdfUTD-3KEnsTnN.js +2 -0
  9. package/dist/node/command-executor-d6DdfUTD-DhFSDHvb.js +2 -0
  10. package/dist/node/{command-executor-DV-H2oD6-OKy8pwYH.js.map → command-executor-d6DdfUTD-DhFSDHvb.js.map} +1 -1
  11. package/dist/node/headless.js +2 -2
  12. package/dist/node/{http-executor-cWzI2svn-TG_TMYKh.js → http-executor-ADn4locH-D-8crzF8.js} +2 -2
  13. package/dist/node/{http-executor-cWzI2svn-TG_TMYKh.js.map → http-executor-ADn4locH-D-8crzF8.js.map} +1 -1
  14. package/dist/node/{http-executor-cWzI2svn-Ype0naht.js → http-executor-ADn4locH-uz5KV6N-.js} +1 -1
  15. package/dist/node/index.d.ts +263 -100
  16. package/dist/node/index.d.ts.map +1 -1
  17. package/dist/node/index.js +388 -346
  18. package/dist/node/index.js.map +1 -1
  19. package/dist/node/node-B48couEE.js +14 -0
  20. package/dist/node/node-B48couEE.js.map +1 -0
  21. package/dist/node/node-CgLYctGm.js +67 -0
  22. package/dist/node/node-CgLYctGm.js.map +1 -0
  23. package/dist/node/{node-B8BhZ3NW.js → node-CpH9vRQt.js} +1 -1
  24. package/dist/node/node-DAOCaE6N.js +67 -0
  25. package/dist/node/node-DlLm_kxn.js +14 -0
  26. package/dist/node/{verdict-decoder-Dk_w2NG9-Bka7Cldj.js → verdict-decoder-Dz_rjoIZ-DA8BKB83.js} +2 -2
  27. package/dist/node/{verdict-decoder-Dk_w2NG9-Bka7Cldj.js.map → verdict-decoder-Dz_rjoIZ-DA8BKB83.js.map} +1 -1
  28. package/dist/node/{verdict-decoder-Dk_w2NG9-BKWMtxp8.js → verdict-decoder-Dz_rjoIZ-qM0jJVsB.js} +1 -1
  29. package/dist/web/assets/index-DSLs7oij.js +309 -0
  30. package/dist/web/assets/index-OqCBtMJ2.css +1 -0
  31. package/dist/web/index.html +2 -2
  32. package/package.json +37 -36
  33. package/dist/node/bounded-output-DpjpS98g-DFgAppgl.js +0 -2
  34. package/dist/node/bounded-output-DpjpS98g-DFgAppgl.js.map +0 -1
  35. package/dist/node/bounded-output-DpjpS98g-DoRuRGhV.js +0 -2
  36. package/dist/node/cli-core-CfQhvbx3.js +0 -2850
  37. package/dist/node/command-executor-DV-H2oD6-BfKoGoFw.js +0 -2
  38. package/dist/node/command-executor-DV-H2oD6-OKy8pwYH.js +0 -2
  39. package/dist/node/node-CC5tfdMu.js +0 -67
  40. package/dist/node/node-CC5tfdMu.js.map +0 -1
  41. package/dist/node/node-CsbnL0Yf.js +0 -67
  42. package/dist/node/node-DtKU0MUr.js +0 -14
  43. package/dist/node/node-DtKU0MUr.js.map +0 -1
  44. package/dist/node/node-j7KM8r3b.js +0 -14
  45. package/dist/web/assets/index-DbAewVPM.js +0 -244
  46. package/dist/web/assets/index-DzQQd7ql.css +0 -1
package/README.md CHANGED
@@ -1,1295 +1,344 @@
1
1
  **Language:** [English](README.md) | [한국어](docs/README-KO.md)
2
2
 
3
- > **Beta software** — currently `3.0.0-beta`. APIs and behavior may change before stable release.
4
- > Please [report issues](https://github.com/woojubb/robota/issues) to help us improve.
5
-
6
3
  # @robota-sdk/agent-cli
7
4
 
8
- AI coding assistant CLI built on Robota SDK. Loads AGENTS.md/CLAUDE.md for project context and provides a tool-calling REPL with Claude Code-compatible permission modes.
9
-
10
- ## Why Robota?
11
-
12
- | | Robota | Claude Code | Aider |
13
- | ------------------------------------------------- | :----: | :---------: | :---: |
14
- | Multi-provider (Anthropic, OpenAI, Gemini, Qwen…) | ✅ | ❌ | ✅ |
15
- | Embed SDK in your own app | ✅ | ❌ | ❌ |
16
- | Local models (LM Studio, Ollama via OpenAI API) | ✅ | ❌ | ✅ |
17
- | Open source (AGPL-3.0) | ✅ | partial | ✅ |
18
- | Claude Code config compatible (CLAUDE.md, modes) | ✅ | — | ❌ |
19
-
20
- ## Embed in Your App
21
-
22
- ```typescript
23
- import { createAgentRuntime } from '@robota-sdk/agent-framework';
24
- import { createAnthropicProvider } from '@robota-sdk/agent-provider-anthropic';
25
-
26
- const runtime = createAgentRuntime({
27
- cwd: process.cwd(),
28
- provider: createAnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY }),
29
- // projectAccess: hostWorkspaceDecision,
30
- });
31
- const session = runtime.createSession({ permissionMode: 'bypassPermissions' });
32
-
33
- // submit() is event-driven and resolves to void — drive output via listeners
34
- session.on('text_delta', (delta) => process.stdout.write(delta));
35
- session.on('complete', (result) => console.log(result.response));
5
+ `robota` is an AI coding assistant for the terminal, and the reference app of Robota — a collection
6
+ of TypeScript libraries for building AI agents. It reads your project, edits files and runs commands
7
+ under a permission system you control, and works with Anthropic, OpenAI, Gemini, DeepSeek, Qwen and
8
+ local OpenAI-compatible models.
36
9
 
37
- await session.submit('Explain this codebase');
38
- ```
10
+ The CLI is assembled from the same packages you can use in your own app: `@robota-sdk/agent-framework`
11
+ for the session, `@robota-sdk/agent-ui-terminal` for the terminal UI, and one package per model
12
+ provider. To build your own agent rather than use this one, start with the
13
+ [SDK guide](../../content/guide/sdk.md).
39
14
 
40
- A bare `cwd` is provenance, not project authority. Without a host-issued
41
- `TWorkspaceProjectAccess` decision the runtime is observably Restricted and does not load project
42
- context, settings, memory, sessions, or logs. Embedders establish that decision through the
43
- framework `WorkspaceTrustService`; a path, boolean, or generic filesystem cannot substitute for it.
44
- Trusted composition is rejected when `cwd` resolves outside that authority's frozen workspace root.
15
+ > **Beta.** Behavior may change before the stable release. Please
16
+ > [report issues](https://github.com/woojubb/robota/issues).
45
17
 
46
- ## Prerequisites
18
+ ![robota reading a project file and explaining its entry point in the terminal](./docs/demo.gif)
47
19
 
48
- Node.js **22 or higher** is required. The TUI renderer ([ink 7.x](https://github.com/vadimdemedes/ink)) requires Node.js 22+.
20
+ ## Install
49
21
 
50
- ```bash
51
- node --version # Must output v22.x.x or higher
52
- ```
53
-
54
- If your version is below 22, upgrade using one of:
22
+ Requires Node.js 22.12.0 or later.
55
23
 
56
24
  ```bash
57
- # nvm
58
- nvm install 22 && nvm use 22
59
-
60
- # Volta
61
- volta install node@22
25
+ npm install -g @robota-sdk/agent-cli # installs the `robota` command
26
+ npx @robota-sdk/agent-cli # or run it once without installing
62
27
  ```
63
28
 
64
- ## Demo
65
-
66
- ![robota reading a project file and explaining its entry point in the terminal](./docs/demo.gif)
29
+ On macOS, Korean and other CJK input methods can crash inside Terminal.app; use a terminal such as
30
+ [iTerm2](https://iterm2.com/) instead.
67
31
 
68
- Recorded from the real CLI with `pnpm --filter @robota-sdk/agent-cli demo:record`. The tools run for
69
- real; the model turns are replayed from a recorded session log so the recording needs no API key —
70
- see [docs/DEMO-SCRIPT.md](./docs/DEMO-SCRIPT.md).
32
+ ## First run
71
33
 
72
- ## Installation
34
+ Run `robota` inside a Git repository. It first asks whether to trust the folder: only a trusted
35
+ workspace can load the project's own settings, hooks, skills, plugins and MCP servers. Answer no and
36
+ the session starts **Restricted**, with your user settings and the built-in tools only. The first
37
+ time, it then walks you through choosing a provider and filling in its fields (model, base URL, API
38
+ key), and saves the profile to `~/.robota/settings.json`.
73
39
 
74
40
  ```bash
75
- # Try it now — no install needed
76
- npx @robota-sdk/agent-cli
77
-
78
- # Install globally for persistent use
79
- npm install -g @robota-sdk/agent-cli
41
+ cd my-project
42
+ robota
80
43
  ```
81
44
 
82
- > **macOS users**: Korean/CJK IME input may crash macOS Terminal.app. Use **[iTerm2](https://iterm2.com/)** instead. This is a known Ink + Terminal.app issue shared with Claude Code.
83
-
84
- After installing globally, the `robota` command is available system-wide:
45
+ To set up without prompts (for example on a server), create the profile and trust the workspace
46
+ with flags. The profile stores a reference to the environment variable, not the key itself, and the
47
+ variable must be set when you run the command:
85
48
 
86
49
  ```bash
87
- robota # Interactive REPL
88
- robota "prompt" # REPL with initial prompt
89
- robota -p "List all files" # Print mode (one-shot, exit after response)
50
+ export ANTHROPIC_API_KEY=sk-ant-...
51
+ robota --configure-provider anthropic --type anthropic --model claude-sonnet-4-6 \
52
+ --api-key-env ANTHROPIC_API_KEY --set-current
53
+ robota trust --yes
90
54
  ```
91
55
 
92
- Loaded as a library (`startCli`), the package is ESM-only: use `import` or `import()`. It has no
93
- `require()` entry, because the Ink TUI it bundles loads `yoga-layout`, which starts with a top-level
94
- `await` that `require()` cannot run.
56
+ `robota init` writes a starter `AGENTS.md` and `.robota/settings.json` for the current project.
57
+ `robota --configure` reruns the interactive provider setup, and `robota --reset` deletes
58
+ `~/.robota/settings.json`.
95
59
 
96
- ### Environment Variables
60
+ ## What you can do
97
61
 
98
- | Variable | Description | Provider |
99
- | ------------------- | -------------------------------------------------------- | --------- |
100
- | `ANTHROPIC_API_KEY` | Anthropic API key | Anthropic |
101
- | `OPENAI_API_KEY` | OpenAI API key | OpenAI |
102
- | `GEMINI_API_KEY` | Google Gemini API key | Gemini |
103
- | `DEEPSEEK_API_KEY` | DeepSeek API key | DeepSeek |
104
- | `DASHSCOPE_API_KEY` | Alibaba Cloud Model Studio key | Qwen |
105
- | `BRAVE_API_KEY` | Brave Search API key (optional — enables WebSearch tool) | WebSearch |
62
+ ### Work in the terminal UI
106
63
 
107
- Set your key before running:
108
-
109
- ```bash
110
- export ANTHROPIC_API_KEY=sk-ant-...
111
- ```
64
+ `robota` starts an interactive session. Type a request, or `/` to open the command menu (`/help`
65
+ lists every command). `Esc` stops the current response, `Ctrl+R` searches the prompts you have typed
66
+ before, and every key can be rebound in `~/.robota/keybindings.json` (see the
67
+ [keybindings guide](../../content/guide/keybindings.md)).
112
68
 
113
- ## Development Setup (Monorepo)
69
+ The agent works with file and shell tools (`Read`, `Write`, `Edit`, `Glob`, `Grep`, `Bash`),
70
+ `WebFetch`, `WebSearch` (needs a `BRAVE_API_KEY`) and `AskUserQuestion`, and can hand work to
71
+ subagents and background tasks. Your project's `AGENTS.md` and `CLAUDE.md` are loaded as context
72
+ in a trusted workspace, and `@path` in a prompt attaches a project file.
114
73
 
115
74
  ```bash
116
- # Build all packages, including the CLI and its web monitor
117
- pnpm build
75
+ robota # new session
76
+ robota --permission-mode acceptEdits
77
+ robota --screen-reader # plain-text mode for screen readers
118
78
  ```
119
79
 
120
- `pnpm build` runs each package's own build in dependency order. The CLI build runs `tsdown` and then
121
- copies the GUI web app (`agent-gui-web/dist`) into `dist/web`. To see the published tarball, run
122
- `pnpm --filter @robota-sdk/agent-cli pack`.
123
-
124
- Standalone Bun binaries are written to `dist-bun` (`pnpm --filter @robota-sdk/agent-cli build:bun`) and
125
- `dist-bun-headless` (`build:bun:headless`). Native dependencies make these builds exact-host: each
126
- supported Linux x64/arm64, macOS x64/arm64, or Windows x64 binary must be compiled on its matching host,
127
- and a mismatched target is refused.
80
+ ### Run one prompt from a script
128
81
 
129
- ## Usage (Monorepo)
82
+ Print mode (`-p`) runs a single prompt without the terminal UI and exits. With no prompt argument it
83
+ reads the prompt from piped stdin.
130
84
 
131
85
  ```bash
132
- # From monorepo root
133
- cd packages/agent-cli
134
-
135
- # Development mode (no build needed)
136
- pnpm dev
137
-
138
- # Production mode (requires build)
139
- pnpm start
140
-
141
- # With arguments
142
- pnpm dev -- --version
143
- pnpm dev -- --permission-mode plan
144
- pnpm dev -- -p "List all TypeScript files in src/"
86
+ robota -p "List the TypeScript files in src/"
87
+ robota -p "Summarize this repository" --output-format json # one JSON object: result, session_id
88
+ cat task.md | robota -p # prompt from stdin
89
+ robota -p "Review this diff" --bare # without AGENTS.md, CLAUDE.md or plugins
90
+ robota --goal "make the failing tests pass" # work toward a goal over several turns
145
91
  ```
146
92
 
147
- ## CLI Flags
93
+ `--output-format` is `text` (default), `json` or `stream-json`. `--json-schema` asks for JSON matching
94
+ a schema, and `--system-prompt` / `--append-system-prompt` change the system prompt for the run. The
95
+ exit code is `0` on success and `1` on an error; `-p` exits `3` when no usable provider is
96
+ configured, and a `--goal` run that stops without reaching its goal exits `2`. In a Git repository
97
+ you have not trusted, print mode, `--goal`, `--serve`, `robota mcp serve`, `robota daemon start` and
98
+ `robota session start` refuse to start; with `--safe-mode` the first four run Restricted instead, and
99
+ `robota daemon start --restricted-workspace` starts the daemon Restricted.
148
100
 
149
- ```
150
- robota # Interactive REPL (default mode)
151
- robota "prompt" # REPL with initial prompt
152
- robota -p "prompt" # Print mode (one-shot, exit after response)
153
- robota -c # Continue last session
154
- robota -r <session-id> # Resume session by ID
155
- robota --language <lang> # Response language (ko, en, ja, zh)
156
- robota --permission-mode <mode> # plan | default | acceptEdits | bypassPermissions
157
- robota --max-turns <n> # Limit agentic turns per interaction
158
- robota --goal "<objective>" # Pursue an autonomous goal headlessly until satisfied or a bound
159
- robota --goal-max-iterations <n> # Per-goal turn budget (default 25)
160
- robota --output-format <fmt> # text | json | stream-json (print mode)
161
- robota --effort <level> # auto | none | minimal | low | medium | high | xhigh | max
162
- robota --system-prompt <text> # Replace system prompt (print mode)
163
- robota --append-system-prompt <text> # Append to system prompt (print mode)
164
- robota --model claude-sonnet-4-6 # Override provider model for this session
165
- robota --allowed-tools "Bash,Read" # Whitelist specific tools
166
- robota --denied-tools "Bash,Write" # Blacklist specific tools (denied > allowed)
167
- robota --screen-reader # Screen-reader mode: no chrome, no motion, numbered menus, role labels
168
- robota --no-screen-reader # Force it off for this run, whatever the env or settings say
169
- # Pacing (ms): ROBOTA_SCREEN_READER_STARTUP_QUIET_MS=900 ROBOTA_SCREEN_READER_PREPARK_MS=50 (0 disables either)
170
- robota --serve # Run as a headless runtime host over a loopback WS sidecar (used by the desktop GUI)
171
- robota mcp serve # Serve one session to a local MCP client over stdio
172
- robota mcp serve --http-token-file /absolute/private/path/mcp-token --http-port 8765
173
- # Serve Streamable HTTP on 127.0.0.1; the token file must not exist
174
- robota mcp serve --http-public-url https://agents.example.com/robota/mcp --oauth-issuer https://auth.example.com \
175
- --oauth-scopes mcp:use --oauth-allowed-subjects alice@example.com
176
- # Serve remote HTTP behind a proxy, admitting OAuth access tokens
177
- robota trust status # Inspect canonical workspace trust
178
- robota trust --yes # Grant trust for the current Git workspace
179
- robota trust revoke --yes # Revoke the current workspace grant
180
- robota usage # Show the last 7 days of personal usage from local session history
181
- robota usage --period 30d # Show complete buckets for the last 30 calendar days
182
- robota usage --timezone UTC --format json # Emit the versioned JSON projection
183
- robota usage export --endpoint http://127.0.0.1:4318 # Send stored-usage Gauges to a loopback OTLP collector
184
- robota usage export --signal traces --endpoint http://127.0.0.1:4318 # Send recorded prompt/provider/tool spans
185
- robota usage export --signal logs --endpoint http://127.0.0.1:4318 # Send content-free completion events
186
- robota --safe-mode # Every customization off, to rule one out
187
- robota --reset # Delete user settings and exit
188
- robota --check-update # Check npm for a newer CLI version and exit
189
- robota --disable-update-check # Skip interactive startup update check for this run
190
- robota --version # Show version
191
- robota --reduced-motion # Suppress animation for this run (colour is unaffected)
192
- robota --no-reduced-motion # Allow animation, overriding a persisted reducedMotion
193
- ```
101
+ ### Keep sessions, run them in the background, share a daemon
194
102
 
195
- ### Personal Usage
196
-
197
- `robota usage` reads local user and trusted-project session stores without starting a provider or
198
- requiring network access. It reports sessions, started turns, tokens, cost confidence, model/provider/
199
- surface/source breakdowns, privacy-safe activity counts, and coverage diagnostics. The project copy
200
- wins when the same session ID exists in both stores. Stored prompts, responses, paths, and tool
201
- payloads are never printed.
202
-
203
- Use `--period 7d` (the default) or `--period 30d`, choose an IANA timezone with `--timezone`, and use
204
- `--format json` for the external `schemaVersion: 1` projection. An empty store produces an empty
205
- report; a supplied store set containing no readable records exits with an error instead of silently
206
- reporting zero usage.
207
-
208
- `robota usage export` is a separate, explicit, one-shot network action over OTLP/HTTP JSON to a
209
- loopback collector (`127.0.0.1` or `[::1]`) only. The default `metrics` signal sends a current
210
- **Gauge snapshot** of stored session/turn counts, tokens, estimated known USD cost, and unknown-cost
211
- counts to `/v1/metrics`. Repeated exports are snapshots, not new usage to add together. Select
212
- `--signal traces` to send recorded prompt-root, provider-call, and tool spans to `/v1/traces`, or
213
- `--signal logs` to send recorded content-free completion events to `/v1/logs`. Repeating a logs export
214
- can resend the same events; a collector may retain duplicates. These signals use stored records;
215
- they are not live tracing, and legacy records may lack span/event coverage. No signal
216
- exports prompt or tool bodies, credentials, or a remote destination. An unreadable stored session,
217
- collector rejection, or network error fails the command without reporting success.
218
-
219
- Live prompt telemetry is a separate, opt-in Node CLI feature (interactive, print, serve, and MCP serve). Set
220
- `ROBOTA_TELEMETRY_ENABLED=1`, `ROBOTA_TELEMETRY_TRACES=otlp`,
221
- `ROBOTA_TELEMETRY_OTLP_PROTOCOL=http/protobuf`, and
222
- `ROBOTA_TELEMETRY_OTLP_ENDPOINT=https://collector.example` to send content-free prompt/provider/tool
223
- spans to the base URL's `/v1/traces`. `ROBOTA_TELEMETRY_OTLP_TRACES_ENDPOINT` overrides the base with
224
- an exact traces URL. Select `ROBOTA_TELEMETRY_METRICS=otlp` independently to send per-invoked-call
225
- delta counts, complete-usage token totals and price-table-estimated USD cost, plus observed prompt and
226
- tool-completion counts, to `/v1/metrics`;
227
- `ROBOTA_TELEMETRY_OTLP_METRICS_ENDPOINT` overrides that destination. Missing usage or prices are
228
- counted separately, never treated as zero cost. A truncated provider-event batch reports omissions
229
- and does not claim a complete usage/cost total. Metric datapoints omit session, turn, provider and
230
- model labels by default; set `ROBOTA_TELEMETRY_METRIC_ATTRIBUTES` to a comma list drawn from
231
- `session`, `provider` and `model` (canonical lower case, no duplicates) to add them, and only when
232
- metrics export over `otlp` or `console` — the setting is refused otherwise, and startup is refused
233
- for any token that is not exactly one of the three. `session` adds `robota.session.id` (the same key
234
- the trace spans use) to every metric datapoint of the batch. `provider`/`model` add
235
- `robota.provider.id`/`robota.model.id` only to provider-derived metrics (calls, tokens, cost, and the
236
- unpriced/usage-unavailable counts), splitting them into one datapoint per distinct id (or pair); a
237
- call with no id gets its own datapoint without that attribute. In `--serve`/`robota mcp serve`, how
238
- many distinct session ids appear is set by the connecting clients, not by the CLI; provider and model
239
- values come from whatever the host's provider configuration reports, not from a fixed catalog. Select
240
- `ROBOTA_TELEMETRY_LOGS=otlp` independently for content-free
241
- prompt/provider/tool completion events, plus a tool's own permission decision (allowed, denied, or
242
- hook-blocked), at `/v1/logs`; `ROBOTA_TELEMETRY_OTLP_LOGS_ENDPOINT`
243
- overrides that destination. Tool spans and logs carry a validated opaque call ID when available, and an
244
- invoked provider-call span and its completion log carry the provider's own request ID the same way when
245
- the adapter attested one; metric datapoints never use either as a label, though a permission-decision
246
- count by decision value is still reported on `/v1/metrics`. A permission decision has no duration of
247
- its own and never produces a trace span. Only confirmed invocations produce provider-completion events;
248
- omitted child counts remain visible on the prompt event. Plain HTTP is allowed only for loopback; URL credentials and query parameters
249
- are rejected. Export is bounded, best-effort, and does not delay or fail a turn; delivery failures
250
- produce a content-free stderr warning. These switches do not enable content capture,
251
- additional event kinds, or replay of stored traces. Ambient `OTEL_*` values alone do not enable them.
252
-
253
- Prompt, response and tool content is a separate opt-in on top of `ROBOTA_TELEMETRY_LOGS=otlp`.
254
- `ROBOTA_TELEMETRY_LOG_USER_PROMPTS=1` sends what you typed (never the expanded model input, such as
255
- `@file` contents), and `ROBOTA_TELEMETRY_LOG_ASSISTANT_RESPONSES=1` sends the assistant's final answer
256
- for the turn. `ROBOTA_TELEMETRY_LOG_TOOL_ARGUMENTS=1` sends the arguments of the turn's tool calls,
257
- allowed or denied, and `ROBOTA_TELEMETRY_LOG_TOOL_OUTPUT=1` sends the output of its allowed calls
258
- (empty for a tool that crashed). Tool arguments carry whatever the model passed: a `Write` or `Edit`
259
- call's arguments are the file content it writes. Each setting accepts exactly `0` or `1`.
260
- `ROBOTA_TELEMETRY_LOG_CONTENT_MAX_BYTES` bounds each item (an integer from 256 to 16384, default 2048)
261
- and is refused unless one of them is `1`. Content is captured only in the interactive terminal and
262
- only for turns you type yourself, the same turns prompt history records: goal and loop wakeups, peer
263
- and external messages, remote co-drivers, subagents and background work are never captured, and
264
- tool content covers only the calls that turn made itself — never a subagent's, a forked skill's or
265
- background work's, even through a tool they share. A hook-blocked call, an unknown tool, or a call
266
- stopped before its tool ran sends nothing. Print (`-p`, `--goal`), `--serve` and
267
- `robota mcp serve` refuse to start with a content setting at `1` rather than ignore it. It goes only to OTLP log records (`robota.content.captured`, joined to the prompt's trace and
268
- root span, with `robota.content.kind`, `robota.content.truncated`, `robota.content.original_bytes` and,
269
- for an interrupted turn's response, `robota.content.partial`) — never to spans, metrics or console
270
- output, so a content setting with `ROBOTA_TELEMETRY_LOGS=console` is refused. A tool item is joined to
271
- the call's tool span when the trace kept one (else the root span) and adds `robota.tool.call_id`,
272
- `robota.tool.name` and `robota.tool.outcome` (`success`, `failure` or `denied`). Arguments are
273
- rendered with values under secret-looking keys (`password`, `apiKey`, `accessTokens`, …) masked
274
- whole, and binary or base64 payloads replaced by their size. It is sent to the logs
275
- destination with its headers but in its own requests and queue: a content failure never delays or
276
- drops the content-free logs, and is reported on stderr like any other delivery failure. One turn's
277
- content is bounded in item count and total size, with room kept for the prompt and response, and is
278
- sent as a few requests of bounded size; what does not fit is dropped and counted by kind in a
279
- content-free `robota.content.omitted` record, sent last. When a request fails, the rest of that turn's
280
- content and every queued turn are dropped, so that count is lost too. Queued content can hold about
281
- 12 MB in the worst case. Before sending, the CLI masks known credential shapes (vendor API keys, AWS keys, private-key blocks,
282
- JWTs, GitHub, Stripe, npm and GitLab tokens, bearer tokens, URL and `-u user:pass` credentials,
283
- `*_KEY`/`*_TOKEN`/`*_SECRET`/`*_PASSWORD=` values, JSON values whose name looks secret, and
284
- `Authorization`, `Proxy-Authorization`, `Cookie`, `Set-Cookie` and `X-…-Token`/`-Key`/`-Secret`/`-Auth`
285
- header lines, including indented ones and the `> ` lines `curl -v` prints), the literal secrets it knows of (settings keys and
286
- `env` values, every resolved provider key including one switched to mid-session, and collector header
287
- values), your workspace path (`<workspace>`, also inside `file://` URLs) and home directory (`~`),
288
- control characters, and a partial token left at the size cut. This
289
- masking is best effort: anything it does not recognise as a secret is sent as written — file
290
- contents, command output and a response that repeats them included. If the secrets cannot be
291
- read, that request carries no content at all.
292
-
293
- Static collector headers (for example an `Authorization` token) use
294
- `ROBOTA_TELEMETRY_OTLP_HEADERS` for the generic endpoint and `ROBOTA_TELEMETRY_OTLP_TRACES_HEADERS`,
295
- `ROBOTA_TELEMETRY_OTLP_METRICS_HEADERS` or `ROBOTA_TELEMETRY_OTLP_LOGS_HEADERS` for one signal, in
296
- OpenTelemetry's `name=value,name2=value2` form with percent-encoded values
297
- (`Authorization=Bearer%20abc123`). Headers are scoped to their destination: a signal that uses
298
- `ROBOTA_TELEMETRY_OTLP_ENDPOINT` sends the generic headers merged with its own, its own winning on the
299
- same name, while a signal with its own `ROBOTA_TELEMETRY_OTLP_<SIGNAL>_ENDPOINT` sends only its own
300
- headers. Unlike OpenTelemetry, generic headers are never sent to a per-signal endpoint. Startup is
301
- refused for malformed entries, empty names or values, duplicate names, reserved transport,
302
- content-negotiation, proxy, `sec-` or trace-propagation names, control characters other than tab, non-ASCII characters,
303
- oversized settings, headers no OTLP signal would send, and a per-signal endpoint without its own
304
- headers while another signal sends the generic ones. Header helpers and refresh are not supported.
305
- Console output, logs and resource attributes never contain headers, and errors name only the setting
306
- and entry position. The CLI removes every `ROBOTA_TELEMETRY_*` setting from its environment at
307
- startup, so shells, hooks, subagents and other child processes do not inherit them; the only
308
- handover is the supervised runtime that `session start` or `session view` launches, which receives
309
- them in its spawn environment. While telemetry is enabled, any other `ROBOTA_TELEMETRY_*` setting
310
- (for example client certificates, a locked destination or other content capture) stops startup with an
311
- error that names the setting but never prints its value, rather than exporting without it.
312
- Each signal also accepts `console` instead of `otlp` to write a content-free JSON diagnostic to stderr;
313
- console needs neither an endpoint nor a protocol and never includes collector credentials. Signals
314
- remain independent, and the Robota enable switch is still required.
315
- All enabled signals use the same per-process `service.instance.id` and the CLI's version and
316
- presentation mode as resource attributes. Ambient `OTEL_SERVICE_NAME` and other `OTEL_*` values
317
- cannot replace these fields.
318
-
319
- Trace context propagation is a further opt-in. `ROBOTA_TELEMETRY_PROPAGATE_TO` is a comma list of
320
- exact origins (`https://api.anthropic.com,https://gateway.example.com:8443`) that may receive a
321
- W3C `traceparent` on provider requests: `00-<prompt trace id>-<provider-call span id>-01`, where the
322
- span ID is the one the exported `robota.provider_call` span carries. The same list covers MCP servers
323
- reached over Streamable HTTP: a tool call to a server whose URL has a listed origin carries
324
- `00-<prompt trace id>-<tool span id>-01`, where the span ID is the one the exported tool span
325
- carries. Only the `tools/call` request and its cancellation carry it — never initialization,
326
- listing, list refreshes, notifications or the server's event stream — and stdio MCP servers receive
327
- nothing. It needs
328
- `ROBOTA_TELEMETRY_ENABLED=1` and `ROBOTA_TELEMETRY_TRACES=otlp` or `console`, and is inert while
329
- telemetry is off. Each entry must be exactly its own origin: `https`, or `http` only on loopback, with no
330
- path, trailing slash, query, credentials, wildcard or spelled-out default port; a scheme, port or
331
- subdomain difference is a different origin and gets nothing. An internationalized host must be listed
332
- in its punycode (`xn--`) form, and an origin with a trailing dot never matches; both fail closed
333
- (refused at startup or sent nothing). Malformed, duplicate or too many entries
334
- stop startup with an error that names only the setting and entry position. `tracestate` and `baggage`
335
- are never sent, the collector's origin is never trusted implicitly, and collector headers are never
336
- reused for provider requests. The Anthropic and OpenAI (Responses and Chat Completions) adapters
337
- propagate to their client's effective base URL, as do the OpenAI-compatible DeepSeek, Qwen (both its
338
- Chat Completions and Responses surfaces), and Gemma adapters, and Gemini propagates to
339
- `https://generativelanguage.googleapis.com` only — not with `GOOGLE_GEMINI_BASE_URL`,
340
- `GOOGLE_VERTEX_BASE_URL` or Vertex mode. Nothing is sent through a provider executor. When
341
- propagation is configured but the round's provider cannot propagate — an executor, or a client
342
- whose base URL cannot be read — the CLI writes one stderr line per provider naming only that
343
- provider. Only a prompt's own provider calls carry it: subagents, workers and background
344
- tasks do not inherit it.
345
-
346
- Listing a vendor's origin lets that vendor link its own request logs to your trace ID. A redirect
347
- followed by the SDK carries the header to the redirect target. A provider call whose span was omitted or
348
- dropped from export still sent its `traceparent`, so the vendor's parent span may be missing from your
349
- trace; `robota.omitted.provider_count` on the prompt span shows when that happened. Ambient
350
- `TRACEPARENT` and `OTEL_*` values are never adopted.
351
-
352
- `ROBOTA_TELEMETRY_PROPAGATE_TO_SUBPROCESSES` hands the prompt's trace to child processes through the
353
- `TRACEPARENT` environment variable. It is a comma list drawn from exactly `shell` and `hooks`, each at
354
- most once; it needs the same `ROBOTA_TELEMETRY_ENABLED=1` and exported traces, is inert while
355
- telemetry is off, works with or without `ROBOTA_TELEMETRY_PROPAGATE_TO`, and a malformed entry stops
356
- startup naming only the setting and entry position. With `shell`, each foreground `Bash`/`Shell`
357
- command runs with `00-<prompt trace id>-<tool span id>-01`, where the span ID is the one that call's
358
- exported tool span carries. With `hooks`, command hooks fired during a prompt — `UserPromptSubmit`,
359
- `PreToolUse`, `PostToolUse`, `PermissionDecision`, the model-call hooks, `Stop`, `StopFailure` and
360
- the `PreCompact` and `PostCompact` of an automatic compaction — run with `00-<prompt trace id>-<prompt span id>-01`, so
361
- their spans sit beside the provider and tool spans; a hook fired outside a prompt (`SessionStart`,
362
- `SessionEnd`, both hooks of `/compact`, background tasks, subagent worktrees) gets nothing. The value
363
- is only ever in the child's environment, never in a hook's stdin JSON. A `TRACEPARENT` that a hook
364
- group's own `env` sets wins, and the child then sees its environment unchanged; otherwise the ambient
365
- `TRACESTATE` is removed, because it belonged to a different parent. The `!` shell passthrough,
366
- background, managed and scheduled shells, the monitor UI launcher, a sandboxed shell, stdio MCP
367
- servers, and HTTP, prompt and agent hooks never receive it. Robota never modifies its own process
368
- environment, so while the setting is off every child sees exactly the ambient `TRACEPARENT` and
369
- `TRACESTATE` it would have seen anyway.
370
-
371
- Upgrading: an origin already listed for a provider now also sends `traceparent` to an MCP HTTP server
372
- at that exact origin. Remove the origin, or move the MCP server to a different origin, if that server
373
- should not link its logs to your trace.
374
-
375
- ### Doctor
376
-
377
- `robota doctor` (aliases: `checkup`, `diagnose`) diagnoses configuration and runtime readiness
378
- before any session exists, so a broken configuration cannot make the diagnostic unreachable. It reports
379
- every settings layer in precedence order with its state and cause, the merged keys with the layer that
380
- contributed each, provider resolution and endpoint reachability, workspace trust, storage, plugins,
381
- skills, hooks and MCP declarations — naming the exact file and cause, never a credential. Exit code
382
- `0` means no check failed (warnings allowed); `1` means at least one did.
103
+ Sessions are saved, so you can come back to them.
383
104
 
384
105
  ```bash
385
- robota doctor # full report
386
- robota doctor --repair settings.user.robota # one allowlisted repair, asks [y/N] first
387
- robota doctor --repair storage.user --yes # no prompt (required in a non-interactive shell)
106
+ robota -c # continue the most recent session
107
+ robota -r <id-or-name> # resume a session
108
+ robota -r <id> --fork-session # continue a copy, leaving the original as it was
109
+ robota -n "refactor auth" # name a new session
388
110
  ```
389
111
 
390
- Repairs are limited to an empty user settings file (rewritten as `{}`) and a missing or too-open
391
- user storage directory; everything else is reported with the path to fix. `/doctor` runs the same
392
- report inside a session, and `/doctor repair <check-id>` asks before writing.
112
+ Inside a session, `/resume` switches sessions, `/rename` names this one, `/fork` copies the
113
+ conversation into a background session, and `/cd <directory>` moves the conversation to another
114
+ directory.
393
115
 
394
- ### MCP Servers
116
+ A supervised session keeps running after you close the terminal, and a workspace daemon is one
117
+ long-lived runtime that terminals and the desktop app share. In a folder not trusted yet, the desktop
118
+ app asks in its window whether to trust it, start Restricted, or quit before it starts the daemon:
395
119
 
396
- #### Serve Robota to an MCP host
397
-
398
- Install `@robota-sdk/agent-cli`, configure a provider with `robota --configure`, and grant the
399
- intended project with `robota trust --yes` before starting a headless server. Resolve the actual
400
- executable (`command -v robota`) and use its **absolute path** in the host configuration. For an
401
- MCP client that supports a child-process working directory, configure:
402
-
403
- ```json
404
- {
405
- "mcpServers": {
406
- "robota": {
407
- "command": "/absolute/path/to/robota",
408
- "args": ["mcp", "serve"],
409
- "cwd": "/absolute/path/to/trusted/project"
410
- }
411
- }
412
- }
120
+ ```bash
121
+ robota session start --background --name nightly
122
+ robota session list
123
+ robota session attach <supervised-id> # add --observe to watch read-only
124
+ robota daemon start # then: robota --attach
125
+ robota daemon stop
413
126
  ```
414
127
 
415
- The host must launch the process in the intended project directory. Robota uses that inherited
416
- directory for its normal project-root, access and trust decision; an MCP `roots/list` value or an
417
- environment variable does not silently change it. For Claude Code, whose stdio server environment
418
- includes `CLAUDE_PROJECT_DIR`, a project-scoped launch command can select that directory explicitly:
128
+ See [Sessions and the daemon](../../content/guide/sessions-and-daemon.md).
419
129
 
420
- ```sh
421
- claude mcp add --scope project --transport stdio robota -- \
422
- /bin/sh -c 'cd "$CLAUDE_PROJECT_DIR" && exec /absolute/path/to/robota mcp serve'
423
- ```
130
+ ### Control what the agent may do
424
131
 
425
- The host receives the canonical runtime tool catalog plus `robota_submit`. Model-invocable commands
426
- appear only through their canonical `robota_command_*` names. Tools run through the session's normal
427
- permission and hook policy; denied or approval-requiring calls return MCP tool errors, never an
428
- interactive prompt on the protocol stream. The peer is a local process started by the user and can
429
- request actions within that session's admitted workspace and permissions. This mode starts no web
430
- server, WebSocket sidecar or TUI. Stdout carries only MCP messages; startup notices and failures go
431
- to stderr. Closing the host's stdin or sending SIGINT/SIGTERM shuts down the carrier and session.
432
- If the host reports a connection failure, run `robota trust status` and `robota doctor` separately in
433
- the same project directory, then inspect the host's captured stderr for configuration errors.
434
-
435
- The same session can also use an admitted external MCP tool while it serves the host. Its connected
436
- client tools appear in the served catalog under canonical names such as `probe__echo`; they still
437
- run through the session's normal permission policy. The server carrier and outbound client close
438
- independently. An embedding host can supply its own `IMCPActivationApprovalStore` through
439
- `startCli({ mcpApprovalStore })` before startup, along with an explicitly approved
440
- `mcpHttpTransportDeps` egress policy when needed. Neither capability comes from MCP settings or the
441
- remote caller. The ordinary `robota` executable supplies neither automatically.
442
-
443
- #### Serve Robota to a remote MCP client
444
-
445
- `--http-token-file` is for clients on the same machine: it binds only `127.0.0.1`, and its bearer
446
- is never accepted beyond loopback. To serve a client elsewhere, run Robota as an OAuth resource
447
- server behind an HTTPS reverse proxy. An authorization server you already run issues the access
448
- tokens; Robota only verifies them.
449
-
450
- ```sh
451
- robota mcp serve \
452
- --http-public-url https://agents.example.com/robota/mcp \
453
- --oauth-issuer https://auth.example.com \
454
- --oauth-scopes mcp:use \
455
- --oauth-allowed-subjects alice@example.com \
456
- --http-host 127.0.0.1 --http-port 8765 \
457
- --trusted-proxy 127.0.0.1
458
- ```
459
-
460
- - `--http-public-url` is the `https` address clients use. It is also the token audience and the
461
- `resource` Robota advertises. Robota serves MCP at its path (`/robota/mcp` above) and the RFC 9728
462
- protected-resource metadata at `/.well-known/oauth-protected-resource` followed by that path. The
463
- proxy must forward both paths unchanged (do not strip the prefix) and must preserve the client's
464
- `Host` header. Robota checks `Host` and `Origin` against the public URL, not the address it binds.
465
- - `--oauth-issuer`, `--oauth-scopes` and `--oauth-allowed-subjects` are all required. A token must
466
- be an RFC 9068 access token from that issuer, addressed to the public URL, carrying every listed
467
- scope, and issued to a listed subject. Every admitted client drives the same session, so name only
468
- the people you would hand this terminal to.
469
- - `--http-host` defaults to `127.0.0.1`, which suits a proxy on the same machine. Robota binds any
470
- other address only when all the flags above are present.
471
- - A request without a valid token receives `401` with
472
- `WWW-Authenticate: Bearer resource_metadata="…"`, and a token missing a scope receives `403`
473
- with `insufficient_scope`. The body is always empty. MCP clients that support authorization
474
- use that challenge to discover the authorization server.
475
- - Failed requests are counted per client address. After too many failures in a minute, that
476
- address receives `429`; a valid token is never throttled. The client address is read from
477
- `X-Forwarded-For` only when the connection comes from a `--trusted-proxy` address (repeatable).
478
- - Each refusal is logged on stderr as a reason and an address class (`loopback`, `private`,
479
- `public`), never the token or the address itself.
480
- - The server is stateless: it issues no `Mcp-Session-Id`, so there are no sessions to enumerate or
481
- hijack.
482
-
483
- The settings described below configure Robota as an MCP **client**.
484
-
485
- Declare remote MCP servers under an `mcpServers` key in any layered settings file (managed, user, or
486
- project `.robota`/`.claude` settings) — the same precedence order every other setting uses. Each
487
- entry names a `"type": "http"` transport and a `url`; `${VAR}`/`${VAR:-default}` references in `url`,
488
- `headers`, and `env` are resolved from the process environment. Every declared server is
489
- **deny-by-default**: a server must be explicitly approved with `/mcp approve <serverId>` before its
490
- tools are connected. Use `/mcp` (or `/mcp list`) to see every declared server's admission status,
491
- and `/mcp reject`/`/mcp revoke` to withdraw approval. Only approved servers already known at startup
492
- are connected. Approval is in-memory for the ordinary executable, so `/mcp approve` mid-session
493
- records a decision for that session but does not connect the server in the running session or
494
- persist it across a restart. An embedding host can preserve approval state across starts by
495
- supplying the same store; it remains responsible for when to reconnect approved definitions.
496
-
497
- A remote server that declares `"oauth": {}` (optionally with `clientId`, `callbackPort`,
498
- `authServerMetadataUrl` and `scopes`) needs its own sign-in before a session can use it. Sign-in is
499
- per server, naming the server — inside a session with `/mcp login <server>`, or from a terminal:
132
+ Every tool call passes through deny rules, ask rules, allow rules and then the permission mode.
500
133
 
501
- ```bash
502
- robota mcp login files # opens your browser; tokens kept owner-only in ~/.robota/mcp-credentials
503
- robota mcp login files --no-browser # prints the URL; paste back the address your browser was sent to
504
- robota mcp login files --client-secret # a pre-registered confidential client: asks for the secret
505
- robota mcp logout files # deletes the stored tokens, then revokes them where the server allows
506
- ```
134
+ | Mode | Reads | File edits | Shell commands |
135
+ | ------------------- | ----- | ---------- | ---------------------------------------- |
136
+ | `plan` | yes | refused | refused (except built-in read-only ones) |
137
+ | `default` | yes | asks | asks |
138
+ | `acceptEdits` | yes | yes | asks |
139
+ | `auto` | yes | yes | decided by a model classifier |
140
+ | `bypassPermissions` | yes | yes | yes |
507
141
 
508
- Use `--no-browser` when the browser runs on another machine (for example over SSH). After you
509
- approve, the browser is sent to a `http://127.0.0.1:<port>/callback` address that may not load; copy
510
- that full address and paste it at the prompt (the input is not echoed). It is accepted only if it is
511
- this sign-in's redirect address and carries its `state`, and only within the same five minutes the
512
- browser flow allows.
513
-
514
- `robota mcp logout <server>` always deletes that server's local credential, even when the
515
- authorization server cannot be reached or refuses to revoke the tokens; it then says, per token,
516
- whether it was revoked, and otherwise why not (by a short reason) or that the server offers no
517
- revocation. `/mcp` shows each OAuth server's sign-in state — `signed in`, `token expired, will
518
- refresh`, `sign-in required` or `signed out` — never a token, and for a server that needs a sign-in
519
- it names `/mcp login <server>` and `robota mcp login <server>` (the server's name appears in them
520
- only when it is a plain name that is safe to paste into any shell). `/mcp logout <serverId>` signs
521
- out of that one server from inside a session and stops the session sending the token it holds.
522
-
523
- `/mcp login <server>` signs in without leaving the session: it first shows the authorization URL,
524
- where you can choose to open your browser, paste the redirect instead, or cancel. When no browser
525
- can be opened — or with `/mcp login <server> --no-browser` — it asks for the redirect address in the
526
- session's own prompt (the input is not shown). Once signed
527
- in, a server that could not connect at startup is admitted as usual (it must be approved) and
528
- connected, and its tools are available from your next message; a server whose sign-in lapsed
529
- mid-session works again. A failed or cancelled sign-in changes nothing. A client secret is never
530
- typed into a session: for a pre-registered client that needs one, run
531
- `robota mcp login <server> --client-secret` in a terminal.
532
-
533
- ### MCP Background Handoff
534
-
535
- A slow MCP tool call can be handed to a background task instead of blocking the turn. Configure it
536
- under an `mcp` key (beside `mcpServers`, in the same layered settings files):
142
+ Set the mode with `--permission-mode <mode>` (`--dry-run` is `plan`) or change it in a session with
143
+ `/permissions <mode>`. `/permissions` alone lists the rules in force and recent denials. Rules live in
144
+ any settings file:
537
145
 
538
146
  ```json
539
147
  {
540
- "mcp": {
541
- "autoBackgroundMs": 120000,
542
- "callTimeoutMs": 600000
148
+ "permissions": {
149
+ "allow": ["Bash(pnpm *)", "Bash(git status)"],
150
+ "ask": ["Bash(git push *)"],
151
+ "deny": ["Bash(rm -rf *)", "Write(.env)"]
543
152
  }
544
153
  }
545
154
  ```
546
155
 
547
- - `autoBackgroundMs` (default 120000 ms): a tool call still running at this point is handed to a
548
- `tool-invocation` background task; `/tasks` shows it like any other background task. `0` disables
549
- the handoff.
550
- - `callTimeoutMs` (default 600000 ms): the tool call's own budget, enforced by the MCP client in
551
- every mode — including print. `autoBackgroundMs` must be less than `callTimeoutMs`, or the handoff
552
- is disabled with a warning.
553
- - The handoff applies to the interactive TUI and `robota --serve`. In print mode (`-p`) a slow MCP call always runs to completion in the
554
- foreground, bounded by `callTimeoutMs`; a positive `autoBackgroundMs` is reported as ignored there.
555
-
556
- ### CLI Updates
557
-
558
- Robota can check npm for a newer `@robota-sdk/agent-cli` version:
559
-
560
- ```bash
561
- robota --check-update
562
- ```
563
-
564
- When an update is available, Robota prints the npm global install command:
565
-
566
- ```bash
567
- npm install -g '@robota-sdk/agent-cli@latest'
568
- ```
569
-
570
- Robota does not implement its own updater and does not modify `~/.robota/settings.json` for update checks. Interactive startup checks use a user-level operational cache at `~/.robota/update-check.json` and can be skipped for one run with `--disable-update-check`. Print/headless mode (`robota -p`) does not perform automatic startup update checks so scripted stdout and stderr remain deterministic.
571
-
572
- ### Print Mode Output Formats
573
-
574
- Print mode (`-p`) supports three output formats via `--output-format`:
575
-
576
- | Format | Description |
577
- | ------------- | ------------------------------------------------------------------ |
578
- | `text` | Plain text response to stdout (default) |
579
- | `json` | Single JSON object: `{ type, result, session_id, subtype }` |
580
- | `stream-json` | Newline-delimited JSON with `content_block_delta` streaming events |
581
-
582
- ### Exit Codes (print mode)
156
+ An `ask` rule asks even in `bypassPermissions`, and a few actions are never approved automatically
157
+ in any mode: `rm` on the root, home or working directory, and writes into `.git`, `.robota`,
158
+ `.claude`, `.agents` or shell and tool configuration files.
583
159
 
584
- | Code | Meaning |
585
- | ---- | ---------------------------------------------- |
586
- | 0 | Success |
587
- | 1 | General error |
588
- | 2 | Argument error |
589
- | 3 | Configuration error (missing provider/API key) |
590
- | 4 | API error |
591
- | 5 | Tool execution error |
160
+ Shell commands can also run inside an OS sandbox (bubblewrap on Linux, Seatbelt on macOS): `/sandbox`
161
+ switches between `auto-allow`, `regular` and `off`, and the `sandbox` settings key configures it.
162
+ When something misbehaves, `robota --safe-mode` starts with instruction files, skills, plugins, hooks
163
+ and MCP servers all off.
592
164
 
593
- ### Stdin Pipe
594
-
595
- When stdin is piped, the CLI reads it automatically. If a positional prompt is also given, the piped content is appended inside `<stdin>` tags:
165
+ Workspace trust is granted per Git worktree and kept in `~/.robota/workspace-trust.json`:
596
166
 
597
167
  ```bash
598
- echo "Explain this error" | robota -p
599
- cat file.ts | robota -p "Review this code" --output-format json
600
- git diff | robota -p "Summarize changes" --output-format stream-json
601
- ```
602
-
603
- ## First-Run Setup
604
-
605
- When no usable settings file exists, the CLI prompts for:
606
-
607
- 1. **Provider selection** from the providers assembled into the CLI binary
608
- 2. **Provider-specific setup fields** such as model, base URL, and masked API key
609
- 3. **Response language** (ko/en/ja/zh, default: en)
610
-
611
- Creates `~/.robota/settings.json`. Use `robota --reset` to return to first-run state.
612
-
613
- Provider setup is generated from provider definitions. The default CLI build includes Anthropic,
614
- OpenAI-compatible, DeepSeek, Gemma, and Qwen providers; other embeddings can inject their own
615
- provider definitions.
616
- Interactive setup creates a readable profile key from the selected model id, such as
617
- `claude-sonnet-4-6` or `gpt-4o`, and appends `-2`, `-3`, etc. when that key already exists. Generated
618
- profile keys never include API keys or credential hints.
619
-
620
- Inside the TUI, `/provider` and `/provider list` show configured profiles as an interactive picker. Selecting a profile opens command-owned actions for switch, edit, test, duplicate, delete, and cancel. Headless mode prints the same profile list text without opening prompts.
621
-
622
- Non-interactive/headless mode never prompts. Configure a provider ahead of time with `robota --configure` in an interactive terminal, or use `robota --configure-provider <profile> --type <type> ... --set-current`.
623
-
624
- ## Built-in Tools
625
-
626
- The AI agent can invoke 9 distinct local tools (the runtime registers 10 tool names — `Bash` is the model-familiar alias of `Shell`):
627
-
628
- | Tool | Description | Primary Argument |
629
- | ----------------- | ---------------------------------------------- | ---------------- |
630
- | `Bash` | Execute shell commands | `command` |
631
- | `Read` | Read file contents with line numbers | `filePath` |
632
- | `Write` | Write content to a file | `filePath` |
633
- | `Edit` | Replace a string in a file | `filePath` |
634
- | `Glob` | Find files matching a pattern | `pattern` |
635
- | `Grep` | Search file contents with regex | `pattern` |
636
- | `WebFetch` | Fetch URL content as text | `url` |
637
- | `WebSearch` | Search the internet (requires `BRAVE_API_KEY`) | `query` |
638
- | `AskUserQuestion` | Ask the user structured questions mid-task | `questions` |
639
-
640
- > **WebSearch** requires a `BRAVE_API_KEY` environment variable. Without it, the tool returns a setup message instead of results. Get a free key at [brave.com/search/api](https://brave.com/search/api/) (2,000 queries/month free tier).
641
-
642
- ## Recent TUI Capabilities
643
-
644
- - Provider setup and profile management are generated from provider definitions, so the default CLI
645
- build can configure, switch, edit, test, duplicate, and delete Anthropic, OpenAI-compatible,
646
- DeepSeek, Gemma, and Qwen profiles without provider-specific UI branches.
647
- - Interactive startup can check npm for newer CLI versions; print/headless mode skips startup update checks to keep scripted output deterministic.
648
- - Long-running sessions show provider usage summaries, status activity, background job tree rows, and collapsed command-output transcripts.
649
- - Edit results render as context hunks with markdown-friendly diff blocks.
650
- - Background subagents are real runtime jobs with transcripts and resumable task snapshots.
651
- - Explicit multi-agent requests use the `/agent` command module batch path through the SDK runtime.
652
-
653
- ### Recap when you come back
654
-
655
- The TUI notices when you leave the terminal — by focus, where the terminal reports it (iTerm2,
656
- Kitty, WezTerm, Alacritty, Ghostty, VS Code, Windows Terminal, tmux with `focus-events on`), or
657
- after five minutes without a keystroke elsewhere — and on your return prints one line for the
658
- interval, or nothing if nothing happened:
659
-
168
+ robota trust status # is this workspace trusted, and what would trust load? (--json: one line)
169
+ robota trust --yes # trust the Git workspace you are in
170
+ robota trust revoke --yes
660
171
  ```
661
- While away 12m: 2 turns finished (1 wake) · 1 needs input · 1 failed
662
- ```
663
-
664
- Every background row carries its state word beside the glyph (`working`, `needs-input`,
665
- `completed`, `failed`, `stopped`), the one-line headline, and for a sleeping `/schedule` a live
666
- `in 59s` countdown. `ROBOTA_FOCUS_EVENTS=0` turns focus reporting off (idle detection remains);
667
- `=1` requests it even where the TUI would not.
668
-
669
- ### Prompt history
670
172
 
671
- `Ctrl+R` searches every prompt you have typed — in this session, in this project, or anywhere —
672
- the way a shell's reverse search does. Type to narrow the list (newest first, matches highlighted),
673
- `Ctrl+S` cycles the scope `all → session → project`, `Enter` or `Tab` puts the highlighted prompt back
674
- in the input, `Ctrl+E` runs it, `Esc` returns you to exactly the draft you had. Every key is
675
- rebindable in `~/.robota/keybindings.json` (context `history-search`, and `chat-input.history-search`
676
- for the opener).
173
+ See [Permissions and hooks](../../content/guide/permissions-and-hooks.md).
677
174
 
678
- What is written, where, and how to turn it off: the interactive TUI appends each prompt you submit
679
- to `~/.robota/history.jsonl` (one JSON line — timestamp, session id, project root, text; readable by
680
- you only). Prompts are already kept verbatim in the session record; this file is a searchable index
681
- of them across sessions. `--serve` and print mode never write it. Set `"promptHistory": false` in
682
- `~/.robota/settings.json` to turn it off, or `ROBOTA_PROMPT_HISTORY=0` for one run (`=1` overrides
683
- the setting). Delete the file to forget everything.
175
+ ### Choose providers and models
684
176
 
685
- Searching the conversation itself needs no viewer: the TUI never switches to the alternate screen,
686
- so every message of a resumed session is in your terminal's own scrollback and search.
177
+ Each provider profile in `providers` names a `type` (`anthropic`, `openai`, `gemini`, `deepseek`,
178
+ `qwen`, `gemma`), a model, and optionally a base URL and API key; `currentProvider` picks the one to
179
+ use. Setup fills the key in as a reference to an environment variable:
687
180
 
688
- ### Themes
689
-
690
- `/theme` opens a picker: moving the highlight previews that theme in the live region — the input
691
- frame, the status bar and the overlay itself — so you judge a colour scheme against the thing it
692
- applies to rather than a swatch. `Enter` applies it, `Esc` leaves the previous one in place. The
693
- transcript above keeps the colours it was written in, because the terminal owns those lines once
694
- they are printed.
695
-
696
- Four built-ins ship: `dark` (what Robota has always looked like), `light`, and `dark-daltonized` /
697
- `light-daltonized`, which avoid the red/green distinction entirely — blue for "good", orange for
698
- "bad" — for the roughly 1 in 12 men with a colour-vision deficiency. The daltonized pair is checked
699
- mechanically: a test simulates protanopia and deuteranopia over the pairs whose difference in colour
700
- carries meaning and fails if any of them come too close.
701
-
702
- Without the picker: `/theme list` shows what is installed and what is active, `/theme <id>` switches,
703
- `/theme syntax on|off` toggles code-block highlighting, and `/theme motion on|off` toggles animation.
704
- All three persist to `~/.robota/settings.json` as the flat keys `theme`, `syntaxHighlighting` and
705
- `reducedMotion`.
706
-
707
- #### Writing your own
708
-
709
- Drop a `.json` file in `~/.robota/themes/` and it appears in the list as `custom:<file-name>`. The
710
- name becomes part of an id you type, so it may use up to 24 characters from letters, digits, `.`,
711
- `-` and `_` — a file named anything else is skipped with a line saying so, rather than listed as a theme no command can apply:
181
+ | Provider type | Default key variable | Notes |
182
+ | ------------- | -------------------- | ------------------------------------------------------------------------------------ |
183
+ | `anthropic` | `ANTHROPIC_API_KEY` | |
184
+ | `openai` | `OPENAI_API_KEY` | also other OpenAI-compatible endpoints, through `baseURL` |
185
+ | `gemini` | `GEMINI_API_KEY` | |
186
+ | `deepseek` | `DEEPSEEK_API_KEY` | |
187
+ | `qwen` | `DASHSCOPE_API_KEY` | Alibaba Cloud Model Studio |
188
+ | `gemma` | none | a local Gemma model; the base URL defaults to LM Studio's `http://localhost:1234/v1` |
712
189
 
713
190
  ```json
714
191
  {
715
- "name": "Mine",
716
- "base": "light",
717
- "overrides": { "colors": { "text": { "accent": "#56b4e9" } } }
192
+ "currentProvider": "claude",
193
+ "providers": {
194
+ "claude": {
195
+ "type": "anthropic",
196
+ "model": "claude-sonnet-4-6",
197
+ "apiKey": "$ENV:ANTHROPIC_API_KEY"
198
+ }
199
+ }
718
200
  }
719
201
  ```
720
202
 
721
- `base` is any built-in and `overrides` is a sparse map over the same token paths the built-ins use —
722
- `colors`, `markdown`, `syntax` and `motion` — so you change the colours you care about and inherit
723
- the rest. Values use Ink's colour grammar: a chalk colour name, `#rgb`, `#rrggbb`, `ansi256(n)` or
724
- `rgb(r,g,b)`. A raw escape sequence is not in that grammar, so it cannot enter through a theme.
725
-
726
- A plugin ships themes the same way, in its own `themes/` directory; they are listed as
727
- `custom:<plugin>:<file-name>`, and the plugin's own name has to satisfy the same rule for the same
728
- reason. Both namespaces start with `custom:`, so a file can never take a
729
- built-in's name whatever it is called.
730
-
731
- A file is applied whole or not at all. An unknown token, a value that is not a colour, or JSON that
732
- does not parse skips the WHOLE file with the path that refused it — printed once at startup as
733
- `Skipped theme "mine.json": $.overrides.colors.text.accent: "nope" is not a colour …`, and shown in
734
- the picker as a row that carries the same reason and cannot be chosen. Its neighbours still load.
735
-
736
- Motion can also be decided per run: `--reduced-motion` / `--no-reduced-motion` beat
737
- `ROBOTA_REDUCED_MOTION=1|0`, which beats the setting. A run that pins it says so — `/theme motion on`
738
- reports that it saved the setting and that this run keeps what pinned it, rather than appearing to
739
- do nothing. `NO_COLOR`, `FORCE_COLOR=0`, a non-TTY stdout and screen-reader mode still win over
740
- every theme: no colour and no animation, exactly as before.
741
-
742
- ## Workspace Trust
743
-
744
- Robota loads a project's own configuration only from a workspace you have trusted. That covers the
745
- project's settings, hooks, plugins, skills, agent definitions, provider overrides and MCP servers.
746
- In a workspace you have not trusted:
747
-
748
- - the interactive TUI starts **Restricted**: your own user settings, built-in tools and permission
749
- rules work, and nothing the repository contributes is loaded;
750
- - headless startup refuses with `Workspace trust is required before headless startup (state: …)`.
751
- That covers print mode (`-p`), `--goal`, `--serve`, `robota mcp serve`, `robota daemon start`,
752
- `robota session start` and a session that `robota session view` starts.
753
-
754
- There are two exceptions. `--safe-mode` always starts Restricted, so `-p`, `--goal`, `--serve` and
755
- `robota mcp serve` run with it without trust.
756
- A directory that is not in a Git repository cannot be trusted at all, and every mode runs Restricted
757
- there.
758
-
759
- ```bash
760
- robota trust status # Is this workspace trusted? Lists what trust would load when it is not
761
- robota trust --yes # Trust the Git workspace you are in
762
- robota trust revoke --yes # Take the grant back
763
- ```
764
-
765
- Run these from inside the workspace. A grant covers that Git worktree:
766
-
767
- - any directory inside it resolves to the same workspace, and a symlinked path resolves to the real
768
- one;
769
- - a nested repository or submodule is a workspace of its own, and so is each linked worktree.
770
-
771
- Grants are stored in `~/.robota/workspace-trust.json` and survive restarts. Without a TTY, `grant` and
772
- `revoke` require `--yes`.
773
-
774
- A grant is tied to the repository, not only to its path:
775
-
776
- - It **holds** through git commands that rewrite `.git/config` (`push -u`, renaming or deleting a
777
- branch, `remote add`, `git config`) on a filesystem that records a creation time (APFS, ext4,
778
- NTFS, …). On one that does not, such a command can drop it; run `robota trust --yes` again. A
779
- volume that macOS renumbers never drops it.
780
- - It is **not inherited**: a repository deleted and recreated, or a different clone put at the same
781
- path, is untrusted until you trust it. Moving or renaming the repository also needs a new grant.
782
- - MCP server approvals are tied to the grant only where they are stored, which means in a host that
783
- embeds Robota with a persistent approval store. There, revoking, re-trusting, or a grant that
784
- changes as above asks for them again. The `robota` executable keeps them in memory and asks on each
785
- run.
786
-
787
- **After upgrading**, if a workspace you had trusted shows as untrusted, run `robota trust --yes` in
788
- it once. Grants are now keyed differently, so a grant made by an earlier version no longer matches.
789
-
790
- **In this monorepo**, use the source CLI, since a globally installed `robota` may be older than the
791
- source you are working on:
792
-
793
- ```bash
794
- pnpm cli:trust # Trust this repository
795
- pnpm cli:dev # Run the CLI from source
796
- pnpm cli:dev:trust # Trust it if it is not yet, then run the CLI (arguments go to the run)
797
- ```
798
-
799
- To trust another repository with the source CLI, run the launcher from inside that repository:
800
- `/path/to/robota/scripts/dev/robota trust --yes`.
801
-
802
- ## Permission System
803
-
804
- Every tool call passes through a three-step permission gate:
203
+ In a session, `/provider list`, `/provider switch <profile>`, `/provider add` and `/provider test`
204
+ manage profiles. For one run, `--provider <profile>` picks a profile (add `--set-current` to make it
205
+ the default), `--model` overrides the model, `--fallback-model a,b` continues a turn on another model
206
+ when the first is overloaded, `--effort <level>` sets model effort, and `--advisor <profile[:model]>`
207
+ lets the model consult a second model. See [Providers](../../content/guide/providers.md) and
208
+ [Local LLM setup](../../content/guide/local-llm.md).
805
209
 
806
- 1. **Deny list** — if any deny pattern matches, the action is blocked
807
- 2. **Allow list** — if any allow pattern matches, the action is auto-approved
808
- 3. **Mode policy** — the active permission mode determines the decision
809
-
810
- ### Permission Modes
811
-
812
- | Mode | Read/Glob/Grep | Write/Edit | Bash |
813
- | ------------------- | :------------: | :--------: | :-----: |
814
- | `plan` | auto | deny | deny |
815
- | `default` | auto | approve | approve |
816
- | `acceptEdits` | auto | auto | approve |
817
- | `bypassPermissions` | auto | auto | auto |
818
-
819
- ### Changing Mode at Runtime
820
-
821
- Use the `/permissions` slash command:
822
-
823
- ```
824
- > /permissions # Show mode, rules by settings file, approvals, recent denials
825
- > /permissions plan # Switch to plan (read-only)
826
- > /permissions bypassPermissions # Skip all prompts
827
- ```
210
+ ### Connect MCP servers, or serve Robota over MCP
828
211
 
829
- Or set it at startup:
212
+ Declare remote MCP servers under `mcpServers` in a settings file. A declared server is not connected
213
+ until you approve it: `/mcp` shows each server's state, and `/mcp approve <server>` connects it in
214
+ the running session and keeps the approval in `~/.robota/mcp-approvals.json` for later starts. A
215
+ server that uses OAuth also needs a sign-in — `/mcp login <server>` in a session, or
216
+ `robota mcp login <server>` in a terminal — and connects once you sign in.
830
217
 
831
- ```bash
832
- robota --permission-mode plan
218
+ ```json
219
+ {
220
+ "mcpServers": {
221
+ "docs": { "type": "http", "url": "https://mcp.example.com/mcp", "oauth": {} }
222
+ }
223
+ }
833
224
  ```
834
225
 
835
- ### Permission Patterns
836
-
837
- Configure in `.robota/settings.json` or `.robota/settings.local.json`:
226
+ `robota mcp serve` does the reverse: it serves one Robota session to an MCP host over stdio (or over
227
+ authenticated HTTP with the `--http-*` and `--oauth-*` flags). Trust the project first, and give the
228
+ host the absolute path of `robota` and the project directory:
838
229
 
839
230
  ```json
840
231
  {
841
- "permissions": {
842
- "allow": ["Bash(pnpm *)", "Bash(git status)", "Read(/src/**)"],
843
- "deny": ["Bash(rm -rf *)", "Write(.env)"]
232
+ "mcpServers": {
233
+ "robota": {
234
+ "command": "/absolute/path/to/robota",
235
+ "args": ["mcp", "serve"],
236
+ "cwd": "/absolute/path/to/trusted/project"
237
+ }
844
238
  }
845
239
  }
846
240
  ```
847
241
 
848
- Pattern syntax: `ToolName` matches any invocation; `ToolName(pattern)` matches on the primary argument with shell-style globs (`*`, `**`).
849
-
850
- ## Keyboard Controls
851
-
852
- | Key | Action |
853
- | ---------- | ----------------------------------------------------------- |
854
- | Enter | Submit input |
855
- | Ctrl+R | Search prompt history (see "Prompt history") |
856
- | ESC | Abort current execution (graceful — saves partial response) |
857
- | Ctrl+C | Exit process immediately |
858
- | Up/Down | Navigate visual lines in wrapped multi-line input |
859
- | Arrow keys | Navigate slash command autocomplete, permission prompt |
860
-
861
- ## Paste Handling
242
+ See [MCP](../../content/guide/mcp.md).
862
243
 
863
- Bracketed paste mode (DECSET 2004) is enabled on startup. When pasting multiline text, the input area collapses it into a label: `[Pasted text #1 +42 lines]`. Multiple pastes are numbered sequentially. The full content is expanded on submit.
244
+ ### Add skills, commands, agents and plugins
864
245
 
865
- Single-line paste is inserted directly as typed text. Terminals without bracketed paste fall back to heuristic detection.
246
+ Skills and commands are Markdown files the CLI finds in `.robota/skills/`, `.claude/skills/`,
247
+ `.claude/commands/` and `.agents/skills/` — in a trusted project and under your home directory. Each
248
+ one becomes a slash command (`/<name>`), and `/skills` lists them. Agent definitions are read from
249
+ `.robota/agents/`, `.agents/agents/` and `.claude/agents/`. Plugins bundle these together with hooks,
250
+ themes and MCP servers:
866
251
 
867
- ## Edit Diff Display
868
-
869
- After the Edit tool runs, a `DiffBlock` component renders the change inline:
870
-
871
- ```
872
- ✓ Edit(src/provider.ts)
873
- │ src/provider.ts
874
- │ - const DEFAULT_MAX_TOKENS = 4096;
875
- │ + const maxTokens = getModelMaxOutput(modelId);
252
+ ```text
253
+ /plugin marketplace add <source>
254
+ /plugin install <name>@<marketplace>
255
+ /plugin # open the plugin manager
876
256
  ```
877
257
 
878
- Removed lines appear in red with `-`, added lines in green with `+`. Diffs longer than 10 lines show the first 8 + a `... and N more lines` summary.
879
-
880
- ## Session Management
881
-
882
- The CLI supports continuing, resuming, forking, and naming sessions.
883
-
884
- ### CLI Flags
885
-
886
- | Flag | Description |
887
- | --------------------- | ------------------------------------------------ |
888
- | `-c`, `--continue` | Continue the most recent session |
889
- | `-r`, `--resume <id>` | Resume a specific session by ID |
890
- | `--fork-session <id>` | Fork a session (new session with copied history) |
891
- | `--name <name>` | Assign a name to the session at startup |
892
-
893
- ### TUI Commands
894
-
895
- | Command | Description |
896
- | ---------------- | ----------------------------------- |
897
- | `/resume` | List recent sessions and resume one |
898
- | `/rename <name>` | Rename the current session |
899
-
900
- ### Moving to another directory (`/cd`)
258
+ See the [CLI guide](../../content/guide/cli.md) for skill frontmatter and plugin management.
901
259
 
902
- `/cd <directory>` continues the conversation in another directory. Robota starts again there, as if
903
- launched in that directory: its settings, trust decision, tools, skills and `AGENTS.md` apply, and the
904
- conversation resumes. The system prompt is kept as it was, so a provider's prompt cache survives. One
905
- message tells the model about the new directory and its project instructions.
260
+ ### Use the graphical interface
906
261
 
907
- - `/cd` is refused while a turn is running or a background task is still running, and in a session
908
- started with `--no-session-persistence`, which has no saved conversation to carry.
909
- - A restricted (untrusted) session stays restricted after a move. A trusted session takes the target
910
- directory's own trust decision.
911
- - A `Cd(...)` deny rule keeps sessions out of a directory, for example
912
- `"deny": ["Cd(/secrets/**)"]`.
262
+ `robota --serve --open` starts a headless runtime for the current workspace, serves the Robota GUI
263
+ on `127.0.0.1` and opens it in your browser. The Electron desktop app in this repository
264
+ ([`apps/agent-app`](../../apps/agent-app/docs/README.md)) shows the same GUI over the workspace
265
+ daemon; it is not published to npm. See [The GUI and the Desktop App](../../content/guide/gui.md).
913
266
 
914
- ### Session Name Display
267
+ ### Reach other sessions and your other devices
915
268
 
916
- When a session has a name, it appears in three places:
269
+ `/peers` lists the other live `robota` sessions on this machine, and `/peers send <session-id>
270
+ <message>` sends one a message; the receiving session handles it under its own permissions, like
271
+ its own work. `/handoff <session-id>` moves this conversation to another session once both sides
272
+ confirm. To reach your other devices too, create a device identity with `/devices init`, link each
273
+ new device with `/devices add` and `/devices join`, and set `transports.mesh.enabled` to `true` in
274
+ your user settings. `/remote-control enable` pairs a browser to co-drive the current session.
917
275
 
918
- - **Input border** — session name shown in the input area border
919
- - **Terminal title** — updated via ANSI escape sequences
920
- - **StatusBar** — displayed alongside activity, model, and context usage
276
+ See [Devices and remote control](../../content/guide/devices-and-remote.md).
921
277
 
922
- ## Deep Links
923
-
924
- A `robota://open` link starts a session in a directory you have already trusted, with a prompt
925
- already in the composer and **not** submitted — you read it and press Enter, or clear it.
278
+ ### Check your setup and usage
926
279
 
927
280
  ```bash
928
- robota open 'robota://open?v=1&prompt=Summarize%20the%20README&cwd=/absolute/path/to/repo'
929
- ```
930
-
931
- | Key | Meaning |
932
- | -------- | -------------------------------------------------------------------------------------------------------------------- |
933
- | `v` | Contract version. Required, and must be `1`. |
934
- | `prompt` | The text to prefill. At most 5,000 characters; it may not begin with `/`. |
935
- | `cwd` | Absolute path of the directory to open. |
936
- | `repo` | `owner/name` of an already-trusted local clone, when you do not want to name a path. `cwd` wins if both are present. |
937
-
938
- Everything else is refused, and a refusal discards the whole link, says which rule it broke, writes
939
- to stderr and exits non-zero without starting a session: an unknown key (so a link cannot carry
940
- `provider=`, `permission-mode=`, `plugin=` or any other configuration), a duplicate key, a missing
941
- or different `v`, a link over 8,192 characters, a prompt over 5,000, a prompt beginning with `/`
942
- (one Enter would otherwise run it as a command), a relative, UNC or `..`-bearing path, and a second
943
- link appended after the first (the argv shape a desktop handler can be made to produce). Your own
944
- flags still apply after the link; a trailing token that is neither a link nor a flag is currently
945
- discarded rather than refused — a CLI-wide gap tracked separately, not specific to links.
946
-
947
- The target must already be trusted — `robota trust --yes` in that directory — for `cwd=` exactly as
948
- for `repo=`. A link opens only what you have already approved; it never clones, never fetches, and
949
- never reads a repository you have not trusted. While the composer still holds exactly what the link
950
- supplied, the line `Prompt from an external link` sits below the input, and above 1,000 characters it
951
- adds the character count and asks you to read the whole thing before sending. Edit that text and the
952
- line goes: what is in the composer is then yours, and the label would be claiming otherwise.
953
-
954
- **Known limitations.** No URL scheme is registered with the operating system yet, so a browser
955
- cannot hand the link over: pass it to `robota open` yourself, or point your own handler at that
956
- command. Registering the scheme on macOS, Linux and Windows, and the HTTPS launcher that works
957
- around chat clients stripping custom schemes, are tracked separately.
958
-
959
- ## Slash Commands
960
-
961
- Typing `/` in the TUI opens an autocomplete popup. Arrow keys navigate, Tab inserts without executing, Enter executes. Subcommands (e.g., `/provider list`) show a nested submenu.
962
-
963
- ### Session & Context
964
-
965
- | Command | Description |
966
- | ------------------------- | ------------------------------------------------------------------------------------------------ |
967
- | `/clear` | Clear conversation history |
968
- | `/compact [instructions]` | Compress context window |
969
- | `/context` | Context window details, reference inventory, and auto-compact controls |
970
- | `/cost` | Show session token usage and cost |
971
- | `/effort [level]` | Show or change model effort (`auto`, `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`) |
972
- | `/resume` | List recent sessions and resume one |
973
- | `/rename <name>` | Rename the current session |
974
- | `/cd <directory>` | Move this conversation to another directory (see below) |
975
- | `/rewind` | List, inspect, restore, or rollback edit checkpoints |
976
-
977
- ### Providers & Settings
978
-
979
- | Command | Description |
980
- | ------------------------ | -------------------------------------------------------------------- | ------- | -------------------------------------------------- |
981
- | `/provider [subcommand]` | Manage provider profiles: `list`, `switch`, `add`, `test`, `current` |
982
- | `/mode [mode]` | Show or switch permission mode |
983
- | `/permissions [mode]` | Show permission rules or change permission mode |
984
- | `/sandbox [mode]` | Show or change how shell commands are confined |
985
- | `/settings` | Open transport settings (enable/disable transports) |
986
- | `/language [lang]` | Set response language (ko, en, ja, zh), saves and restarts |
987
- | `/statusline [on | off | reset]` | Configure status-line fields (model, context, git) |
988
-
989
- ### Tools & Memory
990
-
991
- | Command | Description |
992
- | ---------------------- | -------------------------------------------------------------------- |
993
- | `/memory [subcommand]` | Inspect, add, or review project memory entries |
994
- | `/background` | List and control background tasks |
995
- | `/goal <objective>` | Assign an autonomous goal pursued across turns (`status` / `cancel`) |
996
- | `/agent` | Run and manage background subagent jobs |
997
- | `/skills [name]` | List registered skills or activate one by name |
998
- | `/plugin [subcommand]` | Plugin management |
999
-
1000
- ### Git
1001
-
1002
- | Command | Description |
1003
- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
1004
- | `/git status` | Branch plus the staged, unstaged and untracked paths |
1005
- | `/git diff [--staged \| <rev> \| <a>..<b>] [-- <path> ...]` | Unstaged diff, staged diff, or a diff against one or two revisions (each revision is verified first) |
1006
- | `/git commit [<subject>]` | Commit the staged changes after a confirmation listing the message and the staged files |
1007
-
1008
- `/git commit` operates on the staged set only — no `-a`, no paths. The subject must follow the
1009
- Conventional Commits form `<type>[(scope)][!]: <description>`; the type list, the 72-character limit
1010
- and a trailing period are warnings, not refusals. With nothing staged it says so, with the unstaged and
1011
- untracked counts. Headless (`-p`) runs cancel the commit because no confirmation can be asked for.
1012
- Other git flags are not accepted — `/shell git ...` remains the way to run arbitrary git.
1013
-
1014
- ### Sessions on this host
1015
-
1016
- | Command | Description |
1017
- | ------------------------------------ | ----------------------------------------- |
1018
- | `/peers` | List the other live sessions on this host |
1019
- | `/peers send <session-id> <message>` | Send a message to one of them |
1020
-
1021
- See [Talking to another session](#talking-to-another-session) for the flow and what happens when the
1022
- other session is busy.
1023
-
1024
- ### Utility
1025
-
1026
- | Command | Description |
1027
- | -------- | -------------------------------------------------- |
1028
- | `/help` | Show available commands |
1029
- | `/reset` | Delete user settings and return to first-run state |
1030
- | `/exit` | Exit CLI |
1031
-
1032
- Skill commands from the CLI's ordered `.robota/skills/`, `.claude/skills/`, `.claude/commands/`, and
1033
- `.agents/skills/` roots appear alongside built-in commands.
1034
-
1035
- ## Talking to another session
1036
-
1037
- Two `robota` sessions running on the same host, as the same user, can see and address each other.
1038
- Nothing crosses a machine boundary and nothing is configured — a session becomes discoverable by
1039
- being alive and stops being discoverable when it exits.
1040
-
1041
- ### Seeing who is there
1042
-
1043
- ```
1044
- /peers
1045
- ```
1046
-
1047
- With nothing else running:
1048
-
1049
- ```
1050
- No other live session is announced. Start a second session on this host, as this user,
1051
- and it appears here.
1052
- ```
1053
-
1054
- That is a sentence rather than an empty list on purpose: "no one is there" and "discovery is not
1055
- working" are different answers, and an empty list cannot tell you which one you got.
1056
-
1057
- With a second session up:
1058
-
281
+ robota doctor # settings layers, provider, trust, storage, plugins, hooks, MCP
282
+ robota usage # sessions, turns, tokens and cost for the last 7 days (--period 30d)
283
+ robota --check-update # is a newer version on npm?
284
+ robota eval <definition> # run an evals-as-code definition; exits 1 on a metric breach
1059
285
  ```
1060
- Live sessions:
1061
- b8319b98-bb7b-486c-a499-cf1585b39e61 (this session)
1062
- 97ffafe3-f770-4877-90a4-bd86df6be010
1063
286
 
1064
- Send to one: /peers send <session-id> <message>
1065
- ```
1066
-
1067
- ### Sending
1068
-
1069
- ```
1070
- /peers send 97ffafe3-f770-4877-90a4-bd86df6be010 rerun the failing suite
1071
- ```
1072
-
1073
- The message becomes a **turn** in the other session — the agent there answers it as if the operator
1074
- had typed it. The sender is told which of four things happened, as a sentence:
1075
-
1076
- | Outcome | What it means |
1077
- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
1078
- | `Delivered to <id>.` | It arrived and started. |
1079
- | `<id> has the message; it is waiting behind work already running there.` | It arrived and is queued. Deliberately **not** reported as delivered. |
1080
- | `<id> had already seen that message.` | A retry that the receiver recognised; it does not run twice. |
1081
- | `Not delivered to <id>. <reason>` | Nothing ran. The reason names the target, e.g. that no such session is announced. |
1082
-
1083
- The second row is the one worth knowing. Reporting a queued message as delivered would hide a wait
1084
- the operator can otherwise see and act on.
1085
-
1086
- ### What the receiving operator sees
1087
-
1088
- The message is attributed to the sender, not to whoever is sitting at the receiving terminal:
1089
-
1090
- ```
1091
- peer:b8319b98-bb7b-486c-a499-cf1585b39e61:
1092
- rerun the failing suite
1093
- Robota:
1094
- …
1095
- ```
1096
-
1097
- Your own turns still read `You:`. The name in the label is **derived from the peer's session id** —
1098
- it is not a display name the sender chose, because a name the transcript's reader trusts must not be
1099
- picked by the party being named. It is display attribution only: nothing anywhere uses it to decide
1100
- what a turn is allowed to do.
287
+ ## Configuration files
1101
288
 
1102
- ### When the other session is busy
289
+ Settings are merged from these files, lowest priority first. The two user files always apply; the
290
+ four project files apply only in a trusted workspace.
1103
291
 
1104
- A message arriving mid-turn joins that session's existing pending queue rather than interrupting or
1105
- opening a second one. Three things follow from how that queue works:
292
+ | File | Scope |
293
+ | ----------------------------- | --------------------------------------- |
294
+ | `~/.robota/settings.json` | user |
295
+ | `~/.claude/settings.json` | user (Claude Code-compatible) |
296
+ | `.robota/settings.json` | project, committed |
297
+ | `.robota/settings.local.json` | project, local to this machine |
298
+ | `.claude/settings.json` | project (Claude Code-compatible) |
299
+ | `.claude/settings.local.json` | project, local (Claude Code-compatible) |
1106
300
 
1107
- - **Consecutive messages from the same sender coalesce**, last one wins. That is right for a person
1108
- retyping and wrong for a peer saying two separate things. The replaced one is not swallowed: it
1109
- settles as refused with the reason `coalesced`, so the sender learns it never ran rather than
1110
- assuming both did. Tracked as `PEER-003`.
1111
- - **Messages from different senders do not coalesce** — they queue in arrival order.
1112
- - **The queue holds 32.** Beyond that a message settles as refused with the reason `dropped`.
1113
- - **A cleared queue** — abort, cancel, or shutdown — settles the waiting entries with `cancelled`.
301
+ Other files the CLI keeps under `~/.robota/`:
1114
302
 
1115
- In every case the submission that never became a turn says so. A message that arrived and was then
1116
- displaced is a different thing from one that ran, and the sender is told which it got.
303
+ | Path | Contents |
304
+ | ---------------------- | ----------------------------------------------------------------------- |
305
+ | `workspace-trust.json` | workspaces you have trusted |
306
+ | `sessions/` | saved sessions (a trusted project may keep them in `.robota/sessions/`) |
307
+ | `history.jsonl` | prompts you typed, for `Ctrl+R` (`"promptHistory": false` turns it off) |
308
+ | `keybindings.json` | key bindings |
309
+ | `themes/` | your own `/theme` themes |
310
+ | `plugins/` | installed plugins (a project may have its own `.robota/plugins/`) |
311
+ | `mcp-credentials/` | OAuth tokens for MCP servers, readable by you only |
1117
312
 
1118
- ### Limits
313
+ ## Use it from code
1119
314
 
1120
- - Same host, same user. There is no network path here.
1121
- - A session that exits removes its own entry; a crashed one is reaped by the next session that looks.
1122
- - Session ids are what you address. There are no aliases.
315
+ The package also exports `startCli`, the function the `robota` executable runs, and its options
316
+ type `IStartCliOptions`. The package is ESM-only: load it with `import`, not `require()`.
1123
317
 
1124
- ## Plugin Management
318
+ A subagent runs in a child process that starts your entry script again with a worker flag; when
319
+ `startCli()` sees that flag it runs the subagent instead of the CLI, and the returned promise never
320
+ settles (the worker ends the process). So call `startCli()` from the script Node started, and put
321
+ nothing before it that must not run once per subagent.
1125
322
 
1126
- The `/plugin` command opens an interactive TUI or runs plugin operations through the injected plugin command module:
323
+ ## Work on the CLI in this repository
1127
324
 
1128
- | Subcommand | Description |
1129
- | ---------------------------------------- | ------------------------------------- |
1130
- | `/plugin` or `/plugin manage` | Open the plugin manager TUI |
1131
- | `/plugin install <name>@<marketplace>` | Install a plugin from a marketplace |
1132
- | `/plugin uninstall <name>@<marketplace>` | Remove an installed plugin |
1133
- | `/plugin enable <name>@<marketplace>` | Enable a disabled plugin |
1134
- | `/plugin disable <name>@<marketplace>` | Disable a plugin without uninstalling |
1135
- | `/plugin marketplace add <source>` | Add a marketplace source |
1136
- | `/plugin marketplace remove <name>` | Remove a marketplace source |
1137
- | `/plugin marketplace update <name>` | Update a marketplace source |
1138
- | `/plugin marketplace list` | List configured marketplace sources |
1139
-
1140
- ## Configuration
1141
-
1142
- Settings are merged in this order, from lowest to highest priority:
1143
-
1144
- 1. `~/.robota/settings.json` (user global)
1145
- 2. `~/.claude/settings.json` (user global, Claude Code compatible)
1146
- 3. `.robota/settings.json` (project, shared)
1147
- 4. `.robota/settings.local.json` (local, gitignored)
1148
- 5. `.claude/settings.json` (project, Claude Code compatible)
1149
- 6. `.claude/settings.local.json` (local, gitignored, Claude Code compatible)
1150
-
1151
- The two user layers are always host-owned. The four project layers participate only when the CLI host
1152
- supplies trusted project access; Restricted composition does not probe them. Project writes require a
1153
- separately approved settings writer for the same authority.
1154
-
1155
- ### Provider endpoints and trust
1156
-
1157
- Project settings apply only in a trusted workspace (see [Workspace Trust](#workspace-trust)).
1158
- `robota doctor` reports trust and endpoint provenance without printing credentials. If a lower-trust
1159
- settings layer changes a provider endpoint without providing its own key, Robota removes the inherited
1160
- key and reports `provider endpoint quarantined`.
1161
-
1162
- ```json
1163
- {
1164
- "defaultMode": "default",
1165
- "language": "en",
1166
- "currentProvider": "qwen-plus",
1167
- "providers": {
1168
- "qwen-plus": {
1169
- "type": "qwen",
1170
- "model": "qwen-plus",
1171
- "apiKey": "$ENV:DASHSCOPE_API_KEY",
1172
- "baseURL": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
1173
- },
1174
- "supergemma4-26b-uncensored-v2": {
1175
- "type": "gemma",
1176
- "model": "supergemma4-26b-uncensored-v2",
1177
- "apiKey": "lm-studio",
1178
- "baseURL": "http://localhost:1234/v1"
1179
- },
1180
- "gpt-4o": {
1181
- "type": "openai",
1182
- "model": "gpt-4o",
1183
- "apiKey": "$ENV:OPENAI_API_KEY"
1184
- },
1185
- "claude-sonnet-4-6": {
1186
- "type": "anthropic",
1187
- "model": "claude-sonnet-4-6",
1188
- "apiKey": "$ENV:ANTHROPIC_API_KEY"
1189
- }
1190
- },
1191
- "permissions": {
1192
- "allow": ["Bash(pnpm *)"],
1193
- "deny": ["Bash(rm -rf *)"]
1194
- }
1195
- }
1196
- ```
1197
-
1198
- `currentProvider` selects a profile key from `providers`. The key is the stable profile identity, not
1199
- the provider type; multiple profile keys may use the same provider type and model when they represent
1200
- different credentials, endpoints, accounts, or operational defaults. Qwen Model Studio profiles use
1201
- `type: "qwen"` with a DashScope-compatible `baseURL`; the API key is usually stored as
1202
- `$ENV:DASHSCOPE_API_KEY`. DeepSeek profiles use `type: "deepseek"` with
1203
- `https://api.deepseek.com` and `$ENV:DEEPSEEK_API_KEY`. Gemma-family LM Studio models use
1204
- `type: "gemma"` so Robota can apply Gemma-specific channel-marker projection while still talking to
1205
- the OpenAI-compatible `/v1/chat/completions` API through `baseURL`. Generic OpenAI-compatible profiles use
1206
- `type: "openai"` and do not apply provider-specific projection. Use `--provider <profile>` for a
1207
- one-shot invocation override; add `--set-current` only when the selected profile should become the
1208
- persisted default. The legacy single-provider shape remains supported:
1209
-
1210
- ```json
1211
- {
1212
- "provider": {
1213
- "name": "anthropic",
1214
- "model": "claude-sonnet-4-6",
1215
- "apiKey": "$ENV:ANTHROPIC_API_KEY"
1216
- }
1217
- }
1218
- ```
1219
-
1220
- ## Context Discovery
1221
-
1222
- With trusted project access, the CLI discovers and loads:
1223
-
1224
- - **AGENTS.md** — walking only within the authenticated worktree root
1225
- - **CLAUDE.md** — the same root-bounded discovery
1226
- - **Project metadata** — from `package.json`, `tsconfig.json`
1227
-
1228
- All context is assembled into the system prompt.
1229
-
1230
- Ordinary prompts may also reference workspace-local files with path-like `@file` tokens, for
1231
- example `@AGENTS.md` or `@docs/SPEC.md`. The CLI passes those prompts through unchanged; the SDK
1232
- resolves bounded file content through the accepted project reader, sends the enriched prompt to the model, and
1233
- records a structured file-reference event in the session history.
1234
-
1235
- ## Memory Management
1236
-
1237
- - **Message windowing** — React state keeps the most recent 100 messages. Older messages are dropped from the render tree; full history remains in the session store.
1238
- - **Tool state cleanup** — Completed tool execution states are trimmed to the most recent 50 entries.
1239
- - **React.memo** — `MessageItem` uses `React.memo` to skip redundant re-renders.
1240
-
1241
- ## Session Logging
1242
-
1243
- Trusted composition writes project session logs and resumable records through authority-backed
1244
- `session-logs` and `sessions` state facets. Restricted composition opens no project log path and uses
1245
- the user session store instead. Session records include messages, UI history, the exact system prompt,
1246
- registered tool schemas, and background task snapshots; high-frequency streaming chunks remain in
1247
- the separately injected JSONL sink.
1248
-
1249
- ## Architecture
1250
-
1251
- The CLI is a pure TUI layer. All business logic lives in `@robota-sdk/agent-framework`'s `InteractiveSession`. `useInteractiveSession` is the sole React↔SDK bridge, converting SDK events to React state.
1252
-
1253
- ```
1254
- bin.ts → cli.ts (arg parsing)
1255
- └── ui/render.tsx → App.tsx (thin JSX shell)
1256
- ├── useInteractiveSession (ONLY React↔SDK bridge)
1257
- │ ├── InteractiveSession (SDK)
1258
- │ ├── CommandRegistry (SDK, re-exported by CLI)
1259
- │ │ ├── BuiltinCommandSource (SDK, empty by default)
1260
- │ │ ├── agent-command (./skills) (/skills command + virtual skill aliases)
1261
- │ │ ├── PluginCommandSource (SDK, plugin skills)
1262
- │ │ └── ICommandModule sources (/help, /compact, ...)
1263
- │ └── SystemCommandExecutor (SDK)
1264
- ├── plugin-hooks-merger.ts (merges plugin hooks into SDK config)
1265
- ├── MessageList.tsx
1266
- ├── InputArea.tsx (CjkTextInput, bracketed paste, slash detection)
1267
- ├── StatusBar.tsx (activity, conditional mode, model, context %)
1268
- ├── PermissionPrompt.tsx (arrow-key Allow/Deny)
1269
- ├── SlashAutocomplete.tsx (command popup with scroll)
1270
- ├── DiffBlock.tsx (Edit tool diff display)
1271
- ├── MenuSelect.tsx (arrow-key menu, Plugin TUI)
1272
- ├── PluginTUI.tsx (plugin management screen stack)
1273
- ├── TextPrompt.tsx (text input for Plugin TUI)
1274
- └── ConfirmPrompt.tsx (reusable yes/no prompt)
325
+ ```bash
326
+ pnpm install && pnpm build
327
+ pnpm cli:dev # run the CLI from source
328
+ pnpm cli:trust # trust this repository for the source CLI
1275
329
  ```
1276
330
 
1277
- ## Dependencies
1278
-
1279
- | Package | Purpose |
1280
- | ----------------------------- | ----------------------------------------------------------------------------- |
1281
- | `@robota-sdk/agent-framework` | Session factory, query, config, context |
1282
- | `@robota-sdk/agent-core` | Types (TPermissionMode, TToolArgs) |
1283
- | `@robota-sdk/agent-framework` | Headless runner and registry for print mode (`-p`); terminal I/O is CLI-local |
1284
- | `ink` 7, `react` 19.2+ | TUI rendering |
1285
- | `chalk` | Terminal colors |
1286
- | `marked`, `marked-terminal` | Markdown parsing and terminal rendering |
1287
- | `string-width` | Unicode-aware string width (CJK support) |
1288
-
1289
331
  ## Documentation
1290
332
 
1291
- See [docs/SPEC.md](./docs/SPEC.md) for the full specification, architecture details, and design decisions.
333
+ - [CLI guide](../../content/guide/cli.md) — every command, flag and setting
334
+ - [Sessions and the daemon](../../content/guide/sessions-and-daemon.md)
335
+ - [Permissions and hooks](../../content/guide/permissions-and-hooks.md)
336
+ - [Providers](../../content/guide/providers.md) and [Local LLM setup](../../content/guide/local-llm.md)
337
+ - [MCP](../../content/guide/mcp.md)
338
+ - [Devices and remote control](../../content/guide/devices-and-remote.md)
339
+ - [SPEC.md](./docs/SPEC.md) — what this package owns and guarantees
1292
340
 
1293
341
  ## License
1294
342
 
1295
- Robota is dual-licensed under the [GNU AGPL-3.0](../../LICENSE) or a [commercial license](../../COMMERCIAL.md). See [LICENSING.md](../../LICENSING.md).
343
+ Robota is dual-licensed under the [GNU AGPL-3.0](../../LICENSE) or a
344
+ [commercial license](../../COMMERCIAL.md). See [LICENSING.md](../../LICENSING.md).