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