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.
Files changed (285) hide show
  1. package/README.md +72 -34
  2. package/dist/cli/nlm.js +223 -33
  3. package/dist/cli/nlm.js.map +1 -1
  4. package/dist/core/adapters/cursor.d.ts +45 -0
  5. package/dist/core/adapters/cursor.js +397 -0
  6. package/dist/core/adapters/cursor.js.map +1 -0
  7. package/dist/core/adapters/from-source.js +10 -0
  8. package/dist/core/adapters/from-source.js.map +1 -1
  9. package/dist/core/adapters/windsurf.d.ts +44 -0
  10. package/dist/core/adapters/windsurf.js +299 -0
  11. package/dist/core/adapters/windsurf.js.map +1 -0
  12. package/dist/core/hook/claude-settings.d.ts +12 -5
  13. package/dist/core/hook/claude-settings.js +21 -6
  14. package/dist/core/hook/claude-settings.js.map +1 -1
  15. package/dist/core/sources/source-registry.d.ts +1 -1
  16. package/dist/core/sources/source-registry.js +18 -0
  17. package/dist/core/sources/source-registry.js.map +1 -1
  18. package/dist/core/storage/sqlite-session-store.d.ts +2 -0
  19. package/dist/core/storage/sqlite-session-store.js +38 -2
  20. package/dist/core/storage/sqlite-session-store.js.map +1 -1
  21. package/dist/hook/hook-auth.d.ts +13 -0
  22. package/dist/hook/hook-auth.js +19 -0
  23. package/dist/hook/hook-auth.js.map +1 -0
  24. package/dist/hook/prompt-recall-hook.js +7 -1
  25. package/dist/hook/prompt-recall-hook.js.map +1 -1
  26. package/dist/hook/session-start-hook.js +4 -1
  27. package/dist/hook/session-start-hook.js.map +1 -1
  28. package/dist/hook/stop-hook.js +4 -1
  29. package/dist/hook/stop-hook.js.map +1 -1
  30. package/dist/http/app.d.ts +2 -0
  31. package/dist/http/app.js +76 -1
  32. package/dist/http/app.js.map +1 -1
  33. package/dist/install/claude-code.js +1 -1
  34. package/dist/install/claude-code.js.map +1 -1
  35. package/dist/install/cursor.d.ts +25 -0
  36. package/dist/install/cursor.js +43 -0
  37. package/dist/install/cursor.js.map +1 -0
  38. package/dist/install/nlm-dir-perms.d.ts +19 -0
  39. package/dist/install/nlm-dir-perms.js +43 -0
  40. package/dist/install/nlm-dir-perms.js.map +1 -0
  41. package/dist/install/ollama.d.ts +18 -1
  42. package/dist/install/ollama.js +62 -7
  43. package/dist/install/ollama.js.map +1 -1
  44. package/dist/install/setup.d.ts +4 -0
  45. package/dist/install/setup.js +141 -18
  46. package/dist/install/setup.js.map +1 -1
  47. package/dist/install/windsurf.d.ts +25 -0
  48. package/dist/install/windsurf.js +43 -0
  49. package/dist/install/windsurf.js.map +1 -0
  50. package/dist/mcp/server.js +20 -1
  51. package/dist/mcp/server.js.map +1 -1
  52. package/dist/shared/types.d.ts +4 -0
  53. package/dist/ui/assets/{index-BA6IpU8g.css → index-Beo8psd-.css} +1 -1
  54. package/dist/ui/assets/index-CSPTTeeM.js +69 -0
  55. package/dist/ui/index.html +2 -2
  56. package/package.json +26 -1
  57. package/plugin/scripts/prompt-recall-hook.mjs +55 -4
  58. package/plugin/scripts/stop-hook.mjs +57 -6
  59. package/.agents/plugins/marketplace.json +0 -20
  60. package/.github/workflows/ci.yml +0 -30
  61. package/dist/ui/assets/index-B_qIVV0k.js +0 -69
  62. package/docs/methodology/re-derivation-rate.md +0 -112
  63. package/docs/methodology/useful-hit-rate.md +0 -79
  64. package/docs/plans/2026-05-20-fts5-lexical-recall.md +0 -1088
  65. package/docs/plans/2026-05-20-recall-daemon-wedge-fix.md +0 -662
  66. package/docs/plans/2026-05-20-recall-hook-design.md +0 -131
  67. package/docs/plans/2026-05-20-recall-hook-implementation.md +0 -1222
  68. package/docs/plans/desktop-product.md +0 -69
  69. package/docs/plans/factstore-design.md +0 -236
  70. package/logs/CHANGELOG/CHANGELOG-2026.md +0 -1389
  71. package/logs/CHANGELOG/CHANGELOG.md +0 -337
  72. package/migrations/000_initial_schema.sql +0 -174
  73. package/migrations/001_entity_type_rename.sql +0 -17
  74. package/migrations/002_adapter_state_extend.sql +0 -12
  75. package/migrations/003_session_embeddings.sql +0 -11
  76. package/migrations/004_facts.sql +0 -46
  77. package/migrations/005_sources.sql +0 -31
  78. package/migrations/006_providers.sql +0 -33
  79. package/migrations/007_source_tokens.sql +0 -17
  80. package/migrations/008_fts_rebuild.sql +0 -9
  81. package/migrations/009_session_embedding_chunks.sql +0 -46
  82. package/migrations/010_sources_opencode.sql +0 -30
  83. package/migrations/011_sources_hermes_agent.sql +0 -30
  84. package/migrations/012_sources_aider.sql +0 -30
  85. package/migrations/013_adapter_state_failure_count.sql +0 -12
  86. package/plugin-hermes-agent/README.md +0 -49
  87. package/plugin-hermes-agent/__init__.py +0 -75
  88. package/plugin-hermes-agent/plugin.yaml +0 -15
  89. package/scripts/backfill-citations.mjs +0 -0
  90. package/scripts/build-codex-plugin.mjs +0 -61
  91. package/scripts/deepseek-probe.mjs +0 -67
  92. package/scripts/extract-triples.mjs +0 -207
  93. package/scripts/longmemeval/embedding-cache.ts +0 -77
  94. package/scripts/longmemeval/fetch-dataset.sh +0 -25
  95. package/scripts/longmemeval/run-harness.ts +0 -315
  96. package/scripts/longmemeval/scorer.ts +0 -99
  97. package/scripts/longmemeval/tsconfig.json +0 -9
  98. package/scripts/longmemeval/types.ts +0 -35
  99. package/scripts/nlm-daily-digest.py +0 -239
  100. package/scripts/nlm-daily-digest.sh +0 -28
  101. package/src/cli/classify-parity.ts +0 -257
  102. package/src/cli/launchctl-helpers.ts +0 -49
  103. package/src/cli/nlm.ts +0 -885
  104. package/src/core/actions/actions-log.ts +0 -118
  105. package/src/core/actions/overlay.ts +0 -117
  106. package/src/core/adapters/aider.ts +0 -205
  107. package/src/core/adapters/claude-code.ts +0 -293
  108. package/src/core/adapters/common.ts +0 -54
  109. package/src/core/adapters/from-source.ts +0 -57
  110. package/src/core/adapters/hermes-agent.ts +0 -240
  111. package/src/core/adapters/hermes.ts +0 -277
  112. package/src/core/adapters/jsonl-generic.ts +0 -208
  113. package/src/core/adapters/opencode.ts +0 -281
  114. package/src/core/adapters/pi.ts +0 -264
  115. package/src/core/classifier/prompt.ts +0 -200
  116. package/src/core/dataset/build-dataset.ts +0 -463
  117. package/src/core/embedding/chunk-body.ts +0 -76
  118. package/src/core/embedding/embed-backfill.ts +0 -210
  119. package/src/core/embedding/embed-normalize.ts +0 -135
  120. package/src/core/facts/backfill-facts.ts +0 -254
  121. package/src/core/facts/extract-facts.ts +0 -50
  122. package/src/core/hook/citation-detect.ts +0 -124
  123. package/src/core/hook/cite-memo.ts +0 -68
  124. package/src/core/hook/claude-settings.ts +0 -166
  125. package/src/core/hook/gate.ts +0 -25
  126. package/src/core/hook/hook-log.ts +0 -41
  127. package/src/core/hook/memo-sweep.ts +0 -164
  128. package/src/core/hook/memo.ts +0 -67
  129. package/src/core/hook/pointer-block.ts +0 -26
  130. package/src/core/hook/select.ts +0 -32
  131. package/src/core/hook/transcript.ts +0 -121
  132. package/src/core/ingest/ingest-session.ts +0 -111
  133. package/src/core/providers/provider-models.ts +0 -100
  134. package/src/core/providers/provider-registry.ts +0 -196
  135. package/src/core/recall/citation-log.ts +0 -108
  136. package/src/core/recall/filter.ts +0 -27
  137. package/src/core/recall/index.ts +0 -6
  138. package/src/core/recall/match-fields.ts +0 -40
  139. package/src/core/recall/query-log.ts +0 -149
  140. package/src/core/recall/query-shape.ts +0 -66
  141. package/src/core/recall/recall-service.ts +0 -320
  142. package/src/core/recall/recent-log.ts +0 -59
  143. package/src/core/recall/tokenize.ts +0 -18
  144. package/src/core/recall/useful-scan.ts +0 -336
  145. package/src/core/recall-facts/fact-query-log.ts +0 -150
  146. package/src/core/recall-facts/fact-recall-service.ts +0 -327
  147. package/src/core/scheduler/scan-once.ts +0 -142
  148. package/src/core/scheduler/scheduler.ts +0 -225
  149. package/src/core/sources/source-registry.ts +0 -260
  150. package/src/core/storage/db-restore.ts +0 -133
  151. package/src/core/storage/live-status.ts +0 -45
  152. package/src/core/storage/migrate.ts +0 -72
  153. package/src/core/storage/sqlite-fact-store.ts +0 -304
  154. package/src/core/storage/sqlite-session-store.ts +0 -765
  155. package/src/hook/prompt-recall-hook.ts +0 -174
  156. package/src/hook/session-end-hook.ts +0 -81
  157. package/src/hook/session-start-hook.ts +0 -165
  158. package/src/hook/stop-hook.ts +0 -236
  159. package/src/http/app.ts +0 -1137
  160. package/src/install/claude-code.ts +0 -128
  161. package/src/install/codex.ts +0 -367
  162. package/src/install/hermes-agent.ts +0 -76
  163. package/src/install/hermes.ts +0 -78
  164. package/src/install/ollama.ts +0 -211
  165. package/src/install/setup.ts +0 -368
  166. package/src/llm/classifier-box.ts +0 -64
  167. package/src/llm/deepseek-client.ts +0 -150
  168. package/src/llm/env-autoload.ts +0 -55
  169. package/src/llm/ollama-client.ts +0 -189
  170. package/src/mcp/server.ts +0 -534
  171. package/src/ports/fact-store.ts +0 -102
  172. package/src/ports/llm-client.ts +0 -52
  173. package/src/ports/logger.ts +0 -16
  174. package/src/ports/session-store.ts +0 -45
  175. package/src/ports/transcript-adapter.ts +0 -55
  176. package/src/shared/types.ts +0 -145
  177. package/src/ui/App.tsx +0 -58
  178. package/src/ui/components/PromoteOpenButton.tsx +0 -65
  179. package/src/ui/components/SessionDrawer.tsx +0 -136
  180. package/src/ui/components/SideNav.tsx +0 -162
  181. package/src/ui/components/Skeleton.tsx +0 -107
  182. package/src/ui/index.html +0 -13
  183. package/src/ui/lib/actions.ts +0 -30
  184. package/src/ui/lib/api.ts +0 -92
  185. package/src/ui/lib/dataset.ts +0 -141
  186. package/src/ui/lib/registries.ts +0 -155
  187. package/src/ui/lib/view-settings.ts +0 -41
  188. package/src/ui/main.tsx +0 -15
  189. package/src/ui/pages/Live.tsx +0 -229
  190. package/src/ui/pages/Pulse.tsx +0 -415
  191. package/src/ui/pages/Recall.tsx +0 -190
  192. package/src/ui/pages/River.tsx +0 -308
  193. package/src/ui/pages/Search.tsx +0 -93
  194. package/src/ui/pages/Stub.tsx +0 -9
  195. package/src/ui/pages/Thread.tsx +0 -262
  196. package/src/ui/pages/settings/Classifier.tsx +0 -227
  197. package/src/ui/pages/settings/Data.tsx +0 -190
  198. package/src/ui/pages/settings/Index.tsx +0 -65
  199. package/src/ui/pages/settings/Labels.tsx +0 -224
  200. package/src/ui/pages/settings/Providers.tsx +0 -305
  201. package/src/ui/pages/settings/SettingsSubnav.tsx +0 -28
  202. package/src/ui/pages/settings/Sources.tsx +0 -326
  203. package/src/ui/pages/settings/Views.tsx +0 -96
  204. package/src/ui/styles.css +0 -1766
  205. package/src/ui/tsconfig.json +0 -21
  206. package/src/ui/vite.config.ts +0 -19
  207. package/tests/fixtures/claude_code/short_session.jsonl +0 -2
  208. package/tests/fixtures/claude_code/standard_iso.jsonl +0 -4
  209. package/tests/fixtures/claude_code/tool_heavy.jsonl +0 -8
  210. package/tests/fixtures/claude_code/with_subagent.jsonl +0 -7
  211. package/tests/fixtures/facts.ts +0 -17
  212. package/tests/fixtures/golden-corpus.ts +0 -85
  213. package/tests/fixtures/hermes/paired_request_dump.json +0 -24
  214. package/tests/fixtures/hermes/paired_session.json +0 -23
  215. package/tests/fixtures/hermes/request_dump.json +0 -28
  216. package/tests/fixtures/hermes/session_iso.json +0 -38
  217. package/tests/fixtures/hermes/session_unix.json +0 -38
  218. package/tests/fixtures/hermes/system_only.json +0 -18
  219. package/tests/fixtures/pi/error-connection-abort.jsonl +0 -8
  220. package/tests/fixtures/pi/short-successful.jsonl +0 -5
  221. package/tests/fixtures/pi/with-custom-message.jsonl +0 -6
  222. package/tests/fixtures/sessions.ts +0 -22
  223. package/tests/integration/backfill-facts.test.ts +0 -362
  224. package/tests/integration/citation-explicit.test.ts +0 -111
  225. package/tests/integration/cite-event.test.ts +0 -169
  226. package/tests/integration/cite-memo.test.ts +0 -87
  227. package/tests/integration/db-restore.test.ts +0 -153
  228. package/tests/integration/embed-backfill.test.ts +0 -176
  229. package/tests/integration/fact-supersedence.test.ts +0 -313
  230. package/tests/integration/fts-index.test.ts +0 -60
  231. package/tests/integration/getbyids-sqlite.test.ts +0 -60
  232. package/tests/integration/hermes-agent-hooks.test.ts +0 -248
  233. package/tests/integration/hook-claude-settings.test.ts +0 -205
  234. package/tests/integration/hook-log.test.ts +0 -54
  235. package/tests/integration/hook-memo.test.ts +0 -68
  236. package/tests/integration/hook-pre-compact.test.ts +0 -105
  237. package/tests/integration/hook-subagent-start.test.ts +0 -102
  238. package/tests/integration/http.test.ts +0 -401
  239. package/tests/integration/keyword-search-fts.test.ts +0 -66
  240. package/tests/integration/mcp-recall-logging.test.ts +0 -88
  241. package/tests/integration/mcp.test.ts +0 -248
  242. package/tests/integration/memo-sweep.test.ts +0 -91
  243. package/tests/integration/prompt-recall-hook.test.ts +0 -88
  244. package/tests/integration/provider-registry.test.ts +0 -107
  245. package/tests/integration/recall-golden.test.ts +0 -59
  246. package/tests/integration/recall-sqlite.test.ts +0 -169
  247. package/tests/integration/scheduler.test.ts +0 -391
  248. package/tests/integration/session-end-hook.test.ts +0 -48
  249. package/tests/integration/session-start-hook.test.ts +0 -126
  250. package/tests/integration/source-registry.test.ts +0 -120
  251. package/tests/integration/sqlite-fact-store.test.ts +0 -346
  252. package/tests/integration/stop-hook.test.ts +0 -560
  253. package/tests/integration/wal-checkpoint.test.ts +0 -49
  254. package/tests/unit/cli/launchctl-helpers.test.ts +0 -60
  255. package/tests/unit/core/adapters/aider.test.ts +0 -230
  256. package/tests/unit/core/adapters/claude-code.test.ts +0 -118
  257. package/tests/unit/core/adapters/hermes-agent.test.ts +0 -329
  258. package/tests/unit/core/adapters/hermes.test.ts +0 -81
  259. package/tests/unit/core/adapters/jsonl-generic.test.ts +0 -142
  260. package/tests/unit/core/adapters/opencode.test.ts +0 -354
  261. package/tests/unit/core/adapters/pi.test.ts +0 -110
  262. package/tests/unit/core/classifier/prompt.test.ts +0 -126
  263. package/tests/unit/core/embedding/chunk-body.test.ts +0 -100
  264. package/tests/unit/core/facts/extract-facts.test.ts +0 -117
  265. package/tests/unit/core/filter.test.ts +0 -40
  266. package/tests/unit/core/hook/citation-detect-cite-session.test.ts +0 -96
  267. package/tests/unit/core/hook/citation-detect.test.ts +0 -124
  268. package/tests/unit/core/hook/gate.test.ts +0 -29
  269. package/tests/unit/core/hook/pointer-block.test.ts +0 -22
  270. package/tests/unit/core/hook/select.test.ts +0 -66
  271. package/tests/unit/core/match-fields.test.ts +0 -39
  272. package/tests/unit/core/mcp-cite-session.test.ts +0 -51
  273. package/tests/unit/core/providers/provider-models.test.ts +0 -101
  274. package/tests/unit/core/query-shape.test.ts +0 -92
  275. package/tests/unit/core/recall-facts/fact-recall-service.test.ts +0 -258
  276. package/tests/unit/core/recall-service.test.ts +0 -200
  277. package/tests/unit/core/storage/live-status.test.ts +0 -54
  278. package/tests/unit/core/tokenize.test.ts +0 -32
  279. package/tests/unit/core/useful-scan.test.ts +0 -537
  280. package/tests/unit/llm/embed.test.ts +0 -93
  281. package/tests/unit/llm/ollama-client.test.ts +0 -124
  282. package/tests/unit/scripts/longmemeval-scorer.test.ts +0 -114
  283. package/tsconfig.json +0 -31
  284. package/tsconfig.test.json +0 -11
  285. package/vitest.config.ts +0 -22
@@ -1,260 +0,0 @@
1
- /**
2
- * SourceRegistry — CRUD over the `sources` table.
3
- *
4
- * A "source" is any transcript origin the daemon scans (Claude Code's
5
- * projects dir, Hermes's sessions dir, pi.dev, a user-defined JSONL
6
- * directory, or a webhook).
7
- *
8
- * The three legacy adapters (claude-code, hermes, pi) seed as preset rows
9
- * pointing at fixed `path_or_url` values. The generic JSONL adapter and
10
- * webhook ingest piggy-back on this same table — the scheduler chooses
11
- * which adapter to dispatch by reading `kind`.
12
- *
13
- * See docs/plans/desktop-product.md (Phase 0).
14
- */
15
-
16
- import { randomBytes } from "node:crypto";
17
- import { existsSync } from "node:fs";
18
- import { homedir } from "node:os";
19
- import { join } from "node:path";
20
- import type Database from "better-sqlite3";
21
- import { defaultHistoryFile as defaultAiderHistoryFile } from "../adapters/aider.js";
22
- import { defaultDbPath as defaultHermesAgentDbPath } from "../adapters/hermes-agent.js";
23
- import { defaultDbPath as defaultOpenCodeDbPath } from "../adapters/opencode.js";
24
-
25
- export type SourceKind = "claude-code" | "hermes" | "hermes-agent" | "aider" | "opencode" | "pi" | "jsonl-generic" | "webhook";
26
-
27
- export interface SourceRow {
28
- readonly id: number;
29
- readonly kind: SourceKind;
30
- readonly name: string;
31
- readonly pathOrUrl: string | null;
32
- readonly runtimeLabel: string;
33
- readonly parseConfig: Record<string, unknown>;
34
- readonly enabled: boolean;
35
- /** Only populated on the response from `insert()` for webhook sources.
36
- * Always `null` from `list()` / `get()`. Use `getToken()` inside the daemon. */
37
- readonly token: string | null;
38
- readonly hasToken: boolean;
39
- readonly createdAt: string;
40
- readonly updatedAt: string;
41
- }
42
-
43
- export interface SourceInsert {
44
- readonly kind: SourceKind;
45
- readonly name: string;
46
- readonly pathOrUrl?: string | null;
47
- readonly runtimeLabel: string;
48
- readonly parseConfig?: Record<string, unknown>;
49
- readonly enabled?: boolean;
50
- }
51
-
52
- export interface SourceUpdate {
53
- readonly name?: string;
54
- readonly pathOrUrl?: string | null;
55
- readonly runtimeLabel?: string;
56
- readonly parseConfig?: Record<string, unknown>;
57
- readonly enabled?: boolean;
58
- }
59
-
60
- interface SourceDbRow {
61
- id: number;
62
- kind: string;
63
- name: string;
64
- path_or_url: string | null;
65
- runtime_label: string;
66
- parse_config: string;
67
- enabled: number;
68
- token: string | null;
69
- created_at: string;
70
- updated_at: string;
71
- }
72
-
73
- function rowFromDb(r: SourceDbRow, revealedToken: string | null = null): SourceRow {
74
- let parsed: Record<string, unknown> = {};
75
- try {
76
- parsed = r.parse_config ? (JSON.parse(r.parse_config) as Record<string, unknown>) : {};
77
- } catch {
78
- parsed = {};
79
- }
80
- return {
81
- id: r.id,
82
- kind: r.kind as SourceKind,
83
- name: r.name,
84
- pathOrUrl: r.path_or_url,
85
- runtimeLabel: r.runtime_label,
86
- parseConfig: parsed,
87
- enabled: r.enabled === 1,
88
- token: revealedToken,
89
- hasToken: r.token !== null && r.token.length > 0,
90
- createdAt: r.created_at,
91
- updatedAt: r.updated_at,
92
- };
93
- }
94
-
95
- function mintToken(): string {
96
- return `nlm_${randomBytes(24).toString("hex")}`;
97
- }
98
-
99
- export class SourceRegistry {
100
- constructor(private readonly db: Database.Database) {}
101
-
102
- list(): SourceRow[] {
103
- const rows = this.db.prepare<[], SourceDbRow>(
104
- `SELECT * FROM sources ORDER BY id ASC`,
105
- ).all();
106
- return rows.map((r) => rowFromDb(r));
107
- }
108
-
109
- get(id: number): SourceRow | null {
110
- const row = this.db.prepare<[number], SourceDbRow>(
111
- `SELECT * FROM sources WHERE id = ?`,
112
- ).get(id);
113
- return row ? rowFromDb(row) : null;
114
- }
115
-
116
- getByName(name: string): SourceRow | null {
117
- const row = this.db.prepare<[string], SourceDbRow>(
118
- `SELECT * FROM sources WHERE name = ?`,
119
- ).get(name);
120
- return row ? rowFromDb(row) : null;
121
- }
122
-
123
- insert(input: SourceInsert): SourceRow {
124
- const token = input.kind === "webhook" ? mintToken() : null;
125
- const stmt = this.db.prepare(`
126
- INSERT INTO sources (kind, name, path_or_url, runtime_label, parse_config, enabled, token)
127
- VALUES (@kind, @name, @path_or_url, @runtime_label, @parse_config, @enabled, @token)
128
- `);
129
- const result = stmt.run({
130
- kind: input.kind,
131
- name: input.name,
132
- path_or_url: input.pathOrUrl ?? null,
133
- runtime_label: input.runtimeLabel,
134
- parse_config: JSON.stringify(input.parseConfig ?? {}),
135
- enabled: input.enabled === false ? 0 : 1,
136
- token,
137
- });
138
- const id = Number(result.lastInsertRowid);
139
- const dbRow = this.db.prepare<[number], SourceDbRow>(
140
- `SELECT * FROM sources WHERE id = ?`,
141
- ).get(id);
142
- if (!dbRow) throw new Error(`SourceRegistry.insert: row ${id} not found after insert`);
143
- // Reveal the token on the insert response only — this is the user's
144
- // one chance to copy it. Subsequent list/get redact.
145
- return rowFromDb(dbRow, token);
146
- }
147
-
148
- /** Daemon-internal: resolve a bearer token to its owning source. */
149
- findByToken(token: string): SourceRow | null {
150
- if (!token) return null;
151
- const row = this.db.prepare<[string], SourceDbRow>(
152
- `SELECT * FROM sources WHERE token = ?`,
153
- ).get(token);
154
- return row ? rowFromDb(row) : null;
155
- }
156
-
157
- /** Daemon-internal: returns the raw token. Never echo to HTTP responses. */
158
- getToken(id: number): string | null {
159
- const row = this.db.prepare<[number], SourceDbRow>(
160
- `SELECT token FROM sources WHERE id = ?`,
161
- ).get(id);
162
- return row?.token ?? null;
163
- }
164
-
165
- /** Mint a fresh token, invalidating any previous one. */
166
- regenerateToken(id: number): string | null {
167
- const current = this.get(id);
168
- if (!current || current.kind !== "webhook") return null;
169
- const token = mintToken();
170
- this.db.prepare(`UPDATE sources SET token = ?, updated_at = datetime('now') WHERE id = ?`)
171
- .run(token, id);
172
- return token;
173
- }
174
-
175
- update(id: number, patch: SourceUpdate): SourceRow | null {
176
- const fields: string[] = [];
177
- const params: Record<string, unknown> = { id };
178
- if (patch.name !== undefined) { fields.push("name = @name"); params["name"] = patch.name; }
179
- if (patch.pathOrUrl !== undefined) { fields.push("path_or_url = @path"); params["path"] = patch.pathOrUrl; }
180
- if (patch.runtimeLabel !== undefined) { fields.push("runtime_label = @rt"); params["rt"] = patch.runtimeLabel; }
181
- if (patch.parseConfig !== undefined) { fields.push("parse_config = @cfg"); params["cfg"] = JSON.stringify(patch.parseConfig); }
182
- if (patch.enabled !== undefined) { fields.push("enabled = @en"); params["en"] = patch.enabled ? 1 : 0; }
183
- if (fields.length === 0) return this.get(id);
184
- fields.push("updated_at = datetime('now')");
185
- this.db.prepare(`UPDATE sources SET ${fields.join(", ")} WHERE id = @id`).run(params);
186
- return this.get(id);
187
- }
188
-
189
- delete(id: number): boolean {
190
- const result = this.db.prepare(`DELETE FROM sources WHERE id = ?`).run(id);
191
- return result.changes > 0;
192
- }
193
-
194
- /**
195
- * Seed the three legacy adapter presets on first boot of an empty
196
- * registry. Subsequent boots are no-ops. Respects per-runtime env
197
- * overrides so existing installs don't lose their custom paths.
198
- */
199
- seedDefaults(): void {
200
- const count = this.db.prepare<[], { c: number }>(`SELECT COUNT(*) AS c FROM sources`).get();
201
- if ((count?.c ?? 0) > 0) return;
202
-
203
- const claudePath = process.env["NLM_CLAUDE_PROJECTS_PATH"]
204
- ?? join(homedir(), ".claude", "projects");
205
- const hermesPath = process.env["NLM_HERMES_SESSIONS_PATH"]
206
- ?? join(homedir(), ".hermes", "sessions");
207
- const piPath = process.env["PI_SESSIONS_PATH"]
208
- ?? join(homedir(), ".pi", "agent", "sessions");
209
-
210
- const openCodeDbPath = defaultOpenCodeDbPath();
211
- const hermesAgentDbPath = defaultHermesAgentDbPath();
212
- const aiderHistoryFile = defaultAiderHistoryFile();
213
-
214
- const presets: SourceInsert[] = [
215
- {
216
- kind: "claude-code",
217
- name: "Claude Code",
218
- pathOrUrl: claudePath,
219
- runtimeLabel: "claude-code/1.0",
220
- enabled: existsSync(claudePath),
221
- },
222
- {
223
- kind: "hermes",
224
- name: "Hermes",
225
- pathOrUrl: hermesPath,
226
- runtimeLabel: "hermes/1.0",
227
- enabled: existsSync(hermesPath),
228
- },
229
- {
230
- kind: "hermes-agent",
231
- name: "Hermes Agent",
232
- pathOrUrl: hermesAgentDbPath,
233
- runtimeLabel: "hermes-agent/1.0",
234
- enabled: existsSync(hermesAgentDbPath),
235
- },
236
- {
237
- kind: "aider",
238
- name: "Aider",
239
- pathOrUrl: aiderHistoryFile,
240
- runtimeLabel: "aider/1.0",
241
- enabled: existsSync(aiderHistoryFile),
242
- },
243
- {
244
- kind: "opencode",
245
- name: "OpenCode",
246
- pathOrUrl: openCodeDbPath,
247
- runtimeLabel: "opencode/1.0",
248
- enabled: existsSync(openCodeDbPath),
249
- },
250
- {
251
- kind: "pi",
252
- name: "pi.dev",
253
- pathOrUrl: piPath,
254
- runtimeLabel: "pi/1.0",
255
- enabled: existsSync(piPath),
256
- },
257
- ];
258
- for (const p of presets) this.insert(p);
259
- }
260
- }
@@ -1,133 +0,0 @@
1
- /**
2
- * Backup + restore for the canonical SQLite store.
3
- *
4
- * Backup is live-safe: `VACUUM INTO` takes a read lock and writes a clean,
5
- * defragmented, single-file snapshot — no WAL sidecars, consistent even
6
- * while the daemon is ingesting.
7
- *
8
- * Restore cannot swap a file the daemon holds open. Instead the uploaded
9
- * DB is validated and parked at `<dbPath>.restore-pending`; the next daemon
10
- * boot calls `applyPendingRestore()` before opening the store, moving the
11
- * current DB aside to `<dbPath>.pre-restore-<ts>` and promoting the pending
12
- * file. The desktop shell turns "restart required" into one click.
13
- */
14
-
15
- import Database from "better-sqlite3";
16
- import { existsSync, renameSync, rmSync, statSync } from "node:fs";
17
- import { dirname, join } from "node:path";
18
-
19
- export const PENDING_SUFFIX = ".restore-pending";
20
-
21
- export interface RestoreValidation {
22
- ok: boolean;
23
- error?: string;
24
- sessions?: number;
25
- schemaVersion?: number;
26
- }
27
-
28
- /**
29
- * Validate that `filePath` is a usable canonical store: passes integrity
30
- * check and carries the `sessions` + `schema_migrations` tables. Opened
31
- * read-only and without the sqlite-vec extension — we never touch the
32
- * vec virtual tables here, so the extension isn't needed.
33
- */
34
- export function validateRestoreCandidate(filePath: string): RestoreValidation {
35
- let db: Database.Database | null = null;
36
- try {
37
- db = new Database(filePath, { readonly: true, fileMustExist: true });
38
- const integrity = db.pragma("integrity_check", { simple: true });
39
- if (integrity !== "ok") {
40
- return { ok: false, error: `integrity check failed: ${String(integrity)}` };
41
- }
42
- const tables = db
43
- .prepare<[], { name: string }>(
44
- "SELECT name FROM sqlite_master WHERE type='table' AND name IN ('sessions','schema_migrations')",
45
- )
46
- .all()
47
- .map((r) => r.name);
48
- if (!tables.includes("sessions") || !tables.includes("schema_migrations")) {
49
- return { ok: false, error: "not an nlm-memory database (missing sessions/schema_migrations)" };
50
- }
51
- const sessions = db
52
- .prepare<[], { n: number }>("SELECT COUNT(*) AS n FROM sessions")
53
- .get();
54
- const version = db
55
- .prepare<[], { v: number | null }>("SELECT MAX(version) AS v FROM schema_migrations")
56
- .get();
57
- return {
58
- ok: true,
59
- sessions: sessions?.n ?? 0,
60
- schemaVersion: version?.v ?? 0,
61
- };
62
- } catch (e) {
63
- return { ok: false, error: e instanceof Error ? e.message : String(e) };
64
- } finally {
65
- db?.close();
66
- }
67
- }
68
-
69
- /**
70
- * Park an already-written candidate file as the pending restore for
71
- * `dbPath`. Validates first; on failure the candidate is removed and the
72
- * validation error returned. On success the candidate is renamed to
73
- * `<dbPath>.restore-pending` (same directory, so the rename is atomic).
74
- */
75
- export function stageRestore(dbPath: string, candidatePath: string): RestoreValidation {
76
- const validation = validateRestoreCandidate(candidatePath);
77
- if (!validation.ok) {
78
- rmSync(candidatePath, { force: true });
79
- return validation;
80
- }
81
- const pending = dbPath + PENDING_SUFFIX;
82
- rmSync(pending, { force: true });
83
- renameSync(candidatePath, pending);
84
- return validation;
85
- }
86
-
87
- export interface PendingRestoreResult {
88
- applied: boolean;
89
- archivedTo?: string;
90
- }
91
-
92
- /**
93
- * If a pending restore exists for `dbPath`, promote it: move the current
94
- * DB (and its WAL/SHM sidecars) aside, then rename the pending file into
95
- * place. Call once at boot, before the store is opened.
96
- */
97
- export function applyPendingRestore(dbPath: string): PendingRestoreResult {
98
- const pending = dbPath + PENDING_SUFFIX;
99
- if (!existsSync(pending)) return { applied: false };
100
-
101
- const stamp = new Date().toISOString().replace(/[:.]/g, "-");
102
- const archived = `${dbPath}.pre-restore-${stamp}`;
103
-
104
- if (existsSync(dbPath)) {
105
- renameSync(dbPath, archived);
106
- }
107
- // The archived DB's WAL/SHM belong to it — drop the live sidecars so the
108
- // promoted file isn't paired with a stale WAL.
109
- for (const sidecar of [`${dbPath}-wal`, `${dbPath}-shm`]) {
110
- rmSync(sidecar, { force: true });
111
- }
112
- renameSync(pending, dbPath);
113
-
114
- return existsSync(archived)
115
- ? { applied: true, archivedTo: archived }
116
- : { applied: true };
117
- }
118
-
119
- /**
120
- * Write a live-consistent snapshot of `db` to a fresh file via
121
- * `VACUUM INTO`. The destination must not already exist. Returns the
122
- * snapshot's size in bytes.
123
- */
124
- export function vacuumSnapshot(db: Database.Database, destPath: string): number {
125
- rmSync(destPath, { force: true });
126
- db.prepare("VACUUM INTO ?").run(destPath);
127
- return statSync(destPath).size;
128
- }
129
-
130
- /** Scratch path for a backup snapshot, alongside the DB so rename stays atomic. */
131
- export function snapshotScratchPath(dbPath: string): string {
132
- return join(dirname(dbPath), `.backup-${process.pid}-${Date.now()}.sqlite`);
133
- }
@@ -1,45 +0,0 @@
1
- /**
2
- * live-status — derive the three-tier session status (active / idle / closed)
3
- * from a transcript file's mtime. Mirrors the Python daemon's
4
- * live_session_status(): explicit supersedence wins; missing file → closed;
5
- * otherwise bucketed by age.
6
- *
7
- * Thresholds match Python exactly:
8
- * < 15 min → active
9
- * 15 min – 24 h → idle
10
- * ≥ 24 h → closed
11
- *
12
- * Pure function over filesystem mtime. Tested with synthetic file ages.
13
- */
14
-
15
- import { statSync } from "node:fs";
16
- import { join } from "node:path";
17
- import { homedir } from "node:os";
18
- import type { SessionStatus } from "@shared/types.js";
19
-
20
- const ACTIVE_THRESHOLD_MS = 15 * 60 * 1000;
21
- const IDLE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
22
-
23
- function expandHome(path: string): string {
24
- if (path.startsWith("~/")) return join(homedir(), path.slice(2));
25
- return path;
26
- }
27
-
28
- export function liveSessionStatus(
29
- transcriptPath: string | null,
30
- persistedStatus: SessionStatus | "active" | "closed" | "superseded",
31
- now: number = Date.now(),
32
- ): SessionStatus {
33
- if (persistedStatus === "superseded") return "superseded";
34
- if (!transcriptPath) return "closed";
35
- try {
36
- const expanded = expandHome(transcriptPath);
37
- const st = statSync(expanded);
38
- const ageMs = now - st.mtimeMs;
39
- if (ageMs < ACTIVE_THRESHOLD_MS) return "active";
40
- if (ageMs < IDLE_THRESHOLD_MS) return "idle";
41
- return "closed";
42
- } catch {
43
- return "closed";
44
- }
45
- }
@@ -1,72 +0,0 @@
1
- /**
2
- * Migration runner. Reads versioned *.sql files from a directory, applies any
3
- * whose integer prefix is not yet in schema_migrations, and returns the list
4
- * of newly applied versions. Idempotent: re-running on an up-to-date database
5
- * is a no-op. Each migration file is expected to end with its own
6
- * `INSERT OR IGNORE INTO schema_migrations (...) VALUES (...)`; the runner
7
- * also defensively upserts the row in case a file forgets.
8
- */
9
-
10
- import { readFileSync, readdirSync } from "node:fs";
11
- import { join } from "node:path";
12
- import type Database from "better-sqlite3";
13
-
14
- export interface AppliedMigration {
15
- readonly version: number;
16
- readonly name: string;
17
- }
18
-
19
- const FILE_PATTERN = /^(\d+)_([a-z0-9_-]+)\.sql$/i;
20
-
21
- export function runMigrations(
22
- db: Database.Database,
23
- migrationsDir: string,
24
- ): ReadonlyArray<AppliedMigration> {
25
- db.exec(`
26
- CREATE TABLE IF NOT EXISTS schema_migrations (
27
- version INTEGER PRIMARY KEY,
28
- name TEXT NOT NULL,
29
- applied_at TEXT NOT NULL DEFAULT (datetime('now'))
30
- );
31
- `);
32
-
33
- const applied = new Set<number>(
34
- db
35
- .prepare<[], { version: number }>("SELECT version FROM schema_migrations")
36
- .all()
37
- .map((r) => r.version),
38
- );
39
-
40
- const files = readdirSync(migrationsDir)
41
- .filter((f) => FILE_PATTERN.test(f))
42
- .sort();
43
-
44
- const newlyApplied: AppliedMigration[] = [];
45
- const upsert = db.prepare(
46
- "INSERT OR IGNORE INTO schema_migrations (version, name) VALUES (?, ?)",
47
- );
48
-
49
- for (const file of files) {
50
- const match = FILE_PATTERN.exec(file);
51
- if (!match) continue;
52
- const version = Number(match[1]);
53
- const name = match[2] ?? file;
54
- if (applied.has(version)) continue;
55
-
56
- const sql = readFileSync(join(migrationsDir, file), "utf8");
57
- db.exec("BEGIN");
58
- try {
59
- db.exec(sql);
60
- upsert.run(version, name);
61
- db.exec("COMMIT");
62
- } catch (err) {
63
- db.exec("ROLLBACK");
64
- throw new Error(
65
- `Migration ${file} failed: ${err instanceof Error ? err.message : String(err)}`,
66
- );
67
- }
68
- newlyApplied.push({ version, name });
69
- }
70
-
71
- return newlyApplied;
72
- }