nlm-memory 0.4.2 → 0.5.1
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/README.md +72 -34
- package/dist/cli/nlm.js +223 -33
- package/dist/cli/nlm.js.map +1 -1
- package/dist/core/adapters/cursor.d.ts +45 -0
- package/dist/core/adapters/cursor.js +397 -0
- package/dist/core/adapters/cursor.js.map +1 -0
- package/dist/core/adapters/from-source.js +10 -0
- package/dist/core/adapters/from-source.js.map +1 -1
- package/dist/core/adapters/windsurf.d.ts +44 -0
- package/dist/core/adapters/windsurf.js +299 -0
- package/dist/core/adapters/windsurf.js.map +1 -0
- package/dist/core/hook/claude-settings.d.ts +12 -5
- package/dist/core/hook/claude-settings.js +21 -6
- package/dist/core/hook/claude-settings.js.map +1 -1
- package/dist/core/sources/source-registry.d.ts +1 -1
- package/dist/core/sources/source-registry.js +18 -0
- package/dist/core/sources/source-registry.js.map +1 -1
- package/dist/core/storage/sqlite-session-store.d.ts +2 -0
- package/dist/core/storage/sqlite-session-store.js +38 -2
- package/dist/core/storage/sqlite-session-store.js.map +1 -1
- package/dist/hook/hook-auth.d.ts +13 -0
- package/dist/hook/hook-auth.js +19 -0
- package/dist/hook/hook-auth.js.map +1 -0
- package/dist/hook/prompt-recall-hook.js +7 -1
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/session-start-hook.js +4 -1
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +4 -1
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/http/app.d.ts +2 -0
- package/dist/http/app.js +76 -1
- package/dist/http/app.js.map +1 -1
- package/dist/install/claude-code.js +1 -1
- package/dist/install/claude-code.js.map +1 -1
- package/dist/install/cursor.d.ts +25 -0
- package/dist/install/cursor.js +43 -0
- package/dist/install/cursor.js.map +1 -0
- package/dist/install/nlm-dir-perms.d.ts +19 -0
- package/dist/install/nlm-dir-perms.js +43 -0
- package/dist/install/nlm-dir-perms.js.map +1 -0
- package/dist/install/ollama.d.ts +18 -1
- package/dist/install/ollama.js +62 -7
- package/dist/install/ollama.js.map +1 -1
- package/dist/install/setup.d.ts +4 -0
- package/dist/install/setup.js +141 -18
- package/dist/install/setup.js.map +1 -1
- package/dist/install/windsurf.d.ts +25 -0
- package/dist/install/windsurf.js +43 -0
- package/dist/install/windsurf.js.map +1 -0
- package/dist/mcp/server.js +20 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/shared/types.d.ts +4 -0
- package/dist/ui/assets/{index-BA6IpU8g.css → index-Beo8psd-.css} +1 -1
- package/dist/ui/assets/index-CSPTTeeM.js +69 -0
- package/dist/ui/index.html +2 -2
- package/package.json +26 -1
- package/plugin/scripts/prompt-recall-hook.mjs +55 -4
- package/plugin/scripts/stop-hook.mjs +57 -6
- package/.agents/plugins/marketplace.json +0 -20
- package/.github/workflows/ci.yml +0 -30
- package/dist/ui/assets/index-B_qIVV0k.js +0 -69
- package/docs/methodology/re-derivation-rate.md +0 -112
- package/docs/methodology/useful-hit-rate.md +0 -79
- package/docs/plans/2026-05-20-fts5-lexical-recall.md +0 -1088
- package/docs/plans/2026-05-20-recall-daemon-wedge-fix.md +0 -662
- package/docs/plans/2026-05-20-recall-hook-design.md +0 -131
- package/docs/plans/2026-05-20-recall-hook-implementation.md +0 -1222
- package/docs/plans/desktop-product.md +0 -69
- package/docs/plans/factstore-design.md +0 -236
- package/logs/CHANGELOG/CHANGELOG-2026.md +0 -1389
- package/logs/CHANGELOG/CHANGELOG.md +0 -337
- package/migrations/000_initial_schema.sql +0 -174
- package/migrations/001_entity_type_rename.sql +0 -17
- package/migrations/002_adapter_state_extend.sql +0 -12
- package/migrations/003_session_embeddings.sql +0 -11
- package/migrations/004_facts.sql +0 -46
- package/migrations/005_sources.sql +0 -31
- package/migrations/006_providers.sql +0 -33
- package/migrations/007_source_tokens.sql +0 -17
- package/migrations/008_fts_rebuild.sql +0 -9
- package/migrations/009_session_embedding_chunks.sql +0 -46
- package/migrations/010_sources_opencode.sql +0 -30
- package/migrations/011_sources_hermes_agent.sql +0 -30
- package/migrations/012_sources_aider.sql +0 -30
- package/migrations/013_adapter_state_failure_count.sql +0 -12
- package/plugin-hermes-agent/README.md +0 -49
- package/plugin-hermes-agent/__init__.py +0 -75
- package/plugin-hermes-agent/plugin.yaml +0 -15
- package/scripts/backfill-citations.mjs +0 -0
- package/scripts/build-codex-plugin.mjs +0 -61
- package/scripts/deepseek-probe.mjs +0 -67
- package/scripts/extract-triples.mjs +0 -207
- package/scripts/longmemeval/embedding-cache.ts +0 -77
- package/scripts/longmemeval/fetch-dataset.sh +0 -25
- package/scripts/longmemeval/run-harness.ts +0 -315
- package/scripts/longmemeval/scorer.ts +0 -99
- package/scripts/longmemeval/tsconfig.json +0 -9
- package/scripts/longmemeval/types.ts +0 -35
- package/scripts/nlm-daily-digest.py +0 -239
- package/scripts/nlm-daily-digest.sh +0 -28
- package/src/cli/classify-parity.ts +0 -257
- package/src/cli/launchctl-helpers.ts +0 -49
- package/src/cli/nlm.ts +0 -885
- package/src/core/actions/actions-log.ts +0 -118
- package/src/core/actions/overlay.ts +0 -117
- package/src/core/adapters/aider.ts +0 -205
- package/src/core/adapters/claude-code.ts +0 -293
- package/src/core/adapters/common.ts +0 -54
- package/src/core/adapters/from-source.ts +0 -57
- package/src/core/adapters/hermes-agent.ts +0 -240
- package/src/core/adapters/hermes.ts +0 -277
- package/src/core/adapters/jsonl-generic.ts +0 -208
- package/src/core/adapters/opencode.ts +0 -281
- package/src/core/adapters/pi.ts +0 -264
- package/src/core/classifier/prompt.ts +0 -200
- package/src/core/dataset/build-dataset.ts +0 -463
- package/src/core/embedding/chunk-body.ts +0 -76
- package/src/core/embedding/embed-backfill.ts +0 -210
- package/src/core/embedding/embed-normalize.ts +0 -135
- package/src/core/facts/backfill-facts.ts +0 -254
- package/src/core/facts/extract-facts.ts +0 -50
- package/src/core/hook/citation-detect.ts +0 -124
- package/src/core/hook/cite-memo.ts +0 -68
- package/src/core/hook/claude-settings.ts +0 -166
- package/src/core/hook/gate.ts +0 -25
- package/src/core/hook/hook-log.ts +0 -41
- package/src/core/hook/memo-sweep.ts +0 -164
- package/src/core/hook/memo.ts +0 -67
- package/src/core/hook/pointer-block.ts +0 -26
- package/src/core/hook/select.ts +0 -32
- package/src/core/hook/transcript.ts +0 -121
- package/src/core/ingest/ingest-session.ts +0 -111
- package/src/core/providers/provider-models.ts +0 -100
- package/src/core/providers/provider-registry.ts +0 -196
- package/src/core/recall/citation-log.ts +0 -108
- package/src/core/recall/filter.ts +0 -27
- package/src/core/recall/index.ts +0 -6
- package/src/core/recall/match-fields.ts +0 -40
- package/src/core/recall/query-log.ts +0 -149
- package/src/core/recall/query-shape.ts +0 -66
- package/src/core/recall/recall-service.ts +0 -320
- package/src/core/recall/recent-log.ts +0 -59
- package/src/core/recall/tokenize.ts +0 -18
- package/src/core/recall/useful-scan.ts +0 -336
- package/src/core/recall-facts/fact-query-log.ts +0 -150
- package/src/core/recall-facts/fact-recall-service.ts +0 -327
- package/src/core/scheduler/scan-once.ts +0 -142
- package/src/core/scheduler/scheduler.ts +0 -225
- package/src/core/sources/source-registry.ts +0 -260
- package/src/core/storage/db-restore.ts +0 -133
- package/src/core/storage/live-status.ts +0 -45
- package/src/core/storage/migrate.ts +0 -72
- package/src/core/storage/sqlite-fact-store.ts +0 -304
- package/src/core/storage/sqlite-session-store.ts +0 -765
- package/src/hook/prompt-recall-hook.ts +0 -174
- package/src/hook/session-end-hook.ts +0 -81
- package/src/hook/session-start-hook.ts +0 -165
- package/src/hook/stop-hook.ts +0 -236
- package/src/http/app.ts +0 -1137
- package/src/install/claude-code.ts +0 -128
- package/src/install/codex.ts +0 -367
- package/src/install/hermes-agent.ts +0 -76
- package/src/install/hermes.ts +0 -78
- package/src/install/ollama.ts +0 -211
- package/src/install/setup.ts +0 -368
- package/src/llm/classifier-box.ts +0 -64
- package/src/llm/deepseek-client.ts +0 -150
- package/src/llm/env-autoload.ts +0 -55
- package/src/llm/ollama-client.ts +0 -189
- package/src/mcp/server.ts +0 -534
- package/src/ports/fact-store.ts +0 -102
- package/src/ports/llm-client.ts +0 -52
- package/src/ports/logger.ts +0 -16
- package/src/ports/session-store.ts +0 -45
- package/src/ports/transcript-adapter.ts +0 -55
- package/src/shared/types.ts +0 -145
- package/src/ui/App.tsx +0 -58
- package/src/ui/components/PromoteOpenButton.tsx +0 -65
- package/src/ui/components/SessionDrawer.tsx +0 -136
- package/src/ui/components/SideNav.tsx +0 -162
- package/src/ui/components/Skeleton.tsx +0 -107
- package/src/ui/index.html +0 -13
- package/src/ui/lib/actions.ts +0 -30
- package/src/ui/lib/api.ts +0 -92
- package/src/ui/lib/dataset.ts +0 -141
- package/src/ui/lib/registries.ts +0 -155
- package/src/ui/lib/view-settings.ts +0 -41
- package/src/ui/main.tsx +0 -15
- package/src/ui/pages/Live.tsx +0 -229
- package/src/ui/pages/Pulse.tsx +0 -415
- package/src/ui/pages/Recall.tsx +0 -190
- package/src/ui/pages/River.tsx +0 -308
- package/src/ui/pages/Search.tsx +0 -93
- package/src/ui/pages/Stub.tsx +0 -9
- package/src/ui/pages/Thread.tsx +0 -262
- package/src/ui/pages/settings/Classifier.tsx +0 -227
- package/src/ui/pages/settings/Data.tsx +0 -190
- package/src/ui/pages/settings/Index.tsx +0 -65
- package/src/ui/pages/settings/Labels.tsx +0 -224
- package/src/ui/pages/settings/Providers.tsx +0 -305
- package/src/ui/pages/settings/SettingsSubnav.tsx +0 -28
- package/src/ui/pages/settings/Sources.tsx +0 -326
- package/src/ui/pages/settings/Views.tsx +0 -96
- package/src/ui/styles.css +0 -1766
- package/src/ui/tsconfig.json +0 -21
- package/src/ui/vite.config.ts +0 -19
- package/tests/fixtures/claude_code/short_session.jsonl +0 -2
- package/tests/fixtures/claude_code/standard_iso.jsonl +0 -4
- package/tests/fixtures/claude_code/tool_heavy.jsonl +0 -8
- package/tests/fixtures/claude_code/with_subagent.jsonl +0 -7
- package/tests/fixtures/facts.ts +0 -17
- package/tests/fixtures/golden-corpus.ts +0 -85
- package/tests/fixtures/hermes/paired_request_dump.json +0 -24
- package/tests/fixtures/hermes/paired_session.json +0 -23
- package/tests/fixtures/hermes/request_dump.json +0 -28
- package/tests/fixtures/hermes/session_iso.json +0 -38
- package/tests/fixtures/hermes/session_unix.json +0 -38
- package/tests/fixtures/hermes/system_only.json +0 -18
- package/tests/fixtures/pi/error-connection-abort.jsonl +0 -8
- package/tests/fixtures/pi/short-successful.jsonl +0 -5
- package/tests/fixtures/pi/with-custom-message.jsonl +0 -6
- package/tests/fixtures/sessions.ts +0 -22
- package/tests/integration/backfill-facts.test.ts +0 -362
- package/tests/integration/citation-explicit.test.ts +0 -111
- package/tests/integration/cite-event.test.ts +0 -169
- package/tests/integration/cite-memo.test.ts +0 -87
- package/tests/integration/db-restore.test.ts +0 -153
- package/tests/integration/embed-backfill.test.ts +0 -176
- package/tests/integration/fact-supersedence.test.ts +0 -313
- package/tests/integration/fts-index.test.ts +0 -60
- package/tests/integration/getbyids-sqlite.test.ts +0 -60
- package/tests/integration/hermes-agent-hooks.test.ts +0 -248
- package/tests/integration/hook-claude-settings.test.ts +0 -205
- package/tests/integration/hook-log.test.ts +0 -54
- package/tests/integration/hook-memo.test.ts +0 -68
- package/tests/integration/hook-pre-compact.test.ts +0 -105
- package/tests/integration/hook-subagent-start.test.ts +0 -102
- package/tests/integration/http.test.ts +0 -401
- package/tests/integration/keyword-search-fts.test.ts +0 -66
- package/tests/integration/mcp-recall-logging.test.ts +0 -88
- package/tests/integration/mcp.test.ts +0 -248
- package/tests/integration/memo-sweep.test.ts +0 -91
- package/tests/integration/prompt-recall-hook.test.ts +0 -88
- package/tests/integration/provider-registry.test.ts +0 -107
- package/tests/integration/recall-golden.test.ts +0 -59
- package/tests/integration/recall-sqlite.test.ts +0 -169
- package/tests/integration/scheduler.test.ts +0 -391
- package/tests/integration/session-end-hook.test.ts +0 -48
- package/tests/integration/session-start-hook.test.ts +0 -126
- package/tests/integration/source-registry.test.ts +0 -120
- package/tests/integration/sqlite-fact-store.test.ts +0 -346
- package/tests/integration/stop-hook.test.ts +0 -560
- package/tests/integration/wal-checkpoint.test.ts +0 -49
- package/tests/unit/cli/launchctl-helpers.test.ts +0 -60
- package/tests/unit/core/adapters/aider.test.ts +0 -230
- package/tests/unit/core/adapters/claude-code.test.ts +0 -118
- package/tests/unit/core/adapters/hermes-agent.test.ts +0 -329
- package/tests/unit/core/adapters/hermes.test.ts +0 -81
- package/tests/unit/core/adapters/jsonl-generic.test.ts +0 -142
- package/tests/unit/core/adapters/opencode.test.ts +0 -354
- package/tests/unit/core/adapters/pi.test.ts +0 -110
- package/tests/unit/core/classifier/prompt.test.ts +0 -126
- package/tests/unit/core/embedding/chunk-body.test.ts +0 -100
- package/tests/unit/core/facts/extract-facts.test.ts +0 -117
- package/tests/unit/core/filter.test.ts +0 -40
- package/tests/unit/core/hook/citation-detect-cite-session.test.ts +0 -96
- package/tests/unit/core/hook/citation-detect.test.ts +0 -124
- package/tests/unit/core/hook/gate.test.ts +0 -29
- package/tests/unit/core/hook/pointer-block.test.ts +0 -22
- package/tests/unit/core/hook/select.test.ts +0 -66
- package/tests/unit/core/match-fields.test.ts +0 -39
- package/tests/unit/core/mcp-cite-session.test.ts +0 -51
- package/tests/unit/core/providers/provider-models.test.ts +0 -101
- package/tests/unit/core/query-shape.test.ts +0 -92
- package/tests/unit/core/recall-facts/fact-recall-service.test.ts +0 -258
- package/tests/unit/core/recall-service.test.ts +0 -200
- package/tests/unit/core/storage/live-status.test.ts +0 -54
- package/tests/unit/core/tokenize.test.ts +0 -32
- package/tests/unit/core/useful-scan.test.ts +0 -537
- package/tests/unit/llm/embed.test.ts +0 -93
- package/tests/unit/llm/ollama-client.test.ts +0 -124
- package/tests/unit/scripts/longmemeval-scorer.test.ts +0 -114
- package/tsconfig.json +0 -31
- package/tsconfig.test.json +0 -11
- package/vitest.config.ts +0 -22
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
> Local-first memory OS for AI operators — one corpus across every runtime you use.
|
|
4
4
|
|
|
5
|
-
`nlm-memory` indexes every session from Claude Code, Codex, OpenCode, Hermes, Aider, and pi into a single searchable store on your machine. Three properties no competitor ships together:
|
|
5
|
+
`nlm-memory` indexes every session from Claude Code, Codex, OpenCode, Cursor, Windsurf, Hermes, Aider, and pi into a single searchable store on your machine. Three properties no competitor ships together:
|
|
6
6
|
|
|
7
|
-
1. **Cross-runtime reach.**
|
|
7
|
+
1. **Cross-runtime reach.** One index spans every adapter — not one per runtime.
|
|
8
8
|
2. **Editable timeline.** Sessions can be superseded, retired, or marked aborted. Memory is non-linear: patch history retroactively. No other memory layer lets you do this.
|
|
9
9
|
3. **97.2% R@5 baseline.** On a 14-month corpus, keyword recall surfaces the right session in the top 5 on 97.2% of evaluator queries. No fine-tuning, no cloud, no account.
|
|
10
10
|
|
|
@@ -15,81 +15,119 @@ Everything stays on your machine. No telemetry, no account required beyond your
|
|
|
15
15
|
## Requirements
|
|
16
16
|
|
|
17
17
|
- **Node 20+**
|
|
18
|
-
- **[Ollama](https://ollama.com)** running locally with `nomic-embed-text` pulled:
|
|
18
|
+
- **[Ollama](https://ollama.com)** running locally with `nomic-embed-text` pulled for semantic search:
|
|
19
19
|
```sh
|
|
20
20
|
ollama pull nomic-embed-text
|
|
21
21
|
```
|
|
22
|
-
- **A classifier** —
|
|
22
|
+
- **A classifier** — pick during setup:
|
|
23
|
+
- **DeepSeek cloud** (recommended for speed) — fast, cheap (~$0.002/session). Sends up to 30K chars of each session transcript to `api.deepseek.com`.
|
|
24
|
+
- **Ollama local** — fully offline. Slower; uses whichever chat model you select from your local pull list.
|
|
23
25
|
|
|
24
26
|
---
|
|
25
27
|
|
|
26
28
|
## Install
|
|
27
29
|
|
|
28
30
|
```sh
|
|
29
|
-
npm install -g
|
|
30
|
-
nlm
|
|
31
|
-
nlm install
|
|
31
|
+
npm install -g nlm-memory
|
|
32
|
+
nlm setup
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
`nlm
|
|
35
|
+
`nlm setup` is the interactive first-run wizard. It asks you to pick your classifier, model, and which runtimes to connect (Claude Code, Codex, Hermes), then installs the daemon. After it finishes, open **http://localhost:3940/ui** — done.
|
|
36
|
+
|
|
37
|
+
### Platform support
|
|
38
|
+
|
|
39
|
+
| Platform | Daemon | Notes |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| **macOS** | LaunchAgent at `~/Library/LaunchAgents/com.github.pbmagnet4.nlm-memory.plist` | Auto-starts on login |
|
|
42
|
+
| **Linux** | systemd user unit at `~/.config/systemd/user/nlm.service` | Run `loginctl enable-linger $USER` on headless servers so the daemon survives logout |
|
|
43
|
+
| **Windows** | Manual `nlm start` for now | Hook + MCP install paths are platform-aware; daemon supervisor lands in the next release |
|
|
35
44
|
|
|
36
45
|
To stop or remove:
|
|
37
46
|
```sh
|
|
38
|
-
|
|
39
|
-
nlm uninstall # remove the LaunchAgent entirely
|
|
47
|
+
nlm uninstall # remove the daemon supervisor on your platform
|
|
40
48
|
```
|
|
41
49
|
|
|
42
50
|
---
|
|
43
51
|
|
|
44
|
-
##
|
|
52
|
+
## How recall works
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
Once installed, NLM runs as a quiet background daemon. Two ways your AI agents get to it:
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
{
|
|
50
|
-
"mcpServers": {
|
|
51
|
-
"nlm-memory": {
|
|
52
|
-
"command": "node",
|
|
53
|
-
"args": ["<path-to-global-npm>/lib/node_modules/nlm-memory/dist/cli/nlm.js", "mcp"]
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
```
|
|
56
|
+
### 1. Hooks (Claude Code) — automatic context injection
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
`nlm connect claude-code` installs five hooks into `~/.claude/settings.json`:
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
- **UserPromptSubmit / SessionStart** — before each turn, NLM scores the prompt against your past sessions and silently prepends a pointer block listing the 0–3 most likely-relevant prior sessions. The model sees them as conversational context.
|
|
61
|
+
- **Stop** — after the model responds, NLM scans the response for citations of surfaced session IDs to measure useful-hit rate.
|
|
62
|
+
- **PreCompact / SubagentStart** — link conversations across compactions and subagent dispatches so threads stay coherent.
|
|
63
|
+
|
|
64
|
+
Default mode is **live** (recall injected into prompts). Switch to **shadow** (log-only, no injection) by setting `NLM_HOOK_MODE=shadow` in your hook commands.
|
|
65
|
+
|
|
66
|
+
### 2. MCP — explicit tools any agent can call
|
|
62
67
|
|
|
63
68
|
```sh
|
|
64
|
-
nlm connect claude-code # writes
|
|
69
|
+
nlm connect claude-code # writes ~/.mcp.json + installs hooks
|
|
65
70
|
nlm connect codex # installs as a Codex marketplace plugin
|
|
66
|
-
nlm connect hermes # writes
|
|
71
|
+
nlm connect hermes # writes ~/.hermes/config.yaml (MCP)
|
|
67
72
|
nlm connect hermes-agent # installs as a NousResearch Hermes plugin (hooks + MCP)
|
|
68
73
|
```
|
|
69
74
|
|
|
70
|
-
Once wired, agents can call `recall_sessions` (search past conversations)
|
|
75
|
+
Once wired, agents can call `recall_sessions` (search past conversations), `recall_facts` (decisions/open questions/project state), `get_session` (pull a full session), `get_fact_history` (how a decision evolved), and `cite_session` (explicitly mark a session as referenced).
|
|
76
|
+
|
|
77
|
+
For container-hosted agents that can't use stdio MCP, the daemon also exposes Streamable-HTTP MCP at `POST /mcp`. Use the auto-generated `NLM_MCP_TOKEN` from `~/.nlm/.env` as a bearer.
|
|
71
78
|
|
|
72
79
|
---
|
|
73
80
|
|
|
74
|
-
## What's inside
|
|
81
|
+
## What's inside the UI
|
|
82
|
+
|
|
83
|
+
Open `http://localhost:3940/ui` after the daemon starts.
|
|
75
84
|
|
|
76
85
|
| Page | What it shows |
|
|
77
86
|
|---|---|
|
|
78
87
|
| **Live** | Sessions being written in real time, recent reads and decisions |
|
|
79
88
|
| **Pulse** | System health — coherence, runtimes, stale entities, recent sessions |
|
|
80
|
-
| **River** | Full session timeline with density controls |
|
|
81
|
-
| **Thread** | Per-entity conversation history |
|
|
82
|
-
| **Search** | Keyword, semantic, or hybrid recall |
|
|
89
|
+
| **River** | Full session timeline with density controls and supersedence visualization |
|
|
90
|
+
| **Thread** | Per-entity conversation history with runtime filters |
|
|
91
|
+
| **Search** | Keyword, semantic, or hybrid recall with match snippets |
|
|
83
92
|
| **Recall** | Adoption telemetry — is the memory system actually being used? |
|
|
84
93
|
| **Settings** | Sources, providers, classifier, data backup/restore |
|
|
85
94
|
|
|
86
95
|
---
|
|
87
96
|
|
|
97
|
+
## Security
|
|
98
|
+
|
|
99
|
+
NLM is local-first by design. The daemon:
|
|
100
|
+
|
|
101
|
+
- Binds to `127.0.0.1` only — never `0.0.0.0`.
|
|
102
|
+
- Enforces Host + Origin checks on `/api/*` to block DNS rebinding and cross-origin drive-by.
|
|
103
|
+
- Generates a 256-bit `NLM_MCP_TOKEN` on first run and persists to `~/.nlm/.env` (mode `0600`). All non-browser API requests (hooks, MCP container clients) authenticate with `Authorization: Bearer ${NLM_MCP_TOKEN}`.
|
|
104
|
+
- Recursively enforces `0700` on `~/.nlm/` and `0600` on its contents on every start.
|
|
105
|
+
- Sends nothing outbound except:
|
|
106
|
+
- Ollama (`localhost:11434`) for embeddings + local classifier
|
|
107
|
+
- DeepSeek API (`api.deepseek.com`) — only when classifier is set to DeepSeek
|
|
108
|
+
- Your AI runtime transcript files (read-only)
|
|
109
|
+
|
|
110
|
+
No telemetry. No vendor calls. No account.
|
|
111
|
+
|
|
112
|
+
Report vulnerabilities via [SECURITY.md](SECURITY.md).
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Upgrading from v0.4.x
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
npm update -g nlm-memory
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Old installs have `NLM_HOOK_MODE=shadow` hardcoded in `~/.claude/settings.json` — shadow mode is silent, so re-run `nlm hook install` to switch to live recall injection. Permissions and `NLM_MCP_TOKEN` self-heal on the next `nlm start`.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
88
126
|
## How it differs from mem0 and graphiti
|
|
89
127
|
|
|
90
128
|
- **Unit of memory:** whole sessions with extracted markers (decisions, open questions, entities), not individual facts or graph edges.
|
|
91
129
|
- **Audience:** you querying your own past work, not an embedded SDK for app developers.
|
|
92
|
-
- **Cross-runtime:** one corpus across Claude Code, Codex, OpenCode, Hermes, and more. Competitors target one runtime.
|
|
130
|
+
- **Cross-runtime:** one corpus across Claude Code, Codex, OpenCode, Cursor, Windsurf, Hermes, and more. Competitors target one runtime.
|
|
93
131
|
- **Editable timeline:** sessions can be superseded, retired, aborted. No other tool lets you retrofit memory — a record from 6 months ago can be corrected today.
|
|
94
132
|
- **Local-only:** no hosted offering, no telemetry, no vendor dependency.
|
|
95
133
|
|
|
@@ -104,11 +142,11 @@ npm install # install dependencies
|
|
|
104
142
|
npm run build # compile dist/ — commit the result, it ships in the repo
|
|
105
143
|
npm run dev # hot-reload daemon
|
|
106
144
|
npm run ui:dev # hot-reload UI at localhost:5173 (proxies /api to :3940)
|
|
107
|
-
npm test #
|
|
145
|
+
npm test # 601 tests across 62 files
|
|
108
146
|
npm run typecheck
|
|
109
147
|
```
|
|
110
148
|
|
|
111
|
-
`dist/` is committed to the repo so
|
|
149
|
+
`dist/` is committed to the repo so the global install works without a build step on the user's machine. Rebuild and commit `dist/` whenever you change `src/`.
|
|
112
150
|
|
|
113
151
|
Database lives at `~/.nlm/canonical.sqlite`. Override with `NLM_DB_PATH`.
|
|
114
152
|
|
package/dist/cli/nlm.js
CHANGED
|
@@ -26,8 +26,9 @@ import { fileURLToPath } from "node:url";
|
|
|
26
26
|
import { dirname, resolve, join } from "node:path";
|
|
27
27
|
import { homedir } from "node:os";
|
|
28
28
|
import { mkdirSync, writeFileSync, existsSync, rmSync } from "node:fs";
|
|
29
|
-
import { execFileSync } from "node:child_process";
|
|
29
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
30
30
|
import { Command } from "commander";
|
|
31
|
+
import pkg from "../../package.json" with { type: "json" };
|
|
31
32
|
import { serve } from "@hono/node-server";
|
|
32
33
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
33
34
|
import { FactRecallService } from "../core/recall-facts/fact-recall-service.js";
|
|
@@ -45,9 +46,13 @@ import { OllamaClient } from "../llm/ollama-client.js";
|
|
|
45
46
|
import { autoloadEnv } from "../llm/env-autoload.js";
|
|
46
47
|
import { addHook, buildHookCommand, removeHook, smokeTestHookCommand } from "../core/hook/claude-settings.js";
|
|
47
48
|
import { codexBinaryAvailable, connectCodex, disconnectCodex, pluginScriptsDir, } from "../install/codex.js";
|
|
48
|
-
import { connectClaudeCode, disconnectClaudeCode, installClaudeCodeHooks } from "../install/claude-code.js";
|
|
49
|
+
import { connectClaudeCode, disconnectClaudeCode, installClaudeCodeHooks, mcpConfigPath } from "../install/claude-code.js";
|
|
50
|
+
import { hardenNlmDirPermissions } from "../install/nlm-dir-perms.js";
|
|
51
|
+
import { ensureMcpToken } from "../install/ollama.js";
|
|
52
|
+
import { connectCursor, disconnectCursor } from "../install/cursor.js";
|
|
49
53
|
import { connectHermes, disconnectHermes, hermesConfigPath } from "../install/hermes.js";
|
|
50
54
|
import { connectHermesAgent, disconnectHermesAgent, hermesAgentPluginDir } from "../install/hermes-agent.js";
|
|
55
|
+
import { connectWindsurf, disconnectWindsurf } from "../install/windsurf.js";
|
|
51
56
|
import { runSetup } from "../install/setup.js";
|
|
52
57
|
import { runParity } from "./classify-parity.js";
|
|
53
58
|
import { reembedCorpus } from "../core/embedding/embed-backfill.js";
|
|
@@ -149,13 +154,20 @@ const program = new Command();
|
|
|
149
154
|
program
|
|
150
155
|
.name("nlm")
|
|
151
156
|
.description("Local-first memory operating system for AI operators")
|
|
152
|
-
.version(
|
|
157
|
+
.version(pkg.version);
|
|
153
158
|
program
|
|
154
159
|
.command("start")
|
|
155
160
|
.description("Boot the HTTP server + ingest scheduler")
|
|
156
161
|
.option("--no-scheduler", "HTTP only; skip the ingest tick loop")
|
|
157
162
|
.option("--interval-min <n>", "scheduler tick interval (min, default 30)", (v) => Number.parseInt(v, 10), 30)
|
|
158
163
|
.action(async (opts) => {
|
|
164
|
+
// Self-heal perms on every daemon start. Idempotent. Covers upgrade
|
|
165
|
+
// path from pre-v0.4.2 installs where ~/.nlm contents were world-readable.
|
|
166
|
+
hardenNlmDirPermissions();
|
|
167
|
+
// Generate NLM_MCP_TOKEN if missing so /api/* gets Bearer-protected for
|
|
168
|
+
// non-browser callers. Idempotent: re-reads persisted token first.
|
|
169
|
+
autoloadEnv();
|
|
170
|
+
ensureMcpToken();
|
|
159
171
|
const { store, facts, sources, providers, recall, factRecall, embedder, classifier } = buildStack();
|
|
160
172
|
const { existsSync } = await import("node:fs");
|
|
161
173
|
const hasMcpToken = Boolean(process.env["NLM_MCP_TOKEN"]);
|
|
@@ -387,6 +399,37 @@ program
|
|
|
387
399
|
});
|
|
388
400
|
const LAUNCH_AGENT_LABEL = "com.github.pbmagnet4.nlm-memory";
|
|
389
401
|
const LAUNCH_AGENT_PLIST = join(homedir(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
|
|
402
|
+
const LINUX_SYSTEMD_UNIT_NAME = "nlm.service";
|
|
403
|
+
const LINUX_SYSTEMD_UNIT_PATH = join(homedir(), ".config", "systemd", "user", LINUX_SYSTEMD_UNIT_NAME);
|
|
404
|
+
function buildSystemdUnit(nodeExec, nlmJs) {
|
|
405
|
+
const logDir = join(homedir(), ".nlm", "logs");
|
|
406
|
+
return `[Unit]
|
|
407
|
+
Description=NLM Memory — local AI session memory daemon
|
|
408
|
+
After=network.target
|
|
409
|
+
|
|
410
|
+
[Service]
|
|
411
|
+
Type=simple
|
|
412
|
+
ExecStart=${nodeExec} ${nlmJs} start
|
|
413
|
+
WorkingDirectory=${homedir()}
|
|
414
|
+
Restart=on-failure
|
|
415
|
+
RestartSec=10
|
|
416
|
+
StandardOutput=append:${logDir}/daemon-out.log
|
|
417
|
+
StandardError=append:${logDir}/daemon-err.log
|
|
418
|
+
|
|
419
|
+
[Install]
|
|
420
|
+
WantedBy=default.target
|
|
421
|
+
`;
|
|
422
|
+
}
|
|
423
|
+
// systemd user instance needs XDG_RUNTIME_DIR (a real user session) and
|
|
424
|
+
// systemctl --user to respond. Both are missing on headless servers without
|
|
425
|
+
// loginctl enable-linger and in many minimal containers.
|
|
426
|
+
function linuxSystemdUserAvailable() {
|
|
427
|
+
if (process.platform !== "linux")
|
|
428
|
+
return false;
|
|
429
|
+
if (!process.env["XDG_RUNTIME_DIR"])
|
|
430
|
+
return false;
|
|
431
|
+
return spawnSync("systemctl", ["--user", "--version"], { encoding: "utf8" }).status === 0;
|
|
432
|
+
}
|
|
390
433
|
function buildPlist(nodeExec, nlmJs) {
|
|
391
434
|
const logDir = join(homedir(), ".nlm", "logs");
|
|
392
435
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -424,38 +467,91 @@ function buildPlist(nodeExec, nlmJs) {
|
|
|
424
467
|
}
|
|
425
468
|
program
|
|
426
469
|
.command("install")
|
|
427
|
-
.description("Install the
|
|
470
|
+
.description("Install the auto-start daemon (LaunchAgent on macOS, systemd user unit on Linux)")
|
|
428
471
|
.action(() => {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
472
|
+
// Harden before installing the daemon so the persisted unit owner-
|
|
473
|
+
// checks succeed against locked-down ~/.nlm logs.
|
|
474
|
+
hardenNlmDirPermissions();
|
|
475
|
+
if (process.platform === "darwin") {
|
|
476
|
+
const uid = process.getuid?.();
|
|
477
|
+
if (uid === undefined) {
|
|
478
|
+
console.error("nlm install: could not determine UID");
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
mkdirSync(join(homedir(), ".nlm", "logs"), { recursive: true });
|
|
482
|
+
writeFileSync(LAUNCH_AGENT_PLIST, buildPlist(process.execPath, __filename), "utf8");
|
|
483
|
+
console.error(`nlm: wrote ${LAUNCH_AGENT_PLIST}`);
|
|
484
|
+
try {
|
|
485
|
+
execFileSync("launchctl", ["bootout", `gui/${uid}`, LAUNCH_AGENT_LABEL], { stdio: "ignore" });
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
// not loaded yet — expected on first install
|
|
489
|
+
}
|
|
490
|
+
execFileSync("launchctl", ["bootstrap", `gui/${uid}`, LAUNCH_AGENT_PLIST]);
|
|
491
|
+
console.error("nlm: daemon installed and started.");
|
|
492
|
+
console.error(` UI: http://localhost:${port()}/ui`);
|
|
493
|
+
console.error(` To stop: launchctl stop ${LAUNCH_AGENT_LABEL}`);
|
|
494
|
+
console.error(" To remove: nlm uninstall");
|
|
495
|
+
return;
|
|
443
496
|
}
|
|
444
|
-
|
|
445
|
-
|
|
497
|
+
if (process.platform === "linux") {
|
|
498
|
+
if (!linuxSystemdUserAvailable()) {
|
|
499
|
+
console.error("nlm install: systemd user instance not available.");
|
|
500
|
+
console.error(" XDG_RUNTIME_DIR missing or `systemctl --user` did not respond.");
|
|
501
|
+
console.error(" Common on headless servers without an active user session.");
|
|
502
|
+
console.error(" Start manually with: nlm start &");
|
|
503
|
+
console.error(" Or enable lingering so user units run without login:");
|
|
504
|
+
console.error(" sudo loginctl enable-linger $USER");
|
|
505
|
+
console.error(" Then re-run: nlm install");
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
mkdirSync(dirname(LINUX_SYSTEMD_UNIT_PATH), { recursive: true });
|
|
509
|
+
mkdirSync(join(homedir(), ".nlm", "logs"), { recursive: true });
|
|
510
|
+
writeFileSync(LINUX_SYSTEMD_UNIT_PATH, buildSystemdUnit(process.execPath, __filename), "utf8");
|
|
511
|
+
console.error(`nlm: wrote ${LINUX_SYSTEMD_UNIT_PATH}`);
|
|
512
|
+
execFileSync("systemctl", ["--user", "daemon-reload"]);
|
|
513
|
+
execFileSync("systemctl", ["--user", "enable", "--now", LINUX_SYSTEMD_UNIT_NAME]);
|
|
514
|
+
console.error("nlm: daemon installed and started.");
|
|
515
|
+
console.error(` UI: http://localhost:${port()}/ui`);
|
|
516
|
+
console.error(` Status: systemctl --user status ${LINUX_SYSTEMD_UNIT_NAME}`);
|
|
517
|
+
console.error(` To stop: systemctl --user stop ${LINUX_SYSTEMD_UNIT_NAME}`);
|
|
518
|
+
console.error(" To remove: nlm uninstall");
|
|
519
|
+
console.error(" Headless? Run `sudo loginctl enable-linger $USER` so the daemon survives logout.");
|
|
520
|
+
return;
|
|
446
521
|
}
|
|
447
|
-
|
|
448
|
-
console.error("nlm
|
|
449
|
-
|
|
450
|
-
console.error(` To stop: launchctl stop ${LAUNCH_AGENT_LABEL}`);
|
|
451
|
-
console.error(" To remove: nlm uninstall");
|
|
522
|
+
console.error("nlm install: only macOS and Linux (systemd) are supported.");
|
|
523
|
+
console.error(" On Windows, run `nlm start` manually or via Task Scheduler.");
|
|
524
|
+
process.exit(1);
|
|
452
525
|
});
|
|
453
526
|
program
|
|
454
527
|
.command("uninstall")
|
|
455
|
-
.description("Remove the macOS
|
|
528
|
+
.description("Remove the auto-start daemon (LaunchAgent on macOS, systemd user unit on Linux)")
|
|
456
529
|
.action(() => {
|
|
530
|
+
if (process.platform === "linux") {
|
|
531
|
+
// Stop + disable, then remove the unit. Idempotent: ignore "not loaded"
|
|
532
|
+
// errors so re-running uninstall on a half-removed state still finishes.
|
|
533
|
+
try {
|
|
534
|
+
execFileSync("systemctl", ["--user", "disable", "--now", LINUX_SYSTEMD_UNIT_NAME], { stdio: "pipe" });
|
|
535
|
+
console.error(`nlm: stopped and disabled ${LINUX_SYSTEMD_UNIT_NAME}`);
|
|
536
|
+
}
|
|
537
|
+
catch {
|
|
538
|
+
// Unit wasn't loaded — fine, proceed to file cleanup.
|
|
539
|
+
}
|
|
540
|
+
if (existsSync(LINUX_SYSTEMD_UNIT_PATH)) {
|
|
541
|
+
rmSync(LINUX_SYSTEMD_UNIT_PATH);
|
|
542
|
+
console.error(`nlm: removed ${LINUX_SYSTEMD_UNIT_PATH}`);
|
|
543
|
+
}
|
|
544
|
+
try {
|
|
545
|
+
execFileSync("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
// systemd unavailable — file already removed, nothing more to do.
|
|
549
|
+
}
|
|
550
|
+
console.error("nlm: uninstalled. Run `nlm install` to reinstall.");
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
457
553
|
if (process.platform !== "darwin") {
|
|
458
|
-
console.error("nlm uninstall: only macOS
|
|
554
|
+
console.error("nlm uninstall: only macOS and Linux (systemd) are supported.");
|
|
459
555
|
process.exit(1);
|
|
460
556
|
}
|
|
461
557
|
const uid = process.getuid?.();
|
|
@@ -527,7 +623,7 @@ const hook = program
|
|
|
527
623
|
.description("Manage the Claude Code NLM hooks");
|
|
528
624
|
hook
|
|
529
625
|
.command("install")
|
|
530
|
-
.description("Add the NLM hooks (recall + session-end + stop) to ~/.claude/settings.json (
|
|
626
|
+
.description("Add the NLM hooks (recall + session-end + stop) to ~/.claude/settings.json (live mode)")
|
|
531
627
|
.action(() => {
|
|
532
628
|
const path = claudeSettingsPath();
|
|
533
629
|
const hookLogPath = process.env["NLM_HOOK_LOG"] ?? join(homedir(), ".nlm", "hook-log.jsonl");
|
|
@@ -537,7 +633,7 @@ hook
|
|
|
537
633
|
// partial failure is the bug class we shipped #161 to prevent.
|
|
538
634
|
const installed = [];
|
|
539
635
|
for (const spec of ALL_HOOKS) {
|
|
540
|
-
const command = buildHookCommand(process.execPath, spec.script, "
|
|
636
|
+
const command = buildHookCommand(process.execPath, spec.script, "live");
|
|
541
637
|
addHook(path, command, spec.event);
|
|
542
638
|
const smoke = smokeTestHookCommand(command, hookLogPath);
|
|
543
639
|
if (!smoke.ok) {
|
|
@@ -557,14 +653,14 @@ hook
|
|
|
557
653
|
}
|
|
558
654
|
installed.push(spec);
|
|
559
655
|
}
|
|
560
|
-
console.error(`nlm: NLM hooks installed in ${path} (
|
|
656
|
+
console.error(`nlm: NLM hooks installed in ${path} (live mode):`);
|
|
561
657
|
for (const spec of installed) {
|
|
562
658
|
console.error(` - ${spec.event} → ${spec.label}-hook`);
|
|
563
659
|
}
|
|
564
660
|
console.error(" Smoke tests passed — all hooks appended synthetic entries to hook-log.jsonl.");
|
|
565
|
-
console.error(" Recall hooks log to ~/.nlm/hook-log.jsonl
|
|
661
|
+
console.error(" Recall hooks inject prior-session context on UserPromptSubmit and log to ~/.nlm/hook-log.jsonl.");
|
|
566
662
|
console.error(" Session-end hook cleans up ~/.nlm/hook-state/<session>.json on session close.");
|
|
567
|
-
console.error(" To
|
|
663
|
+
console.error(" To run silently for calibration (no injection): set NLM_HOOK_MODE=shadow in the command.");
|
|
568
664
|
console.error(" To remove: nlm hook uninstall");
|
|
569
665
|
});
|
|
570
666
|
hook
|
|
@@ -637,7 +733,7 @@ connect
|
|
|
637
733
|
.action((opts) => {
|
|
638
734
|
if (opts.dryRun) {
|
|
639
735
|
console.error("nlm connect claude-code (dry run):");
|
|
640
|
-
console.error(` write [mcpServers.nlm-memory] to ${
|
|
736
|
+
console.error(` write [mcpServers.nlm-memory] to ${mcpConfigPath()}`);
|
|
641
737
|
if (opts.withHooks)
|
|
642
738
|
console.error(" install 6 Claude Code hooks");
|
|
643
739
|
return;
|
|
@@ -702,6 +798,54 @@ connect
|
|
|
702
798
|
}
|
|
703
799
|
console.error(" Also run: nlm connect hermes (to wire the MCP server)");
|
|
704
800
|
});
|
|
801
|
+
connect
|
|
802
|
+
.command("cursor")
|
|
803
|
+
.description("Register Cursor as an nlm source (reads state.vscdb directly — no files installed)")
|
|
804
|
+
.option("--db-path <path>", "override path to globalStorage/state.vscdb")
|
|
805
|
+
.option("--dry-run", "print what would happen without changing files")
|
|
806
|
+
.action((opts) => {
|
|
807
|
+
const store = new SqliteSessionStore({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
|
|
808
|
+
try {
|
|
809
|
+
const registry = new SourceRegistry(store.rawDb());
|
|
810
|
+
const report = connectCursor(registry, {
|
|
811
|
+
...(opts.dbPath ? { dbPath: opts.dbPath } : {}),
|
|
812
|
+
dryRun: Boolean(opts.dryRun),
|
|
813
|
+
});
|
|
814
|
+
if (opts.dryRun) {
|
|
815
|
+
console.error(`nlm connect cursor (dry run): register source at ${report.adapterDbPath}${report.adapterExists ? "" : " (not found yet)"}`);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
const suffix = report.adapterExists ? "" : " (DB not found — will activate when Cursor is installed)";
|
|
819
|
+
console.error(`nlm: Cursor source ${report.action} → ${report.adapterDbPath}${suffix}`);
|
|
820
|
+
}
|
|
821
|
+
finally {
|
|
822
|
+
store.close();
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
connect
|
|
826
|
+
.command("windsurf")
|
|
827
|
+
.description("Register Windsurf as an nlm source (reads state.vscdb files directly — no files installed)")
|
|
828
|
+
.option("--user-dir <path>", "override path to Windsurf User directory")
|
|
829
|
+
.option("--dry-run", "print what would happen without changing files")
|
|
830
|
+
.action((opts) => {
|
|
831
|
+
const store = new SqliteSessionStore({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
|
|
832
|
+
try {
|
|
833
|
+
const registry = new SourceRegistry(store.rawDb());
|
|
834
|
+
const report = connectWindsurf(registry, {
|
|
835
|
+
...(opts.userDir ? { userDir: opts.userDir } : {}),
|
|
836
|
+
dryRun: Boolean(opts.dryRun),
|
|
837
|
+
});
|
|
838
|
+
if (opts.dryRun) {
|
|
839
|
+
console.error(`nlm connect windsurf (dry run): register source at ${report.userDir}${report.dirExists ? "" : " (not found yet)"}`);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
const suffix = report.dirExists ? "" : " (User dir not found — will activate when Windsurf is installed)";
|
|
843
|
+
console.error(`nlm: Windsurf source ${report.action} → ${report.userDir}${suffix}`);
|
|
844
|
+
}
|
|
845
|
+
finally {
|
|
846
|
+
store.close();
|
|
847
|
+
}
|
|
848
|
+
});
|
|
705
849
|
const disconnect = program
|
|
706
850
|
.command("disconnect")
|
|
707
851
|
.description("Disconnect nlm-memory from an AI coding runtime");
|
|
@@ -791,6 +935,48 @@ disconnect
|
|
|
791
935
|
? `nlm: removed plugin directory ${report.destDir}`
|
|
792
936
|
: `nlm: no plugin directory found at ${report.destDir}`);
|
|
793
937
|
});
|
|
938
|
+
disconnect
|
|
939
|
+
.command("cursor")
|
|
940
|
+
.description("Disable the Cursor source in the nlm registry (leaves Cursor untouched)")
|
|
941
|
+
.option("--dry-run", "print what would happen without changing files")
|
|
942
|
+
.action((opts) => {
|
|
943
|
+
const store = new SqliteSessionStore({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
|
|
944
|
+
try {
|
|
945
|
+
const registry = new SourceRegistry(store.rawDb());
|
|
946
|
+
const report = disconnectCursor(registry, { dryRun: Boolean(opts.dryRun) });
|
|
947
|
+
if (opts.dryRun) {
|
|
948
|
+
console.error("nlm disconnect cursor (dry run): disable Cursor source in registry");
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
console.error(report.action === "disabled"
|
|
952
|
+
? "nlm: Cursor source disabled"
|
|
953
|
+
: "nlm: no Cursor source found in registry");
|
|
954
|
+
}
|
|
955
|
+
finally {
|
|
956
|
+
store.close();
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
disconnect
|
|
960
|
+
.command("windsurf")
|
|
961
|
+
.description("Disable the Windsurf source in the nlm registry (leaves Windsurf untouched)")
|
|
962
|
+
.option("--dry-run", "print what would happen without changing files")
|
|
963
|
+
.action((opts) => {
|
|
964
|
+
const store = new SqliteSessionStore({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
|
|
965
|
+
try {
|
|
966
|
+
const registry = new SourceRegistry(store.rawDb());
|
|
967
|
+
const report = disconnectWindsurf(registry, { dryRun: Boolean(opts.dryRun) });
|
|
968
|
+
if (opts.dryRun) {
|
|
969
|
+
console.error("nlm disconnect windsurf (dry run): disable Windsurf source in registry");
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
console.error(report.action === "disabled"
|
|
973
|
+
? "nlm: Windsurf source disabled"
|
|
974
|
+
: "nlm: no Windsurf source found in registry");
|
|
975
|
+
}
|
|
976
|
+
finally {
|
|
977
|
+
store.close();
|
|
978
|
+
}
|
|
979
|
+
});
|
|
794
980
|
program
|
|
795
981
|
.command("setup")
|
|
796
982
|
.description("Interactive first-run setup: detect runtimes, wire MCP + hooks, start daemon")
|
|
@@ -804,6 +990,10 @@ program
|
|
|
804
990
|
launchAgentLabel: LAUNCH_AGENT_LABEL,
|
|
805
991
|
launchAgentPlist: LAUNCH_AGENT_PLIST,
|
|
806
992
|
buildPlist,
|
|
993
|
+
linuxSystemdUnitName: LINUX_SYSTEMD_UNIT_NAME,
|
|
994
|
+
linuxSystemdUnitPath: LINUX_SYSTEMD_UNIT_PATH,
|
|
995
|
+
buildSystemdUnit,
|
|
996
|
+
linuxSystemdUserAvailable,
|
|
807
997
|
claudeSettingsPath: claudeSettingsPath(),
|
|
808
998
|
allHooks: ALL_HOOKS,
|
|
809
999
|
addHook,
|