@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,60 @@
1
+ /** mem0's contribution to the wizard's closing summary. */
2
+ import path from "node:path";
3
+ import { readKey } from "./env-file.mjs";
4
+ import { resolveEnvFile, ensureState } from "./state.mjs";
5
+ import { containerUrl, displayUrl, resolveApiUrl, urlProblem } from "./url.mjs";
6
+
7
+ /** @typedef {import("../../../wizard/lib/provider-wizard/helpers.mjs").ProviderWizardHelpers} Helpers */
8
+
9
+ const DOCS = "https://docs.mem0.ai/open-source/features/rest-api";
10
+ const OPERATOR = "providers/mem0/OPERATOR.md";
11
+
12
+ /**
13
+ * @param {Record<string, any>} ctx
14
+ * @param {Helpers} h
15
+ * @returns {{ running: string[], configFiles: string[], notes: string[], logServices: string[] }}
16
+ */
17
+ export function summary(ctx, h) {
18
+ ensureState(ctx);
19
+ const raw = resolveApiUrl(ctx, h);
20
+ const valid = !urlProblem(raw);
21
+ const url = valid ? displayUrl(raw) : "(invalid URL)";
22
+ const envFile = resolveEnvFile(ctx, h);
23
+
24
+ const running = [
25
+ ` mem0 API ${url} external server (not managed by this stack)`,
26
+ ` indexer sink mem0 -> ${valid ? containerUrl(raw) : "(invalid URL)"} (the container's view)`,
27
+ ];
28
+ const configFiles = [
29
+ ` ${envFile} (MEM0_API_KEY for the indexer, mode 600)`,
30
+ ` ${path.join(ctx.repoRoot, ".env")} (MEM0_API_URL, MEM0_INDEXER_URL; no secrets)`,
31
+ ];
32
+
33
+ /** @type {string[]} */
34
+ const notes = [];
35
+ if (ctx.action === "down") return { running, configFiles, notes, logServices: ["indexer"] };
36
+ let loopback = false;
37
+ if (valid) {
38
+ const parsed = new URL(raw);
39
+ loopback = ["127.0.0.1", "localhost", "0.0.0.0", "[::1]"].includes(parsed.hostname);
40
+ }
41
+ let hasKey = false;
42
+ try {
43
+ hasKey = readKey(envFile) !== "";
44
+ } catch {
45
+ hasKey = false;
46
+ }
47
+ if (!hasKey) {
48
+ notes.push(
49
+ `API key: create one in your mem0 dashboard (port varies by install) or with POST /api-keys, then put MEM0_API_KEY=<key> in ${envFile} (mode 600). ` +
50
+ "A server run with AUTH_DISABLED=true needs none (development only).",
51
+ );
52
+ }
53
+ notes.push(
54
+ `The mem0 server is yours to run: first-time setup (JWT_SECRET, embedder keys) is provider-native — ${OPERATOR} and ${DOCS}`,
55
+ loopback
56
+ ? "The indexer container reaches a loopback server as host.docker.internal; on Linux a server bound only to 127.0.0.1 is not reachable that way: bind it to 0.0.0.0 or the docker bridge IP, or use a hostname in MEM0_API_URL."
57
+ : "The indexer container must be able to reach the server at the URL above (it uses the same host name).",
58
+ );
59
+ return { running, configFiles, notes, logServices: ["indexer"] };
60
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The mem0 server URL as the operator's host sees it: resolution order, validation (the
3
+ * same rules as scripts/setup.mjs, through `indexerUrl`) and the announced
4
+ * `resolvePorts` step. mem0 has no local port to pick.
5
+ */
6
+ import path from "node:path";
7
+ import * as stackEnv from "./stack-env.mjs";
8
+ import { DEFAULT_API_URL, ensureState, envFileProblem, resolveEnvFile, defaultEnvFile } from "./state.mjs";
9
+
10
+ /** @typedef {import("../../../wizard/lib/provider-wizard/helpers.mjs").ProviderWizardHelpers} Helpers */
11
+
12
+ /** @param {string} url */
13
+ function stripSlash(url) {
14
+ return url.replace(/\/+$/, "");
15
+ }
16
+
17
+ /**
18
+ * The host-view URL: --api-url (ctx.apiUrl), MEM0_API_URL, PM_MEM0_API_URL, the context
19
+ * value ([memory.mem0] api_url), the stack .env, wizard.conf, then the default. Not
20
+ * validated; callers check it with `urlProblem`.
21
+ * @param {Record<string, any>} ctx
22
+ * @param {Helpers} h
23
+ * @returns {string}
24
+ */
25
+ export function resolveApiUrl(ctx, h) {
26
+ const state = ensureState(ctx);
27
+ const stackEnvFile = ctx.repoRoot ? path.join(ctx.repoRoot, ".env") : "";
28
+ const candidates = [
29
+ ctx.apiUrl,
30
+ process.env.MEM0_API_URL,
31
+ process.env.PM_MEM0_API_URL,
32
+ state.apiUrl,
33
+ stackEnvFile && h.envValue(stackEnvFile, "MEM0_API_URL"),
34
+ state.savedUrl || (ctx.wizardConf && h.envValue(ctx.wizardConf, "MEM0_API_URL")),
35
+ ];
36
+ const first = candidates.map((v) => String(v || "").trim()).find(Boolean);
37
+ return stripSlash(first || DEFAULT_API_URL);
38
+ }
39
+
40
+ /**
41
+ * What is wrong with a URL, or "". The message never echoes the URL (it may hold a secret).
42
+ * @param {string} url
43
+ * @returns {string}
44
+ */
45
+ export function urlProblem(url) {
46
+ try {
47
+ stackEnv.indexerUrl(url);
48
+ return "";
49
+ } catch (e) {
50
+ return /** @type {Error} */ (e).message;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * The URL in the form the stack .env stores (validated first, so no secret can be in it).
56
+ * @param {string} url
57
+ */
58
+ export function displayUrl(url) {
59
+ try {
60
+ return typeof stackEnv.normalizeHostUrl === "function" ? stackEnv.normalizeHostUrl(url) : stripSlash(url);
61
+ } catch {
62
+ return stripSlash(url);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Container view of the URL, or "" when it does not validate.
68
+ * @param {string} url
69
+ */
70
+ export function containerUrl(url) {
71
+ try {
72
+ return stackEnv.indexerUrl(url);
73
+ } catch {
74
+ return "";
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Announced: validate the URL and export MEM0_API_URL (and a non-default env file) so
80
+ * compose and scripts/setup.mjs see them. No port probing: the server is external.
81
+ * A stop (`--down`) skips it so a bad URL never blocks shutting the stack down.
82
+ * @param {Record<string, any>} ctx
83
+ * @param {Helpers} h
84
+ */
85
+ export async function resolvePorts(ctx, h) {
86
+ const state = ensureState(ctx);
87
+ if (ctx.action === "down") return;
88
+ const raw = resolveApiUrl(ctx, h);
89
+ const problem = urlProblem(raw);
90
+ if (problem) {
91
+ h.problem(ctx, `mem0 API URL: ${problem}`);
92
+ return;
93
+ }
94
+ const url = displayUrl(raw);
95
+ state.url = url;
96
+ process.env.MEM0_API_URL = url;
97
+ const envFile = resolveEnvFile(ctx, h);
98
+ const fileProblem = envFileProblem(envFile);
99
+ if (fileProblem) {
100
+ h.problem(ctx, fileProblem);
101
+ return;
102
+ }
103
+ // The indexer's env_file path is interpolated by compose from the process environment.
104
+ if (envFile !== defaultEnvFile(ctx.home)) process.env.MEM0_ENV_FILE = envFile;
105
+ h.ok(`mem0 server ${url} (external; no local port to check)`);
106
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * mem0 checks that run after the meta wizard's provider-neutral validation, plus the
3
+ * `beforeStack` reachability and file-mode checks. Secrets must never be in the stack .env
4
+ * (it is mode 644), only in the mode 600 env file.
5
+ */
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import { describeError, request } from "./http.mjs";
9
+ import { dirTooOpen, fileMode, readKey, tooOpen } from "./env-file.mjs";
10
+ import { indexerUrl } from "./stack-env.mjs";
11
+ import { defaultEnvFile, ensureState, envFileProblem, resolveEnvFile } from "./state.mjs";
12
+ import { displayUrl, resolveApiUrl, urlProblem } from "./url.mjs";
13
+
14
+ /** @typedef {import("../../../wizard/lib/provider-wizard/helpers.mjs").ProviderWizardHelpers} Helpers */
15
+
16
+ const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "0.0.0.0", "[::1]"]);
17
+ const HOSTED_MEM0 = /(^|\.)api\.mem0\.ai$/i;
18
+
19
+ /**
20
+ * Warn when the server does not answer GET /docs; never fails (it may start later) and
21
+ * never sends the key. Also warns about an env file that others can read.
22
+ * @param {Record<string, any>} ctx
23
+ * @param {Helpers} h
24
+ */
25
+ export async function beforeStack(ctx, h) {
26
+ ensureState(ctx);
27
+ const raw = resolveApiUrl(ctx, h);
28
+ const issue = urlProblem(raw);
29
+ if (issue) {
30
+ h.problem(ctx, `mem0 API URL: ${issue}`);
31
+ return;
32
+ }
33
+ const base = displayUrl(raw);
34
+ const envFile = resolveEnvFile(ctx, h);
35
+
36
+ if (ctx.dry) {
37
+ h.info(`[dry-run] check that ${base}/docs answers (a warning only: the server may start later)`);
38
+ } else {
39
+ try {
40
+ const res = await request(base, "GET", "/docs", { timeoutMs: 5000 });
41
+ if (res.ok) h.ok(`mem0 server answers at ${base}`);
42
+ else {
43
+ h.warn(
44
+ `mem0 server at ${base} answered ${res.status} for GET /docs. The indexer keeps retrying; ` +
45
+ "check the URL and that this is a mem0 REST server",
46
+ );
47
+ }
48
+ } catch (e) {
49
+ h.warn(
50
+ `mem0 server is not reachable at ${base} (${describeError(e)}). Start it before indexing; ` +
51
+ "the stack health check waits for it and fails when it never comes up",
52
+ );
53
+ }
54
+ }
55
+
56
+ if (tooOpen(envFile)) {
57
+ h.warn(`${envFile} is group/other accessible (mode ${fileMode(envFile).toString(8)}); run: chmod 600 ${envFile}`);
58
+ }
59
+ if (fs.existsSync(envFile) && dirTooOpen(envFile)) {
60
+ h.warn(`${path.dirname(envFile)} is accessible to group or others; run: chmod 700 ${path.dirname(envFile)}`);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Appends to ctx.validationErrors and ctx.validationChecks.
66
+ * @param {Record<string, any>} ctx
67
+ * @param {Helpers} h
68
+ */
69
+ export function validateMem0(ctx, h) {
70
+ ensureState(ctx);
71
+ const errors = (ctx.validationErrors ||= []);
72
+ const checks = (ctx.validationChecks ||= []);
73
+
74
+ const apiUrl = resolveApiUrl(ctx, h);
75
+ const issue = urlProblem(apiUrl);
76
+ if (issue) errors.push(`mem0 API URL: ${issue}`);
77
+ else {
78
+ checks.push("mem0 API URL parses");
79
+ try {
80
+ const host = new URL(apiUrl).hostname;
81
+ if (HOSTED_MEM0.test(host)) {
82
+ errors.push(
83
+ "mem0 API URL points at api.mem0.ai (hosted platform); this sink is for the self-hosted REST server only — see providers/mem0/OPERATOR.md",
84
+ );
85
+ } else checks.push("mem0 API URL is not the hosted api.mem0.ai platform");
86
+ } catch {
87
+ // urlProblem already covered unparseable URLs.
88
+ }
89
+ }
90
+
91
+ const envFile = resolveEnvFile(ctx, h);
92
+ const fileIssue = envFileProblem(envFile);
93
+ if (fileIssue) errors.push(fileIssue);
94
+ else if (!fs.existsSync(envFile)) {
95
+ checks.push("mem0 env file absent (fine when the server has auth disabled)");
96
+ } else if (tooOpen(envFile)) {
97
+ errors.push(`${envFile} must not be group/other accessible (want mode 600)`);
98
+ } else checks.push("mem0 env file mode 600");
99
+
100
+ const stackEnv = path.join(ctx.repoRoot, ".env");
101
+ if (fs.existsSync(stackEnv)) {
102
+ const hostApi = h.envValue(stackEnv, "MEM0_API_URL");
103
+ const indexer = h.envValue(stackEnv, "MEM0_INDEXER_URL");
104
+ for (const key of ["MEM0_INDEXER_URL", "MEM0_API_URL"]) {
105
+ if (!h.envValue(stackEnv, key)) errors.push(`repo .env missing ${key}`);
106
+ }
107
+ if (hostApi && indexer) {
108
+ try {
109
+ const expected = indexerUrl(hostApi);
110
+ if (indexer !== expected) {
111
+ errors.push(
112
+ `repo .env MEM0_INDEXER_URL must match the rewrite of MEM0_API_URL (want ${expected}; rerun ./setup)`,
113
+ );
114
+ } else checks.push("repo .env MEM0_INDEXER_URL matches MEM0_API_URL rewrite");
115
+ } catch (e) {
116
+ errors.push(`repo .env MEM0_API_URL: ${/** @type {Error} */ (e).message}`);
117
+ }
118
+ }
119
+ if (/^\s*(?:export\s+)?MEM0_API_KEY\s*=/m.test(fs.readFileSync(stackEnv, "utf8"))) {
120
+ errors.push("repo .env must not hold MEM0_API_KEY (the stack .env is world-readable; use the mem0 env file)");
121
+ }
122
+ checks.push("repo .env mem0 URLs and no key");
123
+ }
124
+
125
+ // A custom key file must reach compose through the stack .env, or a hand-run compose would
126
+ // read the default file while the wizard reads another.
127
+ if (!fileIssue && fs.existsSync(stackEnv) && envFile !== defaultEnvFile(ctx.home)) {
128
+ if (h.envValue(stackEnv, "MEM0_ENV_FILE") !== envFile) {
129
+ errors.push("repo .env MEM0_ENV_FILE does not match the mem0 env file in use (rerun ./setup)");
130
+ } else checks.push("repo .env MEM0_ENV_FILE matches");
131
+ }
132
+
133
+ // Cleartext key over non-loopback http (warn only; same guidance as the sink).
134
+ if (!issue && !fileIssue) {
135
+ try {
136
+ const parsed = new URL(apiUrl);
137
+ let hasKey = false;
138
+ try {
139
+ hasKey = readKey(envFile) !== "";
140
+ } catch {
141
+ hasKey = false;
142
+ }
143
+ if (parsed.protocol === "http:" && !LOOPBACK_HOSTS.has(parsed.hostname) && hasKey) {
144
+ h.warn(
145
+ "MEM0_API_URL is plain http to a non-loopback host with an API key set; the key travels in cleartext — prefer https (see providers/mem0/OPERATOR.md)",
146
+ );
147
+ }
148
+ } catch {
149
+ // ignore
150
+ }
151
+ }
152
+
153
+ for (const file of ctx.memoryProvider?.compose?.files || []) {
154
+ const abs = path.join(ctx.repoRoot, file);
155
+ if (!fs.existsSync(abs)) errors.push(`missing compose file ${abs}`);
156
+ else checks.push(`compose fragment ${file} present`);
157
+ }
158
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "id": "mem0",
3
+ "label": "mem0",
4
+ "status": "supported",
5
+ "notes": "URL-only v1: the operator runs a mem0 REST server and this stack points the shared indexer at it; no mem0 container yet (docker mode is a follow-up). Operator runbook: providers/mem0/OPERATOR.md. Not api.mem0.ai. The API key lives in ~/.config/personal-memory/mem0.env (mode 600), never the stack .env. The wizard.* HTTP fields below are informational: the wizard's smoke hook replaces the generic HTTP smoke (it needs an X-API-Key header), and deletePath is admin-only upstream, so the hook deletes by memory id instead.",
6
+ "settings": {
7
+ "nativeDocs": "providers/mem0/OPERATOR.md (stack) and https://docs.mem0.ai/open-source/features/rest-api (upstream)",
8
+ "nativeConfigPaths": ["~/.config/personal-memory/mem0.env"],
9
+ "wizardOwns": ["indexer URL and the host-gateway rewrite", "API key file for the indexer (mode 600)", "index.yml sources (shared indexer)"]
10
+ },
11
+ "compose": {
12
+ "profile": "mem0",
13
+ "files": ["providers/mem0/compose.yaml"],
14
+ "services": ["indexer"],
15
+ "healthService": null,
16
+ "healthPath": "/docs",
17
+ "hostPortEnv": "MEM0_PORT",
18
+ "defaultHostPort": 18888
19
+ },
20
+ "indexer": {
21
+ "apiUrlEnv": "MEM0_API_URL",
22
+ "sink": "mem0",
23
+ "sinkDriver": "mem0-rest"
24
+ },
25
+ "codingAgent": {
26
+ "pluginPackage": null,
27
+ "configTemplate": null
28
+ },
29
+ "wizardHooks": {
30
+ "module": "wizard.mjs",
31
+ "delegates": {}
32
+ },
33
+ "wizard": {
34
+ "note": "Informational: the mem0 smoke hook (providers/mem0/lib/smoke.mjs) replaces these HTTP fields because the generic smoke cannot send X-API-Key; deletePath is admin-only upstream and the hook deletes by memory id.",
35
+ "smokeBank": "wizard-smoke",
36
+ "retainMethod": "POST",
37
+ "retainPath": "/memories",
38
+ "retainBody": "{\"messages\":[{\"role\":\"user\",\"content\":\"Wizard smoke test: the project codename is heron.\"}],\"user_id\":\"wizard-smoke\",\"agent_id\":\"openmem-wizard\",\"run_id\":\"wizard-smoke-doc\",\"infer\":false}",
39
+ "recallMethod": "POST",
40
+ "recallPath": "/search",
41
+ "recallBody": "{\"query\":\"What is the project codename?\",\"filters\":{\"user_id\":\"wizard-smoke\"}}",
42
+ "recallMustMatch": "heron",
43
+ "recallHint": "check the mem0 server logs and its embedder key",
44
+ "deleteMethod": "DELETE",
45
+ "deletePath": "/memories?user_id={bank}"
46
+ }
47
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * mem0 sub-wizard (hook API v2), URL-only: the operator runs a mem0 REST server (see
3
+ * https://docs.mem0.ai/open-source/features/rest-api) and this stack only points the shared
4
+ * indexer at it. The wizard asks for the server URL and API key, keeps the key in a mode 600
5
+ * env file (never the stack .env, wizard.conf or config.toml), and smoke tests the server.
6
+ * See lib/ for the implementations and docs/provider-wizard-hooks.md for the hook contract.
7
+ *
8
+ * This module must not import wizard/lib/run-wizard.mjs; it receives `helpers`.
9
+ */
10
+ import { applyConfig, configToml as buildConfigToml } from "./lib/config.mjs";
11
+ import { setupCodingAgent as printCodingAgentPaths } from "./lib/coding-agent.mjs";
12
+ import { clackSetup as askInClack, configureUrlAndKey } from "./lib/configure.mjs";
13
+ import { captureKeyFromEnv } from "./lib/secret.mjs";
14
+ import { smoke as mem0Smoke } from "./lib/smoke.mjs";
15
+ import { ensureState } from "./lib/state.mjs";
16
+ import { summary as mem0Summary } from "./lib/summary.mjs";
17
+ import { resolveApiUrl, resolvePorts as resolveMem0Url } from "./lib/url.mjs";
18
+ import { beforeStack as checkBeforeStack, validateMem0 } from "./lib/validate.mjs";
19
+
20
+ // Called by scripts/setup.mjs (the stack .env writer), not by the wizard.
21
+ export { prepareStackEnv, setupNotices } from "./lib/stack-env.mjs";
22
+
23
+ /** @typedef {import('../../wizard/lib/provider-wizard/helpers.mjs').ProviderWizardHelpers} Helpers */
24
+
25
+ /**
26
+ * Quiet: seed ctx.mem0 and read the [memory.mem0] table (api_url, env_file). Also takes the
27
+ * API key out of process.env (a module-private variable), before preflight starts docker and
28
+ * curl, so no child process inherits it.
29
+ * @param {Record<string, any>} ctx
30
+ */
31
+ export async function init(ctx) {
32
+ captureKeyFromEnv();
33
+ ensureState(ctx);
34
+ applyConfig(ctx);
35
+ }
36
+
37
+ /**
38
+ * Quiet: remember the URL saved in wizard.conf for `--up`.
39
+ * @param {Record<string, any>} ctx
40
+ * @param {Helpers} h
41
+ */
42
+ export async function hydrate(ctx, h) {
43
+ const state = ensureState(ctx);
44
+ applyConfig(ctx);
45
+ state.savedUrl = (h.envValue(ctx.wizardConf, "MEM0_API_URL") || "").trim();
46
+ }
47
+
48
+ /**
49
+ * Quiet: the HOST view of the server URL. Without this hook the manifest's hostPortEnv
50
+ * would derive http://127.0.0.1:<MEM0_PORT>, which is wrong for a remote server.
51
+ * @param {Record<string, any>} ctx
52
+ * @param {Helpers} h
53
+ * @returns {string}
54
+ */
55
+ export function apiBase(ctx, h) {
56
+ return resolveApiUrl(ctx, h);
57
+ }
58
+
59
+ /**
60
+ * Announced: there is no local port to choose. Validates the URL and exports
61
+ * MEM0_API_URL so compose and scripts/setup.mjs see it.
62
+ */
63
+ export const resolvePorts = resolveMem0Url;
64
+
65
+ /**
66
+ * Announced step "URL, key and login": server URL, API key into the mode 600 env file.
67
+ * @param {Record<string, any>} ctx
68
+ * @param {Helpers} h
69
+ */
70
+ export async function configureModelRouting(ctx, h) {
71
+ await configureUrlAndKey(ctx, h);
72
+ }
73
+
74
+ /**
75
+ * Quiet: hand the URL to scripts/setup.mjs, which writes MEM0_API_URL, MEM0_INDEXER_URL,
76
+ * COMPOSE_PROFILES and LITELLM_CONFIG into the stack .env. Never a secret.
77
+ * @param {Record<string, any>} ctx
78
+ * @param {Helpers} h
79
+ */
80
+ export function stackEnv(ctx, h) {
81
+ return {
82
+ description:
83
+ "scripts/setup.mjs: write .env (MEM0_API_URL, MEM0_INDEXER_URL, no secrets), compose.override.yaml",
84
+ env: { MEM0_API_URL: resolveApiUrl(ctx, h) },
85
+ };
86
+ }
87
+
88
+ /** Announced: print SDK / MCP / REST install paths (pluginPackage stays null). */
89
+ export const setupCodingAgent = printCodingAgentPaths;
90
+
91
+ /** Announced: warn (never fail) when the server is unreachable; check the env file mode. */
92
+ export const beforeStack = checkBeforeStack;
93
+
94
+ /** Announced: GET /docs, retain verbatim, search, delete (X-API-Key when a key exists). */
95
+ export const smoke = mem0Smoke;
96
+
97
+ /**
98
+ * Announced: appends to ctx.validationErrors / ctx.validationChecks.
99
+ * @param {Record<string, any>} ctx
100
+ * @param {Helpers} h
101
+ */
102
+ export async function extendValidation(ctx, h) {
103
+ validateMem0(ctx, h);
104
+ }
105
+
106
+ /**
107
+ * Quiet: `[memory.mem0]` for config.toml (api_url, optional env_file; never a key).
108
+ * @param {Record<string, any>} ctx
109
+ * @param {Helpers} h
110
+ */
111
+ export function configToml(ctx, h) {
112
+ return buildConfigToml(ctx, resolveApiUrl(ctx, h));
113
+ }
114
+
115
+ /**
116
+ * Quiet: lines for the closing summary.
117
+ * @param {Record<string, any>} ctx
118
+ * @param {Helpers} h
119
+ */
120
+ export function summary(ctx, h) {
121
+ return mem0Summary(ctx, h);
122
+ }
123
+
124
+ /**
125
+ * Clack-only: ask the URL and the (masked) API key, return the environment that makes the
126
+ * wizard skip its own prompts (WIZARD_SKIP_BACKEND_STEP).
127
+ * @param {Record<string, any>} ctx
128
+ * @param {typeof import("@clack/prompts")} ui
129
+ * @param {Helpers} h
130
+ */
131
+ export async function clackSetup(ctx, ui, h) {
132
+ return askInClack(ctx, ui, h);
133
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "id": "zep",
3
+ "label": "Zep",
4
+ "status": "planned",
5
+ "notes": "Placeholder manifest. Zep Cloud/self-hosted API integration is not wired; track via issues before changing status.",
6
+ "settings": {
7
+ "nativeDocs": "https://docs.getzep.com/",
8
+ "nativeConfigPaths": [],
9
+ "wizardOwns": []
10
+ },
11
+ "compose": {
12
+ "profile": "zep",
13
+ "files": [],
14
+ "services": [],
15
+ "healthService": null,
16
+ "healthPath": null,
17
+ "hostPortEnv": null,
18
+ "defaultHostPort": null
19
+ },
20
+ "indexer": {
21
+ "apiUrlEnv": "ZEP_API_URL",
22
+ "sink": "zep"
23
+ },
24
+ "codingAgent": {
25
+ "pluginPackage": null,
26
+ "configTemplate": null
27
+ },
28
+ "wizard": {}
29
+ }