monomind 2.0.3 → 2.1.3
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/README.md +74 -92
- package/package.json +11 -7
- package/packages/@monomind/cli/.claude/helpers/control-start.cjs +13 -20
- package/packages/@monomind/cli/.claude/helpers/event-logger.cjs +109 -6
- package/packages/@monomind/cli/.claude/helpers/graphify-freshen.cjs +19 -17
- package/packages/@monomind/cli/.claude/helpers/handlers/agent-start-handler.cjs +60 -28
- package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +99 -28
- package/packages/@monomind/cli/.claude/helpers/handlers/edit-handler.cjs +27 -9
- package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +18 -9
- package/packages/@monomind/cli/.claude/helpers/handlers/loops-status-handler.cjs +1 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +33 -4
- package/packages/@monomind/cli/.claude/helpers/handlers/session-handler.cjs +75 -45
- package/packages/@monomind/cli/.claude/helpers/handlers/session-restore-handler.cjs +0 -12
- package/packages/@monomind/cli/.claude/helpers/handlers/task-handler.cjs +53 -22
- package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +75 -4
- package/packages/@monomind/cli/.claude/helpers/intelligence.cjs +94 -15
- package/packages/@monomind/cli/.claude/helpers/session.cjs +6 -1
- package/packages/@monomind/cli/.claude/helpers/statusline.cjs +10 -10
- package/packages/@monomind/cli/.claude/helpers/token-tracker.cjs +3 -3
- package/packages/@monomind/cli/.claude/helpers/utils/agent-registrations.cjs +41 -0
- package/packages/@monomind/cli/.claude/helpers/utils/fs-helpers.cjs +183 -0
- package/packages/@monomind/cli/.claude/helpers/utils/micro-agents.cjs +10 -6
- package/packages/@monomind/cli/.claude/helpers/utils/monograph.cjs +73 -2
- package/packages/@monomind/cli/.claude/helpers/utils/telemetry.cjs +35 -19
- package/packages/@monomind/cli/README.md +74 -92
- package/packages/@monomind/cli/bin/cli.js +10 -1
- package/packages/@monomind/cli/dist/src/browser/workflow/store.d.ts +2 -2
- package/packages/@monomind/cli/dist/src/commands/agent-ops.js +9 -15
- package/packages/@monomind/cli/dist/src/commands/browse.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +20 -4
- package/packages/@monomind/cli/dist/src/commands/completions.js +66 -261
- package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.js +5 -0
- package/packages/@monomind/cli/dist/src/commands/doctor.js +7 -2
- package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +7 -0
- package/packages/@monomind/cli/dist/src/commands/index.js +3 -0
- package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -6
- package/packages/@monomind/cli/dist/src/commands/memory-admin.js +12 -6
- package/packages/@monomind/cli/dist/src/commands/memory-transfer.js +6 -13
- package/packages/@monomind/cli/dist/src/commands/monograph.js +3 -3
- package/packages/@monomind/cli/dist/src/commands/org.d.ts +3 -1
- package/packages/@monomind/cli/dist/src/commands/org.js +321 -127
- package/packages/@monomind/cli/dist/src/commands/platforms.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/commands/start.js +142 -37
- package/packages/@monomind/cli/dist/src/commands/tokens.js +21 -3
- package/packages/@monomind/cli/dist/src/config-adapter.js +37 -17
- package/packages/@monomind/cli/dist/src/index.js +64 -28
- package/packages/@monomind/cli/dist/src/init/claudemd-generator.js +44 -11
- package/packages/@monomind/cli/dist/src/init/executor.js +93 -55
- package/packages/@monomind/cli/dist/src/mcp-client.d.ts +8 -3
- package/packages/@monomind/cli/dist/src/mcp-client.js +37 -3
- package/packages/@monomind/cli/dist/src/mcp-server.js +8 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +10 -5
- package/packages/@monomind/cli/dist/src/mcp-tools/auto-install.d.ts +8 -8
- package/packages/@monomind/cli/dist/src/mcp-tools/coherence/types.d.ts +31 -31
- package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +30 -8
- package/packages/@monomind/cli/dist/src/mcp-tools/graphify-tools.js +11 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +56 -35
- package/packages/@monomind/cli/dist/src/mcp-tools/knowledge-tools.js +9 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +56 -14
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +88 -41
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/coverage-analysis/prioritize-gaps.d.ts +36 -36
- package/packages/@monomind/cli/dist/src/mcp-tools/quality/security-compliance/detect-secrets.d.ts +4 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +6 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +4 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/types.d.ts +20 -0
- package/packages/@monomind/cli/dist/src/mcp-tools/types.js +36 -1
- package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/memory/hnsw-operations.d.ts +13 -1
- package/packages/@monomind/cli/dist/src/memory/hnsw-operations.js +66 -14
- package/packages/@monomind/cli/dist/src/memory/intelligence.js +99 -3
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.d.ts +7 -0
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +20 -0
- package/packages/@monomind/cli/dist/src/memory/sona-optimizer.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.d.ts +26 -0
- package/packages/@monomind/cli/dist/src/orgrt/broker.js +73 -0
- package/packages/@monomind/cli/dist/src/orgrt/bus.d.ts +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/bus.js +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +46 -0
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +201 -18
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.d.ts +10 -5
- package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +138 -8
- package/packages/@monomind/cli/dist/src/orgrt/inbox.d.ts +11 -0
- package/packages/@monomind/cli/dist/src/orgrt/inbox.js +56 -0
- package/packages/@monomind/cli/dist/src/orgrt/policy.d.ts +5 -1
- package/packages/@monomind/cli/dist/src/orgrt/policy.js +58 -5
- package/packages/@monomind/cli/dist/src/orgrt/provider.js +1 -0
- package/packages/@monomind/cli/dist/src/orgrt/scheduler.d.ts +13 -0
- package/packages/@monomind/cli/dist/src/orgrt/scheduler.js +48 -0
- package/packages/@monomind/cli/dist/src/orgrt/server.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/orgrt/server.js +39 -0
- package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +10 -1
- package/packages/@monomind/cli/dist/src/orgrt/session.js +24 -1
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.d.ts +12 -0
- package/packages/@monomind/cli/dist/src/orgrt/test-loop.js +108 -0
- package/packages/@monomind/cli/dist/src/orgrt/types.d.ts +52 -52
- package/packages/@monomind/cli/dist/src/output.d.ts +29 -29
- package/packages/@monomind/cli/dist/src/output.js +22 -7
- package/packages/@monomind/cli/dist/src/parser.d.ts +32 -0
- package/packages/@monomind/cli/dist/src/parser.js +130 -5
- package/packages/@monomind/cli/dist/src/routing/embed-worker.js +13 -1
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +31 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.d.ts +21 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +60 -7
- package/packages/@monomind/cli/dist/src/services/crash-reporter.js +2 -1
- package/packages/@monomind/cli/dist/src/types.d.ts +2 -2
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +243 -49
- package/packages/@monomind/cli/dist/src/ui/orgs-files.js +192 -0
- package/packages/@monomind/cli/dist/src/ui/orgs.html +79 -11
- package/packages/@monomind/cli/dist/src/ui/server.mjs +346 -139
- package/packages/@monomind/cli/dist/src/utils/input-guards.d.ts +9 -0
- package/packages/@monomind/cli/dist/src/utils/input-guards.js +39 -5
- package/packages/@monomind/cli/package.json +15 -15
|
@@ -77,9 +77,12 @@ export class OutputFormatter {
|
|
|
77
77
|
// Check for NO_COLOR environment variable
|
|
78
78
|
if (process.env.NO_COLOR !== undefined)
|
|
79
79
|
return false;
|
|
80
|
-
// Check for FORCE_COLOR environment variable
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
// Check for FORCE_COLOR environment variable.
|
|
81
|
+
// Per the supports-color convention, FORCE_COLOR=0 (or "false") forces
|
|
82
|
+
// color OFF; any other defined, non-empty value forces color ON.
|
|
83
|
+
if (process.env.FORCE_COLOR !== undefined) {
|
|
84
|
+
return process.env.FORCE_COLOR !== '0' && process.env.FORCE_COLOR !== 'false';
|
|
85
|
+
}
|
|
83
86
|
// Check if stdout is a TTY
|
|
84
87
|
return process.stdout.isTTY ?? false;
|
|
85
88
|
}
|
|
@@ -149,14 +152,18 @@ export class OutputFormatter {
|
|
|
149
152
|
if (this.verbosity === 'quiet')
|
|
150
153
|
return;
|
|
151
154
|
const icon = this.color('[WARN]', 'yellow', 'bold');
|
|
152
|
-
|
|
155
|
+
// Write to stderr (not stdout) so it never interleaves with structured
|
|
156
|
+
// stdout output (e.g. --format json | jq .)
|
|
157
|
+
this.writeErrorln(`${icon} ${message}`);
|
|
153
158
|
}
|
|
154
159
|
printInfo(message) {
|
|
155
160
|
// Info suppressed in quiet mode
|
|
156
161
|
if (this.verbosity === 'quiet')
|
|
157
162
|
return;
|
|
158
163
|
const icon = this.color('[INFO]', 'blue', 'bold');
|
|
159
|
-
|
|
164
|
+
// Write to stderr (not stdout) so it never interleaves with structured
|
|
165
|
+
// stdout output (e.g. --format json | jq .)
|
|
166
|
+
this.writeErrorln(`${icon} ${message}`);
|
|
160
167
|
}
|
|
161
168
|
printDebug(message) {
|
|
162
169
|
// Debug only shows in verbose/debug mode
|
|
@@ -477,6 +484,14 @@ export class Spinner {
|
|
|
477
484
|
start() {
|
|
478
485
|
if (this.interval)
|
|
479
486
|
return;
|
|
487
|
+
// `\r`-based in-place redraw only works on a real TTY. Without one
|
|
488
|
+
// (piped output, CI logs, non-interactive terminals) each animation
|
|
489
|
+
// frame prints as its own line instead of overwriting the last —
|
|
490
|
+
// so print the text once and skip the animation entirely.
|
|
491
|
+
if (!process.stdout.isTTY) {
|
|
492
|
+
process.stdout.write(`${this.text}\n`);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
480
495
|
this.interval = setInterval(() => {
|
|
481
496
|
this.render();
|
|
482
497
|
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
@@ -488,9 +503,9 @@ export class Spinner {
|
|
|
488
503
|
if (this.interval) {
|
|
489
504
|
clearInterval(this.interval);
|
|
490
505
|
this.interval = null;
|
|
506
|
+
// Clear the line (only meaningful when the animation actually ran on a TTY)
|
|
507
|
+
process.stdout.write('\r' + ' '.repeat(this.text.length + 10) + '\r');
|
|
491
508
|
}
|
|
492
|
-
// Clear the line
|
|
493
|
-
process.stdout.write('\r' + ' '.repeat(this.text.length + 10) + '\r');
|
|
494
509
|
if (message) {
|
|
495
510
|
this.formatter.writeln(message);
|
|
496
511
|
}
|
|
@@ -35,7 +35,35 @@ export declare class CommandParser {
|
|
|
35
35
|
*/
|
|
36
36
|
private static readonly RESERVED_FLAG_KEYS;
|
|
37
37
|
private setFlagSafe;
|
|
38
|
+
/**
|
|
39
|
+
* Merge a single parsed flag (or set of `_` positionals) into the
|
|
40
|
+
* accumulated result flags, collecting repeats into an array instead of
|
|
41
|
+
* overwriting. Declared `type: 'array'` options always end up as an array
|
|
42
|
+
* (even a single occurrence); any other repeated flag also becomes an
|
|
43
|
+
* array on its second occurrence rather than silently dropping the first
|
|
44
|
+
* value.
|
|
45
|
+
*/
|
|
46
|
+
private mergeParsedFlags;
|
|
38
47
|
parse(args: string[]): ParseResult;
|
|
48
|
+
/**
|
|
49
|
+
* Convert a camelCase key to kebab-case (inverse of normalizeKey).
|
|
50
|
+
*/
|
|
51
|
+
private camelToKebab;
|
|
52
|
+
/**
|
|
53
|
+
* Ensure every flag is reachable under both its camelCase and
|
|
54
|
+
* kebab-case spelling. Runs once, after all flags (including
|
|
55
|
+
* defaults) have been merged into the result.
|
|
56
|
+
*/
|
|
57
|
+
private mirrorFlagKeys;
|
|
58
|
+
/**
|
|
59
|
+
* True when `value` looks like a space-separated negative number
|
|
60
|
+
* (`-0.5`, `-42`) rather than a new flag. Scoped narrowly to "leading `-`
|
|
61
|
+
* immediately followed by a digit" — a legitimate flag name never starts
|
|
62
|
+
* with a digit, so this can't misfire on a genuine following flag while
|
|
63
|
+
* still letting `--threshold -0.5` consume `-0.5` as the value instead of
|
|
64
|
+
* being misparsed as a new (bogus) flag.
|
|
65
|
+
*/
|
|
66
|
+
private looksLikeNegativeNumber;
|
|
39
67
|
private parseFlag;
|
|
40
68
|
private parseValue;
|
|
41
69
|
private normalizeKey;
|
|
@@ -50,6 +78,10 @@ export declare class CommandParser {
|
|
|
50
78
|
* Get boolean flags scoped to a specific command/subcommand.
|
|
51
79
|
*/
|
|
52
80
|
private getScopedBooleanFlags;
|
|
81
|
+
/**
|
|
82
|
+
* Get flags declared `type: 'array'`, scoped to a specific command/subcommand.
|
|
83
|
+
*/
|
|
84
|
+
private getScopedArrayFlags;
|
|
53
85
|
private getBooleanFlags;
|
|
54
86
|
private applyDefaults;
|
|
55
87
|
validateFlags(flags: ParsedFlags, command?: Command): string[];
|
|
@@ -72,6 +72,12 @@ export class CommandParser {
|
|
|
72
72
|
description: 'Enable interactive mode',
|
|
73
73
|
type: 'boolean',
|
|
74
74
|
default: true
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'update',
|
|
78
|
+
description: 'Check for updates on startup (use --no-update to disable)',
|
|
79
|
+
type: 'boolean',
|
|
80
|
+
default: true
|
|
75
81
|
}
|
|
76
82
|
];
|
|
77
83
|
}
|
|
@@ -111,6 +117,48 @@ export class CommandParser {
|
|
|
111
117
|
return;
|
|
112
118
|
flags[key] = value;
|
|
113
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Merge a single parsed flag (or set of `_` positionals) into the
|
|
122
|
+
* accumulated result flags, collecting repeats into an array instead of
|
|
123
|
+
* overwriting. Declared `type: 'array'` options always end up as an array
|
|
124
|
+
* (even a single occurrence); any other repeated flag also becomes an
|
|
125
|
+
* array on its second occurrence rather than silently dropping the first
|
|
126
|
+
* value.
|
|
127
|
+
*/
|
|
128
|
+
mergeParsedFlags(into, from, arrayFlags, booleanFlags) {
|
|
129
|
+
for (const key of Object.keys(from)) {
|
|
130
|
+
if (key === '_') {
|
|
131
|
+
into._.push(...from._);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (CommandParser.RESERVED_FLAG_KEYS.has(key))
|
|
135
|
+
continue;
|
|
136
|
+
const incoming = from[key];
|
|
137
|
+
if (Array.isArray(into[key])) {
|
|
138
|
+
into[key].push(...[].concat(incoming).map(String));
|
|
139
|
+
}
|
|
140
|
+
else if (into[key] !== undefined && booleanFlags?.has(key)) {
|
|
141
|
+
// Declared boolean flag repeated (e.g. `--dry-run --dry-run`) — last
|
|
142
|
+
// value wins instead of promoting to an array. Callers throughout the
|
|
143
|
+
// codebase check booleans with `flags['x'] === true`; an array value
|
|
144
|
+
// would silently fail that check (e.g. guidance.ts's --dry-run,
|
|
145
|
+
// which guards a destructive write).
|
|
146
|
+
into[key] = incoming;
|
|
147
|
+
}
|
|
148
|
+
else if (into[key] !== undefined) {
|
|
149
|
+
// Repeated flag not previously an array — promote to array so the
|
|
150
|
+
// earlier value isn't lost.
|
|
151
|
+
into[key] = [String(into[key]), String(incoming)];
|
|
152
|
+
}
|
|
153
|
+
else if (arrayFlags.has(key)) {
|
|
154
|
+
// First occurrence of a declared array flag — still wrap in an array.
|
|
155
|
+
into[key] = [String(incoming)];
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
into[key] = incoming;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
114
162
|
parse(args) {
|
|
115
163
|
const result = {
|
|
116
164
|
command: [],
|
|
@@ -135,6 +183,7 @@ export class CommandParser {
|
|
|
135
183
|
// Subcommand-specific aliases take priority over global ones
|
|
136
184
|
const aliases = this.buildScopedAliases(resolvedSub || resolvedCmd);
|
|
137
185
|
const booleanFlags = this.getScopedBooleanFlags(resolvedSub || resolvedCmd);
|
|
186
|
+
const arrayFlags = this.getScopedArrayFlags(resolvedSub || resolvedCmd);
|
|
138
187
|
let i = 0;
|
|
139
188
|
let parsingFlags = true;
|
|
140
189
|
while (i < args.length) {
|
|
@@ -148,8 +197,13 @@ export class CommandParser {
|
|
|
148
197
|
// Handle flags
|
|
149
198
|
if (parsingFlags && arg.startsWith('-')) {
|
|
150
199
|
const parseResult = this.parseFlag(args, i, aliases, booleanFlags);
|
|
151
|
-
//
|
|
152
|
-
|
|
200
|
+
// Merge into result flags. A plain Object.assign here would silently
|
|
201
|
+
// drop earlier values whenever the same flag is passed more than once
|
|
202
|
+
// (`-p a=1 -p b=2` kept only `b=2`) — options declared `type: 'array'`
|
|
203
|
+
// (e.g. browse-action.ts's --params/-p, swarm.ts) are meant to
|
|
204
|
+
// collect every occurrence, and even a flag NOT declared array
|
|
205
|
+
// shouldn't silently lose data on repetition.
|
|
206
|
+
this.mergeParsedFlags(result.flags, parseResult.flags, arrayFlags, booleanFlags);
|
|
153
207
|
i = parseResult.nextIndex;
|
|
154
208
|
continue;
|
|
155
209
|
}
|
|
@@ -201,8 +255,55 @@ export class CommandParser {
|
|
|
201
255
|
// shadow same-name global options (e.g. `browse screenshot --format` is an
|
|
202
256
|
// image format, not the global text|json|table output format).
|
|
203
257
|
this.applyDefaults(result.flags, resolvedCmd);
|
|
258
|
+
// Dual-write flag keys: every flag ends up stored under BOTH its
|
|
259
|
+
// camelCase form (`keepConfig`) and its original kebab-case form
|
|
260
|
+
// (`keep-config`). Historically only the camelCase key was ever set,
|
|
261
|
+
// but dozens of command action functions across the codebase read
|
|
262
|
+
// `ctx.flags['kebab-case']` directly — which was always `undefined`,
|
|
263
|
+
// silently disabling those flags (see AUDIT-BACKLOG P0-10). Running
|
|
264
|
+
// this as a single pass over the fully-merged flags object (long
|
|
265
|
+
// flags, `--no-x` negation, short-flag aliases, and defaults are all
|
|
266
|
+
// merged into `result.flags` by this point) covers every entry point
|
|
267
|
+
// in one place instead of touching each call site individually.
|
|
268
|
+
this.mirrorFlagKeys(result.flags);
|
|
204
269
|
return result;
|
|
205
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Convert a camelCase key to kebab-case (inverse of normalizeKey).
|
|
273
|
+
*/
|
|
274
|
+
camelToKebab(key) {
|
|
275
|
+
return key.replace(/[A-Z]/g, (letter) => '-' + letter.toLowerCase());
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Ensure every flag is reachable under both its camelCase and
|
|
279
|
+
* kebab-case spelling. Runs once, after all flags (including
|
|
280
|
+
* defaults) have been merged into the result.
|
|
281
|
+
*/
|
|
282
|
+
mirrorFlagKeys(flags) {
|
|
283
|
+
for (const key of Object.keys(flags)) {
|
|
284
|
+
if (key === '_' || CommandParser.RESERVED_FLAG_KEYS.has(key))
|
|
285
|
+
continue;
|
|
286
|
+
const camelKey = this.normalizeKey(key);
|
|
287
|
+
if (camelKey !== key && flags[camelKey] === undefined && !CommandParser.RESERVED_FLAG_KEYS.has(camelKey)) {
|
|
288
|
+
flags[camelKey] = flags[key];
|
|
289
|
+
}
|
|
290
|
+
const kebabKey = this.camelToKebab(key);
|
|
291
|
+
if (kebabKey !== key && flags[kebabKey] === undefined && !CommandParser.RESERVED_FLAG_KEYS.has(kebabKey)) {
|
|
292
|
+
flags[kebabKey] = flags[key];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* True when `value` looks like a space-separated negative number
|
|
298
|
+
* (`-0.5`, `-42`) rather than a new flag. Scoped narrowly to "leading `-`
|
|
299
|
+
* immediately followed by a digit" — a legitimate flag name never starts
|
|
300
|
+
* with a digit, so this can't misfire on a genuine following flag while
|
|
301
|
+
* still letting `--threshold -0.5` consume `-0.5` as the value instead of
|
|
302
|
+
* being misparsed as a new (bogus) flag.
|
|
303
|
+
*/
|
|
304
|
+
looksLikeNegativeNumber(value) {
|
|
305
|
+
return /^-\d/.test(value);
|
|
306
|
+
}
|
|
206
307
|
parseFlag(args, index, aliases, booleanFlags) {
|
|
207
308
|
const flags = { _: [] };
|
|
208
309
|
const arg = args[index];
|
|
@@ -239,7 +340,8 @@ export class CommandParser {
|
|
|
239
340
|
if (booleanFlags.has(normalizedKey)) {
|
|
240
341
|
this.setFlagSafe(flags, normalizedKey, true);
|
|
241
342
|
}
|
|
242
|
-
else if (nextIndex < args.length &&
|
|
343
|
+
else if (nextIndex < args.length &&
|
|
344
|
+
(!args[nextIndex].startsWith('-') || this.looksLikeNegativeNumber(args[nextIndex]))) {
|
|
243
345
|
this.setFlagSafe(flags, normalizedKey, this.parseValue(args[nextIndex]));
|
|
244
346
|
nextIndex++;
|
|
245
347
|
}
|
|
@@ -258,7 +360,8 @@ export class CommandParser {
|
|
|
258
360
|
if (booleanFlags.has(normalizedKey)) {
|
|
259
361
|
this.setFlagSafe(flags, normalizedKey, true);
|
|
260
362
|
}
|
|
261
|
-
else if (nextIndex < args.length &&
|
|
363
|
+
else if (nextIndex < args.length &&
|
|
364
|
+
(!args[nextIndex].startsWith('-') || this.looksLikeNegativeNumber(args[nextIndex]))) {
|
|
262
365
|
this.setFlagSafe(flags, normalizedKey, this.parseValue(args[nextIndex]));
|
|
263
366
|
nextIndex++;
|
|
264
367
|
}
|
|
@@ -368,6 +471,20 @@ export class CommandParser {
|
|
|
368
471
|
}
|
|
369
472
|
return flags;
|
|
370
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* Get flags declared `type: 'array'`, scoped to a specific command/subcommand.
|
|
476
|
+
*/
|
|
477
|
+
getScopedArrayFlags(resolvedCmd) {
|
|
478
|
+
const flags = new Set();
|
|
479
|
+
if (resolvedCmd?.options) {
|
|
480
|
+
for (const opt of resolvedCmd.options) {
|
|
481
|
+
if (opt.type === 'array') {
|
|
482
|
+
flags.add(this.normalizeKey(opt.name));
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return flags;
|
|
487
|
+
}
|
|
371
488
|
getBooleanFlags() {
|
|
372
489
|
const flags = new Set();
|
|
373
490
|
for (const opt of this.globalOptions) {
|
|
@@ -471,7 +588,15 @@ export class CommandParser {
|
|
|
471
588
|
}
|
|
472
589
|
// Check for unknown flags if not allowed
|
|
473
590
|
if (!this.options.allowUnknownFlags) {
|
|
474
|
-
|
|
591
|
+
// Include both the camelCase and original (kebab-case) spelling of
|
|
592
|
+
// each option name — parse() now mirrors flags under both forms, so
|
|
593
|
+
// validation must recognize both or the mirrored key would be
|
|
594
|
+
// flagged as an unknown option.
|
|
595
|
+
const knownFlags = new Set();
|
|
596
|
+
for (const opt of allOptions) {
|
|
597
|
+
knownFlags.add(this.normalizeKey(opt.name));
|
|
598
|
+
knownFlags.add(opt.name);
|
|
599
|
+
}
|
|
475
600
|
knownFlags.add('_'); // Positional args
|
|
476
601
|
for (const key of Object.keys(flags)) {
|
|
477
602
|
if (!knownFlags.has(key) && key !== '_') {
|
|
@@ -50,7 +50,19 @@ async function main() {
|
|
|
50
50
|
const result = await layer.route(task);
|
|
51
51
|
// Emit on its own line, prefixed, so the parent can unambiguously locate the
|
|
52
52
|
// result even if the model loader wrote stray bytes to stdout earlier.
|
|
53
|
-
|
|
53
|
+
//
|
|
54
|
+
// Fix (P1-3): onnxruntime's native thread pool keeps the event loop alive
|
|
55
|
+
// even after this function returns, so the process never exits on its own
|
|
56
|
+
// and the parent's `close` listener waits until it force-kills us at the
|
|
57
|
+
// timeout — discarding the result we already wrote. Force-exit once the
|
|
58
|
+
// write is flushed (same onnx-teardown class as memory.ts's #1428 fix).
|
|
59
|
+
const wroteSynchronously = process.stdout.write(`\n__ROUTE_RESULT__${JSON.stringify(result)}\n`);
|
|
60
|
+
if (wroteSynchronously) {
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
process.stdout.once('drain', () => process.exit(0));
|
|
65
|
+
}
|
|
54
66
|
}
|
|
55
67
|
main().catch((err) => {
|
|
56
68
|
process.stderr.write(`embed-worker: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
@@ -56,6 +56,35 @@ function runWorker(task) {
|
|
|
56
56
|
reject(new Error('embed worker timed out'));
|
|
57
57
|
}, WORKER_TIMEOUT_MS);
|
|
58
58
|
timer.unref?.();
|
|
59
|
+
// Resolve as soon as the result marker is *seen*, rather than waiting for
|
|
60
|
+
// the child's `close` event. This is a robustness net: onnxruntime's
|
|
61
|
+
// native thread pool can keep the child's event loop alive after it has
|
|
62
|
+
// already written its result, so `close` may never fire (or only fires
|
|
63
|
+
// after a slow OS-level teardown). embed-worker.ts explicitly
|
|
64
|
+
// process.exit()s right after writing the marker, but resolving here too
|
|
65
|
+
// means a future regression in that exit call degrades to "resolves
|
|
66
|
+
// immediately, child lingers" instead of "silently discards a correct
|
|
67
|
+
// result after a 90s timeout".
|
|
68
|
+
const tryResolveFromMarker = () => {
|
|
69
|
+
if (settled)
|
|
70
|
+
return;
|
|
71
|
+
const markedLine = stdout
|
|
72
|
+
.split('\n')
|
|
73
|
+
.reverse()
|
|
74
|
+
.find((l) => l.startsWith(RESULT_MARKER));
|
|
75
|
+
if (!markedLine)
|
|
76
|
+
return;
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(markedLine.slice(RESULT_MARKER.length));
|
|
79
|
+
settled = true;
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
resolve(parsed);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Marker line present but not yet valid JSON (unlikely — it's written
|
|
85
|
+
// in one shot) — fall through and let `close`/timeout handle it.
|
|
86
|
+
}
|
|
87
|
+
};
|
|
59
88
|
child.stdout?.on('data', (d) => {
|
|
60
89
|
stdout += d.toString();
|
|
61
90
|
if (stdout.length > MAX_WORKER_OUTPUT) {
|
|
@@ -68,7 +97,9 @@ function runWorker(task) {
|
|
|
68
97
|
}
|
|
69
98
|
catch { /* already dead */ }
|
|
70
99
|
reject(new Error('embed worker produced excessive output'));
|
|
100
|
+
return;
|
|
71
101
|
}
|
|
102
|
+
tryResolveFromMarker();
|
|
72
103
|
});
|
|
73
104
|
child.stderr?.on('data', (d) => { if (stderr.length < MAX_WORKER_OUTPUT)
|
|
74
105
|
stderr += d.toString(); });
|
|
@@ -2,11 +2,32 @@
|
|
|
2
2
|
* Config File Manager
|
|
3
3
|
* Shared JSON config file persistence with atomic writes and Zod validation
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Default config values.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: never hand this object (or a shallow `{ ...DEFAULT_CONFIG }`
|
|
9
|
+
* copy) out directly — its nested sections (`agents`, `swarm`, `memory`, ...)
|
|
10
|
+
* would be shared-by-reference with every caller, so one caller mutating
|
|
11
|
+
* `config.swarm.maxAgents` would silently corrupt this module-level constant
|
|
12
|
+
* for the rest of the process. Always hand out `cloneDefaultConfig()`.
|
|
13
|
+
*/
|
|
14
|
+
declare const DEFAULT_CONFIG: Record<string, unknown>;
|
|
15
|
+
/** Exposed read-only for callers (e.g. config-adapter.ts) that need to
|
|
16
|
+
* agree with this module's defaults instead of hardcoding their own. */
|
|
17
|
+
export { DEFAULT_CONFIG };
|
|
5
18
|
export declare class ConfigFileManager {
|
|
6
19
|
private configPath;
|
|
7
20
|
private config;
|
|
8
21
|
/** Find config file in search paths starting from cwd */
|
|
9
22
|
findConfig(cwd: string): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Load config from an EXACT file path (used by `--config <file>` / `-c`).
|
|
25
|
+
* Unlike load()/findConfig(), this never falls back to directory search —
|
|
26
|
+
* an explicitly-named config file that doesn't exist or fails to parse is
|
|
27
|
+
* an error, not a silent fallback to defaults or an unrelated file in the
|
|
28
|
+
* same directory.
|
|
29
|
+
*/
|
|
30
|
+
loadExact(filePath: string): Record<string, unknown>;
|
|
10
31
|
/** Load config from file, returns null if not found */
|
|
11
32
|
load(cwd: string): Record<string, unknown> | null;
|
|
12
33
|
/** Get the current config, loading if needed */
|
|
@@ -10,7 +10,15 @@ const CONFIG_FILENAMES = [
|
|
|
10
10
|
'monomind.config.json',
|
|
11
11
|
'.monomind/config.json',
|
|
12
12
|
];
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Default config values.
|
|
15
|
+
*
|
|
16
|
+
* IMPORTANT: never hand this object (or a shallow `{ ...DEFAULT_CONFIG }`
|
|
17
|
+
* copy) out directly — its nested sections (`agents`, `swarm`, `memory`, ...)
|
|
18
|
+
* would be shared-by-reference with every caller, so one caller mutating
|
|
19
|
+
* `config.swarm.maxAgents` would silently corrupt this module-level constant
|
|
20
|
+
* for the rest of the process. Always hand out `cloneDefaultConfig()`.
|
|
21
|
+
*/
|
|
14
22
|
const DEFAULT_CONFIG = {
|
|
15
23
|
version: '3.5',
|
|
16
24
|
agents: {
|
|
@@ -61,6 +69,20 @@ const DEFAULT_CONFIG = {
|
|
|
61
69
|
},
|
|
62
70
|
},
|
|
63
71
|
};
|
|
72
|
+
/**
|
|
73
|
+
* Deep-clone the default config so every caller gets its own independent
|
|
74
|
+
* object graph. `{ ...DEFAULT_CONFIG }` is only a shallow copy — its nested
|
|
75
|
+
* sub-configs (`agents`, `swarm`, `memory`, ...) would remain the SAME
|
|
76
|
+
* object references as the module-level constant, so mutating a nested
|
|
77
|
+
* field on one caller's copy would corrupt the shared default for every
|
|
78
|
+
* subsequent caller in the process.
|
|
79
|
+
*/
|
|
80
|
+
function cloneDefaultConfig() {
|
|
81
|
+
return structuredClone(DEFAULT_CONFIG);
|
|
82
|
+
}
|
|
83
|
+
/** Exposed read-only for callers (e.g. config-adapter.ts) that need to
|
|
84
|
+
* agree with this module's defaults instead of hardcoding their own. */
|
|
85
|
+
export { DEFAULT_CONFIG };
|
|
64
86
|
export class ConfigFileManager {
|
|
65
87
|
configPath = null;
|
|
66
88
|
config = null;
|
|
@@ -83,6 +105,36 @@ export class ConfigFileManager {
|
|
|
83
105
|
}
|
|
84
106
|
return null;
|
|
85
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Load config from an EXACT file path (used by `--config <file>` / `-c`).
|
|
110
|
+
* Unlike load()/findConfig(), this never falls back to directory search —
|
|
111
|
+
* an explicitly-named config file that doesn't exist or fails to parse is
|
|
112
|
+
* an error, not a silent fallback to defaults or an unrelated file in the
|
|
113
|
+
* same directory.
|
|
114
|
+
*/
|
|
115
|
+
loadExact(filePath) {
|
|
116
|
+
const resolved = path.resolve(filePath);
|
|
117
|
+
if (!fs.existsSync(resolved)) {
|
|
118
|
+
throw new Error(`Config file not found: ${resolved}`);
|
|
119
|
+
}
|
|
120
|
+
let content;
|
|
121
|
+
try {
|
|
122
|
+
content = fs.readFileSync(resolved, 'utf-8');
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
throw new Error(`Failed to read config file ${resolved}: ${err instanceof Error ? err.message : String(err)}`);
|
|
126
|
+
}
|
|
127
|
+
let parsed;
|
|
128
|
+
try {
|
|
129
|
+
parsed = JSON.parse(content);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
throw new Error(`Failed to parse config file ${resolved}: ${err instanceof Error ? err.message : String(err)}`);
|
|
133
|
+
}
|
|
134
|
+
this.configPath = resolved;
|
|
135
|
+
this.config = sanitizeConfigObject(parsed);
|
|
136
|
+
return this.config;
|
|
137
|
+
}
|
|
86
138
|
/** Load config from file, returns null if not found */
|
|
87
139
|
load(cwd) {
|
|
88
140
|
this.configPath = this.findConfig(cwd);
|
|
@@ -106,7 +158,7 @@ export class ConfigFileManager {
|
|
|
106
158
|
if (this.config === null) {
|
|
107
159
|
this.load(cwd);
|
|
108
160
|
}
|
|
109
|
-
return this.config ??
|
|
161
|
+
return this.config ?? cloneDefaultConfig();
|
|
110
162
|
}
|
|
111
163
|
/** Get a nested config value by dot-separated key */
|
|
112
164
|
get(cwd, key) {
|
|
@@ -136,7 +188,7 @@ export class ConfigFileManager {
|
|
|
136
188
|
// cross-process atomic without an OS-level flock, but combined with the
|
|
137
189
|
// O_EXCL atomic rename it prevents the most common credential-clobber
|
|
138
190
|
// window where two CLIs interleave their getConfig→set→write cycles.
|
|
139
|
-
let onDisk =
|
|
191
|
+
let onDisk = cloneDefaultConfig();
|
|
140
192
|
if (fs.existsSync(targetPath)) {
|
|
141
193
|
try {
|
|
142
194
|
const parsed = JSON.parse(fs.readFileSync(targetPath, 'utf-8'));
|
|
@@ -155,7 +207,7 @@ export class ConfigFileManager {
|
|
|
155
207
|
if (fs.existsSync(targetPath) && !force) {
|
|
156
208
|
throw new Error(`Config file already exists: ${targetPath}. Use --force to overwrite.`);
|
|
157
209
|
}
|
|
158
|
-
const config = { ...
|
|
210
|
+
const config = { ...cloneDefaultConfig(), ...(overrides ? sanitizeConfigObject(overrides) : {}) };
|
|
159
211
|
this.writeAtomic(targetPath, config);
|
|
160
212
|
this.config = config;
|
|
161
213
|
this.configPath = targetPath;
|
|
@@ -164,8 +216,9 @@ export class ConfigFileManager {
|
|
|
164
216
|
/** Reset config to defaults */
|
|
165
217
|
reset(cwd) {
|
|
166
218
|
const targetPath = this.configPath ?? path.resolve(cwd, CONFIG_FILENAMES[0]);
|
|
167
|
-
|
|
168
|
-
this.
|
|
219
|
+
const fresh = cloneDefaultConfig();
|
|
220
|
+
this.writeAtomic(targetPath, fresh);
|
|
221
|
+
this.config = fresh;
|
|
169
222
|
this.configPath = targetPath;
|
|
170
223
|
return targetPath;
|
|
171
224
|
}
|
|
@@ -228,7 +281,7 @@ export class ConfigFileManager {
|
|
|
228
281
|
}
|
|
229
282
|
/** Get default config */
|
|
230
283
|
getDefaults() {
|
|
231
|
-
return
|
|
284
|
+
return cloneDefaultConfig();
|
|
232
285
|
}
|
|
233
286
|
/** Atomic write with restrictive 0o600 mode.
|
|
234
287
|
* SECURITY: this config file may contain API keys (per `commands/providers.ts`).
|
|
@@ -84,7 +84,8 @@ export function redact(text) {
|
|
|
84
84
|
/(?:secret|password|passwd|pwd)\s*[:=]\s*['"]?[^\s'"]{8,}['"]?/gi,
|
|
85
85
|
/(?:token|bearer)\s*[:=]\s*['"]?[^\s'"]{10,}['"]?/gi,
|
|
86
86
|
/-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
|
|
87
|
-
/sk-[a-zA-Z0-
|
|
87
|
+
/sk-ant-[a-zA-Z0-9_-]{20,}/g,
|
|
88
|
+
/sk-[a-zA-Z0-9_-]{20,}/g,
|
|
88
89
|
/ghp_[a-zA-Z0-9]{36}/g,
|
|
89
90
|
/gho_[a-zA-Z0-9]{36}/g,
|
|
90
91
|
/npm_[a-zA-Z0-9]{36}/g,
|
|
@@ -184,7 +184,7 @@ export interface MultiSelectPromptOptions<T = string> {
|
|
|
184
184
|
export declare class CLIError extends Error {
|
|
185
185
|
code: string;
|
|
186
186
|
exitCode: number;
|
|
187
|
-
details?: unknown
|
|
188
|
-
constructor(message: string, code: string, exitCode?: number, details?: unknown
|
|
187
|
+
details?: unknown;
|
|
188
|
+
constructor(message: string, code: string, exitCode?: number, details?: unknown);
|
|
189
189
|
}
|
|
190
190
|
//# sourceMappingURL=types.d.ts.map
|