@bcmyguest/openmem 0.0.0-stage → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/.agents/skills/openmem/SKILL.md +34 -0
  2. package/LICENSE +190 -0
  3. package/README.md +140 -2
  4. package/bin/hindsight-project-index +530 -0
  5. package/cli/coding-agent/access-policy.mjs +38 -0
  6. package/cli/coding-agent/agents.mjs +34 -0
  7. package/cli/coding-agent/bank-from-cwd.mjs +140 -0
  8. package/cli/coding-agent/capabilities.mjs +34 -0
  9. package/cli/coding-agent/context-store.mjs +81 -0
  10. package/cli/coding-agent/context-trim.mjs +897 -0
  11. package/cli/coding-agent/file-io.mjs +72 -0
  12. package/cli/coding-agent/hook-harnesses.mjs +88 -0
  13. package/cli/coding-agent/hook-runner.mjs +284 -0
  14. package/cli/coding-agent/install-access.mjs +119 -0
  15. package/cli/coding-agent/install-context-proxy.mjs +333 -0
  16. package/cli/coding-agent/install-rtk.mjs +60 -0
  17. package/cli/coding-agent/install-shim.mjs +201 -0
  18. package/cli/coding-agent/opencode-config.mjs +107 -0
  19. package/cli/coding-agent/opencode-plugin.mjs +132 -0
  20. package/cli/coding-agent/opencode2-plugin/index.js +299 -0
  21. package/cli/coding-agent/recall-frame.mjs +29 -0
  22. package/cli/coding-agent/remove-upstream-access.mjs +122 -0
  23. package/cli/coding-agent/runtime.mjs +40 -0
  24. package/cli/coding-agent/session-db.mjs +626 -0
  25. package/cli/coding-agent/session-facade.mjs +86 -0
  26. package/cli/coding-agent/toml-edit.mjs +161 -0
  27. package/cli/cognee-mcp.mjs +260 -0
  28. package/cli/context-management.mjs +254 -0
  29. package/cli/feed-sync.mjs +27 -0
  30. package/cli/install-coding-agent.mjs +298 -0
  31. package/cli/install-memory-mcp.mjs +426 -0
  32. package/cli/is-main.mjs +18 -0
  33. package/cli/mcp/context-sql-runner.mjs +58 -0
  34. package/cli/mcp/context-store.mjs +670 -0
  35. package/cli/mcp/context.mjs +61 -0
  36. package/cli/mcp/export/cognee-dataset-name.mjs +12 -0
  37. package/cli/mcp/export/cognee.mjs +119 -0
  38. package/cli/mcp/export/hindsight.mjs +48 -0
  39. package/cli/mcp/export/index.mjs +26 -0
  40. package/cli/mcp/export/mem0.mjs +31 -0
  41. package/cli/mcp/knowledge.mjs +333 -0
  42. package/cli/mcp/llm-synthesize.mjs +50 -0
  43. package/cli/mcp/mem0-scope-id.mjs +22 -0
  44. package/cli/mcp/memory-facade.mjs +521 -0
  45. package/cli/mcp/memory-reflect-consolidate.mjs +120 -0
  46. package/cli/mcp/notebooklm-banks.mjs +21 -0
  47. package/cli/mcp/notebooklm-hindsight-rest.mjs +45 -0
  48. package/cli/mcp/notebooklm-provider.mjs +225 -0
  49. package/cli/mcp/pmm-records.mjs +23 -0
  50. package/cli/mcp/portability.mjs +158 -0
  51. package/cli/mcp/stdio.mjs +65 -0
  52. package/cli/mcp/unified-mcp-install.mjs +65 -0
  53. package/cli/mem0-mcp.mjs +108 -0
  54. package/cli/notebooklm-mcp.mjs +236 -0
  55. package/cli/openmem-mcp.mjs +38 -0
  56. package/cli/openmem.mjs +222 -0
  57. package/cli/roots.mjs +85 -0
  58. package/cli/schedule.mjs +176 -0
  59. package/cli/stack.mjs +438 -0
  60. package/cli/switch-memory-provider.mjs +358 -0
  61. package/cli/wiring-report.mjs +125 -0
  62. package/compose.bench.yaml +12 -0
  63. package/compose.yaml +227 -0
  64. package/config/coding-agent.json +13 -0
  65. package/config/hindsight.env.example +26 -0
  66. package/config/index.yml +54 -0
  67. package/config/litellm.lemond.yaml +40 -0
  68. package/config/litellm.ollama.yaml +33 -0
  69. package/config/notebooklm.yml +33 -0
  70. package/config/openmem.toml.example +124 -0
  71. package/docs/OPENMEM.md +23 -0
  72. package/docs/architecture.md +245 -0
  73. package/docs/assets/README.md +10 -0
  74. package/docs/assets/setup-demo.gif +0 -0
  75. package/docs/coding-agent-hooks.md +75 -0
  76. package/docs/coding-agent-scopes.md +22 -0
  77. package/docs/coding-agents.md +71 -0
  78. package/docs/cognee.md +37 -0
  79. package/docs/compatibility.md +80 -0
  80. package/docs/concepts.md +135 -0
  81. package/docs/context-management.md +255 -0
  82. package/docs/context-proxy.md +115 -0
  83. package/docs/context-research.md +215 -0
  84. package/docs/context-store.md +277 -0
  85. package/docs/hindsight.md +77 -0
  86. package/docs/index-sources-roadmap.md +38 -0
  87. package/docs/index-sources.md +39 -0
  88. package/docs/indexer-sinks.md +122 -0
  89. package/docs/indexing.md +57 -0
  90. package/docs/manual-stack-setup.md +74 -0
  91. package/docs/mcp-install-guide.md +4 -0
  92. package/docs/mcp-setup.md +104 -0
  93. package/docs/mem0.md +141 -0
  94. package/docs/memory-portability.md +73 -0
  95. package/docs/memory-providers.md +246 -0
  96. package/docs/meridian-litellm.md +198 -0
  97. package/docs/model-configuration.md +106 -0
  98. package/docs/notebooklm.md +83 -0
  99. package/docs/npx-install.md +172 -0
  100. package/docs/openmem-config.md +194 -0
  101. package/docs/provider-wizard-hooks.md +222 -0
  102. package/docs/quickstart.md +107 -0
  103. package/docs/setup-wizard.md +117 -0
  104. package/docs/sources-feeds.md +39 -0
  105. package/docs/sources-readwise.md +23 -0
  106. package/docs/sources-zotero.md +22 -0
  107. package/glama.json +4 -0
  108. package/indexer/Dockerfile +38 -0
  109. package/indexer/code_outline.py +146 -0
  110. package/indexer/feed_sync.py +107 -0
  111. package/indexer/index_config.py +290 -0
  112. package/indexer/pytest.ini +5 -0
  113. package/indexer/sinks/__init__.py +148 -0
  114. package/indexer/sinks/_http.py +44 -0
  115. package/indexer/sinks/cognee.py +329 -0
  116. package/indexer/sinks/hindsight.py +107 -0
  117. package/indexer/sinks/mem0.py +247 -0
  118. package/indexer/watch.py +240 -0
  119. package/package.json +96 -4
  120. package/providers/_template/compose.yaml +52 -0
  121. package/providers/cognee/OPERATOR.md +82 -0
  122. package/providers/cognee/compose.yaml +89 -0
  123. package/providers/cognee/lib/clack.mjs +157 -0
  124. package/providers/cognee/lib/coding-agent.mjs +26 -0
  125. package/providers/cognee/lib/cognee-mcp-install.mjs +153 -0
  126. package/providers/cognee/lib/config.mjs +98 -0
  127. package/providers/cognee/lib/env-file.mjs +194 -0
  128. package/providers/cognee/lib/env-setup.mjs +83 -0
  129. package/providers/cognee/lib/llm-env.mjs +382 -0
  130. package/providers/cognee/lib/ports.mjs +25 -0
  131. package/providers/cognee/lib/smoke.mjs +246 -0
  132. package/providers/cognee/lib/stack-env.mjs +134 -0
  133. package/providers/cognee/lib/state.mjs +101 -0
  134. package/providers/cognee/lib/summary.mjs +56 -0
  135. package/providers/cognee/lib/validate.mjs +97 -0
  136. package/providers/cognee/manifest.json +56 -0
  137. package/providers/cognee/wizard.mjs +134 -0
  138. package/providers/graphiti/manifest.json +15 -0
  139. package/providers/hindsight/lib/clack.mjs +347 -0
  140. package/providers/hindsight/lib/coding-agent.mjs +204 -0
  141. package/providers/hindsight/lib/config.mjs +79 -0
  142. package/providers/hindsight/lib/env-setup.mjs +72 -0
  143. package/providers/hindsight/lib/litellm-routes.mjs +196 -0
  144. package/providers/hindsight/lib/meridian.mjs +307 -0
  145. package/providers/hindsight/lib/model-backend.mjs +243 -0
  146. package/providers/hindsight/lib/notebooklm-mcp-install.mjs +127 -0
  147. package/providers/hindsight/lib/ollama-compose.mjs +51 -0
  148. package/providers/hindsight/lib/ports.mjs +30 -0
  149. package/providers/hindsight/lib/probe.mjs +54 -0
  150. package/providers/hindsight/lib/stack-env.mjs +125 -0
  151. package/providers/hindsight/lib/state.mjs +80 -0
  152. package/providers/hindsight/lib/summary.mjs +57 -0
  153. package/providers/hindsight/lib/validate.mjs +81 -0
  154. package/providers/hindsight/manifest.json +58 -0
  155. package/providers/hindsight/wizard.mjs +150 -0
  156. package/providers/letta/manifest.json +29 -0
  157. package/providers/mem0/OPERATOR.md +143 -0
  158. package/providers/mem0/compose.yaml +79 -0
  159. package/providers/mem0/lib/coding-agent.mjs +58 -0
  160. package/providers/mem0/lib/config.mjs +45 -0
  161. package/providers/mem0/lib/configure.mjs +201 -0
  162. package/providers/mem0/lib/dotenv.mjs +71 -0
  163. package/providers/mem0/lib/env-file.mjs +110 -0
  164. package/providers/mem0/lib/http.mjs +69 -0
  165. package/providers/mem0/lib/litellm.mjs +457 -0
  166. package/providers/mem0/lib/mcp-install.mjs +165 -0
  167. package/providers/mem0/lib/secret.mjs +30 -0
  168. package/providers/mem0/lib/smoke.mjs +179 -0
  169. package/providers/mem0/lib/stack-env.mjs +255 -0
  170. package/providers/mem0/lib/state.mjs +67 -0
  171. package/providers/mem0/lib/summary.mjs +88 -0
  172. package/providers/mem0/lib/url.mjs +106 -0
  173. package/providers/mem0/lib/validate.mjs +158 -0
  174. package/providers/mem0/manifest.json +47 -0
  175. package/providers/mem0/wizard.mjs +146 -0
  176. package/providers/zep/manifest.json +29 -0
  177. package/proxy/config.yaml +33 -0
  178. package/proxy/openmem_context/__init__.py +6 -0
  179. package/proxy/openmem_context/adapters.py +504 -0
  180. package/proxy/openmem_context/compress.py +53 -0
  181. package/proxy/openmem_context/core.py +606 -0
  182. package/proxy/openmem_context/embed.py +212 -0
  183. package/proxy/openmem_context/recall.py +49 -0
  184. package/proxy/openmem_context/session.py +70 -0
  185. package/proxy/openmem_context/sessiondb.py +276 -0
  186. package/proxy/openmem_context/settings.py +134 -0
  187. package/proxy/openmem_context/store.py +129 -0
  188. package/proxy/openmem_context/tokens.py +123 -0
  189. package/proxy/openmem_context/ttl.py +78 -0
  190. package/proxy/openmem_context_hook.py +34 -0
  191. package/scripts/check-pack.mjs +219 -0
  192. package/scripts/gen-masks.mjs +83 -0
  193. package/scripts/gen-masks.sh +4 -0
  194. package/scripts/promotion-provider-smoke.mjs +34 -0
  195. package/scripts/run-setup.sh +30 -0
  196. package/scripts/setup.mjs +107 -0
  197. package/scripts/setup.sh +5 -0
  198. package/scripts/spin-up.sh +45 -0
  199. package/scripts/wizard.sh +5 -0
  200. package/server.json +34 -0
  201. package/sync/Dockerfile +27 -0
  202. package/sync/memory_backend.py +87 -0
  203. package/sync/notebooklm_sync.py +664 -0
  204. package/wizard/bin/pm.mjs +97 -0
  205. package/wizard/bin/setup.mjs +33 -0
  206. package/wizard/lib/compose-version.mjs +32 -0
  207. package/wizard/lib/compose.mjs +70 -0
  208. package/wizard/lib/context-step.mjs +140 -0
  209. package/wizard/lib/curl.mjs +103 -0
  210. package/wizard/lib/docker-build.mjs +148 -0
  211. package/wizard/lib/exec.mjs +41 -0
  212. package/wizard/lib/indexer.mjs +6 -0
  213. package/wizard/lib/litellm-env.mjs +55 -0
  214. package/wizard/lib/load-machine-env.mjs +4 -0
  215. package/wizard/lib/log.mjs +32 -0
  216. package/wizard/lib/notebooklm-agent-access.mjs +92 -0
  217. package/wizard/lib/notebooklm.mjs +214 -0
  218. package/wizard/lib/parse-args.mjs +87 -0
  219. package/wizard/lib/paths.mjs +39 -0
  220. package/wizard/lib/pm-config.mjs +371 -0
  221. package/wizard/lib/ports.mjs +94 -0
  222. package/wizard/lib/prompts.mjs +85 -0
  223. package/wizard/lib/provider-wizard/helpers.mjs +98 -0
  224. package/wizard/lib/provider-wizard/run-steps.mjs +129 -0
  225. package/wizard/lib/providers/registry.mjs +262 -0
  226. package/wizard/lib/run-clack.mjs +108 -0
  227. package/wizard/lib/run-wizard.mjs +750 -0
  228. package/wizard/lib/validate-setup.mjs +62 -0
  229. package/wizard/lib/wizard-action.mjs +34 -0
  230. package/wizard/lib/wizard-conf.mjs +71 -0
@@ -0,0 +1,426 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Install memory-related MCP servers into ~/.cursor/mcp.json only.
4
+ *
5
+ * openmem-install-mcp [--yes] [--dry-run] [--notebooklm] [--notebooklm-only] [--provider-only]
6
+ * npm run install-mcp -- …
7
+ *
8
+ * Provider: MEMORY_PROVIDER → ~/.config/hindsight/wizard.conf → hindsight.
9
+ * NotebookLM MCP: notebooklm.yml enabled:true and/or --notebooklm (unless --provider-only).
10
+ * Never writes machine paths into the shipped config/coding-agent.json template.
11
+ */
12
+ import fs from "node:fs";
13
+ import os from "node:os";
14
+ import path from "node:path";
15
+ import readline from "node:readline";
16
+ import { fileURLToPath } from "node:url";
17
+ import { isMainModule } from "./is-main.mjs";
18
+ import { setNotebooklmAccessEnabled } from "../wizard/lib/notebooklm-agent-access.mjs";
19
+ import { envValue } from "../wizard/lib/paths.mjs";
20
+ import { installNotebooklmMcp } from "../providers/hindsight/lib/notebooklm-mcp-install.mjs";
21
+ import {
22
+ cogneeMcpEntry,
23
+ installCogneeMcp as installCogneeMcpFromProvider,
24
+ resolveCogneeApiUrl,
25
+ } from "../providers/cognee/lib/cognee-mcp-install.mjs";
26
+ import {
27
+ installMem0Mcp as installMem0McpFromProvider,
28
+ mem0McpEntry,
29
+ mem0McpScriptPath,
30
+ mergeMem0McpServer,
31
+ resolveMem0ApiUrl,
32
+ } from "../providers/mem0/lib/mcp-install.mjs";
33
+ import { installUnifiedMcp } from "./mcp/unified-mcp-install.mjs";
34
+ import { resolveStackRoot } from "./stack.mjs";
35
+
36
+ export { mem0McpEntry, mergeMem0McpServer, resolveMem0ApiUrl };
37
+
38
+ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
39
+ const BIN = "openmem-install-mcp";
40
+ const DEFAULT_PROVIDER = "hindsight";
41
+ /** Sibling MCP servers this tool must never rewrite (merge targets mem0/cognee stay off this list). */
42
+ export const PROTECTED_MCP_SERVERS = Object.freeze(["hindsight", "codegraph", "notebooklm"]);
43
+
44
+ const HELP = `${BIN}: merge MCP servers into ~/.cursor/mcp.json
45
+
46
+ By default merges the openmem unified MCP (memory_add/search/recall/forget on the
47
+ active provider). Also NotebookLM when sync is enabled or --notebooklm. Native mem0/Cognee
48
+ MCP: --provider-only. Skip unified: --no-unified-mcp. Hindsight session memory still uses
49
+ the hindsight-coding-agents plugin.
50
+
51
+ Usage:
52
+ ${BIN} [flags]
53
+ npx openmem install-mcp [flags]
54
+ npm run install-mcp -- [flags] # checkout
55
+
56
+ Flags:
57
+ --yes no confirmation prompts
58
+ --dry-run print actions; write nothing
59
+ --notebooklm force NotebookLM MCP + notebooklmAccess.enabled
60
+ --notebooklm-only only NotebookLM step (skip provider MCP)
61
+ --provider-only only provider MCP (skip NotebookLM)
62
+ --no-unified-mcp skip merging the openmem unified MCP server
63
+ -h, --help this help
64
+
65
+ Provider resolution: MEMORY_PROVIDER, else wizard.conf, else hindsight.
66
+ NotebookLM is also detected when ~/.config/hindsight/notebooklm.yml has enabled: true.
67
+ `;
68
+
69
+ /**
70
+ * @typedef {{
71
+ * yes: boolean,
72
+ * dry: boolean,
73
+ * notebooklm: boolean,
74
+ * notebooklmOnly: boolean,
75
+ * providerOnly: boolean,
76
+ * noUnifiedMcp: boolean,
77
+ * help: boolean,
78
+ * unknown: string[],
79
+ * }} InstallMcpFlags
80
+ */
81
+
82
+ /**
83
+ * @param {string[]} argv
84
+ * @returns {InstallMcpFlags}
85
+ */
86
+ export function parseInstallMcpArgs(argv) {
87
+ /** @type {InstallMcpFlags} */
88
+ const flags = {
89
+ yes: false,
90
+ dry: false,
91
+ notebooklm: false,
92
+ notebooklmOnly: false,
93
+ providerOnly: false,
94
+ noUnifiedMcp: false,
95
+ help: false,
96
+ unknown: [],
97
+ };
98
+ for (const arg of argv) {
99
+ switch (arg) {
100
+ case "--yes":
101
+ case "-y":
102
+ flags.yes = true;
103
+ break;
104
+ case "--dry-run":
105
+ flags.dry = true;
106
+ break;
107
+ case "--notebooklm":
108
+ flags.notebooklm = true;
109
+ break;
110
+ case "--notebooklm-only":
111
+ flags.notebooklmOnly = true;
112
+ break;
113
+ case "--provider-only":
114
+ flags.providerOnly = true;
115
+ break;
116
+ case "--no-unified-mcp":
117
+ flags.noUnifiedMcp = true;
118
+ break;
119
+ case "-h":
120
+ case "--help":
121
+ flags.help = true;
122
+ break;
123
+ default:
124
+ flags.unknown.push(arg);
125
+ break;
126
+ }
127
+ }
128
+ return flags;
129
+ }
130
+
131
+ /**
132
+ * @param {string} home
133
+ * @param {NodeJS.ProcessEnv} [env]
134
+ * @returns {string}
135
+ */
136
+ export function resolveMemoryProvider(home, env = process.env) {
137
+ const fromEnv = String(env.MEMORY_PROVIDER || "").trim();
138
+ if (fromEnv) return fromEnv;
139
+ const conf = path.join(home, ".config/hindsight/wizard.conf");
140
+ const fromConf = envValue(conf, "MEMORY_PROVIDER").trim();
141
+ if (fromConf) return fromConf;
142
+ return DEFAULT_PROVIDER;
143
+ }
144
+
145
+ /**
146
+ * True when notebooklm.yml has a top-level `enabled: true` (same regex as the wizard).
147
+ * @param {string} notebookFile
148
+ */
149
+ export function notebooklmEnabledInYml(notebookFile) {
150
+ if (!fs.existsSync(notebookFile)) return false;
151
+ try {
152
+ return /^enabled:\s*true\b/m.test(fs.readFileSync(notebookFile, "utf8"));
153
+ } catch {
154
+ return false;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * @param {{ home: string, flags: InstallMcpFlags }} opts
160
+ */
161
+ export function shouldInstallNotebooklm({ home, flags }) {
162
+ if (flags.providerOnly) return false;
163
+ if (flags.notebooklm || flags.notebooklmOnly) return true;
164
+ return notebooklmEnabledInYml(path.join(home, ".config/hindsight/notebooklm.yml"));
165
+ }
166
+
167
+ /**
168
+ * Merge one named MCP server; refuse to mutate protected sibling keys.
169
+ * @param {Record<string, any>} mcp
170
+ * @param {string} name
171
+ * @param {Record<string, any>} entry
172
+ * @param {{ protect?: string[] }} [opts]
173
+ * @returns {{ mcp: Record<string, any>, changed: boolean, skipped?: string, refused?: string }}
174
+ */
175
+ export function mergeNamedMcpServer(mcp, name, entry, opts = {}) {
176
+ const protect = opts.protect || PROTECTED_MCP_SERVERS;
177
+ if (protect.includes(name) && name !== "notebooklm") {
178
+ // notebooklm is updated only via mergeNotebooklmMcpServer / installNotebooklmMcp.
179
+ return { mcp, changed: false, refused: `refusing to merge protected MCP name "${name}"` };
180
+ }
181
+ const out = { ...mcp };
182
+ const servers = { ...(mcp.mcpServers && typeof mcp.mcpServers === "object" ? mcp.mcpServers : {}) };
183
+ const before = Object.fromEntries(protect.map((k) => [k, servers[k]]));
184
+ const prev = servers[name];
185
+ const same = prev && JSON.stringify(prev) === JSON.stringify(entry);
186
+ if (same) {
187
+ return { mcp: { ...out, mcpServers: servers }, changed: false, skipped: "already configured" };
188
+ }
189
+ servers[name] = entry;
190
+ for (const k of protect) {
191
+ if (k === name) continue;
192
+ if (before[k] !== undefined && servers[k] !== before[k]) {
193
+ return { mcp, changed: false, refused: `refusing to alter ${k} MCP entry` };
194
+ }
195
+ }
196
+ return { mcp: { ...out, mcpServers: servers }, changed: true };
197
+ }
198
+
199
+ /**
200
+ * Back-compat alias: real mem0 MCP entry (node launcher → npx @yeyuan98/mem0-mcp).
201
+ * Prefer mem0McpEntry({ apiUrl, scriptPath, envFile }) for new code.
202
+ * @param {string} apiUrl
203
+ */
204
+ export function mem0McpPlaceholderEntry(apiUrl) {
205
+ return mem0McpEntry({
206
+ apiUrl,
207
+ scriptPath: mem0McpScriptPath(PKG_ROOT),
208
+ });
209
+ }
210
+
211
+ /**
212
+ * Cognee stdio MCP entry (node + script + COGNEE_API_URL). No API key.
213
+ * @param {string} scriptPath absolute path
214
+ * @param {string} [apiUrl]
215
+ */
216
+ export function cogneeMcpStubEntry(scriptPath, apiUrl) {
217
+ return cogneeMcpEntry(scriptPath, apiUrl);
218
+ }
219
+
220
+ /**
221
+ * @param {string} mcpPath
222
+ * @returns {Record<string, any>}
223
+ */
224
+ export function readMcpJson(mcpPath) {
225
+ if (!fs.existsSync(mcpPath)) return {};
226
+ return JSON.parse(fs.readFileSync(mcpPath, "utf8"));
227
+ }
228
+
229
+ /**
230
+ * @param {string} mcpPath
231
+ * @param {Record<string, any>} mcp
232
+ * @param {{ dry?: boolean }} [opts]
233
+ */
234
+ export function writeMcpJson(mcpPath, mcp, opts = {}) {
235
+ if (opts.dry) return;
236
+ fs.mkdirSync(path.dirname(mcpPath), { recursive: true });
237
+ fs.writeFileSync(mcpPath, `${JSON.stringify(mcp, null, 2)}\n`);
238
+ }
239
+
240
+ /** @param {{ info: Function, ok: Function, warn: Function }} h */
241
+ function makeLogger() {
242
+ return {
243
+ info: (/** @type {string} */ m) => console.log(` ${m}`),
244
+ ok: (/** @type {string} */ m) => console.log(`✓ ${m}`),
245
+ warn: (/** @type {string} */ m) => console.warn(`! ${m}`),
246
+ };
247
+ }
248
+
249
+ /**
250
+ * @param {string} question
251
+ * @returns {Promise<boolean>}
252
+ */
253
+ function confirmProceed(question) {
254
+ if (!process.stdin.isTTY) return Promise.resolve(true);
255
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
256
+ return new Promise((resolve) => {
257
+ rl.question(`${question} [Y/n] `, (answer) => {
258
+ rl.close();
259
+ const a = String(answer || "").trim().toLowerCase();
260
+ resolve(a === "" || a === "y" || a === "yes");
261
+ });
262
+ });
263
+ }
264
+
265
+ /**
266
+ * @param {{
267
+ * home: string,
268
+ * repoRoot: string,
269
+ * stackRoot: string,
270
+ * flags: InstallMcpFlags,
271
+ * env?: NodeJS.ProcessEnv,
272
+ * h?: { info: Function, ok: Function, warn: Function },
273
+ * confirm?: (q: string) => Promise<boolean>,
274
+ * }} opts
275
+ */
276
+ export async function runInstallMemoryMcp(opts) {
277
+ const home = opts.home;
278
+ const repoRoot = opts.repoRoot;
279
+ const stackRoot = opts.stackRoot;
280
+ const flags = opts.flags;
281
+ const env = opts.env || process.env;
282
+ const h = opts.h || makeLogger();
283
+ const ask = opts.confirm || confirmProceed;
284
+
285
+ if (flags.notebooklmOnly && flags.providerOnly) {
286
+ throw new Error("--notebooklm-only and --provider-only are mutually exclusive");
287
+ }
288
+
289
+ const provider = resolveMemoryProvider(home, env);
290
+ const doNotebooklm = shouldInstallNotebooklm({ home, flags });
291
+ const doProvider = !flags.notebooklmOnly;
292
+
293
+ const plan = [];
294
+ if (doNotebooklm) plan.push("notebooklmAccess + notebooklm MCP");
295
+ if (doProvider) {
296
+ if (provider === "mem0") plan.push("mem0 MCP (@yeyuan98/mem0-mcp via cli/mem0-mcp.mjs)");
297
+ else if (provider === "cognee") plan.push("cognee MCP (read-only health/search)");
298
+ else if (provider === "hindsight") plan.push("hindsight note (coding-agents plugin separate)");
299
+ else plan.push(`provider=${provider} (no MCP handler)`);
300
+ }
301
+ if (!flags.noUnifiedMcp) plan.push("openmem unified MCP (memory_add/search/recall/forget)");
302
+
303
+ h.info(`provider=${provider}`);
304
+ h.info(`plan: ${plan.length ? plan.join("; ") : "(nothing)"}`);
305
+
306
+ if (!plan.length) {
307
+ h.info("nothing to install");
308
+ return { provider, didNotebooklm: false, didProvider: false };
309
+ }
310
+
311
+ if (!flags.yes && !flags.dry) {
312
+ const ok = await ask(`Apply MCP changes to ${path.join(home, ".cursor/mcp.json")}?`);
313
+ if (!ok) {
314
+ h.warn("aborted");
315
+ return { provider, didNotebooklm: false, didProvider: false, aborted: true };
316
+ }
317
+ }
318
+
319
+ let didNotebooklm = false;
320
+ if (doNotebooklm) {
321
+ const agentPath = path.join(home, ".hindsight", "coding-agent.json");
322
+ const templatePath = path.join(repoRoot, "config/coding-agent.json");
323
+ if (flags.dry) {
324
+ const planAccess = setNotebooklmAccessEnabled(agentPath, templatePath, true, { dry: true });
325
+ h.info(
326
+ `[dry-run] ${planAccess.action} ${agentPath} with notebooklmAccess.enabled=true; merge notebooklm MCP into ~/.cursor/mcp.json`,
327
+ );
328
+ } else {
329
+ const result = setNotebooklmAccessEnabled(agentPath, templatePath, true);
330
+ h.ok(
331
+ `notebooklmAccess.enabled=true in ${agentPath}` +
332
+ (result.action === "materialized" ? " (copied from symlink → regular file)" : ""),
333
+ );
334
+ await installNotebooklmMcp({ home, repoRoot, agentJson: agentPath, dry: false }, h);
335
+ }
336
+ didNotebooklm = true;
337
+ }
338
+
339
+ let didProvider = false;
340
+ if (doProvider) {
341
+ if (provider === "hindsight") {
342
+ h.info(
343
+ "hindsight coding-agents plugin is separate: run `openmem setup` / `npx @vectorize-io/hindsight-coding-agents install …`",
344
+ );
345
+ didProvider = true;
346
+ } else if (provider === "mem0") {
347
+ await installMem0ProviderMcp({ home, repoRoot, stackRoot, flags, env, h });
348
+ didProvider = true;
349
+ } else if (provider === "cognee") {
350
+ await installCogneeMcp({ home, repoRoot, stackRoot, flags, env, h });
351
+ didProvider = true;
352
+ } else {
353
+ h.warn(`no MCP install handler for provider "${provider}"`);
354
+ }
355
+ }
356
+
357
+ let didUnified = false;
358
+ if (!flags.noUnifiedMcp) {
359
+ const u = installUnifiedMcp(
360
+ { home, repoRoot, stackRoot, provider, dry: flags.dry },
361
+ h,
362
+ );
363
+ didUnified = u.changed || flags.dry;
364
+ }
365
+
366
+ if (!flags.dry && (didNotebooklm || didUnified || (didProvider && provider !== "hindsight"))) {
367
+ h.ok("Restart Cursor Agent so it picks up MCP changes");
368
+ }
369
+
370
+ return { provider, didNotebooklm, didProvider, didUnified };
371
+ }
372
+
373
+ /**
374
+ * @param {{ home: string, stackRoot: string, flags: InstallMcpFlags, env: NodeJS.ProcessEnv, h: any, repoRoot: string }} opts
375
+ */
376
+ async function installMem0ProviderMcp({ home, stackRoot, flags, env, h, repoRoot }) {
377
+ await installMem0McpFromProvider({ home, repoRoot, stackRoot, dry: flags.dry, env }, h);
378
+ }
379
+
380
+ /**
381
+ * @param {{
382
+ * home: string,
383
+ * repoRoot: string,
384
+ * stackRoot: string,
385
+ * flags: InstallMcpFlags,
386
+ * env: NodeJS.ProcessEnv,
387
+ * h: any,
388
+ * }} opts
389
+ */
390
+ async function installCogneeMcp({ home, repoRoot, stackRoot, flags, env, h }) {
391
+ const apiUrl = resolveCogneeApiUrl({ home, stackRoot, repoRoot, env });
392
+ await installCogneeMcpFromProvider(
393
+ { home, repoRoot, dry: flags.dry, cogneeUrl: apiUrl },
394
+ h,
395
+ );
396
+ }
397
+
398
+ async function main(argv) {
399
+ const flags = parseInstallMcpArgs(argv);
400
+ if (flags.help) {
401
+ console.log(HELP);
402
+ process.exit(0);
403
+ }
404
+ if (flags.unknown.length) {
405
+ console.error(`${BIN}: unknown option(s): ${flags.unknown.join(", ")}`);
406
+ console.error(HELP);
407
+ process.exit(2);
408
+ }
409
+
410
+ const home = process.env.HOME || os.homedir();
411
+ const stack = resolveStackRoot({ pkgRoot: PKG_ROOT, env: process.env, home });
412
+ // Code (MCP scripts, templates) comes from the package; .env lives in the stack root.
413
+ const repoRoot = PKG_ROOT;
414
+ const stackRoot = stack.root;
415
+
416
+ try {
417
+ await runInstallMemoryMcp({ home, repoRoot, stackRoot, flags });
418
+ } catch (err) {
419
+ console.error(`${BIN}: ${err instanceof Error ? err.message : err}`);
420
+ process.exit(1);
421
+ }
422
+ }
423
+
424
+ if (isMainModule(import.meta.url)) {
425
+ main(process.argv.slice(2));
426
+ }
@@ -0,0 +1,18 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ /**
6
+ * True when this module is the process entrypoint. Compares realpaths so npm
7
+ * `.bin/*` symlinks into `node_modules/<pkg>/cli/*.mjs` still count as main.
8
+ *
9
+ * @param {string} metaUrl import.meta.url of the caller
10
+ */
11
+ export function isMainModule(metaUrl) {
12
+ if (!process.argv[1]) return false;
13
+ try {
14
+ return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(metaUrl));
15
+ } catch {
16
+ return path.resolve(process.argv[1]) === path.resolve(fileURLToPath(metaUrl));
17
+ }
18
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Child process for smart_compact_sql (cli/mcp/context-store.mjs): reads {file, sql, cap, cellMax} as JSON on
3
+ * stdin, runs the statement on a read-only connection and prints {columns, rows, truncated} or {error}. The parent
4
+ * kills it after its time limit, which also stops a long-running SQLite step.
5
+ */
6
+ import fs from "node:fs";
7
+
8
+ const SQLITE_OK = 0;
9
+ const SQLITE_DENY = 1;
10
+ // sqlite3_set_authorizer action codes allowed for a query: SELECT, READ, FUNCTION, RECURSIVE.
11
+ const ALLOWED_ACTIONS = new Set([21, 20, 31, 33]);
12
+ // SQLITE_PRAGMA; FTS5 reads `PRAGMA data_version` internally on a MATCH. User SQL never reaches here with a
13
+ // PRAGMA (the parent's statement check rejects it), and an assignment carries a value in arg2.
14
+ const SQLITE_PRAGMA = 19;
15
+ const READ_PRAGMAS = new Set(["data_version"]);
16
+
17
+ function cell(value, cellMax) {
18
+ if (value instanceof Uint8Array) return `<blob ${value.byteLength} bytes>`;
19
+ if (typeof value === "bigint") return Number.isSafeInteger(Number(value)) ? Number(value) : String(value);
20
+ if (typeof value === "string" && value.length > cellMax) return `${value.slice(0, cellMax)}…`;
21
+ return value;
22
+ }
23
+
24
+ function run({ file, sql, cap, cellMax }) {
25
+ const { DatabaseSync } = process.getBuiltinModule("node:sqlite");
26
+ const db = new DatabaseSync(file, { readOnly: true });
27
+ try {
28
+ db.exec("PRAGMA busy_timeout = 1000");
29
+ // Node >= 24.10 (setAuthorizer): deny every action a query does not need, on top of the read-only
30
+ // connection and the parent's statement check.
31
+ if (typeof db.setAuthorizer === "function") {
32
+ db.setAuthorizer((action, arg1, arg2) => (ALLOWED_ACTIONS.has(action)
33
+ || (action === SQLITE_PRAGMA && READ_PRAGMAS.has(String(arg1).toLowerCase()) && arg2 === null) ? SQLITE_OK : SQLITE_DENY));
34
+ }
35
+ const rows = [];
36
+ let truncated = false;
37
+ let columns = [];
38
+ for (const row of db.prepare(sql).iterate()) {
39
+ if (rows.length === cap) {
40
+ truncated = true;
41
+ break;
42
+ }
43
+ if (!columns.length) columns = Object.keys(row);
44
+ rows.push(Object.fromEntries(Object.entries(row).map(([k, v]) => [k, cell(v, cellMax)])));
45
+ }
46
+ return { columns, rows, truncated };
47
+ } finally {
48
+ db.close();
49
+ }
50
+ }
51
+
52
+ let out;
53
+ try {
54
+ out = run(JSON.parse(fs.readFileSync(0, "utf8")));
55
+ } catch (err) {
56
+ out = { error: err instanceof Error ? err.message : String(err) };
57
+ }
58
+ process.stdout.write(JSON.stringify(out));