@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,236 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Opt-in stdio MCP: list/search notebooklm::* banks on the active memory provider.
4
+ * Config: ~/.hindsight/coding-agent.json (notebooklmAccess); API from MEMORY_PROVIDER + stack env.
5
+ */
6
+ import fs from "node:fs";
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { isMainModule } from "./is-main.mjs";
11
+ import { resolveMcpContext } from "./mcp/context.mjs";
12
+ import { listNotebooklmBanks, searchNotebooklmBank } from "./mcp/notebooklm-provider.mjs";
13
+ import { resolveStackRoot } from "./stack.mjs";
14
+
15
+ export { PREFIX, bankAllowed, filterNotebooklmBanks, isNotebooklmBank, notebooklmSlug } from "./mcp/notebooklm-banks.mjs";
16
+ export { listBanks, listMemories, recallBank } from "./mcp/notebooklm-hindsight-rest.mjs";
17
+
18
+ export const DEFAULT_API_URL = "http://127.0.0.1:8888";
19
+ export const DEFAULT_CONFIG = path.join(os.homedir(), ".hindsight", "coding-agent.json");
20
+
21
+ /** @param {Record<string, any> | null | undefined} cfg */
22
+ export function resolveAccess(cfg) {
23
+ const access = cfg?.notebooklmAccess && typeof cfg.notebooklmAccess === "object" ? cfg.notebooklmAccess : {};
24
+ return {
25
+ enabled: access.enabled === true,
26
+ banks: Array.isArray(access.banks) && access.banks.length ? access.banks.map(String) : ["*"],
27
+ apiUrl: String(cfg?.apiUrl || DEFAULT_API_URL).replace(/\/$/, ""),
28
+ };
29
+ }
30
+
31
+ export function loadCodingAgentConfig(configPath = DEFAULT_CONFIG) {
32
+ return JSON.parse(fs.readFileSync(configPath, "utf8"));
33
+ }
34
+
35
+ import { bankAllowed } from "./mcp/notebooklm-banks.mjs";
36
+
37
+ const TOOLS = [
38
+ {
39
+ name: "notebooklm_list_banks",
40
+ description:
41
+ "List notebooklm::* banks on the active memory provider (filtered by notebooklmAccess.banks). Returns bank_id and fact_count.",
42
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
43
+ },
44
+ {
45
+ name: "notebooklm_search",
46
+ description:
47
+ "Search one allowlisted notebooklm::* bank. Default mode=list is fast full-text (memories/list, no LLM). " +
48
+ "Use mode=recall only when you need Hindsight LLM synthesis — often 30–90s on CPU and may hit MCP timeouts. " +
49
+ "Prefer short keyword queries (e.g. \"time series\") over long phrases. Read-only.",
50
+ inputSchema: {
51
+ type: "object",
52
+ properties: {
53
+ bank_id: { type: "string", description: "e.g. notebooklm::my-notebook" },
54
+ query: { type: "string", description: "Full-text keywords (list) or natural-language (recall)" },
55
+ mode: {
56
+ type: "string",
57
+ enum: ["list", "recall"],
58
+ description: "list (default, fast) or recall (LLM, slow)",
59
+ },
60
+ limit: { type: "integer", description: "list mode page size (default 20)" },
61
+ },
62
+ required: ["bank_id", "query"],
63
+ additionalProperties: false,
64
+ },
65
+ },
66
+ ];
67
+
68
+ function textResult(value, isError = false) {
69
+ const text = typeof value === "string" ? value : JSON.stringify(value, null, 2);
70
+ return { content: [{ type: "text", text }], isError };
71
+ }
72
+
73
+ /** @param {{ apiUrl: string, banks: string[], enabled?: boolean, ctx?: import("./mcp/context.mjs").McpContext }} access @param {string} name @param {Record<string, unknown>} [args] @param {typeof fetch} [fetchFn] */
74
+ export async function callTool(access, name, args = {}, fetchFn = fetch) {
75
+ if (access.enabled !== true) {
76
+ return textResult(
77
+ "notebooklmAccess.enabled is not true in coding-agent.json; " +
78
+ "set notebooklmAccess.enabled to true (and keep ~/.hindsight/coding-agent.json as a real file, not a symlink to the shipped template).",
79
+ true,
80
+ );
81
+ }
82
+ if (!access.ctx) return textResult("MCP context not initialized (internal error)", true);
83
+ if (name === "notebooklm_list_banks") {
84
+ const filtered = await listNotebooklmBanks(access.ctx, access.banks, fetchFn);
85
+ return textResult({ banks: filtered, count: filtered.length, provider: access.ctx.provider });
86
+ }
87
+ if (name === "notebooklm_search") {
88
+ const bankId = String(args.bank_id || "");
89
+ const query = String(args.query || "");
90
+ const mode = String(args.mode || "list").toLowerCase() === "recall" ? "recall" : "list";
91
+ if (!bankId || !query) return textResult("bank_id and query are required", true);
92
+ if (!bankAllowed(bankId, access.banks)) {
93
+ return textResult(`bank not allowlisted or not a notebooklm::* bank: ${bankId}`, true);
94
+ }
95
+ const limit = args.limit != null ? Number(args.limit) : 20;
96
+ const payload = await searchNotebooklmBank(
97
+ access.ctx,
98
+ { bankId, query, mode, limit, hindsightApiUrl: access.apiUrl },
99
+ fetchFn,
100
+ );
101
+ return textResult(payload);
102
+ }
103
+ return textResult(`unknown tool: ${name}`, true);
104
+ }
105
+
106
+ /** @param {Record<string, any>} msg @param {{ apiUrl: string, banks: string[] }} access @param {typeof fetch} [fetchFn] */
107
+ export async function handleMessage(msg, access, fetchFn = fetch) {
108
+ const { id, method, params } = msg;
109
+ if (method === "initialize") {
110
+ // Echo client protocol (Cursor Agent uses 2025-11-25); fall back for older clients.
111
+ const protocolVersion =
112
+ typeof params?.protocolVersion === "string" && params.protocolVersion
113
+ ? params.protocolVersion
114
+ : "2024-11-05";
115
+ return {
116
+ jsonrpc: "2.0",
117
+ id,
118
+ result: {
119
+ protocolVersion,
120
+ capabilities: { tools: {} },
121
+ serverInfo: { name: "notebooklm", version: "0.1.0" },
122
+ },
123
+ };
124
+ }
125
+ if (method === "notifications/initialized" || method === "initialized") return null;
126
+ if (method === "tools/list") return { jsonrpc: "2.0", id, result: { tools: TOOLS } };
127
+ if (method === "tools/call") {
128
+ try {
129
+ return { jsonrpc: "2.0", id, result: await callTool(access, params?.name, params?.arguments || {}, fetchFn) };
130
+ } catch (err) {
131
+ return { jsonrpc: "2.0", id, result: textResult(err instanceof Error ? err.message : String(err), true) };
132
+ }
133
+ }
134
+ if (method === "ping") return { jsonrpc: "2.0", id, result: {} };
135
+ if (id === undefined) return null;
136
+ return { jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } };
137
+ }
138
+
139
+ /** @param {"ndjson" | "content-length"} framing */
140
+ function writeMessage(obj, framing = "ndjson") {
141
+ if (obj == null) return;
142
+ const body = JSON.stringify(obj);
143
+ // Cursor Agent CLI speaks NDJSON on stdio (same as Hindsight MCP). Content-Length is kept for LSP-style clients.
144
+ if (framing === "content-length") {
145
+ process.stdout.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`);
146
+ return;
147
+ }
148
+ process.stdout.write(`${body}\n`);
149
+ }
150
+
151
+ /** Content-Length frames or NDJSON; reply using the same framing the request used. */
152
+ export async function serveStdio(input, onMsg) {
153
+ let buf = Buffer.alloc(0);
154
+ for await (const chunk of input) {
155
+ buf = Buffer.concat([buf, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
156
+ while (buf.length) {
157
+ const asStr = buf.toString("utf8");
158
+ const crlf = asStr.indexOf("\r\n\r\n");
159
+ const lf = asStr.indexOf("\n\n");
160
+ const headerEnd = crlf >= 0 ? crlf : lf;
161
+ const sepLen = crlf >= 0 ? 4 : lf >= 0 ? 2 : -1;
162
+ if (sepLen > 0 && /content-length\s*:/i.test(asStr.slice(0, headerEnd))) {
163
+ const m = /Content-Length:\s*(\d+)/i.exec(asStr.slice(0, headerEnd));
164
+ if (!m) {
165
+ buf = buf.subarray(Buffer.byteLength(asStr.slice(0, headerEnd + sepLen), "utf8"));
166
+ continue;
167
+ }
168
+ const len = Number(m[1]);
169
+ const headerBytes = Buffer.byteLength(asStr.slice(0, headerEnd + sepLen), "utf8");
170
+ if (buf.length < headerBytes + len) break;
171
+ const body = buf.subarray(headerBytes, headerBytes + len).toString("utf8");
172
+ buf = buf.subarray(headerBytes + len);
173
+ let msg;
174
+ try {
175
+ msg = JSON.parse(body);
176
+ } catch (err) {
177
+ writeMessage(
178
+ {
179
+ jsonrpc: "2.0",
180
+ id: null,
181
+ error: { code: -32700, message: `Parse error: ${err instanceof Error ? err.message : err}` },
182
+ },
183
+ "content-length",
184
+ );
185
+ continue;
186
+ }
187
+ writeMessage(await onMsg(msg), "content-length");
188
+ continue;
189
+ }
190
+ const nl = asStr.indexOf("\n");
191
+ if (nl < 0) break;
192
+ const line = asStr.slice(0, nl).trim();
193
+ buf = buf.subarray(Buffer.byteLength(asStr.slice(0, nl + 1), "utf8"));
194
+ if (!line.startsWith("{")) continue;
195
+ try {
196
+ writeMessage(await onMsg(JSON.parse(line)), "ndjson");
197
+ } catch {
198
+ /* skip bad line */
199
+ }
200
+ }
201
+ }
202
+ }
203
+
204
+ async function main() {
205
+ const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
206
+ const configPath = process.env.HINDSIGHT_CODING_AGENT_JSON || DEFAULT_CONFIG;
207
+ let cfg = null;
208
+ let access;
209
+ try {
210
+ cfg = loadCodingAgentConfig(configPath);
211
+ access = resolveAccess(cfg);
212
+ const home = process.env.HOME || os.homedir();
213
+ const stack = resolveStackRoot({ pkgRoot, env: process.env, home });
214
+ access.ctx = resolveMcpContext({ home, stackRoot: stack.root, repoRoot: pkgRoot, env: process.env });
215
+ } catch (err) {
216
+ // Stay alive so Cursor's MCP client does not report "Connection closed".
217
+ // Tools return isError until coding-agent.json is readable.
218
+ console.error(`notebooklm-mcp: cannot read ${configPath}: ${err instanceof Error ? err.message : err}`);
219
+ access = { enabled: false, banks: ["*"], apiUrl: DEFAULT_API_URL, ctx: null };
220
+ }
221
+ if (!access.enabled) {
222
+ console.error(
223
+ "notebooklm-mcp: notebooklmAccess.enabled is not true; tools will error until it is enabled " +
224
+ `(config: ${configPath}).`,
225
+ );
226
+ }
227
+ process.stdin.resume();
228
+ await serveStdio(process.stdin, (msg) => handleMessage(msg, access));
229
+ }
230
+
231
+ if (isMainModule(import.meta.url)) {
232
+ main().catch((err) => {
233
+ console.error(`notebooklm-mcp: ${err instanceof Error ? err.message : err}`);
234
+ process.exit(1);
235
+ });
236
+ }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Unified openmem MCP: stable memory_* tools delegating to the active provider.
4
+ *
5
+ * openmem-mcp
6
+ * node cli/openmem-mcp.mjs
7
+ *
8
+ * Provider: MEMORY_PROVIDER → ~/.config/hindsight/wizard.conf → hindsight.
9
+ * Stack/env resolution matches install-mcp (HINDSIGHT_URL, mem0 URL + key file, Cognee URL).
10
+ */
11
+ import os from "node:os";
12
+ import path from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { isMainModule } from "./is-main.mjs";
15
+ import { resolveMcpContext } from "./mcp/context.mjs";
16
+ import { startContextStore } from "./mcp/context-store.mjs";
17
+ import { handleUnifiedMessage } from "./mcp/memory-facade.mjs";
18
+ import { serveStdio } from "./mcp/stdio.mjs";
19
+ import { resolveStackRoot } from "./stack.mjs";
20
+
21
+ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
22
+
23
+ async function main() {
24
+ const home = process.env.HOME || os.homedir();
25
+ const stack = resolveStackRoot({ pkgRoot: PKG_ROOT, env: process.env, home });
26
+ const ctx = resolveMcpContext({ home, stackRoot: stack.root, repoRoot: PKG_ROOT, env: process.env });
27
+ // Session-file TTL cleanup (docs/context-store.md, "Retention"), at most once a day; never throws.
28
+ await startContextStore(process.env, { home, stackRoot: stack.root });
29
+ process.stdin.resume();
30
+ await serveStdio(process.stdin, (msg) => handleUnifiedMessage(msg, ctx));
31
+ }
32
+
33
+ if (isMainModule(import.meta.url)) {
34
+ main().catch((err) => {
35
+ console.error(`openmem-mcp: ${err instanceof Error ? err.message : err}`);
36
+ process.exit(1);
37
+ });
38
+ }
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * openmem: npx entry point for the memory-stack setup wizard.
4
+ *
5
+ * openmem [setup|configure|up|down|doctor|providers|init|context|--help|--version]
6
+ *
7
+ * The wizard code runs from this package (read-only, may be an npx cache). Everything it
8
+ * writes, and the compose files it drives, live in a stable stack root; see cli/stack.mjs.
9
+ * Real file, not a symlink: npm drops symlinks.
10
+ */
11
+ import { spawnSync } from "node:child_process";
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ import { changedCount, formatReport, resolveStackRoot, syncStack } from "./stack.mjs";
16
+
17
+ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
18
+ const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf8"));
19
+ // The only place the CLI learns the package name; the bin name is a command, not the package.
20
+ const BIN = "openmem";
21
+
22
+ const HELP = `${BIN} ${pkg.version}: set up and run a self-hosted AI memory stack (docker compose).
23
+
24
+ Usage (prefer npx once published: npx ${BIN} <command>):
25
+ ${BIN} interactive setup (same as: ${BIN} setup)
26
+ ${BIN} setup [flags] interactive setup; flags go to the wizard (--yes, --dry-run, ...)
27
+ ${BIN} configure [flags] non-interactive setup and start (wizard --yes)
28
+ ${BIN} up [flags] start the stack with the saved configuration
29
+ ${BIN} down [flags] stop the stack
30
+ ${BIN} doctor [flags] validate the configuration without the smoke test
31
+ ${BIN} wiring [--json] report provider, API URLs, and MCP alignment
32
+ ${BIN} feed-sync fetch RSS/OpenAlex into feeds.out_dir (then indexer watches it)
33
+ ${BIN} schedule <install|remove> [--dry-run] manage hourly feed sync with the OS scheduler
34
+ ${BIN} install-mcp [flags] merge memory MCP servers into ~/.cursor/mcp.json
35
+ ${BIN} install-coding-agent [flags] Hindsight plugin or MCP session path (Claude/Codex/OpenCode/Cursor)
36
+ ${BIN} context <on|off|status> [--dry-run]
37
+ context management (smart compaction): config, agent wiring, proxy service
38
+ ${BIN} switch <provider> [flags]
39
+ change MEMORY_PROVIDER (stack + MCP; no data migration)
40
+ ${BIN} providers [--json] list memory providers and their status
41
+ ${BIN} init [--force] [--dry-run] [--json]
42
+ create or upgrade the stack root without running the wizard
43
+ ${BIN} --version | --help
44
+
45
+ Flags starting with "-" after no command are passed to the wizard: ${BIN} --dry-run --yes
46
+ Run "${BIN} setup --help" for the wizard flags.
47
+
48
+ Stack root (where .env, compose files and generated config live):
49
+ $PM_STACK_ROOT, else \${XDG_DATA_HOME:-~/.local/share}/personal-memory/stack
50
+ PM_USE_CHECKOUT=1 uses this package directory itself (a git checkout does so automatically).
51
+ Upgrades overwrite only shipped files you did not modify; modified ones are reported as conflicts.
52
+ Docs: docs/npx-install.md`;
53
+
54
+ const WIZARD_TASKS = new Set(["configure", "up", "down", "doctor", "install-mcp", "install-coding-agent", "switch"]);
55
+
56
+ /** @param {string} msg */
57
+ function die(msg, code = 1) {
58
+ console.error(`${BIN}: ${msg}`);
59
+ process.exit(code);
60
+ }
61
+
62
+ /**
63
+ * Provider manifests shipped in the package.
64
+ *
65
+ * @returns {{ id: string, status: string, label: string, notes?: string }[]}
66
+ */
67
+ function readProviders() {
68
+ const dir = path.join(PKG_ROOT, "providers");
69
+ /** @type {{ id: string, status: string, label: string, notes?: string }[]} */
70
+ const out = [];
71
+ let names = [];
72
+ try {
73
+ names = fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
74
+ } catch {
75
+ return out;
76
+ }
77
+ for (const id of names) {
78
+ const file = path.join(dir, id, "manifest.json");
79
+ if (!fs.existsSync(file)) continue;
80
+ try {
81
+ const m = JSON.parse(fs.readFileSync(file, "utf8"));
82
+ out.push({ id: m.id || id, status: m.status || "unknown", label: m.label || id, notes: m.notes });
83
+ } catch {
84
+ out.push({ id, status: "invalid", label: `unreadable ${file}` });
85
+ }
86
+ }
87
+ return out;
88
+ }
89
+
90
+ /** @param {string[]} args */
91
+ function cmdProviders(args) {
92
+ const list = readProviders();
93
+ if (args.includes("--json")) {
94
+ console.log(JSON.stringify(list, null, 2));
95
+ return;
96
+ }
97
+ const w = Math.max(2, ...list.map((p) => p.id.length));
98
+ console.log(`${"ID".padEnd(w)} ${"STATUS".padEnd(12)} LABEL`);
99
+ for (const p of list) console.log(`${p.id.padEnd(w)} ${p.status.padEnd(12)} ${p.label}`);
100
+ if (!list.length) console.log("(no provider manifests found)");
101
+ }
102
+
103
+ /**
104
+ * Materialize the stack root (no-op for a checkout).
105
+ *
106
+ * @param {{ force?: boolean, dryRun?: boolean, quiet?: boolean, json?: boolean }} [opts]
107
+ */
108
+ function ensureStack(opts = {}) {
109
+ const sr = resolveStackRoot({ pkgRoot: PKG_ROOT });
110
+ if (!sr.copy) return { sr, report: null };
111
+ let report;
112
+ try {
113
+ report = syncStack({
114
+ pkgRoot: PKG_ROOT,
115
+ stackRoot: sr.root,
116
+ name: pkg.name,
117
+ version: pkg.version,
118
+ force: opts.force,
119
+ dryRun: opts.dryRun,
120
+ });
121
+ } catch (e) {
122
+ die(`cannot prepare the stack root ${sr.root}: ${/** @type {Error} */ (e).message}`);
123
+ }
124
+ if (!opts.quiet && !opts.json && (changedCount(report) || report.conflicts.length)) {
125
+ for (const line of formatReport(report)) console.error(line);
126
+ }
127
+ return { sr, report };
128
+ }
129
+
130
+ /** @param {string[]} args */
131
+ function cmdInit(args) {
132
+ const force = args.includes("--force");
133
+ const dryRun = args.includes("--dry-run");
134
+ const json = args.includes("--json");
135
+ for (const a of args) {
136
+ if (!["--force", "--dry-run", "--json"].includes(a)) die(`init: unknown option ${a}`, 2);
137
+ }
138
+ const { sr, report } = ensureStack({ force, dryRun, json, quiet: true });
139
+ if (json) {
140
+ console.log(JSON.stringify({ stackRoot: sr.root, mode: sr.mode, source: sr.source, report }, null, 2));
141
+ return;
142
+ }
143
+ if (!report) {
144
+ console.log(`stack root: ${sr.root} (${sr.source}; used in place, nothing copied)`);
145
+ return;
146
+ }
147
+ for (const line of formatReport(report)) console.log(line);
148
+ }
149
+
150
+ /**
151
+ * Run a wizard script from the package against the stack root.
152
+ *
153
+ * @param {string} script path relative to the package root
154
+ * @param {string[]} args
155
+ * @param {string} stackRoot
156
+ */
157
+ function runWizardScript(script, args, stackRoot) {
158
+ const r = spawnSync(process.execPath, [path.join(PKG_ROOT, script), ...args], {
159
+ cwd: stackRoot,
160
+ stdio: "inherit",
161
+ env: { ...process.env, PM_STACK_ROOT: stackRoot, PM_PACKAGE_ROOT: PKG_ROOT },
162
+ });
163
+ if (r.error) die(`failed to start the wizard: ${r.error.message}`);
164
+ if (r.signal) process.kill(process.pid, r.signal);
165
+ process.exit(r.status ?? 1);
166
+ }
167
+
168
+ const argv = process.argv.slice(2);
169
+ const cmd = argv[0];
170
+
171
+ if (cmd === "--version" || cmd === "-v" || cmd === "version") {
172
+ console.log(pkg.version);
173
+ } else if (cmd === "--help" || cmd === "-h" || cmd === "help") {
174
+ console.log(HELP);
175
+ } else if (cmd === "providers") {
176
+ cmdProviders(argv.slice(1));
177
+ } else if (cmd === "init") {
178
+ cmdInit(argv.slice(1));
179
+ } else if (cmd === "feed-sync") {
180
+ const r = spawnSync(process.execPath, [path.join(PKG_ROOT, "cli/feed-sync.mjs"), ...argv.slice(1)], {
181
+ stdio: "inherit",
182
+ env: process.env,
183
+ });
184
+ process.exit(r.status ?? 1);
185
+ } else if (cmd === "schedule") {
186
+ const r = spawnSync(process.execPath, [path.join(PKG_ROOT, "cli/schedule.mjs"), ...argv.slice(1)], {
187
+ stdio: "inherit",
188
+ env: process.env,
189
+ });
190
+ process.exit(r.status ?? 1);
191
+ } else if (cmd === "context") {
192
+ // on/off run compose from the stack root, so materialize it first.
193
+ const { sr } = ensureStack({ quiet: true });
194
+ const r = spawnSync(process.execPath, [path.join(PKG_ROOT, "cli/context-management.mjs"), ...argv.slice(1)], {
195
+ stdio: "inherit",
196
+ env: { ...process.env, PM_STACK_ROOT: sr.root },
197
+ });
198
+ process.exit(r.status ?? 1);
199
+ } else if (cmd === "wiring") {
200
+ const r = spawnSync(process.execPath, [path.join(PKG_ROOT, "cli/wiring-report.mjs"), ...argv.slice(1)], {
201
+ stdio: "inherit",
202
+ env: process.env,
203
+ });
204
+ process.exit(r.status ?? 1);
205
+ } else if (cmd === undefined || cmd === "setup" || cmd.startsWith("-")) {
206
+ const args = cmd === "setup" ? argv.slice(1) : argv;
207
+ // Wizard help does not need a stack root.
208
+ if (args.includes("--help") || args.includes("-h")) {
209
+ const sr = resolveStackRoot({ pkgRoot: PKG_ROOT });
210
+ runWizardScript("wizard/bin/setup.mjs", args, fs.existsSync(sr.root) ? sr.root : PKG_ROOT);
211
+ } else {
212
+ const { sr } = ensureStack();
213
+ runWizardScript("wizard/bin/setup.mjs", args, sr.root);
214
+ }
215
+ } else if (WIZARD_TASKS.has(cmd)) {
216
+ const { sr } = ensureStack();
217
+ runWizardScript("wizard/bin/pm.mjs", argv, sr.root);
218
+ } else {
219
+ console.error(`${BIN}: unknown command: ${cmd}\n`);
220
+ console.error(HELP);
221
+ process.exit(2);
222
+ }
package/cli/roots.mjs ADDED
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Shared root and dependency handling for wizard/bin/setup.mjs and wizard/bin/pm.mjs.
3
+ *
4
+ * Contract (see docs/npx-install.md):
5
+ * PM_STACK_ROOT mutable stack root = the wizard's ctx.repoRoot (compose project dir,
6
+ * .env, generated files, docker build context)
7
+ * PM_PACKAGE_ROOT read-only code root (providers/ manifests and hooks, scripts/setup.mjs,
8
+ * wizard/lib)
9
+ * Unset, both default to the checkout root derived from the bin file's location.
10
+ *
11
+ * Node builtins only: this module runs before the wizard dependencies are verified.
12
+ */
13
+ import { spawnSync } from "node:child_process";
14
+ import fs from "node:fs";
15
+ import { createRequire } from "node:module";
16
+ import path from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ /**
20
+ * Compute both roots from the location of a file in wizard/bin and export them to the
21
+ * environment so child processes (scripts/setup.mjs, docker compose helpers) see them.
22
+ *
23
+ * @param {string} binFileUrl import.meta.url of wizard/bin/<file>.mjs
24
+ * @returns {{ pkgRoot: string, repo: string, packaged: boolean }}
25
+ */
26
+ export function resolveWizardRoots(binFileUrl) {
27
+ const here = path.dirname(fileURLToPath(binFileUrl));
28
+ const derived = path.resolve(here, "../..");
29
+ const pkgRoot = path.resolve(process.env.PM_PACKAGE_ROOT || derived);
30
+ const repo = path.resolve(process.env.PM_STACK_ROOT || derived);
31
+ const packaged = derived.split(path.sep).includes("node_modules");
32
+ if (packaged && !process.env.PM_STACK_ROOT) {
33
+ console.error(
34
+ "openmem: this copy lives in an npm package directory, which is read-only and\n" +
35
+ "may be garbage-collected. Run it through the CLI instead (it sets up a stable stack root):\n" +
36
+ " npx openmem | openmem | PM_STACK_ROOT=<dir> to choose one",
37
+ );
38
+ process.exit(1);
39
+ }
40
+ process.env.PM_PACKAGE_ROOT = pkgRoot;
41
+ process.env.PM_STACK_ROOT = repo;
42
+ return { pkgRoot, repo, packaged };
43
+ }
44
+
45
+ /**
46
+ * Is `name` resolvable from a file inside `fromDir`? Checks node_modules lookup
47
+ * directories on disk, so it works for ESM-only packages and hoisted installs.
48
+ *
49
+ * @param {string} fromDir
50
+ * @param {string} name
51
+ */
52
+ function resolvable(fromDir, name) {
53
+ const req = createRequire(path.join(fromDir, "noop.js"));
54
+ const dirs = req.resolve.paths(name) || [];
55
+ return dirs.some((d) => fs.existsSync(path.join(d, name, "package.json")));
56
+ }
57
+
58
+ /**
59
+ * Make sure the wizard dependencies resolve. Packaged installs get them from npm; a
60
+ * checkout runs `npm install` once at the checkout root.
61
+ *
62
+ * @param {string} pkgRoot
63
+ * @param {boolean} packaged
64
+ */
65
+ export function ensureWizardDeps(pkgRoot, packaged) {
66
+ const wizardDir = path.join(pkgRoot, "wizard", "bin");
67
+ const needed = ["@clack/prompts", "@iarna/toml"];
68
+ const missing = needed.filter((n) => !resolvable(wizardDir, n));
69
+ if (!missing.length) return;
70
+ if (packaged || !fs.existsSync(path.join(pkgRoot, "package.json"))) {
71
+ console.error(
72
+ `openmem: missing dependencies (${missing.join(", ")}); reinstall the package.`,
73
+ );
74
+ process.exit(1);
75
+ }
76
+ if (process.stderr.isTTY) process.stderr.write("Installing setup dependencies...\n");
77
+ const r = spawnSync("npm", ["install", "--no-fund", "--no-audit"], {
78
+ cwd: pkgRoot,
79
+ stdio: "inherit",
80
+ });
81
+ if (r.status !== 0) {
82
+ console.error(`npm install failed in ${pkgRoot}; run it there by hand`);
83
+ process.exit(r.status ?? 1);
84
+ }
85
+ }