@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.
- package/.agents/skills/openmem/SKILL.md +34 -0
- package/LICENSE +190 -0
- package/README.md +140 -2
- package/bin/hindsight-project-index +530 -0
- package/cli/coding-agent/access-policy.mjs +38 -0
- package/cli/coding-agent/agents.mjs +34 -0
- package/cli/coding-agent/bank-from-cwd.mjs +140 -0
- package/cli/coding-agent/capabilities.mjs +34 -0
- package/cli/coding-agent/context-store.mjs +81 -0
- package/cli/coding-agent/context-trim.mjs +897 -0
- package/cli/coding-agent/file-io.mjs +72 -0
- package/cli/coding-agent/hook-harnesses.mjs +88 -0
- package/cli/coding-agent/hook-runner.mjs +284 -0
- package/cli/coding-agent/install-access.mjs +119 -0
- package/cli/coding-agent/install-context-proxy.mjs +333 -0
- package/cli/coding-agent/install-rtk.mjs +60 -0
- package/cli/coding-agent/install-shim.mjs +201 -0
- package/cli/coding-agent/opencode-config.mjs +107 -0
- package/cli/coding-agent/opencode-plugin.mjs +132 -0
- package/cli/coding-agent/opencode2-plugin/index.js +299 -0
- package/cli/coding-agent/recall-frame.mjs +29 -0
- package/cli/coding-agent/remove-upstream-access.mjs +122 -0
- package/cli/coding-agent/runtime.mjs +40 -0
- package/cli/coding-agent/session-db.mjs +626 -0
- package/cli/coding-agent/session-facade.mjs +86 -0
- package/cli/coding-agent/toml-edit.mjs +161 -0
- package/cli/cognee-mcp.mjs +260 -0
- package/cli/context-management.mjs +254 -0
- package/cli/feed-sync.mjs +27 -0
- package/cli/install-coding-agent.mjs +298 -0
- package/cli/install-memory-mcp.mjs +426 -0
- package/cli/is-main.mjs +18 -0
- package/cli/mcp/context-sql-runner.mjs +58 -0
- package/cli/mcp/context-store.mjs +670 -0
- package/cli/mcp/context.mjs +61 -0
- package/cli/mcp/export/cognee-dataset-name.mjs +12 -0
- package/cli/mcp/export/cognee.mjs +119 -0
- package/cli/mcp/export/hindsight.mjs +48 -0
- package/cli/mcp/export/index.mjs +26 -0
- package/cli/mcp/export/mem0.mjs +31 -0
- package/cli/mcp/knowledge.mjs +333 -0
- package/cli/mcp/llm-synthesize.mjs +50 -0
- package/cli/mcp/mem0-scope-id.mjs +22 -0
- package/cli/mcp/memory-facade.mjs +521 -0
- package/cli/mcp/memory-reflect-consolidate.mjs +120 -0
- package/cli/mcp/notebooklm-banks.mjs +21 -0
- package/cli/mcp/notebooklm-hindsight-rest.mjs +45 -0
- package/cli/mcp/notebooklm-provider.mjs +225 -0
- package/cli/mcp/pmm-records.mjs +23 -0
- package/cli/mcp/portability.mjs +158 -0
- package/cli/mcp/stdio.mjs +65 -0
- package/cli/mcp/unified-mcp-install.mjs +65 -0
- package/cli/mem0-mcp.mjs +108 -0
- package/cli/notebooklm-mcp.mjs +236 -0
- package/cli/openmem-mcp.mjs +38 -0
- package/cli/openmem.mjs +222 -0
- package/cli/roots.mjs +85 -0
- package/cli/schedule.mjs +176 -0
- package/cli/stack.mjs +438 -0
- package/cli/switch-memory-provider.mjs +358 -0
- package/cli/wiring-report.mjs +125 -0
- package/compose.bench.yaml +12 -0
- package/compose.yaml +227 -0
- package/config/coding-agent.json +13 -0
- package/config/hindsight.env.example +26 -0
- package/config/index.yml +54 -0
- package/config/litellm.lemond.yaml +40 -0
- package/config/litellm.ollama.yaml +33 -0
- package/config/notebooklm.yml +33 -0
- package/config/openmem.toml.example +124 -0
- package/docs/OPENMEM.md +23 -0
- package/docs/architecture.md +245 -0
- package/docs/assets/README.md +10 -0
- package/docs/assets/setup-demo.gif +0 -0
- package/docs/coding-agent-hooks.md +75 -0
- package/docs/coding-agent-scopes.md +22 -0
- package/docs/coding-agents.md +71 -0
- package/docs/cognee.md +37 -0
- package/docs/compatibility.md +80 -0
- package/docs/concepts.md +135 -0
- package/docs/context-management.md +255 -0
- package/docs/context-proxy.md +115 -0
- package/docs/context-research.md +215 -0
- package/docs/context-store.md +277 -0
- package/docs/hindsight.md +77 -0
- package/docs/index-sources-roadmap.md +38 -0
- package/docs/index-sources.md +39 -0
- package/docs/indexer-sinks.md +122 -0
- package/docs/indexing.md +57 -0
- package/docs/manual-stack-setup.md +74 -0
- package/docs/mcp-install-guide.md +4 -0
- package/docs/mcp-setup.md +104 -0
- package/docs/mem0.md +141 -0
- package/docs/memory-portability.md +73 -0
- package/docs/memory-providers.md +246 -0
- package/docs/meridian-litellm.md +198 -0
- package/docs/model-configuration.md +106 -0
- package/docs/notebooklm.md +83 -0
- package/docs/npx-install.md +172 -0
- package/docs/openmem-config.md +194 -0
- package/docs/provider-wizard-hooks.md +222 -0
- package/docs/quickstart.md +107 -0
- package/docs/setup-wizard.md +117 -0
- package/docs/sources-feeds.md +39 -0
- package/docs/sources-readwise.md +23 -0
- package/docs/sources-zotero.md +22 -0
- package/glama.json +4 -0
- package/indexer/Dockerfile +38 -0
- package/indexer/code_outline.py +146 -0
- package/indexer/feed_sync.py +107 -0
- package/indexer/index_config.py +290 -0
- package/indexer/pytest.ini +5 -0
- package/indexer/sinks/__init__.py +148 -0
- package/indexer/sinks/_http.py +44 -0
- package/indexer/sinks/cognee.py +329 -0
- package/indexer/sinks/hindsight.py +107 -0
- package/indexer/sinks/mem0.py +247 -0
- package/indexer/watch.py +240 -0
- package/package.json +96 -4
- package/providers/_template/compose.yaml +52 -0
- package/providers/cognee/OPERATOR.md +82 -0
- package/providers/cognee/compose.yaml +89 -0
- package/providers/cognee/lib/clack.mjs +157 -0
- package/providers/cognee/lib/coding-agent.mjs +26 -0
- package/providers/cognee/lib/cognee-mcp-install.mjs +153 -0
- package/providers/cognee/lib/config.mjs +98 -0
- package/providers/cognee/lib/env-file.mjs +194 -0
- package/providers/cognee/lib/env-setup.mjs +83 -0
- package/providers/cognee/lib/llm-env.mjs +382 -0
- package/providers/cognee/lib/ports.mjs +25 -0
- package/providers/cognee/lib/smoke.mjs +246 -0
- package/providers/cognee/lib/stack-env.mjs +134 -0
- package/providers/cognee/lib/state.mjs +101 -0
- package/providers/cognee/lib/summary.mjs +56 -0
- package/providers/cognee/lib/validate.mjs +97 -0
- package/providers/cognee/manifest.json +56 -0
- package/providers/cognee/wizard.mjs +134 -0
- package/providers/graphiti/manifest.json +15 -0
- package/providers/hindsight/lib/clack.mjs +347 -0
- package/providers/hindsight/lib/coding-agent.mjs +204 -0
- package/providers/hindsight/lib/config.mjs +79 -0
- package/providers/hindsight/lib/env-setup.mjs +72 -0
- package/providers/hindsight/lib/litellm-routes.mjs +196 -0
- package/providers/hindsight/lib/meridian.mjs +307 -0
- package/providers/hindsight/lib/model-backend.mjs +243 -0
- package/providers/hindsight/lib/notebooklm-mcp-install.mjs +127 -0
- package/providers/hindsight/lib/ollama-compose.mjs +51 -0
- package/providers/hindsight/lib/ports.mjs +30 -0
- package/providers/hindsight/lib/probe.mjs +54 -0
- package/providers/hindsight/lib/stack-env.mjs +125 -0
- package/providers/hindsight/lib/state.mjs +80 -0
- package/providers/hindsight/lib/summary.mjs +57 -0
- package/providers/hindsight/lib/validate.mjs +81 -0
- package/providers/hindsight/manifest.json +58 -0
- package/providers/hindsight/wizard.mjs +150 -0
- package/providers/letta/manifest.json +29 -0
- package/providers/mem0/OPERATOR.md +143 -0
- package/providers/mem0/compose.yaml +79 -0
- package/providers/mem0/lib/coding-agent.mjs +58 -0
- package/providers/mem0/lib/config.mjs +45 -0
- package/providers/mem0/lib/configure.mjs +201 -0
- package/providers/mem0/lib/dotenv.mjs +71 -0
- package/providers/mem0/lib/env-file.mjs +110 -0
- package/providers/mem0/lib/http.mjs +69 -0
- package/providers/mem0/lib/litellm.mjs +457 -0
- package/providers/mem0/lib/mcp-install.mjs +165 -0
- package/providers/mem0/lib/secret.mjs +30 -0
- package/providers/mem0/lib/smoke.mjs +179 -0
- package/providers/mem0/lib/stack-env.mjs +255 -0
- package/providers/mem0/lib/state.mjs +67 -0
- package/providers/mem0/lib/summary.mjs +88 -0
- package/providers/mem0/lib/url.mjs +106 -0
- package/providers/mem0/lib/validate.mjs +158 -0
- package/providers/mem0/manifest.json +47 -0
- package/providers/mem0/wizard.mjs +146 -0
- package/providers/zep/manifest.json +29 -0
- package/proxy/config.yaml +33 -0
- package/proxy/openmem_context/__init__.py +6 -0
- package/proxy/openmem_context/adapters.py +504 -0
- package/proxy/openmem_context/compress.py +53 -0
- package/proxy/openmem_context/core.py +606 -0
- package/proxy/openmem_context/embed.py +212 -0
- package/proxy/openmem_context/recall.py +49 -0
- package/proxy/openmem_context/session.py +70 -0
- package/proxy/openmem_context/sessiondb.py +276 -0
- package/proxy/openmem_context/settings.py +134 -0
- package/proxy/openmem_context/store.py +129 -0
- package/proxy/openmem_context/tokens.py +123 -0
- package/proxy/openmem_context/ttl.py +78 -0
- package/proxy/openmem_context_hook.py +34 -0
- package/scripts/check-pack.mjs +219 -0
- package/scripts/gen-masks.mjs +83 -0
- package/scripts/gen-masks.sh +4 -0
- package/scripts/promotion-provider-smoke.mjs +34 -0
- package/scripts/run-setup.sh +30 -0
- package/scripts/setup.mjs +107 -0
- package/scripts/setup.sh +5 -0
- package/scripts/spin-up.sh +45 -0
- package/scripts/wizard.sh +5 -0
- package/server.json +34 -0
- package/sync/Dockerfile +27 -0
- package/sync/memory_backend.py +87 -0
- package/sync/notebooklm_sync.py +664 -0
- package/wizard/bin/pm.mjs +97 -0
- package/wizard/bin/setup.mjs +33 -0
- package/wizard/lib/compose-version.mjs +32 -0
- package/wizard/lib/compose.mjs +70 -0
- package/wizard/lib/context-step.mjs +140 -0
- package/wizard/lib/curl.mjs +103 -0
- package/wizard/lib/docker-build.mjs +148 -0
- package/wizard/lib/exec.mjs +41 -0
- package/wizard/lib/indexer.mjs +6 -0
- package/wizard/lib/litellm-env.mjs +55 -0
- package/wizard/lib/load-machine-env.mjs +4 -0
- package/wizard/lib/log.mjs +32 -0
- package/wizard/lib/notebooklm-agent-access.mjs +92 -0
- package/wizard/lib/notebooklm.mjs +214 -0
- package/wizard/lib/parse-args.mjs +87 -0
- package/wizard/lib/paths.mjs +39 -0
- package/wizard/lib/pm-config.mjs +371 -0
- package/wizard/lib/ports.mjs +94 -0
- package/wizard/lib/prompts.mjs +85 -0
- package/wizard/lib/provider-wizard/helpers.mjs +98 -0
- package/wizard/lib/provider-wizard/run-steps.mjs +129 -0
- package/wizard/lib/providers/registry.mjs +262 -0
- package/wizard/lib/run-clack.mjs +108 -0
- package/wizard/lib/run-wizard.mjs +750 -0
- package/wizard/lib/validate-setup.mjs +62 -0
- package/wizard/lib/wizard-action.mjs +34 -0
- package/wizard/lib/wizard-conf.mjs +71 -0
|
@@ -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)", "optional: stack LiteLLM routes for mem0's LLM on 127.0.0.1 (compose profile litellm); you apply the printed mem0 config"]
|
|
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,146 @@
|
|
|
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 { applyProfile, captureAnthropicKey, configureLitellm, stackEnvFor } from "./lib/litellm.mjs";
|
|
14
|
+
import { captureKeyFromEnv } from "./lib/secret.mjs";
|
|
15
|
+
import { smoke as mem0Smoke } from "./lib/smoke.mjs";
|
|
16
|
+
import { ensureState } from "./lib/state.mjs";
|
|
17
|
+
import { summary as mem0Summary } from "./lib/summary.mjs";
|
|
18
|
+
import { resolveApiUrl, resolvePorts as resolveMem0Url } from "./lib/url.mjs";
|
|
19
|
+
import { beforeStack as checkBeforeStack, validateMem0 } from "./lib/validate.mjs";
|
|
20
|
+
|
|
21
|
+
// Called by scripts/setup.mjs (the stack .env writer), not by the wizard.
|
|
22
|
+
export { prepareStackEnv, setupNotices } from "./lib/stack-env.mjs";
|
|
23
|
+
|
|
24
|
+
/** @typedef {import('../../wizard/lib/provider-wizard/helpers.mjs').ProviderWizardHelpers} Helpers */
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Quiet: seed ctx.mem0 and read the [memory.mem0] table (api_url, env_file). Also takes the
|
|
28
|
+
* API key out of process.env (a module-private variable), before preflight starts docker and
|
|
29
|
+
* curl, so no child process inherits it.
|
|
30
|
+
* @param {Record<string, any>} ctx
|
|
31
|
+
*/
|
|
32
|
+
export async function init(ctx) {
|
|
33
|
+
captureKeyFromEnv();
|
|
34
|
+
captureAnthropicKey();
|
|
35
|
+
ensureState(ctx);
|
|
36
|
+
applyConfig(ctx);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Quiet: remember the URL saved in wizard.conf for `--up`.
|
|
41
|
+
* @param {Record<string, any>} ctx
|
|
42
|
+
* @param {Helpers} h
|
|
43
|
+
*/
|
|
44
|
+
export async function hydrate(ctx, h) {
|
|
45
|
+
const state = ensureState(ctx);
|
|
46
|
+
applyConfig(ctx);
|
|
47
|
+
state.savedUrl = (h.envValue(ctx.wizardConf, "MEM0_API_URL") || "").trim();
|
|
48
|
+
// `--up`: the stack LiteLLM option saved by the last configure run.
|
|
49
|
+
applyProfile(ctx, h.envValue(ctx.wizardConf, "MEM0_LITELLM") === "1");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Quiet: the HOST view of the server URL. Without this hook the manifest's hostPortEnv
|
|
54
|
+
* would derive http://127.0.0.1:<MEM0_PORT>, which is wrong for a remote server.
|
|
55
|
+
* @param {Record<string, any>} ctx
|
|
56
|
+
* @param {Helpers} h
|
|
57
|
+
* @returns {string}
|
|
58
|
+
*/
|
|
59
|
+
export function apiBase(ctx, h) {
|
|
60
|
+
return resolveApiUrl(ctx, h);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Announced: there is no local port to choose. Validates the URL and exports
|
|
65
|
+
* MEM0_API_URL so compose and scripts/setup.mjs see it.
|
|
66
|
+
*/
|
|
67
|
+
export const resolvePorts = resolveMem0Url;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Announced step "URL, key and login": server URL, API key into the mode 600 env file, then the
|
|
71
|
+
* optional "route mem0's LLM through the stack LiteLLM" (lib/litellm.mjs).
|
|
72
|
+
* @param {Record<string, any>} ctx
|
|
73
|
+
* @param {Helpers} h
|
|
74
|
+
*/
|
|
75
|
+
export async function configureModelRouting(ctx, h) {
|
|
76
|
+
await configureUrlAndKey(ctx, h);
|
|
77
|
+
const state = ensureState(ctx);
|
|
78
|
+
if (!state.url) return; // invalid URL: a dry run continues without the option
|
|
79
|
+
const keys = await configureLitellm(ctx, h);
|
|
80
|
+
applyProfile(ctx, Boolean(keys));
|
|
81
|
+
if (!keys) return;
|
|
82
|
+
// configureUrlAndKey saved MEM0_API_URL alone; save again with the option's keys (wizard.conf is rewritten whole).
|
|
83
|
+
if (ctx.dry) h.info(`[dry-run] record ${Object.keys(keys).join(", ")} in ${ctx.wizardConf}`);
|
|
84
|
+
else h.saveWizardConf(ctx, { MEM0_API_URL: state.url, ...keys }, { pmSections: buildConfigToml(ctx, state.url) });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Quiet: hand the URL to scripts/setup.mjs, which writes MEM0_API_URL, MEM0_INDEXER_URL,
|
|
89
|
+
* COMPOSE_PROFILES and LITELLM_CONFIG into the stack .env. Never a secret.
|
|
90
|
+
* @param {Record<string, any>} ctx
|
|
91
|
+
* @param {Helpers} h
|
|
92
|
+
*/
|
|
93
|
+
export function stackEnv(ctx, h) {
|
|
94
|
+
return {
|
|
95
|
+
description:
|
|
96
|
+
"scripts/setup.mjs: write .env (MEM0_API_URL, MEM0_INDEXER_URL, no secrets), compose.override.yaml",
|
|
97
|
+
env: { MEM0_API_URL: resolveApiUrl(ctx, h), ...stackEnvFor(ctx) },
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Announced: print SDK / MCP / REST install paths (pluginPackage stays null). */
|
|
102
|
+
export const setupCodingAgent = printCodingAgentPaths;
|
|
103
|
+
|
|
104
|
+
/** Announced: warn (never fail) when the server is unreachable; check the env file mode. */
|
|
105
|
+
export const beforeStack = checkBeforeStack;
|
|
106
|
+
|
|
107
|
+
/** Announced: GET /docs, retain verbatim, search, delete (X-API-Key when a key exists). */
|
|
108
|
+
export const smoke = mem0Smoke;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Announced: appends to ctx.validationErrors / ctx.validationChecks.
|
|
112
|
+
* @param {Record<string, any>} ctx
|
|
113
|
+
* @param {Helpers} h
|
|
114
|
+
*/
|
|
115
|
+
export async function extendValidation(ctx, h) {
|
|
116
|
+
validateMem0(ctx, h);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Quiet: `[memory.mem0]` for config.toml (api_url, optional env_file; never a key).
|
|
121
|
+
* @param {Record<string, any>} ctx
|
|
122
|
+
* @param {Helpers} h
|
|
123
|
+
*/
|
|
124
|
+
export function configToml(ctx, h) {
|
|
125
|
+
return buildConfigToml(ctx, resolveApiUrl(ctx, h));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Quiet: lines for the closing summary.
|
|
130
|
+
* @param {Record<string, any>} ctx
|
|
131
|
+
* @param {Helpers} h
|
|
132
|
+
*/
|
|
133
|
+
export function summary(ctx, h) {
|
|
134
|
+
return mem0Summary(ctx, h);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Clack-only: ask the URL and the (masked) API key, return the environment that makes the
|
|
139
|
+
* wizard skip its own prompts (WIZARD_SKIP_BACKEND_STEP).
|
|
140
|
+
* @param {Record<string, any>} ctx
|
|
141
|
+
* @param {typeof import("@clack/prompts")} ui
|
|
142
|
+
* @param {Helpers} h
|
|
143
|
+
*/
|
|
144
|
+
export async function clackSetup(ctx, ui, h) {
|
|
145
|
+
return askInClack(ctx, ui, h);
|
|
146
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# LiteLLM config for the OpenMem context proxy (compose service context-proxy). See docs/context-proxy.md.
|
|
2
|
+
#
|
|
3
|
+
# Claude Code: requests on /v1/messages go to Anthropic. With no ANTHROPIC_API_KEY in the proxy environment, the
|
|
4
|
+
# client's own credentials (Claude subscription OAuth or API key) pass through; the client sends the LiteLLM key in
|
|
5
|
+
# the x-litellm-api-key header.
|
|
6
|
+
#
|
|
7
|
+
# OpenAI-format clients (/v1/chat/completions, /v1/responses) need explicit routes; the "*" catch-all below sends
|
|
8
|
+
# every other model name to Anthropic. Add one entry per model, for example:
|
|
9
|
+
#
|
|
10
|
+
# - model_name: gpt-5
|
|
11
|
+
# litellm_params:
|
|
12
|
+
# model: openai/gpt-5
|
|
13
|
+
# api_key: os.environ/OPENAI_API_KEY
|
|
14
|
+
#
|
|
15
|
+
# and put OPENAI_API_KEY in the env file (CONTEXT_PROXY_ENV_FILE).
|
|
16
|
+
#
|
|
17
|
+
# Codex with ChatGPT login needs no route: LiteLLM's built-in /backend-api/codex/{models,responses} pass-through
|
|
18
|
+
# (PR #41522, in the pinned dev image) forwards the ChatGPT bearer to chatgpt.com; the client sends the LiteLLM key
|
|
19
|
+
# in x-litellm-api-key. The hook sees those calls as call type pass_through_endpoint.
|
|
20
|
+
model_list:
|
|
21
|
+
- model_name: "anthropic/*"
|
|
22
|
+
litellm_params:
|
|
23
|
+
model: "anthropic/*"
|
|
24
|
+
- model_name: "*"
|
|
25
|
+
litellm_params:
|
|
26
|
+
model: "anthropic/*"
|
|
27
|
+
|
|
28
|
+
litellm_settings:
|
|
29
|
+
# Resolved relative to this file: proxy/openmem_context_hook.py
|
|
30
|
+
callbacks: openmem_context_hook.handler
|
|
31
|
+
|
|
32
|
+
general_settings:
|
|
33
|
+
master_key: os.environ/LITELLM_MASTER_KEY
|