monomind 2.7.6 → 2.7.7
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/package.json +4 -4
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-server.mjs +38 -1
- package/packages/@monomind/cli/dist/src/commands/agent-ops.js +32 -2
- package/packages/@monomind/cli/dist/src/commands/browse-workflow.js +30 -0
- package/packages/@monomind/cli/dist/src/commands/hooks-extended-commands.js +10 -2
- package/packages/@monomind/cli/dist/src/commands/memory-crud.js +34 -3
- package/packages/@monomind/cli/dist/src/commands/org-observe.js +50 -6
- package/packages/@monomind/cli/dist/src/commands/performance.js +31 -5
- package/packages/@monomind/cli/dist/src/commands/security-misc.js +18 -3
- package/packages/@monomind/cli/dist/src/mcp-client.js +78 -0
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.d.ts +5 -1
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +22 -3
- package/packages/@monomind/cli/dist/src/memory/memory-read.d.ts +4 -0
- package/packages/@monomind/cli/dist/src/memory/memory-read.js +5 -2
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.d.ts +15 -0
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +44 -2
- package/packages/@monomind/cli/package.json +5 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monomind",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.7",
|
|
4
4
|
"description": "Open-source CLI extension for Claude Code. Adds an MCP server with a codebase knowledge graph, persistent memory, multi-agent coordination, and reusable slash commands. MIT licensed, runs locally, no data leaves your machine.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@anthropic-ai/claude-agent-sdk": "^0.3.207",
|
|
65
65
|
"@monoes/monobrowse": "^1.0.6",
|
|
66
|
-
"@monoes/monodesign": "^1.2.
|
|
66
|
+
"@monoes/monodesign": "^1.2.1",
|
|
67
67
|
"@monoes/monograph": "^1.5.4",
|
|
68
68
|
"@noble/ed25519": "^2.1.0",
|
|
69
69
|
"mammoth": "^1.12.0",
|
|
@@ -76,9 +76,9 @@
|
|
|
76
76
|
"optionalDependencies": {
|
|
77
77
|
"@huggingface/transformers": "^3.8.1",
|
|
78
78
|
"@monoes/hooks": "^1.0.0",
|
|
79
|
-
"@monoes/mcp": "^1.0.
|
|
79
|
+
"@monoes/mcp": "^1.0.1",
|
|
80
80
|
"@monoes/memory": "^1.0.10",
|
|
81
|
-
"@monoes/routing": "^1.0.
|
|
81
|
+
"@monoes/routing": "^1.0.1",
|
|
82
82
|
"monofence-ai": "*",
|
|
83
83
|
"sql.js": "^1.14.1"
|
|
84
84
|
},
|
|
@@ -1112,7 +1112,8 @@ applyLegacyDeferredAcceptsOnStartup();
|
|
|
1112
1112
|
restorePendingEventsFromStore();
|
|
1113
1113
|
manualApply.pruneStaleEvidence();
|
|
1114
1114
|
const portArg = args.find(a => a.startsWith('--port='));
|
|
1115
|
-
|
|
1115
|
+
const explicitPort = portArg ? parseInt(portArg.split('=')[1], 10) : null;
|
|
1116
|
+
state.port = explicitPort ?? await findOpenPort();
|
|
1116
1117
|
// Annotation screenshots live in the project root so the agent's Read tool
|
|
1117
1118
|
// doesn't trip a per-file permission prompt. Sessioned by token so concurrent
|
|
1118
1119
|
// projects (or quick restarts) don't collide.
|
|
@@ -1123,7 +1124,43 @@ state.sessionDir = fs.mkdtempSync(path.join(annotRoot, 'session-'));
|
|
|
1123
1124
|
const { detectScript, liveScriptParts } = loadBrowserScripts();
|
|
1124
1125
|
httpServer = http.createServer(createRequestHandler({ detectScript, liveScriptParts }));
|
|
1125
1126
|
|
|
1127
|
+
// findOpenPort() probes a port, closes the probe socket, and only then does the
|
|
1128
|
+
// real server bind it. Another process scanning the same range can take the
|
|
1129
|
+
// port inside that gap, which surfaced as an intermittent
|
|
1130
|
+
// "EADDRINUSE 127.0.0.1:8405" — about one run in five of the test suite, where
|
|
1131
|
+
// several server-starting test files run in parallel.
|
|
1132
|
+
//
|
|
1133
|
+
// Retry on the next port when we picked it ourselves. An explicit --port is
|
|
1134
|
+
// never silently moved: the caller asked for that port, so a conflict there
|
|
1135
|
+
// must be an error they can see.
|
|
1136
|
+
const MAX_PORT_RETRIES = 25;
|
|
1137
|
+
let portRetries = 0;
|
|
1138
|
+
|
|
1139
|
+
httpServer.on('error', (err) => {
|
|
1140
|
+
if (err?.code !== 'EADDRINUSE') throw err;
|
|
1141
|
+
|
|
1142
|
+
if (explicitPort !== null) {
|
|
1143
|
+
console.error(`\nPort ${state.port} is already in use.`);
|
|
1144
|
+
console.error('Another live server may be running — stop it, or pass a different --port.');
|
|
1145
|
+
process.exit(1);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
if (portRetries >= MAX_PORT_RETRIES) {
|
|
1149
|
+
console.error(`\nCould not find a free port after ${MAX_PORT_RETRIES} attempts (last tried ${state.port}).`);
|
|
1150
|
+
process.exit(1);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
portRetries++;
|
|
1154
|
+
state.port++;
|
|
1155
|
+
httpServer.listen(state.port, '127.0.0.1');
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1126
1158
|
httpServer.listen(state.port, '127.0.0.1', () => {
|
|
1159
|
+
// Trust the address the OS actually bound over the one we asked for — after
|
|
1160
|
+
// a retry above, state.port and the bound port must not drift apart.
|
|
1161
|
+
const boundAddr = httpServer.address();
|
|
1162
|
+
if (boundAddr && typeof boundAddr === 'object') state.port = boundAddr.port;
|
|
1163
|
+
|
|
1127
1164
|
writeLiveServerInfo(process.cwd(), { pid: process.pid, port: state.port, token: state.token });
|
|
1128
1165
|
const url = `http://localhost:${state.port}`;
|
|
1129
1166
|
console.log(`\nMonodesign live server running on ${url}`);
|
|
@@ -180,6 +180,36 @@ export const healthCommand = {
|
|
|
180
180
|
{ command: 'monomind agent health -i agent-001 -d', description: 'Detailed health for specific agent' },
|
|
181
181
|
],
|
|
182
182
|
action: async (ctx) => {
|
|
183
|
+
// --watch was declared (and documented as "refresh every 5s") but never
|
|
184
|
+
// read, so the command rendered once and exited. Mirrors the watch loop in
|
|
185
|
+
// commands/status.ts.
|
|
186
|
+
if (ctx.flags.watch) {
|
|
187
|
+
return watchAgentHealth(ctx);
|
|
188
|
+
}
|
|
189
|
+
return renderAgentHealth(ctx);
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
const WATCH_INTERVAL_MS = 5000;
|
|
193
|
+
async function watchAgentHealth(ctx) {
|
|
194
|
+
const refresh = async () => {
|
|
195
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
196
|
+
output.writeln(output.dim(`Last updated: ${new Date().toLocaleTimeString()} — refreshing every 5s. Press Ctrl+C to exit.`));
|
|
197
|
+
await renderAgentHealth(ctx);
|
|
198
|
+
};
|
|
199
|
+
await refresh();
|
|
200
|
+
const intervalId = setInterval(() => { void refresh(); }, WATCH_INTERVAL_MS);
|
|
201
|
+
// `once` so repeated invocations don't accumulate SIGINT handlers.
|
|
202
|
+
return new Promise((resolve) => {
|
|
203
|
+
process.once('SIGINT', () => {
|
|
204
|
+
clearInterval(intervalId);
|
|
205
|
+
output.writeln();
|
|
206
|
+
output.printInfo('Watch mode stopped');
|
|
207
|
+
resolve({ success: true });
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
async function renderAgentHealth(ctx) {
|
|
212
|
+
{
|
|
183
213
|
const agentId = ctx.args[0] || ctx.flags.id;
|
|
184
214
|
const detailed = ctx.flags.detailed;
|
|
185
215
|
try {
|
|
@@ -242,6 +272,6 @@ export const healthCommand = {
|
|
|
242
272
|
output.printError(error instanceof MCPClientError ? `Health check error: ${error.message}` : `Unexpected error: ${String(error)}`);
|
|
243
273
|
return { success: false, exitCode: 1 };
|
|
244
274
|
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
247
277
|
//# sourceMappingURL=agent-ops.js.map
|
|
@@ -53,6 +53,35 @@ const runSubcommand = {
|
|
|
53
53
|
const wf = await readWorkflow(filePath).catch(e => { output.printError(e.message); return null; });
|
|
54
54
|
if (!wf)
|
|
55
55
|
return { success: false, exitCode: 1 };
|
|
56
|
+
// --items feeds the run's input set. Previously the flag was parsed and
|
|
57
|
+
// then dropped, so every run silently used the single empty default item.
|
|
58
|
+
let items;
|
|
59
|
+
const itemsFlag = ctx.flags.items;
|
|
60
|
+
if (itemsFlag) {
|
|
61
|
+
const itemsPath = isAbsolute(itemsFlag) ? itemsFlag : resolve(ctx.cwd, itemsFlag);
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
const { readFile } = await import('fs/promises');
|
|
65
|
+
parsed = JSON.parse(await readFile(itemsPath, 'utf8'));
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
output.printError(`Cannot read items file ${itemsPath}: ${e.message}`);
|
|
69
|
+
return { success: false, exitCode: 1 };
|
|
70
|
+
}
|
|
71
|
+
if (!Array.isArray(parsed)) {
|
|
72
|
+
output.printError(`Items file ${itemsPath} must contain a JSON array`);
|
|
73
|
+
return { success: false, exitCode: 1 };
|
|
74
|
+
}
|
|
75
|
+
// Accept both the engine's `{ data: {...} }` envelope and a bare array
|
|
76
|
+
// of objects, which is the shape people naturally write by hand.
|
|
77
|
+
items = parsed.map((entry) => {
|
|
78
|
+
const rec = entry;
|
|
79
|
+
return rec && typeof rec === 'object' && 'data' in rec && typeof rec.data === 'object' && rec.data !== null
|
|
80
|
+
? { data: rec.data }
|
|
81
|
+
: { data: (rec ?? {}) };
|
|
82
|
+
});
|
|
83
|
+
output.printInfo(`Loaded ${items.length} input item(s) from ${itemsFlag}`);
|
|
84
|
+
}
|
|
56
85
|
const port = ctx.flags.port ?? 4243;
|
|
57
86
|
const dashboard = getDashboardServer(port);
|
|
58
87
|
if (!ctx.flags['no-dashboard']) {
|
|
@@ -65,6 +94,7 @@ const runSubcommand = {
|
|
|
65
94
|
spinner.start();
|
|
66
95
|
const record = await runWorkflow(wf, {
|
|
67
96
|
onEvent: (ev) => dashboard.broadcast(ev),
|
|
97
|
+
...(items ? { items } : {}),
|
|
68
98
|
});
|
|
69
99
|
if (record.status === 'completed') {
|
|
70
100
|
spinner.succeed(`Done — ${record.itemsProcessed} items in ${((record.completedAt - record.startedAt) / 1000).toFixed(1)}s`);
|
|
@@ -200,7 +200,7 @@ export const notifyCommand = {
|
|
|
200
200
|
options: [
|
|
201
201
|
{ name: 'message', short: 'm', type: 'string', description: 'Notification message', required: true },
|
|
202
202
|
{ name: 'level', short: 'l', type: 'string', description: 'Level: info, warn, error', default: 'info' },
|
|
203
|
-
{ name: 'channel', short: 'c', type: 'string', description: 'Notification channel', default: 'console' },
|
|
203
|
+
{ name: 'channel', short: 'c', type: 'string', description: 'Notification channel (only "console" is implemented)', default: 'console' },
|
|
204
204
|
],
|
|
205
205
|
examples: [
|
|
206
206
|
{ command: 'monomind hooks notify -m "Build complete"', description: 'Send info notification' },
|
|
@@ -213,6 +213,14 @@ export const notifyCommand = {
|
|
|
213
213
|
output.printError('Message is required: --message "your message"');
|
|
214
214
|
return { success: false, exitCode: 1 };
|
|
215
215
|
}
|
|
216
|
+
// Console is the only delivery mechanism that exists. Accepting
|
|
217
|
+
// `--channel slack` and then printing to the console anyway would tell the
|
|
218
|
+
// user their message went somewhere it did not.
|
|
219
|
+
const channel = ctx.flags.channel || 'console';
|
|
220
|
+
if (channel !== 'console') {
|
|
221
|
+
output.writeln(output.warning(`Channel "${channel}" is not implemented — delivering to console. ` +
|
|
222
|
+
`Only "console" is supported today.`));
|
|
223
|
+
}
|
|
216
224
|
const timestamp = new Date().toISOString();
|
|
217
225
|
if (level === 'error') {
|
|
218
226
|
output.printError(`[${timestamp}] ${message}`);
|
|
@@ -229,7 +237,7 @@ export const notifyCommand = {
|
|
|
229
237
|
await storeEntry({ key: `notify-${Date.now()}`, value: `[${level}] ${message}`, namespace: 'notifications' });
|
|
230
238
|
}
|
|
231
239
|
catch { /* memory not available */ }
|
|
232
|
-
return { success: true, data: { timestamp, level, message } };
|
|
240
|
+
return { success: true, data: { timestamp, level, message, channel: 'console' } };
|
|
233
241
|
}
|
|
234
242
|
};
|
|
235
243
|
//# sourceMappingURL=hooks-extended-commands.js.map
|
|
@@ -297,7 +297,10 @@ export const searchCommand = {
|
|
|
297
297
|
output.writeln();
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
|
-
|
|
300
|
+
// Requested type only — the method that ACTUALLY ran is printed after the
|
|
301
|
+
// search, from searchResult.searchMethod. Labelling this line "(semantic)"
|
|
302
|
+
// used to claim a vector search that may never have happened.
|
|
303
|
+
output.printInfo(`Searching: "${query}" (requested: ${searchType})`);
|
|
301
304
|
output.writeln();
|
|
302
305
|
// Use direct sql.js search with vector similarity
|
|
303
306
|
try {
|
|
@@ -318,11 +321,39 @@ export const searchCommand = {
|
|
|
318
321
|
namespace: r.namespace,
|
|
319
322
|
preview: r.content
|
|
320
323
|
}));
|
|
324
|
+
const actualMethod = searchResult.searchMethod ?? 'unknown';
|
|
325
|
+
const fallbackReason = searchResult.fallbackReason;
|
|
321
326
|
if (ctx.flags.format === 'json') {
|
|
322
|
-
output.printJson({
|
|
327
|
+
output.printJson({
|
|
328
|
+
query,
|
|
329
|
+
searchType,
|
|
330
|
+
searchMethod: actualMethod,
|
|
331
|
+
...(fallbackReason ? { fallbackReason } : {}),
|
|
332
|
+
results,
|
|
333
|
+
searchTime: `${searchResult.searchTime}ms`,
|
|
334
|
+
});
|
|
323
335
|
return { success: true, data: results };
|
|
324
336
|
}
|
|
325
|
-
// Performance stats
|
|
337
|
+
// Performance stats — method first, so a keyword fallback is never hidden
|
|
338
|
+
// behind a "(semantic)" header.
|
|
339
|
+
const REASON_TEXT = {
|
|
340
|
+
'no-embedding-model': 'embedding model unavailable',
|
|
341
|
+
'embedding-failed': 'embedding generation failed',
|
|
342
|
+
'no-semantic-matches': 'vector search returned no matches',
|
|
343
|
+
};
|
|
344
|
+
if (actualMethod === 'semantic') {
|
|
345
|
+
output.writeln(output.dim(' Method: semantic (vector similarity)'));
|
|
346
|
+
}
|
|
347
|
+
else if (actualMethod === 'hybrid') {
|
|
348
|
+
output.writeln(output.dim(' Method: hybrid (per-entry cosine, keyword overlap where no vector exists)'));
|
|
349
|
+
}
|
|
350
|
+
else if (actualMethod === 'unknown') {
|
|
351
|
+
output.writeln(output.dim(' Method: unknown'));
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
const why = fallbackReason ? REASON_TEXT[fallbackReason] ?? fallbackReason : undefined;
|
|
355
|
+
output.printWarning(`Method: ${actualMethod}${why ? ` — ${why}` : ''}. Scores are token-overlap fractions, not vector similarity.`);
|
|
356
|
+
}
|
|
326
357
|
output.writeln(output.dim(` Search time: ${searchResult.searchTime}ms`));
|
|
327
358
|
output.writeln();
|
|
328
359
|
if (results.length === 0) {
|
|
@@ -178,17 +178,44 @@ export const reportAction = async (ctx, name) => {
|
|
|
178
178
|
}
|
|
179
179
|
return { success: true };
|
|
180
180
|
};
|
|
181
|
+
/** Read questions.json. A MISSING file legitimately means "no questions" → [].
|
|
182
|
+
* Any other failure (unreadable, malformed — e.g. a partial daemon write) THROWS:
|
|
183
|
+
* answerAction rewrites this file from what this returns, so silently coercing a
|
|
184
|
+
* failed read to [] would atomically replace every recorded question with one. */
|
|
181
185
|
const readQuestions = (cwd, name) => {
|
|
186
|
+
const path = join(cwd, ORG_DIR, name, 'questions.json');
|
|
187
|
+
let raw;
|
|
182
188
|
try {
|
|
183
|
-
|
|
189
|
+
raw = readFileSync(path, 'utf8');
|
|
184
190
|
}
|
|
185
|
-
catch {
|
|
186
|
-
|
|
191
|
+
catch (err) {
|
|
192
|
+
if (err.code === 'ENOENT')
|
|
193
|
+
return [];
|
|
194
|
+
throw new Error(`cannot read ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
195
|
+
}
|
|
196
|
+
let parsed;
|
|
197
|
+
try {
|
|
198
|
+
parsed = JSON.parse(raw);
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
throw new Error(`${path} is not valid JSON (${err instanceof Error ? err.message : String(err)})`);
|
|
187
202
|
}
|
|
203
|
+
if (parsed?.questions === undefined || parsed.questions === null)
|
|
204
|
+
return [];
|
|
205
|
+
if (!Array.isArray(parsed.questions))
|
|
206
|
+
throw new Error(`${path}: "questions" is not an array`);
|
|
207
|
+
return parsed.questions;
|
|
188
208
|
};
|
|
189
209
|
/** `org questions <name> [--all]` — list pending (or all) ask_human questions. */
|
|
190
210
|
export const questionsAction = async (ctx, name) => {
|
|
191
|
-
|
|
211
|
+
let all;
|
|
212
|
+
try {
|
|
213
|
+
all = readQuestions(ctx.cwd, name);
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
log(output.error(`Cannot read questions for org ${name}: ${err instanceof Error ? err.message : String(err)}`));
|
|
217
|
+
return { success: false, message: 'questions.json unreadable' };
|
|
218
|
+
}
|
|
192
219
|
const shown = ctx.flags['all'] === true ? all : all.filter(q => q.answer === null);
|
|
193
220
|
if (!shown.length) {
|
|
194
221
|
log(output.info(all.length ? `No pending questions for org ${name} (${all.length} answered — use --all).` : `No questions recorded for org ${name}.`));
|
|
@@ -212,7 +239,14 @@ export const answerAction = async (ctx, name) => {
|
|
|
212
239
|
const answer = ctx.args.slice(2).join(' ').trim();
|
|
213
240
|
if (!questionId || !answer)
|
|
214
241
|
return { success: false, message: `usage: monomind org answer ${name} <question-id> "answer text"` };
|
|
215
|
-
|
|
242
|
+
let questions;
|
|
243
|
+
try {
|
|
244
|
+
questions = readQuestions(ctx.cwd, name);
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
log(output.error(`Cannot read questions for org ${name}: ${err instanceof Error ? err.message : String(err)}`));
|
|
248
|
+
return { success: false, message: 'questions.json unreadable — answer not recorded' };
|
|
249
|
+
}
|
|
216
250
|
const q = questions.find(x => x.questionId === questionId);
|
|
217
251
|
if (!q) {
|
|
218
252
|
log(output.error(`Question "${questionId}" not found for org ${name} — list with: monomind org questions ${name}`));
|
|
@@ -246,7 +280,17 @@ export const answerAction = async (ctx, name) => {
|
|
|
246
280
|
// snapshot can be up to 10s stale (live-delivery timeout), and rewriting
|
|
247
281
|
// from it would delete questions the daemon appended meanwhile and revert
|
|
248
282
|
// answers it recorded (atomic rename prevents torn writes, not lost updates).
|
|
249
|
-
|
|
283
|
+
// A FAILED re-read must abort the write: rewriting from [] would atomically
|
|
284
|
+
// rename a single-question file over every other recorded question.
|
|
285
|
+
let fresh;
|
|
286
|
+
try {
|
|
287
|
+
fresh = readQuestions(ctx.cwd, name);
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
log(output.error(`Refusing to rewrite questions.json — ${err instanceof Error ? err.message : String(err)}`));
|
|
291
|
+
log(output.warning(`The answer was NOT recorded. Fix or restore ${join(ctx.cwd, ORG_DIR, name, 'questions.json')}, then retry.`));
|
|
292
|
+
return { success: false, message: 'questions.json unreadable — answer not recorded' };
|
|
293
|
+
}
|
|
250
294
|
const freshQ = fresh.find(x => x.questionId === questionId);
|
|
251
295
|
if (freshQ && freshQ.answer !== null) {
|
|
252
296
|
return { success: false, message: `question "${questionId}" was answered while this command was running` };
|
|
@@ -521,12 +521,24 @@ const bottleneckCommand = {
|
|
|
521
521
|
],
|
|
522
522
|
examples: [
|
|
523
523
|
{ command: 'monomind performance bottleneck', description: 'Find bottlenecks' },
|
|
524
|
-
{ command: 'monomind performance bottleneck -
|
|
524
|
+
{ command: 'monomind performance bottleneck -c network', description: 'Only the Network component' },
|
|
525
525
|
],
|
|
526
526
|
action: async (ctx) => {
|
|
527
527
|
output.writeln();
|
|
528
528
|
output.writeln(output.bold('Bottleneck Analysis'));
|
|
529
529
|
output.writeln(output.dim('─'.repeat(50)));
|
|
530
|
+
// Components this command actually knows how to inspect. Used both to
|
|
531
|
+
// validate --component and to tell the user what they could have asked for.
|
|
532
|
+
const ANALYZED_COMPONENTS = ['Runtime', 'Vector Search', 'Traces', 'Memory DB', 'Network'];
|
|
533
|
+
// Only a single depth of analysis is implemented. Accepting `--depth full`
|
|
534
|
+
// and quietly running the quick analysis discards the user's request with
|
|
535
|
+
// no indication it was ignored, so say so instead.
|
|
536
|
+
const depthRaw = ctx.flags.depth || 'quick';
|
|
537
|
+
if (depthRaw !== 'quick') {
|
|
538
|
+
output.writeln(output.warning(`--depth ${depthRaw} is not implemented — running the "quick" analysis. ` +
|
|
539
|
+
`Only "quick" is supported today.`));
|
|
540
|
+
}
|
|
541
|
+
const componentFilter = ctx.flags.component?.trim();
|
|
530
542
|
const spinner = output.createSpinner({ text: 'Analyzing system...', spinner: 'dots' });
|
|
531
543
|
spinner.start();
|
|
532
544
|
const fs = await import('node:fs');
|
|
@@ -575,10 +587,24 @@ const bottleneckCommand = {
|
|
|
575
587
|
if (!network.batching) {
|
|
576
588
|
findings.push({ component: 'Network', bottleneck: 'No request batching', severity: output.info('Low'), solution: 'Run: monomind performance optimize --apply -t latency' });
|
|
577
589
|
}
|
|
578
|
-
|
|
579
|
-
|
|
590
|
+
let results = findings;
|
|
591
|
+
if (componentFilter) {
|
|
592
|
+
const known = ANALYZED_COMPONENTS.find(c => c.toLowerCase() === componentFilter.toLowerCase())
|
|
593
|
+
?? ANALYZED_COMPONENTS.find(c => c.toLowerCase().includes(componentFilter.toLowerCase()));
|
|
594
|
+
if (!known) {
|
|
595
|
+
spinner.fail(`Unknown component "${componentFilter}"`);
|
|
596
|
+
output.writeln(output.warning(`Analyzed components: ${ANALYZED_COMPONENTS.join(', ')}.`));
|
|
597
|
+
return { success: false, message: `Unknown component: ${componentFilter}`, exitCode: 1 };
|
|
598
|
+
}
|
|
599
|
+
results = findings.filter(f => f.component === known);
|
|
600
|
+
if (results.length === 0) {
|
|
601
|
+
results = [{ component: known, bottleneck: 'No bottlenecks detected', severity: output.success('None'), solution: `${known} is performing well` }];
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
else if (results.length === 0) {
|
|
605
|
+
results = [{ component: 'System', bottleneck: 'No bottlenecks detected', severity: output.success('None'), solution: 'System is performing well' }];
|
|
580
606
|
}
|
|
581
|
-
spinner.succeed(`Analysis complete — ${
|
|
607
|
+
spinner.succeed(`Analysis complete — ${results.length} finding(s)`);
|
|
582
608
|
output.writeln();
|
|
583
609
|
output.printTable({
|
|
584
610
|
columns: [
|
|
@@ -587,7 +613,7 @@ const bottleneckCommand = {
|
|
|
587
613
|
{ key: 'severity', header: 'Severity', width: 12 },
|
|
588
614
|
{ key: 'solution', header: 'Solution', width: 50 },
|
|
589
615
|
],
|
|
590
|
-
data:
|
|
616
|
+
data: results,
|
|
591
617
|
});
|
|
592
618
|
return { success: true };
|
|
593
619
|
},
|
|
@@ -11,10 +11,11 @@ export const auditCommand = {
|
|
|
11
11
|
options: [
|
|
12
12
|
{ name: 'action', short: 'a', type: 'string', description: 'Action: list (only supported value — log/export/clear are not implemented)', default: 'list' },
|
|
13
13
|
{ name: 'limit', short: 'l', type: 'number', description: 'Number of entries to show', default: '20' },
|
|
14
|
-
{ name: 'filter', short: 'f', type: 'string', description: 'Filter by event type' },
|
|
14
|
+
{ name: 'filter', short: 'f', type: 'string', description: 'Filter by event type (substring, case-insensitive)' },
|
|
15
15
|
],
|
|
16
16
|
examples: [
|
|
17
17
|
{ command: 'monomind security audit --action list', description: 'List audit logs' },
|
|
18
|
+
{ command: 'monomind security audit --filter SWARM', description: 'Only swarm activity events' },
|
|
18
19
|
],
|
|
19
20
|
action: async (ctx) => {
|
|
20
21
|
const requestedAction = ctx.flags.action || 'list';
|
|
@@ -54,7 +55,21 @@ export const auditCommand = {
|
|
|
54
55
|
const now = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
55
56
|
auditEntries.push({ timestamp: now, event: 'AUDIT_RUN', user: 'cli', status: output.success('Success') });
|
|
56
57
|
auditEntries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
57
|
-
|
|
58
|
+
// --filter was previously parsed and discarded, so every invocation
|
|
59
|
+
// returned the full log regardless of what the user asked for.
|
|
60
|
+
const eventFilter = ctx.flags.filter?.trim();
|
|
61
|
+
let visibleEntries = auditEntries;
|
|
62
|
+
if (eventFilter) {
|
|
63
|
+
const needle = eventFilter.toLowerCase();
|
|
64
|
+
visibleEntries = auditEntries.filter(e => e.event.toLowerCase().includes(needle));
|
|
65
|
+
if (visibleEntries.length === 0) {
|
|
66
|
+
const seen = [...new Set(auditEntries.map(e => e.event))].sort();
|
|
67
|
+
output.writeln(output.warning(`No audit events match "${eventFilter}".`));
|
|
68
|
+
output.writeln(output.dim(`Event types present: ${seen.join(', ') || 'none'}`));
|
|
69
|
+
return { success: true, data: { entries: [], filter: eventFilter } };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (visibleEntries.length === 0) {
|
|
58
73
|
output.writeln(output.dim('No audit events found. Initialize a project first: monomind init'));
|
|
59
74
|
}
|
|
60
75
|
else {
|
|
@@ -65,7 +80,7 @@ export const auditCommand = {
|
|
|
65
80
|
{ key: 'user', header: 'User', width: 15 },
|
|
66
81
|
{ key: 'status', header: 'Status', width: 12 },
|
|
67
82
|
],
|
|
68
|
-
data:
|
|
83
|
+
data: visibleEntries.slice(0, parseInt(ctx.flags.limit || '20', 10)),
|
|
69
84
|
});
|
|
70
85
|
}
|
|
71
86
|
return { success: true };
|
|
@@ -110,6 +110,83 @@ export class MCPClientError extends Error {
|
|
|
110
110
|
this.name = 'MCPClientError';
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Runtime JSON-Schema type name for a JS value, or undefined for values we do
|
|
115
|
+
* not model (functions, symbols, bigint). `null` is reported as 'null' so a
|
|
116
|
+
* declared `type: 'object'` does not silently accept it.
|
|
117
|
+
*
|
|
118
|
+
* Note the two JS/JSON mismatches this has to paper over: arrays are objects
|
|
119
|
+
* in JS but a distinct type in JSON Schema, and JSON has no integer type —
|
|
120
|
+
* `integer` is a *number* with a constraint, so `3` satisfies both `number`
|
|
121
|
+
* and `integer` while `3.5` satisfies only `number`.
|
|
122
|
+
*/
|
|
123
|
+
function jsonTypeOf(value) {
|
|
124
|
+
if (value === null)
|
|
125
|
+
return 'null';
|
|
126
|
+
if (Array.isArray(value))
|
|
127
|
+
return 'array';
|
|
128
|
+
switch (typeof value) {
|
|
129
|
+
case 'string': return 'string';
|
|
130
|
+
case 'boolean': return 'boolean';
|
|
131
|
+
case 'number': return Number.isInteger(value) ? 'integer' : 'number';
|
|
132
|
+
case 'object': return 'object';
|
|
133
|
+
default: return undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function matchesDeclaredType(declared, actual) {
|
|
137
|
+
if (declared === actual)
|
|
138
|
+
return true;
|
|
139
|
+
// Every integer is a valid number; the reverse is not true.
|
|
140
|
+
if (declared === 'number' && actual === 'integer')
|
|
141
|
+
return true;
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Check present arguments against the `type` each property declares in the
|
|
146
|
+
* tool's inputSchema and WARN on a mismatch — deliberately non-fatal for now.
|
|
147
|
+
*
|
|
148
|
+
* `required` is already hard-enforced above; `type` is not, because nothing
|
|
149
|
+
* ever checked it, so a tool declaring `{type: 'string'}` has always been free
|
|
150
|
+
* to receive a number and reach its handler. Turning that into a throw without
|
|
151
|
+
* knowing how many real callers violate their own schemas would break working
|
|
152
|
+
* code, so this logs first: run the suite, count the warnings, then decide.
|
|
153
|
+
*
|
|
154
|
+
* Absent properties are ignored — that is `required`'s job, not this one's.
|
|
155
|
+
* Explicit null is also ignored here for the same reason (the required check
|
|
156
|
+
* already rejects it for required params, and an optional param set to null is
|
|
157
|
+
* an "unset" idiom, not a type error).
|
|
158
|
+
*/
|
|
159
|
+
function warnOnTypeMismatch(toolName, schema, input) {
|
|
160
|
+
const properties = schema?.properties;
|
|
161
|
+
if (!properties || typeof properties !== 'object')
|
|
162
|
+
return;
|
|
163
|
+
for (const [key, value] of Object.entries(input)) {
|
|
164
|
+
if (value === undefined || value === null)
|
|
165
|
+
continue;
|
|
166
|
+
const prop = properties[key];
|
|
167
|
+
if (!prop || typeof prop !== 'object')
|
|
168
|
+
continue;
|
|
169
|
+
const declared = prop.type;
|
|
170
|
+
// Union types (`type: ['string','number']`) pass if any branch matches.
|
|
171
|
+
const declaredList = typeof declared === 'string'
|
|
172
|
+
? [declared]
|
|
173
|
+
: Array.isArray(declared) && declared.every(t => typeof t === 'string')
|
|
174
|
+
? declared
|
|
175
|
+
: undefined;
|
|
176
|
+
if (!declaredList || declaredList.length === 0)
|
|
177
|
+
continue;
|
|
178
|
+
const actual = jsonTypeOf(value);
|
|
179
|
+
if (!actual)
|
|
180
|
+
continue;
|
|
181
|
+
if (declaredList.some(d => matchesDeclaredType(d, actual)))
|
|
182
|
+
continue;
|
|
183
|
+
// Report `integer` as `number` — the distinction is an artefact of how we
|
|
184
|
+
// classify JS numbers, not something the caller passed.
|
|
185
|
+
const reported = actual === 'integer' ? 'number' : actual;
|
|
186
|
+
console.error(`[mcp] tool '${toolName}' param '${key}': schema declares ` +
|
|
187
|
+
`${declaredList.join('|')}, got ${reported}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
113
190
|
/**
|
|
114
191
|
* Call an MCP tool by name with input parameters
|
|
115
192
|
*/
|
|
@@ -142,6 +219,7 @@ export async function callMCPTool(toolName, input = {}, context) {
|
|
|
142
219
|
throw new MCPClientError(`MCP tool '${toolName}' missing required parameter${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}`, toolName);
|
|
143
220
|
}
|
|
144
221
|
}
|
|
222
|
+
warnOnTypeMismatch(toolName, tool.inputSchema, input);
|
|
145
223
|
try {
|
|
146
224
|
const result = await tool.handler(input, context);
|
|
147
225
|
return result;
|
|
@@ -63,7 +63,11 @@ export declare function bridgeSearchEntries(options: {
|
|
|
63
63
|
tags?: string[];
|
|
64
64
|
}[];
|
|
65
65
|
searchTime: number;
|
|
66
|
-
|
|
66
|
+
/** What actually ran, never what was requested. 'keyword-fallback' means the
|
|
67
|
+
* vector path was attempted and did not produce the results. */
|
|
68
|
+
searchMethod?: 'semantic' | 'keyword' | 'keyword-fallback';
|
|
69
|
+
/** Why the vector path did not serve these results (absent when it did). */
|
|
70
|
+
fallbackReason?: 'no-embedding-model' | 'embedding-failed' | 'no-semantic-matches';
|
|
67
71
|
error?: string;
|
|
68
72
|
} | null>;
|
|
69
73
|
export declare function bridgeListEntries(options: {
|
|
@@ -406,7 +406,11 @@ export async function bridgeSearchEntries(options) {
|
|
|
406
406
|
const startTime = Date.now();
|
|
407
407
|
let results = [];
|
|
408
408
|
let searchMethod = 'keyword';
|
|
409
|
+
// Reported to callers so "(semantic)" can never be printed over keyword hits.
|
|
410
|
+
let fallbackReason = _embedder && queryStr.length > 0 ? undefined : 'no-embedding-model';
|
|
411
|
+
let semanticAttempted = false;
|
|
409
412
|
if (_embedder && queryStr.length > 0) {
|
|
413
|
+
semanticAttempted = true;
|
|
410
414
|
try {
|
|
411
415
|
const queryEmbedding = await _embedder(queryStr);
|
|
412
416
|
const searchResults = await backend.search(queryEmbedding, {
|
|
@@ -431,8 +435,14 @@ export async function bridgeSearchEntries(options) {
|
|
|
431
435
|
};
|
|
432
436
|
}).sort((a, b) => b.score - a.score);
|
|
433
437
|
searchMethod = 'semantic';
|
|
438
|
+
fallbackReason = undefined;
|
|
439
|
+
}
|
|
440
|
+
catch (e) {
|
|
441
|
+
// fall through to keyword search — but never claim this was semantic
|
|
442
|
+
fallbackReason = 'embedding-failed';
|
|
443
|
+
if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
|
|
444
|
+
console.error('[memory-bridge] semantic search failed — falling back to keyword matching:', e);
|
|
434
445
|
}
|
|
435
|
-
catch { /* fall through to keyword search */ }
|
|
436
446
|
}
|
|
437
447
|
// Keyword fallback — scan all entries in namespace (not just first 100)
|
|
438
448
|
// to avoid missing documents that were ingested later in the batch.
|
|
@@ -463,14 +473,22 @@ export async function bridgeSearchEntries(options) {
|
|
|
463
473
|
id: e.id,
|
|
464
474
|
key: e.key,
|
|
465
475
|
content: e.content || '',
|
|
466
|
-
|
|
476
|
+
// Raw token-overlap fraction, NOT rescaled to look like a cosine.
|
|
477
|
+
// The old `min(0.9, 0.3 + score*0.6)` floor/ceiling made a weak
|
|
478
|
+
// keyword hit outrank a genuine cosine match (0.90 vs 0.63 for the
|
|
479
|
+
// same entry) and fed cosine-calibrated gates (memory-kg dedup)
|
|
480
|
+
// scores that never came from a vector.
|
|
481
|
+
score,
|
|
467
482
|
namespace: e.namespace,
|
|
468
483
|
provenance: `keyword:${score.toFixed(2)}`,
|
|
469
484
|
tags: e.tags ?? [],
|
|
470
485
|
_createdAt: e.createdAt || 0,
|
|
471
486
|
}));
|
|
472
487
|
}
|
|
473
|
-
|
|
488
|
+
// The vector path ran and simply matched nothing — still not semantic.
|
|
489
|
+
searchMethod = semanticAttempted ? 'keyword-fallback' : 'keyword';
|
|
490
|
+
if (semanticAttempted && !fallbackReason)
|
|
491
|
+
fallbackReason = 'no-semantic-matches';
|
|
474
492
|
}
|
|
475
493
|
// Filter stale entries based on automem config — skip for knowledge
|
|
476
494
|
// namespaces (documents should remain searchable indefinitely)
|
|
@@ -494,6 +512,7 @@ export async function bridgeSearchEntries(options) {
|
|
|
494
512
|
results,
|
|
495
513
|
searchTime: Date.now() - startTime,
|
|
496
514
|
searchMethod,
|
|
515
|
+
...(searchMethod === 'semantic' ? {} : { fallbackReason }),
|
|
497
516
|
};
|
|
498
517
|
}
|
|
499
518
|
catch {
|
|
@@ -25,6 +25,10 @@ export declare function searchEntries(options: {
|
|
|
25
25
|
namespace: string;
|
|
26
26
|
}[];
|
|
27
27
|
searchTime: number;
|
|
28
|
+
/** What actually ran — propagated from the bridge so callers can report the
|
|
29
|
+
* real method instead of echoing the requested one. */
|
|
30
|
+
searchMethod?: 'semantic' | 'keyword' | 'keyword-fallback' | 'hybrid';
|
|
31
|
+
fallbackReason?: string;
|
|
28
32
|
error?: string;
|
|
29
33
|
}>;
|
|
30
34
|
/**
|
|
@@ -66,7 +66,8 @@ export async function searchEntries(options) {
|
|
|
66
66
|
return {
|
|
67
67
|
success: true,
|
|
68
68
|
results: filtered,
|
|
69
|
-
searchTime: Date.now() - startTime
|
|
69
|
+
searchTime: Date.now() - startTime,
|
|
70
|
+
searchMethod: 'semantic'
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
73
|
// Fall back to brute-force SQLite search
|
|
@@ -127,7 +128,9 @@ export async function searchEntries(options) {
|
|
|
127
128
|
return {
|
|
128
129
|
success: true,
|
|
129
130
|
results: results.slice(0, limit),
|
|
130
|
-
searchTime: Date.now() - startTime
|
|
131
|
+
searchTime: Date.now() - startTime,
|
|
132
|
+
// Per-row: cosine when the entry had a vector, keyword overlap otherwise.
|
|
133
|
+
searchMethod: 'hybrid'
|
|
131
134
|
};
|
|
132
135
|
}
|
|
133
136
|
catch (error) {
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pick the argv prefix used to spawn the embedding worker.
|
|
3
|
+
*
|
|
4
|
+
* dist/: `embed-worker.js` exists → `node embed-worker.js <task>`, byte-identical
|
|
5
|
+
* to the original behaviour, no extra flags, no extra resolution.
|
|
6
|
+
*
|
|
7
|
+
* src/ (tsx / vitest): only `embed-worker.ts` exists. Node's
|
|
8
|
+
* `--experimental-strip-types` cannot run it — the worker imports
|
|
9
|
+
* `./embedder.js`, and type-stripping does no `.js`→`.ts` extension rewriting,
|
|
10
|
+
* so it dies with ERR_MODULE_NOT_FOUND. tsx does rewrite, so the source path
|
|
11
|
+
* runs through tsx's CLI (a devDependency, never needed by dist/).
|
|
12
|
+
*
|
|
13
|
+
* Exported for tests.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveWorkerArgv(): string[];
|
|
1
16
|
type RouteResult = any;
|
|
2
17
|
export interface ConfiguredRouteLayer {
|
|
3
18
|
route: (taskDescription: string) => Promise<RouteResult>;
|
|
@@ -18,9 +18,44 @@
|
|
|
18
18
|
// loading onnxruntime in-process deterministically SIGSEGVs. The model lives
|
|
19
19
|
// ONLY in embed-worker.ts, reached via the spawn below.
|
|
20
20
|
import { spawn } from 'child_process';
|
|
21
|
+
import { existsSync } from 'fs';
|
|
22
|
+
import { createRequire } from 'module';
|
|
21
23
|
import { fileURLToPath } from 'url';
|
|
22
24
|
import { dirname, join } from 'path';
|
|
23
|
-
const
|
|
25
|
+
const WORKER_DIR = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
/** Compiled worker — the only path production (dist/) ever takes. */
|
|
27
|
+
const COMPILED_WORKER_PATH = join(WORKER_DIR, 'embed-worker.js');
|
|
28
|
+
/** TypeScript sibling — only present when running from src/ (tsx, vitest). */
|
|
29
|
+
const SOURCE_WORKER_PATH = join(WORKER_DIR, 'embed-worker.ts');
|
|
30
|
+
/**
|
|
31
|
+
* Pick the argv prefix used to spawn the embedding worker.
|
|
32
|
+
*
|
|
33
|
+
* dist/: `embed-worker.js` exists → `node embed-worker.js <task>`, byte-identical
|
|
34
|
+
* to the original behaviour, no extra flags, no extra resolution.
|
|
35
|
+
*
|
|
36
|
+
* src/ (tsx / vitest): only `embed-worker.ts` exists. Node's
|
|
37
|
+
* `--experimental-strip-types` cannot run it — the worker imports
|
|
38
|
+
* `./embedder.js`, and type-stripping does no `.js`→`.ts` extension rewriting,
|
|
39
|
+
* so it dies with ERR_MODULE_NOT_FOUND. tsx does rewrite, so the source path
|
|
40
|
+
* runs through tsx's CLI (a devDependency, never needed by dist/).
|
|
41
|
+
*
|
|
42
|
+
* Exported for tests.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveWorkerArgv() {
|
|
45
|
+
if (existsSync(COMPILED_WORKER_PATH))
|
|
46
|
+
return [COMPILED_WORKER_PATH];
|
|
47
|
+
if (existsSync(SOURCE_WORKER_PATH)) {
|
|
48
|
+
let tsxCli;
|
|
49
|
+
try {
|
|
50
|
+
tsxCli = createRequire(import.meta.url).resolve('tsx/cli');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new Error('embed worker: running from source but tsx is not installed (cannot execute embed-worker.ts)');
|
|
54
|
+
}
|
|
55
|
+
return [tsxCli, SOURCE_WORKER_PATH];
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`embed worker not found at ${COMPILED_WORKER_PATH}`);
|
|
58
|
+
}
|
|
24
59
|
/** Generous: the first-ever run computes + caches ~500 utterance embeddings. */
|
|
25
60
|
const WORKER_TIMEOUT_MS = 90_000;
|
|
26
61
|
/** Cap worker stdout so a runaway child can't grow parent memory unbounded. */
|
|
@@ -37,8 +72,15 @@ const RESULT_MARKER = '__ROUTE_RESULT__';
|
|
|
37
72
|
function runWorker(task) {
|
|
38
73
|
// Cap task length before passing as argv to prevent OOM/DoS via oversized args.
|
|
39
74
|
const safeTask = task.length > MAX_TASK_LENGTH ? task.slice(0, MAX_TASK_LENGTH) : task;
|
|
75
|
+
let workerArgv;
|
|
76
|
+
try {
|
|
77
|
+
workerArgv = resolveWorkerArgv();
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
81
|
+
}
|
|
40
82
|
return new Promise((resolve, reject) => {
|
|
41
|
-
const child = spawn(process.execPath, [
|
|
83
|
+
const child = spawn(process.execPath, [...workerArgv, safeTask], {
|
|
42
84
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
43
85
|
windowsHide: true,
|
|
44
86
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/monomindcli",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI engine for Monomind \u2014 an open-source MCP server that extends Claude Code with a codebase knowledge graph (tree-sitter + SQLite), persistent memory, multi-agent task coordination, and session hooks. MIT licensed, fully local.",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -92,13 +92,14 @@
|
|
|
92
92
|
"publish:all": "./scripts/publish.sh"
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
+
"tsx": "^4.21.0",
|
|
95
96
|
"typescript": "^7.0.2",
|
|
96
97
|
"vitest": "^4.1.4"
|
|
97
98
|
},
|
|
98
99
|
"dependencies": {
|
|
99
100
|
"@anthropic-ai/claude-agent-sdk": "^0.3.207",
|
|
100
101
|
"@monoes/monobrowse": "^1.0.6",
|
|
101
|
-
"@monoes/monodesign": "^1.2.
|
|
102
|
+
"@monoes/monodesign": "^1.2.1",
|
|
102
103
|
"@monoes/monograph": "^1.5.4",
|
|
103
104
|
"@noble/ed25519": "^2.1.0",
|
|
104
105
|
"mammoth": "^1.12.0",
|
|
@@ -110,9 +111,9 @@
|
|
|
110
111
|
"optionalDependencies": {
|
|
111
112
|
"@huggingface/transformers": "^3.8.1",
|
|
112
113
|
"@monoes/hooks": "^1.0.0",
|
|
113
|
-
"@monoes/mcp": "^1.0.
|
|
114
|
+
"@monoes/mcp": "^1.0.1",
|
|
114
115
|
"@monoes/memory": "^1.0.10",
|
|
115
|
-
"@monoes/routing": "^1.0.
|
|
116
|
+
"@monoes/routing": "^1.0.1",
|
|
116
117
|
"monofence-ai": "*",
|
|
117
118
|
"sql.js": "^1.14.1"
|
|
118
119
|
},
|