@monoes/cli 1.0.5 → 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.
@@ -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 V1 implementation progress and system status
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("sh -c '" + script + "'", 3000);
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 && claudeConfig.projects) {
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
- const usage = projectConfig.lastModelUsage;
169
- if (usage) {
170
- const ids = Object.keys(usage);
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
- }
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
- break;
254
+ return modelLabel(bestId);
185
255
  }
186
256
  }
187
257
  }
188
258
  } catch { /* ignore */ }
189
259
 
190
- // Fallback: settings.json model field
260
+ // TERTIARY: settings.json model field (configured default, not live session).
191
261
  const settings = getSettings();
192
- if (settings && settings.model) {
193
- const m = settings.model;
194
- if (m.includes('opus')) return 'Opus 4.6 (1M context)';
195
- if (m.includes('sonnet')) return 'Sonnet 4.6';
196
- if (m.includes('haiku')) return 'Haiku 4.5';
197
- }
198
- return 'Claude Code';
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 getDevProgress() {
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 ? (dddData.progress || 0) : 0;
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: (swarmState.agents && swarmState.agents.length) || swarmState.agentCount || 0,
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 && activityData.swarm) {
313
- const updatedAt = activityData.timestamp || (activityData.swarm && activityData.swarm.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 && learningData.intelligence && learningData.intelligence.score !== undefined) {
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 && learningData.sessions && learningData.sessions.total !== undefined) {
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 && activityData.processes && activityData.processes.estimated_agents) {
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 && settings.hooks) {
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 && matcher.hooks;
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 && store.entries) vectorCount += store.entries.length;
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(path.join(CWD, '.monobrain', 'data', 'ranked-context.json'));
452
- if (ranked && ranked.entries && ranked.entries.length > vectorCount) vectorCount = ranked.entries.length;
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. Graph data size
470
- const graphStat = safeStat(path.join(CWD, 'data', 'memory.graph'));
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 or memory package
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
- if (safeStat(p)) { hasHnsw = true; break; }
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
- var testDirNames = ['tests', 'test', '__tests__', 'src', 'v1'];
518
- for (var i = 0; i < testDirNames.length; i++) {
519
- countTestFiles(path.join(CWD, testDirNames[i]));
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 && settings.mcpServers && typeof settings.mcpServers === 'object') {
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 && mcpConfig.mcpServers) {
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
- var sessionPaths = ['.monobrain/session.json', '.claude/session.json'];
558
- for (var i = 0; i < sessionPaths.length; i++) {
559
- const data = readJSON(path.join(CWD, sessionPaths[i]));
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 + 'm' : Math.floor(mins / 60) + 'h' + (mins % 60) + 'm';
564
- return { duration: 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
- // ─── Rendering ──────────────────────────────────────────────────
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
- function progressBar(current, total) {
573
- const width = 5;
574
- const filled = Math.round((current / total) * width);
575
- return '[' + '\\u25CF'.repeat(filled) + '\\u25CB'.repeat(width - filled) + ']';
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 = getGitInfo();
580
- // Prefer model name from Claude Code stdin data, fallback to file-based detection
581
- const modelName = getModelFromStdin() || getModelName();
582
- const ctxInfo = getContextFromStdin();
583
- const costInfo = getCostFromStdin();
584
- const progress = getDevProgress();
585
- const security = getSecurityStatus();
586
- const swarm = getSwarmStatus();
587
- const system = getSystemMetrics();
588
- const adrs = getADRStatus();
589
- const hooks = getHooksStatus();
590
- const agentdb = getAgentDBStats();
591
- const tests = getTestStats();
592
- const session = getSessionStats();
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 lines = [];
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
- header += ' ' + c.dim + '\\u2502' + c.reset + ' ' + c.brightBlue + '\\u23C7 ' + git.gitBranch + c.reset;
601
- const changes = git.modified + git.staged + git.untracked;
602
- if (changes > 0) {
603
- let ind = '';
604
- if (git.staged > 0) ind += c.brightGreen + '+' + git.staged + c.reset;
605
- if (git.modified > 0) ind += c.brightYellow + '~' + git.modified + c.reset;
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
- c.brightCyan + '\\uD83C\\uDFD7\\uFE0F DDD Domains' + c.reset + ' ' + progressBar(progress.domainsCompleted, progress.totalDomains) + ' ' +
644
- domainsColor + progress.domainsCompleted + c.reset + '/' + c.brightWhite + progress.totalDomains + c.reset + ' ' + perfIndicator
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
- // Line 2: Swarm + Hooks + CVE + Memory + Intelligence
648
- const swarmInd = swarm.coordinationActive ? c.brightGreen + '\\u25C9' + c.reset : c.dim + '\\u25CB' + c.reset;
649
- const agentsColor = swarm.activeAgents > 0 ? c.brightGreen : c.red;
650
- const secIcon = security.status === 'CLEAN' ? '\\uD83D\\uDFE2' : (security.status === 'IN_PROGRESS' || security.status === 'STALE') ? '\\uD83D\\uDFE1' : (security.status === 'NONE' ? '\\u26AA' : '\\uD83D\\uDD34');
651
- const secColor = security.status === 'CLEAN' ? c.brightGreen : (security.status === 'IN_PROGRESS' || security.status === 'STALE') ? c.brightYellow : (security.status === 'NONE' ? c.dim : c.brightRed);
652
- const hooksColor = hooks.enabled > 0 ? c.brightGreen : c.dim;
653
- const intellColor = system.intelligencePct >= 80 ? c.brightGreen : system.intelligencePct >= 40 ? c.brightYellow : c.dim;
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
- c.brightYellow + '\\uD83E\\uDD16 Swarm' + c.reset + ' ' + swarmInd + ' [' + agentsColor + String(swarm.activeAgents).padStart(2) + c.reset + '/' + c.brightWhite + swarm.maxAgents + c.reset + '] ' +
657
- c.brightPurple + '\\uD83D\\uDC65 ' + system.subAgents + c.reset + ' ' +
658
- c.brightBlue + '\\uD83E\\uDE9D ' + hooksColor + hooks.enabled + c.reset + '/' + c.brightWhite + hooks.total + c.reset + ' ' +
659
- secIcon + ' ' + secColor + 'CVE ' + security.cvesFixed + c.reset + '/' + c.brightWhite + security.totalCves + c.reset + ' ' +
660
- c.brightCyan + '\\uD83D\\uDCBE ' + system.memoryMB + 'MB' + c.reset + ' ' +
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
- // Line 3: Architecture
665
- const dddColor = progress.dddProgress >= 50 ? c.brightGreen : progress.dddProgress > 0 ? c.yellow : c.red;
666
- const adrColor = adrs.count > 0 ? (adrs.implemented === adrs.count ? c.brightGreen : c.yellow) : c.dim;
667
- const adrDisplay = adrs.compliance > 0 ? adrColor + '\\u25CF' + adrs.compliance + '%' + c.reset : adrColor + '\\u25CF' + adrs.implemented + '/' + adrs.count + c.reset;
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
- c.brightPurple + '\\uD83D\\uDD27 Architecture' + c.reset + ' ' +
671
- c.cyan + 'ADRs' + c.reset + ' ' + adrDisplay + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
672
- c.cyan + 'DDD' + c.reset + ' ' + dddColor + '\\u25CF' + String(progress.dddProgress).padStart(3) + '%' + c.reset + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
673
- c.cyan + 'Security' + c.reset + ' ' + secColor + '\\u25CF' + security.status + c.reset
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
- // Line 4: AgentDB, Tests, Integration
677
- const hnswInd = agentdb.hasHnsw ? c.brightGreen + '\\u26A1' + c.reset : '';
678
- const sizeDisp = agentdb.dbSizeKB >= 1024 ? (agentdb.dbSizeKB / 1024).toFixed(1) + 'MB' : agentdb.dbSizeKB + 'KB';
679
- const vectorColor = agentdb.vectorCount > 0 ? c.brightGreen : c.dim;
680
- const testColor = tests.testFiles > 0 ? c.brightGreen : c.dim;
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
- let integStr = '';
1007
+ const chips = [];
683
1008
  if (integration.mcpServers.total > 0) {
684
- const mcpCol = integration.mcpServers.enabled === integration.mcpServers.total ? c.brightGreen :
685
- integration.mcpServers.enabled > 0 ? c.brightYellow : c.red;
686
- integStr += c.cyan + 'MCP' + c.reset + ' ' + mcpCol + '\\u25CF' + integration.mcpServers.enabled + '/' + integration.mcpServers.total + c.reset;
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) integStr += (integStr ? ' ' : '') + c.brightGreen + '\\u25C6' + c.reset + 'DB';
689
- if (integration.hasApi) integStr += (integStr ? ' ' : '') + c.brightGreen + '\\u25C6' + c.reset + 'API';
690
- if (!integStr) integStr = c.dim + '\\u25CF none' + c.reset;
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
- c.brightCyan + '\\uD83D\\uDCCA AgentDB' + c.reset + ' ' +
694
- c.cyan + 'Vectors' + c.reset + ' ' + vectorColor + '\\u25CF' + agentdb.vectorCount + hnswInd + c.reset + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
695
- c.cyan + 'Size' + c.reset + ' ' + c.brightWhite + sizeDisp + c.reset + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
696
- c.cyan + 'Tests' + c.reset + ' ' + testColor + '\\u25CF' + tests.testFiles + c.reset + ' ' + c.dim + '(~' + tests.testCases + ' cases)' + c.reset + ' ' + c.dim + '\\u2502' + c.reset + ' ' +
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: { name: git.name, gitBranch: git.gitBranch, modelName: getModelName() },
708
- devProgress: getDevProgress(),
709
- security: getSecurityStatus(),
710
- swarm: getSwarmStatus(),
711
- system: getSystemMetrics(),
712
- adrs: getADRStatus(),
713
- hooks: getHooksStatus(),
714
- agentdb: getAgentDBStats(),
715
- tests: getTestStats(),
716
- git: { modified: git.modified, untracked: git.untracked, staged: git.staged, ahead: git.ahead, behind: git.behind },
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
- // ─── Stdin reader (Claude Code pipes session JSON) ──────────────
1071
+ // ─── Mode state file ─────────────────────────────────────────────
1072
+ const MODE_FILE = path.join(CWD, '.monobrain', 'statusline-mode.txt');
722
1073
 
723
- // Claude Code sends session JSON via stdin (model, context, cost, etc.)
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
- // Check if stdin is a TTY (manual run) — skip reading
732
- if (process.stdin.isTTY) { _stdinData = null; return null; }
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
- _stdinData = null;
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.5",
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",