@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/config/app-paths.js
CHANGED
|
@@ -1,16 +1,147 @@
|
|
|
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
|
*/
|
|
51
|
+
import { existsSync, statSync } from 'node:fs';
|
|
52
|
+
import { homedir, tmpdir } from 'node:os';
|
|
53
|
+
import { join, resolve } from 'node:path';
|
|
54
|
+
import process from 'node:process';
|
|
10
55
|
import envPaths from 'env-paths';
|
|
11
56
|
export const APP_NAME = 'aragon-agent';
|
|
12
|
-
export const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
57
|
+
export const HOME_DIR_NAME = '.aragon-agent';
|
|
58
|
+
/**
|
|
59
|
+
* The pre-0.6.0 `env-paths` roots.
|
|
60
|
+
*
|
|
61
|
+
* ONLY the two migration modules may read this. Any other reference is a bug:
|
|
62
|
+
* it would point new code at the directory the migration exists to empty.
|
|
63
|
+
*/
|
|
64
|
+
export const legacyEnvPaths = envPaths(APP_NAME);
|
|
65
|
+
function isDirectory(path) {
|
|
66
|
+
try {
|
|
67
|
+
return statSync(path).isDirectory();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Where to land when `ARAGON_HOME` is absent or unusable.
|
|
75
|
+
*
|
|
76
|
+
* Inside vitest this is a per-pid temp root, never the developer's real home —
|
|
77
|
+
* rule 4 of the contract above, and it has to apply to the FALLBACK too, or a
|
|
78
|
+
* test that sets `ARAGON_HOME` to something bogus quietly gets the real one.
|
|
79
|
+
*/
|
|
80
|
+
function fallbackRoot() {
|
|
81
|
+
if (process.env.VITEST)
|
|
82
|
+
return join(tmpdir(), `aragon-agent-vitest-${process.pid}`);
|
|
83
|
+
return join(homedir(), HOME_DIR_NAME);
|
|
84
|
+
}
|
|
85
|
+
function resolveHomeRoot() {
|
|
86
|
+
const raw = process.env.ARAGON_HOME?.trim();
|
|
87
|
+
if (raw && raw.length > 0) {
|
|
88
|
+
const candidate = resolve(raw);
|
|
89
|
+
if (existsSync(candidate) && !isDirectory(candidate)) {
|
|
90
|
+
return {
|
|
91
|
+
root: fallbackRoot(),
|
|
92
|
+
overridden: false,
|
|
93
|
+
warning: `ARAGON_HOME=${candidate} is not a directory; using ${fallbackRoot()}`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { root: candidate, overridden: true };
|
|
97
|
+
}
|
|
98
|
+
return { root: fallbackRoot(), overridden: false };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Resolved ONCE at module load, matching what `envPaths()` did here before.
|
|
102
|
+
*
|
|
103
|
+
* The existing tests set their environment and then `await import(...)`; keeping
|
|
104
|
+
* the same model means test isolation stays "set the env first", not a new mock
|
|
105
|
+
* framework. It also means `isHomeOverridden()` must report the value captured
|
|
106
|
+
* here rather than re-reading `process.env` — `config.test.ts::clearEnv()` wipes
|
|
107
|
+
* `ARAGON_*` before every test, and a live read would answer `false` for a run
|
|
108
|
+
* that really is overridden.
|
|
109
|
+
*/
|
|
110
|
+
const resolution = resolveHomeRoot();
|
|
111
|
+
/** `~/.aragon-agent` (or `$ARAGON_HOME`). The root of everything below. */
|
|
112
|
+
export function getHomeRoot() {
|
|
113
|
+
return resolution.root;
|
|
114
|
+
}
|
|
115
|
+
export function getConfigPath() {
|
|
116
|
+
return join(resolution.root, 'config.json');
|
|
117
|
+
}
|
|
118
|
+
/** Written by `aragon config edit` before spawning the editor. */
|
|
119
|
+
export function getConfigBackupPath() {
|
|
120
|
+
return join(resolution.root, 'config.json.bak');
|
|
121
|
+
}
|
|
122
|
+
export function getLogsDir() {
|
|
123
|
+
return join(resolution.root, 'logs');
|
|
124
|
+
}
|
|
125
|
+
export function getSessionsDir() {
|
|
126
|
+
return join(resolution.root, 'sessions');
|
|
127
|
+
}
|
|
128
|
+
/** `<home>` — parent of `skills/`, and where sidecar state such as usage lives. */
|
|
129
|
+
export function getUserDataDir() {
|
|
130
|
+
return resolution.root;
|
|
131
|
+
}
|
|
132
|
+
export function getUserSkillsDir() {
|
|
133
|
+
return join(resolution.root, 'skills');
|
|
134
|
+
}
|
|
135
|
+
/** Whether `ARAGON_HOME` was honoured. Captured at load — see `resolution`. */
|
|
136
|
+
export function isHomeOverridden() {
|
|
137
|
+
return resolution.overridden;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Why an `ARAGON_HOME` override was ignored, if it was.
|
|
141
|
+
*
|
|
142
|
+
* Path resolution happens before the logger exists, so the reason is parked
|
|
143
|
+
* here and written out by `installLogging()` once there is somewhere to put it.
|
|
144
|
+
*/
|
|
145
|
+
export function getHomeResolutionWarning() {
|
|
146
|
+
return resolution.warning;
|
|
147
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aragon config get | list | edit | home`, plus the `log.*` branch of
|
|
3
|
+
* `config set`.
|
|
4
|
+
*
|
|
5
|
+
* These live here rather than in `cli.tsx` because `runConfigSet` is already a
|
|
6
|
+
* 21-case switch — well past the complexity ceiling this package writes down —
|
|
7
|
+
* and `cli.tsx` has two other designs queued up against it. `cli.tsx` keeps the
|
|
8
|
+
* wiring; the behaviour is here.
|
|
9
|
+
*/
|
|
10
|
+
import { type PersistedConfig } from './schema.js';
|
|
11
|
+
/** Config keys under `log.` that `aragon config set` accepts. */
|
|
12
|
+
export declare const LOG_CONFIG_SET_KEYS: readonly ["log.level", "log.toFile", "log.dir", "log.maxFileBytes", "log.maxFiles", "log.redactSecrets", "log.previewChars"];
|
|
13
|
+
/**
|
|
14
|
+
* Translate a `log.*` dotted key into a patch, or `null` for anything else.
|
|
15
|
+
*
|
|
16
|
+
* The patch is a PARTIAL `log` section, which is safe only because
|
|
17
|
+
* `updatePersistedConfig` deep-merges that section — with a shallow merge each
|
|
18
|
+
* of these would silently reset every other log setting, `redactSecrets`
|
|
19
|
+
* included.
|
|
20
|
+
*/
|
|
21
|
+
export declare function applyLogConfigSet(key: string, value: string): Partial<PersistedConfig> | null;
|
|
22
|
+
/**
|
|
23
|
+
* `log.redactSecrets false` is the one setting that lets credentials reach the
|
|
24
|
+
* disk. It is command-line-only (there is no settings-screen entry) and it says
|
|
25
|
+
* so out loud, because a user who turns it off and forgets is the user who
|
|
26
|
+
* later pastes a log into an issue.
|
|
27
|
+
*/
|
|
28
|
+
export declare function warnIfRedactionDisabled(key: string, value: string): void;
|
|
29
|
+
export declare function runConfigGet(key: string): void;
|
|
30
|
+
export declare function runConfigList(opts?: {
|
|
31
|
+
json?: boolean;
|
|
32
|
+
}): void;
|
|
33
|
+
/**
|
|
34
|
+
* Open `config.json` in the user's editor.
|
|
35
|
+
*
|
|
36
|
+
* Backs the file up FIRST and re-parses afterwards. Hand-editing is a supported
|
|
37
|
+
* workflow here, and a stray comma otherwise resets every setting the next time
|
|
38
|
+
* the CLI starts, with nothing connecting the two events. Telling the user
|
|
39
|
+
* immediately — and naming the backup — turns a confusing regression into a
|
|
40
|
+
* one-command recovery.
|
|
41
|
+
*/
|
|
42
|
+
export declare function runConfigEdit(): Promise<void>;
|
|
43
|
+
export declare function runConfigHome(): void;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aragon config get | list | edit | home`, plus the `log.*` branch of
|
|
3
|
+
* `config set`.
|
|
4
|
+
*
|
|
5
|
+
* These live here rather than in `cli.tsx` because `runConfigSet` is already a
|
|
6
|
+
* 21-case switch — well past the complexity ceiling this package writes down —
|
|
7
|
+
* and `cli.tsx` has two other designs queued up against it. `cli.tsx` keeps the
|
|
8
|
+
* wiring; the behaviour is here.
|
|
9
|
+
*/
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { copyFileSync, existsSync, readFileSync } from 'node:fs';
|
|
12
|
+
import process from 'node:process';
|
|
13
|
+
import { getConfigBackupPath, getConfigPath, getHomeRoot, getLogsDir, isHomeOverridden, } from './app-paths.js';
|
|
14
|
+
import { getHomeMigrationBreadcrumbPath } from './migrate-home.js';
|
|
15
|
+
import { loadPersistedConfig, updatePersistedConfig } from './store.js';
|
|
16
|
+
import { coercePositiveInt, maskSecret, } from './schema.js';
|
|
17
|
+
import { clampLogLevel } from '../logging/levels.js';
|
|
18
|
+
/** Config keys under `log.` that `aragon config set` accepts. */
|
|
19
|
+
export const LOG_CONFIG_SET_KEYS = [
|
|
20
|
+
'log.level',
|
|
21
|
+
'log.toFile',
|
|
22
|
+
'log.dir',
|
|
23
|
+
'log.maxFileBytes',
|
|
24
|
+
'log.maxFiles',
|
|
25
|
+
'log.redactSecrets',
|
|
26
|
+
'log.previewChars',
|
|
27
|
+
];
|
|
28
|
+
function isTrue(value) {
|
|
29
|
+
return value === 'true' || value === '1';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Translate a `log.*` dotted key into a patch, or `null` for anything else.
|
|
33
|
+
*
|
|
34
|
+
* The patch is a PARTIAL `log` section, which is safe only because
|
|
35
|
+
* `updatePersistedConfig` deep-merges that section — with a shallow merge each
|
|
36
|
+
* of these would silently reset every other log setting, `redactSecrets`
|
|
37
|
+
* included.
|
|
38
|
+
*/
|
|
39
|
+
export function applyLogConfigSet(key, value) {
|
|
40
|
+
const patch = (log) => ({ log });
|
|
41
|
+
switch (key) {
|
|
42
|
+
case 'log.level':
|
|
43
|
+
return patch({ level: clampLogLevel(value, 'info') });
|
|
44
|
+
case 'log.toFile':
|
|
45
|
+
return patch({ toFile: isTrue(value) });
|
|
46
|
+
case 'log.dir':
|
|
47
|
+
return patch({ dir: value.trim() });
|
|
48
|
+
case 'log.maxFileBytes':
|
|
49
|
+
return patch({ maxFileBytes: coercePositiveInt(value, 5 * 1024 * 1024) });
|
|
50
|
+
case 'log.maxFiles':
|
|
51
|
+
return patch({ maxFiles: coercePositiveInt(value, 10) });
|
|
52
|
+
case 'log.redactSecrets':
|
|
53
|
+
return patch({ redactSecrets: isTrue(value) });
|
|
54
|
+
case 'log.previewChars':
|
|
55
|
+
return patch({ previewChars: Number.parseInt(value, 10) });
|
|
56
|
+
default:
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* `log.redactSecrets false` is the one setting that lets credentials reach the
|
|
62
|
+
* disk. It is command-line-only (there is no settings-screen entry) and it says
|
|
63
|
+
* so out loud, because a user who turns it off and forgets is the user who
|
|
64
|
+
* later pastes a log into an issue.
|
|
65
|
+
*/
|
|
66
|
+
export function warnIfRedactionDisabled(key, value) {
|
|
67
|
+
if (key !== 'log.redactSecrets' || isTrue(value))
|
|
68
|
+
return;
|
|
69
|
+
process.stdout.write('WARNING: secret redaction is OFF. API keys may now be written to the log files ' +
|
|
70
|
+
`in ${getLogsDir()}. Re-enable with: aragon config set log.redactSecrets true\n`);
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// get / list
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
/** Every secret is masked wherever it is printed, `--json` included (R-12). */
|
|
76
|
+
function maskConfig(config) {
|
|
77
|
+
const apiKeys = {};
|
|
78
|
+
for (const [provider, key] of Object.entries(config.apiKeys)) {
|
|
79
|
+
apiKeys[provider] = maskSecret(key);
|
|
80
|
+
}
|
|
81
|
+
return { ...config, apiKeys };
|
|
82
|
+
}
|
|
83
|
+
/** Resolve a flat or one-level-dotted key against the persisted config. */
|
|
84
|
+
function readKey(config, key) {
|
|
85
|
+
const [head, tail] = key.split('.', 2);
|
|
86
|
+
const top = config[head];
|
|
87
|
+
if (tail === undefined)
|
|
88
|
+
return top;
|
|
89
|
+
if (!top || typeof top !== 'object')
|
|
90
|
+
return undefined;
|
|
91
|
+
return top[tail];
|
|
92
|
+
}
|
|
93
|
+
export function runConfigGet(key) {
|
|
94
|
+
const masked = maskConfig(loadPersistedConfig());
|
|
95
|
+
const value = readKey(masked, key);
|
|
96
|
+
if (value === undefined) {
|
|
97
|
+
process.stderr.write(`Unknown config key "${key}".\n`);
|
|
98
|
+
process.exitCode = 2;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
process.stdout.write(`${typeof value === 'string' ? value : JSON.stringify(value)}\n`);
|
|
102
|
+
}
|
|
103
|
+
export function runConfigList(opts = {}) {
|
|
104
|
+
const masked = maskConfig(loadPersistedConfig());
|
|
105
|
+
if (opts.json) {
|
|
106
|
+
process.stdout.write(`${JSON.stringify(masked, null, 2)}\n`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
for (const [key, value] of Object.entries(masked)) {
|
|
110
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
111
|
+
for (const [sub, subValue] of Object.entries(value)) {
|
|
112
|
+
process.stdout.write(`${key}.${sub} = ${JSON.stringify(subValue)}\n`);
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
process.stdout.write(`${key} = ${JSON.stringify(value)}\n`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// edit / home
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
function pickEditor() {
|
|
123
|
+
const configured = process.env.VISUAL?.trim() || process.env.EDITOR?.trim();
|
|
124
|
+
if (configured)
|
|
125
|
+
return configured;
|
|
126
|
+
return process.platform === 'win32' ? 'notepad' : 'vi';
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Open `config.json` in the user's editor.
|
|
130
|
+
*
|
|
131
|
+
* Backs the file up FIRST and re-parses afterwards. Hand-editing is a supported
|
|
132
|
+
* workflow here, and a stray comma otherwise resets every setting the next time
|
|
133
|
+
* the CLI starts, with nothing connecting the two events. Telling the user
|
|
134
|
+
* immediately — and naming the backup — turns a confusing regression into a
|
|
135
|
+
* one-command recovery.
|
|
136
|
+
*/
|
|
137
|
+
export async function runConfigEdit() {
|
|
138
|
+
const file = getConfigPath();
|
|
139
|
+
if (!existsSync(file)) {
|
|
140
|
+
// Materialise defaults so the editor has something to show.
|
|
141
|
+
updatePersistedConfig({});
|
|
142
|
+
}
|
|
143
|
+
const backup = getConfigBackupPath();
|
|
144
|
+
try {
|
|
145
|
+
copyFileSync(file, backup);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
149
|
+
process.stderr.write(`Could not write ${backup}: ${reason}\n`);
|
|
150
|
+
process.exitCode = 1;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const editor = pickEditor();
|
|
154
|
+
// A shell is needed on Windows so an `EDITOR` pointing at a `.cmd` / `.bat`
|
|
155
|
+
// launcher (`code.cmd`, most of them) still runs — but a shell re-splits the
|
|
156
|
+
// command line, so the path has to be quoted or `C:\Users\Jane Doe\...` would
|
|
157
|
+
// reach the editor as two arguments and open two empty buffers.
|
|
158
|
+
const useShell = process.platform === 'win32';
|
|
159
|
+
const target = useShell ? `"${file}"` : file;
|
|
160
|
+
const result = spawnSync(editor, [target], { stdio: 'inherit', shell: useShell });
|
|
161
|
+
if (result.error) {
|
|
162
|
+
process.stderr.write(`Could not launch "${editor}": ${result.error.message}\n`);
|
|
163
|
+
process.exitCode = 1;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
JSON.parse(readFileSync(file, 'utf-8'));
|
|
168
|
+
process.stdout.write(`${file} saved.\n`);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
process.stderr.write(`${file} is not valid JSON (${err instanceof Error ? err.message : String(err)}).\n` +
|
|
172
|
+
`The previous version is still at ${backup}.\n`);
|
|
173
|
+
process.exitCode = 1;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
export function runConfigHome() {
|
|
177
|
+
process.stdout.write(`${getHomeRoot()}\n`);
|
|
178
|
+
if (isHomeOverridden())
|
|
179
|
+
process.stdout.write(' (overridden by ARAGON_HOME)\n');
|
|
180
|
+
const breadcrumb = getHomeMigrationBreadcrumbPath();
|
|
181
|
+
if (!existsSync(breadcrumb))
|
|
182
|
+
return;
|
|
183
|
+
// The env-paths directories are left in place on purpose so reinstalling an
|
|
184
|
+
// older build stays lossless; pointing at them is how the user knows they can
|
|
185
|
+
// be deleted once the new location has proved itself.
|
|
186
|
+
try {
|
|
187
|
+
const parsed = JSON.parse(readFileSync(breadcrumb, 'utf-8'));
|
|
188
|
+
if (parsed.from?.config)
|
|
189
|
+
process.stdout.write(` previously: ${parsed.from.config}\n`);
|
|
190
|
+
if (parsed.from?.data)
|
|
191
|
+
process.stdout.write(` previously: ${parsed.from.data}\n`);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// A damaged breadcrumb is a lost diagnostic, not an error worth reporting.
|
|
195
|
+
}
|
|
196
|
+
}
|
package/dist/config/env.d.ts
CHANGED
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
* - Provider secrets: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY /
|
|
6
6
|
* GEMINI_API_KEY.
|
|
7
7
|
* - Overrides: ARAGON_PROVIDER, ARAGON_MODEL, ARAGON_BASE_URL, ARAGON_THINKING,
|
|
8
|
-
* ARAGON_MAX_TOKENS, ARAGON_THEME
|
|
8
|
+
* ARAGON_MAX_TOKENS, ARAGON_THEME, ARAGON_MOUSE, ARAGON_PLAN,
|
|
9
|
+
* ARAGON_LOG_LEVEL, ARAGON_LOG_FILE, ARAGON_LOG_DIR.
|
|
10
|
+
*
|
|
11
|
+
* NOT here: `ARAGON_HOME`. It is read when `config/app-paths.ts` is first
|
|
12
|
+
* evaluated, which is long before `loadDotenv()` runs, so a `.env` can never
|
|
13
|
+
* supply it. README and §5.2 of the design say so explicitly — otherwise "I set
|
|
14
|
+
* ARAGON_HOME in .env and it did nothing" is an unexplainable bug report.
|
|
9
15
|
*/
|
|
10
16
|
import { type PersistedConfig } from './schema.js';
|
|
11
17
|
/**
|
package/dist/config/env.js
CHANGED
|
@@ -5,11 +5,19 @@
|
|
|
5
5
|
* - Provider secrets: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY /
|
|
6
6
|
* GEMINI_API_KEY.
|
|
7
7
|
* - Overrides: ARAGON_PROVIDER, ARAGON_MODEL, ARAGON_BASE_URL, ARAGON_THINKING,
|
|
8
|
-
* ARAGON_MAX_TOKENS, ARAGON_THEME
|
|
8
|
+
* ARAGON_MAX_TOKENS, ARAGON_THEME, ARAGON_MOUSE, ARAGON_PLAN,
|
|
9
|
+
* ARAGON_LOG_LEVEL, ARAGON_LOG_FILE, ARAGON_LOG_DIR.
|
|
10
|
+
*
|
|
11
|
+
* NOT here: `ARAGON_HOME`. It is read when `config/app-paths.ts` is first
|
|
12
|
+
* evaluated, which is long before `loadDotenv()` runs, so a `.env` can never
|
|
13
|
+
* supply it. README and §5.2 of the design say so explicitly — otherwise "I set
|
|
14
|
+
* ARAGON_HOME in .env and it did nothing" is an unexplainable bug report.
|
|
9
15
|
*/
|
|
10
16
|
import { join } from 'node:path';
|
|
11
17
|
import process from 'node:process';
|
|
12
18
|
import dotenv from 'dotenv';
|
|
19
|
+
import { registerSecretsFrom } from '../logging/secret-registry.js';
|
|
20
|
+
import { clampLogLevel } from '../logging/levels.js';
|
|
13
21
|
import { clampTheme, clampThinkingLevel, coerceMaxTokens, } from './schema.js';
|
|
14
22
|
/**
|
|
15
23
|
* Load a project `.env` from the given directory into `process.env` (existing
|
|
@@ -63,6 +71,20 @@ export function readEnvConfig() {
|
|
|
63
71
|
if (fullscreen !== undefined && fullscreen.length > 0) {
|
|
64
72
|
partial.fullscreen = fullscreen === '1' || fullscreen === 'true' || fullscreen === 'on' || fullscreen === 'yes';
|
|
65
73
|
}
|
|
74
|
+
// ARAGON_MOUSE=0 turns off wheel region routing. Parsed by the POSITIVE list
|
|
75
|
+
// above, exactly as ARAGON_FULLSCREEN is — NOT by `envBool` below, which uses
|
|
76
|
+
// a negative list. The two disagree on `ARAGON_MOUSE=disable`, and `mouse` is
|
|
77
|
+
// modelled on `--fullscreen`, so it uses the reader `--fullscreen` uses.
|
|
78
|
+
const mouse = process.env.ARAGON_MOUSE?.trim().toLowerCase();
|
|
79
|
+
if (mouse !== undefined && mouse.length > 0) {
|
|
80
|
+
partial.mouse = mouse === '1' || mouse === 'true' || mouse === 'on' || mouse === 'yes';
|
|
81
|
+
}
|
|
82
|
+
// ARAGON_PLAN=1 starts the session in PLAN mode — the same channel story as
|
|
83
|
+
// ARAGON_FULLSCREEN, for terminals reached through a wrapper that owns argv.
|
|
84
|
+
const plan = process.env.ARAGON_PLAN?.trim().toLowerCase();
|
|
85
|
+
if (plan !== undefined && plan.length > 0) {
|
|
86
|
+
partial.planModeDefault = plan === '1' || plan === 'true' || plan === 'on' || plan === 'yes';
|
|
87
|
+
}
|
|
66
88
|
// ARAGON_SKILLS=0 is the kill switch for the whole skill subsystem; the other
|
|
67
89
|
// two skill env vars (ARAGON_SKILLS_PATH, ARAGON_SKILLS_DISABLED) are read where
|
|
68
90
|
// they are used — the search-root resolver and the service respectively —
|
|
@@ -74,12 +96,42 @@ export function readEnvConfig() {
|
|
|
74
96
|
enabled: !(skills === '0' || skills === 'false' || skills === 'off' || skills === 'no'),
|
|
75
97
|
};
|
|
76
98
|
}
|
|
99
|
+
const log = readEnvLogConfig();
|
|
100
|
+
if (log)
|
|
101
|
+
partial.log = log;
|
|
77
102
|
const apiKeys = {};
|
|
78
103
|
for (const [prov, names] of Object.entries(PROVIDER_ENV_KEYS)) {
|
|
79
104
|
apiKeys[prov] = firstEnv(names);
|
|
80
105
|
}
|
|
106
|
+
// Registration site 2 (§4.4.3). A key that only ever lives in the environment
|
|
107
|
+
// never passes through `updatePersistedConfig`, so this is its only chance to
|
|
108
|
+
// reach the redactor's backstop.
|
|
109
|
+
registerSecretsFrom(apiKeys);
|
|
81
110
|
return { partial, apiKeys };
|
|
82
111
|
}
|
|
112
|
+
/** Interpret `0/false/off/no` as false; anything else non-empty as true. */
|
|
113
|
+
function envBool(value) {
|
|
114
|
+
const v = value.trim().toLowerCase();
|
|
115
|
+
return !(v === '0' || v === 'false' || v === 'off' || v === 'no');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Assemble the `log` section from the environment, or `undefined` when the user
|
|
119
|
+
* said nothing about it. Same shape as the `partial.skills` assembly above: a
|
|
120
|
+
* PARTIAL section, safe only because every consumer deep-merges it.
|
|
121
|
+
*/
|
|
122
|
+
function readEnvLogConfig() {
|
|
123
|
+
const out = {};
|
|
124
|
+
const level = process.env.ARAGON_LOG_LEVEL?.trim();
|
|
125
|
+
if (level)
|
|
126
|
+
out.level = clampLogLevel(level, 'info');
|
|
127
|
+
const toFile = process.env.ARAGON_LOG_FILE?.trim();
|
|
128
|
+
if (toFile !== undefined && toFile.length > 0)
|
|
129
|
+
out.toFile = envBool(toFile);
|
|
130
|
+
const dir = process.env.ARAGON_LOG_DIR?.trim();
|
|
131
|
+
if (dir)
|
|
132
|
+
out.dir = dir;
|
|
133
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
134
|
+
}
|
|
83
135
|
/** Skill names disabled for this run via `ARAGON_SKILLS_DISABLED` (comma-separated). */
|
|
84
136
|
export function readEnvDisabledSkills() {
|
|
85
137
|
const raw = process.env.ARAGON_SKILLS_DISABLED;
|
package/dist/config/load.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface CliFlags {
|
|
|
27
27
|
compact?: boolean;
|
|
28
28
|
/** `--hints` ⇒ true, `--no-hints` ⇒ false, absent ⇒ undefined. */
|
|
29
29
|
hints?: boolean;
|
|
30
|
+
/** `--mouse` ⇒ true, `--no-mouse` ⇒ false, absent ⇒ undefined. */
|
|
31
|
+
mouse?: boolean;
|
|
30
32
|
/** `--no-skills` ⇒ false. Turns the whole skill subsystem off (§7.5). */
|
|
31
33
|
skills?: boolean;
|
|
32
34
|
/** `--skill <name>` ×N — force Level 2 injection for this run. */
|
|
@@ -35,6 +37,14 @@ export interface CliFlags {
|
|
|
35
37
|
skillsYes?: boolean;
|
|
36
38
|
/** `--skill-tool-policy <mode>` — override the tool ceiling for this run (§5.5). */
|
|
37
39
|
skillToolPolicy?: string;
|
|
40
|
+
/** `--plan` ⇒ true, `--no-plan` ⇒ false, absent ⇒ undefined (plan-mode §4.4). */
|
|
41
|
+
plan?: boolean;
|
|
42
|
+
/** `--log-level <lv>` — this run only, never persisted. */
|
|
43
|
+
logLevel?: string;
|
|
44
|
+
/** `--log-file` ⇒ true, `--no-log-file` ⇒ false, absent ⇒ undefined. */
|
|
45
|
+
logFile?: boolean;
|
|
46
|
+
/** `--log-dir <dir>` — this run only. */
|
|
47
|
+
logDir?: string;
|
|
38
48
|
}
|
|
39
49
|
/**
|
|
40
50
|
* Resolve the effective config by merging all layers, then derive the runtime
|
package/dist/config/load.js
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
* legitimately long single tool run can never be watchdog-killed.
|
|
7
7
|
*/
|
|
8
8
|
import process from 'node:process';
|
|
9
|
-
import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampDensity, clampSkillsConfig, clampTheme, clampThinkingLevel, clampTranscriptWindow, coerceMaxTokens, coercePositiveInt, isSkillsToolPolicyMode, } from './schema.js';
|
|
9
|
+
import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampLogConfig, clampSkillsConfig, clampTheme, clampThinkingLevel, clampTranscriptWindow, coerceMaxTokens, coercePositiveInt, isSkillsToolPolicyMode, } from './schema.js';
|
|
10
10
|
import { loadDotenv, readEnvConfig, readEnvDisabledSkills } from './env.js';
|
|
11
11
|
import { readConfigFile } from './store.js';
|
|
12
|
+
import { getLogger } from '../logging/logger.js';
|
|
13
|
+
import { registerSecret, registerSecretsFrom } from '../logging/secret-registry.js';
|
|
12
14
|
import { detectCapabilities } from '../ui/capabilities.js';
|
|
13
15
|
/** Interpret a boolean-ish env value (`1/true/on/yes`) as `true`. */
|
|
14
16
|
function envFlagTrue(value) {
|
|
@@ -31,6 +33,57 @@ function resolveFullscreen(flags, env, file) {
|
|
|
31
33
|
return false;
|
|
32
34
|
return undefined;
|
|
33
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolve wheel region routing: flag › env › file › default `true`.
|
|
38
|
+
*
|
|
39
|
+
* THIS NEEDS ITS OWN RESOLVER; it does not "match every other boolean" in this
|
|
40
|
+
* file. `hints` is `flags.hints !== undefined ? … : file.hints ?? DEFAULT` and
|
|
41
|
+
* reads NO ENV AT ALL — copying that line would ship `ARAGON_MOUSE` as
|
|
42
|
+
* documented-but-dead (P1-9). Only `fullscreen` has an env layer, and only
|
|
43
|
+
* because `resolveFullscreen` above was written to give it one.
|
|
44
|
+
*
|
|
45
|
+
* Unlike `fullscreen` this is a plain boolean rather than a tri-state: there
|
|
46
|
+
* are no heuristics for an explicit `true` to override, so a persisted `true`
|
|
47
|
+
* is just a value and not an opinion that has to be told apart from silence.
|
|
48
|
+
*/
|
|
49
|
+
function resolveMouse(flags, env, file) {
|
|
50
|
+
if (flags.mouse !== undefined)
|
|
51
|
+
return flags.mouse;
|
|
52
|
+
if (env.mouse !== undefined)
|
|
53
|
+
return env.mouse;
|
|
54
|
+
if (file.mouse !== undefined)
|
|
55
|
+
return file.mouse;
|
|
56
|
+
return DEFAULT_CONFIG.mouse;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolve whether logs reach a file: flag › env › an explicit `false` in the
|
|
60
|
+
* file › default `true`.
|
|
61
|
+
*
|
|
62
|
+
* Shaped exactly like `resolveFullscreen` above, and for the same reason: the
|
|
63
|
+
* config file's DEFAULT `true` must not be mistaken for an opinion, or
|
|
64
|
+
* `--no-log-file` would be the only way to ever turn logging off and a stored
|
|
65
|
+
* `toFile: false` would be unreachable.
|
|
66
|
+
*/
|
|
67
|
+
function resolveLogToFile(flags, env, file) {
|
|
68
|
+
if (flags.logFile !== undefined)
|
|
69
|
+
return flags.logFile;
|
|
70
|
+
if (env.log?.toFile !== undefined)
|
|
71
|
+
return env.log.toFile;
|
|
72
|
+
if (file.log?.toFile === false)
|
|
73
|
+
return false;
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
/** Merge the `log` section across all four layers (defaults › file › env › flags). */
|
|
77
|
+
function resolveLogConfig(flags, env, file) {
|
|
78
|
+
const merged = clampLogConfig({
|
|
79
|
+
...DEFAULT_CONFIG.log,
|
|
80
|
+
...(file.log ?? {}),
|
|
81
|
+
...(env.log ?? {}),
|
|
82
|
+
...(flags.logLevel ? { level: flags.logLevel } : {}),
|
|
83
|
+
...(flags.logDir ? { dir: flags.logDir } : {}),
|
|
84
|
+
});
|
|
85
|
+
return { ...merged, toFile: resolveLogToFile(flags, env, file) };
|
|
86
|
+
}
|
|
34
87
|
// ---------------------------------------------------------------------------
|
|
35
88
|
// loadConfig
|
|
36
89
|
// ---------------------------------------------------------------------------
|
|
@@ -49,8 +102,18 @@ export function loadConfig(flags = {}) {
|
|
|
49
102
|
const cwd = flags.cwd ? flags.cwd : process.cwd();
|
|
50
103
|
// `.env` must be loaded before reading env overrides.
|
|
51
104
|
loadDotenv(cwd);
|
|
52
|
-
const
|
|
105
|
+
const read = readConfigFile();
|
|
106
|
+
const file = read.config ?? {};
|
|
53
107
|
const env = readEnvConfig();
|
|
108
|
+
// A config file that exists but will not parse used to reset EVERY setting
|
|
109
|
+
// with nothing anywhere explaining it. The fallback is unchanged; what is new
|
|
110
|
+
// is that it leaves a trace. The record alone is not the explanation, so the
|
|
111
|
+
// two owners of a user-visible channel say it too: `main()` writes one stderr
|
|
112
|
+
// line (headless, inline and every subcommand) and `App.tsx` raises a toast
|
|
113
|
+
// (full-screen, where the alternate screen wipes that line away).
|
|
114
|
+
if (read.parseError) {
|
|
115
|
+
getLogger().error('config', 'config_parse_failed', { error: read.parseError });
|
|
116
|
+
}
|
|
54
117
|
const provider = pick(flags.provider, env.partial.provider, file.provider) ?? DEFAULT_CONFIG.provider;
|
|
55
118
|
const model = pick(flags.model, env.partial.model, file.model) ?? DEFAULT_CONFIG.model;
|
|
56
119
|
const baseUrl = pick(flags.baseUrl, env.partial.baseUrl ?? undefined, file.baseUrl ?? undefined);
|
|
@@ -66,6 +129,13 @@ export function loadConfig(flags = {}) {
|
|
|
66
129
|
: clampDensity(file.density, DEFAULT_CONFIG.density);
|
|
67
130
|
const hints = flags.hints !== undefined ? flags.hints : file.hints ?? DEFAULT_CONFIG.hints;
|
|
68
131
|
const confirmTools = flags.confirm !== undefined ? flags.confirm : file.confirmTools ?? DEFAULT_CONFIG.confirmTools;
|
|
132
|
+
// Plan mode: defaults -> file -> env -> flags, the same order as every other
|
|
133
|
+
// field. `pick` keeps an explicit `false`, so `--no-plan` really does override
|
|
134
|
+
// a config file that turned plan mode on.
|
|
135
|
+
const startInPlanMode = pick(flags.plan, env.partial.planModeDefault, file.planModeDefault) ??
|
|
136
|
+
DEFAULT_CONFIG.planModeDefault;
|
|
137
|
+
const planModeMaxAskRounds = clampAskRounds(file.planModeMaxAskRounds, DEFAULT_CONFIG.planModeMaxAskRounds);
|
|
138
|
+
const planModeHumanTimeoutMs = clampHumanTimeout(file.planModeHumanTimeoutMs, DEFAULT_CONFIG.planModeHumanTimeoutMs);
|
|
69
139
|
const toolTimeoutMs = coercePositiveInt(pick(flags.toolTimeout, file.toolTimeoutMs), DEFAULT_CONFIG.toolTimeoutMs);
|
|
70
140
|
let idleTimeoutMs = coercePositiveInt(pick(flags.idleTimeout, file.idleTimeoutMs), DEFAULT_CONFIG.idleTimeoutMs);
|
|
71
141
|
// Timeout invariant (R1): idle must be strictly greater than the tool ceiling
|
|
@@ -95,6 +165,11 @@ export function loadConfig(flags = {}) {
|
|
|
95
165
|
...new Set([...(file.skills?.disabled ?? []), ...readEnvDisabledSkills()]),
|
|
96
166
|
],
|
|
97
167
|
});
|
|
168
|
+
// Registration site 3 (§4.4.3): the merged view plus the one-shot `--api-key`,
|
|
169
|
+
// which exists nowhere else. Overlaps with sites 1 and 2 on purpose —
|
|
170
|
+
// registering twice costs a `Set.add`, missing once costs a credential.
|
|
171
|
+
registerSecretsFrom(apiKeys);
|
|
172
|
+
registerSecret(flags.apiKey);
|
|
98
173
|
const color = flags.color !== undefined ? flags.color : !process.env.NO_COLOR;
|
|
99
174
|
// `--no-color` implies calmer chrome; env / file may also opt in explicitly.
|
|
100
175
|
const reducedMotion = envFlagTrue(process.env.ARAGON_REDUCED_MOTION) ||
|
|
@@ -124,7 +199,11 @@ export function loadConfig(flags = {}) {
|
|
|
124
199
|
promptHistory: file.promptHistory ?? [],
|
|
125
200
|
density,
|
|
126
201
|
hints,
|
|
202
|
+
mouse: resolveMouse(flags, env.partial, file),
|
|
127
203
|
submitCount: coercePositiveInt(file.submitCount, 0),
|
|
204
|
+
startInPlanMode,
|
|
205
|
+
planModeMaxAskRounds,
|
|
206
|
+
planModeHumanTimeoutMs,
|
|
128
207
|
skills,
|
|
129
208
|
skillsRuntime: {
|
|
130
209
|
forcedSkills: flags.skill ?? [],
|
|
@@ -137,6 +216,7 @@ export function loadConfig(flags = {}) {
|
|
|
137
216
|
? { toolPolicy: flags.skillToolPolicy }
|
|
138
217
|
: {}),
|
|
139
218
|
},
|
|
219
|
+
log: resolveLogConfig(flags, env.partial, file),
|
|
140
220
|
cwd,
|
|
141
221
|
color,
|
|
142
222
|
colorLevel,
|