@monoes/monomindcli 1.6.9 → 1.7.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.
@@ -1457,18 +1457,12 @@
1457
1457
  width: 200px; flex-shrink: 0; border-right: 1px solid var(--border);
1458
1458
  padding: 16px 14px; overflow-y: auto; background: rgba(0,0,0,0.12);
1459
1459
  }
1460
- #po-knowledge-canvas-wrap {
1461
- flex: 1; min-width: 0; border-right: 1px solid var(--border);
1462
- display: flex; flex-direction: column; position: relative;
1463
- }
1464
- #po-knowledge-canvas-controls {
1465
- padding: 5px 12px; border-bottom: 1px solid var(--border);
1466
- display: flex; align-items: center; gap: 14px; flex-shrink: 0;
1467
- font-size: 9px; color: var(--muted); letter-spacing: 0.07em;
1468
- }
1469
- #po-knowledge-canvas-controls label { display: flex; align-items: center; gap: 4px; cursor: pointer; }
1470
- #po-kg2-canvas { flex: 1; width: 100%; display: block; }
1471
- #po-knowledge-main { flex: 1; min-width: 240px; overflow-y: auto; padding: 16px 20px; }
1460
+
1461
+ .gf-tool-tab { background: none; border: 1px solid transparent; color: var(--muted); font-family: 'Azeret Mono', monospace; font-size: 8px; letter-spacing: 0.1em; padding: 3px 10px; cursor: pointer; border-radius: 2px; transition: all 0.15s; }
1462
+ .gf-tool-tab:hover { color: var(--text); background: rgba(255,255,255,0.04); }
1463
+ .gf-tool-tab.active { color: var(--teal); border-color: rgba(0,229,200,0.3); background: rgba(0,229,200,0.06); }
1464
+ .gf-view { display: none; }
1465
+ .gf-view.active { display: flex; }
1472
1466
 
1473
1467
  /* Sidebar stat blocks */
1474
1468
  .kg-stat-group { margin-bottom: 18px; }
@@ -1550,7 +1544,7 @@
1550
1544
  <button class="po-tab" onclick="switchPalaceTab('routing')">ROUTING</button>
1551
1545
  <button class="po-tab" onclick="switchPalaceTab('swarm')">SWARM</button>
1552
1546
  <button class="po-tab" onclick="switchPalaceTab('graph')">AGENT GRAPH</button>
1553
- <button class="po-tab" onclick="switchPalaceTab('knowledge')">CODE GRAPH</button>
1547
+ <button class="po-tab" onclick="switchPalaceTab('knowledge')">GRAPHIFY</button>
1554
1548
  </div>
1555
1549
  <div id="po-stats"></div>
1556
1550
  </div>
@@ -1686,15 +1680,71 @@
1686
1680
  <div id="po-knowledge-sidebar">
1687
1681
  <div id="po-knowledge-stats"><div style="color:var(--muted);font-size:10px;">Loading…</div></div>
1688
1682
  </div>
1689
- <div id="po-knowledge-canvas-wrap">
1690
- <div id="po-knowledge-canvas-controls">
1691
- <span id="po-kg2-info">—</span>
1692
- <label><input type="checkbox" id="po-kg2-labels" checked onchange="kgCodeGraph.toggleLabels(this.checked)"> LABELS</label>
1683
+ <div id="po-knowledge-main-area" style="flex:1;display:flex;flex-direction:column;min-width:0;overflow:hidden;">
1684
+ <div id="po-graphify-toolbar" style="display:flex;align-items:center;gap:8px;padding:6px 14px;border-bottom:1px solid var(--border);flex-shrink:0;">
1685
+ <button class="gf-tool-tab active" data-gftab="graph" onclick="switchGraphifyView('graph')">GRAPH</button>
1686
+ <button class="gf-tool-tab" data-gftab="query" onclick="switchGraphifyView('query')">QUERY</button>
1687
+ <button class="gf-tool-tab" data-gftab="explain" onclick="switchGraphifyView('explain')">EXPLAIN</button>
1688
+ <button class="gf-tool-tab" data-gftab="path" onclick="switchGraphifyView('path')">PATH</button>
1689
+ <button class="gf-tool-tab" data-gftab="benchmark" onclick="switchGraphifyView('benchmark')">BENCHMARK</button>
1690
+ <button class="gf-tool-tab" data-gftab="report" onclick="switchGraphifyView('report')">REPORT</button>
1691
+ <span style="flex:1;"></span>
1692
+ <span id="gf-watch-indicator" style="font-size:8px;letter-spacing:0.06em;"></span>
1693
+ <button id="gf-watch-btn" class="kg-action-btn" style="width:auto;padding:3px 10px;margin:0;" onclick="toggleGraphifyWatch()">WATCH</button>
1694
+ </div>
1695
+
1696
+ <!-- GRAPH sub-view (embedded graphify interactive HTML) -->
1697
+ <div id="gf-view-graph" class="gf-view active" style="flex:1;display:flex;flex-direction:column;">
1698
+ <iframe id="gf-graph-iframe" style="flex:1;width:100%;border:none;background:#0f0f1a;"></iframe>
1699
+ </div>
1700
+
1701
+ <!-- QUERY sub-view -->
1702
+ <div id="gf-view-query" class="gf-view" style="flex:1;overflow-y:auto;padding:16px 20px;">
1703
+ <div style="display:flex;gap:8px;align-items:center;margin-bottom:12px;">
1704
+ <input id="gf-query-input" type="text" placeholder="Ask about the codebase…" style="flex:1;background:rgba(255,255,255,0.04);border:1px solid var(--border);border-radius:3px;padding:7px 10px;color:var(--text);font-family:'Azeret Mono',monospace;font-size:11px;outline:none;" onkeydown="if(event.key==='Enter')runGraphifyQuery()">
1705
+ <select id="gf-query-mode" style="background:rgba(255,255,255,0.04);border:1px solid var(--border);border-radius:3px;padding:5px 8px;color:var(--text);font-family:'Azeret Mono',monospace;font-size:9px;">
1706
+ <option value="dfs">DFS</option>
1707
+ <option value="bfs">BFS</option>
1708
+ </select>
1709
+ <input id="gf-query-budget" type="number" value="2000" min="100" max="50000" step="500" style="width:70px;background:rgba(255,255,255,0.04);border:1px solid var(--border);border-radius:3px;padding:5px 6px;color:var(--text);font-family:'Azeret Mono',monospace;font-size:9px;" title="Token budget">
1710
+ <button class="kg-action-btn" style="width:auto;padding:5px 14px;margin:0;" onclick="runGraphifyQuery()">ASK</button>
1711
+ </div>
1712
+ <div id="gf-query-result" style="font-size:10px;color:var(--dim);line-height:1.7;white-space:pre-wrap;word-break:break-word;"></div>
1713
+ </div>
1714
+
1715
+ <!-- EXPLAIN sub-view -->
1716
+ <div id="gf-view-explain" class="gf-view" style="flex:1;overflow-y:auto;padding:16px 20px;">
1717
+ <div style="display:flex;gap:8px;align-items:center;margin-bottom:12px;">
1718
+ <input id="gf-explain-input" type="text" placeholder="Node name (e.g. UserService, auth.ts)…" style="flex:1;background:rgba(255,255,255,0.04);border:1px solid var(--border);border-radius:3px;padding:7px 10px;color:var(--text);font-family:'Azeret Mono',monospace;font-size:11px;outline:none;" onkeydown="if(event.key==='Enter')runGraphifyExplain()">
1719
+ <button class="kg-action-btn" style="width:auto;padding:5px 14px;margin:0;" onclick="runGraphifyExplain()">EXPLAIN</button>
1720
+ </div>
1721
+ <div id="gf-explain-result" style="font-size:10px;color:var(--dim);line-height:1.7;white-space:pre-wrap;word-break:break-word;"></div>
1722
+ </div>
1723
+
1724
+ <!-- PATH sub-view -->
1725
+ <div id="gf-view-path" class="gf-view" style="flex:1;overflow-y:auto;padding:16px 20px;">
1726
+ <div style="display:flex;gap:8px;align-items:center;margin-bottom:12px;">
1727
+ <input id="gf-path-from" type="text" placeholder="From node…" style="flex:1;background:rgba(255,255,255,0.04);border:1px solid var(--border);border-radius:3px;padding:7px 10px;color:var(--text);font-family:'Azeret Mono',monospace;font-size:11px;outline:none;">
1728
+ <span style="color:var(--muted);font-size:10px;">→</span>
1729
+ <input id="gf-path-to" type="text" placeholder="To node…" style="flex:1;background:rgba(255,255,255,0.04);border:1px solid var(--border);border-radius:3px;padding:7px 10px;color:var(--text);font-family:'Azeret Mono',monospace;font-size:11px;outline:none;" onkeydown="if(event.key==='Enter')runGraphifyPath()">
1730
+ <button class="kg-action-btn" style="width:auto;padding:5px 14px;margin:0;" onclick="runGraphifyPath()">FIND PATH</button>
1731
+ </div>
1732
+ <div id="gf-path-result" style="font-size:10px;color:var(--dim);line-height:1.7;white-space:pre-wrap;word-break:break-word;"></div>
1733
+ </div>
1734
+
1735
+ <!-- BENCHMARK sub-view -->
1736
+ <div id="gf-view-benchmark" class="gf-view" style="flex:1;overflow-y:auto;padding:16px 20px;">
1737
+ <div style="display:flex;gap:8px;align-items:center;margin-bottom:12px;">
1738
+ <button class="kg-action-btn" style="width:auto;padding:5px 14px;margin:0;" onclick="runGraphifyBenchmark()">RUN BENCHMARK</button>
1739
+ <span id="gf-bench-status" style="font-size:9px;color:var(--muted);"></span>
1740
+ </div>
1741
+ <div id="gf-benchmark-result" style="font-size:10px;color:var(--dim);line-height:1.7;white-space:pre-wrap;word-break:break-word;"></div>
1742
+ </div>
1743
+
1744
+ <!-- REPORT sub-view -->
1745
+ <div id="gf-view-report" class="gf-view" style="flex:1;overflow-y:auto;padding:16px 20px;">
1746
+ <div id="po-knowledge-body"><div style="color:var(--muted);font-size:10px;padding:16px;">Loading…</div></div>
1693
1747
  </div>
1694
- <canvas id="po-kg2-canvas"></canvas>
1695
- </div>
1696
- <div id="po-knowledge-main">
1697
- <div id="po-knowledge-body"><div style="color:var(--muted);font-size:10px;padding:16px;">Loading…</div></div>
1698
1748
  </div>
1699
1749
  </div>
1700
1750
  </div>
@@ -3530,7 +3580,7 @@ window.switchPalaceTab = function(tab) {
3530
3580
  const paneIds = { drawers: 'po-drawers-tab', sessions: 'po-sessions-tab', chunks: 'po-chunks-tab', routing: 'po-routing-tab', swarm: 'po-swarm-tab', graph: 'po-graph-tab', knowledge: 'po-knowledge-tab' };
3531
3581
  const pane = document.getElementById(paneIds[tab]);
3532
3582
  if (pane) pane.classList.add('active');
3533
- if (tab !== 'knowledge') kgCodeGraph.stop();
3583
+ if (tab !== 'knowledge' && typeof kgCodeGraph !== 'undefined' && kgCodeGraph.stop) kgCodeGraph.stop();
3534
3584
  if (palaceData) renderPalaceTab(tab);
3535
3585
  };
3536
3586
 
@@ -3628,7 +3678,7 @@ function renderPalaceTab(tab) {
3628
3678
  else if (tab === 'routing') renderHooks(appData || {});
3629
3679
  else if (tab === 'swarm') renderPalaceSwarm();
3630
3680
  else if (tab === 'graph') { renderAgentGraph(palaceData.graph || { nodes: [], edges: [] }); }
3631
- else if (tab === 'knowledge') { renderPalaceKnowledge(); renderPalaceCodeGraph(); }
3681
+ else if (tab === 'knowledge') { renderPalaceKnowledge(); loadGraphifyIframe(); refreshWatchStatus(); }
3632
3682
  }
3633
3683
 
3634
3684
  let _allChunks = [];
@@ -4312,28 +4362,15 @@ async function renderPalaceKnowledge() {
4312
4362
  }
4313
4363
  }
4314
4364
 
4315
- let _codeGraphPending = false;
4316
- let _codeGraphLoaded = false;
4317
- async function renderPalaceCodeGraph(force = false) {
4318
- if (_codeGraphPending) return;
4319
- if (_codeGraphLoaded && !force) return;
4320
- _codeGraphPending = true;
4365
+ let _graphifyIframeLoaded = false;
4366
+ function loadGraphifyIframe(force) {
4367
+ const iframe = document.getElementById('gf-graph-iframe');
4368
+ if (!iframe) return;
4321
4369
  const dir = selectedProjectDir || '';
4322
- let gd;
4323
- try {
4324
- const r = await fetch(`/api/graphify-graph${dir ? '?dir=' + encodeURIComponent(dir) : ''}`);
4325
- gd = r.ok ? await r.json() : null;
4326
- } catch { gd = null; }
4327
- _codeGraphPending = false;
4328
- if ((selectedProjectDir || '') !== dir) return;
4329
- if (palaceCurrentTab !== 'knowledge') return;
4330
- const info = document.getElementById('po-kg2-info');
4331
- if (!gd || !gd.nodes || !gd.nodes.length) {
4332
- if (info) info.textContent = gd && gd.tooLarge ? 'Graph too large to display' : 'No graph data';
4333
- return;
4334
- }
4335
- kgCodeGraph.init();
4336
- kgCodeGraph.render(gd, () => { _codeGraphLoaded = true; });
4370
+ const src = `/api/graphify-html${dir ? '?dir=' + encodeURIComponent(dir) : ''}`;
4371
+ if (!force && _graphifyIframeLoaded && iframe.src && iframe.src.includes('/api/graphify-html')) return;
4372
+ iframe.src = src;
4373
+ _graphifyIframeLoaded = true;
4337
4374
  }
4338
4375
 
4339
4376
  window.triggerPalaceGraphBuild = async function() {
@@ -4350,7 +4387,7 @@ window.triggerPalaceGraphBuild = async function() {
4350
4387
  try {
4351
4388
  const r = await fetch(`/api/graphify-report${dir ? '?dir=' + encodeURIComponent(dir) : ''}`);
4352
4389
  const d = await r.json();
4353
- if (d.stats || d.exists) { clearInterval(poll); renderPalaceKnowledge(); _codeGraphLoaded = false; kgCodeGraph.reset(); renderPalaceCodeGraph(); }
4390
+ if (d.stats || d.exists) { clearInterval(poll); renderPalaceKnowledge(); _graphifyIframeLoaded = false; loadGraphifyIframe(true); }
4354
4391
  else if (attempts >= 36) { clearInterval(poll); if (bodyEl) bodyEl.innerHTML = '<div style="color:var(--amber);font-size:10px;">Build timed out — check server logs.</div>'; }
4355
4392
  } catch {}
4356
4393
  }, 5000);
@@ -4360,6 +4397,143 @@ window.triggerPalaceGraphBuild = async function() {
4360
4397
  }
4361
4398
  };
4362
4399
 
4400
+ // ═══════════════════════════════════════════════════════════════════
4401
+ // GRAPHIFY INTERACTIVE VIEWS
4402
+ // ═══════════════════════════════════════════════════════════════════
4403
+ let _currentGraphifyView = 'graph';
4404
+
4405
+ window.switchGraphifyView = function(view) {
4406
+ _currentGraphifyView = view;
4407
+ document.querySelectorAll('.gf-tool-tab').forEach(b => {
4408
+ b.classList.toggle('active', b.getAttribute('data-gftab') === view);
4409
+ });
4410
+ document.querySelectorAll('.gf-view').forEach(v => v.classList.remove('active'));
4411
+ const pane = document.getElementById('gf-view-' + view);
4412
+ if (pane) pane.classList.add('active');
4413
+ if (view === 'graph') { loadGraphifyIframe(); }
4414
+ if (view === 'report') { renderPalaceKnowledge(); }
4415
+ };
4416
+
4417
+ window.runGraphifyQuery = async function() {
4418
+ const input = document.getElementById('gf-query-input');
4419
+ const mode = document.getElementById('gf-query-mode');
4420
+ const budget = document.getElementById('gf-query-budget');
4421
+ const result = document.getElementById('gf-query-result');
4422
+ const q = (input && input.value || '').trim();
4423
+ if (!q) { if (result) result.textContent = 'Type a question first.'; return; }
4424
+ if (result) result.innerHTML = '<span style="color:var(--teal);">Querying…</span>';
4425
+ const dir = selectedProjectDir || '';
4426
+ try {
4427
+ const params = new URLSearchParams({ q, mode: mode.value, budget: budget.value });
4428
+ if (dir) params.set('dir', dir);
4429
+ const r = await fetch('/api/graphify-query?' + params);
4430
+ const d = await r.json();
4431
+ if (d.error) { result.innerHTML = `<span style="color:var(--red);">${escHtml(d.error)}</span>`; return; }
4432
+ result.innerHTML = `<div class="kg-card kg-card-wide" style="margin-bottom:10px;">
4433
+ <div class="kg-card-title"><span class="kg-card-title-icon">🔍</span>QUERY RESULT</div>
4434
+ <div class="kg-card-body">${escHtml(d.output || d.result || JSON.stringify(d, null, 2))}</div>
4435
+ </div>`;
4436
+ } catch (e) { result.innerHTML = `<span style="color:var(--red);">${escHtml(e.message)}</span>`; }
4437
+ };
4438
+
4439
+ window.runGraphifyExplain = async function() {
4440
+ const input = document.getElementById('gf-explain-input');
4441
+ const result = document.getElementById('gf-explain-result');
4442
+ const node = (input && input.value || '').trim();
4443
+ if (!node) { if (result) result.textContent = 'Enter a node name first.'; return; }
4444
+ if (result) result.innerHTML = '<span style="color:var(--teal);">Explaining…</span>';
4445
+ const dir = selectedProjectDir || '';
4446
+ try {
4447
+ const params = new URLSearchParams({ node });
4448
+ if (dir) params.set('dir', dir);
4449
+ const r = await fetch('/api/graphify-explain?' + params);
4450
+ const d = await r.json();
4451
+ if (d.error) { result.innerHTML = `<span style="color:var(--red);">${escHtml(d.error)}</span>`; return; }
4452
+ result.innerHTML = `<div class="kg-card kg-card-wide" style="margin-bottom:10px;">
4453
+ <div class="kg-card-title"><span class="kg-card-title-icon">📖</span>EXPLAIN: ${escHtml(node.toUpperCase())}</div>
4454
+ <div class="kg-card-body">${escHtml(d.output || d.result || JSON.stringify(d, null, 2))}</div>
4455
+ </div>`;
4456
+ } catch (e) { result.innerHTML = `<span style="color:var(--red);">${escHtml(e.message)}</span>`; }
4457
+ };
4458
+
4459
+ window.runGraphifyPath = async function() {
4460
+ const fromEl = document.getElementById('gf-path-from');
4461
+ const toEl = document.getElementById('gf-path-to');
4462
+ const result = document.getElementById('gf-path-result');
4463
+ const from = (fromEl && fromEl.value || '').trim();
4464
+ const to = (toEl && toEl.value || '').trim();
4465
+ if (!from || !to) { if (result) result.textContent = 'Enter both source and target nodes.'; return; }
4466
+ if (result) result.innerHTML = '<span style="color:var(--teal);">Finding shortest path…</span>';
4467
+ const dir = selectedProjectDir || '';
4468
+ try {
4469
+ const params = new URLSearchParams({ from, to });
4470
+ if (dir) params.set('dir', dir);
4471
+ const r = await fetch('/api/graphify-path?' + params);
4472
+ const d = await r.json();
4473
+ if (d.error) { result.innerHTML = `<span style="color:var(--red);">${escHtml(d.error)}</span>`; return; }
4474
+ result.innerHTML = `<div class="kg-card kg-card-wide" style="margin-bottom:10px;">
4475
+ <div class="kg-card-title"><span class="kg-card-title-icon">🛤</span>PATH: ${escHtml(from)} → ${escHtml(to)}</div>
4476
+ <div class="kg-card-body">${escHtml(d.output || d.result || JSON.stringify(d, null, 2))}</div>
4477
+ </div>`;
4478
+ } catch (e) { result.innerHTML = `<span style="color:var(--red);">${escHtml(e.message)}</span>`; }
4479
+ };
4480
+
4481
+ window.runGraphifyBenchmark = async function() {
4482
+ const result = document.getElementById('gf-benchmark-result');
4483
+ const status = document.getElementById('gf-bench-status');
4484
+ if (result) result.innerHTML = '<span style="color:var(--teal);">Running benchmark…</span>';
4485
+ if (status) status.textContent = 'Running…';
4486
+ const dir = selectedProjectDir || '';
4487
+ try {
4488
+ const params = dir ? '?dir=' + encodeURIComponent(dir) : '';
4489
+ const r = await fetch('/api/graphify-benchmark' + params);
4490
+ const d = await r.json();
4491
+ if (status) status.textContent = '';
4492
+ if (d.error) { result.innerHTML = `<span style="color:var(--red);">${escHtml(d.error)}</span>`; return; }
4493
+ result.innerHTML = `<div class="kg-card kg-card-wide">
4494
+ <div class="kg-card-title"><span class="kg-card-title-icon">⚡</span>BENCHMARK RESULTS</div>
4495
+ <div class="kg-card-body">${escHtml(d.output || d.result || JSON.stringify(d, null, 2))}</div>
4496
+ </div>`;
4497
+ } catch (e) { if (status) status.textContent = ''; result.innerHTML = `<span style="color:var(--red);">${escHtml(e.message)}</span>`; }
4498
+ };
4499
+
4500
+ window.toggleGraphifyWatch = async function() {
4501
+ const btn = document.getElementById('gf-watch-btn');
4502
+ const indicator = document.getElementById('gf-watch-indicator');
4503
+ const dir = selectedProjectDir || '';
4504
+ const params = dir ? '?dir=' + encodeURIComponent(dir) : '';
4505
+ try {
4506
+ const r = await fetch('/api/graphify-watch-toggle' + params, { method: 'POST' });
4507
+ const d = await r.json();
4508
+ updateWatchIndicator(d);
4509
+ } catch (e) {
4510
+ if (indicator) indicator.innerHTML = `<span style="color:var(--red);">Error</span>`;
4511
+ }
4512
+ };
4513
+
4514
+ async function refreshWatchStatus() {
4515
+ const dir = selectedProjectDir || '';
4516
+ const params = dir ? '?dir=' + encodeURIComponent(dir) : '';
4517
+ try {
4518
+ const r = await fetch('/api/graphify-watch-status' + params);
4519
+ const d = await r.json();
4520
+ updateWatchIndicator(d);
4521
+ } catch {}
4522
+ }
4523
+
4524
+ function updateWatchIndicator(d) {
4525
+ const indicator = document.getElementById('gf-watch-indicator');
4526
+ const btn = document.getElementById('gf-watch-btn');
4527
+ if (!indicator) return;
4528
+ if (d.running) {
4529
+ indicator.innerHTML = '<span style="color:var(--green);">● WATCHING</span>';
4530
+ if (btn) btn.textContent = 'STOP';
4531
+ } else {
4532
+ indicator.innerHTML = '<span style="color:var(--muted);">○ IDLE</span>';
4533
+ if (btn) btn.textContent = 'WATCH';
4534
+ }
4535
+ }
4536
+
4363
4537
  let _palaceSelectedSession = null;
4364
4538
 
4365
4539
  function renderPalaceSessions() {
@@ -594,6 +594,23 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
594
594
  return;
595
595
  }
596
596
 
597
+ // ------------------------------------------------------- GET /api/graphify-html
598
+ if (req.method === 'GET' && url === '/api/graphify-html') {
599
+ try {
600
+ const qs = new URL(req.url, 'http://localhost').searchParams;
601
+ const dir = qs.get('dir') || projectDir;
602
+ const d = path.resolve(dir || process.cwd());
603
+ const htmlPath = path.join(d, '.monomind', 'graph', 'graph.html');
604
+ const html = fs.readFileSync(htmlPath, 'utf-8');
605
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
606
+ res.end(html);
607
+ } catch (err) {
608
+ res.writeHead(404, { 'Content-Type': 'text/html' });
609
+ res.end('<html><body style="background:#0f0f1a;color:#888;font-family:monospace;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;"><div style="text-align:center;"><h3 style="color:#4E79A7;">No Graph Built Yet</h3><p>Run <code style="color:#00E5C8;">graphify update .</code> or click BUILD in the sidebar.</p></div></body></html>');
610
+ }
611
+ return;
612
+ }
613
+
597
614
  // ------------------------------------------------------- GET /api/graphify-report
598
615
  if (req.method === 'GET' && url === '/api/graphify-report') {
599
616
  try {
@@ -693,25 +710,193 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
693
710
  const qs = new URL(req.url, 'http://localhost').searchParams;
694
711
  const dir = qs.get('dir') || projectDir;
695
712
  const d = path.resolve(dir || process.cwd());
696
- const outputDir = path.join(d, '.monomind', 'graph');
697
713
 
698
- // Start build in background, respond immediately so UI can show progress state
699
714
  res.writeHead(202, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
700
- res.end(JSON.stringify({ status: 'building', dir: d, outputDir }));
715
+ res.end(JSON.stringify({ status: 'building', dir: d }));
701
716
 
702
- (async () => {
703
- try {
704
- const { buildGraph } = await import('@monoes/graph');
705
- const r = await buildGraph(d, { codeOnly: true, outputDir });
706
- console.log(`[graph] built for ${d}: ${r.filesProcessed} files`);
707
- // Run enrichment after build
708
- try {
709
- const { enrichGraph } = await import('file://' + new URL('../graph/enrich.mjs', import.meta.url).pathname);
710
- const er = await enrichGraph(d, { graphDir: outputDir });
711
- console.log(`[graph] enriched: ${er.metrics.enrichedNodes}/${er.metrics.totalNodes} nodes, PageRank: ${er.metrics.pageRankComputed}`);
712
- } catch (ee) { console.error('[graph] enrichment failed:', ee.message); }
713
- } catch (e) { console.error(`[graph] build failed for ${d}:`, e.message); }
714
- })();
717
+ // Build via graphify CLI in background
718
+ const { spawn: sp } = await import('child_process');
719
+ const child = sp('graphify', ['update', d], { stdio: 'ignore', detached: true, cwd: d });
720
+ child.unref();
721
+ console.log(`[graph] build started for ${d} via graphify update`);
722
+ } catch (err) {
723
+ res.writeHead(500, { 'Content-Type': 'application/json' });
724
+ res.end(JSON.stringify({ error: err.message }));
725
+ }
726
+ return;
727
+ }
728
+
729
+ // -------------------------------------------------- GET /api/graphify-query
730
+ if (req.method === 'GET' && url === '/api/graphify-query') {
731
+ try {
732
+ const qs = new URL(req.url, 'http://localhost').searchParams;
733
+ const dir = qs.get('dir') || projectDir;
734
+ const q = qs.get('q') || '';
735
+ const mode = qs.get('mode') === 'dfs' ? '--dfs' : '';
736
+ const budget = qs.get('budget') || '2000';
737
+ const d = path.resolve(dir || process.cwd());
738
+ const graphPath = path.join(d, '.monomind', 'graph', 'graph.json');
739
+ const legacyPath = path.join(d, 'graphify-out', 'graph.json');
740
+ const gp = fs.existsSync(graphPath) ? graphPath : (fs.existsSync(legacyPath) ? legacyPath : null);
741
+
742
+ if (!q) {
743
+ res.writeHead(400, { 'Content-Type': 'application/json' });
744
+ res.end(JSON.stringify({ error: 'Missing ?q= parameter' }));
745
+ return;
746
+ }
747
+
748
+ const { execSync: ex } = await import('child_process');
749
+ const args = ['query', JSON.stringify(q), '--budget', budget];
750
+ if (mode) args.push(mode);
751
+ if (gp) args.push('--graph', gp);
752
+ const out = ex(`graphify ${args.join(' ')}`, { encoding: 'utf8', cwd: d, timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'] });
753
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
754
+ res.end(JSON.stringify({ success: true, query: q, result: out }));
755
+ } catch (err) {
756
+ res.writeHead(500, { 'Content-Type': 'application/json' });
757
+ res.end(JSON.stringify({ error: err.message }));
758
+ }
759
+ return;
760
+ }
761
+
762
+ // -------------------------------------------------- GET /api/graphify-explain
763
+ if (req.method === 'GET' && url === '/api/graphify-explain') {
764
+ try {
765
+ const qs = new URL(req.url, 'http://localhost').searchParams;
766
+ const dir = qs.get('dir') || projectDir;
767
+ const node = qs.get('node') || '';
768
+ const d = path.resolve(dir || process.cwd());
769
+ const graphPath = path.join(d, '.monomind', 'graph', 'graph.json');
770
+ const legacyPath = path.join(d, 'graphify-out', 'graph.json');
771
+ const gp = fs.existsSync(graphPath) ? graphPath : (fs.existsSync(legacyPath) ? legacyPath : null);
772
+
773
+ if (!node) {
774
+ res.writeHead(400, { 'Content-Type': 'application/json' });
775
+ res.end(JSON.stringify({ error: 'Missing ?node= parameter' }));
776
+ return;
777
+ }
778
+
779
+ const { execSync: ex } = await import('child_process');
780
+ const args = ['explain', JSON.stringify(node)];
781
+ if (gp) args.push('--graph', gp);
782
+ const out = ex(`graphify ${args.join(' ')}`, { encoding: 'utf8', cwd: d, timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'] });
783
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
784
+ res.end(JSON.stringify({ success: true, node, explanation: out }));
785
+ } catch (err) {
786
+ res.writeHead(500, { 'Content-Type': 'application/json' });
787
+ res.end(JSON.stringify({ error: err.message }));
788
+ }
789
+ return;
790
+ }
791
+
792
+ // -------------------------------------------------- GET /api/graphify-path
793
+ if (req.method === 'GET' && url === '/api/graphify-path') {
794
+ try {
795
+ const qs = new URL(req.url, 'http://localhost').searchParams;
796
+ const dir = qs.get('dir') || projectDir;
797
+ const from = qs.get('from') || '';
798
+ const to = qs.get('to') || '';
799
+ const d = path.resolve(dir || process.cwd());
800
+ const graphPath = path.join(d, '.monomind', 'graph', 'graph.json');
801
+ const legacyPath = path.join(d, 'graphify-out', 'graph.json');
802
+ const gp = fs.existsSync(graphPath) ? graphPath : (fs.existsSync(legacyPath) ? legacyPath : null);
803
+
804
+ if (!from || !to) {
805
+ res.writeHead(400, { 'Content-Type': 'application/json' });
806
+ res.end(JSON.stringify({ error: 'Missing ?from= and ?to= parameters' }));
807
+ return;
808
+ }
809
+
810
+ const { execSync: ex } = await import('child_process');
811
+ const args = ['path', JSON.stringify(from), JSON.stringify(to)];
812
+ if (gp) args.push('--graph', gp);
813
+ const out = ex(`graphify ${args.join(' ')}`, { encoding: 'utf8', cwd: d, timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'] });
814
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
815
+ res.end(JSON.stringify({ success: true, from, to, path: out }));
816
+ } catch (err) {
817
+ res.writeHead(500, { 'Content-Type': 'application/json' });
818
+ res.end(JSON.stringify({ error: err.message }));
819
+ }
820
+ return;
821
+ }
822
+
823
+ // -------------------------------------------------- GET /api/graphify-watch-status
824
+ if (req.method === 'GET' && url === '/api/graphify-watch-status') {
825
+ try {
826
+ const qs = new URL(req.url, 'http://localhost').searchParams;
827
+ const dir = qs.get('dir') || projectDir;
828
+ const d = path.resolve(dir || process.cwd());
829
+ const pidPath = path.join(d, '.monomind', 'graph', 'watch.pid');
830
+ let running = false, pid = null;
831
+ try {
832
+ pid = parseInt(fs.readFileSync(pidPath, 'utf-8').trim(), 10);
833
+ process.kill(pid, 0);
834
+ running = true;
835
+ } catch {}
836
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
837
+ res.end(JSON.stringify({ running, pid }));
838
+ } catch (err) {
839
+ res.writeHead(500, { 'Content-Type': 'application/json' });
840
+ res.end(JSON.stringify({ error: err.message }));
841
+ }
842
+ return;
843
+ }
844
+
845
+ // -------------------------------------------------- POST /api/graphify-watch-toggle
846
+ if (req.method === 'POST' && url === '/api/graphify-watch-toggle') {
847
+ try {
848
+ const qs = new URL(req.url, 'http://localhost').searchParams;
849
+ const dir = qs.get('dir') || projectDir;
850
+ const d = path.resolve(dir || process.cwd());
851
+ const pidPath = path.join(d, '.monomind', 'graph', 'watch.pid');
852
+ let wasRunning = false;
853
+ try {
854
+ const pid = parseInt(fs.readFileSync(pidPath, 'utf-8').trim(), 10);
855
+ process.kill(pid, 0);
856
+ wasRunning = true;
857
+ process.kill(pid, 'SIGTERM');
858
+ try { fs.unlinkSync(pidPath); } catch {}
859
+ } catch {}
860
+
861
+ if (wasRunning) {
862
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
863
+ res.end(JSON.stringify({ running: false, action: 'stopped' }));
864
+ } else {
865
+ const { spawn: sp } = await import('child_process');
866
+ const child = sp('graphify', ['watch', d], { stdio: 'ignore', detached: true, cwd: d });
867
+ child.unref();
868
+ try { fs.mkdirSync(path.join(d, '.monomind', 'graph'), { recursive: true }); } catch {}
869
+ try { fs.writeFileSync(pidPath, String(child.pid)); } catch {}
870
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
871
+ res.end(JSON.stringify({ running: true, pid: child.pid, action: 'started' }));
872
+ }
873
+ } catch (err) {
874
+ res.writeHead(500, { 'Content-Type': 'application/json' });
875
+ res.end(JSON.stringify({ error: err.message }));
876
+ }
877
+ return;
878
+ }
879
+
880
+ // -------------------------------------------------- GET /api/graphify-benchmark
881
+ if (req.method === 'GET' && url === '/api/graphify-benchmark') {
882
+ try {
883
+ const qs = new URL(req.url, 'http://localhost').searchParams;
884
+ const dir = qs.get('dir') || projectDir;
885
+ const d = path.resolve(dir || process.cwd());
886
+ const graphPath = path.join(d, '.monomind', 'graph', 'graph.json');
887
+ const legacyPath = path.join(d, 'graphify-out', 'graph.json');
888
+ const gp = fs.existsSync(graphPath) ? graphPath : (fs.existsSync(legacyPath) ? legacyPath : null);
889
+
890
+ if (!gp) {
891
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
892
+ res.end(JSON.stringify({ available: false }));
893
+ return;
894
+ }
895
+
896
+ const { execSync: ex } = await import('child_process');
897
+ const out = ex(`graphify benchmark ${gp}`, { encoding: 'utf8', cwd: d, timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'] });
898
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
899
+ res.end(JSON.stringify({ available: true, result: out }));
715
900
  } catch (err) {
716
901
  res.writeHead(500, { 'Content-Type': 'application/json' });
717
902
  res.end(JSON.stringify({ error: err.message }));