monomind 2.1.3 → 2.1.5

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 (27) hide show
  1. package/README.md +18 -17
  2. package/package.json +1 -1
  3. package/packages/@monomind/cli/.claude/commands/mastermind/createorg.md +8 -9
  4. package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +32 -15
  5. package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +238 -23
  6. package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +74 -1
  7. package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +14 -12
  8. package/packages/@monomind/cli/.claude/helpers/router.cjs +12 -2
  9. package/packages/@monomind/cli/.claude/skills/mastermind-skills/createorg.md +91 -701
  10. package/packages/@monomind/cli/.claude/skills/mastermind-skills/org-settings.md +46 -47
  11. package/packages/@monomind/cli/.claude/skills/mastermind-skills/runorg.md +12 -1
  12. package/packages/@monomind/cli/README.md +18 -17
  13. package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +18 -0
  14. package/packages/@monomind/cli/dist/src/orgrt/daemon.js +76 -1
  15. package/packages/@monomind/cli/dist/src/orgrt/forwarder.js +7 -3
  16. package/packages/@monomind/cli/dist/src/orgrt/server.js +24 -0
  17. package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +1 -0
  18. package/packages/@monomind/cli/dist/src/orgrt/session.js +8 -0
  19. package/packages/@monomind/cli/dist/src/orgrt/types.d.ts +1 -1
  20. package/packages/@monomind/cli/dist/src/pricing/model-pricing.d.ts +1 -1
  21. package/packages/@monomind/cli/dist/src/ui/collector.mjs +20 -5
  22. package/packages/@monomind/cli/dist/src/ui/dashboard.html +270 -499
  23. package/packages/@monomind/cli/dist/src/ui/orgs-files.js +1 -1
  24. package/packages/@monomind/cli/dist/src/ui/server.mjs +132 -158
  25. package/packages/@monomind/cli/package.json +3 -3
  26. package/packages/@monomind/cli/scripts/publish.sh +6 -0
  27. package/scripts/generate-agent-avatars.mjs +3 -3
@@ -1,4 +1,4 @@
1
- // packages/@monomind/cli/dist/src/ui/orgs-files.js
1
+ // packages/@monomind/cli/src/ui/orgs-files.js
2
2
  // Files tab + diff-view feature, split out of orgs.html to keep that file under
3
3
  // the CLAUDE.md 500-line guideline. Loaded as a classic <script> right where this
4
4
  // code used to live inline — shares the same global scope as orgs.html's main
@@ -481,7 +481,9 @@ function _detectMimeType(filePath) {
481
481
  '.json': 'application/json', '.md': 'text/markdown', '.txt': 'text/plain',
482
482
  '.html': 'text/html', '.css': 'text/css', '.py': 'text/x-python',
483
483
  '.sh': 'text/x-shellscript', '.yaml': 'text/yaml', '.yml': 'text/yaml',
484
- '.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' };
485
487
  return map[ext] || 'application/octet-stream';
486
488
  }
487
489
 
@@ -1030,7 +1032,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1030
1032
  if (req.method === 'GET' && url === '/') {
1031
1033
  const htmlPath = path.join(__dirname, 'dashboard.html');
1032
1034
  try {
1033
- 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}">`);
1034
1042
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1035
1043
  res.end(html);
1036
1044
  } catch (err) {
@@ -4740,6 +4748,122 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
4740
4748
  return;
4741
4749
  }
4742
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
+
4743
4867
  // POST /api/org/:name/approvals/:id — approve or reject a pending approval request
4744
4868
  // Body: { action: "approve" | "reject" | "revision_requested" }
4745
4869
  if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/approvals\/[^/]+$/i)) {
@@ -5132,6 +5256,12 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5132
5256
  const stat = fs.statSync(resolved);
5133
5257
  if (!stat.isFile()) { res.writeHead(400); res.end('Not a file'); return; }
5134
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
+ }
5135
5265
  const content = fs.readFileSync(resolved, 'utf8');
5136
5266
  res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
5137
5267
  res.end(content);
@@ -5704,162 +5834,6 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
5704
5834
  return;
5705
5835
  }
5706
5836
 
5707
- // ----------------------------------------------- GET /api/monoagent/platforms
5708
- // Returns all supported platforms from `monoes connect list --all --json`
5709
- if (req.method === 'GET' && url === '/api/monoagent/platforms') {
5710
- try {
5711
- const { execFile } = await import('child_process');
5712
- const out = await new Promise((resolve, reject) => {
5713
- execFile('monoes', ['connect', 'list', '--all', '--json'], { timeout: 8000 }, (err, stdout) => {
5714
- if (err) reject(err); else resolve(stdout);
5715
- });
5716
- });
5717
- // Parse + re-serialize to ensure only valid JSON reaches the client
5718
- // (monoes may emit warning lines before the JSON array)
5719
- let parsed; try { parsed = JSON.parse(out); } catch (_) { parsed = []; }
5720
- res.writeHead(200, { 'Content-Type': 'application/json' });
5721
- res.end(JSON.stringify(Array.isArray(parsed) ? parsed : []));
5722
- } catch (e) { res.writeHead(200); res.end('[]'); }
5723
- return;
5724
- }
5725
-
5726
- // ----------------------------------------------- GET /api/monoagent/connections
5727
- // Returns active API/OAuth connections + browser sessions merged
5728
- if (req.method === 'GET' && url === '/api/monoagent/connections') {
5729
- try {
5730
- const { execFile } = await import('child_process');
5731
- const [connsOut, sessOut] = await Promise.all([
5732
- new Promise((resolve) => {
5733
- execFile('monoes', ['connect', 'list', '--json'], { timeout: 8000 }, (err, stdout) => resolve(err ? '[]' : stdout));
5734
- }),
5735
- new Promise((resolve) => {
5736
- execFile('monoes', ['--json', 'login', 'status'], { timeout: 8000 }, (err, stdout) => resolve(err ? '[]' : stdout));
5737
- }),
5738
- ]);
5739
- let connections = []; try { connections = JSON.parse(connsOut); } catch (_) {}
5740
- let sessions = []; try { sessions = JSON.parse(sessOut); } catch (_) {}
5741
- res.writeHead(200, { 'Content-Type': 'application/json' });
5742
- res.end(JSON.stringify({ connections, sessions }));
5743
- } catch (e) { res.writeHead(200); res.end(JSON.stringify({ connections: [], sessions: [] })); }
5744
- return;
5745
- }
5746
-
5747
- // ----------------------------------------------- POST /api/monoagent/login
5748
- // Launches browser login for social platforms via `monoes login <platform>`
5749
- if (req.method === 'POST' && url === '/api/monoagent/login') {
5750
- try {
5751
- let body = '';
5752
- 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); });
5753
- const { id } = JSON.parse(body);
5754
- if (!id || !/^[a-z][a-z0-9_-]*$/.test(id)) { res.writeHead(400); res.end(JSON.stringify({ error: 'invalid id' })); return; }
5755
- const { spawn } = await import('child_process');
5756
- const child = spawn('monoes', ['login', id, '--timeout', '10m'], { detached: true, stdio: 'ignore' });
5757
- // Defer response until spawn confirms or errors — prevents race where error fires after res.end()
5758
- child.once('spawn', () => {
5759
- child.unref();
5760
- res.writeHead(200, { 'Content-Type': 'application/json' });
5761
- res.end(JSON.stringify({ ok: true }));
5762
- });
5763
- child.once('error', (err) => {
5764
- res.writeHead(500, { 'Content-Type': 'application/json' });
5765
- res.end(JSON.stringify({ error: err.message }));
5766
- });
5767
- } catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); }
5768
- return;
5769
- }
5770
-
5771
- // ----------------------------------------------- POST /api/monoagent/connect
5772
- if (req.method === 'POST' && url === '/api/monoagent/connect') {
5773
- try {
5774
- let body = '';
5775
- 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); });
5776
- const { id, method, fields } = JSON.parse(body);
5777
- if (!id || !/^[a-z][a-z0-9_-]*$/.test(id)) { res.writeHead(400); res.end(JSON.stringify({ error: 'invalid id' })); return; }
5778
- if (method && !/^[a-z][a-z0-9_-]*$/.test(method)) { res.writeHead(400); res.end(JSON.stringify({ error: 'invalid method' })); return; }
5779
- const { execFile } = await import('child_process');
5780
- const args = ['connect', id];
5781
- if (method) args.push('--method', method);
5782
- if (fields && typeof fields === 'object') {
5783
- for (const [k, v] of Object.entries(fields)) {
5784
- // Only allow simple word keys — prevents --flag injection
5785
- if (!/^[a-z][a-z0-9_]*$/.test(k)) continue;
5786
- args.push(`--${k}`, String(v).slice(0, 2048));
5787
- }
5788
- }
5789
- await new Promise((resolve, reject) => {
5790
- execFile('monoes', args, { timeout: 30000 }, (err, stdout, stderr) => {
5791
- const ok = !err;
5792
- res.writeHead(200, { 'Content-Type': 'application/json' });
5793
- res.end(JSON.stringify({ ok, stdout: (stdout || '').trim(), stderr: (stderr || '').trim() }));
5794
- resolve();
5795
- });
5796
- });
5797
- } catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); }
5798
- return;
5799
- }
5800
-
5801
- // ----------------------------------------------- POST /api/monoagent/test
5802
- if (req.method === 'POST' && url === '/api/monoagent/test') {
5803
- try {
5804
- let body = '';
5805
- 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); });
5806
- const { id } = JSON.parse(body);
5807
- if (!id || !/^[a-z0-9][a-z0-9_:-]*$/.test(id)) { res.writeHead(400); res.end(JSON.stringify({ error: 'id required' })); return; }
5808
- const { execFile } = await import('child_process');
5809
- await new Promise((resolve, reject) => {
5810
- execFile('monoes', ['connect', 'test', id], { timeout: 15000 }, (err, stdout, stderr) => {
5811
- if (err) reject(new Error(stderr || err.message)); else resolve(stdout);
5812
- });
5813
- });
5814
- res.writeHead(200, { 'Content-Type': 'application/json' });
5815
- res.end(JSON.stringify({ ok: true }));
5816
- } catch (e) { res.writeHead(200); res.end(JSON.stringify({ ok: false, error: e.message })); }
5817
- return;
5818
- }
5819
-
5820
- // ----------------------------------------------- POST /api/monoagent/disconnect
5821
- if (req.method === 'POST' && url === '/api/monoagent/disconnect') {
5822
- try {
5823
- let body = '';
5824
- 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); });
5825
- const { id, type } = JSON.parse(body);
5826
- if (!id || !/^[a-z0-9][a-z0-9_:-]*$/.test(id)) { res.writeHead(400); res.end(JSON.stringify({ error: 'id required' })); return; }
5827
- if (type !== 'session' && type !== 'connection') { res.writeHead(400); res.end(JSON.stringify({ error: 'invalid type' })); return; }
5828
- const { execFile } = await import('child_process');
5829
- const cmd = type === 'session' ? ['logout', id] : ['connect', 'remove', id];
5830
- await new Promise((resolve, reject) => {
5831
- execFile('monoes', cmd, { timeout: 10000 }, (err, _stdout, stderr) => {
5832
- if (err) reject(new Error(stderr || err.message)); else resolve();
5833
- });
5834
- });
5835
- res.writeHead(200, { 'Content-Type': 'application/json' });
5836
- res.end(JSON.stringify({ ok: true }));
5837
- } catch (e) { res.writeHead(200); res.end(JSON.stringify({ ok: false, error: e.message })); }
5838
- return;
5839
- }
5840
-
5841
- // ----------------------------------------------- GET /api/monoagent/data
5842
- if (req.method === 'GET' && url === '/api/monoagent/data') {
5843
- try {
5844
- const { execFile } = await import('child_process');
5845
- const run = (args) => new Promise(resolve => {
5846
- execFile('monoagent', args, { timeout: 8000 }, (err, stdout) => resolve(err ? '[]' : stdout));
5847
- });
5848
- const [wfOut, actOut] = await Promise.all([
5849
- run(['workflow', 'list', '--json']),
5850
- run(['action', 'list', '--json']),
5851
- ]);
5852
- const workflows = JSON.parse(wfOut.trim() || '[]');
5853
- const actions = JSON.parse(actOut.trim() || '[]');
5854
- res.writeHead(200, { 'Content-Type': 'application/json' });
5855
- res.end(JSON.stringify({ workflows, actions }));
5856
- } catch (e) {
5857
- res.writeHead(200, { 'Content-Type': 'application/json' });
5858
- res.end(JSON.stringify({ workflows: [], actions: [], error: e.message }));
5859
- }
5860
- return;
5861
- }
5862
-
5863
5837
  // ------------------------------------------------- GET /api/playbooks
5864
5838
  // List playbook definitions from <dir>/.monomind/playbooks/*.json.
5865
5839
  // (Previously only POST was registered, so GET /api/playbooks?dir=... fell
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monoes/monomindcli",
3
- "version": "2.1.3",
3
+ "version": "2.1.5",
4
4
  "type": "module",
5
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",
@@ -79,8 +79,8 @@
79
79
  "README.md"
80
80
  ],
81
81
  "scripts": {
82
- "build": "tsc && mkdir -p dist/src/browser/dashboard && cp src/browser/dashboard/ui.html dist/src/browser/dashboard/ui.html",
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",
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",
84
84
  "test": "vitest run",
85
85
  "test:pattern-store": "npx tsx src/transfer/store/tests/standalone-test.ts",
86
86
  "prepublishOnly": "cp ../../../README.md ./README.md",
@@ -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`);