@bcmyguest/openmem 0.0.0-stage → 0.6.3

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 (191) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +136 -2
  3. package/bin/hindsight-project-index +530 -0
  4. package/cli/coding-agent/access-policy.mjs +38 -0
  5. package/cli/coding-agent/agents.mjs +34 -0
  6. package/cli/coding-agent/bank-from-cwd.mjs +140 -0
  7. package/cli/coding-agent/capabilities.mjs +23 -0
  8. package/cli/coding-agent/hook-harnesses.mjs +87 -0
  9. package/cli/coding-agent/hook-runner.mjs +260 -0
  10. package/cli/coding-agent/install-access.mjs +93 -0
  11. package/cli/coding-agent/install-shim.mjs +97 -0
  12. package/cli/coding-agent/opencode-config.mjs +91 -0
  13. package/cli/coding-agent/opencode-plugin.mjs +96 -0
  14. package/cli/coding-agent/runtime.mjs +39 -0
  15. package/cli/coding-agent/session-facade.mjs +62 -0
  16. package/cli/cognee-mcp.mjs +260 -0
  17. package/cli/feed-sync.mjs +11 -0
  18. package/cli/install-coding-agent.mjs +165 -0
  19. package/cli/install-memory-mcp.mjs +426 -0
  20. package/cli/is-main.mjs +18 -0
  21. package/cli/mcp/context.mjs +61 -0
  22. package/cli/mcp/export/cognee-dataset-name.mjs +12 -0
  23. package/cli/mcp/export/cognee.mjs +119 -0
  24. package/cli/mcp/export/hindsight.mjs +48 -0
  25. package/cli/mcp/export/index.mjs +26 -0
  26. package/cli/mcp/export/mem0.mjs +31 -0
  27. package/cli/mcp/llm-synthesize.mjs +50 -0
  28. package/cli/mcp/mem0-scope-id.mjs +22 -0
  29. package/cli/mcp/memory-facade.mjs +455 -0
  30. package/cli/mcp/memory-reflect-consolidate.mjs +120 -0
  31. package/cli/mcp/notebooklm-banks.mjs +21 -0
  32. package/cli/mcp/notebooklm-hindsight-rest.mjs +45 -0
  33. package/cli/mcp/notebooklm-provider.mjs +225 -0
  34. package/cli/mcp/pmm-records.mjs +23 -0
  35. package/cli/mcp/portability.mjs +158 -0
  36. package/cli/mcp/stdio.mjs +65 -0
  37. package/cli/mcp/unified-mcp-install.mjs +65 -0
  38. package/cli/mem0-mcp.mjs +108 -0
  39. package/cli/notebooklm-mcp.mjs +236 -0
  40. package/cli/openmem-mcp.mjs +35 -0
  41. package/cli/openmem.mjs +205 -0
  42. package/cli/roots.mjs +85 -0
  43. package/cli/stack.mjs +436 -0
  44. package/cli/switch-memory-provider.mjs +358 -0
  45. package/cli/wiring-report.mjs +125 -0
  46. package/compose.bench.yaml +12 -0
  47. package/compose.yaml +164 -0
  48. package/config/coding-agent.json +13 -0
  49. package/config/hindsight.env.example +24 -0
  50. package/config/index.yml +54 -0
  51. package/config/litellm.lemond.yaml +40 -0
  52. package/config/litellm.ollama.yaml +33 -0
  53. package/config/notebooklm.yml +33 -0
  54. package/config/openmem.toml.example +62 -0
  55. package/docs/OPENMEM.md +23 -0
  56. package/docs/architecture.md +219 -0
  57. package/docs/assets/README.md +10 -0
  58. package/docs/assets/setup-demo.gif +0 -0
  59. package/docs/coding-agent-hooks.md +51 -0
  60. package/docs/coding-agent-scopes.md +22 -0
  61. package/docs/coding-agents.md +51 -0
  62. package/docs/cognee.md +37 -0
  63. package/docs/concepts.md +112 -0
  64. package/docs/hindsight.md +74 -0
  65. package/docs/index-sources-roadmap.md +38 -0
  66. package/docs/index-sources.md +39 -0
  67. package/docs/indexer-sinks.md +122 -0
  68. package/docs/indexing.md +57 -0
  69. package/docs/manual-stack-setup.md +74 -0
  70. package/docs/mcp-install-guide.md +4 -0
  71. package/docs/mcp-setup.md +101 -0
  72. package/docs/mem0.md +103 -0
  73. package/docs/memory-portability.md +73 -0
  74. package/docs/memory-providers.md +246 -0
  75. package/docs/model-configuration.md +84 -0
  76. package/docs/notebooklm.md +83 -0
  77. package/docs/npx-install.md +170 -0
  78. package/docs/openmem-config.md +194 -0
  79. package/docs/provider-wizard-hooks.md +222 -0
  80. package/docs/quickstart.md +101 -0
  81. package/docs/setup-wizard.md +116 -0
  82. package/docs/sources-feeds.md +52 -0
  83. package/docs/sources-readwise.md +23 -0
  84. package/docs/sources-zotero.md +22 -0
  85. package/glama.json +4 -0
  86. package/indexer/Dockerfile +38 -0
  87. package/indexer/code_outline.py +146 -0
  88. package/indexer/feed_sync.py +107 -0
  89. package/indexer/index_config.py +290 -0
  90. package/indexer/pytest.ini +5 -0
  91. package/indexer/sinks/__init__.py +148 -0
  92. package/indexer/sinks/_http.py +44 -0
  93. package/indexer/sinks/cognee.py +329 -0
  94. package/indexer/sinks/hindsight.py +107 -0
  95. package/indexer/sinks/mem0.py +247 -0
  96. package/indexer/watch.py +240 -0
  97. package/package.json +95 -4
  98. package/providers/_template/compose.yaml +52 -0
  99. package/providers/cognee/OPERATOR.md +82 -0
  100. package/providers/cognee/compose.yaml +89 -0
  101. package/providers/cognee/lib/clack.mjs +157 -0
  102. package/providers/cognee/lib/coding-agent.mjs +26 -0
  103. package/providers/cognee/lib/cognee-mcp-install.mjs +153 -0
  104. package/providers/cognee/lib/config.mjs +98 -0
  105. package/providers/cognee/lib/env-file.mjs +194 -0
  106. package/providers/cognee/lib/env-setup.mjs +83 -0
  107. package/providers/cognee/lib/llm-env.mjs +382 -0
  108. package/providers/cognee/lib/ports.mjs +25 -0
  109. package/providers/cognee/lib/smoke.mjs +246 -0
  110. package/providers/cognee/lib/stack-env.mjs +134 -0
  111. package/providers/cognee/lib/state.mjs +101 -0
  112. package/providers/cognee/lib/summary.mjs +56 -0
  113. package/providers/cognee/lib/validate.mjs +97 -0
  114. package/providers/cognee/manifest.json +56 -0
  115. package/providers/cognee/wizard.mjs +134 -0
  116. package/providers/graphiti/manifest.json +15 -0
  117. package/providers/hindsight/lib/clack.mjs +245 -0
  118. package/providers/hindsight/lib/coding-agent.mjs +204 -0
  119. package/providers/hindsight/lib/config.mjs +73 -0
  120. package/providers/hindsight/lib/env-setup.mjs +71 -0
  121. package/providers/hindsight/lib/litellm-routes.mjs +187 -0
  122. package/providers/hindsight/lib/model-backend.mjs +214 -0
  123. package/providers/hindsight/lib/notebooklm-mcp-install.mjs +127 -0
  124. package/providers/hindsight/lib/ollama-compose.mjs +51 -0
  125. package/providers/hindsight/lib/ports.mjs +30 -0
  126. package/providers/hindsight/lib/probe.mjs +33 -0
  127. package/providers/hindsight/lib/stack-env.mjs +132 -0
  128. package/providers/hindsight/lib/state.mjs +75 -0
  129. package/providers/hindsight/lib/summary.mjs +43 -0
  130. package/providers/hindsight/lib/validate.mjs +75 -0
  131. package/providers/hindsight/manifest.json +58 -0
  132. package/providers/hindsight/wizard.mjs +149 -0
  133. package/providers/letta/manifest.json +29 -0
  134. package/providers/mem0/OPERATOR.md +124 -0
  135. package/providers/mem0/compose.yaml +57 -0
  136. package/providers/mem0/lib/coding-agent.mjs +58 -0
  137. package/providers/mem0/lib/config.mjs +45 -0
  138. package/providers/mem0/lib/configure.mjs +135 -0
  139. package/providers/mem0/lib/dotenv.mjs +71 -0
  140. package/providers/mem0/lib/env-file.mjs +110 -0
  141. package/providers/mem0/lib/http.mjs +69 -0
  142. package/providers/mem0/lib/mcp-install.mjs +165 -0
  143. package/providers/mem0/lib/secret.mjs +30 -0
  144. package/providers/mem0/lib/smoke.mjs +179 -0
  145. package/providers/mem0/lib/stack-env.mjs +216 -0
  146. package/providers/mem0/lib/state.mjs +67 -0
  147. package/providers/mem0/lib/summary.mjs +60 -0
  148. package/providers/mem0/lib/url.mjs +106 -0
  149. package/providers/mem0/lib/validate.mjs +158 -0
  150. package/providers/mem0/manifest.json +47 -0
  151. package/providers/mem0/wizard.mjs +133 -0
  152. package/providers/zep/manifest.json +29 -0
  153. package/scripts/check-pack.mjs +218 -0
  154. package/scripts/gen-masks.mjs +83 -0
  155. package/scripts/gen-masks.sh +4 -0
  156. package/scripts/promotion-provider-smoke.mjs +34 -0
  157. package/scripts/run-setup.sh +30 -0
  158. package/scripts/setup.mjs +94 -0
  159. package/scripts/setup.sh +5 -0
  160. package/scripts/spin-up.sh +45 -0
  161. package/scripts/wizard.sh +5 -0
  162. package/server.json +34 -0
  163. package/skills/openmem/SKILL.md +29 -0
  164. package/sync/Dockerfile +27 -0
  165. package/sync/memory_backend.py +87 -0
  166. package/sync/notebooklm_sync.py +664 -0
  167. package/wizard/bin/pm.mjs +97 -0
  168. package/wizard/bin/setup.mjs +33 -0
  169. package/wizard/lib/compose-version.mjs +32 -0
  170. package/wizard/lib/compose.mjs +70 -0
  171. package/wizard/lib/curl.mjs +103 -0
  172. package/wizard/lib/docker-build.mjs +148 -0
  173. package/wizard/lib/exec.mjs +41 -0
  174. package/wizard/lib/indexer.mjs +6 -0
  175. package/wizard/lib/load-machine-env.mjs +4 -0
  176. package/wizard/lib/log.mjs +32 -0
  177. package/wizard/lib/notebooklm-agent-access.mjs +92 -0
  178. package/wizard/lib/notebooklm.mjs +214 -0
  179. package/wizard/lib/parse-args.mjs +87 -0
  180. package/wizard/lib/paths.mjs +39 -0
  181. package/wizard/lib/pm-config.mjs +163 -0
  182. package/wizard/lib/ports.mjs +94 -0
  183. package/wizard/lib/prompts.mjs +57 -0
  184. package/wizard/lib/provider-wizard/helpers.mjs +98 -0
  185. package/wizard/lib/provider-wizard/run-steps.mjs +129 -0
  186. package/wizard/lib/providers/registry.mjs +262 -0
  187. package/wizard/lib/run-clack.mjs +108 -0
  188. package/wizard/lib/run-wizard.mjs +755 -0
  189. package/wizard/lib/validate-setup.mjs +62 -0
  190. package/wizard/lib/wizard-action.mjs +34 -0
  191. package/wizard/lib/wizard-conf.mjs +71 -0
@@ -0,0 +1,358 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Switch the active memory provider (wizard.conf + optional stack restart + MCP).
4
+ *
5
+ * npx openmem switch <hindsight|mem0|cognee> [--yes] [--dry-run] [--no-stack] [--no-mcp]
6
+ * openmem-switch-provider …
7
+ * npx . switch <id> … / ./pm switch <id> …
8
+ *
9
+ * Does not migrate data between providers. No secrets in logs.
10
+ */
11
+ import { spawnSync } from "node:child_process";
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 { resolveMemoryProvider } from "./install-memory-mcp.mjs";
19
+ import { resolveStackRoot } from "./stack.mjs";
20
+ import { envValue } from "../wizard/lib/paths.mjs";
21
+ import { updatePmConfigProvider, userConfigPath } from "../wizard/lib/pm-config.mjs";
22
+ import { listSelectableProviders, loadManifest } from "../wizard/lib/providers/registry.mjs";
23
+ import { upsertWizardConf } from "../wizard/lib/wizard-conf.mjs";
24
+ import { resolveMem0EnvFilePath, resolveMem0ApiUrl } from "../providers/mem0/lib/mcp-install.mjs";
25
+ import { envFileProblem } from "../providers/mem0/lib/state.mjs";
26
+ import { urlProblem } from "../providers/mem0/lib/url.mjs";
27
+
28
+ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
29
+ const BIN = "openmem-switch-provider";
30
+
31
+ const HELP = `${BIN}: switch the active memory provider
32
+
33
+ Usage:
34
+ ${BIN} <provider> [flags]
35
+ npm run switch-provider -- <provider> [flags]
36
+ ./pm switch <provider> [flags]
37
+
38
+ Providers (selectable): hindsight, mem0, cognee (see providers/*/manifest.json).
39
+
40
+ Flags:
41
+ --yes no confirmation prompts
42
+ --dry-run print actions; write nothing / skip stack+MCP side effects
43
+ --no-stack skip compose down/up for the new provider
44
+ --no-mcp skip install-mcp --provider-only
45
+ -h, --help this help
46
+
47
+ Current provider: MEMORY_PROVIDER, else ~/.config/hindsight/wizard.conf, else hindsight.
48
+ No automatic data migration between providers.
49
+ `;
50
+
51
+ /**
52
+ * @typedef {{
53
+ * yes: boolean,
54
+ * dry: boolean,
55
+ * noStack: boolean,
56
+ * noMcp: boolean,
57
+ * help: boolean,
58
+ * provider: string,
59
+ * unknown: string[],
60
+ * }} SwitchProviderFlags
61
+ */
62
+
63
+ /**
64
+ * @param {string[]} argv
65
+ * @returns {SwitchProviderFlags}
66
+ */
67
+ export function parseSwitchProviderArgs(argv) {
68
+ /** @type {SwitchProviderFlags} */
69
+ const flags = {
70
+ yes: false,
71
+ dry: false,
72
+ noStack: false,
73
+ noMcp: false,
74
+ help: false,
75
+ provider: "",
76
+ unknown: [],
77
+ };
78
+ for (const arg of argv) {
79
+ switch (arg) {
80
+ case "--yes":
81
+ case "-y":
82
+ flags.yes = true;
83
+ break;
84
+ case "--dry-run":
85
+ flags.dry = true;
86
+ break;
87
+ case "--no-stack":
88
+ flags.noStack = true;
89
+ break;
90
+ case "--no-mcp":
91
+ flags.noMcp = true;
92
+ break;
93
+ case "-h":
94
+ case "--help":
95
+ flags.help = true;
96
+ break;
97
+ default:
98
+ if (arg.startsWith("-")) flags.unknown.push(arg);
99
+ else if (!flags.provider) flags.provider = arg;
100
+ else flags.unknown.push(arg);
101
+ break;
102
+ }
103
+ }
104
+ return flags;
105
+ }
106
+
107
+ /** @returns {string[]} */
108
+ export function selectableProviderIds() {
109
+ return listSelectableProviders().map((m) => m.id);
110
+ }
111
+
112
+ export { updatePmConfigProvider };
113
+
114
+ /**
115
+ * mem0 must have a usable API URL (and a valid env-file path). Never logs secrets.
116
+ * @param {{ home: string, stackRoot: string, env?: NodeJS.ProcessEnv }} opts
117
+ */
118
+ export function ensureMem0SwitchReady(opts) {
119
+ const env = opts.env || process.env;
120
+ const apiUrl = resolveMem0ApiUrl({ home: opts.home, stackRoot: opts.stackRoot, env });
121
+ const issue = urlProblem(apiUrl);
122
+ if (issue) {
123
+ throw new Error(`mem0 API URL: ${issue} — see providers/mem0/OPERATOR.md`);
124
+ }
125
+ const envFile = resolveMem0EnvFilePath({ home: opts.home, stackRoot: opts.stackRoot, env });
126
+ const fileIssue = envFileProblem(envFile);
127
+ if (fileIssue) {
128
+ throw new Error(`mem0 env file: ${fileIssue} — see providers/mem0/OPERATOR.md`);
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Cognee needs a sensible COGNEE_PORT (env / stack .env / wizard.conf / default 8766).
134
+ * @param {{ home: string, stackRoot: string, env?: NodeJS.ProcessEnv }} opts
135
+ */
136
+ export function ensureCogneeSwitchReady(opts) {
137
+ const env = opts.env || process.env;
138
+ const stackEnv = path.join(opts.stackRoot, ".env");
139
+ const wizardConf = path.join(opts.home, ".config/hindsight/wizard.conf");
140
+ const candidates = [
141
+ env.COGNEE_PORT,
142
+ envValue(stackEnv, "COGNEE_PORT"),
143
+ envValue(wizardConf, "COGNEE_PORT"),
144
+ "8766",
145
+ ];
146
+ const port = candidates.map((v) => String(v || "").trim()).find(Boolean) || "";
147
+ if (!/^\d+$/.test(port) || Number(port) < 1 || Number(port) > 65535) {
148
+ throw new Error(
149
+ `COGNEE_PORT must be 1–65535 (got ${JSON.stringify(port)}) — see providers/cognee/OPERATOR.md`,
150
+ );
151
+ }
152
+ }
153
+
154
+ /** @param {{ info: Function, ok: Function, warn: Function }} h */
155
+ function makeLogger() {
156
+ return {
157
+ info: (/** @type {string} */ m) => console.log(` ${m}`),
158
+ ok: (/** @type {string} */ m) => console.log(`✓ ${m}`),
159
+ warn: (/** @type {string} */ m) => console.warn(`! ${m}`),
160
+ };
161
+ }
162
+
163
+ /**
164
+ * @param {string} question
165
+ * @returns {Promise<boolean>}
166
+ */
167
+ function confirmProceed(question) {
168
+ if (!process.stdin.isTTY) return Promise.resolve(true);
169
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
170
+ return new Promise((resolve) => {
171
+ rl.question(`${question} [Y/n] `, (answer) => {
172
+ rl.close();
173
+ const a = String(answer || "").trim().toLowerCase();
174
+ resolve(a === "" || a === "y" || a === "yes");
175
+ });
176
+ });
177
+ }
178
+
179
+ /**
180
+ * @param {string} script relative to pkgRoot
181
+ * @param {string[]} args
182
+ * @param {{ pkgRoot: string, stackRoot: string, env: NodeJS.ProcessEnv, dry: boolean, h: any, label: string }} opts
183
+ */
184
+ function spawnNodeScript(script, args, opts) {
185
+ const { pkgRoot, stackRoot, env, dry, h, label } = opts;
186
+ const abs = path.join(pkgRoot, script);
187
+ if (dry) {
188
+ h.info(`[dry-run] ${label}: node ${script} ${args.join(" ")}`);
189
+ return;
190
+ }
191
+ const r = spawnSync(process.execPath, [abs, ...args], {
192
+ cwd: stackRoot,
193
+ stdio: "inherit",
194
+ env,
195
+ });
196
+ if (r.error) throw new Error(`${label}: ${r.error.message}`);
197
+ if (r.status !== 0) throw new Error(`${label} exited ${r.status ?? "null"}`);
198
+ }
199
+
200
+ /**
201
+ * @param {{
202
+ * home: string,
203
+ * repoRoot: string,
204
+ * stackRoot: string,
205
+ * flags: SwitchProviderFlags,
206
+ * env?: NodeJS.ProcessEnv,
207
+ * h?: { info: Function, ok: Function, warn: Function },
208
+ * confirm?: (q: string) => Promise<boolean>,
209
+ * }} opts
210
+ */
211
+ export async function runSwitchMemoryProvider(opts) {
212
+ const home = opts.home;
213
+ const stackRoot = opts.stackRoot;
214
+ const repoRoot = opts.repoRoot;
215
+ const flags = opts.flags;
216
+ const env = opts.env || process.env;
217
+ const h = opts.h || makeLogger();
218
+ const ask = opts.confirm || confirmProceed;
219
+
220
+ const target = String(flags.provider || "").trim();
221
+ if (!target) {
222
+ throw new Error(`provider id required\n\n${HELP}`);
223
+ }
224
+
225
+ const selectable = selectableProviderIds();
226
+ if (!selectable.includes(target)) {
227
+ throw new Error(
228
+ `unknown or non-selectable provider "${target}" (want one of: ${selectable.join(", ")})`,
229
+ );
230
+ }
231
+
232
+ const current = resolveMemoryProvider(home, env);
233
+ h.info(`current=${current} → target=${target}`);
234
+
235
+ if (current === target) {
236
+ h.ok(`provider "${target}" is already active`);
237
+ return { current, target, same: true, aborted: false };
238
+ }
239
+
240
+ h.warn("Switching providers does not migrate memories or banks between backends.");
241
+
242
+ if (!flags.yes && !flags.dry) {
243
+ const ok = await ask(`Switch memory provider from ${current} to ${target}?`);
244
+ if (!ok) {
245
+ h.warn("aborted");
246
+ return { current, target, same: false, aborted: true };
247
+ }
248
+ }
249
+
250
+ const wizardConf = path.join(home, ".config/hindsight/wizard.conf");
251
+ const hsDir = path.join(home, ".config/hindsight");
252
+ const manifest = loadManifest(target);
253
+ const ctx = {
254
+ home,
255
+ hsDir,
256
+ wizardConf,
257
+ dry: flags.dry,
258
+ memoryProvider: manifest || { id: target },
259
+ };
260
+
261
+ if (flags.dry) {
262
+ h.info(`[dry-run] upsert MEMORY_PROVIDER=${target} in ${wizardConf}`);
263
+ } else {
264
+ upsertWizardConf(ctx, {});
265
+ h.ok(`MEMORY_PROVIDER=${target} in ${wizardConf}`);
266
+ }
267
+
268
+ const pmResult = updatePmConfigProvider(home, target, { dry: flags.dry });
269
+ if (pmResult === "updated") h.ok(`memory.provider=${target} in ${userConfigPath(home)}`);
270
+ else if (pmResult === "dry-run") h.info(`[dry-run] would update memory.provider in ${userConfigPath(home)}`);
271
+ else h.info(`${userConfigPath(home)} absent — skipped`);
272
+
273
+ const childEnv = {
274
+ ...env,
275
+ MEMORY_PROVIDER: target,
276
+ PM_STACK_ROOT: stackRoot,
277
+ PM_PACKAGE_ROOT: repoRoot,
278
+ };
279
+
280
+ if (!flags.noStack) {
281
+ if (target === "mem0") {
282
+ ensureMem0SwitchReady({ home, stackRoot, env: childEnv });
283
+ h.ok("mem0 URL/env-file path look usable");
284
+ } else if (target === "cognee") {
285
+ ensureCogneeSwitchReady({ home, stackRoot, env: childEnv });
286
+ h.ok("cognee port looks usable");
287
+ }
288
+
289
+ spawnNodeScript(
290
+ "wizard/bin/setup.mjs",
291
+ ["--down", "--yes"],
292
+ { pkgRoot: repoRoot, stackRoot, env: childEnv, dry: flags.dry, h, label: "setup --down" },
293
+ );
294
+ spawnNodeScript(
295
+ "wizard/bin/setup.mjs",
296
+ ["--up", "--yes"],
297
+ { pkgRoot: repoRoot, stackRoot, env: childEnv, dry: flags.dry, h, label: "setup --up" },
298
+ );
299
+ if (!flags.dry) h.ok(`stack restarted for provider=${target}`);
300
+ } else {
301
+ h.info("skipped stack restart (--no-stack)");
302
+ }
303
+
304
+ if (!flags.noMcp) {
305
+ const mcpArgs = ["--yes", "--provider-only"];
306
+ if (flags.dry) mcpArgs.push("--dry-run");
307
+ spawnNodeScript("cli/install-memory-mcp.mjs", mcpArgs, {
308
+ pkgRoot: repoRoot,
309
+ stackRoot,
310
+ env: childEnv,
311
+ dry: false, // dry-run is passed through to install-mcp
312
+ h,
313
+ label: "install-mcp --provider-only",
314
+ });
315
+ } else {
316
+ h.info("skipped MCP install (--no-mcp)");
317
+ }
318
+
319
+ h.ok("Restart Cursor Agent so it picks up the new provider / MCP wiring");
320
+ h.info("Then run: ./pm doctor");
321
+
322
+ return { current, target, same: false, aborted: false };
323
+ }
324
+
325
+ async function main(argv) {
326
+ const flags = parseSwitchProviderArgs(argv);
327
+ if (flags.help) {
328
+ console.log(HELP);
329
+ process.exit(0);
330
+ }
331
+ if (flags.unknown.length) {
332
+ console.error(`${BIN}: unknown option(s): ${flags.unknown.join(", ")}`);
333
+ console.error(HELP);
334
+ process.exit(2);
335
+ }
336
+ if (!flags.provider) {
337
+ console.error(`${BIN}: provider id required`);
338
+ console.error(HELP);
339
+ process.exit(2);
340
+ }
341
+
342
+ const home = process.env.HOME || os.homedir();
343
+ const stack = resolveStackRoot({ pkgRoot: PKG_ROOT, env: process.env, home });
344
+ const repoRoot = PKG_ROOT;
345
+ const stackRoot = stack.root;
346
+
347
+ try {
348
+ const result = await runSwitchMemoryProvider({ home, repoRoot, stackRoot, flags });
349
+ if (result.aborted) process.exit(1);
350
+ } catch (err) {
351
+ console.error(`${BIN}: ${err instanceof Error ? err.message : err}`);
352
+ process.exit(1);
353
+ }
354
+ }
355
+
356
+ if (isMainModule(import.meta.url)) {
357
+ main(process.argv.slice(2));
358
+ }
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ /** Agent wiring report: provider, API URLs, MCP entries vs expectations. */
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { isMainModule } from "./is-main.mjs";
7
+ import { PROVIDER_CAPABILITIES } from "./coding-agent/capabilities.mjs";
8
+ import { resolveMcpContext } from "./mcp/context.mjs";
9
+ import { resolveMemoryProvider, shouldInstallNotebooklm } from "./install-memory-mcp.mjs";
10
+ import { resolveStackRoot } from "./stack.mjs";
11
+
12
+ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
13
+
14
+ /**
15
+ * @param {{ home: string, stackRoot: string, repoRoot: string, env?: NodeJS.ProcessEnv }} opts
16
+ */
17
+ export function buildWiringReport(opts) {
18
+ const home = opts.home;
19
+ const env = opts.env || process.env;
20
+ const provider = resolveMemoryProvider(home, env);
21
+ const ctx = resolveMcpContext({ ...opts, home, env });
22
+ const mcpPath = path.join(home, ".cursor/mcp.json");
23
+ /** @type {Record<string, unknown>} */
24
+ let mcp = {};
25
+ if (fs.existsSync(mcpPath)) {
26
+ try {
27
+ mcp = JSON.parse(fs.readFileSync(mcpPath, "utf8"));
28
+ } catch {
29
+ mcp = { _parseError: true };
30
+ }
31
+ }
32
+ const servers = mcp.mcpServers && typeof mcp.mcpServers === "object" ? mcp.mcpServers : {};
33
+ const names = Object.keys(servers);
34
+ const expectUnified = true;
35
+ const expectProviderNative = provider === "mem0" || provider === "cognee";
36
+ const nlm = shouldInstallNotebooklm({
37
+ home,
38
+ flags: { notebooklm: false, notebooklmOnly: false, providerOnly: false },
39
+ });
40
+ const issues = [];
41
+ if (expectUnified && !servers["openmem"]) {
42
+ issues.push("missing unified MCP server `openmem` in ~/.cursor/mcp.json (run install-mcp)");
43
+ }
44
+ if (expectProviderNative && !servers[provider]) {
45
+ issues.push(`active provider is ${provider} but mcp.json has no \`${provider}\` entry (optional: install-mcp --provider-only)`);
46
+ }
47
+ if (provider === "hindsight" && servers.mem0) {
48
+ issues.push("MEMORY_PROVIDER is hindsight but mcp.json still has mem0 (stale after switch?)");
49
+ }
50
+ if (provider === "mem0" && servers.hindsight && servers.hindsight.command !== "keep") {
51
+ /* hindsight key often kept intentionally */
52
+ }
53
+ const manifestPath = path.join(opts.repoRoot, "providers", provider, "manifest.json");
54
+ let sinkDriver = null;
55
+ if (fs.existsSync(manifestPath)) {
56
+ try {
57
+ sinkDriver = JSON.parse(fs.readFileSync(manifestPath, "utf8"))?.indexer?.sinkDriver ?? null;
58
+ } catch {
59
+ sinkDriver = null;
60
+ }
61
+ }
62
+ return {
63
+ memory_provider: provider,
64
+ api: {
65
+ hindsightUrl: ctx.hindsightUrl ?? null,
66
+ mem0BaseUrl: ctx.mem0?.baseUrl ?? null,
67
+ cogneeBaseUrl: ctx.cognee?.baseUrl ?? null,
68
+ },
69
+ indexer: { sink_driver: sinkDriver },
70
+ mcp: {
71
+ path: mcpPath,
72
+ servers: names,
73
+ notebooklm_recommended: nlm,
74
+ has_notebooklm: Boolean(servers.notebooklm),
75
+ },
76
+ coding_agent: {
77
+ hindsight_plugin: provider === "hindsight" ? "@vectorize-io/hindsight-coding-agents" : null,
78
+ non_hindsight_session:
79
+ provider !== "hindsight" ? "openmem session shim (install-coding-agent) + memory_* MCP" : null,
80
+ session_capabilities: PROVIDER_CAPABILITIES[provider] ?? {},
81
+ },
82
+ issues,
83
+ ok: issues.length === 0,
84
+ };
85
+ }
86
+
87
+ function main() {
88
+ const json = process.argv.includes("--json");
89
+ const home = process.env.HOME || "";
90
+ const sr = resolveStackRoot({ pkgRoot: PKG_ROOT });
91
+ const report = buildWiringReport({ home, stackRoot: sr.root, repoRoot: PKG_ROOT, env: process.env });
92
+ if (json) {
93
+ console.log(JSON.stringify(report, null, 2));
94
+ process.exit(report.ok ? 0 : 1);
95
+ }
96
+ console.log(`Memory provider: ${report.memory_provider}`);
97
+ if (report.api.hindsightUrl) console.log(` Hindsight: ${report.api.hindsightUrl}`);
98
+ if (report.api.mem0BaseUrl) console.log(` mem0: ${report.api.mem0BaseUrl}`);
99
+ if (report.api.cogneeBaseUrl) console.log(` Cognee: ${report.api.cogneeBaseUrl}`);
100
+ if (report.indexer.sink_driver) console.log(` Indexer sink: ${report.indexer.sink_driver}`);
101
+ console.log(`MCP (${report.mcp.path}): ${report.mcp.servers.join(", ") || "(none)"}`);
102
+ if (report.mcp.notebooklm_recommended && !report.mcp.has_notebooklm) {
103
+ console.log(" NotebookLM sync enabled but notebooklm MCP not in mcp.json");
104
+ }
105
+ if (report.coding_agent.hindsight_plugin) {
106
+ console.log(`Session plugin: ${report.coding_agent.hindsight_plugin}`);
107
+ } else if (report.coding_agent.non_hindsight_session) {
108
+ console.log(`Session memory: ${report.coding_agent.non_hindsight_session}`);
109
+ }
110
+ const caps = report.coding_agent.session_capabilities;
111
+ if (caps && Object.keys(caps).length) {
112
+ const on = Object.entries(caps)
113
+ .filter(([, v]) => v)
114
+ .map(([k]) => k);
115
+ const off = Object.entries(caps)
116
+ .filter(([, v]) => !v)
117
+ .map(([k]) => k);
118
+ if (on.length) console.log(` Session features: ${on.join(", ")}`);
119
+ if (off.length) console.log(` Unavailable for ${report.memory_provider}: ${off.join(", ")}`);
120
+ }
121
+ for (const i of report.issues) console.log(`Issue: ${i}`);
122
+ process.exit(report.ok ? 0 : 1);
123
+ }
124
+
125
+ if (isMainModule(import.meta.url)) main();
@@ -0,0 +1,12 @@
1
+ # Bench overlay: also publish Hindsight on the docker0 gateway so bridge-network
2
+ # fabro sandboxes reach it via host.docker.internal:8888. Loopback binding is kept.
3
+ # Needs the hindsight profile (COMPOSE_PROFILES=hindsight in .env, or add --profile hindsight).
4
+ # Not applied. Apply by recreating only the hindsight service:
5
+ # cd ~/openmem && docker compose -f compose.yaml -f compose.override.yaml -f compose.bench.yaml up -d hindsight
6
+ # The explicit -f list keeps compose.override.yaml, which compose skips when -f is given.
7
+ services:
8
+ hindsight:
9
+ ports: !override
10
+ - "127.0.0.1:${HINDSIGHT_PORT:-8888}:8888"
11
+ - "127.0.0.1:${HINDSIGHT_CP_PORT:-9999}:9999"
12
+ - "172.17.0.1:${HINDSIGHT_PORT:-8888}:8888"
package/compose.yaml ADDED
@@ -0,0 +1,164 @@
1
+ name: openmem
2
+
3
+ # Memory provider stack (Hindsight by default) and the project/Obsidian indexer.
4
+ # Set HINDSIGHT_ENV_FILE to use an env file other than ~/.config/hindsight/hindsight.env.
5
+ #
6
+ # Provider selection is by compose profile:
7
+ # - litellm and hindsight carry the profile "hindsight" (and "notebooklm", which needs them).
8
+ # - indexer and indexer-state-init have no profile and no hard provider dependency; they start for every provider.
9
+ # - Another provider ships providers/<id>/compose.yaml (see providers/_template/compose.yaml) with its own
10
+ # profile, and layers over this file: docker compose -f compose.yaml -f providers/<id>/compose.yaml --profile <id> up -d
11
+ # Compose starts no profiled service unless a profile is enabled, so the plain `docker compose up -d` of the README
12
+ # needs COMPOSE_PROFILES=hindsight in .env (or --profile hindsight). Note that a --profile flag replaces
13
+ # COMPOSE_PROFILES rather than adding to it; name every profile you want on the command line.
14
+
15
+ # Shared build for the indexer and its state-volume init step.
16
+ x-indexer-build: &indexer-build
17
+ build:
18
+ context: .
19
+ dockerfile: indexer/Dockerfile
20
+
21
+ services:
22
+ # OpenAI-compatible proxy in front of the model server. Hindsight calls it at http://litellm:4000/v1 with the
23
+ # fixed aliases memory-retain, memory-reflect, memory-consolidate. Routes live in LITELLM_CONFIG (set by scripts/setup.sh).
24
+ # Internal network only: nothing is published to the host.
25
+ litellm:
26
+ profiles: ["hindsight", "notebooklm"]
27
+ image: ghcr.io/berriai/litellm:v1.105.1
28
+ command: ["--config", "/app/config.yaml", "--port", "4000"]
29
+ env_file: ${LITELLM_ENV_FILE:-${HOME}/.config/hindsight/litellm.env}
30
+ volumes:
31
+ - ${LITELLM_CONFIG:?run scripts/setup.sh}:/app/config.yaml:ro
32
+ extra_hosts:
33
+ - "host.docker.internal:host-gateway"
34
+ restart: unless-stopped
35
+ healthcheck:
36
+ test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:4000/health/liveliness', timeout=5)"]
37
+ interval: 15s
38
+ timeout: 10s
39
+ retries: 10
40
+ start_period: 30s
41
+
42
+ hindsight:
43
+ profiles: ["hindsight", "notebooklm"]
44
+ depends_on:
45
+ litellm:
46
+ condition: service_healthy
47
+ # Pinned to the digest running before the pin (org.opencontainers.image.version 1.0.0). Bump deliberately.
48
+ image: ghcr.io/vectorize-io/hindsight@sha256:a88678c038b0e4340822b02a5efc566c56d8176b5bffa731bc433fd37f978e4c
49
+ # Embedded Postgres needs more than Docker's 64 MB default /dev/shm.
50
+ shm_size: 1g
51
+ env_file: ${HINDSIGHT_ENV_FILE:-${HOME}/.config/hindsight/hindsight.env}
52
+ ports:
53
+ # Host ports from .env (HINDSIGHT_PORT, HINDSIGHT_CP_PORT); defaults 8888 and 9999.
54
+ - "127.0.0.1:${HINDSIGHT_PORT:-8888}:8888"
55
+ - "127.0.0.1:${HINDSIGHT_CP_PORT:-9999}:9999"
56
+ extra_hosts:
57
+ - "host.docker.internal:host-gateway"
58
+ volumes:
59
+ # Named volume: the image's hindsight user (UID 1000) owns it on first use, unlike a host bind mount.
60
+ - hindsight-data:/home/hindsight/.pg0
61
+ restart: unless-stopped
62
+ healthcheck:
63
+ test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8888/health', timeout=5)"]
64
+ interval: 15s
65
+ timeout: 10s
66
+ retries: 10
67
+ start_period: 60s
68
+
69
+ indexer:
70
+ <<: *indexer-build
71
+ image: openmem-indexer:local
72
+ # Host UID/GID from .env, written by scripts/setup.sh. The image needs no passwd entry for this user.
73
+ user: "${HOST_UID:?run scripts/setup.sh}:${HOST_GID:?run scripts/setup.sh}"
74
+ # Same absolute paths inside the container so bank and document IDs match host paths.
75
+ # Read-only: the indexer reads repositories and writes only its state volume below. Tree-sitter outlines
76
+ # need no writes, and CodeGraph's .codegraph/ writes are gone.
77
+ # Secret paths under $HOME are masked by compose.override.yaml, generated by scripts/gen-masks.sh (required).
78
+ volumes:
79
+ - ${HOME}:${HOME}:ro
80
+ # Writable state (state.json, lock). Its mount point is inside the read-only $HOME mount.
81
+ - indexer-state:${HOME}/.cache/hindsight-project-index
82
+ environment:
83
+ HOME: ${HOME}
84
+ HINDSIGHT_URL: http://hindsight:8888
85
+ depends_on:
86
+ # Soft: waits for Hindsight to be healthy when its profile is active and is ignored otherwise (other providers
87
+ # add their own dependency in providers/<id>/compose.yaml). Unlike a hard dependency, an unhealthy Hindsight
88
+ # only logs a warning; the indexer starts anyway and its watcher retries.
89
+ hindsight:
90
+ condition: service_healthy
91
+ required: false
92
+ indexer-state-init:
93
+ condition: service_completed_successfully
94
+ restart: unless-stopped
95
+
96
+ # Named volumes are created root-owned. This one-off root step gives the state volume to the host UID, then exits.
97
+ indexer-state-init:
98
+ <<: *indexer-build
99
+ image: openmem-indexer:local
100
+ user: "0:0"
101
+ entrypoint: ["sh", "-c", "chown ${HOST_UID:?run scripts/setup.sh}:${HOST_GID:?run scripts/setup.sh} /state"]
102
+ volumes:
103
+ - indexer-state:/state
104
+ restart: "no"
105
+
106
+ # Opt-in: docker compose --profile ollama up -d ollama. Used when the host has no Ollama; LiteLLM reaches it at
107
+ # http://ollama:11434 on the compose network. Port 11434 is published on loopback so the wizard can probe and pull.
108
+ ollama:
109
+ profiles: ["ollama"]
110
+ image: ollama/ollama:latest
111
+ ports:
112
+ - "127.0.0.1:11434:11434"
113
+ volumes:
114
+ - ollama-data:/root/.ollama
115
+ restart: unless-stopped
116
+ healthcheck:
117
+ test: ["CMD-SHELL", "ollama list >/dev/null 2>&1 || exit 1"]
118
+ interval: 15s
119
+ timeout: 10s
120
+ retries: 10
121
+ start_period: 20s
122
+
123
+ # Opt-in: docker compose --profile notebooklm up -d notebooklm. Syncs into notebooklm::* banks on the active MEMORY_SINK.
124
+ # Login is on the host (README "NotebookLM"); the storage under ~/.config/notebooklm is bind-mounted read-write.
125
+ notebooklm:
126
+ profiles: ["notebooklm"]
127
+ build:
128
+ context: .
129
+ dockerfile: sync/Dockerfile
130
+ image: openmem-notebooklm:local
131
+ # Host UID/GID from .env, written by scripts/setup.sh; the bind-mounted login storage must stay host-owned.
132
+ user: "${HOST_UID:?run scripts/setup.sh}:${HOST_GID:?run scripts/setup.sh}"
133
+ environment:
134
+ HINDSIGHT_URL: http://hindsight:8888
135
+ MEMORY_SINK: ${MEMORY_SINK:-hindsight}
136
+ MEMORY_API_URL: ${MEMORY_API_URL:-}
137
+ NOTEBOOKLM_HOME: /data/notebooklm
138
+ NOTEBOOKLM_STATE: /data/state/state.json
139
+ volumes:
140
+ - type: bind
141
+ source: ${HOME}/.config/notebooklm
142
+ target: /data/notebooklm
143
+ bind:
144
+ create_host_path: false
145
+ # Per-machine override (only the keys you change). Create the file before starting; compose does not create it.
146
+ - type: bind
147
+ source: ${NOTEBOOKLM_OVERRIDE:-${HOME}/.config/hindsight/notebooklm.yml}
148
+ target: /etc/notebooklm/override.yml
149
+ read_only: true
150
+ bind:
151
+ create_host_path: false
152
+ # Writable sync state (state.json). The image creates it 0777, so Docker copies that mode into the new volume.
153
+ - notebooklm-state:/data/state
154
+ depends_on:
155
+ hindsight:
156
+ condition: service_healthy
157
+ required: false
158
+ restart: unless-stopped
159
+
160
+ volumes:
161
+ hindsight-data:
162
+ indexer-state:
163
+ notebooklm-state:
164
+ ollama-data:
@@ -0,0 +1,13 @@
1
+ {
2
+ "serverMode": "self-hosted",
3
+ "apiUrl": "http://127.0.0.1:8888",
4
+ "optInOnly": true,
5
+ "optInPaths": [],
6
+ "mapPathToBank": {},
7
+ "notebooklmAccess": {
8
+ "enabled": false,
9
+ "banks": [
10
+ "*"
11
+ ]
12
+ }
13
+ }