@monoes/monomindcli 1.12.0 → 1.13.0

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.
@@ -409,12 +409,22 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
409
409
  const cwd = projectDir || process.cwd();
410
410
  const name = gitExec('git config user.name', { cwd, encoding: 'utf8' }).trim();
411
411
  const email = gitExec('git config user.email', { cwd, encoding: 'utf8' }).trim();
412
+ let remoteUrl = '';
413
+ try { remoteUrl = gitExec('git remote get-url origin', { cwd, encoding: 'utf8' }).trim(); } catch {}
414
+ // Normalise SSH remote to HTTPS URL for browser linking
415
+ if (remoteUrl.startsWith('git@')) {
416
+ remoteUrl = remoteUrl.replace(/^git@([^:]+):/, 'https://$1/').replace(/\.git$/, '');
417
+ } else if (remoteUrl.endsWith('.git')) {
418
+ remoteUrl = remoteUrl.slice(0, -4);
419
+ }
420
+ let branch = '';
421
+ try { branch = gitExec('git rev-parse --abbrev-ref HEAD', { cwd, encoding: 'utf8' }).trim(); } catch {}
412
422
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
413
- res.end(JSON.stringify({ name, email, cwd }));
423
+ res.end(JSON.stringify({ name, email, cwd, remoteUrl, branch }));
414
424
  } catch (_) {
415
425
  const cwd2 = projectDir || process.cwd();
416
426
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
417
- res.end(JSON.stringify({ name: '', email: '', cwd: cwd2 }));
427
+ res.end(JSON.stringify({ name: '', email: '', cwd: cwd2, remoteUrl: '', branch: '' }));
418
428
  }
419
429
  return;
420
430
  }
@@ -631,6 +641,69 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
631
641
  return;
632
642
  }
633
643
 
644
+ // ------------------------------------------------------- GET /api/recent-events
645
+ if (req.method === 'GET' && url === '/api/recent-events') {
646
+ try {
647
+ const qs = new URL(req.url, 'http://localhost').searchParams;
648
+ const dir = qs.get('dir') || projectDir || process.cwd();
649
+ const limit = Math.min(parseInt(qs.get('limit') || '50', 10), 200);
650
+ const d = path.resolve(dir || process.cwd());
651
+ const slug = d.replace(/\//g, '-');
652
+ const projectClaudeDir = path.join(os.homedir(), '.claude', 'projects', slug);
653
+ let sessionFiles = [];
654
+ try {
655
+ sessionFiles = fs.readdirSync(projectClaudeDir)
656
+ .filter(f => f.endsWith('.jsonl'))
657
+ .map(f => { try { return { f, mtime: fs.statSync(path.join(projectClaudeDir, f)).mtimeMs }; } catch { return null; } })
658
+ .filter(Boolean)
659
+ .sort((a, b) => b.mtime - a.mtime)
660
+ .slice(0, 5); // check last 5 sessions
661
+ } catch {}
662
+
663
+ const events = [];
664
+ const HOOK_RE = /^<(local-command-|command-name>|command-message>)/;
665
+ for (const { f } of sessionFiles) {
666
+ const fp = path.join(projectClaudeDir, f);
667
+ const sessId = f.replace('.jsonl', '');
668
+ try {
669
+ const lines = fs.readFileSync(fp, 'utf8').split('\n').filter(Boolean).slice(-200);
670
+ for (const line of lines) {
671
+ let e; try { e = JSON.parse(line); } catch { continue; }
672
+ if (e.type === 'assistant') {
673
+ const content = e.message?.content || [];
674
+ for (const block of content) {
675
+ if (block?.type === 'tool_use') {
676
+ events.push({ kind: 'tool', ts: e.timestamp, tool: block.name, session: sessId });
677
+ }
678
+ }
679
+ } else if (e.type === 'user') {
680
+ const content = e.message?.content || [];
681
+ for (const block of content) {
682
+ if (block?.type === 'text' && block.text?.trim() && !HOOK_RE.test(block.text.trim())) {
683
+ events.push({ kind: 'user', ts: e.timestamp, text: block.text.slice(0, 120), session: sessId });
684
+ }
685
+ }
686
+ }
687
+ }
688
+ } catch {}
689
+ }
690
+
691
+ // sort by ts desc, take limit
692
+ events.sort((a, b) => {
693
+ const ta = a.ts ? new Date(a.ts).getTime() : 0;
694
+ const tb = b.ts ? new Date(b.ts).getTime() : 0;
695
+ return tb - ta;
696
+ });
697
+
698
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
699
+ res.end(JSON.stringify({ events: events.slice(0, limit) }));
700
+ } catch (err) {
701
+ res.writeHead(500, { 'Content-Type': 'application/json' });
702
+ res.end(JSON.stringify({ error: err.message }));
703
+ }
704
+ return;
705
+ }
706
+
634
707
  // ------------------------------------------------------- GET /api/tool-errors
635
708
  if (req.method === 'GET' && url === '/api/tool-errors') {
636
709
  try {
@@ -4637,6 +4710,75 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
4637
4710
  return;
4638
4711
  }
4639
4712
 
4713
+ // GET /api/status — live system snapshot for dashboard polling
4714
+ if (req.method === 'GET' && url === '/api/status') {
4715
+ try {
4716
+ const root = projectDir || process.cwd();
4717
+ // Active org runs: { orgName -> runId }
4718
+ const orgRuns = {};
4719
+ activeOrgRuns.forEach((runId, org) => { orgRuns[org] = runId; });
4720
+ // Recent events (last 10)
4721
+ let recentEvents = [];
4722
+ try {
4723
+ const evPath = path.join(root, 'data', 'mastermind-events.jsonl');
4724
+ const lines = fs.readFileSync(evPath, 'utf8').split('\n').filter(l => l.trim()).slice(-10);
4725
+ recentEvents = lines.map(l => { try { return JSON.parse(l); } catch(_) { return null; } }).filter(Boolean);
4726
+ } catch(_) {}
4727
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4728
+ res.end(JSON.stringify({
4729
+ ts: Date.now(),
4730
+ uptime: process.uptime(),
4731
+ sseClients: mmSseClients.size,
4732
+ activeOrgs: Object.keys(orgRuns).length,
4733
+ orgRuns,
4734
+ recentEvents,
4735
+ }));
4736
+ } catch(err) {
4737
+ res.writeHead(500, { 'Content-Type': 'application/json' });
4738
+ res.end(JSON.stringify({ error: err.message }));
4739
+ }
4740
+ return;
4741
+ }
4742
+
4743
+ // GET /api/orgs/:name/runs/current — events from the active run file for an org
4744
+ if (req.method === 'GET' && /^\/api\/orgs\/[^/]+\/runs\/current$/.test(url)) {
4745
+ try {
4746
+ const orgName = decodeURIComponent(url.split('/')[3]);
4747
+ const root = projectDir || process.cwd();
4748
+ // Validate orgName
4749
+ if (!orgName || orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
4750
+ res.writeHead(400); res.end('{"error":"invalid org name"}'); return;
4751
+ }
4752
+ const runId = activeOrgRuns.get(orgName);
4753
+ const monoDir = _getGitMonomindDir(root) || path.join(root, '.monomind');
4754
+ // Try active run first, then fall back to most recent run file
4755
+ let runFile = null;
4756
+ if (runId) {
4757
+ const candidate = path.join(monoDir, 'orgs', orgName, 'runs', `${runId}.jsonl`);
4758
+ if (fs.existsSync(candidate)) runFile = candidate;
4759
+ }
4760
+ if (!runFile) {
4761
+ const runsDir = path.join(monoDir, 'orgs', orgName, 'runs');
4762
+ if (fs.existsSync(runsDir)) {
4763
+ const files = fs.readdirSync(runsDir).filter(f => f.endsWith('.jsonl'));
4764
+ if (files.length) {
4765
+ files.sort();
4766
+ runFile = path.join(runsDir, files[files.length - 1]);
4767
+ }
4768
+ }
4769
+ }
4770
+ if (!runFile) { res.writeHead(404); res.end('{"events":[],"runId":null}'); return; }
4771
+ const detectedRunId = path.basename(runFile, '.jsonl');
4772
+ const lines = fs.readFileSync(runFile, 'utf8').split('\n').filter(l => l.trim()).slice(-100);
4773
+ const events = lines.map(l => { try { return JSON.parse(l); } catch(_) { return null; } }).filter(Boolean);
4774
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
4775
+ res.end(JSON.stringify({ runId: detectedRunId, events, active: activeOrgRuns.has(orgName) }));
4776
+ } catch(err) {
4777
+ res.writeHead(500); res.end(JSON.stringify({ error: err.message }));
4778
+ }
4779
+ return;
4780
+ }
4781
+
4640
4782
  // GET /api/mastermind/metrics — aggregate system metrics from token-summary and swarm-activity
4641
4783
  if (req.method === 'GET' && url === '/api/mastermind/metrics') {
4642
4784
  try {