@monoes/cli 1.0.5 → 1.0.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/dist/src/init/statusline-generator.js +561 -242
- package/package.json +1 -1
|
@@ -23,8 +23,8 @@ export function generateStatuslineScript(options) {
|
|
|
23
23
|
const maxAgents = options.runtime.maxAgents;
|
|
24
24
|
return `#!/usr/bin/env node
|
|
25
25
|
/**
|
|
26
|
-
* Monobrain Statusline Generator (Optimized)
|
|
27
|
-
* Displays real-time
|
|
26
|
+
* Monobrain V1 Statusline Generator (Optimized)
|
|
27
|
+
* Displays real-time v1 implementation progress and system status
|
|
28
28
|
*
|
|
29
29
|
* Usage: node statusline.cjs [--json] [--compact]
|
|
30
30
|
*
|
|
@@ -49,6 +49,31 @@ const CONFIG = {
|
|
|
49
49
|
|
|
50
50
|
const CWD = process.cwd();
|
|
51
51
|
|
|
52
|
+
// Read version from nearest package.json
|
|
53
|
+
function getVersion() {
|
|
54
|
+
const scriptDir = path.dirname(__filename);
|
|
55
|
+
const walkCandidates = [
|
|
56
|
+
path.join(scriptDir, '..', '..', 'package.json'),
|
|
57
|
+
path.join(scriptDir, '..', '..', '..', 'package.json'),
|
|
58
|
+
path.join(scriptDir, '..', '..', '..', '..', 'package.json'),
|
|
59
|
+
];
|
|
60
|
+
for (const p of walkCandidates) {
|
|
61
|
+
try {
|
|
62
|
+
const pkg = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
63
|
+
if (pkg.version && (pkg.name === 'monobrain' || pkg.name === '@monoes/cli' || (pkg.name || '').startsWith('@monobrain'))) {
|
|
64
|
+
return \`v\${pkg.version}\`;
|
|
65
|
+
}
|
|
66
|
+
} catch { /* ignore */ }
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const prefix = execSync('npm config get prefix', { encoding: 'utf-8', timeout: 2000 }).trim();
|
|
70
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(prefix, 'lib', 'node_modules', 'monobrain', 'package.json'), 'utf-8'));
|
|
71
|
+
if (pkg.version) return \`v\${pkg.version}\`;
|
|
72
|
+
} catch { /* ignore */ }
|
|
73
|
+
return 'v1.0.6';
|
|
74
|
+
}
|
|
75
|
+
const VERSION = getVersion();
|
|
76
|
+
|
|
52
77
|
// ANSI colors
|
|
53
78
|
const c = {
|
|
54
79
|
reset: '\\x1b[0m',
|
|
@@ -130,7 +155,7 @@ function getGitInfo() {
|
|
|
130
155
|
'git rev-list --left-right --count HEAD...@{upstream} 2>/dev/null || echo "0 0"',
|
|
131
156
|
].join('; ');
|
|
132
157
|
|
|
133
|
-
const raw = safeExec(
|
|
158
|
+
const raw = safeExec(\`sh -c '\${script}'\`, 3000);
|
|
134
159
|
if (!raw) return result;
|
|
135
160
|
|
|
136
161
|
const parts = raw.split('---SEP---').map(s => s.trim());
|
|
@@ -158,44 +183,101 @@ function getGitInfo() {
|
|
|
158
183
|
return result;
|
|
159
184
|
}
|
|
160
185
|
|
|
186
|
+
// Normalise a model ID string to a short display name
|
|
187
|
+
function modelLabel(id) {
|
|
188
|
+
if (id.includes('opus')) return 'Opus 4.6';
|
|
189
|
+
if (id.includes('sonnet')) return 'Sonnet 4.6';
|
|
190
|
+
if (id.includes('haiku')) return 'Haiku 4.5';
|
|
191
|
+
return id.split('-').slice(1, 3).join(' ');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Read the last assistant model from the most recent session JSONL.
|
|
195
|
+
// Claude Code writes each assistant turn to ~/.claude/projects/<escaped-cwd>/<uuid>.jsonl
|
|
196
|
+
// with a "message.model" field — this is the most accurate live source and
|
|
197
|
+
// correctly reflects /model session overrides.
|
|
198
|
+
function getModelFromSessionJSONL() {
|
|
199
|
+
try {
|
|
200
|
+
// Escape CWD the same way Claude Code does: replace '/' with '-'
|
|
201
|
+
const escaped = CWD.replace(/\\//g, '-');
|
|
202
|
+
const projectsDir = path.join(os.homedir(), '.claude', 'projects', escaped);
|
|
203
|
+
if (!fs.existsSync(projectsDir)) return null;
|
|
204
|
+
|
|
205
|
+
// Most recently modified JSONL = current (or latest) session
|
|
206
|
+
const files = fs.readdirSync(projectsDir)
|
|
207
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
208
|
+
.map(f => ({ f, mt: (() => { try { return fs.statSync(path.join(projectsDir, f)).mtimeMs; } catch { return 0; } })() }))
|
|
209
|
+
.sort((a, b) => b.mt - a.mt);
|
|
210
|
+
if (files.length === 0) return null;
|
|
211
|
+
|
|
212
|
+
const sessionFile = path.join(projectsDir, files[0].f);
|
|
213
|
+
const raw = fs.readFileSync(sessionFile, 'utf-8');
|
|
214
|
+
const lines = raw.split('\\n').filter(Boolean);
|
|
215
|
+
|
|
216
|
+
// Scan from the end to find the most recent assistant model
|
|
217
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
218
|
+
try {
|
|
219
|
+
const entry = JSON.parse(lines[i]);
|
|
220
|
+
const model = entry?.message?.model || entry?.model;
|
|
221
|
+
if (model && typeof model === 'string' && model.startsWith('claude')) {
|
|
222
|
+
return model;
|
|
223
|
+
}
|
|
224
|
+
} catch { /* skip malformed line */ }
|
|
225
|
+
}
|
|
226
|
+
} catch { /* ignore */ }
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
161
230
|
// Detect model name from Claude config (pure file reads, no exec)
|
|
162
231
|
function getModelName() {
|
|
232
|
+
// PRIMARY: scan the live session JSONL — reflects /model overrides in real time
|
|
233
|
+
const sessionModel = getModelFromSessionJSONL();
|
|
234
|
+
if (sessionModel) return modelLabel(sessionModel);
|
|
235
|
+
|
|
236
|
+
// SECONDARY: ~/.claude.json lastModelUsage for this exact project path
|
|
237
|
+
// (longest-prefix match to avoid short paths like /Users matching first)
|
|
163
238
|
try {
|
|
164
239
|
const claudeConfig = readJSON(path.join(os.homedir(), '.claude.json'));
|
|
165
|
-
if (claudeConfig
|
|
240
|
+
if (claudeConfig?.projects) {
|
|
241
|
+
let bestMatch = null;
|
|
242
|
+
let bestLen = -1;
|
|
166
243
|
for (const [projectPath, projectConfig] of Object.entries(claudeConfig.projects)) {
|
|
167
244
|
if (CWD === projectPath || CWD.startsWith(projectPath + '/')) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (ids.length > 0) {
|
|
172
|
-
let modelId = ids[ids.length - 1];
|
|
173
|
-
let latest = 0;
|
|
174
|
-
for (const id of ids) {
|
|
175
|
-
const ts = usage[id] && usage[id].lastUsedAt ? new Date(usage[id].lastUsedAt).getTime() : 0;
|
|
176
|
-
if (ts > latest) { latest = ts; modelId = id; }
|
|
177
|
-
}
|
|
178
|
-
if (modelId.includes('opus')) return 'Opus 4.6 (1M context)';
|
|
179
|
-
if (modelId.includes('sonnet')) return 'Sonnet 4.6';
|
|
180
|
-
if (modelId.includes('haiku')) return 'Haiku 4.5';
|
|
181
|
-
return modelId.split('-').slice(1, 3).join(' ');
|
|
182
|
-
}
|
|
245
|
+
if (projectPath.length > bestLen) {
|
|
246
|
+
bestLen = projectPath.length;
|
|
247
|
+
bestMatch = projectConfig;
|
|
183
248
|
}
|
|
184
|
-
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (bestMatch?.lastModelUsage) {
|
|
252
|
+
const usage = bestMatch.lastModelUsage;
|
|
253
|
+
const ids = Object.keys(usage);
|
|
254
|
+
if (ids.length > 0) {
|
|
255
|
+
let bestId = ids[ids.length - 1];
|
|
256
|
+
let bestTokens = -1;
|
|
257
|
+
for (const id of ids) {
|
|
258
|
+
const e = usage[id] || {};
|
|
259
|
+
const tokens = (e.inputTokens || 0) + (e.outputTokens || 0);
|
|
260
|
+
if (tokens > bestTokens) { bestTokens = tokens; bestId = id; }
|
|
261
|
+
}
|
|
262
|
+
return modelLabel(bestId);
|
|
185
263
|
}
|
|
186
264
|
}
|
|
187
265
|
}
|
|
188
266
|
} catch { /* ignore */ }
|
|
189
267
|
|
|
190
|
-
//
|
|
268
|
+
// TERTIARY: settings.json model field (configured default, not live session).
|
|
191
269
|
const settings = getSettings();
|
|
192
|
-
if (settings
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
270
|
+
if (settings?.model) return modelLabel(settings.model);
|
|
271
|
+
|
|
272
|
+
// QUATERNARY: read ANTHROPIC_MODEL or CLAUDE_MODEL env var (set by the CLI at launch)
|
|
273
|
+
const envModel = process.env.ANTHROPIC_MODEL || process.env.CLAUDE_MODEL || process.env.MODEL;
|
|
274
|
+
if (envModel && envModel.startsWith('claude')) return modelLabel(envModel);
|
|
275
|
+
|
|
276
|
+
// QUINARY: current model from the model ID in the env injected by Claude Code itself
|
|
277
|
+
const claudeModel = process.env.CLAUDE_CODE_MODEL;
|
|
278
|
+
if (claudeModel) return modelLabel(claudeModel);
|
|
279
|
+
|
|
280
|
+
return 'Sonnet 4.6'; // known current default rather than the generic "Claude Code"
|
|
199
281
|
}
|
|
200
282
|
|
|
201
283
|
// Get learning stats from memory database (pure stat calls)
|
|
@@ -233,12 +315,12 @@ function getLearningStats() {
|
|
|
233
315
|
}
|
|
234
316
|
|
|
235
317
|
// progress from metrics files (pure file reads)
|
|
236
|
-
function
|
|
318
|
+
function getv1Progress() {
|
|
237
319
|
const learning = getLearningStats();
|
|
238
320
|
const totalDomains = 5;
|
|
239
321
|
|
|
240
322
|
const dddData = readJSON(path.join(CWD, '.monobrain', 'metrics', 'ddd-progress.json'));
|
|
241
|
-
let dddProgress = dddData
|
|
323
|
+
let dddProgress = dddData?.progress || 0;
|
|
242
324
|
let domainsCompleted = Math.min(5, Math.floor(dddProgress / 20));
|
|
243
325
|
|
|
244
326
|
if (dddProgress === 0 && learning.patterns > 0) {
|
|
@@ -263,6 +345,7 @@ function getSecurityStatus() {
|
|
|
263
345
|
if (auditData) {
|
|
264
346
|
const auditDate = auditData.lastAudit || auditData.lastScan;
|
|
265
347
|
if (!auditDate) {
|
|
348
|
+
// No audit has ever run — show as pending, not stale
|
|
266
349
|
return { status: 'PENDING', cvesFixed: 0, totalCves: 0 };
|
|
267
350
|
}
|
|
268
351
|
const auditAge = Date.now() - new Date(auditDate).getTime();
|
|
@@ -292,8 +375,31 @@ function getSecurityStatus() {
|
|
|
292
375
|
// Swarm status (pure file reads, NO ps aux)
|
|
293
376
|
function getSwarmStatus() {
|
|
294
377
|
const staleThresholdMs = 5 * 60 * 1000;
|
|
378
|
+
const agentRegTtlMs = 30 * 60 * 1000; // registration files expire after 30 min
|
|
295
379
|
const now = Date.now();
|
|
296
380
|
|
|
381
|
+
// PRIMARY: count live registration files written by SubagentStart hook
|
|
382
|
+
// Each file = one active sub-agent. Stale files (>30 min) are ignored.
|
|
383
|
+
const regDir = path.join(CWD, '.monobrain', 'agents', 'registrations');
|
|
384
|
+
if (fs.existsSync(regDir)) {
|
|
385
|
+
try {
|
|
386
|
+
const files = fs.readdirSync(regDir).filter(f => f.endsWith('.json'));
|
|
387
|
+
const liveCount = files.filter(f => {
|
|
388
|
+
try {
|
|
389
|
+
return (now - fs.statSync(path.join(regDir, f)).mtimeMs) < agentRegTtlMs;
|
|
390
|
+
} catch { return false; }
|
|
391
|
+
}).length;
|
|
392
|
+
if (liveCount > 0) {
|
|
393
|
+
return {
|
|
394
|
+
activeAgents: liveCount,
|
|
395
|
+
maxAgents: CONFIG.maxAgents,
|
|
396
|
+
coordinationActive: true,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
} catch { /* fall through */ }
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// SECONDARY: swarm-state.json written by MCP swarm_init — trust if fresh
|
|
297
403
|
const swarmStatePath = path.join(CWD, '.monobrain', 'swarm', 'swarm-state.json');
|
|
298
404
|
const swarmState = readJSON(swarmStatePath);
|
|
299
405
|
if (swarmState) {
|
|
@@ -301,16 +407,17 @@ function getSwarmStatus() {
|
|
|
301
407
|
const age = updatedAt ? now - new Date(updatedAt).getTime() : Infinity;
|
|
302
408
|
if (age < staleThresholdMs) {
|
|
303
409
|
return {
|
|
304
|
-
activeAgents:
|
|
410
|
+
activeAgents: swarmState.agents?.length || swarmState.agentCount || 0,
|
|
305
411
|
maxAgents: swarmState.maxAgents || CONFIG.maxAgents,
|
|
306
412
|
coordinationActive: true,
|
|
307
413
|
};
|
|
308
414
|
}
|
|
309
415
|
}
|
|
310
416
|
|
|
417
|
+
// TERTIARY: swarm-activity.json refreshed by post-task hook
|
|
311
418
|
const activityData = readJSON(path.join(CWD, '.monobrain', 'metrics', 'swarm-activity.json'));
|
|
312
|
-
if (activityData
|
|
313
|
-
const updatedAt = activityData.timestamp ||
|
|
419
|
+
if (activityData?.swarm) {
|
|
420
|
+
const updatedAt = activityData.timestamp || activityData.swarm.timestamp;
|
|
314
421
|
const age = updatedAt ? now - new Date(updatedAt).getTime() : Infinity;
|
|
315
422
|
if (age < staleThresholdMs) {
|
|
316
423
|
return {
|
|
@@ -335,9 +442,10 @@ function getSystemMetrics() {
|
|
|
335
442
|
let intelligencePct = 0;
|
|
336
443
|
let contextPct = 0;
|
|
337
444
|
|
|
338
|
-
if (learningData
|
|
445
|
+
if (learningData?.intelligence?.score !== undefined) {
|
|
339
446
|
intelligencePct = Math.min(100, Math.floor(learningData.intelligence.score));
|
|
340
447
|
} else {
|
|
448
|
+
// Use actual vector/entry counts — 2000 entries = 100%
|
|
341
449
|
const fromPatterns = learning.patterns > 0 ? Math.min(100, Math.floor(learning.patterns / 20)) : 0;
|
|
342
450
|
const fromVectors = agentdb.vectorCount > 0 ? Math.min(100, Math.floor(agentdb.vectorCount / 20)) : 0;
|
|
343
451
|
intelligencePct = Math.max(fromPatterns, fromVectors);
|
|
@@ -356,7 +464,7 @@ function getSystemMetrics() {
|
|
|
356
464
|
intelligencePct = Math.min(100, score);
|
|
357
465
|
}
|
|
358
466
|
|
|
359
|
-
if (learningData
|
|
467
|
+
if (learningData?.sessions?.total !== undefined) {
|
|
360
468
|
contextPct = Math.min(100, learningData.sessions.total * 5);
|
|
361
469
|
} else {
|
|
362
470
|
contextPct = Math.min(100, Math.floor(learning.sessions * 5));
|
|
@@ -365,7 +473,7 @@ function getSystemMetrics() {
|
|
|
365
473
|
// Sub-agents from file metrics (no ps aux)
|
|
366
474
|
let subAgents = 0;
|
|
367
475
|
const activityData = readJSON(path.join(CWD, '.monobrain', 'metrics', 'swarm-activity.json'));
|
|
368
|
-
if (activityData
|
|
476
|
+
if (activityData?.processes?.estimated_agents) {
|
|
369
477
|
subAgents = activityData.processes.estimated_agents;
|
|
370
478
|
}
|
|
371
479
|
|
|
@@ -387,6 +495,7 @@ function getADRStatus() {
|
|
|
387
495
|
const files = fs.readdirSync(adrPath).filter(f =>
|
|
388
496
|
f.endsWith('.md') && (f.startsWith('ADR-') || f.startsWith('adr-') || /^\\d{4}-/.test(f))
|
|
389
497
|
);
|
|
498
|
+
// Report actual count — don't guess compliance without reading files
|
|
390
499
|
return { count: files.length, implemented: files.length, compliance: 0 };
|
|
391
500
|
}
|
|
392
501
|
} catch { /* ignore */ }
|
|
@@ -401,12 +510,12 @@ function getHooksStatus() {
|
|
|
401
510
|
let total = 0;
|
|
402
511
|
const settings = getSettings();
|
|
403
512
|
|
|
404
|
-
if (settings
|
|
513
|
+
if (settings?.hooks) {
|
|
405
514
|
for (const category of Object.keys(settings.hooks)) {
|
|
406
515
|
const matchers = settings.hooks[category];
|
|
407
516
|
if (!Array.isArray(matchers)) continue;
|
|
408
517
|
for (const matcher of matchers) {
|
|
409
|
-
const hooks = matcher
|
|
518
|
+
const hooks = matcher?.hooks;
|
|
410
519
|
if (Array.isArray(hooks)) {
|
|
411
520
|
total += hooks.length;
|
|
412
521
|
enabled += hooks.length;
|
|
@@ -427,6 +536,33 @@ function getHooksStatus() {
|
|
|
427
536
|
return { enabled, total };
|
|
428
537
|
}
|
|
429
538
|
|
|
539
|
+
// Active agent — reads last routing result persisted by hook-handler
|
|
540
|
+
function getActiveAgent() {
|
|
541
|
+
const routeFile = path.join(CWD, '.monobrain', 'last-route.json');
|
|
542
|
+
try {
|
|
543
|
+
if (!fs.existsSync(routeFile)) return null;
|
|
544
|
+
const data = JSON.parse(fs.readFileSync(routeFile, 'utf-8'));
|
|
545
|
+
if (!data || !data.agent) return null;
|
|
546
|
+
|
|
547
|
+
// Stale after 30 minutes (session likely changed)
|
|
548
|
+
const age = Date.now() - new Date(data.updatedAt || 0).getTime();
|
|
549
|
+
if (age > 30 * 60 * 1000) return null;
|
|
550
|
+
|
|
551
|
+
// Prefer display name if set (from load-agent), else format the slug
|
|
552
|
+
const displayName = data.name || data.agent
|
|
553
|
+
.replace(/-/g, ' ')
|
|
554
|
+
.replace(/\\b\\w/g, c => c.toUpperCase());
|
|
555
|
+
|
|
556
|
+
return {
|
|
557
|
+
slug: data.agent,
|
|
558
|
+
name: displayName,
|
|
559
|
+
category: data.category || null,
|
|
560
|
+
confidence: data.confidence || 0,
|
|
561
|
+
activated: data.activated || false, // true = manually loaded extras agent
|
|
562
|
+
};
|
|
563
|
+
} catch { return null; }
|
|
564
|
+
}
|
|
565
|
+
|
|
430
566
|
// AgentDB stats — count real entries, not file-size heuristics
|
|
431
567
|
function getAgentDBStats() {
|
|
432
568
|
let vectorCount = 0;
|
|
@@ -442,14 +578,15 @@ function getAgentDBStats() {
|
|
|
442
578
|
try {
|
|
443
579
|
const store = JSON.parse(fs.readFileSync(storePath, 'utf-8'));
|
|
444
580
|
if (Array.isArray(store)) vectorCount += store.length;
|
|
445
|
-
else if (store
|
|
446
|
-
} catch { /* fall back */ }
|
|
581
|
+
else if (store?.entries) vectorCount += store.entries.length;
|
|
582
|
+
} catch { /* fall back to size estimate */ }
|
|
447
583
|
}
|
|
448
584
|
|
|
449
585
|
// 2. Count entries from ranked-context.json
|
|
586
|
+
const rankedPath = path.join(CWD, '.monobrain', 'data', 'ranked-context.json');
|
|
450
587
|
try {
|
|
451
|
-
const ranked = readJSON(
|
|
452
|
-
if (ranked
|
|
588
|
+
const ranked = readJSON(rankedPath);
|
|
589
|
+
if (ranked?.entries?.length > vectorCount) vectorCount = ranked.entries.length;
|
|
453
590
|
} catch { /* ignore */ }
|
|
454
591
|
|
|
455
592
|
// 3. Add DB file sizes
|
|
@@ -466,18 +603,25 @@ function getAgentDBStats() {
|
|
|
466
603
|
}
|
|
467
604
|
}
|
|
468
605
|
|
|
469
|
-
// 4.
|
|
470
|
-
const
|
|
606
|
+
// 4. Check for graph data
|
|
607
|
+
const graphPath = path.join(CWD, 'data', 'memory.graph');
|
|
608
|
+
const graphStat = safeStat(graphPath);
|
|
471
609
|
if (graphStat) dbSizeKB += graphStat.size / 1024;
|
|
472
610
|
|
|
473
|
-
// 5. HNSW index
|
|
611
|
+
// 5. HNSW index
|
|
474
612
|
const hnswPaths = [
|
|
475
613
|
path.join(CWD, '.swarm', 'hnsw.index'),
|
|
476
614
|
path.join(CWD, '.monobrain', 'hnsw.index'),
|
|
477
615
|
];
|
|
478
616
|
for (const p of hnswPaths) {
|
|
479
|
-
|
|
617
|
+
const stat = safeStat(p);
|
|
618
|
+
if (stat) {
|
|
619
|
+
hasHnsw = true;
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
480
622
|
}
|
|
623
|
+
|
|
624
|
+
// HNSW is available if memory package is present
|
|
481
625
|
if (!hasHnsw) {
|
|
482
626
|
const memPkgPaths = [
|
|
483
627
|
path.join(CWD, 'packages', '@monobrain', 'memory', 'dist'),
|
|
@@ -495,8 +639,7 @@ function getAgentDBStats() {
|
|
|
495
639
|
function getTestStats() {
|
|
496
640
|
let testFiles = 0;
|
|
497
641
|
|
|
498
|
-
function countTestFiles(dir, depth) {
|
|
499
|
-
if (depth === undefined) depth = 0;
|
|
642
|
+
function countTestFiles(dir, depth = 0) {
|
|
500
643
|
if (depth > 6) return;
|
|
501
644
|
try {
|
|
502
645
|
if (!fs.existsSync(dir)) return;
|
|
@@ -514,11 +657,12 @@ function getTestStats() {
|
|
|
514
657
|
} catch { /* ignore */ }
|
|
515
658
|
}
|
|
516
659
|
|
|
517
|
-
|
|
518
|
-
for (
|
|
519
|
-
countTestFiles(path.join(CWD,
|
|
660
|
+
// Scan all source directories
|
|
661
|
+
for (const d of ['tests', 'test', '__tests__', 'src', 'v1']) {
|
|
662
|
+
countTestFiles(path.join(CWD, d));
|
|
520
663
|
}
|
|
521
664
|
|
|
665
|
+
// Estimate ~4 test cases per file (avoids reading every file)
|
|
522
666
|
return { testFiles, testCases: testFiles * 4 };
|
|
523
667
|
}
|
|
524
668
|
|
|
@@ -527,7 +671,7 @@ function getIntegrationStatus() {
|
|
|
527
671
|
const mcpServers = { total: 0, enabled: 0 };
|
|
528
672
|
const settings = getSettings();
|
|
529
673
|
|
|
530
|
-
if (settings
|
|
674
|
+
if (settings?.mcpServers && typeof settings.mcpServers === 'object') {
|
|
531
675
|
const servers = Object.keys(settings.mcpServers);
|
|
532
676
|
mcpServers.total = servers.length;
|
|
533
677
|
mcpServers.enabled = settings.enabledMcpjsonServers
|
|
@@ -535,10 +679,11 @@ function getIntegrationStatus() {
|
|
|
535
679
|
: servers.length;
|
|
536
680
|
}
|
|
537
681
|
|
|
682
|
+
// Fallback: .mcp.json
|
|
538
683
|
if (mcpServers.total === 0) {
|
|
539
684
|
const mcpConfig = readJSON(path.join(CWD, '.mcp.json'))
|
|
540
685
|
|| readJSON(path.join(os.homedir(), '.claude', 'mcp.json'));
|
|
541
|
-
if (mcpConfig
|
|
686
|
+
if (mcpConfig?.mcpServers) {
|
|
542
687
|
const s = Object.keys(mcpConfig.mcpServers);
|
|
543
688
|
mcpServers.total = s.length;
|
|
544
689
|
mcpServers.enabled = s.length;
|
|
@@ -554,237 +699,393 @@ function getIntegrationStatus() {
|
|
|
554
699
|
|
|
555
700
|
// Session stats (pure file reads)
|
|
556
701
|
function getSessionStats() {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (data && data.startTime) {
|
|
702
|
+
for (const p of ['.monobrain/session.json', '.claude/session.json']) {
|
|
703
|
+
const data = readJSON(path.join(CWD, p));
|
|
704
|
+
if (data?.startTime) {
|
|
561
705
|
const diffMs = Date.now() - new Date(data.startTime).getTime();
|
|
562
706
|
const mins = Math.floor(diffMs / 60000);
|
|
563
|
-
const duration = mins < 60 ? mins
|
|
564
|
-
return { duration
|
|
707
|
+
const duration = mins < 60 ? \`\${mins}m\` : \`\${Math.floor(mins / 60)}h\${mins % 60}m\`;
|
|
708
|
+
return { duration };
|
|
565
709
|
}
|
|
566
710
|
}
|
|
567
711
|
return { duration: '' };
|
|
568
712
|
}
|
|
569
713
|
|
|
570
|
-
// ───
|
|
714
|
+
// ─── Extended 256-color palette ─────────────────────────────────
|
|
715
|
+
const x = {
|
|
716
|
+
// Backgrounds (used sparingly for labels)
|
|
717
|
+
bgPurple: '\\x1b[48;5;55m',
|
|
718
|
+
bgTeal: '\\x1b[48;5;23m',
|
|
719
|
+
bgReset: '\\x1b[49m',
|
|
720
|
+
// Foregrounds
|
|
721
|
+
purple: '\\x1b[38;5;141m', // soft lavender-purple (brand)
|
|
722
|
+
violet: '\\x1b[38;5;99m', // deeper purple
|
|
723
|
+
teal: '\\x1b[38;5;51m', // bright teal
|
|
724
|
+
mint: '\\x1b[38;5;120m', // soft green
|
|
725
|
+
gold: '\\x1b[38;5;220m', // warm gold
|
|
726
|
+
orange: '\\x1b[38;5;208m', // alert orange
|
|
727
|
+
coral: '\\x1b[38;5;203m', // error red-pink
|
|
728
|
+
sky: '\\x1b[38;5;117m', // soft blue
|
|
729
|
+
rose: '\\x1b[38;5;218m', // warm pink
|
|
730
|
+
slate: '\\x1b[38;5;245m', // neutral grey
|
|
731
|
+
white: '\\x1b[38;5;255m', // bright white
|
|
732
|
+
green: '\\x1b[38;5;82m', // vivid green
|
|
733
|
+
red: '\\x1b[38;5;196m', // vivid red
|
|
734
|
+
yellow: '\\x1b[38;5;226m', // vivid yellow
|
|
735
|
+
// Shared
|
|
736
|
+
bold: '\\x1b[1m',
|
|
737
|
+
dim: '\\x1b[2m',
|
|
738
|
+
reset: '\\x1b[0m',
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
// ── Helpers ──────────────────────────────────────────────────────
|
|
742
|
+
|
|
743
|
+
// Block progress bar: ▰▰▰▱▱ (5 blocks)
|
|
744
|
+
function blockBar(current, total, width = 5) {
|
|
745
|
+
const filled = Math.min(width, Math.round((current / Math.max(total, 1)) * width));
|
|
746
|
+
return '\\u25B0'.repeat(filled) + \`\${x.slate}\\u25B1\${x.reset}\`.repeat(width - filled);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Health dot: ● colored by status
|
|
750
|
+
function dot(ok) {
|
|
751
|
+
if (ok === 'good') return \`\${x.green}●\${x.reset}\`;
|
|
752
|
+
if (ok === 'warn') return \`\${x.gold}●\${x.reset}\`;
|
|
753
|
+
if (ok === 'error') return \`\${x.coral}●\${x.reset}\`;
|
|
754
|
+
return \`\${x.slate}●\${x.reset}\`; // 'none'
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Pill badge: [ LABEL ] with background
|
|
758
|
+
function badge(label, color) {
|
|
759
|
+
return \`\${color}[\${label}]\${x.reset}\`;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Divider character
|
|
763
|
+
const DIV = \`\${x.slate}│\${x.reset}\`;
|
|
764
|
+
const SEP = \`\${x.slate}──────────────────────────────────────────────────────\${x.reset}\`;
|
|
765
|
+
|
|
766
|
+
// Pct → color
|
|
767
|
+
function pctColor(pct) {
|
|
768
|
+
if (pct >= 75) return x.green;
|
|
769
|
+
if (pct >= 40) return x.gold;
|
|
770
|
+
if (pct > 0) return x.orange;
|
|
771
|
+
return x.slate;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Security status → label + color
|
|
775
|
+
function secBadge(status) {
|
|
776
|
+
if (status === 'CLEAN') return { label: '✔ CLEAN', col: x.green };
|
|
777
|
+
if (status === 'STALE') return { label: '⟳ STALE', col: x.gold };
|
|
778
|
+
if (status === 'IN_PROGRESS') return { label: '⟳ RUNNING', col: x.sky };
|
|
779
|
+
if (status === 'SCANNED') return { label: '✔ SCANNED', col: x.mint };
|
|
780
|
+
if (status === 'PENDING') return { label: '⏸ PENDING', col: x.gold };
|
|
781
|
+
return { label: '✖ NONE', col: x.slate };
|
|
782
|
+
}
|
|
571
783
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const
|
|
575
|
-
|
|
784
|
+
// ── Knowledge & trigger stats (Tasks 28 + 32) ────────────────────
|
|
785
|
+
function getKnowledgeStats() {
|
|
786
|
+
const chunksPath = path.join(CWD, '.monobrain', 'knowledge', 'chunks.jsonl');
|
|
787
|
+
const skillsPath = path.join(CWD, '.monobrain', 'skills.jsonl');
|
|
788
|
+
let chunks = 0, skills = 0;
|
|
789
|
+
try {
|
|
790
|
+
if (fs.existsSync(chunksPath)) {
|
|
791
|
+
chunks = fs.readFileSync(chunksPath, 'utf-8').split('\\n').filter(Boolean).length;
|
|
792
|
+
}
|
|
793
|
+
} catch { /* ignore */ }
|
|
794
|
+
try {
|
|
795
|
+
if (fs.existsSync(skillsPath)) {
|
|
796
|
+
skills = fs.readFileSync(skillsPath, 'utf-8').split('\\n').filter(Boolean).length;
|
|
797
|
+
}
|
|
798
|
+
} catch { /* ignore */ }
|
|
799
|
+
return { chunks, skills };
|
|
576
800
|
}
|
|
577
801
|
|
|
802
|
+
function getTriggerStats() {
|
|
803
|
+
const indexPath = path.join(CWD, '.monobrain', 'trigger-index.json');
|
|
804
|
+
try {
|
|
805
|
+
if (!fs.existsSync(indexPath)) return { triggers: 0, agents: 0 };
|
|
806
|
+
const raw = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
|
|
807
|
+
const idx = raw.index || raw;
|
|
808
|
+
const triggers = Object.keys(idx).length;
|
|
809
|
+
const agents = Object.values(idx).flat().length;
|
|
810
|
+
return { triggers, agents };
|
|
811
|
+
} catch { return { triggers: 0, agents: 0 }; }
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function getSIBudget() {
|
|
815
|
+
const SI_LIMIT = 1500;
|
|
816
|
+
const siPath = path.join(CWD, '.agents', 'shared_instructions.md');
|
|
817
|
+
try {
|
|
818
|
+
if (!fs.existsSync(siPath)) return null;
|
|
819
|
+
const len = fs.readFileSync(siPath, 'utf-8').length;
|
|
820
|
+
return { len, pct: Math.round((len / SI_LIMIT) * 100), limit: SI_LIMIT };
|
|
821
|
+
} catch { return null; }
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// ── Single-line statusline (compact) ─────────────────────────────
|
|
578
825
|
function generateStatusline() {
|
|
579
|
-
const git
|
|
580
|
-
|
|
581
|
-
const
|
|
582
|
-
const
|
|
583
|
-
const
|
|
584
|
-
const
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
826
|
+
const git = getGitInfo();
|
|
827
|
+
const swarm = getSwarmStatus();
|
|
828
|
+
const system = getSystemMetrics();
|
|
829
|
+
const hooks = getHooksStatus();
|
|
830
|
+
const knowledge = getKnowledgeStats();
|
|
831
|
+
const triggers = getTriggerStats();
|
|
832
|
+
const parts = [];
|
|
833
|
+
|
|
834
|
+
// Brand + swarm dot
|
|
835
|
+
const swarmDot = swarm.coordinationActive ? \`\${x.green}●\${x.reset}\` : \`\${x.slate}○\${x.reset}\`;
|
|
836
|
+
parts.push(\`\${x.bold}\${x.purple}▊ Monobrain\${x.reset} \${swarmDot}\`);
|
|
837
|
+
|
|
838
|
+
// Git branch + changes (compact)
|
|
839
|
+
if (git.gitBranch) {
|
|
840
|
+
let b = \`\${x.sky}⎇ \${x.bold}\${git.gitBranch}\${x.reset}\`;
|
|
841
|
+
if (git.staged > 0) b += \` \${x.green}+\${git.staged}\${x.reset}\`;
|
|
842
|
+
if (git.modified > 0) b += \` \${x.gold}~\${git.modified}\${x.reset}\`;
|
|
843
|
+
if (git.ahead > 0) b += \` \${x.green}↑\${git.ahead}\${x.reset}\`;
|
|
844
|
+
if (git.behind > 0) b += \` \${x.coral}↓\${git.behind}\${x.reset}\`;
|
|
845
|
+
parts.push(b);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Model
|
|
849
|
+
parts.push(\`\${x.violet}\${getModelName()}\${x.reset}\`);
|
|
850
|
+
|
|
851
|
+
// Active agent
|
|
852
|
+
const activeAgent = getActiveAgent();
|
|
853
|
+
if (activeAgent) {
|
|
854
|
+
const col = activeAgent.activated ? x.green : x.sky;
|
|
855
|
+
const icon = activeAgent.activated ? '●' : '→';
|
|
856
|
+
parts.push(\`\${col}\${icon} \${x.bold}\${activeAgent.name}\${x.reset}\`);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Intelligence
|
|
860
|
+
const ic = pctColor(system.intelligencePct);
|
|
861
|
+
parts.push(\`\${ic}💡 \${system.intelligencePct}%\${x.reset}\`);
|
|
862
|
+
|
|
863
|
+
// Knowledge chunks (Task 28) — show when populated
|
|
864
|
+
if (knowledge.chunks > 0) {
|
|
865
|
+
parts.push(\`\${x.teal}📚 \${knowledge.chunks}k\${x.reset}\`);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Triggers (Task 32) — show when populated
|
|
869
|
+
if (triggers.triggers > 0) {
|
|
870
|
+
parts.push(\`\${x.mint}🎯 \${triggers.triggers}t\${x.reset}\`);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Swarm agents (only when active)
|
|
874
|
+
if (swarm.activeAgents > 0) {
|
|
875
|
+
parts.push(\`\${x.gold}🐝 \${swarm.activeAgents}/\${swarm.maxAgents}\${x.reset}\`);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Hooks
|
|
879
|
+
if (hooks.enabled > 0) {
|
|
880
|
+
parts.push(\`\${x.mint}⚡ \${hooks.enabled}h\${x.reset}\`);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
return parts.join(\` \${DIV} \`);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// ── Multi-line dashboard (full mode) ─────────────────────────────
|
|
887
|
+
function generateDashboard() {
|
|
888
|
+
const git = getGitInfo();
|
|
889
|
+
const modelName = getModelName();
|
|
890
|
+
const progress = getv1Progress();
|
|
891
|
+
const security = getSecurityStatus();
|
|
892
|
+
const swarm = getSwarmStatus();
|
|
893
|
+
const system = getSystemMetrics();
|
|
894
|
+
const adrs = getADRStatus();
|
|
895
|
+
const hooks = getHooksStatus();
|
|
896
|
+
const agentdb = getAgentDBStats();
|
|
897
|
+
const tests = getTestStats();
|
|
898
|
+
const session = getSessionStats();
|
|
593
899
|
const integration = getIntegrationStatus();
|
|
594
|
-
const
|
|
900
|
+
const knowledge = getKnowledgeStats();
|
|
901
|
+
const triggers = getTriggerStats();
|
|
902
|
+
const si = getSIBudget();
|
|
903
|
+
const sec = secBadge(security.status);
|
|
904
|
+
const activeAgent = getActiveAgent();
|
|
905
|
+
const lines = [];
|
|
906
|
+
|
|
907
|
+
// ── Header: brand + git + model + session ────────────────────
|
|
908
|
+
const swarmDot = swarm.coordinationActive ? \`\${x.green}● LIVE\${x.reset}\` : \`\${x.slate}○ IDLE\${x.reset}\`;
|
|
909
|
+
let hdr = \`\${x.bold}\${x.purple}▊ Monobrain \${VERSION}\${x.reset} \${swarmDot} \${x.teal}\${x.bold}\${git.name}\${x.reset}\`;
|
|
595
910
|
|
|
596
|
-
// Header
|
|
597
|
-
let header = c.bold + c.brightPurple + '\\u258A Monobrain ' + c.reset;
|
|
598
|
-
header += (swarm.coordinationActive ? c.brightCyan : c.dim) + '\\u25CF ' + c.brightCyan + git.name + c.reset;
|
|
599
911
|
if (git.gitBranch) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
if (
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
if (git.untracked > 0) ind += c.dim + '?' + git.untracked + c.reset;
|
|
607
|
-
header += ' ' + ind;
|
|
608
|
-
}
|
|
609
|
-
if (git.ahead > 0) header += ' ' + c.brightGreen + '\\u2191' + git.ahead + c.reset;
|
|
610
|
-
if (git.behind > 0) header += ' ' + c.brightRed + '\\u2193' + git.behind + c.reset;
|
|
611
|
-
}
|
|
612
|
-
header += ' ' + c.dim + '\\u2502' + c.reset + ' ' + c.purple + modelName + c.reset;
|
|
613
|
-
// Show session duration from Claude Code stdin if available, else from local files
|
|
614
|
-
const duration = costInfo ? costInfo.duration : session.duration;
|
|
615
|
-
if (duration) header += ' ' + c.dim + '\\u2502' + c.reset + ' ' + c.cyan + '\\u23F1 ' + duration + c.reset;
|
|
616
|
-
// Show context usage from Claude Code stdin if available
|
|
617
|
-
if (ctxInfo && ctxInfo.usedPct > 0) {
|
|
618
|
-
const ctxColor = ctxInfo.usedPct >= 90 ? c.brightRed : ctxInfo.usedPct >= 70 ? c.brightYellow : c.brightGreen;
|
|
619
|
-
header += ' ' + c.dim + '\\u2502' + c.reset + ' ' + ctxColor + '\\u25CF ' + ctxInfo.usedPct + '% ctx' + c.reset;
|
|
620
|
-
}
|
|
621
|
-
// Show cost from Claude Code stdin if available
|
|
622
|
-
if (costInfo && costInfo.costUsd > 0) {
|
|
623
|
-
header += ' ' + c.dim + '\\u2502' + c.reset + ' ' + c.brightYellow + '$' + costInfo.costUsd.toFixed(2) + c.reset;
|
|
624
|
-
}
|
|
625
|
-
lines.push(header);
|
|
626
|
-
|
|
627
|
-
// Separator
|
|
628
|
-
lines.push(c.dim + '\\u2500'.repeat(53) + c.reset);
|
|
629
|
-
|
|
630
|
-
// Line 1: DDD Domains
|
|
631
|
-
const domainsColor = progress.domainsCompleted >= 3 ? c.brightGreen : progress.domainsCompleted > 0 ? c.yellow : c.red;
|
|
632
|
-
let perfIndicator;
|
|
633
|
-
if (agentdb.hasHnsw && agentdb.vectorCount > 0) {
|
|
634
|
-
const speedup = agentdb.vectorCount > 10000 ? '12500x' : agentdb.vectorCount > 1000 ? '150x' : '10x';
|
|
635
|
-
perfIndicator = c.brightGreen + '\\u26A1 HNSW ' + speedup + c.reset;
|
|
636
|
-
} else if (progress.patternsLearned > 0) {
|
|
637
|
-
const pk = progress.patternsLearned >= 1000 ? (progress.patternsLearned / 1000).toFixed(1) + 'k' : String(progress.patternsLearned);
|
|
638
|
-
perfIndicator = c.brightYellow + '\\uD83D\\uDCDA ' + pk + ' patterns' + c.reset;
|
|
639
|
-
} else {
|
|
640
|
-
perfIndicator = c.dim + '\\u26A1 target: 150x-12500x' + c.reset;
|
|
912
|
+
hdr += \` \${DIV} \${x.sky}⎇ \${x.bold}\${git.gitBranch}\${x.reset}\`;
|
|
913
|
+
if (git.staged > 0) hdr += \` \${x.green}+\${git.staged}\${x.reset}\`;
|
|
914
|
+
if (git.modified > 0) hdr += \` \${x.gold}~\${git.modified} mod\${x.reset}\`;
|
|
915
|
+
if (git.untracked > 0) hdr += \` \${x.slate}?\${git.untracked}\${x.reset}\`;
|
|
916
|
+
if (git.ahead > 0) hdr += \` \${x.green}↑\${git.ahead}\${x.reset}\`;
|
|
917
|
+
if (git.behind > 0) hdr += \` \${x.coral}↓\${git.behind}\${x.reset}\`;
|
|
641
918
|
}
|
|
919
|
+
|
|
920
|
+
hdr += \` \${DIV} 🤖 \${x.violet}\${x.bold}\${modelName}\${x.reset}\`;
|
|
921
|
+
if (session.duration) hdr += \` \${x.dim}⏱ \${session.duration}\${x.reset}\`;
|
|
922
|
+
|
|
923
|
+
lines.push(hdr);
|
|
924
|
+
lines.push(SEP);
|
|
925
|
+
|
|
926
|
+
// ── Row 1: Intelligence & Learning ───────────────────────────
|
|
927
|
+
const intellCol = pctColor(system.intelligencePct);
|
|
928
|
+
const intellBar = blockBar(system.intelligencePct, 100, 6);
|
|
929
|
+
|
|
930
|
+
// Knowledge (Task 28)
|
|
931
|
+
const knowStr = knowledge.chunks > 0
|
|
932
|
+
? \`\${x.teal}📚 \${x.bold}\${knowledge.chunks}\${x.reset}\${x.slate} chunks\${x.reset}\`
|
|
933
|
+
: \`\${x.slate}📚 no chunks\${x.reset}\`;
|
|
934
|
+
|
|
935
|
+
// Skills (Task 45)
|
|
936
|
+
const skillStr = knowledge.skills > 0
|
|
937
|
+
? \` \${x.mint}✦ \${knowledge.skills} skills\${x.reset}\`
|
|
938
|
+
: '';
|
|
939
|
+
|
|
940
|
+
// Patterns
|
|
941
|
+
const patStr = progress.patternsLearned > 0
|
|
942
|
+
? \`\${x.gold}\${progress.patternsLearned >= 1000 ? (progress.patternsLearned / 1000).toFixed(1) + 'k' : progress.patternsLearned} patterns\${x.reset}\`
|
|
943
|
+
: \`\${x.slate}0 patterns\${x.reset}\`;
|
|
944
|
+
|
|
642
945
|
lines.push(
|
|
643
|
-
|
|
644
|
-
|
|
946
|
+
\`\${x.purple}💡 INTEL\${x.reset} \` +
|
|
947
|
+
\`\${intellCol}\${intellBar} \${x.bold}\${system.intelligencePct}%\${x.reset} \${DIV} \` +
|
|
948
|
+
\`\${knowStr}\${skillStr} \${DIV} \` +
|
|
949
|
+
patStr
|
|
645
950
|
);
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const
|
|
951
|
+
lines.push(SEP);
|
|
952
|
+
|
|
953
|
+
// ── Row 2: Agents & Triggers ──────────────────────────────────
|
|
954
|
+
const agentCol = swarm.activeAgents > 0 ? x.green : x.slate;
|
|
955
|
+
const hookCol = hooks.enabled > 0 ? x.mint : x.slate;
|
|
956
|
+
|
|
957
|
+
// Triggers (Task 32)
|
|
958
|
+
const trigStr = triggers.triggers > 0
|
|
959
|
+
? \`\${x.mint}🎯 \${x.bold}\${triggers.triggers}\${x.reset}\${x.slate} triggers · \${triggers.agents} agents\${x.reset}\`
|
|
960
|
+
: \`\${x.slate}🎯 no triggers\${x.reset}\`;
|
|
961
|
+
|
|
962
|
+
// Active agent badge
|
|
963
|
+
let agentBadge;
|
|
964
|
+
if (activeAgent) {
|
|
965
|
+
const col = activeAgent.activated ? x.green : x.sky;
|
|
966
|
+
const mark = activeAgent.activated ? '● ACTIVE' : '→ ROUTED';
|
|
967
|
+
const conf = activeAgent.activated ? '' : \` \${x.slate}\${(activeAgent.confidence * 100).toFixed(0)}%\${x.reset}\`;
|
|
968
|
+
const cat = activeAgent.category ? \` \${x.slate}[\${activeAgent.category}]\${x.reset}\` : '';
|
|
969
|
+
agentBadge = \`\${col}\${x.bold}\${mark}\${x.reset} \${col}👤 \${x.bold}\${activeAgent.name}\${x.reset}\${cat}\${conf}\`;
|
|
970
|
+
} else {
|
|
971
|
+
agentBadge = \`\${x.slate}👤 no agent routed\${x.reset}\`;
|
|
972
|
+
}
|
|
654
973
|
|
|
655
974
|
lines.push(
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
intellColor + '\\uD83E\\uDDE0 ' + String(system.intelligencePct).padStart(3) + '%' + c.reset
|
|
975
|
+
\`\${x.gold}🐝 SWARM\${x.reset} \` +
|
|
976
|
+
\`\${agentCol}\${x.bold}\${swarm.activeAgents}\${x.reset}\${x.slate}/\${x.reset}\${x.white}\${swarm.maxAgents}\${x.reset} agents \` +
|
|
977
|
+
\`\${hookCol}⚡ \${hooks.enabled}/\${hooks.total} hooks\${x.reset} \${DIV} \` +
|
|
978
|
+
\`\${trigStr} \${DIV} \` +
|
|
979
|
+
agentBadge
|
|
662
980
|
);
|
|
981
|
+
lines.push(SEP);
|
|
663
982
|
|
|
664
|
-
//
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
983
|
+
// ── Row 3: Architecture & Security ───────────────────────────
|
|
984
|
+
const adrCol = adrs.count > 0
|
|
985
|
+
? (adrs.implemented >= adrs.count ? x.green : x.gold)
|
|
986
|
+
: x.slate;
|
|
987
|
+
const adrStr = adrs.count > 0
|
|
988
|
+
? \`\${adrCol}\${x.bold}\${adrs.implemented}\${x.reset}\${x.slate}/\${x.reset}\${x.white}\${adrs.count}\${x.reset} ADRs\`
|
|
989
|
+
: \`\${x.slate}no ADRs\${x.reset}\`;
|
|
990
|
+
|
|
991
|
+
const dddCol = pctColor(progress.dddProgress);
|
|
992
|
+
const dddBar = blockBar(progress.dddProgress, 100, 5);
|
|
993
|
+
|
|
994
|
+
const cveStatus = security.totalCves === 0
|
|
995
|
+
? (security.status === 'NONE' ? \`\${x.slate}not scanned\${x.reset}\` : \`\${x.green}✔ clean\${x.reset}\`)
|
|
996
|
+
: \`\${x.coral}\${security.cvesFixed}/\${security.totalCves} fixed\${x.reset}\`;
|
|
668
997
|
|
|
669
998
|
lines.push(
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
999
|
+
\`\${x.purple}🧩 ARCH\${x.reset} \` +
|
|
1000
|
+
\`\${adrStr} \${DIV} \` +
|
|
1001
|
+
\`DDD \${dddBar} \${dddCol}\${x.bold}\${progress.dddProgress}%\${x.reset} \${DIV} \` +
|
|
1002
|
+
\`🛡️ \${sec.col}\${sec.label}\${x.reset} \${DIV} \` +
|
|
1003
|
+
\`CVE \${cveStatus}\`
|
|
674
1004
|
);
|
|
1005
|
+
lines.push(SEP);
|
|
675
1006
|
|
|
676
|
-
//
|
|
677
|
-
const
|
|
678
|
-
const
|
|
679
|
-
const
|
|
680
|
-
|
|
1007
|
+
// ── Row 4: Memory & Tests ─────────────────────────────────────
|
|
1008
|
+
const vecCol = agentdb.vectorCount > 0 ? x.green : x.slate;
|
|
1009
|
+
const hnswTag = agentdb.hasHnsw && agentdb.vectorCount > 0 ? \` \${x.green}⚡ HNSW\${x.reset}\` : '';
|
|
1010
|
+
const sizeDisp = agentdb.dbSizeKB >= 1024
|
|
1011
|
+
? \`\${(agentdb.dbSizeKB / 1024).toFixed(1)} MB\` : \`\${agentdb.dbSizeKB} KB\`;
|
|
1012
|
+
const testCol = tests.testFiles > 0 ? x.green : x.slate;
|
|
1013
|
+
const memCol = system.memoryMB > 200 ? x.orange : x.sky;
|
|
681
1014
|
|
|
682
|
-
|
|
1015
|
+
const chips = [];
|
|
683
1016
|
if (integration.mcpServers.total > 0) {
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
1017
|
+
const mc = integration.mcpServers.enabled === integration.mcpServers.total ? x.green
|
|
1018
|
+
: integration.mcpServers.enabled > 0 ? x.gold : x.coral;
|
|
1019
|
+
chips.push(\`\${mc}MCP \${integration.mcpServers.enabled}/\${integration.mcpServers.total}\${x.reset}\`);
|
|
687
1020
|
}
|
|
688
|
-
if (integration.hasDatabase)
|
|
689
|
-
if (integration.hasApi)
|
|
690
|
-
|
|
1021
|
+
if (integration.hasDatabase) chips.push(\`\${x.green}DB ✔\${x.reset}\`);
|
|
1022
|
+
if (integration.hasApi) chips.push(\`\${x.green}API ✔\${x.reset}\`);
|
|
1023
|
+
const integStr = chips.length ? chips.join(' ') : \`\${x.slate}none\${x.reset}\`;
|
|
691
1024
|
|
|
692
1025
|
lines.push(
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1026
|
+
\`\${x.teal}🗄️ MEMORY\${x.reset} \` +
|
|
1027
|
+
\`\${vecCol}\${x.bold}\${agentdb.vectorCount}\${x.reset}\${x.slate} vectors\${x.reset}\${hnswTag} \${DIV} \` +
|
|
1028
|
+
\`\${x.white}\${sizeDisp}\${x.reset} \${DIV} \` +
|
|
1029
|
+
\`\${testCol}🧪 \${tests.testFiles} test files\${x.reset} \${DIV} \` +
|
|
697
1030
|
integStr
|
|
698
1031
|
);
|
|
1032
|
+
lines.push(SEP);
|
|
1033
|
+
|
|
1034
|
+
// ── Row 5: Context budget ─────────────────────────────────────
|
|
1035
|
+
// SI budget (Task 23 monitor)
|
|
1036
|
+
let siStr;
|
|
1037
|
+
if (si) {
|
|
1038
|
+
const siCol = si.pct > 100 ? x.coral : si.pct > 80 ? x.gold : x.green;
|
|
1039
|
+
siStr = \`\${siCol}📄 SI \${si.pct}% budget\${x.reset} \${x.dim}(\${si.len}/\${si.limit} chars)\${x.reset}\`;
|
|
1040
|
+
} else {
|
|
1041
|
+
siStr = \`\${x.slate}📄 no shared instructions\${x.reset}\`;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Domains
|
|
1045
|
+
const domCol = progress.domainsCompleted >= 4 ? x.green
|
|
1046
|
+
: progress.domainsCompleted >= 2 ? x.gold
|
|
1047
|
+
: progress.domainsCompleted >= 1 ? x.orange
|
|
1048
|
+
: x.slate;
|
|
1049
|
+
const domBar = blockBar(progress.domainsCompleted, progress.totalDomains);
|
|
1050
|
+
|
|
1051
|
+
lines.push(
|
|
1052
|
+
\`\${x.slate}📋 CONTEXT\${x.reset} \` +
|
|
1053
|
+
\`\${siStr} \${DIV} \` +
|
|
1054
|
+
\`\${x.teal}🏗 \${domBar} \${domCol}\${x.bold}\${progress.domainsCompleted}\${x.reset}\${x.slate}/\${x.reset}\${x.white}\${progress.totalDomains}\${x.reset} domains \${DIV} \` +
|
|
1055
|
+
\`\${x.dim}💾 \${system.memoryMB} MB RAM\${x.reset}\`
|
|
1056
|
+
);
|
|
699
1057
|
|
|
700
1058
|
return lines.join('\\n');
|
|
701
1059
|
}
|
|
702
1060
|
|
|
703
|
-
// JSON output
|
|
1061
|
+
// ── JSON output ──────────────────────────────────────────────────
|
|
704
1062
|
function generateJSON() {
|
|
705
1063
|
const git = getGitInfo();
|
|
706
1064
|
return {
|
|
707
|
-
user:
|
|
708
|
-
|
|
709
|
-
security:
|
|
710
|
-
swarm:
|
|
711
|
-
system:
|
|
712
|
-
adrs:
|
|
713
|
-
hooks:
|
|
714
|
-
agentdb:
|
|
715
|
-
tests:
|
|
716
|
-
git:
|
|
1065
|
+
user: { name: git.name, gitBranch: git.gitBranch, modelName: getModelName() },
|
|
1066
|
+
domains: getv1Progress(),
|
|
1067
|
+
security: getSecurityStatus(),
|
|
1068
|
+
swarm: getSwarmStatus(),
|
|
1069
|
+
system: getSystemMetrics(),
|
|
1070
|
+
adrs: getADRStatus(),
|
|
1071
|
+
hooks: getHooksStatus(),
|
|
1072
|
+
agentdb: getAgentDBStats(),
|
|
1073
|
+
tests: getTestStats(),
|
|
1074
|
+
git: { modified: git.modified, untracked: git.untracked, staged: git.staged, ahead: git.ahead, behind: git.behind },
|
|
717
1075
|
lastUpdated: new Date().toISOString(),
|
|
718
1076
|
};
|
|
719
1077
|
}
|
|
720
1078
|
|
|
721
|
-
// ───
|
|
1079
|
+
// ─── Mode state file ─────────────────────────────────────────────
|
|
1080
|
+
const MODE_FILE = path.join(CWD, '.monobrain', 'statusline-mode.txt');
|
|
722
1081
|
|
|
723
|
-
|
|
724
|
-
// Read it synchronously so the script works both:
|
|
725
|
-
// 1. When invoked by Claude Code (stdin has JSON)
|
|
726
|
-
// 2. When invoked manually from terminal (stdin is empty/tty)
|
|
727
|
-
let _stdinData = null;
|
|
728
|
-
function getStdinData() {
|
|
729
|
-
if (_stdinData !== undefined && _stdinData !== null) return _stdinData;
|
|
1082
|
+
function readMode() {
|
|
730
1083
|
try {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
// Read stdin synchronously via fd 0
|
|
734
|
-
const chunks = [];
|
|
735
|
-
const buf = Buffer.alloc(4096);
|
|
736
|
-
let bytesRead;
|
|
737
|
-
try {
|
|
738
|
-
while ((bytesRead = fs.readSync(0, buf, 0, buf.length, null)) > 0) {
|
|
739
|
-
chunks.push(buf.slice(0, bytesRead));
|
|
740
|
-
}
|
|
741
|
-
} catch { /* EOF or read error */ }
|
|
742
|
-
const raw = Buffer.concat(chunks).toString('utf-8').trim();
|
|
743
|
-
if (raw && raw.startsWith('{')) {
|
|
744
|
-
_stdinData = JSON.parse(raw);
|
|
745
|
-
} else {
|
|
746
|
-
_stdinData = null;
|
|
1084
|
+
if (fs.existsSync(MODE_FILE)) {
|
|
1085
|
+
return fs.readFileSync(MODE_FILE, 'utf-8').trim();
|
|
747
1086
|
}
|
|
748
|
-
} catch {
|
|
749
|
-
|
|
750
|
-
}
|
|
751
|
-
return _stdinData;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// Override model detection to prefer stdin data from Claude Code
|
|
755
|
-
function getModelFromStdin() {
|
|
756
|
-
const data = getStdinData();
|
|
757
|
-
if (data && data.model && data.model.display_name) return data.model.display_name;
|
|
758
|
-
return null;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
// Get context window info from Claude Code session
|
|
762
|
-
function getContextFromStdin() {
|
|
763
|
-
const data = getStdinData();
|
|
764
|
-
if (data && data.context_window) {
|
|
765
|
-
return {
|
|
766
|
-
usedPct: Math.floor(data.context_window.used_percentage || 0),
|
|
767
|
-
remainingPct: Math.floor(data.context_window.remaining_percentage || 100),
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
return null;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
// Get cost info from Claude Code session
|
|
774
|
-
function getCostFromStdin() {
|
|
775
|
-
const data = getStdinData();
|
|
776
|
-
if (data && data.cost) {
|
|
777
|
-
const durationMs = data.cost.total_duration_ms || 0;
|
|
778
|
-
const mins = Math.floor(durationMs / 60000);
|
|
779
|
-
const secs = Math.floor((durationMs % 60000) / 1000);
|
|
780
|
-
return {
|
|
781
|
-
costUsd: data.cost.total_cost_usd || 0,
|
|
782
|
-
duration: mins > 0 ? mins + 'm' + secs + 's' : secs + 's',
|
|
783
|
-
linesAdded: data.cost.total_lines_added || 0,
|
|
784
|
-
linesRemoved: data.cost.total_lines_removed || 0,
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
return null;
|
|
1087
|
+
} catch { /* ignore */ }
|
|
1088
|
+
return 'full'; // default
|
|
788
1089
|
}
|
|
789
1090
|
|
|
790
1091
|
// ─── Main ───────────────────────────────────────────────────────
|
|
@@ -792,14 +1093,32 @@ if (process.argv.includes('--json')) {
|
|
|
792
1093
|
console.log(JSON.stringify(generateJSON(), null, 2));
|
|
793
1094
|
} else if (process.argv.includes('--compact')) {
|
|
794
1095
|
console.log(JSON.stringify(generateJSON()));
|
|
795
|
-
} else {
|
|
1096
|
+
} else if (process.argv.includes('--single-line')) {
|
|
796
1097
|
console.log(generateStatusline());
|
|
1098
|
+
} else if (process.argv.includes('--toggle')) {
|
|
1099
|
+
// Toggle mode and print the new view
|
|
1100
|
+
const current = readMode();
|
|
1101
|
+
const next = current === 'compact' ? 'full' : 'compact';
|
|
1102
|
+
try {
|
|
1103
|
+
fs.mkdirSync(path.dirname(MODE_FILE), { recursive: true });
|
|
1104
|
+
fs.writeFileSync(MODE_FILE, next, 'utf-8');
|
|
1105
|
+
} catch { /* ignore */ }
|
|
1106
|
+
if (next === 'compact') {
|
|
1107
|
+
console.log(generateStatusline());
|
|
1108
|
+
} else {
|
|
1109
|
+
console.log(generateDashboard());
|
|
1110
|
+
}
|
|
1111
|
+
} else {
|
|
1112
|
+
// Default: respect mode state file
|
|
1113
|
+
const mode = readMode();
|
|
1114
|
+
if (mode === 'compact') {
|
|
1115
|
+
console.log(generateStatusline());
|
|
1116
|
+
} else {
|
|
1117
|
+
console.log(generateDashboard());
|
|
1118
|
+
}
|
|
797
1119
|
}
|
|
798
1120
|
`;
|
|
799
1121
|
}
|
|
800
|
-
/**
|
|
801
|
-
* Generate statusline hook for shell integration
|
|
802
|
-
*/
|
|
803
1122
|
export function generateStatuslineHook(options) {
|
|
804
1123
|
if (!options.statusline.enabled) {
|
|
805
1124
|
return '#!/bin/bash\n# Statusline disabled\n';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Monobrain CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|