@monoes/monomindcli 1.9.17 → 1.10.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.
- package/.claude/commands/mastermind/_repeat.md +182 -39
- package/.claude/commands/mastermind/architect.md +17 -11
- package/.claude/commands/mastermind/brain.md +4 -0
- package/.claude/commands/mastermind/build.md +4 -0
- package/.claude/commands/mastermind/content.md +4 -0
- package/.claude/commands/mastermind/createorg.md +5 -3
- package/.claude/commands/mastermind/finance.md +4 -0
- package/.claude/commands/mastermind/idea.md +4 -0
- package/.claude/commands/mastermind/marketing.md +4 -0
- package/.claude/commands/mastermind/master.md +63 -37
- package/.claude/commands/mastermind/ops.md +4 -0
- package/.claude/commands/mastermind/release.md +4 -0
- package/.claude/commands/mastermind/research.md +4 -0
- package/.claude/commands/mastermind/review.md +4 -0
- package/.claude/commands/mastermind/runorg.md +5 -3
- package/.claude/commands/mastermind/sales.md +4 -0
- package/.claude/commands/mastermind/techport.md +9 -0
- package/.claude/commands/monomind/do.md +5 -1
- package/.claude/commands/monomind/idea.md +5 -1
- package/.claude/commands/monomind/improve.md +5 -1
- package/.claude/commands/monomind/repeat.md +85 -29
- package/.claude/commands/monomind/review.md +6 -2
- package/.claude/commands/monomind/understand.md +10 -8
- package/.claude/helpers/extras-registry.json +235 -235
- package/.claude/helpers/graphify-freshen.cjs +13 -1
- package/.claude/helpers/hook-handler.cjs +1 -1
- package/.claude/helpers/router.cjs +4 -1
- package/.claude/skills/mastermind/_protocol.md +28 -21
- package/.claude/skills/mastermind/access.md +236 -0
- package/.claude/skills/mastermind/activity.md +191 -0
- package/.claude/skills/mastermind/adapter-manager.md +259 -0
- package/.claude/skills/mastermind/adapters.md +204 -0
- package/.claude/skills/mastermind/agent-detail.md +242 -0
- package/.claude/skills/mastermind/agents.md +178 -0
- package/.claude/skills/mastermind/approval-detail.md +259 -0
- package/.claude/skills/mastermind/approve.md +181 -0
- package/.claude/skills/mastermind/architect.md +24 -8
- package/.claude/skills/mastermind/backup.md +197 -0
- package/.claude/skills/mastermind/bootstrap.md +190 -0
- package/.claude/skills/mastermind/budgets.md +237 -0
- package/.claude/skills/mastermind/companies.md +256 -0
- package/.claude/skills/mastermind/costs.md +151 -0
- package/.claude/skills/mastermind/createorg.md +23 -5
- package/.claude/skills/mastermind/diagnose.md +249 -0
- package/.claude/skills/mastermind/env.md +198 -0
- package/.claude/skills/mastermind/environments.md +250 -0
- package/.claude/skills/mastermind/export.md +324 -0
- package/.claude/skills/mastermind/goal-detail.md +255 -0
- package/.claude/skills/mastermind/goals.md +149 -0
- package/.claude/skills/mastermind/heartbeat.md +164 -0
- package/.claude/skills/mastermind/idea.md +250 -122
- package/.claude/skills/mastermind/import.md +281 -0
- package/.claude/skills/mastermind/inbox.md +214 -0
- package/.claude/skills/mastermind/instance-settings.md +315 -0
- package/.claude/skills/mastermind/instance.md +231 -0
- package/.claude/skills/mastermind/invite-landing.md +227 -0
- package/.claude/skills/mastermind/invites.md +254 -0
- package/.claude/skills/mastermind/issue-detail.md +291 -0
- package/.claude/skills/mastermind/issues.md +235 -0
- package/.claude/skills/mastermind/join-queue.md +170 -0
- package/.claude/skills/mastermind/liveness.md +392 -0
- package/.claude/skills/mastermind/memory.md +321 -0
- package/.claude/skills/mastermind/my-issues.md +146 -0
- package/.claude/skills/mastermind/new-agent.md +241 -0
- package/.claude/skills/mastermind/org-chart.md +207 -0
- package/.claude/skills/mastermind/org-settings.md +217 -0
- package/.claude/skills/mastermind/plan-to-tasks.md +136 -0
- package/.claude/skills/mastermind/plugin-manager.md +241 -0
- package/.claude/skills/mastermind/plugin-settings.md +273 -0
- package/.claude/skills/mastermind/plugins.md +190 -0
- package/.claude/skills/mastermind/profile.md +187 -0
- package/.claude/skills/mastermind/project-detail.md +249 -0
- package/.claude/skills/mastermind/project-workspace.md +244 -0
- package/.claude/skills/mastermind/projects.md +164 -0
- package/.claude/skills/mastermind/routine-detail.md +253 -0
- package/.claude/skills/mastermind/routines.md +202 -0
- package/.claude/skills/mastermind/runorg.md +74 -9
- package/.claude/skills/mastermind/search.md +186 -0
- package/.claude/skills/mastermind/secrets.md +199 -0
- package/.claude/skills/mastermind/skills.md +156 -0
- package/.claude/skills/mastermind/tasks.md +149 -0
- package/.claude/skills/mastermind/techport.md +5 -5
- package/.claude/skills/mastermind/threads.md +259 -0
- package/.claude/skills/mastermind/tree-control.md +250 -0
- package/.claude/skills/mastermind/wiki.md +314 -0
- package/.claude/skills/mastermind/workspace-detail.md +317 -0
- package/.claude/skills/mastermind/workspaces.md +261 -0
- package/.claude/skills/mastermind/worktree.md +187 -0
- package/dist/src/init/executor.js +8 -8
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/statusline-generator.d.ts.map +1 -1
- package/dist/src/init/statusline-generator.js +12 -0
- package/dist/src/init/statusline-generator.js.map +1 -1
- package/dist/src/ui/.monomind/data/ranked-context.json +1 -1
- package/dist/src/ui/.monomind/loops/mastermind-review-1778664132789.json +16 -0
- package/dist/src/ui/.monomind/sessions/current.json +5 -5
- package/dist/src/ui/.monomind/sessions/session-1776778451399.json +15 -0
- package/dist/src/ui/dashboard.html +3030 -181
- package/dist/src/ui/data/mastermind-events.jsonl +8 -0
- package/dist/src/ui/data/mastermind-sessions.json +1 -0
- package/dist/src/ui/server.mjs +738 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/.claude/skills/.monomind/data/ranked-context.json +0 -5
- package/.claude/skills/.monomind/sessions/current.json +0 -13
- package/.claude/skills/.monomind/sessions/session-1777829336455.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777831614725.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777832095857.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777839814183.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777841847131.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777843309463.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777880867159.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777881884593.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777884090471.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777884808221.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777885672155.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777886852818.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777896532690.json +0 -15
package/dist/src/ui/server.mjs
CHANGED
|
@@ -388,6 +388,56 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
388
388
|
return;
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
// ------------------------------------------------------- GET /api/adrs
|
|
392
|
+
if (req.method === 'GET' && url.startsWith('/api/adrs')) {
|
|
393
|
+
try {
|
|
394
|
+
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
395
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
396
|
+
const d = path.resolve(dir || process.cwd());
|
|
397
|
+
|
|
398
|
+
const adrDirs = [
|
|
399
|
+
{ path: path.join(d, 'docs', 'adrs'), group: 'all' },
|
|
400
|
+
];
|
|
401
|
+
|
|
402
|
+
const adrs = [];
|
|
403
|
+
for (const { path: adrDir, group } of adrDirs) {
|
|
404
|
+
if (!fs.existsSync(adrDir)) continue;
|
|
405
|
+
const files = fs.readdirSync(adrDir).filter(f => f.endsWith('.md') && f !== 'README.md' && f !== 'v3-adrs.md' && f !== 'SECURITY-REVIEW-SUMMARY.md');
|
|
406
|
+
for (const fname of files.sort()) {
|
|
407
|
+
const resolvedGroup = /^ADR-G/i.test(fname) ? 'guidance' : 'implementation';
|
|
408
|
+
try {
|
|
409
|
+
const raw = fs.readFileSync(path.join(adrDir, fname), 'utf8');
|
|
410
|
+
const titleMatch = raw.match(/^#\s+(.+)$/m);
|
|
411
|
+
const header = raw.split('\n').slice(0, 20).join('\n');
|
|
412
|
+
const statusTableMatch = header.match(/^\|\s*\*{0,2}Status\*{0,2}\s*\|\s*\*{0,2}([^|*\n]{2,40}?)\*{0,2}\s*\|/im);
|
|
413
|
+
const statusInlineMatch = header.match(/\*\*Status[:\s]+\*?\*?\s*(Accepted|Implemented|Proposed|Superseded|Deprecated|Draft|Rejected|Complete|Active|Retired)[^*]*/i);
|
|
414
|
+
const statusMatch = statusTableMatch || statusInlineMatch;
|
|
415
|
+
const dateInlineMatch = header.match(/\*\*Date[:\s]+\*?\*?\s*([0-9]{4}-[0-9]{2}-[0-9]{2})/i);
|
|
416
|
+
const dateMatch = raw.match(/\|\s*\*{0,2}Date\*{0,2}\s*\|\s*\*{0,2}([^|*\n]+?)\*{0,2}\s*\|/i) || dateInlineMatch || raw.match(/Date[:\s]+([0-9]{4}-[0-9]{2}-[0-9]{2})/);
|
|
417
|
+
const numMatch = fname.match(/ADR-([A-Z]*[0-9]+)/i);
|
|
418
|
+
const summaryMatch = raw.match(/##\s+(?:Context|Summary|Problem Statement)[^\n]*\n+([\s\S]{20,300})/i);
|
|
419
|
+
adrs.push({
|
|
420
|
+
number: numMatch ? 'ADR-' + numMatch[1] : fname.replace('.md', ''),
|
|
421
|
+
title: titleMatch ? titleMatch[1].replace(/^ADR-[A-Z0-9-]+[:\s]+/i, '').trim() : fname.replace('.md', ''),
|
|
422
|
+
status: statusMatch ? statusMatch[1].trim() : 'Unknown',
|
|
423
|
+
date: dateMatch ? dateMatch[1].trim() : null,
|
|
424
|
+
summary: summaryMatch ? summaryMatch[1].replace(/\n/g, ' ').replace(/\s+/g, ' ').trim() : null,
|
|
425
|
+
group: resolvedGroup,
|
|
426
|
+
file: fname,
|
|
427
|
+
});
|
|
428
|
+
} catch { /* skip unreadable */ }
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
433
|
+
res.end(JSON.stringify({ adrs }));
|
|
434
|
+
} catch (err) {
|
|
435
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
436
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
437
|
+
}
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
391
441
|
// ------------------------------------------------------- GET /api/memory-files
|
|
392
442
|
if (req.method === 'GET' && url === '/api/memory-files') {
|
|
393
443
|
try {
|
|
@@ -2138,6 +2188,655 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2138
2188
|
return;
|
|
2139
2189
|
}
|
|
2140
2190
|
|
|
2191
|
+
// GET /api/org/:name — ORG ROOM: rich org data (config + state + tasks + routines + goals)
|
|
2192
|
+
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}$/i.test(url)) {
|
|
2193
|
+
try {
|
|
2194
|
+
const orgName = decodeURIComponent(url.slice('/api/org/'.length));
|
|
2195
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2196
|
+
const d = projectDir || process.cwd();
|
|
2197
|
+
const orgsDir = path.join(d, '.monomind', 'orgs');
|
|
2198
|
+
|
|
2199
|
+
const readJsonSafe = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
|
|
2200
|
+
|
|
2201
|
+
const configFile = path.join(orgsDir, `${orgName}.json`);
|
|
2202
|
+
if (!fs.existsSync(configFile)) { res.writeHead(404); res.end('{"error":"org not found"}'); return; }
|
|
2203
|
+
const config = readJsonSafe(configFile);
|
|
2204
|
+
|
|
2205
|
+
const state = readJsonSafe(path.join(orgsDir, `${orgName}-state.json`)) || { agents: {} };
|
|
2206
|
+
const goalsData = readJsonSafe(path.join(orgsDir, `${orgName}-goals.json`)) || { goals: [] };
|
|
2207
|
+
const routinesData = readJsonSafe(path.join(orgsDir, `${orgName}-routines.json`)) || { routines: [] };
|
|
2208
|
+
const approvalsData = readJsonSafe(path.join(orgsDir, `${orgName}-approvals.json`)) || { approvals: [] };
|
|
2209
|
+
|
|
2210
|
+
// Check running status from stop file absence + state
|
|
2211
|
+
const stopFile = path.join(orgsDir, '.stops', `${orgName}.stop`);
|
|
2212
|
+
const running = !fs.existsSync(stopFile) && Object.values(state.agents || {}).some(a => a.status === 'running');
|
|
2213
|
+
|
|
2214
|
+
const result = { config, state, goals: goalsData.goals, routines: routinesData.routines,
|
|
2215
|
+
approvals: approvalsData.approvals, running, tasks: { todo: [], doing: [], done: [] } };
|
|
2216
|
+
|
|
2217
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2218
|
+
res.end(JSON.stringify(result));
|
|
2219
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
// GET /api/org/:name/activity — recent org events from mastermind-events.jsonl
|
|
2224
|
+
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/activity$/i.test(url)) {
|
|
2225
|
+
try {
|
|
2226
|
+
const parts = url.split('/');
|
|
2227
|
+
const orgName = decodeURIComponent(parts[3]);
|
|
2228
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('[]'); return; }
|
|
2229
|
+
const d = projectDir || process.cwd();
|
|
2230
|
+
const eventsFile = path.join(d, 'data', 'mastermind-events.jsonl');
|
|
2231
|
+
let events = [];
|
|
2232
|
+
if (fs.existsSync(eventsFile)) {
|
|
2233
|
+
const lines = fs.readFileSync(eventsFile, 'utf8').split('\n').filter(Boolean);
|
|
2234
|
+
events = lines.slice(-200).map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean)
|
|
2235
|
+
.filter(e => e.org === orgName || e.session && typeof e.org === 'undefined')
|
|
2236
|
+
.filter(e => e.org === orgName)
|
|
2237
|
+
.reverse().slice(0, 100);
|
|
2238
|
+
}
|
|
2239
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2240
|
+
res.end(JSON.stringify(events));
|
|
2241
|
+
} catch(_) { res.writeHead(500); res.end('[]'); }
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
// GET /api/org/:name/projects — org projects from projects json file
|
|
2246
|
+
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/projects$/i.test(url)) {
|
|
2247
|
+
try {
|
|
2248
|
+
const parts = url.split('/');
|
|
2249
|
+
const orgName = decodeURIComponent(parts[3]);
|
|
2250
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('[]'); return; }
|
|
2251
|
+
const d = projectDir || process.cwd();
|
|
2252
|
+
const projFile = path.join(d, '.monomind', 'orgs', `${orgName}-projects.json`);
|
|
2253
|
+
if (!fs.existsSync(projFile)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end('[]'); return; }
|
|
2254
|
+
const data = JSON.parse(fs.readFileSync(projFile, 'utf8'));
|
|
2255
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2256
|
+
res.end(JSON.stringify(data.projects || []));
|
|
2257
|
+
} catch(_) { res.writeHead(500); res.end('[]'); }
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
// GET /api/org/:name/members — org member list and join requests
|
|
2262
|
+
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/members$/i.test(url)) {
|
|
2263
|
+
try {
|
|
2264
|
+
const parts = url.split('/');
|
|
2265
|
+
const orgName = decodeURIComponent(parts[3]);
|
|
2266
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
|
|
2267
|
+
const d = projectDir || process.cwd();
|
|
2268
|
+
const membersFile = path.join(d, '.monomind', 'orgs', `${orgName}-members.json`);
|
|
2269
|
+
if (!fs.existsSync(membersFile)) {
|
|
2270
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2271
|
+
res.end('{"members":[],"join_requests":[]}');
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
const data = JSON.parse(fs.readFileSync(membersFile, 'utf8'));
|
|
2275
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2276
|
+
res.end(JSON.stringify(data));
|
|
2277
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
// GET /api/org/:name/adapters — org adapter registry
|
|
2282
|
+
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/adapters$/i.test(url)) {
|
|
2283
|
+
try {
|
|
2284
|
+
const parts = url.split('/');
|
|
2285
|
+
const orgName = decodeURIComponent(parts[3]);
|
|
2286
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
|
|
2287
|
+
const d = projectDir || process.cwd();
|
|
2288
|
+
const adaptersFile = path.join(d, '.monomind', 'orgs', `${orgName}-adapters.json`);
|
|
2289
|
+
if (!fs.existsSync(adaptersFile)) {
|
|
2290
|
+
// Return defaults derived from org config if available
|
|
2291
|
+
const orgFile = path.join(d, '.monomind', 'orgs', `${orgName}.json`);
|
|
2292
|
+
let defaultAdapter = 'claude-sonnet-4-6';
|
|
2293
|
+
try { defaultAdapter = JSON.parse(fs.readFileSync(orgFile, 'utf8'))?.run_config?.ceo_adapter || defaultAdapter; } catch(_) {}
|
|
2294
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2295
|
+
res.end(JSON.stringify({ default_adapter: defaultAdapter, adapters: [
|
|
2296
|
+
{ type: 'claude-local', label: 'Claude (local CLI)', source: 'built-in', disabled: false, modelsCount: 3 },
|
|
2297
|
+
{ type: 'gemini-local', label: 'Gemini (local)', source: 'built-in', disabled: false, modelsCount: 1 },
|
|
2298
|
+
{ type: 'http', label: 'HTTP Adapter', source: 'built-in', disabled: true, modelsCount: 0 },
|
|
2299
|
+
]}));
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
const data = JSON.parse(fs.readFileSync(adaptersFile, 'utf8'));
|
|
2303
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2304
|
+
res.end(JSON.stringify(data));
|
|
2305
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
// GET /api/org/:name/skills — list skills from .claude/skills/ mapped to org roles
|
|
2310
|
+
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/skills$/i.test(url)) {
|
|
2311
|
+
try {
|
|
2312
|
+
const parts = url.split('/');
|
|
2313
|
+
const orgName = decodeURIComponent(parts[3]);
|
|
2314
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
|
|
2315
|
+
const d = projectDir || process.cwd();
|
|
2316
|
+
const skillsDir = path.join(d, '.claude', 'skills');
|
|
2317
|
+
const orgFile = path.join(d, '.monomind', 'orgs', `${orgName}.json`);
|
|
2318
|
+
|
|
2319
|
+
// Scan skills directory
|
|
2320
|
+
const skills = [];
|
|
2321
|
+
if (fs.existsSync(skillsDir)) {
|
|
2322
|
+
const scanDir = (dir, prefix) => {
|
|
2323
|
+
try {
|
|
2324
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
2325
|
+
if (entry.isDirectory()) { scanDir(path.join(dir, entry.name), `${entry.name}:`); }
|
|
2326
|
+
else if (entry.name.endsWith('.md') && !entry.name.startsWith('_')) {
|
|
2327
|
+
const slug = entry.name.replace(/\.md$/, '');
|
|
2328
|
+
const content = fs.readFileSync(path.join(dir, entry.name), 'utf8').slice(0, 500);
|
|
2329
|
+
const typeMatch = content.match(/^type:\s*(.+)$/m);
|
|
2330
|
+
const modeMatch = content.match(/^default_mode:\s*(.+)$/m);
|
|
2331
|
+
const descMatch = content.match(/^description:\s*(.+)$/m);
|
|
2332
|
+
skills.push({
|
|
2333
|
+
name: `${prefix}${slug}`,
|
|
2334
|
+
slug,
|
|
2335
|
+
type: typeMatch ? typeMatch[1].trim() : 'skill',
|
|
2336
|
+
default_mode: modeMatch ? modeMatch[1].trim() : 'auto',
|
|
2337
|
+
description: descMatch ? descMatch[1].trim() : '',
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
} catch(_) {}
|
|
2342
|
+
};
|
|
2343
|
+
scanDir(skillsDir, '');
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
// Map skills enabled per role from org config
|
|
2347
|
+
let roleSkillMap = {};
|
|
2348
|
+
if (fs.existsSync(orgFile)) {
|
|
2349
|
+
try {
|
|
2350
|
+
const config = JSON.parse(fs.readFileSync(orgFile, 'utf8'));
|
|
2351
|
+
for (const role of (config.roles || [])) {
|
|
2352
|
+
roleSkillMap[role.id] = role.skills || [];
|
|
2353
|
+
}
|
|
2354
|
+
} catch(_) {}
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2358
|
+
res.end(JSON.stringify({ skills, role_skill_map: roleSkillMap }));
|
|
2359
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
// GET /api/org/:name/search?q=<query> — fuzzy search across org data
|
|
2364
|
+
if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/search(\?.*)?$/i.test(url)) {
|
|
2365
|
+
try {
|
|
2366
|
+
const urlObj = new URL(`http://x${url}`);
|
|
2367
|
+
const orgName = decodeURIComponent(urlObj.pathname.split('/')[3]);
|
|
2368
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
|
|
2369
|
+
const q = (urlObj.searchParams.get('q') || '').toLowerCase().trim();
|
|
2370
|
+
if (!q || q.length < 2) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end('{"hits":[]}'); return; }
|
|
2371
|
+
|
|
2372
|
+
const d = projectDir || process.cwd();
|
|
2373
|
+
const orgsDir = path.join(d, '.monomind', 'orgs');
|
|
2374
|
+
const readJ = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
|
|
2375
|
+
|
|
2376
|
+
const hits = [];
|
|
2377
|
+
const match = (str) => str && str.toLowerCase().includes(q);
|
|
2378
|
+
|
|
2379
|
+
// Agents
|
|
2380
|
+
const config = readJ(path.join(orgsDir, `${orgName}.json`));
|
|
2381
|
+
for (const role of (config?.roles || [])) {
|
|
2382
|
+
if (match(role.id) || match(role.title) || (role.responsibilities || []).some(r => match(r))) {
|
|
2383
|
+
hits.push({ type: 'agent', id: role.id, title: role.title, meta: role.agent_type });
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
// Goals
|
|
2388
|
+
const goals = readJ(path.join(orgsDir, `${orgName}-goals.json`));
|
|
2389
|
+
for (const g of (goals?.goals || [])) {
|
|
2390
|
+
if (match(g.title) || match(g.description)) {
|
|
2391
|
+
hits.push({ type: 'goal', id: g.id, title: g.title, meta: g.status || 'open' });
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
// Routines
|
|
2396
|
+
const routines = readJ(path.join(orgsDir, `${orgName}-routines.json`));
|
|
2397
|
+
for (const r of (routines?.routines || [])) {
|
|
2398
|
+
if (match(r.name) || match(r.description)) {
|
|
2399
|
+
hits.push({ type: 'routine', id: r.name, title: r.name, meta: r.schedule || '' });
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
// Approvals
|
|
2404
|
+
const approvals = readJ(path.join(orgsDir, `${orgName}-approvals.json`));
|
|
2405
|
+
for (const a of (approvals?.approvals || [])) {
|
|
2406
|
+
if (match(a.title) || match(a.action) || match(a.agent_id)) {
|
|
2407
|
+
hits.push({ type: 'approval', id: a.id, title: a.title, meta: a.status });
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
// Projects
|
|
2412
|
+
const projects = readJ(path.join(orgsDir, `${orgName}-projects.json`));
|
|
2413
|
+
for (const p of (projects?.projects || [])) {
|
|
2414
|
+
if (match(p.name) || match(p.description)) {
|
|
2415
|
+
hits.push({ type: 'project', id: p.id || p.name, title: p.name, meta: p.status || 'active' });
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
// Recent activity events
|
|
2420
|
+
const eventsFile = path.join(d, 'data', 'mastermind-events.jsonl');
|
|
2421
|
+
if (fs.existsSync(eventsFile)) {
|
|
2422
|
+
const lines = fs.readFileSync(eventsFile, 'utf8').split('\n').filter(Boolean).slice(-500);
|
|
2423
|
+
for (const l of lines) {
|
|
2424
|
+
try {
|
|
2425
|
+
const e = JSON.parse(l);
|
|
2426
|
+
if (e.org === orgName && match(JSON.stringify(e))) {
|
|
2427
|
+
hits.push({ type: 'event', id: String(e.ts), title: e.type, meta: e.role || e.task || '' });
|
|
2428
|
+
if (hits.length >= 50) break;
|
|
2429
|
+
}
|
|
2430
|
+
} catch(_) {}
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2435
|
+
res.end(JSON.stringify({ q, hits: hits.slice(0, 50) }));
|
|
2436
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
// GET /api/org/:name/issues — org task/issue list from issues file
|
|
2441
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/issues$/i)) {
|
|
2442
|
+
try {
|
|
2443
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
2444
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2445
|
+
const issuesPath = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}-issues.json`);
|
|
2446
|
+
let payload = { issues: [] };
|
|
2447
|
+
try {
|
|
2448
|
+
const raw = JSON.parse(fs.readFileSync(issuesPath, 'utf8'));
|
|
2449
|
+
payload.issues = (raw.issues || []).map(i => ({
|
|
2450
|
+
id: i.id, slug: i.slug, title: i.title, status: i.status || 'open',
|
|
2451
|
+
priority: i.priority || 'medium', assignee_id: i.assignee_id || null,
|
|
2452
|
+
project_id: i.project_id || null, parent_id: i.parent_id || null,
|
|
2453
|
+
created_at: i.created_at, updated_at: i.updated_at
|
|
2454
|
+
}));
|
|
2455
|
+
} catch(_) { /* file missing is fine */ }
|
|
2456
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2457
|
+
res.end(JSON.stringify(payload));
|
|
2458
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
// GET /api/org/:name/health — aggregate org health metrics
|
|
2463
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/health$/i)) {
|
|
2464
|
+
try {
|
|
2465
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
2466
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2467
|
+
const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
|
|
2468
|
+
|
|
2469
|
+
let agentsRunning = 0, agentsIdle = 0, openIssues = 0, inProgressIssues = 0;
|
|
2470
|
+
let budgetUsedTokens = 0, budgetMaxTokens = 0;
|
|
2471
|
+
let successRuns = 0, totalRuns = 0;
|
|
2472
|
+
|
|
2473
|
+
// State: agent statuses
|
|
2474
|
+
try {
|
|
2475
|
+
const state = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-state.json`), 'utf8'));
|
|
2476
|
+
const agents = state.agents || {};
|
|
2477
|
+
Object.values(agents).forEach(a => {
|
|
2478
|
+
if (a.status === 'running') agentsRunning++;
|
|
2479
|
+
else agentsIdle++;
|
|
2480
|
+
budgetUsedTokens += (a.tokens_used || 0);
|
|
2481
|
+
});
|
|
2482
|
+
} catch(_) {}
|
|
2483
|
+
|
|
2484
|
+
// Budget cap from org config
|
|
2485
|
+
try {
|
|
2486
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(base, `${orgName}.json`), 'utf8'));
|
|
2487
|
+
budgetMaxTokens = cfg.run_config?.budget_tokens || cfg.budget_tokens || 0;
|
|
2488
|
+
} catch(_) {}
|
|
2489
|
+
|
|
2490
|
+
// Issues: open count
|
|
2491
|
+
try {
|
|
2492
|
+
const iss = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-issues.json`), 'utf8'));
|
|
2493
|
+
openIssues = (iss.issues || []).filter(i => i.status === 'open').length;
|
|
2494
|
+
inProgressIssues = (iss.issues || []).filter(i => i.status === 'in_progress').length;
|
|
2495
|
+
} catch(_) {}
|
|
2496
|
+
|
|
2497
|
+
// Activity: 7-day success rate
|
|
2498
|
+
try {
|
|
2499
|
+
const actPath = path.join(base, `${orgName}-activity.jsonl`);
|
|
2500
|
+
const lines = fs.readFileSync(actPath, 'utf8').split('\n').filter(Boolean);
|
|
2501
|
+
const cutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
2502
|
+
lines.forEach(line => {
|
|
2503
|
+
try {
|
|
2504
|
+
const ev = JSON.parse(line);
|
|
2505
|
+
if (!ev.ts || ev.ts < cutoff) return;
|
|
2506
|
+
totalRuns++;
|
|
2507
|
+
if (ev.type && ev.type.includes('complete')) successRuns++;
|
|
2508
|
+
} catch(_) {}
|
|
2509
|
+
});
|
|
2510
|
+
} catch(_) {}
|
|
2511
|
+
|
|
2512
|
+
const budgetUsedPct = budgetMaxTokens > 0 ? Math.round((budgetUsedTokens / budgetMaxTokens) * 100) : null;
|
|
2513
|
+
const successRate = totalRuns > 0 ? Math.round((successRuns / totalRuns) * 100) : null;
|
|
2514
|
+
|
|
2515
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2516
|
+
res.end(JSON.stringify({
|
|
2517
|
+
agents_running: agentsRunning,
|
|
2518
|
+
agents_idle: agentsIdle,
|
|
2519
|
+
open_issues: openIssues,
|
|
2520
|
+
in_progress_issues: inProgressIssues,
|
|
2521
|
+
budget_used_tokens: budgetUsedTokens,
|
|
2522
|
+
budget_max_tokens: budgetMaxTokens,
|
|
2523
|
+
budget_used_pct: budgetUsedPct,
|
|
2524
|
+
run_success_rate_7d: successRate,
|
|
2525
|
+
total_runs_7d: totalRuns,
|
|
2526
|
+
}));
|
|
2527
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
// GET /api/org/:name/environments — org execution environments (strips key material)
|
|
2532
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/environments$/i)) {
|
|
2533
|
+
try {
|
|
2534
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
2535
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2536
|
+
const envsPath = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}-environments.json`);
|
|
2537
|
+
let payload = { environments: [], default_env: null };
|
|
2538
|
+
try {
|
|
2539
|
+
const raw = JSON.parse(fs.readFileSync(envsPath, 'utf8'));
|
|
2540
|
+
// Strip any accidental key_material or private_key fields — never send to browser
|
|
2541
|
+
payload.default_env = raw.default_env || null;
|
|
2542
|
+
payload.environments = (raw.environments || []).map(e => {
|
|
2543
|
+
const safe = { ...e };
|
|
2544
|
+
delete safe.key_material;
|
|
2545
|
+
delete safe.private_key;
|
|
2546
|
+
delete safe.ssh_key;
|
|
2547
|
+
delete safe.password;
|
|
2548
|
+
return safe;
|
|
2549
|
+
});
|
|
2550
|
+
} catch(_) { /* file missing is fine — return empty */ }
|
|
2551
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2552
|
+
res.end(JSON.stringify(payload));
|
|
2553
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// GET /api/org/:name/workspaces — org workspaces cross-referenced with worktree registry
|
|
2558
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/workspaces$/i)) {
|
|
2559
|
+
try {
|
|
2560
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
2561
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2562
|
+
const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
|
|
2563
|
+
let payload = { workspaces: [] };
|
|
2564
|
+
try {
|
|
2565
|
+
const wsRaw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-workspaces.json`), 'utf8'));
|
|
2566
|
+
const workspaces = wsRaw.workspaces || [];
|
|
2567
|
+
// Optionally cross-reference worktree registry for branch/status enrichment
|
|
2568
|
+
let worktreeMap = {};
|
|
2569
|
+
try {
|
|
2570
|
+
const wtRaw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-worktrees.json`), 'utf8'));
|
|
2571
|
+
(wtRaw.worktrees || []).forEach(wt => { worktreeMap[wt.path] = wt; });
|
|
2572
|
+
} catch(_) { /* no worktree registry, that's fine */ }
|
|
2573
|
+
payload.workspaces = workspaces.map(w => {
|
|
2574
|
+
const wt = w.worktree_path ? worktreeMap[w.worktree_path] : null;
|
|
2575
|
+
return wt ? { ...w, branch: w.branch || wt.branch || w.branch } : w;
|
|
2576
|
+
});
|
|
2577
|
+
} catch(_) { /* file missing is fine */ }
|
|
2578
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2579
|
+
res.end(JSON.stringify(payload));
|
|
2580
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2581
|
+
return;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
// GET /api/org/:name/invites — active invites + pending join requests
|
|
2585
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/invites$/i)) {
|
|
2586
|
+
try {
|
|
2587
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
2588
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2589
|
+
const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
|
|
2590
|
+
let payload = { invites: [], join_requests: [] };
|
|
2591
|
+
try {
|
|
2592
|
+
const raw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-members.json`), 'utf8'));
|
|
2593
|
+
const all = raw.join_requests || [];
|
|
2594
|
+
payload.invites = all.filter(r => r.type === 'invite' && r.status === 'pending')
|
|
2595
|
+
.map(r => ({ id: r.id, token: r.token ? r.token.slice(0, 8) + '…' : r.id, role: r.role || 'operator', createdAt: r.createdAt || null, status: r.status }));
|
|
2596
|
+
payload.join_requests = all.filter(r => r.type !== 'invite' && r.status === 'pending_approval')
|
|
2597
|
+
.map(r => ({ id: r.id, requestType: r.requestType || 'human', role: r.role || 'viewer', createdAt: r.createdAt || null, message: r.message || '' }));
|
|
2598
|
+
} catch(_) { /* members file missing */ }
|
|
2599
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2600
|
+
res.end(JSON.stringify(payload));
|
|
2601
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2602
|
+
return;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
// GET /api/org/:name/plugins — plugins from registry filtered/merged with org overrides
|
|
2606
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/plugins$/i)) {
|
|
2607
|
+
try {
|
|
2608
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
2609
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2610
|
+
const base = path.join(projectDir || process.cwd(), '.monomind');
|
|
2611
|
+
let plugins = [];
|
|
2612
|
+
try {
|
|
2613
|
+
const reg = JSON.parse(fs.readFileSync(path.join(base, 'plugins', 'registry.json'), 'utf8'));
|
|
2614
|
+
plugins = reg.plugins || [];
|
|
2615
|
+
// Strip sensitive config fields from output
|
|
2616
|
+
plugins = plugins.map(p => {
|
|
2617
|
+
const safe = { ...p };
|
|
2618
|
+
if (safe.config) {
|
|
2619
|
+
safe.config = Object.fromEntries(
|
|
2620
|
+
Object.entries(safe.config).map(([k, v]) =>
|
|
2621
|
+
(/key|token|secret|password|api/i.test(k) ? [k, '***'] : [k, v])
|
|
2622
|
+
)
|
|
2623
|
+
);
|
|
2624
|
+
}
|
|
2625
|
+
return safe;
|
|
2626
|
+
});
|
|
2627
|
+
} catch(_) { /* no global registry */ }
|
|
2628
|
+
// Merge org-level overrides
|
|
2629
|
+
try {
|
|
2630
|
+
const orgPlugins = JSON.parse(fs.readFileSync(path.join(base, 'orgs', `${orgName}-plugins.json`), 'utf8'));
|
|
2631
|
+
const overrideMap = {};
|
|
2632
|
+
(orgPlugins.plugins || []).forEach(p => { overrideMap[p.id] = p; });
|
|
2633
|
+
if (Object.keys(overrideMap).length) {
|
|
2634
|
+
plugins = plugins.map(p => overrideMap[p.id] ? { ...p, ...overrideMap[p.id], _orgOverride: true } : p);
|
|
2635
|
+
}
|
|
2636
|
+
} catch(_) { /* no org-level overrides */ }
|
|
2637
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2638
|
+
res.end(JSON.stringify({ plugins }));
|
|
2639
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
// GET /api/org/:name/my-issues — open + in_progress issues (self-assignable queue)
|
|
2644
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/my-issues$/i)) {
|
|
2645
|
+
try {
|
|
2646
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
2647
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2648
|
+
const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
|
|
2649
|
+
let payload = { issues: [] };
|
|
2650
|
+
try {
|
|
2651
|
+
const raw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-issues.json`), 'utf8'));
|
|
2652
|
+
// Return open + in_progress issues — the "my issues" queue for the operator
|
|
2653
|
+
payload.issues = (raw.issues || [])
|
|
2654
|
+
.filter(i => i.status === 'open' || i.status === 'in_progress')
|
|
2655
|
+
.map(i => ({
|
|
2656
|
+
id: i.id,
|
|
2657
|
+
title: i.title || null,
|
|
2658
|
+
status: i.status || 'open',
|
|
2659
|
+
priority: i.priority || 'medium',
|
|
2660
|
+
assigneeId: i.assigneeId || i.assigned_to || null,
|
|
2661
|
+
projectId: i.projectId || i.project_id || null,
|
|
2662
|
+
createdAt: i.createdAt || null,
|
|
2663
|
+
lastActivityAt: i.lastActivityAt || null,
|
|
2664
|
+
}));
|
|
2665
|
+
} catch(_) { /* issues file missing */ }
|
|
2666
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2667
|
+
res.end(JSON.stringify(payload));
|
|
2668
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
// GET /api/org/:name/agents — agents from roles + merged heartbeat state
|
|
2673
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/agents$/i)) {
|
|
2674
|
+
try {
|
|
2675
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
2676
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2677
|
+
const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
|
|
2678
|
+
const readJsonSafe = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
|
|
2679
|
+
const config = readJsonSafe(path.join(base, `${orgName}.json`)) || {};
|
|
2680
|
+
const stateData = readJsonSafe(path.join(base, `${orgName}-state.json`)) || {};
|
|
2681
|
+
const agentState = stateData.agents || stateData.roles
|
|
2682
|
+
? (stateData.agents || Object.fromEntries((stateData.roles||[]).map(r => [r.id, r])))
|
|
2683
|
+
: {};
|
|
2684
|
+
const roles = config.roles || [];
|
|
2685
|
+
const agents = roles.map(r => {
|
|
2686
|
+
const s = agentState[r.id] || {};
|
|
2687
|
+
return {
|
|
2688
|
+
id: r.id,
|
|
2689
|
+
title: r.title || r.id,
|
|
2690
|
+
adapterType: (r.adapter && r.adapter.type) || null,
|
|
2691
|
+
adapterModel: (r.adapter && r.adapter.model) || null,
|
|
2692
|
+
governance: r.governance || null,
|
|
2693
|
+
reportsTo: r.reports_to || null,
|
|
2694
|
+
status: s.status || 'idle',
|
|
2695
|
+
lastHeartbeat: s.last_heartbeat || s.lastHeartbeat || null,
|
|
2696
|
+
tokensIn: s.tokens_in || 0,
|
|
2697
|
+
tokensOut: s.tokens_out || 0,
|
|
2698
|
+
skills: r.skills || [],
|
|
2699
|
+
};
|
|
2700
|
+
});
|
|
2701
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2702
|
+
res.end(JSON.stringify({ agents }));
|
|
2703
|
+
} catch(_) { res.writeHead(500); res.end('{"agents":[]}'); }
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
// GET /api/org/:name/approvals — full approvals list with status filter support
|
|
2708
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/approvals(\?.*)?$/i)) {
|
|
2709
|
+
try {
|
|
2710
|
+
const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
|
|
2711
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2712
|
+
const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
|
|
2713
|
+
const readJsonSafe = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
|
|
2714
|
+
const data = readJsonSafe(path.join(base, `${orgName}-approvals.json`)) || { approvals: [] };
|
|
2715
|
+
const approvals = (data.approvals || [])
|
|
2716
|
+
.sort((a, b) => new Date(b.createdAt || 0) - new Date(a.createdAt || 0))
|
|
2717
|
+
.map(a => ({
|
|
2718
|
+
id: a.id,
|
|
2719
|
+
title: a.title || a.action || null,
|
|
2720
|
+
status: a.status || 'pending',
|
|
2721
|
+
agentId: a.agentId || a.agent_id || null,
|
|
2722
|
+
agentTitle: a.agentTitle || null,
|
|
2723
|
+
payload: a.payload || null,
|
|
2724
|
+
createdAt: a.createdAt || null,
|
|
2725
|
+
updatedAt: a.updatedAt || null,
|
|
2726
|
+
resolvedAt: a.resolvedAt || null,
|
|
2727
|
+
resolvedBy: a.resolvedBy || null,
|
|
2728
|
+
}));
|
|
2729
|
+
const pending = approvals.filter(a => a.status === 'pending' || a.status === 'revision_requested').length;
|
|
2730
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2731
|
+
res.end(JSON.stringify({ approvals, pending }));
|
|
2732
|
+
} catch(_) { res.writeHead(500); res.end('{"approvals":[],"pending":0}'); }
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
// GET /api/org/:name/secrets — masked secrets list (NEVER exposes values)
|
|
2737
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/secrets$/i)) {
|
|
2738
|
+
try {
|
|
2739
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
2740
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2741
|
+
const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
|
|
2742
|
+
const secretsDir = path.join(base, '.secrets');
|
|
2743
|
+
const readJsonSafe = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
|
|
2744
|
+
// Read secrets index — NEVER expose actual values
|
|
2745
|
+
const indexFile = path.join(secretsDir, `${orgName}-index.json`);
|
|
2746
|
+
const data = readJsonSafe(indexFile) || { secrets: [] };
|
|
2747
|
+
const secrets = (data.secrets || []).map(s => ({
|
|
2748
|
+
name: s.name,
|
|
2749
|
+
maskedRef: s.maskedRef || `${(s.name||'').substring(0,4)}***`,
|
|
2750
|
+
status: s.status || 'active',
|
|
2751
|
+
createdAt: s.createdAt || null,
|
|
2752
|
+
rotatedAt: s.rotatedAt || null,
|
|
2753
|
+
lastUsedAt: s.lastUsedAt || null,
|
|
2754
|
+
usageCount: s.usageCount || 0,
|
|
2755
|
+
}));
|
|
2756
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2757
|
+
res.end(JSON.stringify({ secrets }));
|
|
2758
|
+
} catch(_) { res.writeHead(500); res.end('{"secrets":[]}'); }
|
|
2759
|
+
return;
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
// GET /api/org/:name/budgets — org and per-agent budget data
|
|
2763
|
+
// Returns: { org_budget: {limit_tokens, limit_usd}, agent_budgets: {agentId: {limit_usd}}, agents: [{id, title, tokens_in, tokens_out, total_cost_usd}] }
|
|
2764
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/budgets$/i)) {
|
|
2765
|
+
try {
|
|
2766
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
2767
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2768
|
+
const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
|
|
2769
|
+
let budgetData = { org_budget: {}, agent_budgets: {}, period: 'monthly', currency: 'USD' };
|
|
2770
|
+
try { budgetData = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-budgets.json`), 'utf8')); } catch(_) {}
|
|
2771
|
+
// Enrich with per-agent spend from state file
|
|
2772
|
+
let agents = [];
|
|
2773
|
+
try {
|
|
2774
|
+
const state = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-state.json`), 'utf8'));
|
|
2775
|
+
agents = (state.roles || []).map(r => ({
|
|
2776
|
+
id: r.id, title: r.title,
|
|
2777
|
+
tokens_in: r.tokens_in || 0, tokens_out: r.tokens_out || 0, total_cost_usd: r.total_cost_usd || 0
|
|
2778
|
+
}));
|
|
2779
|
+
} catch(_) {}
|
|
2780
|
+
// Also include roles from org config if state is empty
|
|
2781
|
+
if (!agents.length) {
|
|
2782
|
+
try {
|
|
2783
|
+
const org = JSON.parse(fs.readFileSync(path.join(base, `${orgName}.json`), 'utf8'));
|
|
2784
|
+
agents = (org.roles || []).map(r => ({ id: r.id, title: r.title, tokens_in: 0, tokens_out: 0, total_cost_usd: 0 }));
|
|
2785
|
+
} catch(_) {}
|
|
2786
|
+
}
|
|
2787
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2788
|
+
res.end(JSON.stringify({ ...budgetData, agents }));
|
|
2789
|
+
} catch(_) { res.writeHead(500); res.end('{"org_budget":{},"agent_budgets":{},"agents":[]}'); }
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
// GET /api/org/:name/threads — conversation threads from threads.jsonl
|
|
2794
|
+
// Returns: { threads: [{id, subject, authorId, authorName, issueId, createdAt, messages:[]}] }
|
|
2795
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/threads$/i)) {
|
|
2796
|
+
try {
|
|
2797
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
2798
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2799
|
+
const threadsFile = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}-threads.jsonl`);
|
|
2800
|
+
let threads = [];
|
|
2801
|
+
try {
|
|
2802
|
+
const lines = fs.readFileSync(threadsFile, 'utf8').split('\n').filter(l => l.trim());
|
|
2803
|
+
threads = lines.map(l => { try { return JSON.parse(l); } catch(_) { return null; } }).filter(Boolean);
|
|
2804
|
+
threads = threads.filter(t => t.type === 'thread' || !t.type);
|
|
2805
|
+
} catch(_) {}
|
|
2806
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2807
|
+
res.end(JSON.stringify({ threads }));
|
|
2808
|
+
} catch(_) { res.writeHead(500); res.end('{"threads":[]}'); }
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
// GET /api/org/:name/join-requests — pending join requests for this org
|
|
2813
|
+
// Returns: { requests: [{id, requesterId, requesterName, type, status, createdAt, resolvedAt}], pending: N }
|
|
2814
|
+
if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/join-requests(\?.*)?$/i)) {
|
|
2815
|
+
try {
|
|
2816
|
+
const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
|
|
2817
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2818
|
+
const joinFile = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}-join-requests.json`);
|
|
2819
|
+
let requests = [];
|
|
2820
|
+
try {
|
|
2821
|
+
const raw = fs.readFileSync(joinFile, 'utf8');
|
|
2822
|
+
const data = JSON.parse(raw);
|
|
2823
|
+
requests = (data.requests || []).map(r => ({
|
|
2824
|
+
id: r.id,
|
|
2825
|
+
requesterId: r.requesterId,
|
|
2826
|
+
requesterName: r.requesterName || r.requesterId,
|
|
2827
|
+
type: r.type || 'human',
|
|
2828
|
+
status: r.status || 'pending_approval',
|
|
2829
|
+
createdAt: r.createdAt,
|
|
2830
|
+
resolvedAt: r.resolvedAt || null,
|
|
2831
|
+
}));
|
|
2832
|
+
} catch(_) {}
|
|
2833
|
+
const pending = requests.filter(r => r.status === 'pending_approval').length;
|
|
2834
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2835
|
+
res.end(JSON.stringify({ requests, pending }));
|
|
2836
|
+
} catch(_) { res.writeHead(500); res.end('{"requests":[],"pending":0}'); }
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2141
2840
|
// POST /api/orgs/:name/stop — send stop signal to a running org
|
|
2142
2841
|
if (req.method === 'POST' && url.match(/^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}\/stop$/i)) {
|
|
2143
2842
|
try {
|
|
@@ -2349,6 +3048,45 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
2349
3048
|
return;
|
|
2350
3049
|
}
|
|
2351
3050
|
|
|
3051
|
+
// GET /api/mastermind/loops — list all active loop state files
|
|
3052
|
+
if (req.method === 'GET' && url === '/api/mastermind/loops') {
|
|
3053
|
+
try {
|
|
3054
|
+
const loopsDir = path.join(projectDir || process.cwd(), '.monomind', 'loops');
|
|
3055
|
+
const loops = [];
|
|
3056
|
+
if (fs.existsSync(loopsDir)) {
|
|
3057
|
+
const files = fs.readdirSync(loopsDir).filter(f => f.endsWith('.json') && !f.includes('-hil'));
|
|
3058
|
+
for (const f of files) {
|
|
3059
|
+
try {
|
|
3060
|
+
const d = JSON.parse(fs.readFileSync(path.join(loopsDir, f), 'utf8'));
|
|
3061
|
+
loops.push(d);
|
|
3062
|
+
} catch(_) {}
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
loops.sort((a, b) => (b.lastRunAt || 0) - (a.lastRunAt || 0));
|
|
3066
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
3067
|
+
res.end(JSON.stringify({ loops }));
|
|
3068
|
+
} catch(_) { res.writeHead(500); res.end('{"loops":[]}'); }
|
|
3069
|
+
return;
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
// GET /api/mastermind/metrics — aggregate system metrics from token-summary and swarm-activity
|
|
3073
|
+
if (req.method === 'GET' && url === '/api/mastermind/metrics') {
|
|
3074
|
+
try {
|
|
3075
|
+
const base = path.join(projectDir || process.cwd(), '.monomind', 'metrics');
|
|
3076
|
+
let tokens = {}, swarm = {}, events = [];
|
|
3077
|
+
try { tokens = JSON.parse(fs.readFileSync(path.join(base, 'token-summary.json'), 'utf8')); } catch(_) {}
|
|
3078
|
+
try { swarm = JSON.parse(fs.readFileSync(path.join(base, 'swarm-activity.json'), 'utf8')); } catch(_) {}
|
|
3079
|
+
try {
|
|
3080
|
+
const evPath = path.join(projectDir || process.cwd(), 'data', 'mastermind-events.jsonl');
|
|
3081
|
+
const lines = fs.readFileSync(evPath, 'utf8').split('\n').filter(l => l.trim()).slice(-20);
|
|
3082
|
+
events = lines.map(l => { try { return JSON.parse(l); } catch(_) { return null; } }).filter(Boolean);
|
|
3083
|
+
} catch(_) {}
|
|
3084
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
3085
|
+
res.end(JSON.stringify({ tokens, swarm, recentEvents: events }));
|
|
3086
|
+
} catch(_) { res.writeHead(500); res.end('{"tokens":{},"swarm":{},"recentEvents":[]}'); }
|
|
3087
|
+
return;
|
|
3088
|
+
}
|
|
3089
|
+
|
|
2352
3090
|
// ------------------------------------------------------------------ 404
|
|
2353
3091
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
2354
3092
|
res.end('Not found');
|