@aragon-agent/cli 0.5.0 → 0.5.1

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 (82) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +227 -3
  3. package/dist/agent/agent-mode.d.ts +53 -0
  4. package/dist/agent/agent-mode.js +69 -0
  5. package/dist/agent/controller.d.ts +69 -0
  6. package/dist/agent/controller.js +154 -1
  7. package/dist/agent/plan-prompt.d.ts +18 -0
  8. package/dist/agent/plan-prompt.js +93 -0
  9. package/dist/agent/reducer.d.ts +14 -1
  10. package/dist/agent/reducer.js +9 -0
  11. package/dist/agent/system-prompt.d.ts +15 -0
  12. package/dist/agent/system-prompt.js +8 -0
  13. package/dist/cli.d.ts +1 -1
  14. package/dist/cli.js +183 -20
  15. package/dist/commands/builtins.js +60 -0
  16. package/dist/commands/registry.d.ts +17 -0
  17. package/dist/config/app-paths.d.ts +71 -4
  18. package/dist/config/app-paths.js +139 -8
  19. package/dist/config/cli-commands.d.ts +43 -0
  20. package/dist/config/cli-commands.js +196 -0
  21. package/dist/config/env.d.ts +7 -1
  22. package/dist/config/env.js +45 -1
  23. package/dist/config/load.d.ts +8 -0
  24. package/dist/config/load.js +59 -2
  25. package/dist/config/migrate-home.d.ts +63 -0
  26. package/dist/config/migrate-home.js +165 -0
  27. package/dist/config/migrate-legacy-state.d.ts +10 -0
  28. package/dist/config/migrate-legacy-state.js +15 -6
  29. package/dist/config/schema.d.ts +61 -0
  30. package/dist/config/schema.js +83 -0
  31. package/dist/config/store.d.ts +30 -12
  32. package/dist/config/store.js +47 -35
  33. package/dist/logging/cli-commands.d.ts +25 -0
  34. package/dist/logging/cli-commands.js +221 -0
  35. package/dist/logging/file-sink.d.ts +114 -0
  36. package/dist/logging/file-sink.js +326 -0
  37. package/dist/logging/install.d.ts +163 -0
  38. package/dist/logging/install.js +302 -0
  39. package/dist/logging/levels.d.ts +40 -0
  40. package/dist/logging/levels.js +59 -0
  41. package/dist/logging/logger.d.ts +110 -0
  42. package/dist/logging/logger.js +237 -0
  43. package/dist/logging/redact.d.ts +28 -0
  44. package/dist/logging/redact.js +90 -0
  45. package/dist/logging/secret-registry.d.ts +30 -0
  46. package/dist/logging/secret-registry.js +55 -0
  47. package/dist/skills/paths.d.ts +2 -4
  48. package/dist/skills/paths.js +7 -9
  49. package/dist/tools/human-input.d.ts +179 -0
  50. package/dist/tools/human-input.js +227 -0
  51. package/dist/tools/index.d.ts +35 -3
  52. package/dist/tools/index.js +96 -9
  53. package/dist/tools/plan-tools.d.ts +64 -0
  54. package/dist/tools/plan-tools.js +198 -0
  55. package/dist/ui/App.d.ts +4 -1
  56. package/dist/ui/App.js +182 -11
  57. package/dist/ui/Composer.d.ts +4 -1
  58. package/dist/ui/Composer.js +26 -7
  59. package/dist/ui/ModeChip.d.ts +29 -0
  60. package/dist/ui/ModeChip.js +15 -0
  61. package/dist/ui/PromptInput.d.ts +8 -1
  62. package/dist/ui/PromptInput.js +12 -2
  63. package/dist/ui/StatusBar.d.ts +15 -0
  64. package/dist/ui/StatusBar.js +10 -2
  65. package/dist/ui/entries/ToolPreview.d.ts +2 -0
  66. package/dist/ui/entries/ToolPreview.js +64 -0
  67. package/dist/ui/glyphs.d.ts +9 -0
  68. package/dist/ui/glyphs.js +8 -0
  69. package/dist/ui/layout/wrap-rows.d.ts +24 -0
  70. package/dist/ui/layout/wrap-rows.js +74 -0
  71. package/dist/ui/overlays/HelpOverlay.js +13 -0
  72. package/dist/ui/overlays/PlanReviewOverlay.d.ts +50 -0
  73. package/dist/ui/overlays/PlanReviewOverlay.js +117 -0
  74. package/dist/ui/overlays/QuestionOverlay.d.ts +30 -0
  75. package/dist/ui/overlays/QuestionOverlay.js +193 -0
  76. package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
  77. package/dist/ui/overlays/SettingsScreen.js +8 -1
  78. package/dist/ui/use-startup-notices.d.ts +15 -0
  79. package/dist/ui/use-startup-notices.js +38 -0
  80. package/package.json +3 -2
  81. package/runtime/insecure-tls-warning.cjs +62 -0
  82. package/runtime/insecure-tls-warning.d.cts +5 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,137 @@ All notable changes to `@aragon-agent/cli` are documented here. Entries at
5
5
  — those releases really were published under that name, so they are left as they
6
6
  were written.
7
7
 
8
+ ## Unreleased
9
+
10
+ ### Changed
11
+
12
+ - **Your files moved to `~/.aragon-agent/`.** Config, sessions, installed skills
13
+ and the usage counters now live in one directory in your home — on Windows
14
+ `C:\Users\<you>\.aragon-agent\`, and the same path on macOS and Linux. They
15
+ used to be split across `%APPDATA%\aragon-agent-nodejs\Config` and
16
+ `%LOCALAPPDATA%\aragon-agent-nodejs\Data` (or the XDG equivalents), which are
17
+ correct locations and terrible answers to "where do I change the model?".
18
+
19
+ **The move happens once, automatically, on first launch, and prints a line on
20
+ stderr when it does.** Nothing is deleted: `config.json` is COPIED rather than
21
+ moved, and the old directories are left where they are, so reinstalling 0.5.0
22
+ remains lossless. `aragon config home` prints the new location and names the
23
+ old ones. Once you are satisfied, you can delete them yourself.
24
+
25
+ `ARAGON_HOME` relocates the whole tree. It has to be a real environment
26
+ variable — the path is resolved before any `.env` is read, so putting it in a
27
+ `.env` will not work. `aragon config home` always prints what is actually in
28
+ use.
29
+
30
+ ### Added
31
+
32
+ - **Logging.** Records go to `~/.aragon-agent/logs/aragon-<date>.log` as JSON
33
+ Lines: startup and exit, config writes, turn boundaries with token counts,
34
+ tool names with durations, skill loads, and every uncaught error. Files rotate
35
+ by day and by size (`log.maxFileBytes`, default 5 MiB) and the oldest beyond
36
+ `log.maxFiles` (default 10) are deleted.
37
+
38
+ **`info`, the default, records no conversation content**, so attaching a log
39
+ to a bug report is safe. `--log-level debug` (or `--verbose`) adds prompts,
40
+ replies and tool arguments truncated to `log.previewChars`; `trace` adds them
41
+ in full. Those two write what you typed — check before sharing one.
42
+
43
+ API keys are removed at every level by two independent passes: fields whose
44
+ name looks like a credential are blanked whatever they hold, and every string
45
+ is scanned both for known key formats and for the literal keys this process is
46
+ holding — which is what covers a custom endpoint whose key looks like nothing
47
+ in particular. `log.redactSecrets false` turns both off and warns you on
48
+ stdout when you set it.
49
+
50
+ Nothing is ever uploaded. There is no network path out of this subsystem.
51
+
52
+ New commands: `aragon logs path | list | tail [-n N] [--follow] [--level lv]
53
+ [--json] | clear --yes | open`, and `/logs` inside the TUI. New flags:
54
+ `--log-level`, `--verbose`, `--log-file` / `--no-log-file`, `--log-dir`. New
55
+ environment variables: `ARAGON_LOG_LEVEL`, `ARAGON_LOG_FILE`, `ARAGON_LOG_DIR`
56
+ — these three, unlike `ARAGON_HOME`, can come from a `.env`.
57
+
58
+ - **`aragon config get <key>` / `list [--json]` / `edit` / `home`.** `get` and
59
+ `list` mask every secret, `--json` included. `edit` opens `config.json` in
60
+ `$VISUAL` / `$EDITOR` (`notepad` on Windows), takes a `config.json.bak` first,
61
+ and re-parses on exit — an invalid edit is reported immediately, and the
62
+ backup is named in the message.
63
+
64
+ - **`/reload`** re-reads `config.json` into the running session, for when you
65
+ have edited it in another window. Refused while a run is in progress: swapping
66
+ the model mid-turn would let the second half of an answer come from different
67
+ settings than the first, with nothing afterwards able to explain the result.
68
+
69
+ - **Log level in the settings screen**, alongside provider / model / thinking.
70
+ The rest of the log settings — and `redactSecrets` above all — stay
71
+ command-line-only, so the one switch that can put credentials on disk cannot
72
+ be flipped with an arrow key.
73
+
74
+ ### Fixed
75
+
76
+ - **A malformed `config.json` no longer resets everything in silence.** A stray
77
+ comma used to send the model, theme, timeouts, skills and (as far as you could
78
+ tell) your API key back to defaults, with nothing anywhere explaining it. The
79
+ fallback is unchanged — a bad config still never stops the CLI from starting —
80
+ but it now says so, and records why.
81
+
82
+ - **Ctrl+C now flushes pending log records in every mode.** The signal handlers
83
+ only existed in the full-screen branch, so `-p`, the inline renderer and every
84
+ subcommand took Node's default termination, which does not run exit handlers.
85
+
86
+ ### Added
87
+
88
+ - **Plan mode.** `Shift+Tab` toggles between `BUILD` (everything as before) and
89
+ `PLAN`, a read-only research-and-design posture. `/plan [on|off|status]` is a
90
+ first-class equivalent for terminals that swallow `CSI Z`, and `--plan` /
91
+ `--no-plan` / `ARAGON_PLAN=1` / `planModeDefault` cover the startup case.
92
+
93
+ In `PLAN`, `write_file`, `edit_file`, `bash`, `skill_install` and
94
+ `skill_create` are refused **at the tool boundary**, so read-only is a
95
+ property of the wiring rather than a promise in a prompt. `bash` is refused in
96
+ full, `git status` included: a shell string cannot be classified reliably, and
97
+ a gate that is right most of the time is worse than one that is always right,
98
+ because you stop trusting the badge. The refusal names `read_file` / `glob` /
99
+ `grep` as the alternatives.
100
+
101
+ Two tools exist only while a human is attached. `ask_user` renders a
102
+ keyboard-driven wizard - 1-5 questions, 2-4 options each, exactly one marked
103
+ `RECOMMENDED`, plus an `Other...` free-text option - where pressing `Enter`
104
+ through the wizard accepts every recommendation. `submit_plan` renders the
105
+ plan as a scrollable card: `a` approves, `r` sends written feedback (`<-`
106
+ returns to the card), `Esc` dismisses. **Approving flips the session to
107
+ `BUILD` immediately and the same run continues into implementation.**
108
+
109
+ Dismissing a plan asks for a better one rather than stopping - the agent is
110
+ told to refine and resubmit - so the way out of a run is `Esc` twice: the
111
+ first closes the card, the second aborts the run.
112
+
113
+ Toggling mid-run is asymmetric on purpose: `build -> plan` applies at once,
114
+ `plan -> build` is deferred to the end of the run (the status bar shows
115
+ `PLAN -> BUILD` meanwhile) so a run launched under a read-only guarantee
116
+ cannot start writing files because of one stray keypress.
117
+
118
+ `aragon -p --plan "..."` registers no question or plan tool - there is nobody
119
+ to ask - but the read-only gate still applies, so it means "tell me how you
120
+ would do this, and do not touch my repository".
121
+
122
+ - Three flat config keys: `planModeDefault` (default `false`),
123
+ `planModeMaxAskRounds` (default `4`, clamped `[1, 10]`) and
124
+ `planModeHumanTimeoutMs` (default `1800000`, clamped `[60000, 7200000]`). All
125
+ three are settable with `aragon config set`. Additive, as always: a config
126
+ file written by an earlier version loads unchanged.
127
+
128
+ ### Fixed
129
+
130
+ - A `--confirm` prompt no longer races the idle watchdog. Taking longer than
131
+ `idleTimeoutMs` over `Proceed? (y/N)` used to abort the run with no
132
+ explanation; every human wait now suspends the watchdog and restarts it in a
133
+ `finally`.
134
+
135
+ - Honor an explicit `NODE_TLS_REJECT_UNAUTHORIZED=0` setting in the CLI and the
136
+ official npm release workflow while hiding only Node's standard insecure-TLS
137
+ warning. Other process warnings remain visible.
138
+
8
139
  ## 0.5.0
9
140
 
10
141
  ### Breaking
package/README.md CHANGED
@@ -71,6 +71,7 @@ aragon --help | -h Print help
71
71
  | `--fullscreen` | Force the full-screen TUI, overriding the automatic downgrades |
72
72
  | `--no-fullscreen` | Force the inline renderer (the 0.2.0 behavior) |
73
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 |
74
75
  | `--no-skills` | Disable the skill system entirely (no catalog, no skill tools) |
75
76
  | `--skill <name>` | Force-load a skill's full body for this run (repeatable) |
76
77
  | `--skills-yes` | Approve skill installs for this run (CI / headless) |
@@ -94,9 +95,12 @@ Exit codes: `0` success · `1` agent/runtime error · `2` config/usage error ·
94
95
  | `/cwd [dir]` | Show / change the tool working directory |
95
96
  | `/save [file]` | Save the session to JSON |
96
97
  | `/resume [file]` | Load a saved session |
98
+ | `/plan [on\|off\|status]` | Toggle plan mode - the keyboard-free equivalent of `Shift+Tab` |
97
99
  | `/theme <auto\|warm\|cool\|light>` | Switch the color theme live (persisted) |
98
100
  | `/expand [n]` | Expand / collapse the n-th-from-last (default last) tool card |
99
101
  | `/copy` | Copy the last answer to the clipboard |
102
+ | `/logs` | Show where this session is being logged, and at what level |
103
+ | `/reload` | Re-read `config.json` after editing it in another window (refused mid-run) |
100
104
  | `/skills [sub]` | List and manage skills (see below) |
101
105
  | `/<skill-name> [args]` | Run an installed skill directly |
102
106
  | `/exit` (`/quit`) | Exit |
@@ -111,6 +115,7 @@ completes, `Up` / `Down` moves the selection, `Esc` closes it.
111
115
  | --- | --- |
112
116
  | `Enter` | Submit (idle) / queue a steering message (running) |
113
117
  | `Alt+Enter` / `Shift+Enter` | Insert a newline |
118
+ | `Shift+Tab` | Toggle **plan mode** (`BUILD` <-> `PLAN`). Equivalent: `/plan` |
114
119
  | `Esc` | Abort the run / close an overlay / close a popup |
115
120
  | `Ctrl+C` ×2 | Exit (first press warns) |
116
121
  | `Ctrl+L` | Redraw the frame (clear the screen in inline mode) |
@@ -184,6 +189,90 @@ this mode, and mouse tracking is deliberately left off because enabling it costs
184
189
  text selection and copy in most terminals. If a crash ever strands your terminal
185
190
  on the alternate screen, `reset` restores it.
186
191
 
192
+ ## Plan mode
193
+
194
+ `aragon` has two postures, and `Shift+Tab` switches between them. `/plan` does
195
+ the same thing for terminals that swallow the key (see below).
196
+
197
+ | Mode | What it means |
198
+ | --- | --- |
199
+ | `BUILD` | The default, and unchanged from earlier versions: you ask, the agent does it. |
200
+ | `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. |
201
+
202
+ `BUILD` is right for "rename this symbol". `PLAN` is right for "add multi-tenant
203
+ support" — anything where you want to see and steer the *approach* before a
204
+ single byte changes.
205
+
206
+ ### What plan mode actually stops
207
+
208
+ In `PLAN`, five tools are refused at the tool boundary: `write_file`,
209
+ `edit_file`, `bash`, `skill_install` and `skill_create`. That is a property of
210
+ the wiring, not a promise in a prompt — the refusal happens before the tool runs,
211
+ so the badge is telling you something that is structurally true.
212
+
213
+ **`bash` is refused in full, `git status` included.** This is the most likely
214
+ question this feature generates, so: there is no reliable way to classify a shell
215
+ string as read-only, and a gate that is right 95 % of the time is worse than one
216
+ that is always right, because you stop trusting the badge. Use `read_file`,
217
+ `list_dir`, `glob` and `grep`, which stay available; the refusal message names
218
+ them so the agent has somewhere to go. There is no allowlist and there will not
219
+ be one built out of substring matching.
220
+
221
+ Everything else still works: reading, searching, loading skills, and asking you
222
+ questions.
223
+
224
+ ### The two plan-mode tools
225
+
226
+ - **`ask_user`** renders a keyboard-driven wizard: 1–5 questions, 2–4 options
227
+ each, exactly one marked `RECOMMENDED`, plus an `Other…` free-text option.
228
+ `↑`/`↓` choose, `Enter` confirms and advances, `←` goes back, `Esc` cancels.
229
+ Pressing `Enter` through the whole wizard accepts every recommendation, which
230
+ is a deliberate "use your judgement" path rather than an accident. A last
231
+ screen lets you review before submitting.
232
+ - **`submit_plan`** renders the plan as a scrollable card: `a` approves, `r`
233
+ opens a one-line feedback field (`Enter` sends it, `←` goes back to the card),
234
+ `Esc` dismisses. **Approving flips the session to `BUILD` immediately and the
235
+ same run continues straight into implementation** — you do not have to re-ask.
236
+
237
+ Cancelling either one is not an error. The agent is told to proceed on stated
238
+ assumptions rather than to retry.
239
+
240
+ **Dismissing a plan is not how you stop.** `Esc` on a plan card asks for a
241
+ *better* plan — the agent is told to refine it and submit again — so a model that
242
+ keeps submitting keeps getting cards. To stop the run itself, **press `Esc`
243
+ twice**: the first closes the card, the second aborts the run. `Ctrl+C` twice
244
+ still exits the session outright.
245
+
246
+ ### Where the mode is shown
247
+
248
+ The status bar always names a non-default mode (`PLAN`, or `PLAN → BUILD` while
249
+ a switch is pending), including on short terminals, with `--no-hints`, and in
250
+ inline mode. The composer additionally shows a `PLAN` chip on its hint row and
251
+ tints its border, where there is room for it.
252
+
253
+ ### Switching mid-run
254
+
255
+ Tightening applies at once; loosening waits. Pressing `Shift+Tab` toward `PLAN`
256
+ during a run takes effect immediately — you pressed it *because* the agent is
257
+ about to do something. Pressing it toward `BUILD` during a run is **deferred to
258
+ the end of the run**, so a session you launched under a read-only guarantee
259
+ cannot start writing files because of one stray keypress. The status bar shows
260
+ `PLAN → BUILD` while that is pending. Approving a plan is the one exception, and
261
+ it is an informed act rather than a stray keypress.
262
+
263
+ ### If `Shift+Tab` does nothing
264
+
265
+ Some multiplexers and remote-desktop stacks never send `CSI Z`. `/plan`,
266
+ `/plan on`, `/plan off` and `/plan status` are first-class equivalents, and
267
+ `--plan` / `ARAGON_PLAN=1` / `planModeDefault` cover the startup case.
268
+
269
+ ### Headless
270
+
271
+ `aragon -p --plan "add SSO"` is a useful combination: the read-only gate still
272
+ applies, so it means *"tell me how you would do this, and do not touch my
273
+ repository"*. No question or plan tool is registered there — there is nobody to
274
+ ask — and the agent writes the finished plan as its final message in markdown.
275
+
187
276
  ## Built-in tools
188
277
 
189
278
  `read_file`, `write_file`, `edit_file`, `list_dir`, `glob`, `grep`, `bash` — all
@@ -484,17 +573,26 @@ env / `.env` → CLI flags**.
484
573
  - **Env / `.env`**: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`,
485
574
  `GOOGLE_API_KEY` / `GEMINI_API_KEY`, plus `ARAGON_PROVIDER`, `ARAGON_MODEL`,
486
575
  `ARAGON_BASE_URL`, `ARAGON_THINKING`, `ARAGON_MAX_TOKENS`, `ARAGON_THEME`,
487
- `ARAGON_FULLSCREEN`, `ARAGON_SKILLS` (`0` disables), `ARAGON_SKILLS_PATH`,
488
- `ARAGON_SKILLS_DISABLED` (comma-separated names).
576
+ `ARAGON_FULLSCREEN`, `ARAGON_PLAN` (`1` starts in plan mode),
577
+ `ARAGON_SKILLS` (`0` disables), `ARAGON_SKILLS_PATH`,
578
+ `ARAGON_SKILLS_DISABLED` (comma-separated names), `ARAGON_LOG_LEVEL`,
579
+ `ARAGON_LOG_FILE` (`0` disables), `ARAGON_LOG_DIR`.
580
+ `ARAGON_HOME` is the exception that **cannot** come from `.env`: it is read
581
+ before any `.env` is loaded. See [Files & logs](#files--logs).
489
582
  - **User config file** (`aragon config path`): JSON, written `0600` on POSIX.
490
583
  Holds provider/model/keys and MRU lists. Keys are masked in the UI and never
491
- logged.
584
+ logged. It lives in your home directory — see [Files & logs](#files--logs) —
585
+ and editing it by hand is supported: `aragon config edit` opens it, and an
586
+ invalid edit is reported rather than silently resetting every setting.
492
587
 
493
588
  | Config key | Default | Meaning |
494
589
  | --- | --- | --- |
495
590
  | `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. |
496
591
  | `exitTranscript` | `true` | Replay a plain-text session summary after exiting (full-screen only). |
497
592
  | `transcriptWindow` | `300` | How many trailing entries the full-screen viewport renders, clamped to `[50, 2000]`. The full history stays in memory and in `/save`. |
593
+ | `planModeDefault` | `false` | Start every session in plan mode. `--no-plan` still overrides it for a single run. |
594
+ | `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. |
595
+ | `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. |
498
596
  | `skills.enabled` | `true` | Master switch for the skill system. |
499
597
  | `skills.requireApproval` | `true` | Require a human to approve `skill_install` / `skill_create`. |
500
598
  | `skills.catalogMaxBytes` | `6000` | Level 1 catalog ceiling, in UTF-8 **bytes**, clamped to `[500, 40000]`. |
@@ -505,6 +603,13 @@ env / `.env` → CLI flags**.
505
603
  | `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. |
506
604
  | `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`. |
507
605
  | `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. |
606
+ | `log.level` | `info` | `silent` · `error` · `warn` · `info` · `debug` · `trace`. See [Files & logs](#files--logs) for what each level records. |
607
+ | `log.toFile` | `true` | Write records to a file at all. `false` here is respected; `--log-file` overrides it for one run. |
608
+ | `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. |
609
+ | `log.maxFileBytes` | `5242880` | Rotate once a file would exceed this, clamped to `[65536, 268435456]`. |
610
+ | `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. |
611
+ | `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. |
612
+ | `log.previewChars` | `512` | How much user content `debug` keeps per field, clamped to `[0, 8192]`. `trace` is never truncated. |
508
613
 
509
614
  Byte, not character: `ToolExecutor` truncates at 100 000 **bytes**, so a
510
615
  character-based budget would be roughly triple its nominal size for CJK text
@@ -513,6 +618,108 @@ and would get cut mid-tag.
513
618
  These are additive: a config file written by an earlier version loads
514
619
  unchanged, with no migration and no version bump.
515
620
 
621
+ ## Files & logs
622
+
623
+ Everything AragonAgent keeps for you lives under one directory in your home:
624
+
625
+ ```
626
+ ~/.aragon-agent/ (Windows: C:\Users\<you>\.aragon-agent\)
627
+ ├── config.json settings + API keys (0600 on POSIX)
628
+ ├── config.json.bak written by `aragon config edit` before it opens
629
+ ├── logs/
630
+ │ ├── aragon-2026-07-27.log today, JSON Lines
631
+ │ └── aragon-2026-07-27.1.log rotated at log.maxFileBytes
632
+ ├── sessions/ /save and /resume
633
+ ├── skills/ aragon skills install
634
+ └── skill-usage.json
635
+ ```
636
+
637
+ `ARAGON_HOME` moves the whole tree. **It cannot come from a `.env` file** — the
638
+ path is resolved when the process starts, long before a `.env` is read — so set
639
+ it as a real environment variable. `aragon config home` always prints the
640
+ directory actually in use.
641
+
642
+ Upgrading from 0.5.0 or earlier moves your config, sessions and skills here
643
+ automatically, once, and prints a line on stderr when it does. The old
644
+ `%APPDATA%\aragon-agent-nodejs` / `%LOCALAPPDATA%\aragon-agent-nodejs`
645
+ directories are **left in place** so that reinstalling an older build stays
646
+ lossless; `aragon config home` names them, and you can delete them once you are
647
+ satisfied.
648
+
649
+ ### What gets logged
650
+
651
+ Logs are local files. Nothing is uploaded, ever — there is no network path out
652
+ of this subsystem at all.
653
+
654
+ | Level | Records | Contains your content? |
655
+ | --- | --- | --- |
656
+ | `error` | crashes, failed requests, config write/parse failures | no |
657
+ | `warn` | degraded migrations, clamped values, dropped records | no |
658
+ | `info` *(default)* | start/exit, config writes, turn boundaries with token counts, tool names and durations | **no** |
659
+ | `debug` | the above, plus prompts, replies and tool arguments truncated to `log.previewChars` | yes, truncated |
660
+ | `trace` | full message bodies and full tool output | yes, in full |
661
+
662
+ The default is `info` specifically so that attaching a log to a bug report is
663
+ safe. **`debug` and `trace` write what you typed and what the model replied** —
664
+ check before sharing one.
665
+
666
+ API keys are removed at every level, by two independent passes: any field whose
667
+ name looks like a credential is blanked whatever it holds, and every string is
668
+ scanned for known key formats *and* for the literal keys this process is
669
+ holding. The second half is what covers a custom endpoint whose key looks like
670
+ nothing in particular. Setting `log.redactSecrets false` disables both, and says
671
+ so on stdout when you do.
672
+
673
+ ### Commands
674
+
675
+ ```bash
676
+ aragon logs path # where records are going right now, and at what level
677
+ aragon logs list # files, sizes, timestamps
678
+ aragon logs tail -n 50 # render the last 50 records
679
+ aragon logs tail --follow # keep printing new ones
680
+ aragon logs tail --json # raw JSON Lines, for jq
681
+ aragon logs tail --level warn # only warn and above
682
+ aragon logs clear --yes # delete every log file
683
+ aragon logs open # open the directory in your file manager
684
+
685
+ aragon config home # the user-state root (and whether ARAGON_HOME set it)
686
+ aragon config get log.level # one value; secrets always masked
687
+ aragon config list --json # everything; secrets always masked here too
688
+ aragon config edit # open config.json in $VISUAL / $EDITOR
689
+ ```
690
+
691
+ Per-run overrides, none of which are persisted:
692
+
693
+ ```bash
694
+ aragon --log-level debug "…" # or --verbose, which means the same thing
695
+ aragon --no-log-file "…" # nothing reaches a file this run
696
+ aragon --log-dir D:\logs "…"
697
+ ```
698
+
699
+ `ARAGON_LOG_LEVEL`, `ARAGON_LOG_FILE=0` and `ARAGON_LOG_DIR` do the same from the
700
+ environment, and unlike `ARAGON_HOME` these three *can* come from a `.env`.
701
+
702
+ ### Insecure TLS opt-in
703
+
704
+ When `NODE_TLS_REJECT_UNAUTHORIZED` is the exact string `0`, the installed or
705
+ locally built `aragon` process honors that setting and suppresses only Node's
706
+ standard insecure-TLS warning. Every unrelated process warning remains visible,
707
+ and any other value leaves warning behavior unchanged. The check happens when a
708
+ warning is emitted, so a `0` loaded from the project `.env` is covered too; the
709
+ CLI never creates, normalizes, or persists this variable.
710
+
711
+ > **Security warning:** `0` genuinely disables certificate verification. It does
712
+ > not make an insecure connection safe and can expose credentials or request
713
+ > contents to interception. Use it only in a controlled environment where you
714
+ > accept that risk.
715
+
716
+ The automatic filter starts with the `aragon` process. An earlier `npm install`
717
+ or `npx` bootstrap belongs to npm and is outside the installed package's process
718
+ boundary, so that parent may still print Node's warning. The official repository
719
+ release flow covers its npm children through `publish-latest.ps1`; callers that
720
+ own another npm/npx parent must preload the filter in that parent themselves if
721
+ quiet output is required.
722
+
516
723
  ### Theming & color
517
724
 
518
725
  `--theme` / `config.theme` / `/theme` pick a palette: `warm`, `cool`, `light`, or
@@ -552,6 +759,23 @@ node packages/cli/dist/cli.js --version
552
759
  - `echo "list files" | node dist/cli.js -p` prints an answer and exits `0`.
553
760
  - A missing/invalid key produces a visible, actionable message (never a blank
554
761
  screen), both in the TUI and headless.
762
+ - `Shift+Tab` at 40, 80 and 120 columns - the chip, the hint and the status bar
763
+ are all legible and nothing wraps onto a second row.
764
+ - The question wizard at 24 rows with 5 questions x 4 options - no clipping, and
765
+ the `n/N` counter matches what is on screen.
766
+ - Approve a plan and watch the SAME run start writing files, with no second
767
+ prompt.
768
+ - `--no-color` on a legacy `cmd.exe` - the chip renders `[PLAN]`, radios render
769
+ `*`/`o`, checkboxes render `[x]`/`[ ]`, no mojibake anywhere.
770
+ - A plan with a 600-character summary and a 400-character step detail - the text
771
+ wraps and scrolls, the footer count matches the real number of rows, and
772
+ nothing is truncated to a single line.
773
+ - `--no-hints` on a 20-row terminal in plan mode - the status bar still says
774
+ `PLAN` with no chip on screen.
775
+ - Dismiss a plan card with `Esc`, let the agent resubmit, then press `Esc` twice:
776
+ the run aborts and the session returns to an idle prompt. Do this one BY HAND
777
+ as well as in a test - the point of the check is whether the exit is findable,
778
+ which no assertion can tell you.
555
779
 
556
780
  ## License
557
781
 
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Agent mode vocabulary (plan-mode §5.1).
3
+ *
4
+ * Pure: no React, no I/O, no `node:*`. Imported by the controller, the tool
5
+ * gate, the prompt builder, the slash command and four UI components, which is
6
+ * exactly why it must stay free of every one of their dependencies.
7
+ *
8
+ * `PLAN_MODE_BLOCKED_TOOLS` is deliberately NOT here. It lives in
9
+ * `tools/index.ts` beside `SKILL_TOOL_FLOOR`, because the invariant worth having
10
+ * is that the two sets PARTITION `HOST_TOOL_NAMES` — and a partition test is
11
+ * only as trustworthy as the distance between the two definitions. This file
12
+ * owns the mode vocabulary; that file owns which tools the modes act on.
13
+ *
14
+ * ASCII ONLY in every string literal below. `agent/` is inside the glyph
15
+ * scanner's scope (`glyphs.test.ts`), so a stray ellipsis here fails the build —
16
+ * which is why `OTHER_OPTION_LABEL` is a bare word and the ellipsis is appended
17
+ * at render time from `glyphs.ellipsis`.
18
+ */
19
+ export type AgentMode = 'build' | 'plan';
20
+ export declare const AGENT_MODES: readonly AgentMode[];
21
+ /** The user-visible word for each mode. English, per the repo convention. */
22
+ export declare const MODE_LABEL: Record<AgentMode, string>;
23
+ /**
24
+ * The next mode in the cycle.
25
+ *
26
+ * Written as a cycle rather than a boolean flip so that adding Claude Code's
27
+ * "auto-accept edits" as a third posture is a one-line change to `AGENT_MODES`
28
+ * and nothing else. Two ship today (§1 non-goals).
29
+ */
30
+ export declare function nextMode(current: AgentMode): AgentMode;
31
+ /** The two tools that only exist because a human is attached. */
32
+ export declare const PLAN_TOOL_NAMES: readonly ["ask_user", "submit_plan"];
33
+ /**
34
+ * The synthetic free-text option appended to every question.
35
+ *
36
+ * Plain `Other`, with no ellipsis: this module is inside the glyph scanner's
37
+ * scope, and the overlay appends `glyphs.ellipsis` itself so the label degrades
38
+ * to `Other...` on a terminal without Unicode.
39
+ */
40
+ export declare const OTHER_OPTION_LABEL = "Other";
41
+ /**
42
+ * Why a mutating tool was refused in plan mode.
43
+ *
44
+ * The wording carries weight beyond politeness (§3.9). A run that flips
45
+ * `build -> plan` mid-flight keeps its original system prompt to the end — the
46
+ * loop reads `ctx.systemPrompt`, which was copied by value when the run
47
+ * started — so for the rest of that run this refusal is the ONLY place the
48
+ * model learns that plan mode is on. A bare "not permitted" reads as a tool
49
+ * malfunction and gets retried.
50
+ */
51
+ export declare function planRefusal(tool: string): string;
52
+ /** Why `submit_plan` was refused outside plan mode. */
53
+ export declare function submitPlanRefusal(): string;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Agent mode vocabulary (plan-mode §5.1).
3
+ *
4
+ * Pure: no React, no I/O, no `node:*`. Imported by the controller, the tool
5
+ * gate, the prompt builder, the slash command and four UI components, which is
6
+ * exactly why it must stay free of every one of their dependencies.
7
+ *
8
+ * `PLAN_MODE_BLOCKED_TOOLS` is deliberately NOT here. It lives in
9
+ * `tools/index.ts` beside `SKILL_TOOL_FLOOR`, because the invariant worth having
10
+ * is that the two sets PARTITION `HOST_TOOL_NAMES` — and a partition test is
11
+ * only as trustworthy as the distance between the two definitions. This file
12
+ * owns the mode vocabulary; that file owns which tools the modes act on.
13
+ *
14
+ * ASCII ONLY in every string literal below. `agent/` is inside the glyph
15
+ * scanner's scope (`glyphs.test.ts`), so a stray ellipsis here fails the build —
16
+ * which is why `OTHER_OPTION_LABEL` is a bare word and the ellipsis is appended
17
+ * at render time from `glyphs.ellipsis`.
18
+ */
19
+ export const AGENT_MODES = ['build', 'plan'];
20
+ /** The user-visible word for each mode. English, per the repo convention. */
21
+ export const MODE_LABEL = { build: 'BUILD', plan: 'PLAN' };
22
+ /**
23
+ * The next mode in the cycle.
24
+ *
25
+ * Written as a cycle rather than a boolean flip so that adding Claude Code's
26
+ * "auto-accept edits" as a third posture is a one-line change to `AGENT_MODES`
27
+ * and nothing else. Two ship today (§1 non-goals).
28
+ */
29
+ export function nextMode(current) {
30
+ const index = AGENT_MODES.indexOf(current);
31
+ return AGENT_MODES[(index + 1) % AGENT_MODES.length];
32
+ }
33
+ /** The two tools that only exist because a human is attached. */
34
+ export const PLAN_TOOL_NAMES = ['ask_user', 'submit_plan'];
35
+ /**
36
+ * The synthetic free-text option appended to every question.
37
+ *
38
+ * Plain `Other`, with no ellipsis: this module is inside the glyph scanner's
39
+ * scope, and the overlay appends `glyphs.ellipsis` itself so the label degrades
40
+ * to `Other...` on a terminal without Unicode.
41
+ */
42
+ export const OTHER_OPTION_LABEL = 'Other';
43
+ /** Read-only alternatives named in every refusal, so the model has somewhere to go. */
44
+ const READ_ONLY_ALTERNATIVES = 'read_file / list_dir / glob / grep';
45
+ /**
46
+ * Why a mutating tool was refused in plan mode.
47
+ *
48
+ * The wording carries weight beyond politeness (§3.9). A run that flips
49
+ * `build -> plan` mid-flight keeps its original system prompt to the end — the
50
+ * loop reads `ctx.systemPrompt`, which was copied by value when the run
51
+ * started — so for the rest of that run this refusal is the ONLY place the
52
+ * model learns that plan mode is on. A bare "not permitted" reads as a tool
53
+ * malfunction and gets retried.
54
+ */
55
+ export function planRefusal(tool) {
56
+ const extra = tool === 'bash'
57
+ ? ' bash is refused in full, including read-only commands like "git status":' +
58
+ ' a shell string cannot be classified reliably, and a gate that is right' +
59
+ ' most of the time is worse than one that is always right.'
60
+ : '';
61
+ return (`Plan mode is active, so ${tool} is refused.${extra}` +
62
+ ` Use ${READ_ONLY_ALTERNATIVES} to research, then call submit_plan.` +
63
+ ' Do not try to work around this; finish the plan instead.');
64
+ }
65
+ /** Why `submit_plan` was refused outside plan mode. */
66
+ export function submitPlanRefusal() {
67
+ return ('submit_plan is only available in Plan mode. You are in Build mode - ' +
68
+ 'just do the work and summarize when you are done.');
69
+ }
@@ -11,7 +11,9 @@
11
11
  import { ModelRegistry, type AgentEvent, type AgentTool, type Message, type ModelInfo, type ProviderRegistry, type ThinkingLevel } from '@aragon-agent/core';
12
12
  import type { CliConfig, ThemeName } from '../config/schema.js';
13
13
  import { type ConfirmRequest } from '../tools/index.js';
14
+ import { type HumanInputGate } from '../tools/human-input.js';
14
15
  import { SkillService, type ApprovalGate } from '../skills/service.js';
16
+ import type { AgentMode } from './agent-mode.js';
15
17
  import type { NoticeLevel } from './reducer.js';
16
18
  /** Approval gate used whenever there is provably no human to ask (headless). */
17
19
  export declare const DENY_ALL_APPROVAL: ApprovalGate;
@@ -37,6 +39,24 @@ export interface ControllerDeps {
37
39
  onSkillsChanged?: () => void;
38
40
  /** Transcript notices from the skill subsystem. */
39
41
  notify?: (level: NoticeLevel, text: string) => void;
42
+ /**
43
+ * The plan-mode human channel (§3.5). Defaults to `DENY_ALL_HUMAN_INPUT`, so
44
+ * forgetting to pass one registers NO plan tools rather than registering tools
45
+ * that would hang. A THIRD approval channel on purpose: `confirm` auto-approves
46
+ * when unattached and `approval` denies, but neither can render a question
47
+ * wizard or a plan card.
48
+ */
49
+ humanInput?: HumanInputGate;
50
+ }
51
+ /** What `setAgentMode` actually adopted — see `AgentController.setAgentMode`. */
52
+ export interface AgentModeState {
53
+ effective: AgentMode;
54
+ /** Non-null only while a `plan -> build` switch is waiting for `agent_end`. */
55
+ pending: AgentMode | null;
56
+ }
57
+ export interface PlanStatus extends AgentModeState {
58
+ askRoundsUsed: number;
59
+ maxAskRounds: number;
40
60
  }
41
61
  export interface PreflightResult {
42
62
  ok: boolean;
@@ -55,6 +75,11 @@ export declare class AgentController {
55
75
  private onSkillsChanged?;
56
76
  /** Mutable session cwd — tools resolve relative paths against this. */
57
77
  private cwd;
78
+ private effectiveMode;
79
+ private pendingMode;
80
+ private askRounds;
81
+ /** Static: whether this session could ever render a question overlay. */
82
+ private readonly planInteractive;
58
83
  constructor(config: CliConfig, deps?: ControllerDeps);
59
84
  private resolveKey;
60
85
  /**
@@ -110,6 +135,50 @@ export declare class AgentController {
110
135
  steer(text: string): void;
111
136
  followUp(text: string): void;
112
137
  isRunning(): boolean;
138
+ getAgentMode(): AgentMode;
139
+ /**
140
+ * Request a mode change and return what was ACTUALLY ADOPTED.
141
+ *
142
+ * Returning the adopted state rather than `void` is the whole point of this
143
+ * signature. The caller dispatches THIS value into the view, never the value
144
+ * it asked for — dispatching the request is the one way to make the badge lie
145
+ * about what the gate will do, and the API shape forecloses it (R-P7).
146
+ *
147
+ * The two directions are deliberately asymmetric:
148
+ *
149
+ * - `build -> plan` applies IMMEDIATELY. Tightening a permission mid-run is
150
+ * always safe; the user pressed the key *because* the agent is about to do
151
+ * something they want to stop.
152
+ * - `plan -> build` is DEFERRED to `agent_end`. Applying it live would mean a
153
+ * run the user launched under a read-only guarantee could start writing
154
+ * files because of one stray keypress.
155
+ * - `{ force: true }` is the single exception, used only by `submit_plan`'s
156
+ * approval path: the user has just read the plan and authorized exactly
157
+ * that work. That is an informed act; a stray Shift+Tab is not. If this
158
+ * code is ever refactored, that distinction is the thing to preserve.
159
+ */
160
+ setAgentMode(next: AgentMode, opts?: {
161
+ force?: boolean;
162
+ }): AgentModeState;
163
+ /**
164
+ * Adopt a deferred `plan -> build` switch. Call from the App's `agent_end`
165
+ * handling. Returns `null` when nothing was pending, so the caller can skip
166
+ * both the dispatch and the toast.
167
+ */
168
+ applyPendingMode(): AgentModeState | null;
169
+ /** Named read surface for `/plan status`, so no command reaches into internals. */
170
+ getPlanStatus(): PlanStatus;
171
+ /** Spend one `ask_user` round. Called from the tool, never from the UI. */
172
+ private takeAskRound;
173
+ /**
174
+ * Run `fn` with the idle watchdog paused.
175
+ *
176
+ * `try/finally` rather than two sequential calls: a throw inside the overlay
177
+ * path must not leave the watchdog disarmed for the rest of the run. Also used
178
+ * by the `--confirm` gate, which had exactly this bug — a user who took four
179
+ * minutes over `Proceed? (y/N)` had their run killed by the watchdog.
180
+ */
181
+ withHumanWait<T>(fn: () => Promise<T>): Promise<T>;
113
182
  setModel(provider: string, model: string, baseUrl?: string): void;
114
183
  setThinkingLevel(level: ThinkingLevel): void;
115
184
  setMaxTokens(value: number | undefined): void;