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