@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,165 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Install coding-agent session memory for Claude Code, Codex, OpenCode, Cursor CLI.
4
+ * All providers: unified MCP + in-repo session hooks.
5
+ * Optional: --hindsight-plugin also runs @vectorize-io/hindsight-coding-agents (Hindsight stacks only).
6
+ */
7
+ import { spawnSync } from "node:child_process";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { CODING_AGENTS, DEFAULT_AGENT_IDS } from "./coding-agent/agents.mjs";
11
+ import { AGENT_IDS, PROVIDER_CAPABILITIES } from "./coding-agent/capabilities.mjs";
12
+ import { installSessionShim } from "./coding-agent/install-shim.mjs";
13
+ import { installAgentAccess } from "./coding-agent/install-access.mjs";
14
+ import { prepareAgentRuntime } from "./coding-agent/runtime.mjs";
15
+ import { SHIM_AGENT_IDS } from "./coding-agent/hook-harnesses.mjs";
16
+ import { resolveMemoryProvider } from "./install-memory-mcp.mjs";
17
+ import { isMainModule } from "./is-main.mjs";
18
+ import { resolveHindsightApiUrl } from "./mcp/context.mjs";
19
+ import { resolveStackRoot } from "./stack.mjs";
20
+ import { runInstallMemoryMcp } from "./install-memory-mcp.mjs";
21
+
22
+ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
23
+ const BIN = "openmem-install-coding-agent";
24
+
25
+ const HELP = `${BIN}: wire session memory for coding agents
26
+
27
+ Usage:
28
+ npx openmem install-coding-agent [--yes] [--dry-run] [--agents a,b] [--hindsight-plugin]
29
+
30
+ Default agents: ${DEFAULT_AGENT_IDS.join(", ")}
31
+ All known agents: ${CODING_AGENTS.map((a) => a.id).join(", ")}
32
+
33
+ Every provider gets install-mcp + openmem session hooks (SessionStart / prompt / Stop).
34
+ With --hindsight-plugin and MEMORY_PROVIDER=hindsight, also runs hindsight-coding-agents install.
35
+ `;
36
+
37
+ function parseArgs(argv) {
38
+ const flags = {
39
+ yes: false,
40
+ dry: false,
41
+ help: false,
42
+ hindsightPlugin: false,
43
+ agents: [...DEFAULT_AGENT_IDS],
44
+ unknown: [],
45
+ };
46
+ for (let i = 0; i < argv.length; i++) {
47
+ const a = argv[i];
48
+ if (a === "--yes" || a === "-y") flags.yes = true;
49
+ else if (a === "--dry-run") flags.dry = true;
50
+ else if (a === "--help" || a === "-h") flags.help = true;
51
+ else if (a === "--hindsight-plugin") flags.hindsightPlugin = true;
52
+ else if (a === "--agents") flags.agents = String(argv[++i] || "").split(",").map((s) => s.trim()).filter(Boolean);
53
+ else flags.unknown.push(a);
54
+ }
55
+ return flags;
56
+ }
57
+
58
+ function h() {
59
+ return {
60
+ info: (m) => console.log(m),
61
+ ok: (m) => console.log(m),
62
+ warn: (m) => console.warn(m),
63
+ };
64
+ }
65
+
66
+ export async function runInstallCodingAgent(opts) {
67
+ const home = opts.home;
68
+ const env = opts.env || process.env;
69
+ const flags = opts.flags;
70
+ for (const agentId of flags.agents) {
71
+ if (!AGENT_IDS.includes(agentId)) {
72
+ opts.h?.warn(`Unknown agent "${agentId}" (known: ${AGENT_IDS.join(", ")})`);
73
+ }
74
+ }
75
+ const provider = resolveMemoryProvider(home, env);
76
+ const caps = PROVIDER_CAPABILITIES[provider] || {};
77
+ opts.h?.info(`Provider ${provider}: capabilities ${JSON.stringify(caps)}`);
78
+
79
+ const shimAgents = flags.agents.filter((id) => SHIM_AGENT_IDS.includes(id) || id === "opencode");
80
+ const runtimeRoot = prepareAgentRuntime({
81
+ home, repoRoot: opts.repoRoot, stackRoot: opts.stackRoot, dry: flags.dry || !flags.yes, h: opts.h || h(),
82
+ });
83
+ if (flags.dry) {
84
+ opts.h?.info("dry-run: would run install-mcp --yes");
85
+ installSessionShim({ home, repoRoot: runtimeRoot, agents: shimAgents, dry: true, h: opts.h || h() });
86
+ installAgentAccess({ home, runtimeRoot, stackRoot: opts.stackRoot, agents: flags.agents, dry: true, h: opts.h || h() });
87
+ if (flags.hindsightPlugin && provider === "hindsight") {
88
+ const apiUrl = resolveHindsightApiUrl({ home, stackRoot: opts.stackRoot, env });
89
+ opts.h?.info(
90
+ `dry-run: would also run npx @vectorize-io/hindsight-coding-agents install ${flags.agents.join(" ")} --server self-hosted --api-url ${apiUrl}`,
91
+ );
92
+ }
93
+ return { provider, ran: false };
94
+ }
95
+ if (!flags.yes) {
96
+ opts.h?.warn("Re-run with --yes to install MCP + session hooks");
97
+ return { provider, ran: false };
98
+ }
99
+
100
+ await runInstallMemoryMcp({
101
+ home,
102
+ repoRoot: runtimeRoot,
103
+ stackRoot: opts.stackRoot,
104
+ flags: { yes: true, dry: false, notebooklm: false, notebooklmOnly: false, providerOnly: false, noUnifiedMcp: false, help: false, unknown: [] },
105
+ env,
106
+ h: opts.h || h(),
107
+ });
108
+ installSessionShim({
109
+ home,
110
+ repoRoot: runtimeRoot,
111
+ agents: shimAgents,
112
+ dry: false,
113
+ h: opts.h || h(),
114
+ });
115
+ installAgentAccess({
116
+ home, runtimeRoot, stackRoot: opts.stackRoot, agents: flags.agents, dry: false, h: opts.h || h(),
117
+ });
118
+
119
+ if (flags.hindsightPlugin && provider === "hindsight") {
120
+ const apiUrl = resolveHindsightApiUrl({ home, stackRoot: opts.stackRoot, env });
121
+ const cmd = ["npx", "-y", "@vectorize-io/hindsight-coding-agents", "install", ...flags.agents, "--server", "self-hosted", "--api-url", apiUrl];
122
+ opts.h?.info(`Also installing upstream Hindsight plugin: ${cmd.join(" ")}`);
123
+ const r = spawnSync(cmd[0], cmd.slice(1), { stdio: "inherit", env });
124
+ return { provider, ran: true, status: r.status ?? 1 };
125
+ }
126
+
127
+ for (const agentId of flags.agents) {
128
+ if (shimAgents.includes(agentId)) {
129
+ opts.h?.info(` ${agentId}: openmem session hooks + unified MCP`);
130
+ } else {
131
+ const row = CODING_AGENTS.find((a) => a.id === agentId);
132
+ opts.h?.info(` ${agentId}: ${row?.sessionPath || "unified MCP memory_*"}`);
133
+ }
134
+ }
135
+ return { provider, ran: true, status: 0 };
136
+ }
137
+
138
+ async function main() {
139
+ const flags = parseArgs(process.argv.slice(2));
140
+ if (flags.help || flags.unknown.length) {
141
+ console.log(HELP);
142
+ process.exit(flags.unknown.length ? 2 : 0);
143
+ }
144
+ const home = process.env.HOME || "";
145
+ const sr = resolveStackRoot({ pkgRoot: PKG_ROOT });
146
+ const out = await runInstallCodingAgent({
147
+ home,
148
+ repoRoot: PKG_ROOT,
149
+ stackRoot: sr.root,
150
+ flags,
151
+ env: process.env,
152
+ h: h(),
153
+ });
154
+ return typeof out.status === "number" ? out.status : 0;
155
+ }
156
+
157
+ if (isMainModule(import.meta.url)) {
158
+ main().then(
159
+ (code) => process.exit(code ?? 0),
160
+ (err) => {
161
+ console.error(err);
162
+ process.exit(1);
163
+ },
164
+ );
165
+ }
@@ -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,61 @@
1
+ import path from "node:path";
2
+ import { envValue } from "../../wizard/lib/paths.mjs";
3
+ import { resolveMemoryProvider } from "../install-memory-mcp.mjs";
4
+ import { resolveMem0ApiUrl, resolveMem0EnvFilePath } from "../../providers/mem0/lib/mcp-install.mjs";
5
+ import { resolveCogneeApiUrl } from "../../providers/cognee/lib/cognee-mcp-install.mjs";
6
+ import { readKey } from "../../providers/mem0/lib/env-file.mjs";
7
+
8
+ const DEFAULT_HINDSIGHT_PORT = 8888;
9
+
10
+ /**
11
+ * @param {{ home: string, stackRoot: string, env?: NodeJS.ProcessEnv }} opts
12
+ */
13
+ export function resolveHindsightApiUrl({ home, stackRoot, env = process.env }) {
14
+ const candidates = [
15
+ env.HINDSIGHT_URL,
16
+ env.PM_HINDSIGHT_URL,
17
+ envValue(path.join(stackRoot, ".env"), "HINDSIGHT_URL"),
18
+ envValue(path.join(home, ".config/hindsight/wizard.conf"), "HINDSIGHT_URL"),
19
+ ];
20
+ const url = candidates.map((v) => String(v || "").trim()).find(Boolean);
21
+ if (url) return url.replace(/\/+$/, "");
22
+ const portCandidates = [
23
+ env.HINDSIGHT_PORT,
24
+ envValue(path.join(stackRoot, ".env"), "HINDSIGHT_PORT"),
25
+ envValue(path.join(home, ".config/hindsight/wizard.conf"), "HINDSIGHT_PORT"),
26
+ ];
27
+ const port = portCandidates.map((v) => String(v || "").trim()).find(Boolean) || String(DEFAULT_HINDSIGHT_PORT);
28
+ return `http://127.0.0.1:${port}`;
29
+ }
30
+
31
+ /**
32
+ * @param {{ home: string, stackRoot: string, repoRoot: string, env?: NodeJS.ProcessEnv }} opts
33
+ */
34
+ export function resolveMcpContext(opts) {
35
+ const env = opts.env || process.env;
36
+ const home = opts.home;
37
+ const stackRoot = opts.stackRoot;
38
+ const provider = resolveMemoryProvider(home, env);
39
+ /** @type {{ provider: string, hindsightUrl?: string, mem0?: { baseUrl: string, apiKey?: string }, cognee?: { baseUrl: string, apiKey?: string } }} */
40
+ const ctx = { provider };
41
+ if (provider === "hindsight") {
42
+ ctx.hindsightUrl = resolveHindsightApiUrl({ home, stackRoot, env });
43
+ } else if (provider === "mem0") {
44
+ const baseUrl = resolveMem0ApiUrl({ home, stackRoot, env });
45
+ const envFile = resolveMem0EnvFilePath({ home, stackRoot, env });
46
+ let apiKey = String(env.MEM0_API_KEY || "").trim();
47
+ if (!apiKey) {
48
+ try {
49
+ apiKey = readKey(envFile) || "";
50
+ } catch {
51
+ apiKey = "";
52
+ }
53
+ }
54
+ ctx.mem0 = { baseUrl, apiKey: apiKey || undefined };
55
+ } else if (provider === "cognee") {
56
+ const baseUrl = resolveCogneeApiUrl({ home, stackRoot, repoRoot: opts.repoRoot, env });
57
+ const apiKey = String(env.COGNEE_API_KEY || "").trim() || undefined;
58
+ ctx.cognee = { baseUrl, apiKey };
59
+ }
60
+ return ctx;
61
+ }
@@ -0,0 +1,12 @@
1
+ import crypto from "node:crypto";
2
+
3
+ const SLUG_MAX = 40;
4
+
5
+ /** Match `indexer/sinks/cognee.py` `dataset_name(bank)`. */
6
+ export function cogneeDatasetName(bank) {
7
+ const raw = String(bank ?? "");
8
+ let slug = raw.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
9
+ slug = slug.slice(0, SLUG_MAX).replace(/^_+|_+$/g, "") || "bank";
10
+ const hash = crypto.createHash("sha256").update(raw).digest("hex").slice(0, 8);
11
+ return `${slug}_${hash}`;
12
+ }