monomind 2.1.0 → 2.1.4

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.
Files changed (57) hide show
  1. package/README.md +77 -94
  2. package/package.json +3 -3
  3. package/packages/@monomind/cli/.claude/commands/mastermind/createorg.md +8 -9
  4. package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +24 -10
  5. package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +2 -2
  6. package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +29 -0
  7. package/packages/@monomind/cli/.claude/helpers/handlers/session-handler.cjs +35 -23
  8. package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +21 -0
  9. package/packages/@monomind/cli/.claude/helpers/utils/monograph.cjs +73 -2
  10. package/packages/@monomind/cli/.claude/skills/mastermind-skills/createorg.md +91 -701
  11. package/packages/@monomind/cli/.claude/skills/mastermind-skills/org-settings.md +46 -47
  12. package/packages/@monomind/cli/.claude/skills/mastermind-skills/runorg.md +12 -1
  13. package/packages/@monomind/cli/README.md +77 -94
  14. package/packages/@monomind/cli/dist/src/browser/workflow/store.d.ts +2 -2
  15. package/packages/@monomind/cli/dist/src/commands/browse.d.ts +1 -1
  16. package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.js +5 -0
  17. package/packages/@monomind/cli/dist/src/commands/org.js +46 -29
  18. package/packages/@monomind/cli/dist/src/commands/platforms.d.ts +1 -1
  19. package/packages/@monomind/cli/dist/src/init/executor.js +30 -6
  20. package/packages/@monomind/cli/dist/src/mcp-tools/auto-install.d.ts +8 -8
  21. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/types.d.ts +31 -31
  22. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +5 -1
  23. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +28 -3
  24. package/packages/@monomind/cli/dist/src/mcp-tools/quality/coverage-analysis/prioritize-gaps.d.ts +36 -36
  25. package/packages/@monomind/cli/dist/src/mcp-tools/quality/security-compliance/detect-secrets.d.ts +4 -4
  26. package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.d.ts +12 -0
  27. package/packages/@monomind/cli/dist/src/memory/sona-optimizer.d.ts +12 -0
  28. package/packages/@monomind/cli/dist/src/orgrt/broker.d.ts +3 -1
  29. package/packages/@monomind/cli/dist/src/orgrt/broker.js +8 -3
  30. package/packages/@monomind/cli/dist/src/orgrt/bus.d.ts +5 -1
  31. package/packages/@monomind/cli/dist/src/orgrt/bus.js +5 -1
  32. package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +35 -1
  33. package/packages/@monomind/cli/dist/src/orgrt/daemon.js +190 -21
  34. package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +47 -10
  35. package/packages/@monomind/cli/dist/src/orgrt/inbox.d.ts +11 -0
  36. package/packages/@monomind/cli/dist/src/orgrt/inbox.js +56 -0
  37. package/packages/@monomind/cli/dist/src/orgrt/policy.d.ts +5 -1
  38. package/packages/@monomind/cli/dist/src/orgrt/policy.js +58 -5
  39. package/packages/@monomind/cli/dist/src/orgrt/server.d.ts +3 -2
  40. package/packages/@monomind/cli/dist/src/orgrt/server.js +31 -40
  41. package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +11 -1
  42. package/packages/@monomind/cli/dist/src/orgrt/session.js +32 -1
  43. package/packages/@monomind/cli/dist/src/orgrt/test-loop.js +8 -8
  44. package/packages/@monomind/cli/dist/src/orgrt/types.d.ts +53 -53
  45. package/packages/@monomind/cli/dist/src/output.d.ts +29 -29
  46. package/packages/@monomind/cli/dist/src/output.js +10 -2
  47. package/packages/@monomind/cli/dist/src/pricing/model-pricing.d.ts +1 -1
  48. package/packages/@monomind/cli/dist/src/types.d.ts +2 -2
  49. package/packages/@monomind/cli/dist/src/ui/collector.mjs +20 -5
  50. package/packages/@monomind/cli/dist/src/ui/dashboard.html +465 -500
  51. package/packages/@monomind/cli/dist/src/ui/orgs-files.js +192 -0
  52. package/packages/@monomind/cli/dist/src/ui/orgs.html +21 -52
  53. package/packages/@monomind/cli/dist/src/ui/server.mjs +174 -164
  54. package/packages/@monomind/cli/package.json +12 -16
  55. package/packages/@monomind/cli/scripts/publish.sh +6 -0
  56. package/scripts/generate-agent-avatars.mjs +3 -3
  57. package/packages/@monomind/cli/dist/src/orgrt/live.html +0 -56
@@ -9,6 +9,9 @@ import { collectAll, getWatchPaths, collectProject, collectSessions, collectSwar
9
9
  import { addSseClient, removeSseClient, broadcast, getSseClientCount, closeSseClients, addMmClient, removeMmClient, broadcastMm, getMmClientCount } from './sse-manager.mjs';
10
10
 
11
11
  const JSONL_SIZE_CAP = 10 * 1024 * 1024; // 10 MB — skip files larger than this in /api/graph
12
+ // Session id format for data/sessions/<id>.jsonl persistence — no path traversal (".."), starts
13
+ // with a word char, rest is word chars/dot/dash. Shared by every session-id-accepting endpoint.
14
+ const SESSION_ID_RE = /^(?!.*\.\.)[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/;
12
15
  const buildDocsState = new Map();
13
16
 
14
17
  // Pricing per token (mirrors token-tracker.cjs FALLBACK_PRICING)
@@ -478,7 +481,9 @@ function _detectMimeType(filePath) {
478
481
  '.json': 'application/json', '.md': 'text/markdown', '.txt': 'text/plain',
479
482
  '.html': 'text/html', '.css': 'text/css', '.py': 'text/x-python',
480
483
  '.sh': 'text/x-shellscript', '.yaml': 'text/yaml', '.yml': 'text/yaml',
481
- '.toml': 'text/plain', '.env': 'text/plain', '.xml': 'text/xml' };
484
+ '.toml': 'text/plain', '.env': 'text/plain', '.xml': 'text/xml',
485
+ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
486
+ '.gif': 'image/gif', '.svg': 'image/svg+xml', '.webp': 'image/webp' };
482
487
  return map[ext] || 'application/octet-stream';
483
488
  }
484
489
 
@@ -608,10 +613,26 @@ function bindServer(server, port) {
608
613
  * uses a greedy BFS over the real filesystem to find the correct path.
609
614
  * Falls back to cwd in session files, then to direct slug replacement.
610
615
  */
616
+ const _slugPathCache = new Map();
617
+
611
618
  function resolveSlugToPath(slug, projDir) {
612
- // 1. Try filesystem BFS (most reliable)
619
+ if (_slugPathCache.has(slug)) return _slugPathCache.get(slug);
620
+ const resolved = _resolveSlugToPathUncached(slug, projDir);
621
+ _slugPathCache.set(slug, resolved);
622
+ return resolved;
623
+ }
624
+
625
+ function _resolveSlugToPathUncached(slug, projDir) {
626
+ // 1. Try filesystem BFS (most reliable). Branching is O(2^hyphens) in the
627
+ // worst case (a slug with N hyphens can require exploring both "new path
628
+ // segment" and "hyphen is part of this segment's name" at each of the N
629
+ // positions) — bound total exploration so a slug with many hyphens (e.g.
630
+ // a UUID-embedded temp/scratchpad dir) can't hang the event loop.
613
631
  const parts = slug.replace(/^-/, '').split('-');
632
+ const MAX_CALLS = 20000;
633
+ let calls = 0;
614
634
  function tryPaths(idx, current) {
635
+ if (++calls > MAX_CALLS) return null;
615
636
  if (idx === parts.length) return fs.existsSync(current) ? current : null;
616
637
  // Option A: next part is a new path component
617
638
  const asDir = path.join(current, parts[idx]);
@@ -690,7 +711,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
690
711
  // Extracted from the request dispatcher to reduce cyclomatic complexity.
691
712
  // Handles POST /api/mastermind/event: parses body, enriches with runId/session,
692
713
  // persists to JSONL files, broadcasts to SSE clients, returns {ok:true}.
693
- async function handleMastermindEvent(req, res) {
714
+ async function handleMastermindEvent(req, res, corsOrigin) {
694
715
  let body = '';
695
716
  for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
696
717
  let event = {};
@@ -887,7 +908,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
887
908
  // Format: data/sessions/<sessionId>.jsonl + data/sessions/_index.json
888
909
  try {
889
910
  const _sid = String(event.session || '').trim();
890
- if (_sid.length > 0 && _sid.length <= 128 && /^(?!.*\.\.)[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/.test(_sid)) {
911
+ if (_sid.length > 0 && _sid.length <= 128 && SESSION_ID_RE.test(_sid)) {
891
912
  const sessDir = path.join(dataDir, 'sessions');
892
913
  fs.mkdirSync(sessDir, { recursive: true });
893
914
  // Append event to per-session JSONL (O(1), no read)
@@ -1011,7 +1032,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1011
1032
  if (req.method === 'GET' && url === '/') {
1012
1033
  const htmlPath = path.join(__dirname, 'dashboard.html');
1013
1034
  try {
1014
- const html = fs.readFileSync(htmlPath, 'utf8');
1035
+ let html = fs.readFileSync(htmlPath, 'utf8');
1036
+ // Inject this process's auth credential so the page's own fetch() calls can
1037
+ // attach it (see the fetch-wrapper near the top of dashboard.html's <script>
1038
+ // block) — every non-GET route above requires it, but the served HTML was
1039
+ // previously never given a way to know it, so every write action 401'd from
1040
+ // the actual browser UI.
1041
+ html = html.replace('<head>', `<head>\n<meta name="mm-token" content="${dashboardAuthValue}">`);
1015
1042
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1016
1043
  res.end(html);
1017
1044
  } catch (err) {
@@ -4721,6 +4748,122 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4721
4748
  return;
4722
4749
  }
4723
4750
 
4751
+ // GET /api/questions?dir=<projectDir> — list unanswered ask_human questions for
4752
+ // every org in one project. Mirrors the existing -approvals.json sidecar convention,
4753
+ // but reads this feature's .monomind/orgs/<org>/questions.json files (one per org dir).
4754
+ if (req.method === 'GET' && url === '/api/questions') {
4755
+ try {
4756
+ const _qDir = new URL(req.url, 'http://localhost').searchParams.get('dir') || projectDir || process.cwd();
4757
+ const base = path.join(path.resolve(_qDir), '.monomind', 'orgs');
4758
+ const out = [];
4759
+ if (fs.existsSync(base)) {
4760
+ for (const orgName of fs.readdirSync(base)) {
4761
+ const qFile = path.join(base, orgName, 'questions.json');
4762
+ if (!fs.existsSync(qFile)) continue;
4763
+ let data = { questions: [] };
4764
+ try { data = JSON.parse(fs.readFileSync(qFile, 'utf8')); } catch (_) {}
4765
+ for (const q of (data.questions || [])) {
4766
+ if (q.answer === null || q.answer === undefined) out.push({ org: orgName, ...q });
4767
+ }
4768
+ }
4769
+ }
4770
+ res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
4771
+ res.end(JSON.stringify({ questions: out }));
4772
+ } catch (_e) { res.writeHead(500); res.end('{"questions":[]}'); }
4773
+ return;
4774
+ }
4775
+
4776
+ // POST /api/questions/answer — forward a human's answer to the org's live process
4777
+ // (looked up via the same file-based broker registry orgrt's cross-process delivery
4778
+ // already uses), or fail with a clear error if no process anywhere hosts it.
4779
+ if (req.method === 'POST' && url === '/api/questions/answer') {
4780
+ let body = '';
4781
+ for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
4782
+ try {
4783
+ const parsed = JSON.parse(body);
4784
+ const { dir, org, role, questionId, answer } = parsed;
4785
+ if (!org || !role || !questionId || answer === undefined) {
4786
+ res.writeHead(400, { 'Content-Type': 'application/json' });
4787
+ res.end(JSON.stringify({ ok: false, error: 'org, role, questionId, answer are required' }));
4788
+ return;
4789
+ }
4790
+ if (String(org).length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(String(org))) {
4791
+ res.writeHead(400, { 'Content-Type': 'application/json' });
4792
+ res.end(JSON.stringify({ ok: false, error: 'Invalid org name' }));
4793
+ return;
4794
+ }
4795
+ const brokerDir = path.join(os.homedir(), '.monomind', 'orgrt-broker');
4796
+ const entryPath = path.join(brokerDir, `${org}.json`);
4797
+ let hostUrl = null;
4798
+ try {
4799
+ const entry = JSON.parse(fs.readFileSync(entryPath, 'utf8'));
4800
+ if (Date.now() - entry.updatedAt < 90000) hostUrl = entry.url;
4801
+ } catch (_) {}
4802
+ if (!hostUrl) {
4803
+ // No live process to forward to — the control server has no daemon instance of
4804
+ // its own to call autoWake() on. If the org's definition still exists on disk,
4805
+ // queue the answer the same way inbox.ts's queueMessage()/drainInbox() already
4806
+ // do for offline cross-org messages, so it's delivered whenever the org next
4807
+ // starts (manually or via its own schedule) — matching the offline-delivery goal
4808
+ // for the dashboard path, not just the direct-daemon path Task 4 already covers.
4809
+ const projDir = path.resolve(dir || projectDir || process.cwd());
4810
+ const orgDefFile = path.join(projDir, '.monomind', 'orgs', `${org}.json`);
4811
+ if (!fs.existsSync(orgDefFile)) {
4812
+ res.writeHead(404, { 'Content-Type': 'application/json' });
4813
+ res.end(JSON.stringify({ ok: false, error: `org "${org}" not found — no running process and no saved definition` }));
4814
+ return;
4815
+ }
4816
+ const qFile = path.join(projDir, '.monomind', 'orgs', org, 'questions.json');
4817
+ let qData = { questions: [] };
4818
+ try { qData = JSON.parse(fs.readFileSync(qFile, 'utf8')); } catch (_) {}
4819
+ const qIdx = (qData.questions || []).findIndex(q => q.questionId === questionId);
4820
+ if (qIdx === -1) {
4821
+ res.writeHead(404, { 'Content-Type': 'application/json' });
4822
+ res.end(JSON.stringify({ ok: false, error: `question "${questionId}" not found for org "${org}"` }));
4823
+ return;
4824
+ }
4825
+ if (qData.questions[qIdx].answer !== null && qData.questions[qIdx].answer !== undefined) {
4826
+ res.writeHead(200, { 'Content-Type': 'application/json' });
4827
+ res.end(JSON.stringify({ ok: true, alreadyAnswered: true }));
4828
+ return;
4829
+ }
4830
+ const answeredQuestion = qData.questions[qIdx];
4831
+ qData.questions[qIdx] = { ...answeredQuestion, answer, answeredAt: Date.now() };
4832
+ const qTmp = `${qFile}.tmp`;
4833
+ fs.writeFileSync(qTmp, JSON.stringify(qData, null, 2));
4834
+ fs.renameSync(qTmp, qFile);
4835
+ const inboxDir = path.join(projDir, '.monomind', 'orgs', org);
4836
+ fs.mkdirSync(inboxDir, { recursive: true });
4837
+ fs.appendFileSync(path.join(inboxDir, 'inbox.jsonl'), JSON.stringify({
4838
+ fromQualified: 'human', toRole: role, subject: `answer:${questionId}`, body: `question: ${answeredQuestion.question}\n\nanswer: ${answer}`, ts: Date.now(),
4839
+ }) + '\n');
4840
+ const queuedEvent = { type: 'org:question-answered', org, role, questionId, ts: Date.now(), queued: true };
4841
+ appendToFile(path.join(projDir, 'data', 'mastermind-events.jsonl'), JSON.stringify(queuedEvent) + '\n').catch(() => {});
4842
+ broadcastMm(queuedEvent);
4843
+ res.writeHead(200, { 'Content-Type': 'application/json' });
4844
+ res.end(JSON.stringify({ ok: true, queued: true }));
4845
+ return;
4846
+ }
4847
+ const fwd = await fetch(`${hostUrl}/api/answer-question`, {
4848
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
4849
+ body: JSON.stringify({ org, role, questionId, answer }),
4850
+ signal: AbortSignal.timeout(5000),
4851
+ });
4852
+ const fwdData = await fwd.json().catch(() => ({}));
4853
+ if (fwd.ok && fwdData.ok) {
4854
+ const event = { type: 'org:question-answered', org, role, questionId, ts: Date.now() };
4855
+ appendToFile(path.join(path.resolve(dir || projectDir || process.cwd()), 'data', 'mastermind-events.jsonl'), JSON.stringify(event) + '\n').catch(() => {});
4856
+ broadcastMm(event);
4857
+ res.writeHead(200, { 'Content-Type': 'application/json' });
4858
+ res.end(JSON.stringify({ ok: true }));
4859
+ } else {
4860
+ res.writeHead(fwd.status || 500, { 'Content-Type': 'application/json' });
4861
+ res.end(JSON.stringify({ ok: false, error: fwdData.error || 'answer delivery failed' }));
4862
+ }
4863
+ } catch (_e) { res.writeHead(500); res.end('{"ok":false}'); }
4864
+ return;
4865
+ }
4866
+
4724
4867
  // POST /api/org/:name/approvals/:id — approve or reject a pending approval request
4725
4868
  // Body: { action: "approve" | "reject" | "revision_requested" }
4726
4869
  if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/approvals\/[^/]+$/i)) {
@@ -5113,6 +5256,12 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5113
5256
  const stat = fs.statSync(resolved);
5114
5257
  if (!stat.isFile()) { res.writeHead(400); res.end('Not a file'); return; }
5115
5258
  if (stat.size > 524288) { res.writeHead(413); res.end('File too large'); return; }
5259
+ const _fcMime = _detectMimeType(resolved);
5260
+ if (_fcMime.startsWith('image/')) {
5261
+ res.writeHead(200, { 'Content-Type': _fcMime, ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5262
+ res.end(fs.readFileSync(resolved));
5263
+ return;
5264
+ }
5116
5265
  const content = fs.readFileSync(resolved, 'utf8');
5117
5266
  res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5118
5267
  res.end(content);
@@ -5397,7 +5546,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5397
5546
  // ------------------------------------------------- Mastermind event system
5398
5547
  // POST /api/mastermind/event — ingest event from mastermind skill
5399
5548
  if (req.method === 'POST' && url === '/api/mastermind/event') {
5400
- return handleMastermindEvent(req, res);
5549
+ return handleMastermindEvent(req, res, corsOrigin);
5401
5550
  }
5402
5551
 
5403
5552
  // GET /api/mastermind-stream — SSE for real-time events
@@ -5449,7 +5598,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5449
5598
  const top = idx.slice(0, limitParam);
5450
5599
  for (const entry of top) {
5451
5600
  const _sid = String(entry.id || '').trim();
5452
- if (!_sid || !/^(?!.*\.\.)[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/.test(_sid)) continue;
5601
+ if (!_sid || !SESSION_ID_RE.test(_sid)) continue;
5453
5602
  let events = [];
5454
5603
  try {
5455
5604
  const jl = fs.readFileSync(path.join(sessDir, `${_sid}.jsonl`), 'utf8');
@@ -5557,6 +5706,23 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5557
5706
  return;
5558
5707
  }
5559
5708
 
5709
+ // ------------------------------------------------ GET /orgs-files.js
5710
+ // Files-tab + diff-view script, split out of orgs.html (see orgs.html's
5711
+ // script-src comment). Not a generic static handler — this UI serves each
5712
+ // sibling asset via its own hardcoded route, same as GET /orgs above.
5713
+ if (req.method === 'GET' && url === '/orgs-files.js') {
5714
+ try {
5715
+ const jsPath = path.join(__dirname, 'orgs-files.js');
5716
+ const js = fs.readFileSync(jsPath, 'utf8');
5717
+ res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8' });
5718
+ res.end(js);
5719
+ } catch (err) {
5720
+ res.writeHead(404);
5721
+ res.end(`orgs-files.js not found: ${err.message}`);
5722
+ }
5723
+ return;
5724
+ }
5725
+
5560
5726
  // GET /api/mastermind/loops — list all active loop state files
5561
5727
  if (req.method === 'GET' && url === '/api/mastermind/loops') {
5562
5728
  try {
@@ -5668,162 +5834,6 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5668
5834
  return;
5669
5835
  }
5670
5836
 
5671
- // ----------------------------------------------- GET /api/monoagent/platforms
5672
- // Returns all supported platforms from `monoes connect list --all --json`
5673
- if (req.method === 'GET' && url === '/api/monoagent/platforms') {
5674
- try {
5675
- const { execFile } = await import('child_process');
5676
- const out = await new Promise((resolve, reject) => {
5677
- execFile('monoes', ['connect', 'list', '--all', '--json'], { timeout: 8000 }, (err, stdout) => {
5678
- if (err) reject(err); else resolve(stdout);
5679
- });
5680
- });
5681
- // Parse + re-serialize to ensure only valid JSON reaches the client
5682
- // (monoes may emit warning lines before the JSON array)
5683
- let parsed; try { parsed = JSON.parse(out); } catch (_) { parsed = []; }
5684
- res.writeHead(200, { 'Content-Type': 'application/json' });
5685
- res.end(JSON.stringify(Array.isArray(parsed) ? parsed : []));
5686
- } catch (e) { res.writeHead(200); res.end('[]'); }
5687
- return;
5688
- }
5689
-
5690
- // ----------------------------------------------- GET /api/monoagent/connections
5691
- // Returns active API/OAuth connections + browser sessions merged
5692
- if (req.method === 'GET' && url === '/api/monoagent/connections') {
5693
- try {
5694
- const { execFile } = await import('child_process');
5695
- const [connsOut, sessOut] = await Promise.all([
5696
- new Promise((resolve) => {
5697
- execFile('monoes', ['connect', 'list', '--json'], { timeout: 8000 }, (err, stdout) => resolve(err ? '[]' : stdout));
5698
- }),
5699
- new Promise((resolve) => {
5700
- execFile('monoes', ['--json', 'login', 'status'], { timeout: 8000 }, (err, stdout) => resolve(err ? '[]' : stdout));
5701
- }),
5702
- ]);
5703
- let connections = []; try { connections = JSON.parse(connsOut); } catch (_) {}
5704
- let sessions = []; try { sessions = JSON.parse(sessOut); } catch (_) {}
5705
- res.writeHead(200, { 'Content-Type': 'application/json' });
5706
- res.end(JSON.stringify({ connections, sessions }));
5707
- } catch (e) { res.writeHead(200); res.end(JSON.stringify({ connections: [], sessions: [] })); }
5708
- return;
5709
- }
5710
-
5711
- // ----------------------------------------------- POST /api/monoagent/login
5712
- // Launches browser login for social platforms via `monoes login <platform>`
5713
- if (req.method === 'POST' && url === '/api/monoagent/login') {
5714
- try {
5715
- let body = '';
5716
- await new Promise((resolve, reject) => { req.on('data', d => { body += d; if (body.length > 65536) { req.destroy(); reject(new Error('body too large')); } }); req.on('end', resolve); req.on('error', reject); });
5717
- const { id } = JSON.parse(body);
5718
- if (!id || !/^[a-z][a-z0-9_-]*$/.test(id)) { res.writeHead(400); res.end(JSON.stringify({ error: 'invalid id' })); return; }
5719
- const { spawn } = await import('child_process');
5720
- const child = spawn('monoes', ['login', id, '--timeout', '10m'], { detached: true, stdio: 'ignore' });
5721
- // Defer response until spawn confirms or errors — prevents race where error fires after res.end()
5722
- child.once('spawn', () => {
5723
- child.unref();
5724
- res.writeHead(200, { 'Content-Type': 'application/json' });
5725
- res.end(JSON.stringify({ ok: true }));
5726
- });
5727
- child.once('error', (err) => {
5728
- res.writeHead(500, { 'Content-Type': 'application/json' });
5729
- res.end(JSON.stringify({ error: err.message }));
5730
- });
5731
- } catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); }
5732
- return;
5733
- }
5734
-
5735
- // ----------------------------------------------- POST /api/monoagent/connect
5736
- if (req.method === 'POST' && url === '/api/monoagent/connect') {
5737
- try {
5738
- let body = '';
5739
- await new Promise((resolve, reject) => { req.on('data', d => { body += d; if (body.length > 65536) { req.destroy(); reject(new Error('body too large')); } }); req.on('end', resolve); req.on('error', reject); });
5740
- const { id, method, fields } = JSON.parse(body);
5741
- if (!id || !/^[a-z][a-z0-9_-]*$/.test(id)) { res.writeHead(400); res.end(JSON.stringify({ error: 'invalid id' })); return; }
5742
- if (method && !/^[a-z][a-z0-9_-]*$/.test(method)) { res.writeHead(400); res.end(JSON.stringify({ error: 'invalid method' })); return; }
5743
- const { execFile } = await import('child_process');
5744
- const args = ['connect', id];
5745
- if (method) args.push('--method', method);
5746
- if (fields && typeof fields === 'object') {
5747
- for (const [k, v] of Object.entries(fields)) {
5748
- // Only allow simple word keys — prevents --flag injection
5749
- if (!/^[a-z][a-z0-9_]*$/.test(k)) continue;
5750
- args.push(`--${k}`, String(v).slice(0, 2048));
5751
- }
5752
- }
5753
- await new Promise((resolve, reject) => {
5754
- execFile('monoes', args, { timeout: 30000 }, (err, stdout, stderr) => {
5755
- const ok = !err;
5756
- res.writeHead(200, { 'Content-Type': 'application/json' });
5757
- res.end(JSON.stringify({ ok, stdout: (stdout || '').trim(), stderr: (stderr || '').trim() }));
5758
- resolve();
5759
- });
5760
- });
5761
- } catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); }
5762
- return;
5763
- }
5764
-
5765
- // ----------------------------------------------- POST /api/monoagent/test
5766
- if (req.method === 'POST' && url === '/api/monoagent/test') {
5767
- try {
5768
- let body = '';
5769
- await new Promise((resolve, reject) => { req.on('data', d => { body += d; if (body.length > 65536) { req.destroy(); reject(new Error('body too large')); } }); req.on('end', resolve); req.on('error', reject); });
5770
- const { id } = JSON.parse(body);
5771
- if (!id || !/^[a-z0-9][a-z0-9_:-]*$/.test(id)) { res.writeHead(400); res.end(JSON.stringify({ error: 'id required' })); return; }
5772
- const { execFile } = await import('child_process');
5773
- await new Promise((resolve, reject) => {
5774
- execFile('monoes', ['connect', 'test', id], { timeout: 15000 }, (err, stdout, stderr) => {
5775
- if (err) reject(new Error(stderr || err.message)); else resolve(stdout);
5776
- });
5777
- });
5778
- res.writeHead(200, { 'Content-Type': 'application/json' });
5779
- res.end(JSON.stringify({ ok: true }));
5780
- } catch (e) { res.writeHead(200); res.end(JSON.stringify({ ok: false, error: e.message })); }
5781
- return;
5782
- }
5783
-
5784
- // ----------------------------------------------- POST /api/monoagent/disconnect
5785
- if (req.method === 'POST' && url === '/api/monoagent/disconnect') {
5786
- try {
5787
- let body = '';
5788
- await new Promise((resolve, reject) => { req.on('data', d => { body += d; if (body.length > 65536) { req.destroy(); reject(new Error('body too large')); } }); req.on('end', resolve); req.on('error', reject); });
5789
- const { id, type } = JSON.parse(body);
5790
- if (!id || !/^[a-z0-9][a-z0-9_:-]*$/.test(id)) { res.writeHead(400); res.end(JSON.stringify({ error: 'id required' })); return; }
5791
- if (type !== 'session' && type !== 'connection') { res.writeHead(400); res.end(JSON.stringify({ error: 'invalid type' })); return; }
5792
- const { execFile } = await import('child_process');
5793
- const cmd = type === 'session' ? ['logout', id] : ['connect', 'remove', id];
5794
- await new Promise((resolve, reject) => {
5795
- execFile('monoes', cmd, { timeout: 10000 }, (err, _stdout, stderr) => {
5796
- if (err) reject(new Error(stderr || err.message)); else resolve();
5797
- });
5798
- });
5799
- res.writeHead(200, { 'Content-Type': 'application/json' });
5800
- res.end(JSON.stringify({ ok: true }));
5801
- } catch (e) { res.writeHead(200); res.end(JSON.stringify({ ok: false, error: e.message })); }
5802
- return;
5803
- }
5804
-
5805
- // ----------------------------------------------- GET /api/monoagent/data
5806
- if (req.method === 'GET' && url === '/api/monoagent/data') {
5807
- try {
5808
- const { execFile } = await import('child_process');
5809
- const run = (args) => new Promise(resolve => {
5810
- execFile('monoagent', args, { timeout: 8000 }, (err, stdout) => resolve(err ? '[]' : stdout));
5811
- });
5812
- const [wfOut, actOut] = await Promise.all([
5813
- run(['workflow', 'list', '--json']),
5814
- run(['action', 'list', '--json']),
5815
- ]);
5816
- const workflows = JSON.parse(wfOut.trim() || '[]');
5817
- const actions = JSON.parse(actOut.trim() || '[]');
5818
- res.writeHead(200, { 'Content-Type': 'application/json' });
5819
- res.end(JSON.stringify({ workflows, actions }));
5820
- } catch (e) {
5821
- res.writeHead(200, { 'Content-Type': 'application/json' });
5822
- res.end(JSON.stringify({ workflows: [], actions: [], error: e.message }));
5823
- }
5824
- return;
5825
- }
5826
-
5827
5837
  // ------------------------------------------------- GET /api/playbooks
5828
5838
  // List playbook definitions from <dir>/.monomind/playbooks/*.json.
5829
5839
  // (Previously only POST was registered, so GET /api/playbooks?dir=... fell
@@ -6193,7 +6203,7 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
6193
6203
  const _migIndex = [];
6194
6204
  for (const sess of (_migOld || [])) {
6195
6205
  const _msid = String(sess.id || '').trim();
6196
- if (!_msid || !/^(?!.*\.\.)[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/.test(_msid)) continue;
6206
+ if (!_msid || !SESSION_ID_RE.test(_msid)) continue;
6197
6207
  // Write per-session JSONL
6198
6208
  const _mEvts = (sess.events || []);
6199
6209
  const _mLines = _mEvts.map(e => JSON.stringify(e)).join('\n');
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@monoes/monomindcli",
3
- "version": "2.1.0",
3
+ "version": "2.1.4",
4
4
  "type": "module",
5
- "description": "Monomind CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
5
+ "description": "CLI engine for Monomind an open-source MCP server that extends Claude Code with a codebase knowledge graph (tree-sitter + SQLite), persistent memory, multi-agent task coordination, and session hooks. MIT licensed, fully local.",
6
6
  "main": "dist/src/index.js",
7
7
  "types": "dist/src/index.d.ts",
8
8
  "sideEffects": false,
@@ -27,21 +27,17 @@
27
27
  "claude",
28
28
  "claude-code",
29
29
  "anthropic",
30
- "ai-agents",
31
- "multi-agent",
32
- "swarm",
33
30
  "mcp",
34
31
  "model-context-protocol",
35
- "llm",
32
+ "knowledge-graph",
33
+ "tree-sitter",
34
+ "code-analysis",
35
+ "ai-agents",
36
+ "multi-agent",
36
37
  "cli",
37
- "orchestration",
38
- "automation",
39
38
  "developer-tools",
40
- "coding-assistant",
41
- "vector-database",
42
- "embeddings",
43
- "self-learning",
44
- "enterprise"
39
+ "open-source",
40
+ "local-first"
45
41
  ],
46
42
  "author": {
47
43
  "name": "nokhodian",
@@ -83,8 +79,8 @@
83
79
  "README.md"
84
80
  ],
85
81
  "scripts": {
86
- "build": "tsc && mkdir -p dist/src/browser/dashboard && cp src/browser/dashboard/ui.html dist/src/browser/dashboard/ui.html && mkdir -p dist/src/orgrt && cp src/orgrt/live.html dist/src/orgrt/",
87
- "build:loose": "tsc --noEmitOnError false || true && mkdir -p dist/src/browser/dashboard && cp src/browser/dashboard/ui.html dist/src/browser/dashboard/ui.html && mkdir -p dist/src/orgrt && cp src/orgrt/live.html dist/src/orgrt/",
82
+ "build": "tsc && mkdir -p dist/src/browser/dashboard && cp src/browser/dashboard/ui.html dist/src/browser/dashboard/ui.html && mkdir -p dist/src/ui && cp src/ui/dashboard.html src/ui/server.mjs src/ui/collector.mjs src/ui/orgs.html src/ui/orgs-files.js src/ui/sse-manager.mjs dist/src/ui/ && rm -rf dist/src/ui/data && cp -r src/ui/data dist/src/ui/data",
83
+ "build:loose": "tsc --noEmitOnError false || true && mkdir -p dist/src/browser/dashboard && cp src/browser/dashboard/ui.html dist/src/browser/dashboard/ui.html && mkdir -p dist/src/ui && cp src/ui/dashboard.html src/ui/server.mjs src/ui/collector.mjs src/ui/orgs.html src/ui/orgs-files.js src/ui/sse-manager.mjs dist/src/ui/ && rm -rf dist/src/ui/data && cp -r src/ui/data dist/src/ui/data",
88
84
  "test": "vitest run",
89
85
  "test:pattern-store": "npx tsx src/transfer/store/tests/standalone-test.ts",
90
86
  "prepublishOnly": "cp ../../../README.md ./README.md",
@@ -92,7 +88,7 @@
92
88
  "publish:all": "./scripts/publish.sh"
93
89
  },
94
90
  "devDependencies": {
95
- "typescript": "^5.3.0",
91
+ "typescript": "^7.0.2",
96
92
  "vitest": "^4.1.4"
97
93
  },
98
94
  "dependencies": {
@@ -9,6 +9,12 @@ CLI_DIR="$(dirname "$SCRIPT_DIR")"
9
9
 
10
10
  cd "$CLI_DIR"
11
11
 
12
+ # dist/src/ui/* (dashboard, server, etc.) is hand-authored source under src/ui/
13
+ # copied into dist/ by the build step — it is no longer committed to git, so a
14
+ # stale or missing dist/ here would silently ship a broken/absent dashboard.
15
+ echo "=== Building ==="
16
+ npm run build
17
+
12
18
  # Get current version
13
19
  VERSION=$(node -p "require('./package.json').version")
14
20
  echo "Publishing version: $VERSION"
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * Generates 120 unique SVG agent avatar files + a sprite-sheet HTML.
4
- * Output: packages/@monomind/cli/dist/src/ui/data/avatars/
4
+ * Output: packages/@monomind/cli/src/ui/data/avatars/
5
5
  * Run: node scripts/generate-agent-avatars.mjs
6
6
  */
7
7
 
@@ -10,7 +10,7 @@ import path from 'path';
10
10
  import { fileURLToPath } from 'url';
11
11
 
12
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
- const OUT_DIR = path.resolve(__dirname, '../packages/@monomind/cli/dist/src/ui/data/avatars');
13
+ const OUT_DIR = path.resolve(__dirname, '../packages/@monomind/cli/src/ui/data/avatars');
14
14
  fs.mkdirSync(OUT_DIR, { recursive: true });
15
15
 
16
16
  // ─── Palettes ────────────────────────────────────────────────────────────────
@@ -489,4 +489,4 @@ const spritePath = path.join(OUT_DIR, '../agent-avatars.html');
489
489
  fs.writeFileSync(spritePath, spriteHtml, 'utf8');
490
490
  console.log(`✅ Sprite-sheet viewer: data/agent-avatars.html`);
491
491
  console.log(`\n🎉 Done! ${AGENTS120.length} avatars ready.`);
492
- console.log(` Open: packages/@monomind/cli/dist/src/ui/data/agent-avatars.html`);
492
+ console.log(` Open: packages/@monomind/cli/src/ui/data/agent-avatars.html`);
@@ -1,56 +0,0 @@
1
- <!-- packages/@monomind/cli/src/orgrt/live.html -->
2
- <!doctype html>
3
- <html><head><meta charset="utf-8"><title>org live</title>
4
- <style>
5
- :root { color-scheme: dark; }
6
- body { margin:0; font:13px/1.5 ui-monospace,monospace; background:#0d1117; color:#e6edf3;
7
- display:grid; grid-template-columns:220px 1fr 1fr; grid-template-rows:auto 1fr 1fr; gap:1px; height:100vh; }
8
- header { grid-column:1/4; padding:8px 14px; background:#161b22; font-weight:700; }
9
- section { background:#161b22; overflow:auto; padding:10px; }
10
- h2 { font-size:11px; text-transform:uppercase; letter-spacing:.1em; color:#7d8590; margin:0 0 8px; }
11
- #agents { grid-row:2/4; }
12
- .agent { padding:6px 8px; border-left:3px solid #2f81f7; margin-bottom:6px; background:#0d1117; }
13
- .agent small { color:#7d8590; display:block; }
14
- .row { padding:4px 6px; border-bottom:1px solid #21262d; word-break:break-word; }
15
- .from { color:#2f81f7; font-weight:700; } .to { color:#a371f7; }
16
- .deny { color:#f85149; } .allow { color:#3fb950; } .xorg { color:#d29922; font-weight:700; }
17
- .ts { color:#484f58; font-size:11px; margin-right:6px; }
18
- </style></head>
19
- <body>
20
- <header>org live — <span id="conn">connecting…</span></header>
21
- <section id="agents"><h2>Agents</h2><div id="agents-list"></div></section>
22
- <section id="chat"><h2>Chats &amp; Messages</h2><div id="chat-list"></div></section>
23
- <section id="tools"><h2>Tool Activity &amp; Assets</h2><div id="tools-list"></div></section>
24
- <script>
25
- const $ = id => document.getElementById(id);
26
- const fmt = ts => new Date(ts).toLocaleTimeString();
27
- const add = (listId, html, cap = 500) => {
28
- const el = document.createElement('div'); el.className = 'row'; el.innerHTML = html;
29
- const list = $(listId); list.prepend(el);
30
- while (list.children.length > cap) list.lastChild.remove();
31
- };
32
- const esc = s => String(s ?? '').replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));
33
- const decisionClass = d => (d === 'allow' || d === 'deny') ? d : 'unknown';
34
- function render(e) {
35
- const t = `<span class="ts">${fmt(e.ts)}</span>`;
36
- if (e.type === 'chat') add('chat-list', `${t}<span class="from">${esc(e.org)}/${esc(e.from)}</span>: ${esc(e.msg)}`);
37
- else if (e.type === 'message') add('chat-list', `${t}<span class="from">${esc(e.from)}</span> → <span class="to">${esc(e.to)}</span> [${esc(e.subject||'')}] ${esc(e.msg)}`);
38
- else if (e.type === 'xorg') add('chat-list', `${t}<span class="xorg">⇄ ${esc(e.from)} → ${esc(e.to)}</span> [${esc(e.subject||'')}] ${esc(e.msg)}`);
39
- else if (e.type === 'tool') add('tools-list', `${t}<span class="from">${esc(e.from)}</span> ${esc(e.tool)} <span class="${decisionClass(e.decision)}">${esc(e.decision)}</span>${e.reason ? ' — ' + esc(e.reason) : ''}`);
40
- else if (e.type === 'asset') add('tools-list', `${t}<span class="from">${esc(e.from)}</span> 📄 <b>${esc(e.path)}</b>`);
41
- else if (e.type === 'status' || e.type === 'usage') add('tools-list', `${t}<i>${esc(e.org)}/${esc(e.from ?? 'org')}: ${esc(e.msg ?? JSON.stringify(e.data))}</i>`);
42
- }
43
- async function refreshAgents() {
44
- const orgs = await fetch('/api/orgs').then(r => r.json()).catch(() => []);
45
- $('agents-list').innerHTML = orgs.map(o =>
46
- `<div class="agent"><b>${esc(o.name)}</b><small>${esc(o.run)}</small>` +
47
- o.agents.map(a => `<div>${a.closed ? '⚫' : '🟢'} ${esc(a.id)} <small>${esc(a.usage)} tok</small></div>`).join('') +
48
- `</div>`).join('') || '<i>no running orgs</i>';
49
- }
50
- const ws = new WebSocket(`ws://${location.host}/ws`);
51
- ws.onopen = () => { $('conn').textContent = 'live'; refreshAgents(); };
52
- ws.onclose = () => { $('conn').textContent = 'disconnected'; };
53
- ws.onmessage = ev => { render(JSON.parse(ev.data)); };
54
- setInterval(refreshAgents, 5000);
55
- </script>
56
- </body></html>