@aragon-agent/cli 0.5.0 → 0.5.2
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 +158 -0
- package/README.md +254 -7
- package/dist/agent/agent-mode.d.ts +53 -0
- package/dist/agent/agent-mode.js +69 -0
- package/dist/agent/controller.d.ts +69 -0
- package/dist/agent/controller.js +154 -1
- package/dist/agent/plan-prompt.d.ts +18 -0
- package/dist/agent/plan-prompt.js +93 -0
- package/dist/agent/reducer.d.ts +14 -1
- package/dist/agent/reducer.js +9 -0
- package/dist/agent/system-prompt.d.ts +15 -0
- package/dist/agent/system-prompt.js +8 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +247 -23
- package/dist/commands/builtins.js +60 -0
- package/dist/commands/registry.d.ts +17 -0
- package/dist/config/app-paths.d.ts +71 -4
- package/dist/config/app-paths.js +139 -8
- package/dist/config/cli-commands.d.ts +43 -0
- package/dist/config/cli-commands.js +196 -0
- package/dist/config/env.d.ts +7 -1
- package/dist/config/env.js +53 -1
- package/dist/config/load.d.ts +10 -0
- package/dist/config/load.js +82 -2
- package/dist/config/migrate-home.d.ts +63 -0
- package/dist/config/migrate-home.js +165 -0
- package/dist/config/migrate-legacy-state.d.ts +10 -0
- package/dist/config/migrate-legacy-state.js +15 -6
- package/dist/config/schema.d.ts +81 -0
- package/dist/config/schema.js +85 -0
- package/dist/config/store.d.ts +30 -12
- package/dist/config/store.js +47 -35
- package/dist/input/mouse-events.d.ts +61 -0
- package/dist/input/mouse-events.js +153 -0
- package/dist/input/stdin-mouse-filter.d.ts +42 -0
- package/dist/input/stdin-mouse-filter.js +153 -0
- package/dist/logging/cli-commands.d.ts +25 -0
- package/dist/logging/cli-commands.js +221 -0
- package/dist/logging/file-sink.d.ts +114 -0
- package/dist/logging/file-sink.js +326 -0
- package/dist/logging/install.d.ts +163 -0
- package/dist/logging/install.js +302 -0
- package/dist/logging/levels.d.ts +40 -0
- package/dist/logging/levels.js +59 -0
- package/dist/logging/logger.d.ts +110 -0
- package/dist/logging/logger.js +237 -0
- package/dist/logging/redact.d.ts +28 -0
- package/dist/logging/redact.js +90 -0
- package/dist/logging/secret-registry.d.ts +30 -0
- package/dist/logging/secret-registry.js +55 -0
- package/dist/skills/paths.d.ts +2 -4
- package/dist/skills/paths.js +7 -9
- package/dist/tools/human-input.d.ts +179 -0
- package/dist/tools/human-input.js +227 -0
- package/dist/tools/index.d.ts +35 -3
- package/dist/tools/index.js +96 -9
- package/dist/tools/plan-tools.d.ts +64 -0
- package/dist/tools/plan-tools.js +198 -0
- package/dist/ui/App.d.ts +12 -1
- package/dist/ui/App.js +223 -17
- package/dist/ui/Composer.d.ts +9 -1
- package/dist/ui/Composer.js +26 -7
- package/dist/ui/ModeChip.d.ts +29 -0
- package/dist/ui/ModeChip.js +15 -0
- package/dist/ui/PromptInput.d.ts +22 -1
- package/dist/ui/PromptInput.js +49 -2
- package/dist/ui/StatusBar.d.ts +15 -0
- package/dist/ui/StatusBar.js +10 -2
- package/dist/ui/entries/ToolPreview.d.ts +2 -0
- package/dist/ui/entries/ToolPreview.js +64 -0
- package/dist/ui/glyphs.d.ts +21 -0
- package/dist/ui/glyphs.js +12 -0
- package/dist/ui/layout/AppShell.d.ts +10 -1
- package/dist/ui/layout/AppShell.js +38 -3
- package/dist/ui/layout/ScrollIndicator.d.ts +31 -0
- package/dist/ui/layout/ScrollIndicator.js +15 -0
- package/dist/ui/layout/ScrollViewport.d.ts +8 -2
- package/dist/ui/layout/ScrollViewport.js +24 -4
- package/dist/ui/layout/regions.d.ts +55 -0
- package/dist/ui/layout/regions.js +52 -0
- package/dist/ui/layout/scroll-indicator.d.ts +27 -0
- package/dist/ui/layout/scroll-indicator.js +35 -0
- package/dist/ui/layout/scroll.d.ts +9 -0
- package/dist/ui/layout/scroll.js +23 -0
- package/dist/ui/layout/wrap-rows.d.ts +24 -0
- package/dist/ui/layout/wrap-rows.js +74 -0
- package/dist/ui/overlays/HelpOverlay.js +15 -0
- package/dist/ui/overlays/PlanReviewOverlay.d.ts +50 -0
- package/dist/ui/overlays/PlanReviewOverlay.js +117 -0
- package/dist/ui/overlays/QuestionOverlay.d.ts +30 -0
- package/dist/ui/overlays/QuestionOverlay.js +193 -0
- package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
- package/dist/ui/overlays/SettingsScreen.js +8 -1
- package/dist/ui/screen.d.ts +14 -1
- package/dist/ui/screen.js +37 -3
- package/dist/ui/use-startup-notices.d.ts +25 -0
- package/dist/ui/use-startup-notices.js +64 -0
- package/dist/ui/use-wheel-routing.d.ts +68 -0
- package/dist/ui/use-wheel-routing.js +139 -0
- package/package.json +3 -2
- package/runtime/insecure-tls-warning.cjs +62 -0
- package/runtime/insecure-tls-warning.d.cts +5 -0
package/dist/cli.js
CHANGED
|
@@ -14,23 +14,31 @@ import { readFileSync } from 'node:fs';
|
|
|
14
14
|
import { dirname, join } from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
import process from 'node:process';
|
|
17
|
+
import '../runtime/insecure-tls-warning.cjs';
|
|
17
18
|
import { render } from 'ink';
|
|
18
19
|
import { Command } from 'commander';
|
|
19
20
|
import { loadConfig } from './config/load.js';
|
|
20
|
-
import { getConfigPath, updatePersistedConfig } from './config/store.js';
|
|
21
|
+
import { getConfigPath, readConfigFile, updatePersistedConfig } from './config/store.js';
|
|
21
22
|
import { formatMigrationNotice, migrateLegacyState } from './config/migrate-legacy-state.js';
|
|
22
|
-
import {
|
|
23
|
+
import { formatHomeMigrationNotice, migrateToHome } from './config/migrate-home.js';
|
|
24
|
+
import { LOG_CONFIG_SET_KEYS, applyLogConfigSet, runConfigEdit, runConfigGet, runConfigHome, runConfigList, warnIfRedactionDisabled, } from './config/cli-commands.js';
|
|
25
|
+
import { attachAgentEvents, installLogging, setScreenRestore, setSignalTerminator, } from './logging/install.js';
|
|
26
|
+
import { getLogger } from './logging/logger.js';
|
|
27
|
+
import { runLogsCommand } from './logging/cli-commands.js';
|
|
28
|
+
import { ADAPTER_PROVIDERS, clampDensity, clampTheme, clampThinkingLevel, clampAskRounds, clampHumanTimeout, clampSkillsIntegrity, clampSkillsToolPolicy, clampTranscriptWindow, coerceMaxTokens, coercePositiveInt, isAdapterProvider, DEFAULT_PLAN_HUMAN_TIMEOUT_MS, DEFAULT_PLAN_MAX_ASK_ROUNDS, DEFAULT_SKILLS_CONFIG, } from './config/schema.js';
|
|
23
29
|
import { makeGetApiKey } from './config/load.js';
|
|
24
30
|
import { AgentController, DENY_ALL_APPROVAL } from './agent/controller.js';
|
|
25
31
|
import { runSkillsCommand } from './skills/cli-commands.js';
|
|
26
32
|
import { runHeadless } from './agent/headless.js';
|
|
27
33
|
import { App } from './ui/App.js';
|
|
28
34
|
import { decideRenderMode } from './ui/layout/frame.js';
|
|
35
|
+
import { tryCreateMouseFilter } from './input/stdin-mouse-filter.js';
|
|
29
36
|
import { enterAltScreen, writeExitTranscript } from './ui/screen.js';
|
|
30
37
|
import { readExitSnapshot } from './ui/exit-snapshot.js';
|
|
31
38
|
import { renderTranscriptText } from './ui/transcript-text.js';
|
|
32
39
|
import { detectCapabilities } from './ui/capabilities.js';
|
|
33
40
|
import { pickGlyphs } from './ui/glyphs.js';
|
|
41
|
+
import { DENY_ALL_HUMAN_INPUT } from './tools/human-input.js';
|
|
34
42
|
// ---------------------------------------------------------------------------
|
|
35
43
|
// Version (read from the shipped package.json next to dist/)
|
|
36
44
|
// ---------------------------------------------------------------------------
|
|
@@ -63,10 +71,19 @@ function toFlags(opts) {
|
|
|
63
71
|
exitTranscript: opts.exitTranscript,
|
|
64
72
|
compact: opts.compact,
|
|
65
73
|
hints: opts.hints,
|
|
74
|
+
// Without this line the flag never reaches `CliFlags` and `--no-mouse` is
|
|
75
|
+
// inert — a failure with no compile error and no runtime error (P1-2).
|
|
76
|
+
mouse: opts.mouse,
|
|
66
77
|
skills: opts.skills,
|
|
67
78
|
skill: opts.skill,
|
|
68
79
|
skillsYes: opts.skillsYes,
|
|
69
80
|
skillToolPolicy: opts.skillToolPolicy,
|
|
81
|
+
plan: opts.plan,
|
|
82
|
+
// `--verbose` is just a shorthand for `--log-level debug`; an explicit
|
|
83
|
+
// `--log-level` still wins, so the two can be combined without surprise.
|
|
84
|
+
logLevel: opts.logLevel ?? (opts.verbose ? 'debug' : undefined),
|
|
85
|
+
logFile: opts.logFile,
|
|
86
|
+
logDir: opts.logDir,
|
|
70
87
|
};
|
|
71
88
|
}
|
|
72
89
|
// ---------------------------------------------------------------------------
|
|
@@ -92,7 +109,14 @@ function toFlags(opts) {
|
|
|
92
109
|
*/
|
|
93
110
|
function makeController(flags, opts = { interactive: true }) {
|
|
94
111
|
const config = loadConfig(flags);
|
|
112
|
+
// The authoritative log level lands here (§4.4.5). `installLogging()` only
|
|
113
|
+
// resolved a bootstrap level — it ran before commander and before `.env` were
|
|
114
|
+
// readable — and without this hand-off the fully resolved one never applies.
|
|
115
|
+
getLogger().reconfigure(config.log);
|
|
95
116
|
const confirmBridge = { handler: null };
|
|
117
|
+
// `cancelPending` is replaced by the App's own implementation on mount; this
|
|
118
|
+
// no-op keeps the object total so nothing has to null-check it.
|
|
119
|
+
const humanInputBridge = { handler: null, cancelPending: () => { } };
|
|
96
120
|
const controller = new AgentController(config, {
|
|
97
121
|
version: VERSION,
|
|
98
122
|
confirm: (req) => confirmBridge.handler ? confirmBridge.handler(req) : Promise.resolve(true),
|
|
@@ -109,11 +133,23 @@ function makeController(flags, opts = { interactive: true }) {
|
|
|
109
133
|
if (!opts.interactive)
|
|
110
134
|
process.stderr.write(`[skills] ${level}: ${text}\n`);
|
|
111
135
|
},
|
|
136
|
+
// A THIRD approval channel, and it must not be conflated with the other two.
|
|
137
|
+
// `confirm` auto-approves when unattached and `approval` denies; neither can
|
|
138
|
+
// render a question wizard or a plan card. Headless gets the fail-closed
|
|
139
|
+
// gate, whose STATIC `neverPrompts` is also what stops `createPlanTools`
|
|
140
|
+
// registering two tools the model could never use there.
|
|
141
|
+
humanInput: opts.interactive
|
|
142
|
+
? {
|
|
143
|
+
// Live probe, never cached — same contract as `approval` above.
|
|
144
|
+
canPrompt: () => humanInputBridge.handler !== null,
|
|
145
|
+
request: (req, signal) => humanInputBridge.handler
|
|
146
|
+
? humanInputBridge.handler(req, signal)
|
|
147
|
+
: Promise.resolve(null),
|
|
148
|
+
}
|
|
149
|
+
: DENY_ALL_HUMAN_INPUT,
|
|
112
150
|
});
|
|
113
|
-
return { controller, confirmBridge };
|
|
151
|
+
return { controller, confirmBridge, humanInputBridge };
|
|
114
152
|
}
|
|
115
|
-
/** POSIX signal numbers for the `128 + signo` exit-code convention. */
|
|
116
|
-
const SIGNAL_NUMBERS = { SIGINT: 2, SIGTERM: 15, SIGHUP: 1 };
|
|
117
153
|
/**
|
|
118
154
|
* The interactive TUI — and the ONLY place that may touch the screen.
|
|
119
155
|
*
|
|
@@ -124,11 +160,39 @@ const SIGNAL_NUMBERS = { SIGINT: 2, SIGTERM: 15, SIGHUP: 1 };
|
|
|
124
160
|
* renders Ink, `runOneShot()` goes to `runHeadless()`, which never does.
|
|
125
161
|
*/
|
|
126
162
|
function runInteractive(flags, extras = {}) {
|
|
127
|
-
const { controller, confirmBridge } = makeController(flags);
|
|
163
|
+
const { controller, confirmBridge, humanInputBridge } = makeController(flags);
|
|
128
164
|
const config = controller.getConfig();
|
|
129
165
|
const mode = decideRenderMode({ fullscreen: config.fullscreen }, process.env, process.stdout);
|
|
166
|
+
const logger = getLogger();
|
|
167
|
+
logger.info('cli', 'run_interactive', { mode, provider: config.provider, model: config.model });
|
|
168
|
+
attachAgentEvents(logger, controller);
|
|
130
169
|
let screen = null;
|
|
131
170
|
let replayed = false;
|
|
171
|
+
// --- Mouse wheel region routing (mouse-wheel-region-routing §4.2). -------
|
|
172
|
+
//
|
|
173
|
+
// BUILD THE FILTER FIRST, THEN ENABLE REPORTING. The order is invariant I-8
|
|
174
|
+
// and it is not negotiable: `\x1b[?1000h` tells the terminal to send SGR
|
|
175
|
+
// reports, and Ink 5.2.1 has no mouse branch at all, so any report that
|
|
176
|
+
// reaches its key parser is INSERTED INTO THE USER'S MESSAGE (`[` is 0x5B, so
|
|
177
|
+
// `PromptInput`'s `isControlSeq` guard passes it). A window in which
|
|
178
|
+
// reporting is on and Ink holds the raw stream is not a degraded feature — it
|
|
179
|
+
// is every click typing `[<0;12;5M` into the composer, strictly worse than
|
|
180
|
+
// the bug being fixed.
|
|
181
|
+
//
|
|
182
|
+
// `tryCreateMouseFilter` returns `null` rather than throwing, so a
|
|
183
|
+
// construction failure degrades to "the wheel is inert" (rung 3 of the
|
|
184
|
+
// fail-safe ladder), never to "reporting on, no filter".
|
|
185
|
+
// BOTH streams have to be terminals. `stdout.isTTY` is what decides whether
|
|
186
|
+
// `enterAltScreen` writes anything at all, but the reports come back on
|
|
187
|
+
// STDIN, so a TTY stdout with a piped stdin (`aragon config < file`, which
|
|
188
|
+
// reaches `runInteractive` without the stdin check the default action makes)
|
|
189
|
+
// would wrap a stream no report can ever arrive on and ask the terminal to
|
|
190
|
+
// start reporting anyway. §4.2's "non-TTY ⇒ no filter" covers both ends.
|
|
191
|
+
const wantMouse = mode === 'fullscreen' && config.mouse && !!process.stdout.isTTY && !!process.stdin.isTTY;
|
|
192
|
+
const mouseFilter = wantMouse
|
|
193
|
+
? tryCreateMouseFilter(process.stdin, (reason) => logger.warn('cli', 'mouse_filter_failed', { reason }))
|
|
194
|
+
: null;
|
|
195
|
+
const disposeMouse = () => mouseFilter?.dispose();
|
|
132
196
|
const replayTranscript = () => {
|
|
133
197
|
if (replayed || mode !== 'fullscreen' || !config.exitTranscript)
|
|
134
198
|
return;
|
|
@@ -146,7 +210,10 @@ function runInteractive(flags, extras = {}) {
|
|
|
146
210
|
}));
|
|
147
211
|
};
|
|
148
212
|
if (mode === 'fullscreen') {
|
|
149
|
-
|
|
213
|
+
// `mouse` here means "a filter is installed", NOT "the user wants mouse
|
|
214
|
+
// support" — see `AltScreenOptions`. `screen.ts` must never derive it for
|
|
215
|
+
// itself, or a later refactor re-opens the gap I-8 closes.
|
|
216
|
+
screen = enterAltScreen(process.stdout, { mouse: mouseFilter !== null });
|
|
150
217
|
const restore = () => screen?.restore();
|
|
151
218
|
// Four idempotent restore paths (§4.4). The signal hook is NOT redundant:
|
|
152
219
|
// - `waitUntilExit().then()` is a microtask and may never be reached when
|
|
@@ -158,27 +225,50 @@ function runInteractive(flags, extras = {}) {
|
|
|
158
225
|
// parent all leave the user staring at a blank alternate screen with `reset`
|
|
159
226
|
// as their only way out.
|
|
160
227
|
process.on('exit', restore);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
228
|
+
// The signal listeners themselves now belong to `installLogging()`, which
|
|
229
|
+
// registered them before this function ever ran — and a second listener per
|
|
230
|
+
// signal would race the first. This branch owns the SCREEN, so it supplies
|
|
231
|
+
// the two screen-shaped pieces and nothing else: the crash handler calls
|
|
232
|
+
// `screenRestore` before printing a stack (otherwise the stack vanishes with
|
|
233
|
+
// the alternate screen), and the signal path restores before exiting.
|
|
234
|
+
setScreenRestore(restore);
|
|
235
|
+
setSignalTerminator((signo) => {
|
|
236
|
+
restore(); // Synchronous: stdout.write on a TTY is sync on every platform.
|
|
237
|
+
process.exit(128 + signo);
|
|
238
|
+
});
|
|
167
239
|
}
|
|
168
|
-
const instance = render(_jsx(App, { controller: controller, version: VERSION, mode: mode, confirmBridge: confirmBridge, initialPrompt: extras.initialPrompt, initialOverlay: extras.initialOverlay }),
|
|
240
|
+
const instance = render(_jsx(App, { controller: controller, version: VERSION, mode: mode, confirmBridge: confirmBridge, humanInputBridge: humanInputBridge, initialPrompt: extras.initialPrompt, initialOverlay: extras.initialOverlay, mouseSource: mouseFilter?.source }),
|
|
169
241
|
// Full-screen owns console.* itself (I-4): Ink's patchConsole writes
|
|
170
242
|
// straight to stdout and permanently shifts the fixed frame's accounting.
|
|
171
|
-
{
|
|
243
|
+
{
|
|
244
|
+
exitOnCtrlC: false,
|
|
245
|
+
patchConsole: mode === 'inline',
|
|
246
|
+
// I-5: with mouse support off the REAL stdin is handed over unwrapped, so
|
|
247
|
+
// every existing path is provably unchanged.
|
|
248
|
+
stdin: mouseFilter?.stdin ?? process.stdin,
|
|
249
|
+
});
|
|
172
250
|
// The return value MUST be captured — `waitUntilExit()` is the normal-exit
|
|
173
|
-
// restore path and it cannot be registered otherwise.
|
|
251
|
+
// restore path and it cannot be registered otherwise. `dispose()` runs on
|
|
252
|
+
// BOTH branches (I-7): a live `'data'` listener on stdin keeps the event loop
|
|
253
|
+
// alive and `aragon` would never return to the shell. The signal path does
|
|
254
|
+
// not need it — it calls `process.exit()`.
|
|
255
|
+
//
|
|
256
|
+
// RESTORE BEFORE DISPOSE. `restore()` writes `?1000l`, so the terminal stops
|
|
257
|
+
// reporting first and anything already in flight is still consumed by a live
|
|
258
|
+
// filter. Disposing first leaves a window — narrow, but real — in which the
|
|
259
|
+
// terminal is still reporting and nothing is draining stdin, so a notch taken
|
|
260
|
+
// in that instant survives in the OS buffer and is handed to the shell as
|
|
261
|
+
// `[<64;12;5M` on exit. That is the I-2 failure mode, just smaller.
|
|
174
262
|
void instance
|
|
175
263
|
.waitUntilExit()
|
|
176
264
|
.then(() => {
|
|
177
265
|
screen?.restore();
|
|
266
|
+
disposeMouse();
|
|
178
267
|
replayTranscript();
|
|
179
268
|
})
|
|
180
269
|
.catch(() => {
|
|
181
270
|
screen?.restore();
|
|
271
|
+
disposeMouse();
|
|
182
272
|
});
|
|
183
273
|
}
|
|
184
274
|
async function runOneShot(flags, prompt, quiet) {
|
|
@@ -186,11 +276,23 @@ async function runOneShot(flags, prompt, quiet) {
|
|
|
186
276
|
// install. The gate must know that up front rather than discovering a null
|
|
187
277
|
// handler and defaulting to "yes" (§7.4 / AC-13).
|
|
188
278
|
const { controller } = makeController(flags, { interactive: false });
|
|
279
|
+
const logger = getLogger();
|
|
280
|
+
logger.info('cli', 'run_oneshot', { quiet });
|
|
281
|
+
// Headless has no frame, so a logging failure can safely reach stderr — the
|
|
282
|
+
// toast channel `App.tsx` uses does not exist here.
|
|
283
|
+
logger.onFailure((reason) => process.stderr.write(`[log] ${reason}\n`));
|
|
284
|
+
attachAgentEvents(logger, controller);
|
|
189
285
|
const untrusted = controller.getSkillService().untrustedDirs();
|
|
190
286
|
for (const dir of untrusted) {
|
|
191
287
|
process.stderr.write(`[skills] project skills in ${dir} skipped (untrusted). Run: aragon skills trust ${dir}\n`);
|
|
192
288
|
}
|
|
193
289
|
const code = await runHeadless(controller, prompt, { quiet });
|
|
290
|
+
// Flushed HERE and not inside `runHeadless`: that module is deliberately
|
|
291
|
+
// injectable (its own controller interface, its own stdout/stderr) and has an
|
|
292
|
+
// early `return 2`, so a singleton dependency in there would be both a new
|
|
293
|
+
// coupling and a branch that skips the flush. `process.on('exit')` remains the
|
|
294
|
+
// real backstop; this just makes the common path immediate.
|
|
295
|
+
getLogger().flushSync();
|
|
194
296
|
process.exitCode = code;
|
|
195
297
|
}
|
|
196
298
|
async function readStdin() {
|
|
@@ -247,6 +349,14 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
247
349
|
// flags and never persist -- inconsistent with every other config field.
|
|
248
350
|
'density',
|
|
249
351
|
'hints',
|
|
352
|
+
// MEMBERSHIP HERE ONLY DECIDES THAT THE KEY IS NOT REJECTED. The `switch`
|
|
353
|
+
// below is what builds the patch, and a key present here but absent there
|
|
354
|
+
// falls through every case, writes nothing, and still prints
|
|
355
|
+
// `Set mouse = false` (P1-2) -- exactly what happened to `density`/`hints`.
|
|
356
|
+
'mouse',
|
|
357
|
+
'planModeDefault',
|
|
358
|
+
'planModeMaxAskRounds',
|
|
359
|
+
'planModeHumanTimeoutMs',
|
|
250
360
|
// Dotted keys route into the nested `skills` object; see the switch below.
|
|
251
361
|
'skills.enabled',
|
|
252
362
|
'skills.requireApproval',
|
|
@@ -255,6 +365,9 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
255
365
|
'skills.integrity',
|
|
256
366
|
'skills.usageTracking',
|
|
257
367
|
'skills.toolPolicy',
|
|
368
|
+
// `log.*` keys are handled by `applyLogConfigSet` below, NOT by the switch:
|
|
369
|
+
// that switch is already past this package's complexity ceiling.
|
|
370
|
+
...LOG_CONFIG_SET_KEYS,
|
|
258
371
|
]);
|
|
259
372
|
function runConfigSet(key, value) {
|
|
260
373
|
if (!CONFIG_SET_KEYS.has(key)) {
|
|
@@ -262,6 +375,13 @@ function runConfigSet(key, value) {
|
|
|
262
375
|
process.exitCode = 2;
|
|
263
376
|
return;
|
|
264
377
|
}
|
|
378
|
+
const logPatch = applyLogConfigSet(key, value);
|
|
379
|
+
if (logPatch) {
|
|
380
|
+
updatePersistedConfig(logPatch);
|
|
381
|
+
warnIfRedactionDisabled(key, value);
|
|
382
|
+
process.stdout.write(`Set ${key} = ${value}\n`);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
265
385
|
const patch = {};
|
|
266
386
|
switch (key) {
|
|
267
387
|
case 'provider':
|
|
@@ -306,6 +426,22 @@ function runConfigSet(key, value) {
|
|
|
306
426
|
case 'hints':
|
|
307
427
|
patch.hints = value === 'true' || value === '1';
|
|
308
428
|
break;
|
|
429
|
+
case 'mouse':
|
|
430
|
+
patch.mouse = value === 'true' || value === '1';
|
|
431
|
+
break;
|
|
432
|
+
// Clamped rather than rejected, for the same reason as `theme` and
|
|
433
|
+
// `skills.integrity`: hardening only the READ path leaves a bad value on
|
|
434
|
+
// disk that reverts to the default on every launch, which presents as
|
|
435
|
+
// "my setting won't stick".
|
|
436
|
+
case 'planModeDefault':
|
|
437
|
+
patch.planModeDefault = value === 'true' || value === '1';
|
|
438
|
+
break;
|
|
439
|
+
case 'planModeMaxAskRounds':
|
|
440
|
+
patch.planModeMaxAskRounds = clampAskRounds(value, DEFAULT_PLAN_MAX_ASK_ROUNDS);
|
|
441
|
+
break;
|
|
442
|
+
case 'planModeHumanTimeoutMs':
|
|
443
|
+
patch.planModeHumanTimeoutMs = clampHumanTimeout(value, DEFAULT_PLAN_HUMAN_TIMEOUT_MS);
|
|
444
|
+
break;
|
|
309
445
|
// The dotted keys write a PARTIAL `skills` object. That is safe only
|
|
310
446
|
// because `updatePersistedConfig` deep-merges this section (§10.3.2) —
|
|
311
447
|
// with the old shallow merge each of these would wipe trustedProjectDirs.
|
|
@@ -374,16 +510,34 @@ function buildProgram() {
|
|
|
374
510
|
.option('--no-compact', 'Comfortable transcript density')
|
|
375
511
|
.option('--hints', 'Always show the composer hint row')
|
|
376
512
|
.option('--no-hints', 'Hide the composer hint row')
|
|
513
|
+
// Positive form first, same tri-state reason as `--fullscreen` above.
|
|
514
|
+
.option('--mouse', 'Wheel scrolls the region under the pointer (full-screen mode)')
|
|
515
|
+
.option('--no-mouse', 'Leave the mouse to the terminal (the wheel does nothing)')
|
|
377
516
|
// Declaring `--fullscreen` BEFORE `--no-fullscreen` keeps the default
|
|
378
517
|
// `undefined` instead of `true`, which is what makes the tri-state work:
|
|
379
518
|
// "no opinion" must stay distinguishable from "force it".
|
|
380
519
|
.option('--fullscreen', 'Force the full-screen TUI (overrides auto-downgrade, except non-TTY)')
|
|
381
520
|
.option('--no-fullscreen', 'Force the inline renderer (v0.2.0 behavior)')
|
|
382
521
|
.option('--no-exit-transcript', 'Do not replay the session summary after exiting')
|
|
522
|
+
// Same tri-state shape as `--fullscreen`: the positive form is declared
|
|
523
|
+
// FIRST so the default stays `undefined` and "no opinion" remains
|
|
524
|
+
// distinguishable from an explicit `--no-plan`, which is what lets the
|
|
525
|
+
// config layer win when neither flag was given.
|
|
526
|
+
.option('--plan', 'Start the session in PLAN mode (read-only research + review)')
|
|
527
|
+
.option('--no-plan', 'Start the session in BUILD mode (overrides planModeDefault)')
|
|
383
528
|
.option('--no-skills', 'Disable the skill system entirely (no catalog, no skill tools)')
|
|
384
529
|
.option('--skill <name>', 'Force-load a skill at Level 2 for this run (repeatable)', (value, previous = []) => [...previous, value])
|
|
385
530
|
.option('--skills-yes', 'Approve skill installs for this run (CI / headless)')
|
|
386
531
|
.option('--skill-tool-policy <mode>', 'Tool ceiling from allowed-tools for this run: off | warn | enforce (not persisted)')
|
|
532
|
+
// `--log-file` is declared BEFORE `--no-log-file` for the tri-state reason
|
|
533
|
+
// recorded twice above: negative-only would default it to `true` and make a
|
|
534
|
+
// persisted `log.toFile: false` permanently unreachable.
|
|
535
|
+
.option('--log-file', 'Write this run to a log file')
|
|
536
|
+
.option('--no-log-file', 'Do not write this run to a log file')
|
|
537
|
+
.option('--log-level <level>', 'silent|error|warn|info|debug|trace (this run only)')
|
|
538
|
+
// No short form: `-v` is already `--version`.
|
|
539
|
+
.option('--verbose', 'Shorthand for --log-level debug')
|
|
540
|
+
.option('--log-dir <dir>', 'Absolute log directory for this run')
|
|
387
541
|
.option('--no-color', 'Disable ANSI color')
|
|
388
542
|
.option('--quiet', '(print mode) suppress tool/usage lines on stderr')
|
|
389
543
|
.action(async (prompt, opts) => {
|
|
@@ -433,6 +587,37 @@ function buildProgram() {
|
|
|
433
587
|
.action(() => {
|
|
434
588
|
process.stdout.write(`${getConfigPath()}\n`);
|
|
435
589
|
});
|
|
590
|
+
configCmd
|
|
591
|
+
.command('get <key>')
|
|
592
|
+
.description('Print a single config value (secrets are masked)')
|
|
593
|
+
.action((key) => runConfigGet(key));
|
|
594
|
+
configCmd
|
|
595
|
+
.command('list')
|
|
596
|
+
.description('Print the whole config (secrets are masked)')
|
|
597
|
+
.option('--json', 'Machine-readable output')
|
|
598
|
+
.action((opts) => runConfigList(opts));
|
|
599
|
+
configCmd
|
|
600
|
+
.command('edit')
|
|
601
|
+
.description('Open config.json in $VISUAL / $EDITOR (backs it up first)')
|
|
602
|
+
.action(async () => {
|
|
603
|
+
await runConfigEdit();
|
|
604
|
+
});
|
|
605
|
+
configCmd
|
|
606
|
+
.command('home')
|
|
607
|
+
.description('Print the user-state root directory')
|
|
608
|
+
.action(() => runConfigHome());
|
|
609
|
+
// aragon logs <sub> [-n N] [--follow] [--level lv] [--json] [--yes]
|
|
610
|
+
program
|
|
611
|
+
.command('logs [subcommand]')
|
|
612
|
+
.description('Inspect the log files: path | list | tail | clear | open')
|
|
613
|
+
.option('-n, --lines <n>', 'Lines for `logs tail` (default 100)')
|
|
614
|
+
.option('--follow', 'Keep printing new records (`logs tail`)')
|
|
615
|
+
.option('--level <level>', 'Only show records at this level or more severe')
|
|
616
|
+
.option('--json', 'Print raw JSONL instead of rendered lines')
|
|
617
|
+
.option('--yes', 'Confirm destructive actions (required by `logs clear`)')
|
|
618
|
+
.action(async (subcommand, opts) => {
|
|
619
|
+
process.exitCode = await runLogsCommand(subcommand ?? 'path', opts, toFlags(program.opts()));
|
|
620
|
+
});
|
|
436
621
|
// aragon skills <sub> [arg] [--scope] [--name] [--description] [--yes] [--json]
|
|
437
622
|
program
|
|
438
623
|
.command('skills [subcommand] [argument]')
|
|
@@ -466,16 +651,55 @@ function buildProgram() {
|
|
|
466
651
|
return program;
|
|
467
652
|
}
|
|
468
653
|
async function main() {
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
654
|
+
// ① and ② are the first two statements on purpose: everything below reads
|
|
655
|
+
// config or skills. THE ORDER IS NOT INTERCHANGEABLE — ① moves 0.4.x data out
|
|
656
|
+
// of the pre-rename env-paths tree, and only then does ② have anything to
|
|
657
|
+
// move into `~/.aragon-agent`. Run the other way round, `config.json` is
|
|
658
|
+
// stranded permanently (see migrate-home.ts). Neither ever throws.
|
|
659
|
+
const legacy = migrateLegacyState();
|
|
660
|
+
const home = migrateToHome();
|
|
661
|
+
for (const notice of [formatMigrationNotice(legacy), formatHomeMigrationNotice(home)]) {
|
|
662
|
+
if (notice)
|
|
663
|
+
process.stderr.write(`${notice}\n`);
|
|
664
|
+
}
|
|
665
|
+
// ③ comes after both because the log directory lives inside a home that ②
|
|
666
|
+
// may just have created. What ① and ② did is replayed from `pending` rather
|
|
667
|
+
// than lost.
|
|
668
|
+
installLogging({ pending: migrationNotes(legacy, home) });
|
|
669
|
+
// An unparseable config.json still falls back to defaults — the CLI has to
|
|
670
|
+
// start — but it must not do so in silence, or a stray comma presents as
|
|
671
|
+
// "every setting I ever chose is gone". stderr, never stdout, so
|
|
672
|
+
// `aragon -p "…" > out.txt` keeps carrying model output and nothing else.
|
|
673
|
+
const { parseError } = readConfigFile();
|
|
674
|
+
if (parseError) {
|
|
675
|
+
process.stderr.write(`config: using defaults, could not parse ${parseError}\n`);
|
|
676
|
+
}
|
|
476
677
|
const program = buildProgram();
|
|
477
678
|
await program.parseAsync(process.argv);
|
|
478
679
|
}
|
|
680
|
+
/** The ①/② outcomes, as records for `installLogging` to write once it exists. */
|
|
681
|
+
function migrationNotes(legacy, home) {
|
|
682
|
+
const notes = [
|
|
683
|
+
{ level: 'info', scope: 'cli', msg: 'cli_start', data: { version: VERSION } },
|
|
684
|
+
];
|
|
685
|
+
if (legacy.mode !== 'none' || legacy.error) {
|
|
686
|
+
notes.push({
|
|
687
|
+
level: legacy.error ? 'warn' : 'info',
|
|
688
|
+
scope: 'migrate',
|
|
689
|
+
msg: 'migrate_brand',
|
|
690
|
+
data: { ...legacy },
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
if (home.mode !== 'none' || home.error) {
|
|
694
|
+
notes.push({
|
|
695
|
+
level: home.error ? 'warn' : 'info',
|
|
696
|
+
scope: 'migrate',
|
|
697
|
+
msg: 'migrate_home',
|
|
698
|
+
data: { ...home },
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
return notes;
|
|
702
|
+
}
|
|
479
703
|
main().catch((err) => {
|
|
480
704
|
process.stderr.write(`${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
481
705
|
process.exitCode = 1;
|
|
@@ -8,7 +8,10 @@ import { existsSync, statSync } from 'node:fs';
|
|
|
8
8
|
import { isAbsolute, resolve } from 'node:path';
|
|
9
9
|
import process from 'node:process';
|
|
10
10
|
import { clampThinkingLevel, clampTheme, isThemeName, LEGACY_DARK_THEME, THEME_NAMES, THINKING_LEVELS, } from '../config/schema.js';
|
|
11
|
+
import { MODE_LABEL, nextMode } from '../agent/agent-mode.js';
|
|
12
|
+
import { loadConfig } from '../config/load.js';
|
|
11
13
|
import { loadSession, resolveSessionPath, saveSession } from '../session/persist.js';
|
|
14
|
+
import { getLogger } from '../logging/logger.js';
|
|
12
15
|
const COMMANDS = [
|
|
13
16
|
{
|
|
14
17
|
name: 'help',
|
|
@@ -63,6 +66,30 @@ const COMMANDS = [
|
|
|
63
66
|
ctx.toast('success', `Theme set to ${name}.`);
|
|
64
67
|
},
|
|
65
68
|
},
|
|
69
|
+
{
|
|
70
|
+
name: 'plan',
|
|
71
|
+
description: 'Toggle plan mode (on | off | status)',
|
|
72
|
+
// Exists next to Shift+Tab for two reasons: a handful of terminal
|
|
73
|
+
// multiplexer and remote-desktop stacks never send `CSI Z` at all (R-P1),
|
|
74
|
+
// and slash commands are the discoverable surface — `/` opens the palette.
|
|
75
|
+
run: (ctx) => {
|
|
76
|
+
const arg = ctx.args.trim().toLowerCase();
|
|
77
|
+
if (arg === 'status') {
|
|
78
|
+
const s = ctx.controller.getPlanStatus();
|
|
79
|
+
const pending = s.pending ? ` (pending ${MODE_LABEL[s.pending]} after this run)` : '';
|
|
80
|
+
ctx.notify('info', `Mode: ${MODE_LABEL[s.effective]}${pending}. ` +
|
|
81
|
+
`Ask rounds used: ${s.askRoundsUsed}/${s.maxAskRounds}.`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (arg.length > 0 && arg !== 'on' && arg !== 'off') {
|
|
85
|
+
ctx.notify('warn', `Unknown argument "${arg}" - use /plan [on|off|status].`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const current = ctx.controller.getAgentMode();
|
|
89
|
+
const target = arg === 'on' ? 'plan' : arg === 'off' ? 'build' : nextMode(current);
|
|
90
|
+
ctx.applyAgentMode(target);
|
|
91
|
+
},
|
|
92
|
+
},
|
|
66
93
|
{
|
|
67
94
|
name: 'expand',
|
|
68
95
|
description: 'Expand / collapse a recent tool card (default: last)',
|
|
@@ -179,6 +206,39 @@ const COMMANDS = [
|
|
|
179
206
|
ctx.toast(ok ? 'success' : 'warn', ok ? 'Copied to clipboard.' : 'Clipboard not available.');
|
|
180
207
|
},
|
|
181
208
|
},
|
|
209
|
+
{
|
|
210
|
+
name: 'logs',
|
|
211
|
+
description: 'Show where this session is being logged',
|
|
212
|
+
run: (ctx) => {
|
|
213
|
+
const logger = getLogger();
|
|
214
|
+
const suffix = logger.sinkDisabled ? `\nLogging is OFF: ${logger.sinkError}` : '';
|
|
215
|
+
ctx.notify('info', `Log file: ${logger.currentLogPath()}\nLevel: ${logger.level}` +
|
|
216
|
+
' (debug/trace record prompt and tool content)' +
|
|
217
|
+
suffix);
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'reload',
|
|
222
|
+
description: 'Re-read config.json (after editing it in another window)',
|
|
223
|
+
run: (ctx) => {
|
|
224
|
+
// REFUSED WHILE RUNNING, and not out of caution: swapping provider or
|
|
225
|
+
// model mid-turn would let the second half of an answer be produced under
|
|
226
|
+
// different settings than the first, with nothing afterwards able to
|
|
227
|
+
// explain why that answer looks the way it does.
|
|
228
|
+
if (ctx.state.status === 'running') {
|
|
229
|
+
ctx.notify('warn', 'Cannot reload the config while a run is in progress.');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const config = loadConfig({ cwd: ctx.controller.getCwd() });
|
|
233
|
+
ctx.controller.setModel(config.provider, config.model, config.baseUrl);
|
|
234
|
+
ctx.controller.setThinkingLevel(config.thinkingLevel);
|
|
235
|
+
ctx.controller.setMaxTokens(config.maxTokens);
|
|
236
|
+
ctx.controller.setTheme(config.theme);
|
|
237
|
+
getLogger().reconfigure(config.log);
|
|
238
|
+
getLogger().info('config', 'reload', { provider: config.provider, model: config.model });
|
|
239
|
+
ctx.toast('success', `Reloaded config: ${config.provider}:${config.model}.`);
|
|
240
|
+
},
|
|
241
|
+
},
|
|
182
242
|
{
|
|
183
243
|
name: 'exit',
|
|
184
244
|
aliases: ['quit'],
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* input is routed here instead of to the agent; `//` is the literal-`/` escape.
|
|
4
4
|
*/
|
|
5
5
|
import type { AgentController } from '../agent/controller.js';
|
|
6
|
+
import type { AgentMode } from '../agent/agent-mode.js';
|
|
6
7
|
import type { NoticeLevel, Overlay, ToastLevel, ViewAction, ViewState } from '../agent/reducer.js';
|
|
7
8
|
import type { PersistedConfig } from '../config/schema.js';
|
|
8
9
|
export interface CommandContext {
|
|
@@ -27,6 +28,22 @@ export interface CommandContext {
|
|
|
27
28
|
submit: (text: string) => void;
|
|
28
29
|
/** Rebuild the system prompt and the dynamic command list after a skill change. */
|
|
29
30
|
refreshSkills: () => void;
|
|
31
|
+
/**
|
|
32
|
+
* Apply a mode change through the App's SINGLE write path (§3.1 / R-P7).
|
|
33
|
+
*
|
|
34
|
+
* `controller` and `dispatch` are both already in this context, so `/plan`
|
|
35
|
+
* COULD write the controller and dispatch the mirror itself — and that would
|
|
36
|
+
* be a second mode write path, which is the one thing the `setAgentMode`
|
|
37
|
+
* signature was shaped to forbid. One line here instead keeps `/plan` and
|
|
38
|
+
* `Shift+Tab` literally the same code.
|
|
39
|
+
*
|
|
40
|
+
* Returns the ADOPTED state, because `plan -> build` may be deferred and the
|
|
41
|
+
* command has to report what actually happened, not what it asked for.
|
|
42
|
+
*/
|
|
43
|
+
applyAgentMode: (next: AgentMode) => {
|
|
44
|
+
effective: AgentMode;
|
|
45
|
+
pending: AgentMode | null;
|
|
46
|
+
};
|
|
30
47
|
}
|
|
31
48
|
export interface SlashCommand {
|
|
32
49
|
name: string;
|
|
@@ -1,11 +1,78 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The user-state root, in one place.
|
|
3
3
|
*
|
|
4
4
|
* `config/store.ts` and `skills/paths.ts` each used to carry their own copy of
|
|
5
|
-
*
|
|
5
|
+
* the path arithmetic. Two copies of a name is exactly the shape that lets a
|
|
6
6
|
* rename land in one of them and not the other, and the failure is silent: the
|
|
7
7
|
* config file and the skills directory simply stop agreeing about where the
|
|
8
|
-
* user's data lives.
|
|
8
|
+
* user's data lives. Everything below derives from `homeRoot`.
|
|
9
|
+
*
|
|
10
|
+
* LAYOUT (aragon-home-config-and-logging §4.1):
|
|
11
|
+
*
|
|
12
|
+
* ~/.aragon-agent/
|
|
13
|
+
* ├── config.json persisted config (0600 on POSIX)
|
|
14
|
+
* ├── config.json.bak `aragon config edit` pre-edit backup
|
|
15
|
+
* ├── logs/ JSONL logs (0700 on POSIX)
|
|
16
|
+
* ├── sessions/ /save · /resume
|
|
17
|
+
* ├── skills/ `aragon skills install` target
|
|
18
|
+
* └── skill-usage.json
|
|
19
|
+
*
|
|
20
|
+
* The same path on all three platforms, deliberately. Claude Code, git and npm
|
|
21
|
+
* all put their dot-directory in the user's home; one path means documentation,
|
|
22
|
+
* support scripts and community answers have exactly one thing to say. A user
|
|
23
|
+
* who wants XDG has `ARAGON_HOME=$XDG_CONFIG_HOME/aragon-agent`.
|
|
24
|
+
*
|
|
25
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
26
|
+
* TEST-ISOLATION CONTRACT — read before editing anything here or in the tests.
|
|
27
|
+
*
|
|
28
|
+
* `getHomeRoot()` returns the WHOLE user root, not just a config directory.
|
|
29
|
+
* Under the old `env-paths` layout the config directory held one file; here it
|
|
30
|
+
* holds the API key, every saved session and every installed skill. That makes
|
|
31
|
+
* a careless `rmSync(<root>, { recursive: true })` in a test an unrecoverable
|
|
32
|
+
* data loss on the developer's own machine, at a moment when nobody expects the
|
|
33
|
+
* filesystem to be touched at all. Four rules keep that impossible:
|
|
34
|
+
*
|
|
35
|
+
* 1. There is NO `getConfigDir()`. A function whose name says "config dir"
|
|
36
|
+
* must never hand back the user root. Code that needs the root calls
|
|
37
|
+
* `getHomeRoot()`, where the size of what you are holding is obvious.
|
|
38
|
+
* 2. `config.test.ts::clearEnv()` must EXEMPT `ARAGON_HOME` — deleting it
|
|
39
|
+
* would send any later, non-module-level resolution at the developer's
|
|
40
|
+
* real home.
|
|
41
|
+
* 3. Tests never recursively delete a path a function returned. Delete a
|
|
42
|
+
* single file (`rmSync(getConfigPath(), { force: true })`), or delete a
|
|
43
|
+
* directory the test itself built under `os.tmpdir()`.
|
|
44
|
+
* 4. The VITEST branch below. In a test process with no explicit
|
|
45
|
+
* `ARAGON_HOME`, the root is redirected into `os.tmpdir()`. Redirecting
|
|
46
|
+
* rather than throwing is deliberate: throwing would turn every unrelated
|
|
47
|
+
* test that merely imports this module red, and that cost is what tempts
|
|
48
|
+
* someone to delete the guard.
|
|
49
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
9
50
|
*/
|
|
10
51
|
export declare const APP_NAME = "aragon-agent";
|
|
11
|
-
export declare const
|
|
52
|
+
export declare const HOME_DIR_NAME = ".aragon-agent";
|
|
53
|
+
/**
|
|
54
|
+
* The pre-0.6.0 `env-paths` roots.
|
|
55
|
+
*
|
|
56
|
+
* ONLY the two migration modules may read this. Any other reference is a bug:
|
|
57
|
+
* it would point new code at the directory the migration exists to empty.
|
|
58
|
+
*/
|
|
59
|
+
export declare const legacyEnvPaths: import("env-paths").Paths;
|
|
60
|
+
/** `~/.aragon-agent` (or `$ARAGON_HOME`). The root of everything below. */
|
|
61
|
+
export declare function getHomeRoot(): string;
|
|
62
|
+
export declare function getConfigPath(): string;
|
|
63
|
+
/** Written by `aragon config edit` before spawning the editor. */
|
|
64
|
+
export declare function getConfigBackupPath(): string;
|
|
65
|
+
export declare function getLogsDir(): string;
|
|
66
|
+
export declare function getSessionsDir(): string;
|
|
67
|
+
/** `<home>` — parent of `skills/`, and where sidecar state such as usage lives. */
|
|
68
|
+
export declare function getUserDataDir(): string;
|
|
69
|
+
export declare function getUserSkillsDir(): string;
|
|
70
|
+
/** Whether `ARAGON_HOME` was honoured. Captured at load — see `resolution`. */
|
|
71
|
+
export declare function isHomeOverridden(): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Why an `ARAGON_HOME` override was ignored, if it was.
|
|
74
|
+
*
|
|
75
|
+
* Path resolution happens before the logger exists, so the reason is parked
|
|
76
|
+
* here and written out by `installLogging()` once there is somewhere to put it.
|
|
77
|
+
*/
|
|
78
|
+
export declare function getHomeResolutionWarning(): string | undefined;
|