lemma-mcp 0.11.0 → 0.11.2

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.
@@ -195,6 +195,22 @@ select:focus{outline:none;border-color:var(--accent)}
195
195
  #tooltip .tt-title{font-weight:600;color:var(--text);margin-bottom:2px}
196
196
  #tooltip .tt-meta{color:var(--text2)}
197
197
 
198
+ #ctx-menu{
199
+ position:fixed;z-index:50;min-width:180px;
200
+ background:var(--bg2);border:1px solid var(--border);border-radius:10px;
201
+ box-shadow:0 8px 24px rgba(0,0,0,.12);padding:4px;
202
+ display:none;
203
+ }
204
+ #ctx-menu.open{display:block}
205
+ .ctx-item{
206
+ display:flex;align-items:center;gap:8px;padding:7px 12px;border-radius:6px;
207
+ font-size:13px;color:var(--text);cursor:pointer;border:none;background:none;width:100%;text-align:left;
208
+ }
209
+ .ctx-item:hover{background:var(--bg3)}
210
+ .ctx-item.danger{color:var(--danger)}
211
+ .ctx-sep{height:1px;background:var(--border);margin:3px 8px}
212
+ .ctx-icon{width:16px;text-align:center;font-size:13px;opacity:.7}
213
+
198
214
  ::-webkit-scrollbar{width:6px}
199
215
  ::-webkit-scrollbar-track{background:var(--bg)}
200
216
  ::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
@@ -434,6 +450,17 @@ select:focus{outline:none;border-color:var(--accent)}
434
450
  </div>
435
451
  </div>
436
452
 
453
+ <div id="ctx-menu">
454
+ <button class="ctx-item" data-action="detail"><span class="ctx-icon">&#x1F4CB;</span>View Detail</button>
455
+ <button class="ctx-item" data-action="edit"><span class="ctx-icon">&#x270F;</span>Edit</button>
456
+ <div class="ctx-sep"></div>
457
+ <button class="ctx-item" data-action="link"><span class="ctx-icon">&#x1F517;</span>Link from here...</button>
458
+ <button class="ctx-item" data-action="unlink"><span class="ctx-icon">&#x2716;</span>Remove all links</button>
459
+ <div class="ctx-sep"></div>
460
+ <button class="ctx-item" data-action="select"><span class="ctx-icon">&#x2611;</span>Select</button>
461
+ <button class="ctx-item danger" data-action="delete"><span class="ctx-icon">&#x1F5D1;</span>Delete</button>
462
+ </div>
463
+
437
464
  <script>
438
465
  const TYPE_COLORS = {fact:'#6ba3d6',pattern:'#a47dc4',lesson:'#5cb88a',warning:'#d97c6b',context:'#d4a94e'};
439
466
  const LINK_COLORS = {associated:'#c4bfb6',related:'#b8b0c4',related_to:'#b8b0c4',contradicts:'#d97c6b',supersedes:'#d4a94e',supports:'#5cb88a'};
@@ -676,6 +703,16 @@ function renderGraph() {
676
703
  .attr('dy', d => nodeRadius(d.confidence, d.access_count) + 13)
677
704
  .text(d => { const t = d.title||''; return t.length > 18 ? t.slice(0,16)+'..' : t; });
678
705
 
706
+ nodeEls.on('contextmenu', (e, d) => {
707
+ e.preventDefault();
708
+ e.stopPropagation();
709
+ const menu = document.getElementById('ctx-menu');
710
+ menu._targetNode = d;
711
+ menu.style.left = e.clientX + 'px';
712
+ menu.style.top = e.clientY + 'px';
713
+ menu.classList.add('open');
714
+ });
715
+
679
716
  nodeEls.on('click', (e, d) => {
680
717
  e.stopPropagation();
681
718
  if (linkMode) {
@@ -708,6 +745,7 @@ function renderGraph() {
708
745
  nodeEls.on('pointerleave', () => tooltip.classList.remove('visible'));
709
746
 
710
747
  svg.on('click', () => {
748
+ document.getElementById('ctx-menu').classList.remove('open');
711
749
  if (!linkMode) hideDetail();
712
750
  selectedLinkData = null;
713
751
  document.getElementById('unlink-bar').style.display = 'none';
@@ -985,6 +1023,48 @@ if (SERVER_MODE) {
985
1023
  }
986
1024
  })();
987
1025
  }
1026
+
1027
+ document.getElementById('ctx-menu').addEventListener('click', async (e) => {
1028
+ const btn = e.target.closest('.ctx-item');
1029
+ if (!btn) return;
1030
+ const menu = document.getElementById('ctx-menu');
1031
+ const d = menu._targetNode;
1032
+ menu.classList.remove('open');
1033
+ if (!d) return;
1034
+
1035
+ const action = btn.dataset.action;
1036
+ if (action === 'detail') showDetail(d);
1037
+ else if (action === 'edit') openEditModal(d);
1038
+ else if (action === 'delete') { if (confirm('Delete "' + d.title + '"?')) await deleteFragment(d.id); }
1039
+ else if (action === 'select') {
1040
+ if (!selectedNodes.has(d.id)) selectedNodes.add(d.id);
1041
+ updateStats(); updateBulkBar(); renderGraph();
1042
+ }
1043
+ else if (action === 'link') {
1044
+ linkSource = d.id;
1045
+ linkMode = true;
1046
+ document.getElementById('graph-container').classList.add('link-mode');
1047
+ document.getElementById('link-bar').style.display = 'flex';
1048
+ document.getElementById('link-step-text').textContent = `Source: "${d.title.slice(0,25)}" — click target node`;
1049
+ document.getElementById('btn-link').classList.add('active');
1050
+ nodeGroup.selectAll('.node-group').classed('link-source', n => n.id === d.id);
1051
+ }
1052
+ else if (action === 'unlink') {
1053
+ const count = relations.filter(r => r.source === d.id || r.target === d.id).length;
1054
+ if (count === 0) return;
1055
+ if (!confirm(`Remove all ${count} link(s) from "${d.title}"?`)) return;
1056
+ const toRemove = relations.filter(r => r.source === d.id || r.target === d.id);
1057
+ for (const r of toRemove) {
1058
+ if (SERVER_MODE) await api('DELETE', 'relations', { source: r.source, target: r.target, type: r.type });
1059
+ }
1060
+ relations = relations.filter(r => r.source !== d.id && r.target !== d.id);
1061
+ updateUI();
1062
+ }
1063
+ });
1064
+
1065
+ document.addEventListener('click', (e) => {
1066
+ if (!e.target.closest('#ctx-menu')) document.getElementById('ctx-menu').classList.remove('open');
1067
+ });
988
1068
  </script>
989
1069
  </body>
990
1070
  </html>
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { spawn } from "child_process";
2
3
  import { startServer, runLibMode } from "./server/index.js";
3
4
  import { startVisualizeServer } from "./server/visualize.js";
4
5
  const args = process.argv.slice(2);
@@ -11,10 +12,30 @@ if (args.includes("-lib")) {
11
12
  else if (args.includes("-vis")) {
12
13
  const portIdx = args.indexOf("-p");
13
14
  const port = portIdx !== -1 && args[portIdx + 1] ? parseInt(args[portIdx + 1], 10) : undefined;
14
- startVisualizeServer(port).catch((error) => {
15
- console.error("Fatal error:", error);
16
- process.exit(1);
17
- });
15
+ if (!args.includes("--fg")) {
16
+ const serverArgs = [process.argv[1], "-vis", "--fg"];
17
+ if (port) {
18
+ serverArgs.push("-p", String(port));
19
+ }
20
+ const child = spawn(process.execPath, serverArgs, {
21
+ detached: true,
22
+ stdio: "ignore",
23
+ windowsHide: true,
24
+ });
25
+ child.unref();
26
+ console.error(``);
27
+ console.error(` Lemma Visualizer started in background (PID ${child.pid})`);
28
+ console.error(` http://localhost:${port || 3456}`);
29
+ console.error(` Stop: kill ${child.pid}`);
30
+ console.error(``);
31
+ process.exit(0);
32
+ }
33
+ else {
34
+ startVisualizeServer(port).catch((error) => {
35
+ console.error("Fatal error:", error);
36
+ process.exit(1);
37
+ });
38
+ }
18
39
  }
19
40
  else {
20
41
  startServer().catch((error) => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;IAC1B,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,KAAY,EAAE,EAAE;QAClC,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;KAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/F,oBAAoB,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAY,EAAE,EAAE;QAChD,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;KAAM,CAAC;IACN,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,KAAY,EAAE,EAAE;QACnC,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;IAC1B,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,KAAY,EAAE,EAAE;QAClC,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;KAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE/F,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QACrD,IAAI,IAAI,EAAE,CAAC;YAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAAC,CAAC;QAElD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,EAAE;YAChD,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,QAAQ;YACf,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,EAAE,CAAC;QAEd,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,iDAAiD,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;QAC7E,OAAO,CAAC,KAAK,CAAC,sBAAsB,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;QACpD,OAAO,CAAC,KAAK,CAAC,gBAAgB,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;SAAM,CAAC;QACN,oBAAoB,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAY,EAAE,EAAE;YAChD,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;YACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;KAAM,CAAC;IACN,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,KAAY,EAAE,EAAE;QACnC,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -123,7 +123,7 @@ Relations are bidirectional — the reverse relation is created automatically.
123
123
  When the user sends one of these shorthand commands, execute the corresponding action immediately:
124
124
 
125
125
  - **-lib** → Call memory_library. This gives a full snapshot of your knowledge base with analysis signals, stale fragments, distill candidates, and suggested actions. After reviewing the snapshot, take maintenance actions as needed (merge, forget, distill, relate).
126
- - **-vis** → Run npx lemma-mcp -vis via bash to start the memory visualizer server. Opens an interactive graph in the browser on localhost:3456. All changes write directly to the database.
126
+ - **-vis** → Run npx lemma-mcp -vis via bash. It auto-daemonizes and stays alive in background. Then sleep 2 and curl http://localhost:3456/api/health to confirm.
127
127
  </user_commands>`;
128
128
  export function injectAgentsMd(projectDir) {
129
129
  const agentsPath = path.join(projectDir, "AGENTS.md");
@@ -195,6 +195,22 @@ select:focus{outline:none;border-color:var(--accent)}
195
195
  #tooltip .tt-title{font-weight:600;color:var(--text);margin-bottom:2px}
196
196
  #tooltip .tt-meta{color:var(--text2)}
197
197
 
198
+ #ctx-menu{
199
+ position:fixed;z-index:50;min-width:180px;
200
+ background:var(--bg2);border:1px solid var(--border);border-radius:10px;
201
+ box-shadow:0 8px 24px rgba(0,0,0,.12);padding:4px;
202
+ display:none;
203
+ }
204
+ #ctx-menu.open{display:block}
205
+ .ctx-item{
206
+ display:flex;align-items:center;gap:8px;padding:7px 12px;border-radius:6px;
207
+ font-size:13px;color:var(--text);cursor:pointer;border:none;background:none;width:100%;text-align:left;
208
+ }
209
+ .ctx-item:hover{background:var(--bg3)}
210
+ .ctx-item.danger{color:var(--danger)}
211
+ .ctx-sep{height:1px;background:var(--border);margin:3px 8px}
212
+ .ctx-icon{width:16px;text-align:center;font-size:13px;opacity:.7}
213
+
198
214
  ::-webkit-scrollbar{width:6px}
199
215
  ::-webkit-scrollbar-track{background:var(--bg)}
200
216
  ::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
@@ -434,6 +450,17 @@ select:focus{outline:none;border-color:var(--accent)}
434
450
  </div>
435
451
  </div>
436
452
 
453
+ <div id="ctx-menu">
454
+ <button class="ctx-item" data-action="detail"><span class="ctx-icon">&#x1F4CB;</span>View Detail</button>
455
+ <button class="ctx-item" data-action="edit"><span class="ctx-icon">&#x270F;</span>Edit</button>
456
+ <div class="ctx-sep"></div>
457
+ <button class="ctx-item" data-action="link"><span class="ctx-icon">&#x1F517;</span>Link from here...</button>
458
+ <button class="ctx-item" data-action="unlink"><span class="ctx-icon">&#x2716;</span>Remove all links</button>
459
+ <div class="ctx-sep"></div>
460
+ <button class="ctx-item" data-action="select"><span class="ctx-icon">&#x2611;</span>Select</button>
461
+ <button class="ctx-item danger" data-action="delete"><span class="ctx-icon">&#x1F5D1;</span>Delete</button>
462
+ </div>
463
+
437
464
  <script>
438
465
  const TYPE_COLORS = {fact:'#6ba3d6',pattern:'#a47dc4',lesson:'#5cb88a',warning:'#d97c6b',context:'#d4a94e'};
439
466
  const LINK_COLORS = {associated:'#c4bfb6',related:'#b8b0c4',related_to:'#b8b0c4',contradicts:'#d97c6b',supersedes:'#d4a94e',supports:'#5cb88a'};
@@ -676,6 +703,16 @@ function renderGraph() {
676
703
  .attr('dy', d => nodeRadius(d.confidence, d.access_count) + 13)
677
704
  .text(d => { const t = d.title||''; return t.length > 18 ? t.slice(0,16)+'..' : t; });
678
705
 
706
+ nodeEls.on('contextmenu', (e, d) => {
707
+ e.preventDefault();
708
+ e.stopPropagation();
709
+ const menu = document.getElementById('ctx-menu');
710
+ menu._targetNode = d;
711
+ menu.style.left = e.clientX + 'px';
712
+ menu.style.top = e.clientY + 'px';
713
+ menu.classList.add('open');
714
+ });
715
+
679
716
  nodeEls.on('click', (e, d) => {
680
717
  e.stopPropagation();
681
718
  if (linkMode) {
@@ -708,6 +745,7 @@ function renderGraph() {
708
745
  nodeEls.on('pointerleave', () => tooltip.classList.remove('visible'));
709
746
 
710
747
  svg.on('click', () => {
748
+ document.getElementById('ctx-menu').classList.remove('open');
711
749
  if (!linkMode) hideDetail();
712
750
  selectedLinkData = null;
713
751
  document.getElementById('unlink-bar').style.display = 'none';
@@ -985,6 +1023,48 @@ if (SERVER_MODE) {
985
1023
  }
986
1024
  })();
987
1025
  }
1026
+
1027
+ document.getElementById('ctx-menu').addEventListener('click', async (e) => {
1028
+ const btn = e.target.closest('.ctx-item');
1029
+ if (!btn) return;
1030
+ const menu = document.getElementById('ctx-menu');
1031
+ const d = menu._targetNode;
1032
+ menu.classList.remove('open');
1033
+ if (!d) return;
1034
+
1035
+ const action = btn.dataset.action;
1036
+ if (action === 'detail') showDetail(d);
1037
+ else if (action === 'edit') openEditModal(d);
1038
+ else if (action === 'delete') { if (confirm('Delete "' + d.title + '"?')) await deleteFragment(d.id); }
1039
+ else if (action === 'select') {
1040
+ if (!selectedNodes.has(d.id)) selectedNodes.add(d.id);
1041
+ updateStats(); updateBulkBar(); renderGraph();
1042
+ }
1043
+ else if (action === 'link') {
1044
+ linkSource = d.id;
1045
+ linkMode = true;
1046
+ document.getElementById('graph-container').classList.add('link-mode');
1047
+ document.getElementById('link-bar').style.display = 'flex';
1048
+ document.getElementById('link-step-text').textContent = `Source: "${d.title.slice(0,25)}" — click target node`;
1049
+ document.getElementById('btn-link').classList.add('active');
1050
+ nodeGroup.selectAll('.node-group').classed('link-source', n => n.id === d.id);
1051
+ }
1052
+ else if (action === 'unlink') {
1053
+ const count = relations.filter(r => r.source === d.id || r.target === d.id).length;
1054
+ if (count === 0) return;
1055
+ if (!confirm(`Remove all ${count} link(s) from "${d.title}"?`)) return;
1056
+ const toRemove = relations.filter(r => r.source === d.id || r.target === d.id);
1057
+ for (const r of toRemove) {
1058
+ if (SERVER_MODE) await api('DELETE', 'relations', { source: r.source, target: r.target, type: r.type });
1059
+ }
1060
+ relations = relations.filter(r => r.source !== d.id && r.target !== d.id);
1061
+ updateUI();
1062
+ }
1063
+ });
1064
+
1065
+ document.addEventListener('click', (e) => {
1066
+ if (!e.target.closest('#ctx-menu')) document.getElementById('ctx-menu').classList.remove('open');
1067
+ });
988
1068
  </script>
989
1069
  </body>
990
1070
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lemma-mcp",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Persistent memory layer for LLMs via MCP",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",