@aragon-agent/cli 0.5.4 → 0.5.6

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 (60) hide show
  1. package/CHANGELOG.md +670 -577
  2. package/README.md +1105 -995
  3. package/dist/agent/controller.d.ts +71 -1
  4. package/dist/agent/controller.js +135 -1
  5. package/dist/agent/headless.d.ts +29 -0
  6. package/dist/agent/headless.js +86 -1
  7. package/dist/agent/reducer.d.ts +45 -0
  8. package/dist/agent/reducer.js +80 -1
  9. package/dist/agent/system-prompt.d.ts +8 -0
  10. package/dist/agent/system-prompt.js +2 -0
  11. package/dist/cli.js +40 -1
  12. package/dist/commands/builtins.js +166 -0
  13. package/dist/commands/registry.d.ts +27 -1
  14. package/dist/config/env.js +43 -0
  15. package/dist/config/load.d.ts +27 -0
  16. package/dist/config/load.js +25 -1
  17. package/dist/config/schema.d.ts +47 -0
  18. package/dist/config/schema.js +38 -0
  19. package/dist/config/store.js +10 -1
  20. package/dist/session/persist.d.ts +19 -1
  21. package/dist/session/persist.js +21 -2
  22. package/dist/team/subagent.js +11 -0
  23. package/dist/todo/follow-through.d.ts +117 -0
  24. package/dist/todo/follow-through.js +186 -0
  25. package/dist/todo/limits.d.ts +114 -0
  26. package/dist/todo/limits.js +114 -0
  27. package/dist/todo/normalize.d.ts +33 -0
  28. package/dist/todo/normalize.js +117 -0
  29. package/dist/todo/panel-rows.d.ts +53 -0
  30. package/dist/todo/panel-rows.js +103 -0
  31. package/dist/todo/prompt.d.ts +54 -0
  32. package/dist/todo/prompt.js +76 -0
  33. package/dist/todo/store.d.ts +86 -0
  34. package/dist/todo/store.js +183 -0
  35. package/dist/todo/todo-tool.d.ts +38 -0
  36. package/dist/todo/todo-tool.js +134 -0
  37. package/dist/todo/types.d.ts +62 -0
  38. package/dist/todo/types.js +12 -0
  39. package/dist/tools/index.d.ts +16 -2
  40. package/dist/tools/index.js +13 -0
  41. package/dist/ui/App.js +244 -7
  42. package/dist/ui/StatusBar.d.ts +18 -0
  43. package/dist/ui/StatusBar.js +5 -2
  44. package/dist/ui/TodoPanel.d.ts +48 -0
  45. package/dist/ui/TodoPanel.js +74 -0
  46. package/dist/ui/TodoStrip.d.ts +36 -0
  47. package/dist/ui/TodoStrip.js +32 -0
  48. package/dist/ui/Transcript.js +11 -1
  49. package/dist/ui/entries/TodoCard.d.ts +31 -0
  50. package/dist/ui/entries/TodoCard.js +35 -0
  51. package/dist/ui/glyphs.d.ts +12 -0
  52. package/dist/ui/glyphs.js +8 -0
  53. package/dist/ui/layout/AppShell.d.ts +34 -1
  54. package/dist/ui/layout/AppShell.js +3 -3
  55. package/dist/ui/layout/ScrollViewport.d.ts +13 -1
  56. package/dist/ui/layout/ScrollViewport.js +2 -2
  57. package/dist/ui/layout/rail.d.ts +56 -0
  58. package/dist/ui/layout/rail.js +68 -0
  59. package/dist/ui/overlays/HelpOverlay.js +4 -0
  60. package/package.json +3 -2
package/README.md CHANGED
@@ -1,995 +1,1105 @@
1
- # @aragon-agent/cli
2
-
3
- A Claude-Code / Codex-style **interactive terminal UI** for the
4
- [`@aragon-agent/core`](../core) engine. Type a task, watch the agent think and
5
- stream its answer, see each tool call render as a live card, and interrupt /
6
- steer / configure the session — all without leaving the terminal.
7
-
8
- > ⚠️ **Full permission, no sandbox.** This first version runs at **maximum
9
- > permission**: `bash` executes directly, file writes hit the real filesystem,
10
- > and there is **no per-action approval gate by default**. Only run it in
11
- > workspaces you trust. An opt-in confirmation mode is available via `--confirm`.
12
-
13
- ## Install
14
-
15
- ```bash
16
- # Global
17
- npm i -g @aragon-agent/cli
18
- aragon
19
-
20
- # Zero-install
21
- npx @aragon-agent/cli
22
- ```
23
-
24
- > **Release note (R3):** `npx @aragon-agent/cli` resolves `@aragon-agent/core`
25
- > from the npm registry, so `@aragon-agent/core@^0.1.0` must be published first
26
- > (or bundled into the CLI `dist`). During local development inside this
27
- > monorepo, npm workspaces link the local `packages/core` automatically.
28
-
29
- ## Quick start
30
-
31
- ```bash
32
- # Set a key (any one of these)
33
- export ANTHROPIC_API_KEY=sk-ant-...
34
- # ...or paste it live in the TUI via /settings, or pass --api-key
35
-
36
- aragon # interactive TUI
37
- aragon "summarize README.md" # interactive, auto-submitting the prompt
38
- echo "list files" | aragon -p # one-shot, prints to stdout then exits
39
- ```
40
-
41
- ## Usage
42
-
43
- ```
44
- aragon [prompt] Start the interactive TUI (or one-shot with -p / piped stdin)
45
- aragon -p, --print [prompt] Headless: stream the answer to stdout, then exit
46
- aragon config Open the settings screen
47
- aragon config set <key> <value> Non-interactive config write (e.g. model, provider)
48
- aragon config path Print the config file path
49
- aragon models [--provider p] List builtin + discovered models
50
- aragon --version | -v Print the version
51
- aragon --help | -h Print help
52
- ```
53
-
54
- ### Global flags
55
-
56
- | Flag | Meaning |
57
- | --- | --- |
58
- | `--provider <id>` | `anthropic` \| `openai` \| `google` |
59
- | `--model <id>` | Model id |
60
- | `--base-url <url>` | Override the provider base URL (OpenAI-compatible endpoints) |
61
- | `--api-key <key>` | One-shot key override (not persisted) |
62
- | `--thinking <level>` | `off\|minimal\|low\|medium\|high\|xhigh` |
63
- | `--max-tokens <n\|auto>` | Output token cap: a number, or `auto` for the model's own ceiling (default `64000`) |
64
- | `--cwd <dir>` | Working directory for tools |
65
- | `--confirm` | Confirm each mutating tool call |
66
- | `--tool-timeout <ms>` | Per-tool executor ceiling (default 180000) |
67
- | `--idle-timeout <ms>` | Watchdog idle timeout (auto-raised to ≥ tool-timeout + 30s) |
68
- | `--theme <name>` | `auto\|warm\|cool\|light` (`dark` is a compatibility alias for `cool`) |
69
- | `--compact` / `--no-compact` | Transcript density: no blank rows between turns, or the default |
70
- | `--hints` / `--no-hints` | Show or hide the composer hint row |
71
- | `--fullscreen` | Force the full-screen TUI, overriding the automatic downgrades |
72
- | `--no-fullscreen` | Force the inline renderer (the 0.2.0 behavior) |
73
- | `--no-exit-transcript` | Do not replay the session summary after exiting |
74
- | `--plan` / `--no-plan` | Start the session in PLAN mode (read-only research + review) or force BUILD |
75
- | `--team` / `--no-team` | Enable or disable team subagents. `--no-team` does not register the `task` tool at all |
76
- | `--team-max <n>` | Max subagents per dispatch for this run, clamped to `[1, 10]` |
77
- | `--no-skills` | Disable the skill system entirely (no catalog, no skill tools) |
78
- | `--skill <name>` | Force-load a skill's full body for this run (repeatable) |
79
- | `--skills-yes` | Approve skill installs for this run (CI / headless) |
80
- | `--no-color` | Disable ANSI color |
81
- | `--quiet` | (print mode) suppress tool/usage lines on stderr |
82
-
83
- Exit codes: `0` success · `1` agent/runtime error · `2` config/usage error ·
84
- `130` interrupted.
85
-
86
- ## Slash commands (interactive)
87
-
88
- | Command | Action |
89
- | --- | --- |
90
- | `/help` | Keybindings + command help |
91
- | `/model` | Open the model picker |
92
- | `/settings` | Open the settings screen |
93
- | `/thinking <level>` | Set the thinking level |
94
- | `/max-tokens [<n>\|auto\|default]` | Output token cap; with no argument, reports the setting **and** the effective cap for the live model |
95
- | `/tools` | List active tools |
96
- | `/clear` | Clear the visible transcript |
97
- | `/reset` | New conversation |
98
- | `/cwd [dir]` | Show / change the tool working directory |
99
- | `/save [file]` | Save the session to JSON |
100
- | `/resume [file]` | Load a saved session |
101
- | `/plan [on\|off\|status]` | Toggle plan mode - the keyboard-free equivalent of `Shift+Tab` |
102
- | `/team [on\|off\|max <n>]` | Team subagents: status and the live roster, the on/off switch, the fan-out width |
103
- | `/theme <auto\|warm\|cool\|light>` | Switch the color theme live (persisted) |
104
- | `/expand [n]` | Expand / collapse the n-th-from-last (default last) tool card |
105
- | `/copy` | Copy the last answer to the clipboard |
106
- | `/logs` | Show where this session is being logged, and at what level |
107
- | `/reload` | Re-read `config.json` after editing it in another window (refused mid-run) |
108
- | `/skills [sub]` | List and manage skills (see below) |
109
- | `/<skill-name> [args]` | Run an installed skill directly |
110
- | `/exit` (`/quit`) | Exit |
111
-
112
- A leading `/` meant literally can be escaped as `//`. Typing `/` opens a
113
- command palette and `@` opens a file-path completion popup — `Tab` / `→`
114
- completes, `Up` / `Down` moves the selection, `Esc` closes it.
115
-
116
- ## Keybindings
117
-
118
- | Key | Action |
119
- | --- | --- |
120
- | `Enter` | Submit (idle) / queue a steering message (running) |
121
- | `Alt+Enter` / `Shift+Enter` | Insert a newline |
122
- | `Shift+Tab` | Toggle **plan mode** (`BUILD` <-> `PLAN`). Equivalent: `/plan` |
123
- | `Esc` | Abort the run / close an overlay / close a popup |
124
- | `Ctrl+C` ×2 | Exit (first press warns) |
125
- | `Ctrl+L` | Redraw the frame (clear the screen in inline mode) |
126
- | `Ctrl+T` | Toggle thinking blocks |
127
- | `Ctrl+O` | Expand / collapse the most recent tool card |
128
- | `PgUp` / `PgDn` | Scroll the transcript a page (full-screen mode) |
129
- | `Shift+↑` / `Shift+↓` | Scroll the transcript a line (full-screen mode) |
130
- | Mouse wheel | Scroll the transcript (over the input box: prompt history) |
131
- | `Shift`+wheel | Scroll the transcript a page |
132
- | `Home` / `End`, `Ctrl+A` / `Ctrl+E` | Cursor to line start / end |
133
- | `Alt/Ctrl+←` / `→` | Word-wise cursor jump |
134
- | `Ctrl+W`, `Alt+Backspace` | Delete the previous word |
135
- | `Ctrl+U` / `Ctrl+K` | Kill to line start / end |
136
- | `Up` / `Down` | Move between draft lines; recall prompt history at the edges |
137
- | `?` | Open help (empty input) |
138
-
139
- In **full-screen mode** the transcript is scrolled by the app itself: `PgUp` /
140
- `PgDn` and `Shift+↑` / `Shift+↓`. While pinned to the bottom the viewport follows
141
- new output automatically; once you scroll away the status bar shows `↑N`, a slim
142
- rail on the right edge shows where you are, and a hint counts the lines below
143
- you. Submitting a message always re-pins to the newest output. In **inline mode**
144
- (`--no-fullscreen`) history lives in your terminal's native scrollback, printed
145
- once via Ink `<Static>`.
146
-
147
- **The mouse wheel scrolls whatever is under the pointer.** Over the transcript
148
- it scrolls the transcript three rows per notch (a page with `Shift`); over an
149
- open overlay it scrolls that overlay; over the input box at the bottom it steps
150
- prompt history one entry per notch, exactly as `↑` / `↓` do — including the rule
151
- that an in-progress draft is never overwritten.
152
-
153
- Two things follow from the app owning the mouse, and they are worth knowing
154
- before you meet them:
155
-
156
- - **Selecting text now needs `Shift`+drag** in most terminals. That is the
157
- terminal's convention for "an application is tracking the mouse", not
158
- something this CLI can opt out of while still reading wheel events.
159
- - **`tmux` with `mouse on` keeps the wheel for itself**, so the app never sees
160
- it. `set -g mouse off` hands it back.
161
-
162
- Turn the whole thing off with `--no-mouse`, `ARAGON_MOUSE=0`, or
163
- `aragon config set mouse false`. With mouse support off the wheel simply does
164
- nothing in full-screen mode — it does not fall back to editing your draft, which
165
- is what it used to do. Inline mode is unaffected either way.
166
-
167
- ## Themes
168
-
169
- Three palettes plus `auto`:
170
-
171
- | Name | Look |
172
- | --- | --- |
173
- | `warm` | Terracotta + amber over warm neutrals. The default (`auto` resolves here). |
174
- | `cool` | The blue/violet palette that shipped as `dark` through 0.3.x. |
175
- | `light` | For light terminal backgrounds. |
176
-
177
- `auto` never guesses `light`: terminals cannot report their background reliably,
178
- and guessing wrong makes the app unreadable rather than merely wrong-looking.
179
-
180
- **Migrating from 0.3.x** — `dark` was renamed `cool` when `warm` became the
181
- default, and the old name still works everywhere: `--theme dark`,
182
- `aragon config set theme dark`, `/theme dark`, and a config file containing
183
- `"theme": "dark"` all resolve to `cool`, so a screen you explicitly chose does
184
- not change under you. The value is rewritten as `cool` the next time the config
185
- is saved. Only users who never picked a theme see the new default.
186
-
187
- Density and hints are settable the same three ways: `--compact` / `--no-hints`
188
- for one run, `aragon config set density compact` / `aragon config set hints false`
189
- to persist. The composer hint row also shortens to `? help` on its own after a
190
- few sessions — except while a run is in progress, when it always spells out
191
- `esc abort` in full.
192
-
193
- ## Full-screen mode
194
-
195
- `aragon` takes over the terminal's **alternate screen buffer** — the mechanism
196
- `vim`, `htop`, and `lazygit` use. Two consequences worth knowing:
197
-
198
- - Your previous shell output is **covered, not erased**, and returns untouched
199
- when you exit. Nothing in your scrollback is destroyed.
200
- - The frame is fixed at `rows - 1` tall, which is what keeps the composer and the
201
- status bar at the bottom of the screen even on an empty session.
202
-
203
- On exit the session is replayed into the normal buffer as plain text so the
204
- conversation survives leaving the screen (`--no-exit-transcript` opts out;
205
- `/save` still exports the full JSON).
206
-
207
- It downgrades to the inline renderer automatically when stdout is not a TTY,
208
- `TERM=dumb`, a CI environment variable is set, or the terminal is under 12 rows
209
- or 40 columns. `--fullscreen` overrides all of those except the non-TTY check —
210
- writing screen-control sequences into a pipe or a redirected file is never safe.
211
- `--no-fullscreen`, `ARAGON_FULLSCREEN=0`, or `aragon config set fullscreen false`
212
- opt out permanently.
213
-
214
- The terminal's native scrollback and mouse wheel do not scroll the transcript in
215
- this mode, and mouse tracking is deliberately left off because enabling it costs
216
- text selection and copy in most terminals. If a crash ever strands your terminal
217
- on the alternate screen, `reset` restores it.
218
-
219
- ## Plan mode
220
-
221
- `aragon` has two postures, and `Shift+Tab` switches between them. `/plan` does
222
- the same thing for terminals that swallow the key (see below).
223
-
224
- | Mode | What it means |
225
- | --- | --- |
226
- | `BUILD` | The default, and unchanged from earlier versions: you ask, the agent does it. |
227
- | `PLAN` | Research and design only. The agent reads, asks you the decisions it cannot settle by reading, and submits a plan for approval before anything is written. |
228
-
229
- `BUILD` is right for "rename this symbol". `PLAN` is right for "add multi-tenant
230
- support" — anything where you want to see and steer the *approach* before a
231
- single byte changes.
232
-
233
- ### What plan mode actually stops
234
-
235
- In `PLAN`, five tools are refused at the tool boundary: `write_file`,
236
- `edit_file`, `bash`, `skill_install` and `skill_create`. That is a property of
237
- the wiring, not a promise in a prompt — the refusal happens before the tool runs,
238
- so the badge is telling you something that is structurally true.
239
-
240
- **`bash` is refused in full, `git status` included.** This is the most likely
241
- question this feature generates, so: there is no reliable way to classify a shell
242
- string as read-only, and a gate that is right 95 % of the time is worse than one
243
- that is always right, because you stop trusting the badge. Use `read_file`,
244
- `list_dir`, `glob` and `grep`, which stay available; the refusal message names
245
- them so the agent has somewhere to go. There is no allowlist and there will not
246
- be one built out of substring matching.
247
-
248
- Everything else still works: reading, searching, loading skills, and asking you
249
- questions.
250
-
251
- ### The two plan-mode tools
252
-
253
- - **`ask_user`** renders a keyboard-driven wizard: 1–5 questions, 2–4 options
254
- each, exactly one marked `RECOMMENDED`, plus an `Other…` free-text option.
255
- `↑`/`↓` choose, `Enter` confirms and advances, `←` goes back, `Esc` cancels.
256
- Pressing `Enter` through the whole wizard accepts every recommendation, which
257
- is a deliberate "use your judgement" path rather than an accident. A last
258
- screen lets you review before submitting.
259
- - **`submit_plan`** renders the plan as a scrollable card: `a` approves, `r`
260
- opens a one-line feedback field (`Enter` sends it, `←` goes back to the card),
261
- `Esc` dismisses. **Approving flips the session to `BUILD` immediately and the
262
- same run continues straight into implementation** — you do not have to re-ask.
263
-
264
- Cancelling either one is not an error. The agent is told to proceed on stated
265
- assumptions rather than to retry.
266
-
267
- **Dismissing a plan is not how you stop.** `Esc` on a plan card asks for a
268
- *better* plan — the agent is told to refine it and submit again — so a model that
269
- keeps submitting keeps getting cards. To stop the run itself, **press `Esc`
270
- twice**: the first closes the card, the second aborts the run. `Ctrl+C` twice
271
- still exits the session outright.
272
-
273
- ### Where the mode is shown
274
-
275
- The status bar always names a non-default mode (`PLAN`, or `PLAN → BUILD` while
276
- a switch is pending), including on short terminals, with `--no-hints`, and in
277
- inline mode. The composer additionally shows a `PLAN` chip on its hint row and
278
- tints its border, where there is room for it.
279
-
280
- ### Switching mid-run
281
-
282
- Tightening applies at once; loosening waits. Pressing `Shift+Tab` toward `PLAN`
283
- during a run takes effect immediately — you pressed it *because* the agent is
284
- about to do something. Pressing it toward `BUILD` during a run is **deferred to
285
- the end of the run**, so a session you launched under a read-only guarantee
286
- cannot start writing files because of one stray keypress. The status bar shows
287
- `PLAN → BUILD` while that is pending. Approving a plan is the one exception, and
288
- it is an informed act rather than a stray keypress.
289
-
290
- ### If `Shift+Tab` does nothing
291
-
292
- Some multiplexers and remote-desktop stacks never send `CSI Z`. `/plan`,
293
- `/plan on`, `/plan off` and `/plan status` are first-class equivalents, and
294
- `--plan` / `ARAGON_PLAN=1` / `planModeDefault` cover the startup case.
295
-
296
- ### Headless
297
-
298
- `aragon -p --plan "add SSO"` is a useful combination: the read-only gate still
299
- applies, so it means *"tell me how you would do this, and do not touch my
300
- repository"*. No question or plan tool is registered there — there is nobody to
301
- ask — and the agent writes the finished plan as its final message in markdown.
302
-
303
- ## Built-in tools
304
-
305
- `read_file`, `write_file`, `edit_file`, `list_dir`, `glob`, `grep`, `bash` — all
306
- at full permission. Paths resolve against the session working directory. The
307
- per-tool timeout and 100 KB output truncation are enforced by the core executor;
308
- `bash`'s `timeout` param only shrinks within that ceiling.
309
-
310
- When skills are enabled, four more are added — the two read tools `skill` and
311
- `skill_find`, then the two mutating ones `skill_install` and `skill_create` (see
312
- below). `--no-skills` removes them again. Updating a skill is deliberately not
313
- among them; that is a `aragon skills update` decision for you, not the model.
314
-
315
- With team mode on — which is the default — one more is added: `task`, the
316
- delegation tool (see below). `--no-team` removes it again.
317
-
318
- ## Team subagents
319
-
320
- The agent can hand parts of a job to short-lived **subagents** that run in
321
- parallel, then read one combined report of what they each found. It decides for
322
- itself whether to delegate; a session that never calls `task` is the session
323
- that existed before this feature.
324
-
325
- ```
326
- > read the auth middleware, map the route table, and check the migration
327
-
328
- ◆ team 2 running · 1 done · 84s ✉ 1
329
- ⠋ a1 read the auth middleware tool: grep 22.1s
330
- ⠙ a2 map the route table thinking 31.6s
331
- ✔ a3 check the migration done 4 turns 18.2s
332
- ```
333
-
334
- **What a subagent is.** A full agent with its own message history, the same
335
- tools and the same working directory — but no memory of your conversation, so
336
- its brief has to be self-contained. It cannot delegate further (depth is capped
337
- at one by construction) and it cannot ask you anything.
338
-
339
- **Delegate wide, not deep.** Two to four subagents is the usual size. The
340
- default cap is 5 per dispatch and 3 in flight at once; the hard maximum is 10
341
- and no config file can raise it. `maxConcurrent` is separate from `maxSubagents`
342
- on purpose: five simultaneous streams against one API key is a reliable way to
343
- collect rate-limit errors, and each one would land in the report as a failed
344
- subagent.
345
-
346
- **The report.** One text result: a header with the ok/failed counts, aggregate
347
- tokens and cost, then one section per subagent with its status, timing, the
348
- files it wrote and its own final summary. A failed subagent never fails the
349
- dispatch — partial results are the normal outcome of a fan-out. When two
350
- subagents wrote the same file the header says so, phrased as a warning rather
351
- than a fact: the file list is derived from `write_file` / `edit_file`, so a
352
- write made through `bash` is invisible to it.
353
-
354
- **Talking to each other.** Subagents get two extra tools, `team_send` and
355
- `team_wait`. Messages are delivered by attaching them to the recipient's next
356
- tool result, so nobody has to poll and nobody's work is interrupted. The limits
357
- are the point rather than an afterthought: **6 messages per subagent, one every
358
- 15 seconds, 24 per dispatch**, a broadcast costing two. Every refusal is a plain
359
- result naming the limit, never an error — an error reads as a malfunction and
360
- invites a retry, which is the behaviour the limits exist to prevent.
361
-
362
- **What you see.** While a dispatch runs, the roster above sits between the
363
- transcript and the composer and the status bar carries `agents 3/5` (`[3]` under
364
- 100 columns). Both disappear when it finishes and the transcript keeps a card
365
- with the per-agent results; `Ctrl+O` expands it to their summaries. Under `-p`
366
- the same transitions go to stderr as `[team] a1 ok 22.1s 7 tools` lines, and the
367
- children's tokens are folded into the `[usage]` footer.
368
-
369
- **Interaction with the other subsystems.**
370
-
371
- - **Plan mode reaches one level down.** In PLAN, every subagent is built with
372
- the same read-only gate, so `write_file` / `edit_file` / `bash` are refused
373
- inside them too. Parallel research is plan mode's best use, so `task` itself
374
- is allowed. A single subagent can be pinned read-only with `readOnly: true`
375
- even in a BUILD session — the field can only ever tighten.
376
- - **A skill's `allowed-tools` ceiling reaches one level down too**, so
377
- delegating is not a way around it. Subagents get `skill_find` but not `skill`:
378
- loading a skill mutates a turn-scoped registry the lead owns, and three
379
- subagents doing it at once would narrow what the lead may do next in a way
380
- nothing would report. Always-on skills still reach them.
381
- - **`--confirm`** queues subagent confirmations one at a time, labelled with the
382
- subagent that is asking (`[a2] Write: src/routes.ts`).
383
- - **Logs.** `dispatch_start` and `dispatch_end` are recorded at `info` with the
384
- ok / failed / aborted counts and aggregate usage; phase transitions at
385
- `debug`. A `team_send` subject is recorded, never its body; a subagent's brief
386
- and summary only at `trace`.
387
-
388
- **`Esc`** aborts the whole dispatch — every subagent within a couple of seconds
389
- — and the report comes back marked `ABORTED` with whatever was finished.
390
-
391
- ## Skills
392
-
393
- A skill is a reusable expert procedure stored on disk: a directory containing a
394
- `SKILL.md` (YAML frontmatter + Markdown body) and, optionally, `reference/`,
395
- `scripts/` and `assets/`. The format matches Claude Code's, so an existing
396
- community skill directory works as-is.
397
-
398
- ### Progressive disclosure
399
-
400
- This is the part that makes skills cheap enough to install a lot of:
401
-
402
- | Level | What the model gets | When |
403
- | --- | --- | --- |
404
- | 1 | Name, scope, and a one-line "what + when" | Always, in the system prompt. Whole catalog capped at 6 000 bytes. |
405
- | 2 | The full `SKILL.md` body + a list of bundled files | Only when the model calls `skill(name=…)` |
406
- | 3 | The bundled files themselves | Only when it reads them with `read_file` / `bash` |
407
-
408
- A skill costs a line in the prompt rather than its whole body, so installing
409
- many of them stays cheap. The model decides on its own whether a task matches a
410
- skill — you do not have to name one.
411
-
412
- At Level 2 the file list is split into what to read and what to run, and carries
413
- the skill's absolute root so the model can build real paths. The accompanying
414
- guidance is **platform-specific**: on Windows the `bash` tool runs `cmd.exe`, so
415
- the model is told to run `.py` with `python <abs path>` and that a `.sh` file
416
- will not work — rather than the previous, flatly untrue "run scripts with bash".
417
- `aragon skills doctor` warns when a skill bundles only scripts for the other
418
- shell.
419
-
420
- **How many fit in Level 1 depends on your descriptions, and on the language they
421
- are written in.** The 6 000-byte ceiling is measured in UTF-8 bytes: a terse
422
- English description costs roughly 180 bytes a line (~30 skills), while a Chinese
423
- one at the 220-character cap can cost 660 (~10 skills). Past that point the
424
- catalog truncates.
425
-
426
- Truncation is not a dead end. Entries are ranked before it happens, and what
427
- falls off is still reachable:
428
-
429
- - **Ranking**: scope first (`env` > `project` > `user` > `bundled`), then how
430
- recently and how often you have actually used each skill. A skill you ran an
431
- hour ago outranks one you have never used, so what survives truncation is what
432
- you actually work with — not whatever sorts first alphabetically.
433
- - **`skill_find`**: when the catalog *does* truncate, it says so and names this
434
- tool, which searches everything installed by name, description and keyword.
435
- It only ever searches **what is already on this machine** — it never reaches
436
- the network and never suggests something to install.
437
-
438
- Both are off the critical path: with no usage data recorded the order is exactly
439
- the plain scope-then-name order, and `skill_find` is only advertised in the
440
- prompt when something was actually omitted.
441
-
442
- ### Using them
443
-
444
- ```bash
445
- aragon skills list [--sort=recent] # what is installed
446
- aragon skills info <name> # version, path, provenance, files
447
- aragon skills install <source> [--yes] # dir | git repo | https .md / .zip
448
- aragon skills install github:owner/repo#v1.2.0
449
- aragon skills update <name> [--yes] [--force] [--dry-run]
450
- aragon skills update --all [--yes] # every skill that has an upstream
451
- aragon skills update --check [<name>|--all] # is there a newer version?
452
- aragon skills remove <name> [--yes]
453
- aragon skills create <name> --description "what + when" [--yes]
454
- aragon skills path # user skills directory
455
- aragon skills doctor # validate + detect tampering
456
- aragon skills usage [--json] # local use counters
457
- aragon skills usage --reset --yes # delete them
458
- aragon skills trust <dir> # allow a project skills directory
459
- ```
460
-
461
- #### Updating
462
-
463
- `update` re-fetches a skill from the source recorded in its install manifest,
464
- keeping `installedAt` and recording `updatedAt` + `previousVersion`.
465
-
466
- - **Local edits are protected.** If you have changed an installed skill, `update`
467
- refuses and names the files, *before downloading anything*. `--force`
468
- overwrites them — and **`--force` discards your edits**; copy them out first.
469
- The replaced directory is parked under the staging trash, so a mistake is
470
- recoverable by hand.
471
- - **The host allowlist is re-checked** against your *current*
472
- `skills.allowedHosts`, not the one in force when you installed. Narrowing that
473
- list therefore also constrains updates.
474
- - **`--all` never stops on one failure.** Skills that cannot be updated
475
- (hand-authored, bundled, or locally modified) are reported as `skipped`, which
476
- is not an error; only a genuine I/O or validation failure exits non-zero.
477
- - **`--check` reports `unknown` freely.** It compares the commit recorded at
478
- install time against `git ls-remote`, so it can only answer for git sources
479
- installed by a version that records one. Anything else is honestly reported as
480
- unknown rather than guessed.
481
- - **The model cannot call `update`.** Installing is something you pointed it at;
482
- swapping working instructions for different ones is a maintenance decision and
483
- stays with you.
484
-
485
- Interactively the same lives under `/skills …` (`/skills update <name> [--force]`,
486
- `/skills update --all`, `/skills list --sort=recent`, `/skills policy [mode]`,
487
- `/skills unload`, `/skills usage [--reset]`). Every usable skill also becomes a
488
- slash command:
489
-
490
- ```
491
- /pdf-forms invoice.pdf --flatten
492
- ```
493
-
494
- Both delivery paths share `skills.bodyMaxBytes`, so an oversized `SKILL.md` is
495
- truncated with a visible marker rather than submitted whole. Loading the same
496
- skill twice in one conversation returns a short digest (description, file list,
497
- guidance) instead of a second copy of the body; the model can ask for the full
498
- text back with `skill(name="…", force=true)`.
499
-
500
- Bodies may use `$ARGUMENTS` and `$1..$9`; `$$` is a literal `$`. If a skill
501
- declares no placeholder, the arguments are appended under an `## Arguments`
502
- heading instead. Where a skill's name collides with a built-in command, the
503
- **built-in wins** and the skill is reachable as `/skill:<name>`.
504
-
505
- ### Where skills come from
506
-
507
- Four scopes, later ones overriding earlier ones by name:
508
-
509
- | Scope | Location | Writable |
510
- | --- | --- | --- |
511
- | `bundled` | `<package>/skills/` | no |
512
- | `user` | `aragon skills path` | yes — the default install target |
513
- | `project` | `<cwd>/.aragon/skills/` | yes |
514
- | `project` | `<cwd>/.claude/skills/` | no — read-only Claude Code interop |
515
- | `env` | `ARAGON_SKILLS_PATH` (`:`/`;`-separated) | no |
516
-
517
- A shadowed skill is not lost: `/skills` shows it as `~shadowed by <scope>`.
518
-
519
- ### Frontmatter
520
-
521
- ```yaml
522
- ---
523
- name: pdf-forms # required, kebab-case, ≤ 64 chars
524
- description: Fill, flatten and validate AcroForm PDFs. Use when the task
525
- mentions PDF forms, field filling, or flattening a fillable PDF.
526
- version: 1.2.0
527
- license: MIT
528
- author: Jane Doe
529
- homepage: https://github.com/jane/pdf-forms
530
- keywords: [pdf, forms]
531
- allowed-tools: [read_file, bash] # enforced as a per-turn tool ceiling (union)
532
- activation: auto # auto | always | manual
533
- ---
534
- ```
535
-
536
- `description` is the only text a model sees before deciding to load the skill,
537
- so it has to say both what the skill does and when to use it. Unknown keys are
538
- preserved, so a newer Claude Code field will not break parsing.
539
-
540
- `activation: always` injects the body into every session's system prompt (12 000
541
- bytes total across all such skills); `manual` keeps it out of the catalog so it
542
- runs only when explicitly invoked.
543
-
544
- ### Security model — and its limits
545
-
546
- Be clear about what this does and does not buy you:
547
-
548
- - **Installing requires a human.** The approval gate checks whether a person is
549
- actually reachable *before* asking, and refuses when nobody is. Under `-p`
550
- there is no TUI and therefore no approver, so `skill_install` fails with an
551
- actionable message instead of writing to disk unattended. Opt out
552
- deliberately with `--skills-yes` or `skills.requireApproval=false`.
553
- - **Project directories need trusting once.** Cloning an unfamiliar repository
554
- does not grant it the right to inject instructions into your session.
555
- - **Nothing is executed at install time.** No postinstall, no dependency
556
- install; the executable bit is not preserved. Scripts run only when the model
557
- invokes `bash`.
558
- - **Archives are gated on real bytes** — total size, per-entry size, compression
559
- ratio and entry count, all abortable mid-extraction. Path traversal,
560
- absolute paths, symlinks and Windows device names are refused.
561
- - **Git is never run through a shell**, and ref / owner / repo fragments are
562
- whitelisted and refused if they start with `-`.
563
- - **Third-party text cannot break out of its prompt block**: angle brackets in
564
- descriptions, bodies and file names are neutralized before rendering.
565
-
566
- - **A changed `SKILL.md` is noticed at load time.** Every install records a
567
- sha256; discovery re-checks the one file that reaches the model's context and
568
- flags it as `!modified` if it no longer matches what you approved. See
569
- `skills.integrity` below.
570
- - **Concurrent installs cannot corrupt a skill.** Writes take a per-directory
571
- advisory lock, so two `aragon` processes installing at once queue instead of
572
- interleaving their atomic-replace steps. *Limit:* the lock relies on exclusive
573
- file creation, which is not reliably atomic on every NFS implementation. It
574
- self-expires after 60 seconds and waits at most 5, so the worst case there is
575
- no mutual exclusion — never a hang.
576
-
577
- What it does **not** do: there is **no sandbox**. Approval blocks *silent*
578
- installation; it does not make a skill you approved safe. A skill you accept can
579
- contain adversarial instructions, and its scripts run with your full user
580
- permissions — the same trust model as pasting text into the prompt and the same
581
- as the `bash` tool. `/skills info` records provenance and a sha256 per file so
582
- you can audit what arrived and notice if it changed. The integrity check answers
583
- "is this still the file I approved?", **not** "is this file from someone I
584
- trust" — there is no signing and no key distribution.
585
-
586
- `allowed-tools` **is** enforced now (see below), but read the next section before
587
- treating it as a permission boundary: it is blast-radius control, not a security
588
- boundary, and a skill can widen its own ceiling by loading another skill.
589
-
590
- ### The tool ceiling (`allowed-tools`)
591
-
592
- A skill that declares `allowed-tools` constrains which tools the model may use
593
- **for the rest of the current turn**:
594
-
595
- ```yaml
596
- allowed-tools: [read_file, write_file]
597
- ```
598
-
599
- - **Scope is one turn.** The ceiling appears when a skill is loaded — by the
600
- model calling `skill(...)`, or by you running `/<skill-name>` — and it is gone
601
- as soon as you send the next message. A skill you used this morning does not
602
- restrict unrelated work this afternoon.
603
- - **Read-only tools are always allowed**, declared or not: `read_file`,
604
- `list_dir`, `glob`, `grep`, plus `skill` and `skill_find`. What the declaration
605
- actually controls is the five tools that change something — `write_file`,
606
- `edit_file`, `bash`, `skill_install`, `skill_create`.
607
- - **Claude Code names work.** `[Read, Bash, MultiEdit]` maps onto this host's
608
- `read_file` / `bash` / `edit_file`. Tools that exist there but not here
609
- (`WebFetch`, `Task`, `TodoWrite`, …) are ignored rather than treated as errors.
610
- - **A name we cannot resolve waives the whole declaration**, and says so. One
611
- typo means that skill imposes no ceiling at all — never a narrower one you did
612
- not write. `aragon skills doctor` reports these up front.
613
- - **`activation: always` skills never impose a ceiling.** They are ambient
614
- reference material, not a procedure you chose to run.
615
-
616
- Three modes, via `skills.toolPolicy` (default `enforce`),
617
- `aragon --skill-tool-policy <mode>` for one run, or `/skills policy <mode>` for
618
- the current session (which takes effect immediately, even mid-turn):
619
-
620
- | Mode | Behaviour |
621
- | --- | --- |
622
- | `enforce` | The call is refused; the model is told what it may use instead. |
623
- | `warn` | The call runs, and you get one notice per tool per turn. |
624
- | `off` | No ceiling, and nothing about `allowed-tools` reaches the model. |
625
-
626
- `/skills unload` drops the ceiling for the current turn. It does **not** remove
627
- skill text already in the conversation — nothing can.
628
-
629
- **What this is and is not.** It is **blast-radius control, not a security
630
- boundary**. The declaration is written by the skill's own author, so a malicious
631
- skill can simply declare `[bash]`. Its value is that a declaration is now
632
- binding: you can read one in `/skills info` and decide whether to install, and an
633
- honest author's `[read_file]` means a "while you're there, run `rm -rf`" line
634
- smuggled into that skill's text — or into a file it tells the model to read —
635
- cannot execute.
636
-
637
- **And a known limit:** the ceiling is a **union** across every skill loaded this
638
- turn, and `skill` is always available. So a skill can widen its own ceiling by
639
- instructing the model to load a second, more permissive skill. This is not
640
- blocked — blocking it would either strand the model inside a skill it cannot
641
- escape, or break every multi-skill workflow — but every widening is printed to
642
- the transcript:
643
-
644
- ```
645
- Tool ceiling widened by skill "skill-creator": +write_file, +skill_create
646
- ```
647
-
648
- So it stops the smuggled instruction; it does not stop a skill that deliberately
649
- teaches the model to reach for another one.
650
-
651
- ### Usage counters
652
-
653
- To rank the catalog, the CLI keeps a small file next to your skills directory:
654
-
655
- ```jsonc
656
- // <aragon skills path>/../skill-usage.json
657
- { "schema": 1, "skills": { "deploy-preview": { "useCount": 12, "lastUsedAt": 1785050000000 } } }
658
- ```
659
-
660
- **It contains the skill name, a count, and a timestamp. Nothing else** — no
661
- arguments, no conversation content, no working directory, no machine identifier.
662
- It is never transmitted anywhere, by anything. Delete it whenever you like; the
663
- only effect is that the catalog falls back to plain scope-then-name order. Set
664
- `skills.usageTracking=false` to switch it off entirely, after which the file is
665
- neither read nor written.
666
-
667
- Automatically injected `activation: always` skills are deliberately **not**
668
- counted — they load on every turn, so counting them would say nothing about what
669
- you actually use.
670
-
671
- ## Configuration
672
-
673
- Config is layered (highest priority last): **defaults → user config file →
674
- env / `.env` → CLI flags**.
675
-
676
- - **Env / `.env`**: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`,
677
- `GOOGLE_API_KEY` / `GEMINI_API_KEY`, plus `ARAGON_PROVIDER`, `ARAGON_MODEL`,
678
- `ARAGON_BASE_URL`, `ARAGON_THINKING`,
679
- `ARAGON_MAX_TOKENS` (a number, or `auto` / `0` for the per-model ceiling),
680
- `ARAGON_THEME`,
681
- `ARAGON_FULLSCREEN`, `ARAGON_PLAN` (`1` starts in plan mode),
682
- `ARAGON_TEAM` (`0` disables team subagents), `ARAGON_TEAM_MAX` (fan-out width),
683
- `ARAGON_SKILLS` (`0` disables), `ARAGON_SKILLS_PATH`,
684
- `ARAGON_SKILLS_DISABLED` (comma-separated names), `ARAGON_LOG_LEVEL`,
685
- `ARAGON_LOG_FILE` (`0` disables), `ARAGON_LOG_DIR`.
686
- `ARAGON_HOME` is the exception that **cannot** come from `.env`: it is read
687
- before any `.env` is loaded. See [Files & logs](#files--logs).
688
- - **User config file** (`aragon config path`): JSON, written `0600` on POSIX.
689
- Holds provider / model / keys and your preferences, and nothing else — the
690
- prompts you type and the counters behind one-time notices live in their own
691
- files (see [Files & logs](#files--logs)). Keys are masked in the UI and never
692
- logged. It lives in your home directory — see [Files & logs](#files--logs) —
693
- and editing it by hand is supported: `aragon config edit` opens it, and an
694
- invalid edit is reported rather than silently resetting every setting.
695
-
696
- | Config key | Default | Meaning |
697
- | --- | --- | --- |
698
- | `maxTokens` | `64000` | Output token cap, clamped to `[256, 200000]`. `null` means **auto** — use each model's own ceiling, never above 64000. Absent means the default. See [Output token limits](#output-token-limits). |
699
- | `fullscreen` | `true` | Use the full-screen TUI (still subject to the automatic downgrades). Setting it to `false` opts out permanently; leaving it `true` is *not* a force — only `--fullscreen` / `ARAGON_FULLSCREEN=1` override the heuristics. |
700
- | `exitTranscript` | `true` | Replay a plain-text session summary after exiting (full-screen only). |
701
- | `transcriptWindow` | `300` | How many trailing entries the full-screen viewport renders, clamped to `[50, 2000]`. The full history stays in memory and in `/save`. |
702
- | `planModeDefault` | `false` | Start every session in plan mode. `--no-plan` still overrides it for a single run. |
703
- | `planModeMaxAskRounds` | `4` | `ask_user` rounds allowed per user message, clamped to `[1, 10]`. The counter resets on your next message; steering does **not** reset it. |
704
- | `planModeHumanTimeoutMs` | `1800000` | Ceiling on a single question / plan wait, clamped to `[60000, 7200000]`. Thirty minutes is far outside "went to get coffee", but a ceiling has to exist so an unattended terminal cannot wedge a CI job forever. |
705
- | `historyEnabled` | `true` | Record submitted prompts to `prompt-history.jsonl` for `↑` recall. Setting it to `false` stops new writes; it does not delete what is already stored (that is `aragon history clear --yes`) and does not stop reading. See [Prompt history](#prompt-history). |
706
- | `team.enabled` | `true` | Master switch for team subagents. `false` means the `task` tool is never registered — `/team on` in such a session saves the setting for next launch and says so, rather than advertising a tool that is not there. |
707
- | `team.maxSubagents` | `5` | Subagents per dispatch, clamped to `[1, 10]`. The 10 is the hard ceiling and is enforced twice — on the config value and again on what the model asks for — so hand-editing this file cannot raise it. |
708
- | `team.maxConcurrent` | `3` | Subagents in flight at once, clamped to `[1, 10]` and capped at `maxSubagents`. A separate knob because provider rate limits are a different constraint from context economics. |
709
- | `team.subagentTimeoutMs` | `300000` | One subagent's wall clock before it is aborted, clamped to `[30000, 1800000]`. A wedged subagent dies on its own ceiling without taking the dispatch with it. |
710
- | `team.dispatchTimeoutMs` | `900000` | The whole dispatch's wall clock, clamped to `[60000, 3600000]`. |
711
- | `team.maxTurnsPerSubagent` | `24` | Runaway-loop cap, clamped to `[4, 100]`. A subagent stopped here is reported as `stopped: turn cap reached`, with whatever it had produced. |
712
- | `skills.enabled` | `true` | Master switch for the skill system. |
713
- | `skills.requireApproval` | `true` | Require a human to approve `skill_install` / `skill_create`. |
714
- | `skills.catalogMaxBytes` | `6000` | Level 1 catalog ceiling, in UTF-8 **bytes**, clamped to `[500, 40000]`. |
715
- | `skills.bodyMaxBytes` | `30000` | Level 2 body ceiling, in UTF-8 **bytes**, clamped to `[1000, 50000]`. |
716
- | `skills.allowedHosts` | GitHub / GitLab hosts | Hosts an install **or update** may fetch from. Re-checked on every update, not just at install time. |
717
- | `skills.disabled` | `[]` | Skill names switched off. |
718
- | `skills.trustedProjectDirs` | `[]` | Project skill directories you have trusted. |
719
- | `skills.integrity` | `warn` | What to do when `SKILL.md` no longer matches the copy you approved. `off` skips the check; `warn` flags it in `/skills list` and once per session; `strict` removes it from the catalog and makes `skill()` refuse it. Defaults to `warn` because editing an installed skill is a legitimate thing to do — `strict` suits CI and managed environments. |
720
- | `skills.usageTracking` | `true` | Keep local per-skill use counters to rank the catalog. Name, count and timestamp only; never transmitted. Inspect with `aragon skills usage`, erase with `--reset --yes`. |
721
- | `skills.toolPolicy` | `enforce` | How hard a skill's `allowed-tools` bites, for the turn it is loaded in. `off` disables it and says nothing about it to the model; `warn` runs the tool and reports it; `enforce` refuses. Unlike `skills.integrity`, a false positive here means a skill AUTHOR under-declared their own skill — which `aragon skills doctor` reports before it can bite. |
722
- | `log.level` | `info` | `silent` · `error` · `warn` · `info` · `debug` · `trace`. See [Files & logs](#files--logs) for what each level records. |
723
- | `log.toFile` | `true` | Write records to a file at all. `false` here is respected; `--log-file` overrides it for one run. |
724
- | `log.dir` | `""` | Log directory. Empty means `<home>/logs`. Only **absolute** paths are accepted — a relative one would mean a different directory for every working directory you launch from. |
725
- | `log.maxFileBytes` | `5242880` | Rotate once a file would exceed this, clamped to `[65536, 268435456]`. |
726
- | `log.maxFiles` | `10` | Files kept in the log directory, clamped to `[1, 200]`. The oldest beyond this are deleted — on rotation **and** on the first write of each day. |
727
- | `log.redactSecrets` | `true` | Run every record through redaction. Setting it to `false` is the one way to get credentials onto disk, so it prints a warning and has no settings-screen entry. |
728
- | `log.previewChars` | `512` | How much user content `debug` keeps per field, clamped to `[0, 8192]`. `trace` is never truncated. |
729
-
730
- Byte, not character: `ToolExecutor` truncates at 100 000 **bytes**, so a
731
- character-based budget would be roughly triple its nominal size for CJK text
732
- and would get cut mid-tag.
733
-
734
- ### Output token limits
735
-
736
- Every model and every provider defaults to **64000** output tokens. You never
737
- have to configure this, and if you never touch it the number stays 64000.
738
-
739
- The number you set is an **ambition, not a promise**: it is clamped down to
740
- whatever the target model actually accepts. `gpt-4o` caps at 16384 and
741
- `gemini-1.5-pro` at 8192, and sending 64000 to either is an HTTP 400 that ends
742
- the turn — so `aragon` sends what the model will take, silently. When the model
743
- is one it has never seen (a proxy, an endpoint behind `--base-url`, a model that
744
- shipped last week), it sends 64000, and if the provider objects it reads the real
745
- ceiling out of the provider's own error, repairs the request, sends it again, and
746
- remembers the answer for the rest of the session. You see one warning line and
747
- the run continues.
748
-
749
- Four ways to change it, highest priority first:
750
-
751
- ```bash
752
- aragon --max-tokens 32000 # this run only
753
- aragon --max-tokens auto # this run only, per-model ceiling
754
- ARAGON_MAX_TOKENS=32000 aragon # env layer, between the flag and the file
755
- aragon config set maxTokens 32000 # persisted; prints "Set maxTokens = 32000"
756
- aragon config set maxTokens auto # persisted as null
757
- aragon config set maxTokens default # back to 64000
758
- aragon config get maxTokens # "64000" | "auto"
759
- ```
760
-
761
- Inside the TUI, `/max-tokens` reports the current setting **and** the effective
762
- cap for the live model, and `/max-tokens <n|auto|default>` changes it. The
763
- settings screen (`/settings`) has the same field with an `Effective:` line under
764
- it, so a value the model will not honour is visible before you save it.
765
-
766
- And by hand, in `~/.aragon-agent/config.json` (`aragon config path` prints the
767
- exact location; `aragon config edit` opens it, and `/reload` picks up an edit
768
- made in another window):
769
-
770
- ```jsonc
771
- {
772
- "version": 1,
773
- "provider": "anthropic",
774
- "model": "claude-sonnet-4-5-20250929",
775
-
776
- // Output token cap.
777
- // 64000 (or any 256..200000 integer) — an explicit cap, clamped down to
778
- // the target model's real ceiling when that ceiling is lower.
779
- // null — auto: use each model's own ceiling, never above 64000.
780
- // absent — the product default, 64000.
781
- "maxTokens": 64000
782
- }
783
- ```
784
-
785
- Out-of-range numbers are clamped on the way in **and** on the way out, so a
786
- hand-edited `900000` becomes `200000` on disk rather than reverting to the
787
- default on every launch.
788
-
789
- Extended thinking is reconciled with the cap automatically: `--thinking xhigh`
790
- asks for a 65536-token budget, Anthropic requires `max_tokens` to be strictly
791
- greater than it, and the two are resolved together rather than left to collide.
792
-
793
- These are additive: a config file written by an earlier version loads
794
- unchanged, with no migration and no version bump.
795
-
796
- ## Files & logs
797
-
798
- Everything AragonAgent keeps for you lives under one directory in your home:
799
-
800
- ```
801
- ~/.aragon-agent/ (Windows: C:\Users\<you>\.aragon-agent\)
802
- ├── config.json settings + API keys (0600 on POSIX)
803
- ├── config.json.bak written by `aragon config edit` before it opens
804
- ├── prompt-history.jsonl prompts you submitted, for ↑ recall (0600 on POSIX)
805
- ├── state.json UI bookkeeping: submit count, one-shot notices
806
- ├── logs/
807
- │ ├── aragon-2026-07-27.log today, JSON Lines
808
- │ └── aragon-2026-07-27.1.log rotated at log.maxFileBytes
809
- ├── sessions/ /save and /resume
810
- ├── skills/ aragon skills install
811
- └── skill-usage.json
812
- ```
813
-
814
- `config.json` holds **settings only**. What you typed and how often you have
815
- typed it live in the two files next to it, so that editing your config means
816
- scrolling past your own settings and nothing else, and so that submitting a
817
- prompt no longer rewrites the file that holds your API keys.
818
-
819
- `ARAGON_HOME` moves the whole tree. **It cannot come from a `.env` file** — the
820
- path is resolved when the process starts, long before a `.env` is read — so set
821
- it as a real environment variable. `aragon config home` always prints the
822
- directory actually in use.
823
-
824
- Upgrading from 0.5.0 or earlier moves your config, sessions and skills here
825
- automatically, once, and prints a line on stderr when it does. The old
826
- `%APPDATA%\aragon-agent-nodejs` / `%LOCALAPPDATA%\aragon-agent-nodejs`
827
- directories are **left in place** so that reinstalling an older build stays
828
- lossless; `aragon config home` names them, and you can delete them once you are
829
- satisfied.
830
-
831
- ### Prompt history
832
-
833
- Every prompt you submit is appended to `~/.aragon-agent/prompt-history.jsonl`,
834
- one JSON object per line, so that `↑` recalls it — in this session and in every
835
- later one. It is written `0600` on POSIX, it never leaves the machine, and it is
836
- **stored verbatim**: a redacted history would recall the wrong text, which is
837
- worse than not recalling at all.
838
-
839
- The last 100 distinct prompts are what `↑` walks. The file is rewritten down to
840
- that many entries once it passes 400 lines, and a single submission longer than
841
- 16 KiB is not recorded at all rather than recorded truncated.
842
-
843
- ```bash
844
- aragon history path # where it is, plus a reminder of what is in it
845
- aragon history list -n 50 # the 50 most recent, newest first
846
- aragon history list --json # raw entries with timestamps
847
- aragon history clear --yes # delete the file
848
- ```
849
-
850
- Set `historyEnabled` to `false` (`aragon config set historyEnabled false`) to
851
- stop recording. Switching it off does **not** delete what is already stored —
852
- `aragon history clear --yes` does that — and reading keeps working either way,
853
- so you can look before you erase.
854
-
855
- Upgrading from 0.5.x moves an existing `promptHistory` out of `config.json` into
856
- this file once, automatically and silently.
857
-
858
- ### What gets logged
859
-
860
- Logs are local files. Nothing is uploaded, ever — there is no network path out
861
- of this subsystem at all.
862
-
863
- | Level | Records | Contains your content? |
864
- | --- | --- | --- |
865
- | `error` | crashes, failed requests, config write/parse failures | no |
866
- | `warn` | degraded migrations, clamped values, dropped records | no |
867
- | `info` *(default)* | start/exit, config writes, turn boundaries with token counts, tool names and durations | **no** |
868
- | `debug` | the above, plus prompts, replies and tool arguments truncated to `log.previewChars` | yes, truncated |
869
- | `trace` | full message bodies and full tool output | yes, in full |
870
-
871
- The default is `info` specifically so that attaching a log to a bug report is
872
- safe. **`debug` and `trace` write what you typed and what the model replied** —
873
- check before sharing one.
874
-
875
- API keys are removed at every level, by two independent passes: any field whose
876
- name looks like a credential is blanked whatever it holds, and every string is
877
- scanned for known key formats *and* for the literal keys this process is
878
- holding. The second half is what covers a custom endpoint whose key looks like
879
- nothing in particular. Setting `log.redactSecrets false` disables both, and says
880
- so on stdout when you do.
881
-
882
- ### Commands
883
-
884
- ```bash
885
- aragon logs path # where records are going right now, and at what level
886
- aragon logs list # files, sizes, timestamps
887
- aragon logs tail -n 50 # render the last 50 records
888
- aragon logs tail --follow # keep printing new ones
889
- aragon logs tail --json # raw JSON Lines, for jq
890
- aragon logs tail --level warn # only warn and above
891
- aragon logs clear --yes # delete every log file
892
- aragon logs open # open the directory in your file manager
893
-
894
- aragon history path # where prompt history is kept
895
- aragon history list -n 50 # the 50 most recent prompts, newest first
896
- aragon history clear --yes # delete the prompt history
897
-
898
- aragon config home # the user-state root (and whether ARAGON_HOME set it)
899
- aragon config get log.level # one value; secrets always masked
900
- aragon config list --json # everything; secrets always masked here too
901
- aragon config edit # open config.json in $VISUAL / $EDITOR
902
- ```
903
-
904
- Per-run overrides, none of which are persisted:
905
-
906
- ```bash
907
- aragon --log-level debug "…" # or --verbose, which means the same thing
908
- aragon --no-log-file "…" # nothing reaches a file this run
909
- aragon --log-dir D:\logs "…"
910
- ```
911
-
912
- `ARAGON_LOG_LEVEL`, `ARAGON_LOG_FILE=0` and `ARAGON_LOG_DIR` do the same from the
913
- environment, and unlike `ARAGON_HOME` these three *can* come from a `.env`.
914
-
915
- ### Insecure TLS opt-in
916
-
917
- When `NODE_TLS_REJECT_UNAUTHORIZED` is the exact string `0`, the installed or
918
- locally built `aragon` process honors that setting and suppresses only Node's
919
- standard insecure-TLS warning. Every unrelated process warning remains visible,
920
- and any other value leaves warning behavior unchanged. The check happens when a
921
- warning is emitted, so a `0` loaded from the project `.env` is covered too; the
922
- CLI never creates, normalizes, or persists this variable.
923
-
924
- > **Security warning:** `0` genuinely disables certificate verification. It does
925
- > not make an insecure connection safe and can expose credentials or request
926
- > contents to interception. Use it only in a controlled environment where you
927
- > accept that risk.
928
-
929
- The automatic filter starts with the `aragon` process. An earlier `npm install`
930
- or `npx` bootstrap belongs to npm and is outside the installed package's process
931
- boundary, so that parent may still print Node's warning. The official repository
932
- release flow covers its npm children through `publish-latest.ps1`; callers that
933
- own another npm/npx parent must preload the filter in that parent themselves if
934
- quiet output is required.
935
-
936
- ### Theming & color
937
-
938
- `--theme` / `config.theme` / `/theme` pick a palette: `warm`, `cool`, `light`, or
939
- `auto` (which resolves to `warm` — terminals can't reliably report their
940
- background). `dark` remains accepted everywhere as an alias for `cool`.
941
- Colors degrade automatically to your terminal's depth (truecolor → 256 → 16 →
942
- monochrome) and glyphs fall back to ASCII on terminals without Unicode. Color is
943
- disabled — and the UI renders plain monochrome with ASCII glyphs — when any of
944
- `NO_COLOR`, `--no-color`, `TERM=dumb`, or `config.color=false` is set; `FORCE_COLOR`
945
- is respected. `reducedMotion` (also `ARAGON_REDUCED_MOTION`, and implied by
946
- `--no-color`) replaces spinners with a static glyph.
947
-
948
- ### Timeout invariant
949
-
950
- The core idle watchdog (60 s by default) is **shorter** than the tool timeout
951
- (120 s), so a long single tool run (a build, `npm install`) would be killed
952
- mid-run. The CLI fixes this by always constructing the agent with
953
- `idleTimeout ≥ toolTimeout` (defaults 210 s ≥ 180 s), re-derived after config
954
- merge. Raise both `--tool-timeout` / `--idle-timeout` for genuinely long jobs.
955
-
956
- ## Development
957
-
958
- ```bash
959
- npm run build -w packages/cli # tsc + shebang → dist/cli.js
960
- npm test -w packages/cli # vitest
961
- node packages/cli/dist/cli.js --version
962
- ```
963
-
964
- ### Manual smoke checklist
965
-
966
- - `npm run build -w packages/cli` produces an executable `dist/cli.js` with a
967
- shebang; `node packages/cli/dist/cli.js --version` prints the version.
968
- - `npm pack -w packages/cli --dry-run --json` lists `dist/cli.js` under `files`.
969
- - With `ANTHROPIC_API_KEY` set: `aragon` streams a reply, a tool card renders,
970
- `Esc` aborts mid-run, `/model` switches models, `/settings` saves a key,
971
- `Ctrl+C` twice exits.
972
- - `echo "list files" | node dist/cli.js -p` prints an answer and exits `0`.
973
- - A missing/invalid key produces a visible, actionable message (never a blank
974
- screen), both in the TUI and headless.
975
- - `Shift+Tab` at 40, 80 and 120 columns - the chip, the hint and the status bar
976
- are all legible and nothing wraps onto a second row.
977
- - The question wizard at 24 rows with 5 questions x 4 options - no clipping, and
978
- the `n/N` counter matches what is on screen.
979
- - Approve a plan and watch the SAME run start writing files, with no second
980
- prompt.
981
- - `--no-color` on a legacy `cmd.exe` - the chip renders `[PLAN]`, radios render
982
- `*`/`o`, checkboxes render `[x]`/`[ ]`, no mojibake anywhere.
983
- - A plan with a 600-character summary and a 400-character step detail - the text
984
- wraps and scrolls, the footer count matches the real number of rows, and
985
- nothing is truncated to a single line.
986
- - `--no-hints` on a 20-row terminal in plan mode - the status bar still says
987
- `PLAN` with no chip on screen.
988
- - Dismiss a plan card with `Esc`, let the agent resubmit, then press `Esc` twice:
989
- the run aborts and the session returns to an idle prompt. Do this one BY HAND
990
- as well as in a test - the point of the check is whether the exit is findable,
991
- which no assertion can tell you.
992
-
993
- ## License
994
-
995
- MIT
1
+ # @aragon-agent/cli
2
+
3
+ A Claude-Code / Codex-style **interactive terminal UI** for the
4
+ [`@aragon-agent/core`](../core) engine. Type a task, watch the agent think and
5
+ stream its answer, see each tool call render as a live card, and interrupt /
6
+ steer / configure the session — all without leaving the terminal.
7
+
8
+ > ⚠️ **Full permission, no sandbox.** This first version runs at **maximum
9
+ > permission**: `bash` executes directly, file writes hit the real filesystem,
10
+ > and there is **no per-action approval gate by default**. Only run it in
11
+ > workspaces you trust. An opt-in confirmation mode is available via `--confirm`.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ # Global
17
+ npm i -g @aragon-agent/cli
18
+ aragon
19
+
20
+ # Zero-install
21
+ npx @aragon-agent/cli
22
+ ```
23
+
24
+ > **Release note (R3):** `npx @aragon-agent/cli` resolves `@aragon-agent/core`
25
+ > from the npm registry, so `@aragon-agent/core@^0.1.0` must be published first
26
+ > (or bundled into the CLI `dist`). During local development inside this
27
+ > monorepo, npm workspaces link the local `packages/core` automatically.
28
+
29
+ ## Quick start
30
+
31
+ ```bash
32
+ # Set a key (any one of these)
33
+ export ANTHROPIC_API_KEY=sk-ant-...
34
+ # ...or paste it live in the TUI via /settings, or pass --api-key
35
+
36
+ aragon # interactive TUI
37
+ aragon "summarize README.md" # interactive, auto-submitting the prompt
38
+ echo "list files" | aragon -p # one-shot, prints to stdout then exits
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ ```
44
+ aragon [prompt] Start the interactive TUI (or one-shot with -p / piped stdin)
45
+ aragon -p, --print [prompt] Headless: stream the answer to stdout, then exit
46
+ aragon config Open the settings screen
47
+ aragon config set <key> <value> Non-interactive config write (e.g. model, provider)
48
+ aragon config path Print the config file path
49
+ aragon models [--provider p] List builtin + discovered models
50
+ aragon --version | -v Print the version
51
+ aragon --help | -h Print help
52
+ ```
53
+
54
+ ### Global flags
55
+
56
+ | Flag | Meaning |
57
+ | --- | --- |
58
+ | `--provider <id>` | `anthropic` \| `openai` \| `google` |
59
+ | `--model <id>` | Model id |
60
+ | `--base-url <url>` | Override the provider base URL (OpenAI-compatible endpoints) |
61
+ | `--api-key <key>` | One-shot key override (not persisted) |
62
+ | `--thinking <level>` | `off\|minimal\|low\|medium\|high\|xhigh` |
63
+ | `--max-tokens <n\|auto>` | Output token cap: a number, or `auto` for the model's own ceiling (default `64000`) |
64
+ | `--cwd <dir>` | Working directory for tools |
65
+ | `--confirm` | Confirm each mutating tool call |
66
+ | `--tool-timeout <ms>` | Per-tool executor ceiling (default 180000) |
67
+ | `--idle-timeout <ms>` | Watchdog idle timeout (auto-raised to ≥ tool-timeout + 30s) |
68
+ | `--theme <name>` | `auto\|warm\|cool\|light` (`dark` is a compatibility alias for `cool`) |
69
+ | `--compact` / `--no-compact` | Transcript density: no blank rows between turns, or the default |
70
+ | `--hints` / `--no-hints` | Show or hide the composer hint row |
71
+ | `--fullscreen` | Force the full-screen TUI, overriding the automatic downgrades |
72
+ | `--no-fullscreen` | Force the inline renderer (the 0.2.0 behavior) |
73
+ | `--no-exit-transcript` | Do not replay the session summary after exiting |
74
+ | `--plan` / `--no-plan` | Start the session in PLAN mode (read-only research + review) or force BUILD |
75
+ | `--team` / `--no-team` | Enable or disable team subagents. `--no-team` does not register the `task` tool at all |
76
+ | `--team-max <n>` | Max subagents per dispatch for this run, clamped to `[1, 10]` |
77
+ | `--todo` / `--no-todo` | Enable or disable todo planning. `--no-todo` does not register the `todo_write` tool at all |
78
+ | `--todo-panel` / `--no-todo-panel` | Show or hide the right-hand todo rail. The tool stays registered either way |
79
+ | `--todo-follow <mode>` | What happens when a run ends with steps left: `notify` (default), `auto`, `off` |
80
+ | `--no-skills` | Disable the skill system entirely (no catalog, no skill tools) |
81
+ | `--skill <name>` | Force-load a skill's full body for this run (repeatable) |
82
+ | `--skills-yes` | Approve skill installs for this run (CI / headless) |
83
+ | `--no-color` | Disable ANSI color |
84
+ | `--quiet` | (print mode) suppress tool/usage lines on stderr |
85
+
86
+ Exit codes: `0` success · `1` agent/runtime error · `2` config/usage error ·
87
+ `130` interrupted.
88
+
89
+ ## Slash commands (interactive)
90
+
91
+ | Command | Action |
92
+ | --- | --- |
93
+ | `/help` | Keybindings + command help |
94
+ | `/model` | Open the model picker |
95
+ | `/settings` | Open the settings screen |
96
+ | `/thinking <level>` | Set the thinking level |
97
+ | `/max-tokens [<n>\|auto\|default]` | Output token cap; with no argument, reports the setting **and** the effective cap for the live model |
98
+ | `/tools` | List active tools |
99
+ | `/clear` | Clear the visible transcript |
100
+ | `/reset` | New conversation |
101
+ | `/cwd [dir]` | Show / change the tool working directory |
102
+ | `/save [file]` | Save the session to JSON |
103
+ | `/resume [file]` | Load a saved session |
104
+ | `/plan [on\|off\|status]` | Toggle plan mode - the keyboard-free equivalent of `Shift+Tab` |
105
+ | `/team [on\|off\|max <n>]` | Team subagents: status and the live roster, the on/off switch, the fan-out width |
106
+ | `/todo [on\|off\|panel on\|off\|follow <mode>\|clear\|continue]` | Todo planning: status, the on/off switch, the rail, follow-through, and picking an unfinished plan back up |
107
+ | `/theme <auto\|warm\|cool\|light>` | Switch the color theme live (persisted) |
108
+ | `/expand [n]` | Expand / collapse the n-th-from-last (default last) tool card |
109
+ | `/copy` | Copy the last answer to the clipboard |
110
+ | `/logs` | Show where this session is being logged, and at what level |
111
+ | `/reload` | Re-read `config.json` after editing it in another window (refused mid-run) |
112
+ | `/skills [sub]` | List and manage skills (see below) |
113
+ | `/<skill-name> [args]` | Run an installed skill directly |
114
+ | `/exit` (`/quit`) | Exit |
115
+
116
+ A leading `/` meant literally can be escaped as `//`. Typing `/` opens a
117
+ command palette and `@` opens a file-path completion popup — `Tab` / `→`
118
+ completes, `Up` / `Down` moves the selection, `Esc` closes it.
119
+
120
+ ## Keybindings
121
+
122
+ | Key | Action |
123
+ | --- | --- |
124
+ | `Enter` | Submit (idle) / queue a steering message (running) |
125
+ | `Alt+Enter` / `Shift+Enter` | Insert a newline |
126
+ | `Shift+Tab` | Toggle **plan mode** (`BUILD` <-> `PLAN`). Equivalent: `/plan` |
127
+ | `Esc` | Abort the run / close an overlay / close a popup |
128
+ | `Ctrl+C` ×2 | Exit (first press warns) |
129
+ | `Ctrl+L` | Redraw the frame (clear the screen in inline mode) |
130
+ | `Ctrl+T` | Toggle thinking blocks |
131
+ | `Ctrl+O` | Expand / collapse the most recent tool card |
132
+ | `PgUp` / `PgDn` | Scroll the transcript a page (full-screen mode) |
133
+ | `Shift+↑` / `Shift+↓` | Scroll the transcript a line (full-screen mode) |
134
+ | Mouse wheel | Scroll the transcript (over the input box: prompt history) |
135
+ | `Shift`+wheel | Scroll the transcript a page |
136
+ | `Home` / `End`, `Ctrl+A` / `Ctrl+E` | Cursor to line start / end |
137
+ | `Alt/Ctrl+←` / `→` | Word-wise cursor jump |
138
+ | `Ctrl+W`, `Alt+Backspace` | Delete the previous word |
139
+ | `Ctrl+U` / `Ctrl+K` | Kill to line start / end |
140
+ | `Up` / `Down` | Move between draft lines; recall prompt history at the edges |
141
+ | `?` | Open help (empty input) |
142
+
143
+ In **full-screen mode** the transcript is scrolled by the app itself: `PgUp` /
144
+ `PgDn` and `Shift+↑` / `Shift+↓`. While pinned to the bottom the viewport follows
145
+ new output automatically; once you scroll away the status bar shows `↑N`, a slim
146
+ rail on the right edge shows where you are, and a hint counts the lines below
147
+ you. Submitting a message always re-pins to the newest output. In **inline mode**
148
+ (`--no-fullscreen`) history lives in your terminal's native scrollback, printed
149
+ once via Ink `<Static>`.
150
+
151
+ **The mouse wheel scrolls whatever is under the pointer.** Over the transcript
152
+ it scrolls the transcript three rows per notch (a page with `Shift`); over an
153
+ open overlay it scrolls that overlay; over the input box at the bottom it steps
154
+ prompt history one entry per notch, exactly as `↑` / `↓` do — including the rule
155
+ that an in-progress draft is never overwritten.
156
+
157
+ Two things follow from the app owning the mouse, and they are worth knowing
158
+ before you meet them:
159
+
160
+ - **Selecting text now needs `Shift`+drag** in most terminals. That is the
161
+ terminal's convention for "an application is tracking the mouse", not
162
+ something this CLI can opt out of while still reading wheel events.
163
+ - **`tmux` with `mouse on` keeps the wheel for itself**, so the app never sees
164
+ it. `set -g mouse off` hands it back.
165
+
166
+ Turn the whole thing off with `--no-mouse`, `ARAGON_MOUSE=0`, or
167
+ `aragon config set mouse false`. With mouse support off the wheel simply does
168
+ nothing in full-screen mode — it does not fall back to editing your draft, which
169
+ is what it used to do. Inline mode is unaffected either way.
170
+
171
+ ## Themes
172
+
173
+ Three palettes plus `auto`:
174
+
175
+ | Name | Look |
176
+ | --- | --- |
177
+ | `warm` | Terracotta + amber over warm neutrals. The default (`auto` resolves here). |
178
+ | `cool` | The blue/violet palette that shipped as `dark` through 0.3.x. |
179
+ | `light` | For light terminal backgrounds. |
180
+
181
+ `auto` never guesses `light`: terminals cannot report their background reliably,
182
+ and guessing wrong makes the app unreadable rather than merely wrong-looking.
183
+
184
+ **Migrating from 0.3.x** — `dark` was renamed `cool` when `warm` became the
185
+ default, and the old name still works everywhere: `--theme dark`,
186
+ `aragon config set theme dark`, `/theme dark`, and a config file containing
187
+ `"theme": "dark"` all resolve to `cool`, so a screen you explicitly chose does
188
+ not change under you. The value is rewritten as `cool` the next time the config
189
+ is saved. Only users who never picked a theme see the new default.
190
+
191
+ Density and hints are settable the same three ways: `--compact` / `--no-hints`
192
+ for one run, `aragon config set density compact` / `aragon config set hints false`
193
+ to persist. The composer hint row also shortens to `? help` on its own after a
194
+ few sessions — except while a run is in progress, when it always spells out
195
+ `esc abort` in full.
196
+
197
+ ## Full-screen mode
198
+
199
+ `aragon` takes over the terminal's **alternate screen buffer** — the mechanism
200
+ `vim`, `htop`, and `lazygit` use. Two consequences worth knowing:
201
+
202
+ - Your previous shell output is **covered, not erased**, and returns untouched
203
+ when you exit. Nothing in your scrollback is destroyed.
204
+ - The frame is fixed at `rows - 1` tall, which is what keeps the composer and the
205
+ status bar at the bottom of the screen even on an empty session.
206
+
207
+ On exit the session is replayed into the normal buffer as plain text so the
208
+ conversation survives leaving the screen (`--no-exit-transcript` opts out;
209
+ `/save` still exports the full JSON).
210
+
211
+ It downgrades to the inline renderer automatically when stdout is not a TTY,
212
+ `TERM=dumb`, a CI environment variable is set, or the terminal is under 12 rows
213
+ or 40 columns. `--fullscreen` overrides all of those except the non-TTY check —
214
+ writing screen-control sequences into a pipe or a redirected file is never safe.
215
+ `--no-fullscreen`, `ARAGON_FULLSCREEN=0`, or `aragon config set fullscreen false`
216
+ opt out permanently.
217
+
218
+ The terminal's native scrollback and mouse wheel do not scroll the transcript in
219
+ this mode, and mouse tracking is deliberately left off because enabling it costs
220
+ text selection and copy in most terminals. If a crash ever strands your terminal
221
+ on the alternate screen, `reset` restores it.
222
+
223
+ ## Plan mode
224
+
225
+ `aragon` has two postures, and `Shift+Tab` switches between them. `/plan` does
226
+ the same thing for terminals that swallow the key (see below).
227
+
228
+ | Mode | What it means |
229
+ | --- | --- |
230
+ | `BUILD` | The default, and unchanged from earlier versions: you ask, the agent does it. |
231
+ | `PLAN` | Research and design only. The agent reads, asks you the decisions it cannot settle by reading, and submits a plan for approval before anything is written. |
232
+
233
+ `BUILD` is right for "rename this symbol". `PLAN` is right for "add multi-tenant
234
+ support" — anything where you want to see and steer the *approach* before a
235
+ single byte changes.
236
+
237
+ ### What plan mode actually stops
238
+
239
+ In `PLAN`, five tools are refused at the tool boundary: `write_file`,
240
+ `edit_file`, `bash`, `skill_install` and `skill_create`. That is a property of
241
+ the wiring, not a promise in a prompt — the refusal happens before the tool runs,
242
+ so the badge is telling you something that is structurally true.
243
+
244
+ **`bash` is refused in full, `git status` included.** This is the most likely
245
+ question this feature generates, so: there is no reliable way to classify a shell
246
+ string as read-only, and a gate that is right 95 % of the time is worse than one
247
+ that is always right, because you stop trusting the badge. Use `read_file`,
248
+ `list_dir`, `glob` and `grep`, which stay available; the refusal message names
249
+ them so the agent has somewhere to go. There is no allowlist and there will not
250
+ be one built out of substring matching.
251
+
252
+ Everything else still works: reading, searching, loading skills, and asking you
253
+ questions.
254
+
255
+ ### The two plan-mode tools
256
+
257
+ - **`ask_user`** renders a keyboard-driven wizard: 1–5 questions, 2–4 options
258
+ each, exactly one marked `RECOMMENDED`, plus an `Other…` free-text option.
259
+ `↑`/`↓` choose, `Enter` confirms and advances, `←` goes back, `Esc` cancels.
260
+ Pressing `Enter` through the whole wizard accepts every recommendation, which
261
+ is a deliberate "use your judgement" path rather than an accident. A last
262
+ screen lets you review before submitting.
263
+ - **`submit_plan`** renders the plan as a scrollable card: `a` approves, `r`
264
+ opens a one-line feedback field (`Enter` sends it, `←` goes back to the card),
265
+ `Esc` dismisses. **Approving flips the session to `BUILD` immediately and the
266
+ same run continues straight into implementation** — you do not have to re-ask.
267
+
268
+ Cancelling either one is not an error. The agent is told to proceed on stated
269
+ assumptions rather than to retry.
270
+
271
+ **Dismissing a plan is not how you stop.** `Esc` on a plan card asks for a
272
+ *better* plan — the agent is told to refine it and submit again — so a model that
273
+ keeps submitting keeps getting cards. To stop the run itself, **press `Esc`
274
+ twice**: the first closes the card, the second aborts the run. `Ctrl+C` twice
275
+ still exits the session outright.
276
+
277
+ ### Where the mode is shown
278
+
279
+ The status bar always names a non-default mode (`PLAN`, or `PLAN → BUILD` while
280
+ a switch is pending), including on short terminals, with `--no-hints`, and in
281
+ inline mode. The composer additionally shows a `PLAN` chip on its hint row and
282
+ tints its border, where there is room for it.
283
+
284
+ ### Switching mid-run
285
+
286
+ Tightening applies at once; loosening waits. Pressing `Shift+Tab` toward `PLAN`
287
+ during a run takes effect immediately — you pressed it *because* the agent is
288
+ about to do something. Pressing it toward `BUILD` during a run is **deferred to
289
+ the end of the run**, so a session you launched under a read-only guarantee
290
+ cannot start writing files because of one stray keypress. The status bar shows
291
+ `PLAN → BUILD` while that is pending. Approving a plan is the one exception, and
292
+ it is an informed act rather than a stray keypress.
293
+
294
+ ### If `Shift+Tab` does nothing
295
+
296
+ Some multiplexers and remote-desktop stacks never send `CSI Z`. `/plan`,
297
+ `/plan on`, `/plan off` and `/plan status` are first-class equivalents, and
298
+ `--plan` / `ARAGON_PLAN=1` / `planModeDefault` cover the startup case.
299
+
300
+ ### Headless
301
+
302
+ `aragon -p --plan "add SSO"` is a useful combination: the read-only gate still
303
+ applies, so it means *"tell me how you would do this, and do not touch my
304
+ repository"*. No question or plan tool is registered there — there is nobody to
305
+ ask — and the agent writes the finished plan as its final message in markdown.
306
+
307
+ ## TODO planning
308
+
309
+ For work with three or more distinct steps the agent keeps a **visible plan**:
310
+ it writes a checklist with the `todo_write` tool, marks exactly one item as in
311
+ progress, and ticks it off before starting the next. In full-screen mode that
312
+ list is rendered as a right-hand rail, about a fifth of the terminal wide.
313
+
314
+ ```
315
+ transcript ... | TODO 2/7
316
+ | ####------
317
+ |
318
+ | v 1 Read the reducer
319
+ | v 2 Design the store
320
+ | > 3 Wiring the rail into
321
+ | AppShell
322
+ | o 4 Add the panel-rows test
323
+ | o 5 Update the README
324
+ | +2 more
325
+ ```
326
+
327
+ Ask for something small and nothing appears. The agent is told, in its system
328
+ prompt, **not** to use the tool for anything it can finish in one or two steps,
329
+ for a question, or for a search with no work attached — and a one-item list
330
+ against an empty plan is refused outright. A session that never plans has
331
+ byte-identical layout to a build without this feature.
332
+
333
+ **The list is a projection of what the model believes**, so nothing that does
334
+ not also tell the model may change it:
335
+
336
+ - `/clear` wipes the transcript but not the conversation, so the plan stays.
337
+ - `/reset` clears the conversation, so the plan goes with it.
338
+ - `/save` writes the list and `/resume` restores it. Resuming a session that has
339
+ no list clears the current one, rather than leaving a plan on screen whose
340
+ conversation has just been replaced.
341
+ - A finished plan is dropped at the START of your next message, so you still get
342
+ to see `7/7 done`. An unfinished one survives "continue" / "now do the rest",
343
+ and is dropped after three unrelated turns rather than holding a fifth of the
344
+ screen for the rest of the session.
345
+
346
+ ### When a run ends with steps left
347
+
348
+ By default you get a notice and `/todo continue` — nothing auto-continues on
349
+ your behalf. `/todo continue` no longer says "continue the remaining items"; it
350
+ sends the model the numbered list of steps that are still open, so a plan that
351
+ scrolled out of the context window some turns ago is still followed exactly.
352
+
353
+ `/todo follow auto` (or `--todo-follow auto`, or `todo.followThrough` in
354
+ `config.json`) turns that into an actual loop: the CLI announces
355
+ `Continuing with 4 remaining steps in 3s - Esc to stop.`, waits, and then sends
356
+ the continuation itself. Two structural limits bound it, and neither is a
357
+ setting:
358
+
359
+ - A continuation that completes no step buys exactly **one** more attempt.
360
+ After that you get a sentence and the keyboard back.
361
+ - A single plan is continued at most **25** times however productive it looks.
362
+ A re-plan forgives the first counter but never the second, so a model that
363
+ reshapes its list every turn cannot lift the ceiling.
364
+
365
+ `Esc` during the grace window cancels; so does typing anything. A run that ended
366
+ with an **error** never auto-continues in any mode — you get a warning instead.
367
+ A run **you** aborted with `Esc` now says nothing at all, where it used to
368
+ report what you had just interrupted.
369
+
370
+ `/todo follow off` is the third mode: no notice, no continuation, silence.
371
+
372
+ Under `-p` there is no grace window (there is nobody there to use it), so the
373
+ two counters are the whole of the protection. `[todo] continuing (3 steps left)`
374
+ goes to stderr before each continuation and `[todo] 3 steps unfinished` when it
375
+ stops; both are suppressed by `--quiet`, and an unfinished plan never changes
376
+ the exit code.
377
+
378
+ ### Inline mode
379
+
380
+ Inline mode has no fixed frame and therefore no rail, but it does get a one-row
381
+ plan strip under the transcript:
382
+
383
+ ```
384
+ todo 3/7 > Adding the rail to AppShell +2 done
385
+ ```
386
+
387
+ It never wraps and never grows past one row, so the composer does not move. The
388
+ `+N done` suffix drops below 80 columns and the counter compacts to `[3/7]`
389
+ below 100, matching the status bar. `--no-todo-panel` hides the strip as well as
390
+ the rail.
391
+
392
+ `/todo` reports status and switches things off: `/todo off` unregisters nothing
393
+ (the tool list is fixed when the session starts) but stops the tool working and
394
+ drops the guidance from the system prompt, while `/todo panel off` keeps the
395
+ planning discipline and hides the column — which is what you want with a screen
396
+ reader, and what `--no-todo-panel` does for a whole session. Under `-p` the plan
397
+ is written to stderr as `[todo] 3/7 <step>` lines, suppressed by `--quiet`.
398
+
399
+ The rail is not mounted below 80 columns, while an overlay is open, in inline
400
+ mode, or when the viewport is too short to say anything useful; the transcript
401
+ keeps at least 62 columns at every width. In those cases the counter appears in
402
+ the status bar instead, and the checklist is still written into the transcript
403
+ as one card per turn.
404
+
405
+ ## Built-in tools
406
+
407
+ `read_file`, `write_file`, `edit_file`, `list_dir`, `glob`, `grep`, `bash` — all
408
+ at full permission. Paths resolve against the session working directory. The
409
+ per-tool timeout and 100 KB output truncation are enforced by the core executor;
410
+ `bash`'s `timeout` param only shrinks within that ceiling.
411
+
412
+ When skills are enabled, four more are added — the two read tools `skill` and
413
+ `skill_find`, then the two mutating ones `skill_install` and `skill_create` (see
414
+ below). `--no-skills` removes them again. Updating a skill is deliberately not
415
+ among them; that is a `aragon skills update` decision for you, not the model.
416
+
417
+ With team mode on — which is the default — one more is added: `task`, the
418
+ delegation tool (see below). `--no-team` removes it again.
419
+
420
+ With todo planning on — also the default — one more is added: `todo_write`, the
421
+ planning tool (see above). `--no-todo` removes it again.
422
+
423
+ ## Team subagents
424
+
425
+ The agent can hand parts of a job to short-lived **subagents** that run in
426
+ parallel, then read one combined report of what they each found. It decides for
427
+ itself whether to delegate; a session that never calls `task` is the session
428
+ that existed before this feature.
429
+
430
+ ```
431
+ > read the auth middleware, map the route table, and check the migration
432
+
433
+ ◆ team 2 running · 1 done · 84s ✉ 1
434
+ ⠋ a1 read the auth middleware tool: grep 22.1s
435
+ ⠙ a2 map the route table thinking 31.6s
436
+ ✔ a3 check the migration done 4 turns 18.2s
437
+ ```
438
+
439
+ **What a subagent is.** A full agent with its own message history, the same
440
+ tools and the same working directory — but no memory of your conversation, so
441
+ its brief has to be self-contained. It cannot delegate further (depth is capped
442
+ at one by construction) and it cannot ask you anything.
443
+
444
+ **Delegate wide, not deep.** Two to four subagents is the usual size. The
445
+ default cap is 5 per dispatch and 3 in flight at once; the hard maximum is 10
446
+ and no config file can raise it. `maxConcurrent` is separate from `maxSubagents`
447
+ on purpose: five simultaneous streams against one API key is a reliable way to
448
+ collect rate-limit errors, and each one would land in the report as a failed
449
+ subagent.
450
+
451
+ **The report.** One text result: a header with the ok/failed counts, aggregate
452
+ tokens and cost, then one section per subagent with its status, timing, the
453
+ files it wrote and its own final summary. A failed subagent never fails the
454
+ dispatch — partial results are the normal outcome of a fan-out. When two
455
+ subagents wrote the same file the header says so, phrased as a warning rather
456
+ than a fact: the file list is derived from `write_file` / `edit_file`, so a
457
+ write made through `bash` is invisible to it.
458
+
459
+ **Talking to each other.** Subagents get two extra tools, `team_send` and
460
+ `team_wait`. Messages are delivered by attaching them to the recipient's next
461
+ tool result, so nobody has to poll and nobody's work is interrupted. The limits
462
+ are the point rather than an afterthought: **6 messages per subagent, one every
463
+ 15 seconds, 24 per dispatch**, a broadcast costing two. Every refusal is a plain
464
+ result naming the limit, never an error — an error reads as a malfunction and
465
+ invites a retry, which is the behaviour the limits exist to prevent.
466
+
467
+ **What you see.** While a dispatch runs, the roster above sits between the
468
+ transcript and the composer and the status bar carries `agents 3/5` (`[3]` under
469
+ 100 columns). Both disappear when it finishes and the transcript keeps a card
470
+ with the per-agent results; `Ctrl+O` expands it to their summaries. Under `-p`
471
+ the same transitions go to stderr as `[team] a1 ok 22.1s 7 tools` lines, and the
472
+ children's tokens are folded into the `[usage]` footer.
473
+
474
+ **Interaction with the other subsystems.**
475
+
476
+ - **Plan mode reaches one level down.** In PLAN, every subagent is built with
477
+ the same read-only gate, so `write_file` / `edit_file` / `bash` are refused
478
+ inside them too. Parallel research is plan mode's best use, so `task` itself
479
+ is allowed. A single subagent can be pinned read-only with `readOnly: true`
480
+ even in a BUILD session — the field can only ever tighten.
481
+ - **A skill's `allowed-tools` ceiling reaches one level down too**, so
482
+ delegating is not a way around it. Subagents get `skill_find` but not `skill`:
483
+ loading a skill mutates a turn-scoped registry the lead owns, and three
484
+ subagents doing it at once would narrow what the lead may do next in a way
485
+ nothing would report. Always-on skills still reach them.
486
+ - **`--confirm`** queues subagent confirmations one at a time, labelled with the
487
+ subagent that is asking (`[a2] Write: src/routes.ts`).
488
+ - **Logs.** `dispatch_start` and `dispatch_end` are recorded at `info` with the
489
+ ok / failed / aborted counts and aggregate usage; phase transitions at
490
+ `debug`. A `team_send` subject is recorded, never its body; a subagent's brief
491
+ and summary only at `trace`.
492
+
493
+ **`Esc`** aborts the whole dispatch — every subagent within a couple of seconds
494
+ — and the report comes back marked `ABORTED` with whatever was finished.
495
+
496
+ ## Skills
497
+
498
+ A skill is a reusable expert procedure stored on disk: a directory containing a
499
+ `SKILL.md` (YAML frontmatter + Markdown body) and, optionally, `reference/`,
500
+ `scripts/` and `assets/`. The format matches Claude Code's, so an existing
501
+ community skill directory works as-is.
502
+
503
+ ### Progressive disclosure
504
+
505
+ This is the part that makes skills cheap enough to install a lot of:
506
+
507
+ | Level | What the model gets | When |
508
+ | --- | --- | --- |
509
+ | 1 | Name, scope, and a one-line "what + when" | Always, in the system prompt. Whole catalog capped at 6 000 bytes. |
510
+ | 2 | The full `SKILL.md` body + a list of bundled files | Only when the model calls `skill(name=…)` |
511
+ | 3 | The bundled files themselves | Only when it reads them with `read_file` / `bash` |
512
+
513
+ A skill costs a line in the prompt rather than its whole body, so installing
514
+ many of them stays cheap. The model decides on its own whether a task matches a
515
+ skill — you do not have to name one.
516
+
517
+ At Level 2 the file list is split into what to read and what to run, and carries
518
+ the skill's absolute root so the model can build real paths. The accompanying
519
+ guidance is **platform-specific**: on Windows the `bash` tool runs `cmd.exe`, so
520
+ the model is told to run `.py` with `python <abs path>` and that a `.sh` file
521
+ will not work — rather than the previous, flatly untrue "run scripts with bash".
522
+ `aragon skills doctor` warns when a skill bundles only scripts for the other
523
+ shell.
524
+
525
+ **How many fit in Level 1 depends on your descriptions, and on the language they
526
+ are written in.** The 6 000-byte ceiling is measured in UTF-8 bytes: a terse
527
+ English description costs roughly 180 bytes a line (~30 skills), while a Chinese
528
+ one at the 220-character cap can cost 660 (~10 skills). Past that point the
529
+ catalog truncates.
530
+
531
+ Truncation is not a dead end. Entries are ranked before it happens, and what
532
+ falls off is still reachable:
533
+
534
+ - **Ranking**: scope first (`env` > `project` > `user` > `bundled`), then how
535
+ recently and how often you have actually used each skill. A skill you ran an
536
+ hour ago outranks one you have never used, so what survives truncation is what
537
+ you actually work with — not whatever sorts first alphabetically.
538
+ - **`skill_find`**: when the catalog *does* truncate, it says so and names this
539
+ tool, which searches everything installed by name, description and keyword.
540
+ It only ever searches **what is already on this machine** — it never reaches
541
+ the network and never suggests something to install.
542
+
543
+ Both are off the critical path: with no usage data recorded the order is exactly
544
+ the plain scope-then-name order, and `skill_find` is only advertised in the
545
+ prompt when something was actually omitted.
546
+
547
+ ### Using them
548
+
549
+ ```bash
550
+ aragon skills list [--sort=recent] # what is installed
551
+ aragon skills info <name> # version, path, provenance, files
552
+ aragon skills install <source> [--yes] # dir | git repo | https .md / .zip
553
+ aragon skills install github:owner/repo#v1.2.0
554
+ aragon skills update <name> [--yes] [--force] [--dry-run]
555
+ aragon skills update --all [--yes] # every skill that has an upstream
556
+ aragon skills update --check [<name>|--all] # is there a newer version?
557
+ aragon skills remove <name> [--yes]
558
+ aragon skills create <name> --description "what + when" [--yes]
559
+ aragon skills path # user skills directory
560
+ aragon skills doctor # validate + detect tampering
561
+ aragon skills usage [--json] # local use counters
562
+ aragon skills usage --reset --yes # delete them
563
+ aragon skills trust <dir> # allow a project skills directory
564
+ ```
565
+
566
+ #### Updating
567
+
568
+ `update` re-fetches a skill from the source recorded in its install manifest,
569
+ keeping `installedAt` and recording `updatedAt` + `previousVersion`.
570
+
571
+ - **Local edits are protected.** If you have changed an installed skill, `update`
572
+ refuses and names the files, *before downloading anything*. `--force`
573
+ overwrites them — and **`--force` discards your edits**; copy them out first.
574
+ The replaced directory is parked under the staging trash, so a mistake is
575
+ recoverable by hand.
576
+ - **The host allowlist is re-checked** against your *current*
577
+ `skills.allowedHosts`, not the one in force when you installed. Narrowing that
578
+ list therefore also constrains updates.
579
+ - **`--all` never stops on one failure.** Skills that cannot be updated
580
+ (hand-authored, bundled, or locally modified) are reported as `skipped`, which
581
+ is not an error; only a genuine I/O or validation failure exits non-zero.
582
+ - **`--check` reports `unknown` freely.** It compares the commit recorded at
583
+ install time against `git ls-remote`, so it can only answer for git sources
584
+ installed by a version that records one. Anything else is honestly reported as
585
+ unknown rather than guessed.
586
+ - **The model cannot call `update`.** Installing is something you pointed it at;
587
+ swapping working instructions for different ones is a maintenance decision and
588
+ stays with you.
589
+
590
+ Interactively the same lives under `/skills …` (`/skills update <name> [--force]`,
591
+ `/skills update --all`, `/skills list --sort=recent`, `/skills policy [mode]`,
592
+ `/skills unload`, `/skills usage [--reset]`). Every usable skill also becomes a
593
+ slash command:
594
+
595
+ ```
596
+ /pdf-forms invoice.pdf --flatten
597
+ ```
598
+
599
+ Both delivery paths share `skills.bodyMaxBytes`, so an oversized `SKILL.md` is
600
+ truncated with a visible marker rather than submitted whole. Loading the same
601
+ skill twice in one conversation returns a short digest (description, file list,
602
+ guidance) instead of a second copy of the body; the model can ask for the full
603
+ text back with `skill(name="…", force=true)`.
604
+
605
+ Bodies may use `$ARGUMENTS` and `$1..$9`; `$$` is a literal `$`. If a skill
606
+ declares no placeholder, the arguments are appended under an `## Arguments`
607
+ heading instead. Where a skill's name collides with a built-in command, the
608
+ **built-in wins** and the skill is reachable as `/skill:<name>`.
609
+
610
+ ### Where skills come from
611
+
612
+ Four scopes, later ones overriding earlier ones by name:
613
+
614
+ | Scope | Location | Writable |
615
+ | --- | --- | --- |
616
+ | `bundled` | `<package>/skills/` | no |
617
+ | `user` | `aragon skills path` | yes — the default install target |
618
+ | `project` | `<cwd>/.aragon/skills/` | yes |
619
+ | `project` | `<cwd>/.claude/skills/` | no — read-only Claude Code interop |
620
+ | `env` | `ARAGON_SKILLS_PATH` (`:`/`;`-separated) | no |
621
+
622
+ A shadowed skill is not lost: `/skills` shows it as `~shadowed by <scope>`.
623
+
624
+ ### Frontmatter
625
+
626
+ ```yaml
627
+ ---
628
+ name: pdf-forms # required, kebab-case, ≤ 64 chars
629
+ description: Fill, flatten and validate AcroForm PDFs. Use when the task
630
+ mentions PDF forms, field filling, or flattening a fillable PDF.
631
+ version: 1.2.0
632
+ license: MIT
633
+ author: Jane Doe
634
+ homepage: https://github.com/jane/pdf-forms
635
+ keywords: [pdf, forms]
636
+ allowed-tools: [read_file, bash] # enforced as a per-turn tool ceiling (union)
637
+ activation: auto # auto | always | manual
638
+ ---
639
+ ```
640
+
641
+ `description` is the only text a model sees before deciding to load the skill,
642
+ so it has to say both what the skill does and when to use it. Unknown keys are
643
+ preserved, so a newer Claude Code field will not break parsing.
644
+
645
+ `activation: always` injects the body into every session's system prompt (12 000
646
+ bytes total across all such skills); `manual` keeps it out of the catalog so it
647
+ runs only when explicitly invoked.
648
+
649
+ ### Security model — and its limits
650
+
651
+ Be clear about what this does and does not buy you:
652
+
653
+ - **Installing requires a human.** The approval gate checks whether a person is
654
+ actually reachable *before* asking, and refuses when nobody is. Under `-p`
655
+ there is no TUI and therefore no approver, so `skill_install` fails with an
656
+ actionable message instead of writing to disk unattended. Opt out
657
+ deliberately with `--skills-yes` or `skills.requireApproval=false`.
658
+ - **Project directories need trusting once.** Cloning an unfamiliar repository
659
+ does not grant it the right to inject instructions into your session.
660
+ - **Nothing is executed at install time.** No postinstall, no dependency
661
+ install; the executable bit is not preserved. Scripts run only when the model
662
+ invokes `bash`.
663
+ - **Archives are gated on real bytes** — total size, per-entry size, compression
664
+ ratio and entry count, all abortable mid-extraction. Path traversal,
665
+ absolute paths, symlinks and Windows device names are refused.
666
+ - **Git is never run through a shell**, and ref / owner / repo fragments are
667
+ whitelisted and refused if they start with `-`.
668
+ - **Third-party text cannot break out of its prompt block**: angle brackets in
669
+ descriptions, bodies and file names are neutralized before rendering.
670
+
671
+ - **A changed `SKILL.md` is noticed at load time.** Every install records a
672
+ sha256; discovery re-checks the one file that reaches the model's context and
673
+ flags it as `!modified` if it no longer matches what you approved. See
674
+ `skills.integrity` below.
675
+ - **Concurrent installs cannot corrupt a skill.** Writes take a per-directory
676
+ advisory lock, so two `aragon` processes installing at once queue instead of
677
+ interleaving their atomic-replace steps. *Limit:* the lock relies on exclusive
678
+ file creation, which is not reliably atomic on every NFS implementation. It
679
+ self-expires after 60 seconds and waits at most 5, so the worst case there is
680
+ no mutual exclusion — never a hang.
681
+
682
+ What it does **not** do: there is **no sandbox**. Approval blocks *silent*
683
+ installation; it does not make a skill you approved safe. A skill you accept can
684
+ contain adversarial instructions, and its scripts run with your full user
685
+ permissions — the same trust model as pasting text into the prompt and the same
686
+ as the `bash` tool. `/skills info` records provenance and a sha256 per file so
687
+ you can audit what arrived and notice if it changed. The integrity check answers
688
+ "is this still the file I approved?", **not** "is this file from someone I
689
+ trust" — there is no signing and no key distribution.
690
+
691
+ `allowed-tools` **is** enforced now (see below), but read the next section before
692
+ treating it as a permission boundary: it is blast-radius control, not a security
693
+ boundary, and a skill can widen its own ceiling by loading another skill.
694
+
695
+ ### The tool ceiling (`allowed-tools`)
696
+
697
+ A skill that declares `allowed-tools` constrains which tools the model may use
698
+ **for the rest of the current turn**:
699
+
700
+ ```yaml
701
+ allowed-tools: [read_file, write_file]
702
+ ```
703
+
704
+ - **Scope is one turn.** The ceiling appears when a skill is loaded — by the
705
+ model calling `skill(...)`, or by you running `/<skill-name>` — and it is gone
706
+ as soon as you send the next message. A skill you used this morning does not
707
+ restrict unrelated work this afternoon.
708
+ - **Read-only tools are always allowed**, declared or not: `read_file`,
709
+ `list_dir`, `glob`, `grep`, plus `skill` and `skill_find`. What the declaration
710
+ actually controls is the five tools that change something — `write_file`,
711
+ `edit_file`, `bash`, `skill_install`, `skill_create`.
712
+ - **Claude Code names work.** `[Read, Bash, MultiEdit]` maps onto this host's
713
+ `read_file` / `bash` / `edit_file`. Tools that exist there but not here
714
+ (`WebFetch`, `Task`, `TodoWrite`, …) are ignored rather than treated as errors.
715
+ - **A name we cannot resolve waives the whole declaration**, and says so. One
716
+ typo means that skill imposes no ceiling at all — never a narrower one you did
717
+ not write. `aragon skills doctor` reports these up front.
718
+ - **`activation: always` skills never impose a ceiling.** They are ambient
719
+ reference material, not a procedure you chose to run.
720
+
721
+ Three modes, via `skills.toolPolicy` (default `enforce`),
722
+ `aragon --skill-tool-policy <mode>` for one run, or `/skills policy <mode>` for
723
+ the current session (which takes effect immediately, even mid-turn):
724
+
725
+ | Mode | Behaviour |
726
+ | --- | --- |
727
+ | `enforce` | The call is refused; the model is told what it may use instead. |
728
+ | `warn` | The call runs, and you get one notice per tool per turn. |
729
+ | `off` | No ceiling, and nothing about `allowed-tools` reaches the model. |
730
+
731
+ `/skills unload` drops the ceiling for the current turn. It does **not** remove
732
+ skill text already in the conversation — nothing can.
733
+
734
+ **What this is and is not.** It is **blast-radius control, not a security
735
+ boundary**. The declaration is written by the skill's own author, so a malicious
736
+ skill can simply declare `[bash]`. Its value is that a declaration is now
737
+ binding: you can read one in `/skills info` and decide whether to install, and an
738
+ honest author's `[read_file]` means a "while you're there, run `rm -rf`" line
739
+ smuggled into that skill's text — or into a file it tells the model to read —
740
+ cannot execute.
741
+
742
+ **And a known limit:** the ceiling is a **union** across every skill loaded this
743
+ turn, and `skill` is always available. So a skill can widen its own ceiling by
744
+ instructing the model to load a second, more permissive skill. This is not
745
+ blocked — blocking it would either strand the model inside a skill it cannot
746
+ escape, or break every multi-skill workflow — but every widening is printed to
747
+ the transcript:
748
+
749
+ ```
750
+ Tool ceiling widened by skill "skill-creator": +write_file, +skill_create
751
+ ```
752
+
753
+ So it stops the smuggled instruction; it does not stop a skill that deliberately
754
+ teaches the model to reach for another one.
755
+
756
+ ### Usage counters
757
+
758
+ To rank the catalog, the CLI keeps a small file next to your skills directory:
759
+
760
+ ```jsonc
761
+ // <aragon skills path>/../skill-usage.json
762
+ { "schema": 1, "skills": { "deploy-preview": { "useCount": 12, "lastUsedAt": 1785050000000 } } }
763
+ ```
764
+
765
+ **It contains the skill name, a count, and a timestamp. Nothing else** — no
766
+ arguments, no conversation content, no working directory, no machine identifier.
767
+ It is never transmitted anywhere, by anything. Delete it whenever you like; the
768
+ only effect is that the catalog falls back to plain scope-then-name order. Set
769
+ `skills.usageTracking=false` to switch it off entirely, after which the file is
770
+ neither read nor written.
771
+
772
+ Automatically injected `activation: always` skills are deliberately **not**
773
+ counted — they load on every turn, so counting them would say nothing about what
774
+ you actually use.
775
+
776
+ ## Configuration
777
+
778
+ Config is layered (highest priority last): **defaults → user config file →
779
+ env / `.env` → CLI flags**.
780
+
781
+ - **Env / `.env`**: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`,
782
+ `GOOGLE_API_KEY` / `GEMINI_API_KEY`, plus `ARAGON_PROVIDER`, `ARAGON_MODEL`,
783
+ `ARAGON_BASE_URL`, `ARAGON_THINKING`,
784
+ `ARAGON_MAX_TOKENS` (a number, or `auto` / `0` for the per-model ceiling),
785
+ `ARAGON_THEME`,
786
+ `ARAGON_FULLSCREEN`, `ARAGON_PLAN` (`1` starts in plan mode),
787
+ `ARAGON_TEAM` (`0` disables team subagents), `ARAGON_TEAM_MAX` (fan-out width),
788
+ `ARAGON_TODO` (`0` disables todo planning entirely),
789
+ `ARAGON_TODO_FOLLOW` (`notify` / `auto` / `off`),
790
+ `ARAGON_SKILLS` (`0` disables), `ARAGON_SKILLS_PATH`,
791
+ `ARAGON_SKILLS_DISABLED` (comma-separated names), `ARAGON_LOG_LEVEL`,
792
+ `ARAGON_LOG_FILE` (`0` disables), `ARAGON_LOG_DIR`.
793
+ `ARAGON_HOME` is the exception that **cannot** come from `.env`: it is read
794
+ before any `.env` is loaded. See [Files & logs](#files--logs).
795
+ - **User config file** (`aragon config path`): JSON, written `0600` on POSIX.
796
+ Holds provider / model / keys and your preferences, and nothing else — the
797
+ prompts you type and the counters behind one-time notices live in their own
798
+ files (see [Files & logs](#files--logs)). Keys are masked in the UI and never
799
+ logged. It lives in your home directory — see [Files & logs](#files--logs) —
800
+ and editing it by hand is supported: `aragon config edit` opens it, and an
801
+ invalid edit is reported rather than silently resetting every setting.
802
+
803
+ | Config key | Default | Meaning |
804
+ | --- | --- | --- |
805
+ | `maxTokens` | `64000` | Output token cap, clamped to `[256, 200000]`. `null` means **auto** — use each model's own ceiling, never above 64000. Absent means the default. See [Output token limits](#output-token-limits). |
806
+ | `fullscreen` | `true` | Use the full-screen TUI (still subject to the automatic downgrades). Setting it to `false` opts out permanently; leaving it `true` is *not* a force — only `--fullscreen` / `ARAGON_FULLSCREEN=1` override the heuristics. |
807
+ | `exitTranscript` | `true` | Replay a plain-text session summary after exiting (full-screen only). |
808
+ | `transcriptWindow` | `300` | How many trailing entries the full-screen viewport renders, clamped to `[50, 2000]`. The full history stays in memory and in `/save`. |
809
+ | `planModeDefault` | `false` | Start every session in plan mode. `--no-plan` still overrides it for a single run. |
810
+ | `planModeMaxAskRounds` | `4` | `ask_user` rounds allowed per user message, clamped to `[1, 10]`. The counter resets on your next message; steering does **not** reset it. |
811
+ | `planModeHumanTimeoutMs` | `1800000` | Ceiling on a single question / plan wait, clamped to `[60000, 7200000]`. Thirty minutes is far outside "went to get coffee", but a ceiling has to exist so an unattended terminal cannot wedge a CI job forever. |
812
+ | `historyEnabled` | `true` | Record submitted prompts to `prompt-history.jsonl` for `↑` recall. Setting it to `false` stops new writes; it does not delete what is already stored (that is `aragon history clear --yes`) and does not stop reading. See [Prompt history](#prompt-history). |
813
+ | `team.enabled` | `true` | Master switch for team subagents. `false` means the `task` tool is never registered — `/team on` in such a session saves the setting for next launch and says so, rather than advertising a tool that is not there. |
814
+ | `team.maxSubagents` | `5` | Subagents per dispatch, clamped to `[1, 10]`. The 10 is the hard ceiling and is enforced twice — on the config value and again on what the model asks for — so hand-editing this file cannot raise it. |
815
+ | `team.maxConcurrent` | `3` | Subagents in flight at once, clamped to `[1, 10]` and capped at `maxSubagents`. A separate knob because provider rate limits are a different constraint from context economics. |
816
+ | `team.subagentTimeoutMs` | `300000` | One subagent's wall clock before it is aborted, clamped to `[30000, 1800000]`. A wedged subagent dies on its own ceiling without taking the dispatch with it. |
817
+ | `team.dispatchTimeoutMs` | `900000` | The whole dispatch's wall clock, clamped to `[60000, 3600000]`. |
818
+ | `team.maxTurnsPerSubagent` | `24` | Runaway-loop cap, clamped to `[4, 100]`. A subagent stopped here is reported as `stopped: turn cap reached`, with whatever it had produced. |
819
+ | `todo.enabled` | `true` | Master switch for todo planning. `false` means the `todo_write` tool is never registered — `/todo on` in such a session saves the setting for next launch and says so, rather than advertising a tool that is not there. |
820
+ | `todo.panel` | `true` | Render the right-hand rail (and, in inline mode, the one-row plan strip). Independent of `todo.enabled` on purpose: a screen-reader user wants the planning discipline without the column, and the system prompt varies one sentence accordingly. |
821
+ | `todo.followThrough` | `"notify"` | What happens when a run ends with steps left. `notify` says so and stops; `auto` continues the plan after a 3-second grace window, bounded by two structural limits (one fruitless attempt, 25 continuations per plan) that are deliberately **not** settings; `off` is silent. An unrecognized value falls back to `notify`. |
822
+ | `skills.enabled` | `true` | Master switch for the skill system. |
823
+ | `skills.requireApproval` | `true` | Require a human to approve `skill_install` / `skill_create`. |
824
+ | `skills.catalogMaxBytes` | `6000` | Level 1 catalog ceiling, in UTF-8 **bytes**, clamped to `[500, 40000]`. |
825
+ | `skills.bodyMaxBytes` | `30000` | Level 2 body ceiling, in UTF-8 **bytes**, clamped to `[1000, 50000]`. |
826
+ | `skills.allowedHosts` | GitHub / GitLab hosts | Hosts an install **or update** may fetch from. Re-checked on every update, not just at install time. |
827
+ | `skills.disabled` | `[]` | Skill names switched off. |
828
+ | `skills.trustedProjectDirs` | `[]` | Project skill directories you have trusted. |
829
+ | `skills.integrity` | `warn` | What to do when `SKILL.md` no longer matches the copy you approved. `off` skips the check; `warn` flags it in `/skills list` and once per session; `strict` removes it from the catalog and makes `skill()` refuse it. Defaults to `warn` because editing an installed skill is a legitimate thing to do — `strict` suits CI and managed environments. |
830
+ | `skills.usageTracking` | `true` | Keep local per-skill use counters to rank the catalog. Name, count and timestamp only; never transmitted. Inspect with `aragon skills usage`, erase with `--reset --yes`. |
831
+ | `skills.toolPolicy` | `enforce` | How hard a skill's `allowed-tools` bites, for the turn it is loaded in. `off` disables it and says nothing about it to the model; `warn` runs the tool and reports it; `enforce` refuses. Unlike `skills.integrity`, a false positive here means a skill AUTHOR under-declared their own skill — which `aragon skills doctor` reports before it can bite. |
832
+ | `log.level` | `info` | `silent` · `error` · `warn` · `info` · `debug` · `trace`. See [Files & logs](#files--logs) for what each level records. |
833
+ | `log.toFile` | `true` | Write records to a file at all. `false` here is respected; `--log-file` overrides it for one run. |
834
+ | `log.dir` | `""` | Log directory. Empty means `<home>/logs`. Only **absolute** paths are accepted — a relative one would mean a different directory for every working directory you launch from. |
835
+ | `log.maxFileBytes` | `5242880` | Rotate once a file would exceed this, clamped to `[65536, 268435456]`. |
836
+ | `log.maxFiles` | `10` | Files kept in the log directory, clamped to `[1, 200]`. The oldest beyond this are deleted — on rotation **and** on the first write of each day. |
837
+ | `log.redactSecrets` | `true` | Run every record through redaction. Setting it to `false` is the one way to get credentials onto disk, so it prints a warning and has no settings-screen entry. |
838
+ | `log.previewChars` | `512` | How much user content `debug` keeps per field, clamped to `[0, 8192]`. `trace` is never truncated. |
839
+
840
+ Byte, not character: `ToolExecutor` truncates at 100 000 **bytes**, so a
841
+ character-based budget would be roughly triple its nominal size for CJK text
842
+ and would get cut mid-tag.
843
+
844
+ ### Output token limits
845
+
846
+ Every model and every provider defaults to **64000** output tokens. You never
847
+ have to configure this, and if you never touch it the number stays 64000.
848
+
849
+ The number you set is an **ambition, not a promise**: it is clamped down to
850
+ whatever the target model actually accepts. `gpt-4o` caps at 16384 and
851
+ `gemini-1.5-pro` at 8192, and sending 64000 to either is an HTTP 400 that ends
852
+ the turn — so `aragon` sends what the model will take, silently. When the model
853
+ is one it has never seen (a proxy, an endpoint behind `--base-url`, a model that
854
+ shipped last week), it sends 64000, and if the provider objects it reads the real
855
+ ceiling out of the provider's own error, repairs the request, sends it again, and
856
+ remembers the answer for the rest of the session. You see one warning line and
857
+ the run continues.
858
+
859
+ Four ways to change it, highest priority first:
860
+
861
+ ```bash
862
+ aragon --max-tokens 32000 # this run only
863
+ aragon --max-tokens auto # this run only, per-model ceiling
864
+ ARAGON_MAX_TOKENS=32000 aragon # env layer, between the flag and the file
865
+ aragon config set maxTokens 32000 # persisted; prints "Set maxTokens = 32000"
866
+ aragon config set maxTokens auto # persisted as null
867
+ aragon config set maxTokens default # back to 64000
868
+ aragon config get maxTokens # "64000" | "auto"
869
+ ```
870
+
871
+ Inside the TUI, `/max-tokens` reports the current setting **and** the effective
872
+ cap for the live model, and `/max-tokens <n|auto|default>` changes it. The
873
+ settings screen (`/settings`) has the same field with an `Effective:` line under
874
+ it, so a value the model will not honour is visible before you save it.
875
+
876
+ And by hand, in `~/.aragon-agent/config.json` (`aragon config path` prints the
877
+ exact location; `aragon config edit` opens it, and `/reload` picks up an edit
878
+ made in another window):
879
+
880
+ ```jsonc
881
+ {
882
+ "version": 1,
883
+ "provider": "anthropic",
884
+ "model": "claude-sonnet-4-5-20250929",
885
+
886
+ // Output token cap.
887
+ // 64000 (or any 256..200000 integer) — an explicit cap, clamped down to
888
+ // the target model's real ceiling when that ceiling is lower.
889
+ // null — auto: use each model's own ceiling, never above 64000.
890
+ // absent — the product default, 64000.
891
+ "maxTokens": 64000
892
+ }
893
+ ```
894
+
895
+ Out-of-range numbers are clamped on the way in **and** on the way out, so a
896
+ hand-edited `900000` becomes `200000` on disk rather than reverting to the
897
+ default on every launch.
898
+
899
+ Extended thinking is reconciled with the cap automatically: `--thinking xhigh`
900
+ asks for a 65536-token budget, Anthropic requires `max_tokens` to be strictly
901
+ greater than it, and the two are resolved together rather than left to collide.
902
+
903
+ These are additive: a config file written by an earlier version loads
904
+ unchanged, with no migration and no version bump.
905
+
906
+ ## Files & logs
907
+
908
+ Everything AragonAgent keeps for you lives under one directory in your home:
909
+
910
+ ```
911
+ ~/.aragon-agent/ (Windows: C:\Users\<you>\.aragon-agent\)
912
+ ├── config.json settings + API keys (0600 on POSIX)
913
+ ├── config.json.bak written by `aragon config edit` before it opens
914
+ ├── prompt-history.jsonl prompts you submitted, for ↑ recall (0600 on POSIX)
915
+ ├── state.json UI bookkeeping: submit count, one-shot notices
916
+ ├── logs/
917
+ │ ├── aragon-2026-07-27.log today, JSON Lines
918
+ │ └── aragon-2026-07-27.1.log rotated at log.maxFileBytes
919
+ ├── sessions/ /save and /resume
920
+ ├── skills/ aragon skills install
921
+ └── skill-usage.json
922
+ ```
923
+
924
+ `config.json` holds **settings only**. What you typed and how often you have
925
+ typed it live in the two files next to it, so that editing your config means
926
+ scrolling past your own settings and nothing else, and so that submitting a
927
+ prompt no longer rewrites the file that holds your API keys.
928
+
929
+ `ARAGON_HOME` moves the whole tree. **It cannot come from a `.env` file** — the
930
+ path is resolved when the process starts, long before a `.env` is read — so set
931
+ it as a real environment variable. `aragon config home` always prints the
932
+ directory actually in use.
933
+
934
+ Upgrading from 0.5.0 or earlier moves your config, sessions and skills here
935
+ automatically, once, and prints a line on stderr when it does. The old
936
+ `%APPDATA%\aragon-agent-nodejs` / `%LOCALAPPDATA%\aragon-agent-nodejs`
937
+ directories are **left in place** so that reinstalling an older build stays
938
+ lossless; `aragon config home` names them, and you can delete them once you are
939
+ satisfied.
940
+
941
+ ### Prompt history
942
+
943
+ Every prompt you submit is appended to `~/.aragon-agent/prompt-history.jsonl`,
944
+ one JSON object per line, so that `↑` recalls it — in this session and in every
945
+ later one. It is written `0600` on POSIX, it never leaves the machine, and it is
946
+ **stored verbatim**: a redacted history would recall the wrong text, which is
947
+ worse than not recalling at all.
948
+
949
+ The last 100 distinct prompts are what `↑` walks. The file is rewritten down to
950
+ that many entries once it passes 400 lines, and a single submission longer than
951
+ 16 KiB is not recorded at all rather than recorded truncated.
952
+
953
+ ```bash
954
+ aragon history path # where it is, plus a reminder of what is in it
955
+ aragon history list -n 50 # the 50 most recent, newest first
956
+ aragon history list --json # raw entries with timestamps
957
+ aragon history clear --yes # delete the file
958
+ ```
959
+
960
+ Set `historyEnabled` to `false` (`aragon config set historyEnabled false`) to
961
+ stop recording. Switching it off does **not** delete what is already stored —
962
+ `aragon history clear --yes` does that — and reading keeps working either way,
963
+ so you can look before you erase.
964
+
965
+ Upgrading from 0.5.x moves an existing `promptHistory` out of `config.json` into
966
+ this file once, automatically and silently.
967
+
968
+ ### What gets logged
969
+
970
+ Logs are local files. Nothing is uploaded, ever — there is no network path out
971
+ of this subsystem at all.
972
+
973
+ | Level | Records | Contains your content? |
974
+ | --- | --- | --- |
975
+ | `error` | crashes, failed requests, config write/parse failures | no |
976
+ | `warn` | degraded migrations, clamped values, dropped records | no |
977
+ | `info` *(default)* | start/exit, config writes, turn boundaries with token counts, tool names and durations | **no** |
978
+ | `debug` | the above, plus prompts, replies and tool arguments truncated to `log.previewChars` | yes, truncated |
979
+ | `trace` | full message bodies and full tool output | yes, in full |
980
+
981
+ The default is `info` specifically so that attaching a log to a bug report is
982
+ safe. **`debug` and `trace` write what you typed and what the model replied** —
983
+ check before sharing one.
984
+
985
+ API keys are removed at every level, by two independent passes: any field whose
986
+ name looks like a credential is blanked whatever it holds, and every string is
987
+ scanned for known key formats *and* for the literal keys this process is
988
+ holding. The second half is what covers a custom endpoint whose key looks like
989
+ nothing in particular. Setting `log.redactSecrets false` disables both, and says
990
+ so on stdout when you do.
991
+
992
+ ### Commands
993
+
994
+ ```bash
995
+ aragon logs path # where records are going right now, and at what level
996
+ aragon logs list # files, sizes, timestamps
997
+ aragon logs tail -n 50 # render the last 50 records
998
+ aragon logs tail --follow # keep printing new ones
999
+ aragon logs tail --json # raw JSON Lines, for jq
1000
+ aragon logs tail --level warn # only warn and above
1001
+ aragon logs clear --yes # delete every log file
1002
+ aragon logs open # open the directory in your file manager
1003
+
1004
+ aragon history path # where prompt history is kept
1005
+ aragon history list -n 50 # the 50 most recent prompts, newest first
1006
+ aragon history clear --yes # delete the prompt history
1007
+
1008
+ aragon config home # the user-state root (and whether ARAGON_HOME set it)
1009
+ aragon config get log.level # one value; secrets always masked
1010
+ aragon config list --json # everything; secrets always masked here too
1011
+ aragon config edit # open config.json in $VISUAL / $EDITOR
1012
+ ```
1013
+
1014
+ Per-run overrides, none of which are persisted:
1015
+
1016
+ ```bash
1017
+ aragon --log-level debug "…" # or --verbose, which means the same thing
1018
+ aragon --no-log-file "…" # nothing reaches a file this run
1019
+ aragon --log-dir D:\logs "…"
1020
+ ```
1021
+
1022
+ `ARAGON_LOG_LEVEL`, `ARAGON_LOG_FILE=0` and `ARAGON_LOG_DIR` do the same from the
1023
+ environment, and unlike `ARAGON_HOME` these three *can* come from a `.env`.
1024
+
1025
+ ### Insecure TLS opt-in
1026
+
1027
+ When `NODE_TLS_REJECT_UNAUTHORIZED` is the exact string `0`, the installed or
1028
+ locally built `aragon` process honors that setting and suppresses only Node's
1029
+ standard insecure-TLS warning. Every unrelated process warning remains visible,
1030
+ and any other value leaves warning behavior unchanged. The check happens when a
1031
+ warning is emitted, so a `0` loaded from the project `.env` is covered too; the
1032
+ CLI never creates, normalizes, or persists this variable.
1033
+
1034
+ > **Security warning:** `0` genuinely disables certificate verification. It does
1035
+ > not make an insecure connection safe and can expose credentials or request
1036
+ > contents to interception. Use it only in a controlled environment where you
1037
+ > accept that risk.
1038
+
1039
+ The automatic filter starts with the `aragon` process. An earlier `npm install`
1040
+ or `npx` bootstrap belongs to npm and is outside the installed package's process
1041
+ boundary, so that parent may still print Node's warning. The official repository
1042
+ release flow covers its npm children through `publish-latest.ps1`; callers that
1043
+ own another npm/npx parent must preload the filter in that parent themselves if
1044
+ quiet output is required.
1045
+
1046
+ ### Theming & color
1047
+
1048
+ `--theme` / `config.theme` / `/theme` pick a palette: `warm`, `cool`, `light`, or
1049
+ `auto` (which resolves to `warm` — terminals can't reliably report their
1050
+ background). `dark` remains accepted everywhere as an alias for `cool`.
1051
+ Colors degrade automatically to your terminal's depth (truecolor → 256 → 16 →
1052
+ monochrome) and glyphs fall back to ASCII on terminals without Unicode. Color is
1053
+ disabled — and the UI renders plain monochrome with ASCII glyphs — when any of
1054
+ `NO_COLOR`, `--no-color`, `TERM=dumb`, or `config.color=false` is set; `FORCE_COLOR`
1055
+ is respected. `reducedMotion` (also `ARAGON_REDUCED_MOTION`, and implied by
1056
+ `--no-color`) replaces spinners with a static glyph.
1057
+
1058
+ ### Timeout invariant
1059
+
1060
+ The core idle watchdog (60 s by default) is **shorter** than the tool timeout
1061
+ (120 s), so a long single tool run (a build, `npm install`) would be killed
1062
+ mid-run. The CLI fixes this by always constructing the agent with
1063
+ `idleTimeout ≥ toolTimeout` (defaults 210 s ≥ 180 s), re-derived after config
1064
+ merge. Raise both `--tool-timeout` / `--idle-timeout` for genuinely long jobs.
1065
+
1066
+ ## Development
1067
+
1068
+ ```bash
1069
+ npm run build -w packages/cli # tsc + shebang → dist/cli.js
1070
+ npm test -w packages/cli # vitest
1071
+ node packages/cli/dist/cli.js --version
1072
+ ```
1073
+
1074
+ ### Manual smoke checklist
1075
+
1076
+ - `npm run build -w packages/cli` produces an executable `dist/cli.js` with a
1077
+ shebang; `node packages/cli/dist/cli.js --version` prints the version.
1078
+ - `npm pack -w packages/cli --dry-run --json` lists `dist/cli.js` under `files`.
1079
+ - With `ANTHROPIC_API_KEY` set: `aragon` streams a reply, a tool card renders,
1080
+ `Esc` aborts mid-run, `/model` switches models, `/settings` saves a key,
1081
+ `Ctrl+C` twice exits.
1082
+ - `echo "list files" | node dist/cli.js -p` prints an answer and exits `0`.
1083
+ - A missing/invalid key produces a visible, actionable message (never a blank
1084
+ screen), both in the TUI and headless.
1085
+ - `Shift+Tab` at 40, 80 and 120 columns - the chip, the hint and the status bar
1086
+ are all legible and nothing wraps onto a second row.
1087
+ - The question wizard at 24 rows with 5 questions x 4 options - no clipping, and
1088
+ the `n/N` counter matches what is on screen.
1089
+ - Approve a plan and watch the SAME run start writing files, with no second
1090
+ prompt.
1091
+ - `--no-color` on a legacy `cmd.exe` - the chip renders `[PLAN]`, radios render
1092
+ `*`/`o`, checkboxes render `[x]`/`[ ]`, no mojibake anywhere.
1093
+ - A plan with a 600-character summary and a 400-character step detail - the text
1094
+ wraps and scrolls, the footer count matches the real number of rows, and
1095
+ nothing is truncated to a single line.
1096
+ - `--no-hints` on a 20-row terminal in plan mode - the status bar still says
1097
+ `PLAN` with no chip on screen.
1098
+ - Dismiss a plan card with `Esc`, let the agent resubmit, then press `Esc` twice:
1099
+ the run aborts and the session returns to an idle prompt. Do this one BY HAND
1100
+ as well as in a test - the point of the check is whether the exit is findable,
1101
+ which no assertion can tell you.
1102
+
1103
+ ## License
1104
+
1105
+ MIT