@aragon-agent/cli 0.5.8 → 0.5.10
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.
- package/CHANGELOG.md +1157 -747
- package/README.md +1552 -1163
- package/dist/agent/coalesce.d.ts +13 -0
- package/dist/agent/coalesce.js +50 -11
- package/dist/agent/controller.d.ts +141 -2
- package/dist/agent/controller.js +396 -4
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +40 -0
- package/dist/agent/reducer.d.ts +245 -3
- package/dist/agent/reducer.js +490 -17
- package/dist/agent/retry-view.d.ts +68 -0
- package/dist/agent/retry-view.js +95 -0
- package/dist/agent/system-prompt.d.ts +11 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/boot/guard.d.ts +135 -0
- package/dist/boot/guard.js +218 -0
- package/dist/boot/rollback.d.ts +86 -0
- package/dist/boot/rollback.js +185 -0
- package/dist/cli.js +368 -3
- package/dist/commands/builtins.d.ts +1 -1
- package/dist/commands/builtins.js +426 -2
- package/dist/commands/registry.d.ts +17 -0
- package/dist/config/app-paths.d.ts +31 -0
- package/dist/config/app-paths.js +37 -0
- package/dist/config/cli-commands.d.ts +79 -2
- package/dist/config/cli-commands.js +185 -3
- package/dist/config/env.d.ts +15 -3
- package/dist/config/env.js +143 -3
- package/dist/config/load.d.ts +66 -0
- package/dist/config/load.js +107 -1
- package/dist/config/schema.d.ts +268 -2
- package/dist/config/schema.js +288 -1
- package/dist/config/store.js +32 -1
- package/dist/fast/critique.d.ts +43 -0
- package/dist/fast/critique.js +62 -0
- package/dist/fast/digest.d.ts +42 -0
- package/dist/fast/digest.js +85 -0
- package/dist/fast/frames.d.ts +99 -0
- package/dist/fast/frames.js +180 -0
- package/dist/fast/limits.d.ts +98 -0
- package/dist/fast/limits.js +98 -0
- package/dist/fast/prompt.d.ts +72 -0
- package/dist/fast/prompt.js +94 -0
- package/dist/fast/resolve.d.ts +43 -0
- package/dist/fast/resolve.js +105 -0
- package/dist/fast/review-call.d.ts +99 -0
- package/dist/fast/review-call.js +130 -0
- package/dist/fast/reviewer.d.ts +248 -0
- package/dist/fast/reviewer.js +664 -0
- package/dist/fast/types.d.ts +128 -0
- package/dist/fast/types.js +14 -0
- package/dist/fast/wiring.d.ts +170 -0
- package/dist/fast/wiring.js +316 -0
- package/dist/launcher.d.ts +14 -0
- package/dist/launcher.js +43 -0
- package/dist/logging/install.d.ts +15 -1
- package/dist/logging/install.js +37 -4
- package/dist/logging/logger.d.ts +1 -1
- package/dist/session/persist.js +57 -1
- package/dist/team/normalize.d.ts +26 -1
- package/dist/team/normalize.js +23 -3
- package/dist/team/prompt.d.ts +11 -0
- package/dist/team/prompt.js +10 -0
- package/dist/team/report.d.ts +18 -0
- package/dist/team/report.js +27 -1
- package/dist/team/runtime.d.ts +13 -2
- package/dist/team/runtime.js +33 -6
- package/dist/team/subagent.d.ts +19 -1
- package/dist/team/subagent.js +72 -10
- package/dist/team/task-tool.d.ts +26 -0
- package/dist/team/task-tool.js +48 -4
- package/dist/team/types.d.ts +80 -1
- package/dist/tools/bash-tool.js +19 -3
- package/dist/tools/diff.d.ts +25 -5
- package/dist/tools/diff.js +72 -41
- package/dist/tools/file-change-store.d.ts +34 -0
- package/dist/tools/file-change-store.js +57 -0
- package/dist/tools/fs-tools.d.ts +26 -0
- package/dist/tools/fs-tools.js +62 -5
- package/dist/tools/index.d.ts +31 -0
- package/dist/tools/index.js +7 -1
- package/dist/tools/patch.d.ts +115 -0
- package/dist/tools/patch.js +453 -0
- package/dist/tools/terminal-output.d.ts +107 -0
- package/dist/tools/terminal-output.js +198 -0
- package/dist/tools/tool-output-store.d.ts +68 -0
- package/dist/tools/tool-output-store.js +102 -0
- package/dist/ui/ActivityLine.d.ts +48 -0
- package/dist/ui/ActivityLine.js +22 -0
- package/dist/ui/App.d.ts +11 -4
- package/dist/ui/App.js +359 -37
- package/dist/ui/BottomStatusRow.d.ts +65 -0
- package/dist/ui/BottomStatusRow.js +17 -0
- package/dist/ui/Composer.d.ts +1 -6
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/PromptInput.d.ts +1 -15
- package/dist/ui/PromptInput.js +1 -38
- package/dist/ui/StatusBar.d.ts +37 -0
- package/dist/ui/StatusBar.js +5 -3
- package/dist/ui/TeamPanel.d.ts +5 -4
- package/dist/ui/TeamPanel.js +1 -1
- package/dist/ui/TodoPanel.d.ts +5 -4
- package/dist/ui/Transcript.d.ts +26 -3
- package/dist/ui/Transcript.js +76 -10
- package/dist/ui/UpdateLine.d.ts +44 -0
- package/dist/ui/UpdateLine.js +129 -0
- package/dist/ui/activity-phrases.d.ts +33 -0
- package/dist/ui/activity-phrases.js +50 -0
- package/dist/ui/entries/AssistantEntry.d.ts +20 -0
- package/dist/ui/entries/AssistantEntry.js +3 -2
- package/dist/ui/entries/DiffView.d.ts +67 -0
- package/dist/ui/entries/DiffView.js +103 -0
- package/dist/ui/entries/FastCard.d.ts +53 -0
- package/dist/ui/entries/FastCard.js +87 -0
- package/dist/ui/entries/RetryCard.d.ts +56 -0
- package/dist/ui/entries/RetryCard.js +60 -0
- package/dist/ui/entries/TeamCard.js +6 -1
- package/dist/ui/entries/ToolCard.d.ts +38 -1
- package/dist/ui/entries/ToolCard.js +68 -5
- package/dist/ui/glyphs.d.ts +9 -0
- package/dist/ui/glyphs.js +2 -0
- package/dist/ui/layout/AppShell.d.ts +13 -22
- package/dist/ui/layout/AppShell.js +3 -38
- package/dist/ui/layout/virtual-window.d.ts +1 -1
- package/dist/ui/layout/virtual-window.js +109 -5
- package/dist/ui/overlays/HelpOverlay.js +16 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +82 -1
- package/dist/ui/overlays/SettingsScreen.js +147 -1
- package/dist/ui/transcript-text.js +27 -0
- package/dist/ui/use-wheel-routing.d.ts +16 -32
- package/dist/ui/use-wheel-routing.js +21 -38
- package/dist/update/classify-failure.d.ts +32 -0
- package/dist/update/classify-failure.js +60 -0
- package/dist/update/install-lock.d.ts +55 -0
- package/dist/update/install-lock.js +184 -0
- package/dist/update/install-source.d.ts +97 -0
- package/dist/update/install-source.js +218 -0
- package/dist/update/installer.d.ts +108 -0
- package/dist/update/installer.js +242 -0
- package/dist/update/limits.d.ts +111 -0
- package/dist/update/limits.js +111 -0
- package/dist/update/npm-view.d.ts +62 -0
- package/dist/update/npm-view.js +133 -0
- package/dist/update/registry.d.ts +52 -0
- package/dist/update/registry.js +161 -0
- package/dist/update/semver.d.ts +89 -0
- package/dist/update/semver.js +295 -0
- package/dist/update/service.d.ts +193 -0
- package/dist/update/service.js +636 -0
- package/dist/update/state.d.ts +103 -0
- package/dist/update/state.js +142 -0
- package/dist/update/types.d.ts +169 -0
- package/dist/update/types.js +68 -0
- package/package.json +3 -3
- package/dist/ui/layout/regions.d.ts +0 -55
- package/dist/ui/layout/regions.js +0 -52
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rollback (cli-auto-update-hardening H1 / section 5.1.3).
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY - this tree is inside the glyph scanner's scope (C-19).
|
|
5
|
+
*
|
|
6
|
+
* EVERY IMPORT THAT DOES REAL WORK IS DYNAMIC, AND FOR A REASON THAT IS NOT THE
|
|
7
|
+
* USUAL ONE. This function runs inside a build we have CONCRETE EVIDENCE is
|
|
8
|
+
* broken - it was reached because that build failed to start twice - so every
|
|
9
|
+
* one of `install-lock`, `installer` and `install-source` can reject on import.
|
|
10
|
+
* The whole body is inside one `try`, and the `catch` records one line and
|
|
11
|
+
* returns. Static `import type` specifiers below are erased by tsc and are not
|
|
12
|
+
* runtime edges, which is what keeps `guard.ts`'s own graph inside the list its
|
|
13
|
+
* header pins down.
|
|
14
|
+
*
|
|
15
|
+
* `../update/state.js` IS THE ONE EXCEPTION and is imported statically: the
|
|
16
|
+
* guard has already loaded and read it before it can possibly call us, so a
|
|
17
|
+
* static edge here adds no failure mode that has not already been survived.
|
|
18
|
+
*/
|
|
19
|
+
import process from 'node:process';
|
|
20
|
+
import { readUpdateState, updateUpdateState } from '../update/state.js';
|
|
21
|
+
/** Best-effort, and deliberately so: a logger that fails must not stop a
|
|
22
|
+
* rollback that is the user's only way back to a working CLI. */
|
|
23
|
+
async function record(deps, level, msg, data) {
|
|
24
|
+
try {
|
|
25
|
+
if (deps.log) {
|
|
26
|
+
deps.log(level, msg, data);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const { getLogger } = await import('../logging/logger.js');
|
|
30
|
+
getLogger().child('update')[level](msg, data);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// No logger, or a broken one. The stderr line the guard already wrote is
|
|
34
|
+
// the part the user needs.
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function defaultResolveTarget() {
|
|
38
|
+
const { classifyInstallSource, isAutoInstallable, probeWritable, readSelfManifest, selfPackageRoot } = await import('../update/install-source.js');
|
|
39
|
+
const root = selfPackageRoot();
|
|
40
|
+
if (!root)
|
|
41
|
+
return { root: null, packageName: '', autoInstallable: false, writable: false };
|
|
42
|
+
return {
|
|
43
|
+
root,
|
|
44
|
+
packageName: readSelfManifest(root)?.name ?? '',
|
|
45
|
+
autoInstallable: isAutoInstallable(classifyInstallSource(root)),
|
|
46
|
+
writable: probeWritable(root),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Reinstall `good` and latch `bad` so nothing puts it back.
|
|
51
|
+
*
|
|
52
|
+
* THE ORDER OF OPERATIONS IS LOAD-BEARING.
|
|
53
|
+
*
|
|
54
|
+
* 1. LATCH FIRST, INSTALL SECOND. If the process dies between the latch and the
|
|
55
|
+
* spawn, the machine is left running a broken version that WILL NOT BE
|
|
56
|
+
* REINSTALLED - recoverable by hand. In the other order, a death between the
|
|
57
|
+
* spawn and the latch leaves a good version installed with no latch, and the
|
|
58
|
+
* next check reinstalls the bad one.
|
|
59
|
+
*
|
|
60
|
+
* `skippedVersion` is part of that write and is the reason it works across
|
|
61
|
+
* versions (C-17 / D-28): `readUpdateState` rebuilds from a fixed field list,
|
|
62
|
+
* so an OLDER `aragon` writing this file erases every field it does not know.
|
|
63
|
+
* `skippedVersion` is schema-1 and every shipped version honours it through
|
|
64
|
+
* `decideUpdate`. Per section 1.2 the ordinary rollback target is itself an H1
|
|
65
|
+
* build and preserves all four new fields; this covers the cases that
|
|
66
|
+
* argument does not reach - a hand-edited state file, a user who manually
|
|
67
|
+
* installs a pre-H1 version, and any future field that matters across the
|
|
68
|
+
* boundary.
|
|
69
|
+
*
|
|
70
|
+
* `rolledBackFrom` IS WRITTEN HERE AND NOWHERE ELSE (P0-1d). It is consumed
|
|
71
|
+
* exactly once, by `UpdateService.start()`, which is the only thing that can
|
|
72
|
+
* decide the notice has actually been shown.
|
|
73
|
+
*
|
|
74
|
+
* 2. RE-CLASSIFY BEFORE SPAWNING. The machine may have changed since the
|
|
75
|
+
* install; we do not run `npm i -g` on a tree we would not have installed
|
|
76
|
+
* into.
|
|
77
|
+
*
|
|
78
|
+
* 3. ONE NON-BLOCKING LOCK ATTEMPT (U-5b). If another process holds it, return:
|
|
79
|
+
* it is either installing or already rolling back, and two concurrent
|
|
80
|
+
* `npm i -g` on one prefix is the one thing the lock exists to stop.
|
|
81
|
+
*
|
|
82
|
+
* 4. `runNpmInstall` VERBATIM. It already validates the version against
|
|
83
|
+
* `STRICT_VERSION_RE`, spawns detached through `process.execPath`, writes to
|
|
84
|
+
* a real fd and verifies the classified root afterwards (U-6). A rollback is
|
|
85
|
+
* an install; it does not deserve a second code path.
|
|
86
|
+
*
|
|
87
|
+
* The stderr line belongs to `runBootGuard` and is written before this is called
|
|
88
|
+
* (P2-9). NEVER THROWS: the guard invokes it without `await` and cannot handle a
|
|
89
|
+
* rejection.
|
|
90
|
+
*/
|
|
91
|
+
export async function performRollback(bad, good, deps = {}) {
|
|
92
|
+
const write = deps.writeState ?? updateUpdateState;
|
|
93
|
+
const read = deps.readState ?? readUpdateState;
|
|
94
|
+
try {
|
|
95
|
+
// Read BEFORE the latch: the write below zeroes `bootFailures`, and the
|
|
96
|
+
// count is the one number worth keeping in the log record.
|
|
97
|
+
const failures = read().bootFailures;
|
|
98
|
+
// Step 1 - the latch, in ONE write.
|
|
99
|
+
write({
|
|
100
|
+
skippedVersion: bad,
|
|
101
|
+
pendingRestartVersion: '',
|
|
102
|
+
bootFailures: 0,
|
|
103
|
+
autoInstalledVersion: '',
|
|
104
|
+
rolledBackFrom: bad,
|
|
105
|
+
});
|
|
106
|
+
// Step 2 - re-classify.
|
|
107
|
+
const target = await (deps.resolveTarget ?? defaultResolveTarget)();
|
|
108
|
+
if (!target.root || target.packageName.length === 0) {
|
|
109
|
+
await record(deps, 'warn', 'update_rollback_failed', {
|
|
110
|
+
from: bad,
|
|
111
|
+
to: good,
|
|
112
|
+
reason: 'no-package-root',
|
|
113
|
+
});
|
|
114
|
+
return { ok: false, failure: 'no-package-root' };
|
|
115
|
+
}
|
|
116
|
+
if (!target.autoInstallable) {
|
|
117
|
+
await record(deps, 'warn', 'update_rollback_failed', {
|
|
118
|
+
from: bad,
|
|
119
|
+
to: good,
|
|
120
|
+
reason: 'source-ineligible',
|
|
121
|
+
});
|
|
122
|
+
return { ok: false, failure: 'source-ineligible' };
|
|
123
|
+
}
|
|
124
|
+
if (!target.writable) {
|
|
125
|
+
await record(deps, 'warn', 'update_rollback_failed', {
|
|
126
|
+
from: bad,
|
|
127
|
+
to: good,
|
|
128
|
+
reason: 'not-writable',
|
|
129
|
+
});
|
|
130
|
+
return { ok: false, failure: 'not-writable' };
|
|
131
|
+
}
|
|
132
|
+
// Step 3 - the ordinary install lock.
|
|
133
|
+
const acquire = deps.acquireLock ??
|
|
134
|
+
(await import('../update/install-lock.js')).tryAcquireInstallLock;
|
|
135
|
+
const lock = acquire();
|
|
136
|
+
if (!lock) {
|
|
137
|
+
await record(deps, 'warn', 'update_rollback_failed', {
|
|
138
|
+
from: bad,
|
|
139
|
+
to: good,
|
|
140
|
+
reason: 'locked',
|
|
141
|
+
});
|
|
142
|
+
return { ok: false, failure: 'locked' };
|
|
143
|
+
}
|
|
144
|
+
// Step 4 - the install, reused.
|
|
145
|
+
const install = deps.install ?? (await import('../update/installer.js')).runNpmInstall;
|
|
146
|
+
let result;
|
|
147
|
+
try {
|
|
148
|
+
result = await install({
|
|
149
|
+
packageName: target.packageName,
|
|
150
|
+
version: good,
|
|
151
|
+
root: target.root,
|
|
152
|
+
// Phase two of U-4, exactly as `performInstall` does it: the lock had to
|
|
153
|
+
// precede the spawn, and it is rewritten to the child's pid the instant
|
|
154
|
+
// the child exists. `adoptChild` closes over its own state and may be
|
|
155
|
+
// passed unbound.
|
|
156
|
+
onSpawn: lock.adoptChild,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
lock.release();
|
|
161
|
+
}
|
|
162
|
+
if (!result.ok) {
|
|
163
|
+
await record(deps, 'warn', 'update_rollback_failed', {
|
|
164
|
+
from: bad,
|
|
165
|
+
to: good,
|
|
166
|
+
reason: result.failure ?? 'install-failed',
|
|
167
|
+
});
|
|
168
|
+
return { ok: false, failure: 'install-failed' };
|
|
169
|
+
}
|
|
170
|
+
await record(deps, 'warn', 'update_rolled_back', { from: bad, to: good, failures });
|
|
171
|
+
return { ok: true };
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
// The one place this module writes to the terminal itself, and only because
|
|
175
|
+
// the alternative is a silent no-op on the path the user is already stuck on.
|
|
176
|
+
try {
|
|
177
|
+
process.stderr.write(`aragon: rollback to ${good} could not run (${err instanceof Error ? err.message : String(err)}).\n`);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// stderr is gone too. Nothing further is available to us.
|
|
181
|
+
}
|
|
182
|
+
await record(deps, 'warn', 'update_rollback_failed', { from: bad, to: good, reason: 'error' });
|
|
183
|
+
return { ok: false, failure: 'error' };
|
|
184
|
+
}
|
|
185
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -23,7 +23,7 @@ import { formatMigrationNotice, migrateLegacyState } from './config/migrate-lega
|
|
|
23
23
|
import { formatHomeMigrationNotice, migrateToHome } from './config/migrate-home.js';
|
|
24
24
|
import { migrateStateOutOfConfig } from './config/migrate-state-out-of-config.js';
|
|
25
25
|
import { runHistoryCommand } from './config/history-commands.js';
|
|
26
|
-
import { LOG_CONFIG_SET_KEYS, TEAM_CONFIG_SET_KEYS, applyLogConfigSet, applyTeamConfigSet, runConfigEdit, runConfigGet, runConfigHome, runConfigList, warnIfRedactionDisabled, } from './config/cli-commands.js';
|
|
26
|
+
import { FAST_CONFIG_SET_KEYS, LOG_CONFIG_SET_KEYS, RETRY_CONFIG_SET_KEYS, TEAM_CONFIG_SET_KEYS, UPDATE_CONFIG_SET_KEYS, applyFastConfigSet, applyLogConfigSet, applyRetryConfigSet, applyTeamConfigSet, applyUpdateConfigSet, runConfigEdit, runConfigGet, runConfigHome, runConfigList, warnIfRedactionDisabled, } from './config/cli-commands.js';
|
|
27
27
|
import { attachAgentEvents, attachTeamEvents, installLogging, setScreenRestore, setSignalTerminator, } from './logging/install.js';
|
|
28
28
|
import { getLogger } from './logging/logger.js';
|
|
29
29
|
import { runLogsCommand } from './logging/cli-commands.js';
|
|
@@ -41,6 +41,15 @@ import { renderTranscriptText } from './ui/transcript-text.js';
|
|
|
41
41
|
import { detectCapabilities } from './ui/capabilities.js';
|
|
42
42
|
import { pickGlyphs } from './ui/glyphs.js';
|
|
43
43
|
import { DENY_ALL_HUMAN_INPUT } from './tools/human-input.js';
|
|
44
|
+
// A VALUE import, and the one deliberate exception to the paragraph above
|
|
45
|
+
// (cli-auto-update-hardening P2-7). `boot/guard.js` statically pulls
|
|
46
|
+
// `update/state.js`, so `aragon -p` now EVALUATES that module - it is pure at
|
|
47
|
+
// module scope, calls nothing, and `update-wiring.test.ts` walks the transitive
|
|
48
|
+
// graph against an allow-list so it cannot quietly grow. The alternative, a
|
|
49
|
+
// dynamic import for a two-line disarm on the hottest path in the file, buys
|
|
50
|
+
// nothing: the guard has already loaded and read the same module milliseconds
|
|
51
|
+
// earlier, in the launcher.
|
|
52
|
+
import { markBootHealthy } from './boot/guard.js';
|
|
44
53
|
// ---------------------------------------------------------------------------
|
|
45
54
|
// Version (read from the shipped package.json next to dist/)
|
|
46
55
|
// ---------------------------------------------------------------------------
|
|
@@ -73,6 +82,14 @@ function toFlags(opts) {
|
|
|
73
82
|
exitTranscript: opts.exitTranscript,
|
|
74
83
|
compact: opts.compact,
|
|
75
84
|
hints: opts.hints,
|
|
85
|
+
// Without this line the flag never reaches `CliFlags` and `--show-thinking`
|
|
86
|
+
// is inert -- the failure the `mouse` note below records (P1-2).
|
|
87
|
+
showThinking: opts.showThinking,
|
|
88
|
+
// And again for the live tail, one feature later. The pair is declared in
|
|
89
|
+
// both directions below because `liveToolOutput` is PERSISTED and defaults
|
|
90
|
+
// to `true`: a lone `--no-live-tool-output` would make commander default
|
|
91
|
+
// `opts.liveToolOutput` to `true`, which is indistinguishable from silence.
|
|
92
|
+
liveToolOutput: opts.liveToolOutput,
|
|
76
93
|
// Without this line the flag never reaches `CliFlags` and `--no-mouse` is
|
|
77
94
|
// inert — a failure with no compile error and no runtime error (P1-2).
|
|
78
95
|
mouse: opts.mouse,
|
|
@@ -97,6 +114,20 @@ function toFlags(opts) {
|
|
|
97
114
|
// C-9). A value flag is no less inert than a boolean one when this line is
|
|
98
115
|
// missing.
|
|
99
116
|
todoFollow: opts.todoFollow,
|
|
117
|
+
// Site three of three for `--retry` / `--retry-max` — five features, same
|
|
118
|
+
// trap (`mouse` P1-2, `team` P1-4, `todo` C-9, render L4, now retry). A flag
|
|
119
|
+
// needs the `.option()` declaration below, a field on `CliFlags`, AND a line
|
|
120
|
+
// here; miss this one and the flag is inert with no compile or runtime error.
|
|
121
|
+
retry: opts.retry,
|
|
122
|
+
retryMax: opts.retryMax,
|
|
123
|
+
// Site three of three for the four `--fast*` flags — SIX features, one trap
|
|
124
|
+
// (`mouse` P1-2, `team` P1-4, `todo` C-9, render L4, retry, now fast). It
|
|
125
|
+
// bites hardest here: the tier is OFF by default, so a missing line would
|
|
126
|
+
// make `--fast` a flag that reports nothing and does nothing.
|
|
127
|
+
fast: opts.fast,
|
|
128
|
+
fastModel: opts.fastModel,
|
|
129
|
+
fastProvider: opts.fastProvider,
|
|
130
|
+
fastReview: opts.fastReview,
|
|
100
131
|
// `--verbose` is just a shorthand for `--log-level debug`; an explicit
|
|
101
132
|
// `--log-level` still wins, so the two can be combined without surprise.
|
|
102
133
|
logLevel: opts.logLevel ?? (opts.verbose ? 'debug' : undefined),
|
|
@@ -109,6 +140,15 @@ function toFlags(opts) {
|
|
|
109
140
|
transcriptRetain: opts.transcriptRetain,
|
|
110
141
|
renderGovernor: opts.renderGovernor,
|
|
111
142
|
maxRenderInterval: opts.maxRenderInterval,
|
|
143
|
+
// Site three of three for `--update` / `--no-update` — SEVEN features, one
|
|
144
|
+
// trap (`mouse` P1-2, `team` P1-4, `todo` C-9, render L4, retry, fast, now
|
|
145
|
+
// update). A flag needs the `.option()` declaration below, a field on
|
|
146
|
+
// `CliFlags`, AND a line here; miss this one and the flag is inert with no
|
|
147
|
+
// compile error and no runtime error. It bites hardest here of all: this is
|
|
148
|
+
// the KILL SWITCH for a feature that installs software, so a missing line
|
|
149
|
+
// means `--no-update` reports nothing, does nothing, and leaves the updater
|
|
150
|
+
// running.
|
|
151
|
+
update: opts.update,
|
|
112
152
|
};
|
|
113
153
|
}
|
|
114
154
|
// ---------------------------------------------------------------------------
|
|
@@ -280,7 +320,26 @@ function runInteractive(flags, extras = {}) {
|
|
|
280
320
|
process.exit(128 + signo);
|
|
281
321
|
});
|
|
282
322
|
}
|
|
283
|
-
|
|
323
|
+
// --- Auto-update (cli-auto-update section 3.8). --------------------------
|
|
324
|
+
//
|
|
325
|
+
// THE ONLY CONSTRUCTION SITE. `runOneShot` / `runHeadless` never reach this
|
|
326
|
+
// function, so `aragon -p` executes zero update code and its stdout is
|
|
327
|
+
// byte-identical to a pre-feature build (AC-1); `aragon config <subcommand>`,
|
|
328
|
+
// `aragon logs`, `aragon skills` and `aragon --version` likewise. Bare
|
|
329
|
+
// `aragon config` DOES construct it, because it is a full interactive session
|
|
330
|
+
// with a settings overlay on top - which is correct, and is why AC-1's claim
|
|
331
|
+
// names the SUBCOMMAND form.
|
|
332
|
+
//
|
|
333
|
+
// `CI` is excluded on top of the two TTY tests because a CI runner with a pty
|
|
334
|
+
// allocated is otherwise a machine that silently mutates its own global
|
|
335
|
+
// toolchain mid-pipeline (D-11).
|
|
336
|
+
const updateBridge = { service: null, onAttach: null };
|
|
337
|
+
let updateService = null;
|
|
338
|
+
const updateEligible = !!process.stdout.isTTY &&
|
|
339
|
+
!!process.stdin.isTTY &&
|
|
340
|
+
!process.env.CI &&
|
|
341
|
+
config.update.mode !== 'off';
|
|
342
|
+
const instance = render(_jsx(App, { controller: controller, version: VERSION, mode: mode, confirmBridge: confirmBridge, humanInputBridge: humanInputBridge, initialPrompt: extras.initialPrompt, initialOverlay: extras.initialOverlay, mouseSource: mouseFilter?.source, updateBridge: updateEligible ? updateBridge : undefined }),
|
|
284
343
|
// Full-screen owns console.* itself (I-4): Ink's patchConsole writes
|
|
285
344
|
// straight to stdout and permanently shifts the fixed frame's accounting.
|
|
286
345
|
{
|
|
@@ -290,6 +349,42 @@ function runInteractive(flags, extras = {}) {
|
|
|
290
349
|
// every existing path is provably unchanged.
|
|
291
350
|
stdin: mouseFilter?.stdin ?? process.stdin,
|
|
292
351
|
});
|
|
352
|
+
// H1's primary disarm, and this is the earliest moment at which "this build
|
|
353
|
+
// STARTS" is proven: the module graph loaded, config resolved, Ink mounted
|
|
354
|
+
// (cli-auto-update-hardening section 5.1.4). It is what makes a long session
|
|
355
|
+
// that is later `SIGKILL`ed - terminal window closed, machine slept badly, OOM
|
|
356
|
+
// killer - still count as healthy.
|
|
357
|
+
//
|
|
358
|
+
// UNGATED BY `updateEligible` ON PURPOSE. The guard is armed only by an
|
|
359
|
+
// auto-install, which `mode: 'off'` already prevents, so on a machine that
|
|
360
|
+
// never armed it this is one read and one string compare. Gating it would mean
|
|
361
|
+
// a user who switches `mode` to `off` between the install and the next launch
|
|
362
|
+
// leaves the guard armed with nothing able to disarm it.
|
|
363
|
+
markBootHealthy(VERSION);
|
|
364
|
+
if (updateEligible) {
|
|
365
|
+
// DYNAMIC, and fire-and-forget AFTER `render()` (D-25). Dynamic because a
|
|
366
|
+
// static import would make AC-1 false for `aragon -p`, which shares this
|
|
367
|
+
// module and has no bundler to shake the subsystem out; after `render()`
|
|
368
|
+
// because the service must never be able to delay or fail the first frame.
|
|
369
|
+
// The `.catch` is not decoration: a subsystem that cannot even be imported
|
|
370
|
+
// must degrade to "no updater", never to a crashed TUI.
|
|
371
|
+
void import('./update/service.js')
|
|
372
|
+
.then(({ UpdateService }) => {
|
|
373
|
+
const service = new UpdateService({ config: config.update, currentVersion: VERSION });
|
|
374
|
+
updateService = service;
|
|
375
|
+
updateBridge.service = service;
|
|
376
|
+
// `App` may have mounted first and parked its listener here; it may also
|
|
377
|
+
// not have mounted yet, in which case its own effect reads
|
|
378
|
+
// `bridge.service` directly. Both orders happen (IF-4).
|
|
379
|
+
updateBridge.onAttach?.(service);
|
|
380
|
+
service.start();
|
|
381
|
+
})
|
|
382
|
+
.catch((err) => {
|
|
383
|
+
logger.warn('update', 'update_service_unavailable', {
|
|
384
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
}
|
|
293
388
|
// The return value MUST be captured — `waitUntilExit()` is the normal-exit
|
|
294
389
|
// restore path and it cannot be registered otherwise. `dispose()` runs on
|
|
295
390
|
// BOTH branches (I-7): a live `'data'` listener on stdin keeps the event loop
|
|
@@ -311,12 +406,23 @@ function runInteractive(flags, extras = {}) {
|
|
|
311
406
|
// the event loop open so `aragon` never returns to the shell (R-15). The
|
|
312
407
|
// App's unmount cleanup also calls this; `dispose()` is idempotent.
|
|
313
408
|
controller.dispose();
|
|
409
|
+
// BOTH BRANCHES, exactly as `controller.dispose()` is (section 3.6 / P2-6),
|
|
410
|
+
// and idempotent for the same reason. It clears the timers and drops the
|
|
411
|
+
// listeners; it NEVER signals the installer child (U-3), because killing a
|
|
412
|
+
// running `npm install -g` is the one action here that can leave the
|
|
413
|
+
// user's global installation broken - and it would be triggered by the
|
|
414
|
+
// most ordinary event there is, the user pressing Ctrl+C. The signal path
|
|
415
|
+
// above calls `process.exit()` and reaches neither branch, which is
|
|
416
|
+
// correct and is precisely WHY U-3 matters: the detached child survives
|
|
417
|
+
// that exit and completes.
|
|
418
|
+
updateService?.dispose();
|
|
314
419
|
replayTranscript();
|
|
315
420
|
})
|
|
316
421
|
.catch(() => {
|
|
317
422
|
screen?.restore();
|
|
318
423
|
disposeMouse();
|
|
319
424
|
controller.dispose();
|
|
425
|
+
updateService?.dispose();
|
|
320
426
|
});
|
|
321
427
|
}
|
|
322
428
|
async function runOneShot(flags, prompt, quiet) {
|
|
@@ -414,6 +520,12 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
414
520
|
// flags and never persist -- inconsistent with every other config field.
|
|
415
521
|
'density',
|
|
416
522
|
'hints',
|
|
523
|
+
// agent-activity-presentation §4.1. Listed here AND cased in the switch below,
|
|
524
|
+
// for the reason `transcriptRetain` records above.
|
|
525
|
+
'showThinking',
|
|
526
|
+
// agent-activity-presentation-live §4.1. Listed here AND cased in the switch
|
|
527
|
+
// below, for the reason `transcriptRetain` and `showThinking` both record.
|
|
528
|
+
'liveToolOutput',
|
|
417
529
|
// MEMBERSHIP HERE ONLY DECIDES THAT THE KEY IS NOT REJECTED. The `switch`
|
|
418
530
|
// below is what builds the patch, and a key present here but absent there
|
|
419
531
|
// falls through every case, writes nothing, and still prints
|
|
@@ -439,6 +551,12 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
439
551
|
// ceiling, and a key listed here but forgotten there writes nothing while
|
|
440
552
|
// still printing `Set team.enabled = false`.
|
|
441
553
|
...TEAM_CONFIG_SET_KEYS,
|
|
554
|
+
// `retry.*` keys are handled by `applyRetryConfigSet`, same reasoning again.
|
|
555
|
+
...RETRY_CONFIG_SET_KEYS,
|
|
556
|
+
// `fast.*` keys are handled by `applyFastConfigSet`, same reasoning again.
|
|
557
|
+
...FAST_CONFIG_SET_KEYS,
|
|
558
|
+
// `update.*` keys are handled by `applyUpdateConfigSet`, same reasoning again.
|
|
559
|
+
...UPDATE_CONFIG_SET_KEYS,
|
|
442
560
|
]);
|
|
443
561
|
/**
|
|
444
562
|
* `aragon config set maxTokens <n|auto|default>`.
|
|
@@ -493,6 +611,38 @@ function runConfigSet(key, value) {
|
|
|
493
611
|
process.stdout.write(`Set ${key} = ${String(merged.team[field])}\n`);
|
|
494
612
|
return;
|
|
495
613
|
}
|
|
614
|
+
const retryPatch = applyRetryConfigSet(key, value);
|
|
615
|
+
if (retryPatch) {
|
|
616
|
+
// Echoed from the STORED value for the same reason `team.*` is, and it matters
|
|
617
|
+
// more here: `config set retry.maxRetries 0` used to be the value that
|
|
618
|
+
// silently became 10, so reporting the input would have hidden the bug rather
|
|
619
|
+
// than the fix (R-16).
|
|
620
|
+
const merged = updatePersistedConfig(retryPatch);
|
|
621
|
+
const field = key.slice('retry.'.length);
|
|
622
|
+
process.stdout.write(`Set ${key} = ${String(merged.retry[field])}\n`);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const fastPatch = applyFastConfigSet(key, value);
|
|
626
|
+
if (fastPatch) {
|
|
627
|
+
// Echoed from the STORED value, so `config set fast.reviewEveryTurns 400`
|
|
628
|
+
// prints the clamped 50 rather than reporting a number that was never
|
|
629
|
+
// written (AC-13) — the rule `team.*` and `retry.*` each state above.
|
|
630
|
+
const merged = updatePersistedConfig(fastPatch);
|
|
631
|
+
const field = key.slice('fast.'.length);
|
|
632
|
+
process.stdout.write(`Set ${key} = ${String(merged.fast[field])}\n`);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
const updatePatch = applyUpdateConfigSet(key, value);
|
|
636
|
+
if (updatePatch) {
|
|
637
|
+
// Echoed from the STORED value, so `config set update.checkIntervalMs 5`
|
|
638
|
+
// prints the clamped 900000 rather than reporting a number that was never
|
|
639
|
+
// written (AC-20) — the rule `team.*`, `retry.*` and `fast.*` each state
|
|
640
|
+
// above.
|
|
641
|
+
const merged = updatePersistedConfig(updatePatch);
|
|
642
|
+
const field = key.slice('update.'.length);
|
|
643
|
+
process.stdout.write(`Set ${key} = ${String(merged.update[field])}\n`);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
496
646
|
const patch = {};
|
|
497
647
|
switch (key) {
|
|
498
648
|
case 'provider':
|
|
@@ -540,6 +690,12 @@ function runConfigSet(key, value) {
|
|
|
540
690
|
case 'density':
|
|
541
691
|
patch.density = clampDensity(value, 'comfortable');
|
|
542
692
|
break;
|
|
693
|
+
case 'showThinking':
|
|
694
|
+
patch.showThinking = value === 'true' || value === '1';
|
|
695
|
+
break;
|
|
696
|
+
case 'liveToolOutput':
|
|
697
|
+
patch.liveToolOutput = value === 'true' || value === '1';
|
|
698
|
+
break;
|
|
543
699
|
case 'hints':
|
|
544
700
|
patch.hints = value === 'true' || value === '1';
|
|
545
701
|
break;
|
|
@@ -603,6 +759,166 @@ function runConfigSet(key, value) {
|
|
|
603
759
|
updatePersistedConfig(patch);
|
|
604
760
|
process.stdout.write(`Set ${key} = ${value}\n`);
|
|
605
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Exit codes (cli-auto-update section 4.3): `0` = up to date, or installed;
|
|
764
|
+
* `1` = the check or the install failed; `2` = an install source we will not
|
|
765
|
+
* install for (the advice line is printed); `4` = `--rollback` had nothing to
|
|
766
|
+
* roll back to (cli-auto-update-hardening section 6.2 / P2-8).
|
|
767
|
+
*
|
|
768
|
+
* A SOURCE WE DECLINE IS `2` AND NOT `1`, and the difference matters to the only
|
|
769
|
+
* consumer that reads an exit code - a script. `1` says "try again later"; `2`
|
|
770
|
+
* says "this machine will never auto-update, run the command we printed".
|
|
771
|
+
*/
|
|
772
|
+
function updateExitCode(snapshot) {
|
|
773
|
+
// REASON BEFORE PHASE, and the order is the whole change (P1-1). The three
|
|
774
|
+
// classified failures all arrive with `phase: 'failed'` - they come from
|
|
775
|
+
// `recordFailure` - so leaving the phase test first, as it was, means these
|
|
776
|
+
// two lines can never be reached and the mapping is a silent no-op.
|
|
777
|
+
//
|
|
778
|
+
// THIS IS A BEHAVIOUR CHANGE FOR SCRIPTS AND IS CALLED ONE (R-23). A
|
|
779
|
+
// `blocked-by-os` install used to exit `1` ("try again later") and now exits
|
|
780
|
+
// `2` ("this machine will never auto-update, run the command we printed").
|
|
781
|
+
// That is the honest answer - on Windows the same `npm i -g` from the same
|
|
782
|
+
// shell fails the same way until the user closes something - and it is
|
|
783
|
+
// recorded in the CHANGELOG under the version that ships it.
|
|
784
|
+
if (snapshot.reason === 'blocked-by-os' || snapshot.reason === 'no-space')
|
|
785
|
+
return 2;
|
|
786
|
+
if (snapshot.reason === 'source-ineligible' || snapshot.reason === 'not-writable')
|
|
787
|
+
return 2;
|
|
788
|
+
if (snapshot.phase === 'failed')
|
|
789
|
+
return 1;
|
|
790
|
+
if (snapshot.reason === 'install-ineffective')
|
|
791
|
+
return 1;
|
|
792
|
+
return 0;
|
|
793
|
+
}
|
|
794
|
+
/** Human output: three lines at most (section 4.3). */
|
|
795
|
+
function formatUpdateResult(snapshot) {
|
|
796
|
+
const lines = [];
|
|
797
|
+
switch (snapshot.phase) {
|
|
798
|
+
case 'ready':
|
|
799
|
+
lines.push(`Installed ${snapshot.latestVersion ?? ''}. Restart aragon to apply.`);
|
|
800
|
+
break;
|
|
801
|
+
case 'available':
|
|
802
|
+
lines.push(snapshot.reason === 'install-ineffective'
|
|
803
|
+
? `${snapshot.latestVersion ?? 'The new version'} installed somewhere else - ` +
|
|
804
|
+
'check your npm prefix.'
|
|
805
|
+
: `${snapshot.latestVersion ?? 'A new version'} is available ` +
|
|
806
|
+
`(running ${snapshot.currentVersion}).`);
|
|
807
|
+
if (snapshot.advice)
|
|
808
|
+
lines.push(` ${snapshot.advice}`);
|
|
809
|
+
break;
|
|
810
|
+
case 'failed':
|
|
811
|
+
lines.push('Update failed. See the log for details.');
|
|
812
|
+
if (snapshot.advice)
|
|
813
|
+
lines.push(` ${snapshot.advice}`);
|
|
814
|
+
break;
|
|
815
|
+
default:
|
|
816
|
+
lines.push(`aragon ${snapshot.currentVersion} is up to date.`);
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
if (snapshot.reason && snapshot.phase !== 'ready')
|
|
820
|
+
lines.push(` (${snapshot.reason})`);
|
|
821
|
+
return lines.slice(0, 3).join('\n');
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* `aragon update --rollback` - the MANUAL form of the boot guard's rollback
|
|
825
|
+
* (cli-auto-update-hardening section 6.2).
|
|
826
|
+
*
|
|
827
|
+
* It exists because a release can be BAD WITHOUT FAILING TO START, which is the
|
|
828
|
+
* case non-goal 2 declines to detect automatically precisely because a human
|
|
829
|
+
* detects it in one second. It shares `performRollback` verbatim, so the latch,
|
|
830
|
+
* the re-classification, the lock and the post-install verification are the same
|
|
831
|
+
* code the automatic path uses.
|
|
832
|
+
*
|
|
833
|
+
* Exit `4` for "nothing to roll back to" is a fourth code on a documented
|
|
834
|
+
* contract, so the contract's own comment above moves with it (P2-8). A rollback
|
|
835
|
+
* that RAN and failed is an ordinary failed install and keeps exit `1`.
|
|
836
|
+
*/
|
|
837
|
+
async function runRollbackCommand() {
|
|
838
|
+
const { readUpdateState, updateUpdateState } = await import('./update/state.js');
|
|
839
|
+
const good = readUpdateState().lastGoodVersion;
|
|
840
|
+
if (!good) {
|
|
841
|
+
process.stderr.write('Nothing to roll back to: no previous version was recorded by an auto-update.\n');
|
|
842
|
+
return 4;
|
|
843
|
+
}
|
|
844
|
+
if (good === VERSION) {
|
|
845
|
+
process.stdout.write(`aragon ${VERSION} is already the last known-good version.\n`);
|
|
846
|
+
return 0;
|
|
847
|
+
}
|
|
848
|
+
const { performRollback } = await import('./boot/rollback.js');
|
|
849
|
+
const result = await performRollback(VERSION, good);
|
|
850
|
+
if (!result.ok) {
|
|
851
|
+
process.stderr.write(`Rollback to ${good} did not complete (${result.failure}).\n`);
|
|
852
|
+
return 1;
|
|
853
|
+
}
|
|
854
|
+
// THE NOTICE IS FOR A DOWNGRADE THE USER DID NOT ASK FOR, AND THIS ONE WAS
|
|
855
|
+
// TYPED. `performRollback` sets `rolledBackFrom` because it cannot tell which
|
|
856
|
+
// caller it has, and the field has exactly one rendering: `rolled back to <to>
|
|
857
|
+
// after <bad> FAILED TO START` (section 8). On this path that sentence is
|
|
858
|
+
// false - the version started fine, the user simply did not want it - and a
|
|
859
|
+
// bottom row asserting a crash that never happened is worse than no row.
|
|
860
|
+
//
|
|
861
|
+
// Clearing it here rather than teaching `performRollback` a mode keeps the
|
|
862
|
+
// "verbatim shared mechanism" of section 6.2 intact and adds no fifth state
|
|
863
|
+
// field under C-17. D-38's purpose survives: the notice exists so a SILENT
|
|
864
|
+
// downgrade cannot go unnoticed, and the line below is this one's notice.
|
|
865
|
+
// `/update status` still shows `lastgood`, and `update_rolled_back` is in the
|
|
866
|
+
// log either way.
|
|
867
|
+
updateUpdateState({ rolledBackFrom: '' });
|
|
868
|
+
process.stdout.write(`Rolled back to ${good}. Restart aragon to apply.\n`);
|
|
869
|
+
return 0;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* `aragon update` - a deliberate, foreground check.
|
|
873
|
+
*
|
|
874
|
+
* PRINTS TO STDOUT, unlike every other update surface in this package: here the
|
|
875
|
+
* update IS the command's output, so C-10's "the TUI is the only legal visible
|
|
876
|
+
* channel" does not apply - there is no TUI.
|
|
877
|
+
*
|
|
878
|
+
* Reached through the SAME dynamic `import()` the interactive gate uses, so
|
|
879
|
+
* `dist/cli.js` still carries no static specifier under `update/` (AC-1).
|
|
880
|
+
*/
|
|
881
|
+
async function runUpdateCommand(flags, opts) {
|
|
882
|
+
const config = loadConfig(flags);
|
|
883
|
+
getLogger().reconfigure(config.log);
|
|
884
|
+
const { UpdateService } = await import('./update/service.js');
|
|
885
|
+
const service = new UpdateService({
|
|
886
|
+
// `--check` is exactly `mode: 'notify'` FOR THIS RUN, and expressing it that
|
|
887
|
+
// way rather than with a second flag through the service means there is one
|
|
888
|
+
// code path deciding whether to install, not two.
|
|
889
|
+
config: opts.check ? { ...config.update, mode: 'notify' } : config.update,
|
|
890
|
+
currentVersion: VERSION,
|
|
891
|
+
});
|
|
892
|
+
try {
|
|
893
|
+
if (opts.rollback)
|
|
894
|
+
return await runRollbackCommand();
|
|
895
|
+
const target = opts.to?.trim();
|
|
896
|
+
if (target) {
|
|
897
|
+
// REPORTED, not silently ignored. `installNow` also refuses a malformed
|
|
898
|
+
// version (it is the one entry point whose target comes straight from
|
|
899
|
+
// argv), but there the refusal is a no-op snapshot that would exit 0 and
|
|
900
|
+
// tell the user they are up to date — which is a lie about the command
|
|
901
|
+
// they actually typed.
|
|
902
|
+
const { STRICT_VERSION_RE } = await import('./update/semver.js');
|
|
903
|
+
if (!STRICT_VERSION_RE.test(target)) {
|
|
904
|
+
process.stderr.write(`Not a version: "${target}". Use e.g. --to 0.6.0\n`);
|
|
905
|
+
return 1;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const snapshot = target
|
|
909
|
+
? await service.installNow(target)
|
|
910
|
+
: await service.checkNow({ force: true });
|
|
911
|
+
process.stdout.write(opts.json ? `${JSON.stringify(snapshot)}\n` : `${formatUpdateResult(snapshot)}\n`);
|
|
912
|
+
return updateExitCode(snapshot);
|
|
913
|
+
}
|
|
914
|
+
catch (err) {
|
|
915
|
+
process.stderr.write(`update failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
916
|
+
return 1;
|
|
917
|
+
}
|
|
918
|
+
finally {
|
|
919
|
+
service.dispose();
|
|
920
|
+
}
|
|
921
|
+
}
|
|
606
922
|
// ---------------------------------------------------------------------------
|
|
607
923
|
// CLI definition
|
|
608
924
|
// ---------------------------------------------------------------------------
|
|
@@ -633,7 +949,16 @@ function buildProgram() {
|
|
|
633
949
|
.option('--hints', 'Always show the composer hint row')
|
|
634
950
|
.option('--no-hints', 'Hide the composer hint row')
|
|
635
951
|
// Positive form first, same tri-state reason as `--fullscreen` above.
|
|
636
|
-
.option('--
|
|
952
|
+
.option('--show-thinking', 'Show the reasoning blocks the model returns')
|
|
953
|
+
.option('--no-show-thinking', 'Hide reasoning blocks (the default)')
|
|
954
|
+
// Positive form first, same tri-state reason as `--fullscreen` above. The
|
|
955
|
+
// pair matters more here than for its neighbour because this key defaults to
|
|
956
|
+
// ON: a lone negative form would let commander synthesize `true` and
|
|
957
|
+
// overwrite a stored `false` on every run that passed no flag at all.
|
|
958
|
+
.option('--live-tool-output', 'Show a running command output tail (the default)')
|
|
959
|
+
.option('--no-live-tool-output', 'Keep a running tool card to one line')
|
|
960
|
+
// Positive form first, same tri-state reason as `--fullscreen` above.
|
|
961
|
+
.option('--mouse', 'Wheel scrolls the transcript (full-screen mode)')
|
|
637
962
|
.option('--no-mouse', 'Leave the mouse to the terminal (the wheel does nothing)')
|
|
638
963
|
// Declaring `--fullscreen` BEFORE `--no-fullscreen` keeps the default
|
|
639
964
|
// `undefined` instead of `true`, which is what makes the tri-state work:
|
|
@@ -667,6 +992,30 @@ function buildProgram() {
|
|
|
667
992
|
// passed, so `!== undefined` in `resolveTodoConfig` already distinguishes it
|
|
668
993
|
// from silence. An unrecognized mode is clamped to `notify`, not rejected.
|
|
669
994
|
.option('--todo-follow <mode>', 'What to do when a run ends with unfinished steps: notify (default) | auto | off')
|
|
995
|
+
// BOTH FORMS, positive first, for the reason recorded five times above:
|
|
996
|
+
// `retry.enabled` is PERSISTED, so a lone `--no-retry` would make commander
|
|
997
|
+
// default `opts.retry` to `true` and silently overwrite a stored `false`.
|
|
998
|
+
.option('--retry', 'Retry failed provider calls with backoff (the default)')
|
|
999
|
+
.option('--no-retry', 'Do not retry a failed provider call (fail on the first error)')
|
|
1000
|
+
.option('--retry-max <n>', 'Retries after the first attempt for this run (0-20; 0 = off)')
|
|
1001
|
+
// BOTH FORMS, positive first, for the reason recorded six times above — and
|
|
1002
|
+
// with the polarity reversed from every other pair here: `fast.enabled`
|
|
1003
|
+
// defaults to FALSE, so a lone `--fast` would be indistinguishable from
|
|
1004
|
+
// silence and the off-by-default guarantee (R-d) would rest on a commander
|
|
1005
|
+
// detail rather than on the config layer.
|
|
1006
|
+
.option('--fast', 'Enable the fast model tier for this session')
|
|
1007
|
+
.option('--no-fast', 'Disable the fast model tier (the default)')
|
|
1008
|
+
.option('--fast-model <id>', 'The fast model id (implies nothing about --fast)')
|
|
1009
|
+
.option('--fast-provider <id>', 'Provider for the fast model (default: the main provider)')
|
|
1010
|
+
.option('--fast-review <n|off>', 'Turns between automatic fast reviews, or "off"')
|
|
1011
|
+
// BOTH FORMS, positive first, for the reason recorded seven times above —
|
|
1012
|
+
// and this is the pair whose omission would be worst. `update.mode` is
|
|
1013
|
+
// PERSISTED and defaults to `'auto'`, so a lone `--no-update` would make
|
|
1014
|
+
// commander default `opts.update` to `true` and silently overwrite a stored
|
|
1015
|
+
// `off` on EVERY run that passed no flag at all: the kill switch for a
|
|
1016
|
+
// feature that installs software would quietly un-set itself (C-11).
|
|
1017
|
+
.option('--update', 'Check for and install updates in the background (the default)')
|
|
1018
|
+
.option('--no-update', 'Disable auto-update for this session')
|
|
670
1019
|
.option('--no-skills', 'Disable the skill system entirely (no catalog, no skill tools)')
|
|
671
1020
|
.option('--skill <name>', 'Force-load a skill at Level 2 for this run (repeatable)', (value, previous = []) => [...previous, value])
|
|
672
1021
|
.option('--skills-yes', 'Approve skill installs for this run (CI / headless)')
|
|
@@ -797,6 +1146,22 @@ function buildProgram() {
|
|
|
797
1146
|
const code = await runSkillsCommand(subcommand ?? 'list', argument, opts, toFlags(program.opts()), VERSION);
|
|
798
1147
|
process.exitCode = code;
|
|
799
1148
|
});
|
|
1149
|
+
// aragon update [--check] [--to <version>] [--json]
|
|
1150
|
+
//
|
|
1151
|
+
// THE GUARANTEED REPORTING SURFACE (C-12 / D-18), and the only update path
|
|
1152
|
+
// available to a headless user at all - `runInteractive` is the sole place the
|
|
1153
|
+
// background service is constructed, so without this command `aragon -p` users
|
|
1154
|
+
// could never update deliberately.
|
|
1155
|
+
program
|
|
1156
|
+
.command('update')
|
|
1157
|
+
.description('Check for a new release and install it')
|
|
1158
|
+
.option('--check', 'Report only; never install')
|
|
1159
|
+
.option('--to <version>', 'Install an exact version (an explicit escape hatch)')
|
|
1160
|
+
.option('--rollback', 'Reinstall the version that ran before the last auto-update')
|
|
1161
|
+
.option('--json', 'Machine-readable result on stdout')
|
|
1162
|
+
.action(async (opts) => {
|
|
1163
|
+
process.exitCode = await runUpdateCommand(toFlags(program.opts()), opts);
|
|
1164
|
+
});
|
|
800
1165
|
// aragon models [--provider p]
|
|
801
1166
|
program
|
|
802
1167
|
.command('models')
|