@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,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stack LiteLLM's secrets file (litellm.env, mode 600): LITELLM_MASTER_KEY and the
|
|
3
|
+
* provider keys its routes reference as os.environ/<NAME>. Shared by every provider that
|
|
4
|
+
* enables the `litellm` compose service (Hindsight, mem0).
|
|
5
|
+
*/
|
|
6
|
+
import crypto from "node:crypto";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create litellm.env (mode 600) with a fresh LITELLM_MASTER_KEY when it does not exist.
|
|
12
|
+
* @param {string} litellmEnv
|
|
13
|
+
* @returns {boolean} true when the file was created
|
|
14
|
+
*/
|
|
15
|
+
export function ensureLitellmEnv(litellmEnv) {
|
|
16
|
+
if (fs.existsSync(litellmEnv)) return false;
|
|
17
|
+
const key = crypto.randomBytes(24).toString("hex");
|
|
18
|
+
const body =
|
|
19
|
+
"# LiteLLM proxy secrets. Read by the litellm compose service. Do not commit.\n" +
|
|
20
|
+
`LITELLM_MASTER_KEY=sk-${key}\n` +
|
|
21
|
+
"# lemond ignores the key; any non-empty value works.\n" +
|
|
22
|
+
"LEMOND_API_KEY=placeholder\n" +
|
|
23
|
+
"# Hosted models: add the provider key here, e.g. ANTHROPIC_API_KEY=..., and reference it as os.environ/ANTHROPIC_API_KEY.\n";
|
|
24
|
+
fs.mkdirSync(path.dirname(litellmEnv), { recursive: true, mode: 0o700 });
|
|
25
|
+
fs.writeFileSync(litellmEnv, body, { mode: 0o600 });
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set NAME=value in litellm.env (mode 600), creating the file with a master key first when
|
|
31
|
+
* it does not exist. Never prints the value.
|
|
32
|
+
* @param {string} litellmEnv
|
|
33
|
+
* @param {string} name
|
|
34
|
+
* @param {string} value
|
|
35
|
+
* @returns {"unchanged"|"updated"|"added"}
|
|
36
|
+
*/
|
|
37
|
+
export function setLitellmEnvValue(litellmEnv, name, value) {
|
|
38
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(name)) throw new Error(`invalid variable name: ${name}`);
|
|
39
|
+
if (/[\r\n]/.test(value)) throw new Error(`${name} must be one line`);
|
|
40
|
+
ensureLitellmEnv(litellmEnv);
|
|
41
|
+
const lines = fs.readFileSync(litellmEnv, "utf8").replace(/\n$/, "").split("\n");
|
|
42
|
+
const line = `${name}=${value}`;
|
|
43
|
+
const at = lines.findIndex((l) => l.startsWith(`${name}=`));
|
|
44
|
+
if (at >= 0 && lines[at] === line) {
|
|
45
|
+
fs.chmodSync(litellmEnv, 0o600);
|
|
46
|
+
return "unchanged";
|
|
47
|
+
}
|
|
48
|
+
if (at >= 0) lines[at] = line;
|
|
49
|
+
else lines.push(line);
|
|
50
|
+
const tmp = path.join(path.dirname(litellmEnv), `.litellm.env.${process.pid}`);
|
|
51
|
+
fs.writeFileSync(tmp, `${lines.join("\n")}\n`, { mode: 0o600 });
|
|
52
|
+
fs.chmodSync(tmp, 0o600);
|
|
53
|
+
fs.renameSync(tmp, litellmEnv);
|
|
54
|
+
return at >= 0 ? "updated" : "added";
|
|
55
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const tty = Boolean(process.stdout.isTTY);
|
|
2
|
+
const BOLD = tty ? "\x1b[1m" : "";
|
|
3
|
+
const GREEN = tty ? "\x1b[32m" : "";
|
|
4
|
+
const YELLOW = tty ? "\x1b[33m" : "";
|
|
5
|
+
const RED = tty ? "\x1b[31m" : "";
|
|
6
|
+
const RESET = tty ? "\x1b[0m" : "";
|
|
7
|
+
|
|
8
|
+
export function step(title) {
|
|
9
|
+
console.log(`\n${BOLD}== ${title} ==${RESET}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function info(msg) {
|
|
13
|
+
console.log(` ${msg}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ok(msg) {
|
|
17
|
+
console.log(` ${GREEN}ok${RESET} ${msg}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function warn(msg) {
|
|
21
|
+
console.error(` ${YELLOW}warn${RESET} ${msg}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function die(msg) {
|
|
25
|
+
console.error(` ${RED}error${RESET} ${msg}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function problem(ctx, msg) {
|
|
30
|
+
if (ctx.dry) warn(`${msg} (dry run: continuing)`);
|
|
31
|
+
else die(msg);
|
|
32
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in coding-agent NotebookLM MCP access: materialize ~/.hindsight/coding-agent.json
|
|
3
|
+
* as a real file (never write machine settings into the repo template) and toggle
|
|
4
|
+
* notebooklmAccess.enabled.
|
|
5
|
+
*/
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} agentPath ~/.hindsight/coding-agent.json
|
|
11
|
+
* @param {string} templatePath repo config/coding-agent.json
|
|
12
|
+
* @returns {{ path: string, action: "created" | "materialized" | "exists", cfg: Record<string, any> }}
|
|
13
|
+
*/
|
|
14
|
+
export function ensureCodingAgentJsonRealFile(agentPath, templatePath) {
|
|
15
|
+
fs.mkdirSync(path.dirname(agentPath), { recursive: true });
|
|
16
|
+
|
|
17
|
+
let stat = null;
|
|
18
|
+
try {
|
|
19
|
+
stat = fs.lstatSync(agentPath);
|
|
20
|
+
} catch {
|
|
21
|
+
stat = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (stat?.isSymbolicLink()) {
|
|
25
|
+
const raw = fs.readFileSync(agentPath, "utf8");
|
|
26
|
+
const cfg = JSON.parse(raw);
|
|
27
|
+
fs.unlinkSync(agentPath);
|
|
28
|
+
fs.writeFileSync(agentPath, `${JSON.stringify(cfg, null, 2)}\n`);
|
|
29
|
+
return { path: agentPath, action: "materialized", cfg };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (stat?.isFile()) {
|
|
33
|
+
const cfg = JSON.parse(fs.readFileSync(agentPath, "utf8"));
|
|
34
|
+
return { path: agentPath, action: "exists", cfg };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let cfg;
|
|
38
|
+
if (fs.existsSync(templatePath) && fs.statSync(templatePath).isFile()) {
|
|
39
|
+
cfg = JSON.parse(fs.readFileSync(templatePath, "utf8"));
|
|
40
|
+
} else {
|
|
41
|
+
cfg = {
|
|
42
|
+
serverMode: "self-hosted",
|
|
43
|
+
apiUrl: "http://127.0.0.1:8888",
|
|
44
|
+
optInOnly: true,
|
|
45
|
+
optInPaths: [],
|
|
46
|
+
mapPathToBank: {},
|
|
47
|
+
notebooklmAccess: { enabled: false, banks: ["*"] },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
fs.writeFileSync(agentPath, `${JSON.stringify(cfg, null, 2)}\n`);
|
|
51
|
+
return { path: agentPath, action: "created", cfg };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Set notebooklmAccess.enabled on a real coding-agent.json. Never writes templatePath.
|
|
56
|
+
* Materializes a symlink into a regular file first when needed.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} agentPath
|
|
59
|
+
* @param {string} templatePath
|
|
60
|
+
* @param {boolean} enabled
|
|
61
|
+
* @param {{ dry?: boolean }} [opts]
|
|
62
|
+
* @returns {{ dry?: boolean, action?: string, agentPath: string, enabled: boolean, wrote: boolean }}
|
|
63
|
+
*/
|
|
64
|
+
export function setNotebooklmAccessEnabled(agentPath, templatePath, enabled, opts = {}) {
|
|
65
|
+
const absTemplate = path.resolve(templatePath);
|
|
66
|
+
if (opts.dry) {
|
|
67
|
+
return {
|
|
68
|
+
dry: true,
|
|
69
|
+
agentPath,
|
|
70
|
+
enabled,
|
|
71
|
+
wrote: false,
|
|
72
|
+
action: fs.existsSync(agentPath)
|
|
73
|
+
? fs.lstatSync(agentPath).isSymbolicLink()
|
|
74
|
+
? "would-materialize"
|
|
75
|
+
: "would-update"
|
|
76
|
+
: "would-create",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const { cfg, action } = ensureCodingAgentJsonRealFile(agentPath, templatePath);
|
|
81
|
+
// Guard: never write into the shipped template even if agentPath somehow resolves there.
|
|
82
|
+
if (path.resolve(agentPath) === absTemplate) {
|
|
83
|
+
throw new Error(`refusing to write notebooklmAccess into shipped template ${templatePath}`);
|
|
84
|
+
}
|
|
85
|
+
const access =
|
|
86
|
+
cfg.notebooklmAccess && typeof cfg.notebooklmAccess === "object" ? { ...cfg.notebooklmAccess } : {};
|
|
87
|
+
access.enabled = enabled === true;
|
|
88
|
+
if (!Array.isArray(access.banks) || !access.banks.length) access.banks = ["*"];
|
|
89
|
+
cfg.notebooklmAccess = access;
|
|
90
|
+
fs.writeFileSync(agentPath, `${JSON.stringify(cfg, null, 2)}\n`);
|
|
91
|
+
return { agentPath, enabled: access.enabled, wrote: true, action };
|
|
92
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotebookLM opt-in during setup: install the CLI if needed, run `notebooklm login`
|
|
3
|
+
* (browser) when there is no valid session, then inject NOTEBOOKLM_HOME so host
|
|
4
|
+
* commands and wizard.conf agree with the compose bind mount (~/.config/notebooklm).
|
|
5
|
+
* After sync is enabled, optionally opt in coding-agent MCP access (notebooklm_list_banks /
|
|
6
|
+
* notebooklm_search).
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { step, info, ok, warn } from "./log.mjs";
|
|
12
|
+
import { act, commandExists } from "./exec.mjs";
|
|
13
|
+
import { confirm } from "./prompts.mjs";
|
|
14
|
+
import { upsertWizardConf } from "./wizard-conf.mjs";
|
|
15
|
+
import { setNotebooklmAccessEnabled } from "./notebooklm-agent-access.mjs";
|
|
16
|
+
import { installNotebooklmMcp } from "../../providers/hindsight/lib/notebooklm-mcp-install.mjs";
|
|
17
|
+
|
|
18
|
+
/** @typedef {import("./run-wizard.mjs").WizardContext} WizardContext */
|
|
19
|
+
|
|
20
|
+
const MCP_CONFIRM =
|
|
21
|
+
"Allow coding agents to query NotebookLM banks via MCP (notebooklm_list_banks / notebooklm_search)?";
|
|
22
|
+
|
|
23
|
+
/** @param {WizardContext} ctx */
|
|
24
|
+
function codingAgentPaths(ctx) {
|
|
25
|
+
const agentPath = ctx.agentJson || path.join(ctx.home, ".hindsight", "coding-agent.json");
|
|
26
|
+
const templatePath = path.join(ctx.repoRoot, "config/coding-agent.json");
|
|
27
|
+
return { agentPath, templatePath };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* After NotebookLM sync was enabled this run: opt-in MCP access (default yes).
|
|
32
|
+
* @param {WizardContext} ctx
|
|
33
|
+
*/
|
|
34
|
+
export async function offerNotebooklmMcpAccess(ctx) {
|
|
35
|
+
const allow = await confirm(ctx, MCP_CONFIRM, "y");
|
|
36
|
+
const { agentPath, templatePath } = codingAgentPaths(ctx);
|
|
37
|
+
const h = { info, ok, warn };
|
|
38
|
+
|
|
39
|
+
if (!allow) {
|
|
40
|
+
if (ctx.dry) {
|
|
41
|
+
info(`[dry-run] leave notebooklmAccess.enabled false; skip MCP merge (${agentPath})`);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (fs.existsSync(agentPath)) {
|
|
45
|
+
try {
|
|
46
|
+
const st = fs.lstatSync(agentPath);
|
|
47
|
+
if (st.isFile() || st.isSymbolicLink()) {
|
|
48
|
+
setNotebooklmAccessEnabled(agentPath, templatePath, false);
|
|
49
|
+
ok(`notebooklmAccess.enabled=false in ${agentPath}`);
|
|
50
|
+
}
|
|
51
|
+
} catch (err) {
|
|
52
|
+
warn(`could not clear notebooklmAccess.enabled: ${err instanceof Error ? err.message : err}`);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
info("notebooklmAccess left disabled (no coding-agent.json yet)");
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (ctx.dry) {
|
|
61
|
+
const plan = setNotebooklmAccessEnabled(agentPath, templatePath, true, { dry: true });
|
|
62
|
+
info(
|
|
63
|
+
`[dry-run] ${plan.action} ${agentPath} with notebooklmAccess.enabled=true; merge notebooklm MCP into ~/.cursor/mcp.json`,
|
|
64
|
+
);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const result = setNotebooklmAccessEnabled(agentPath, templatePath, true);
|
|
69
|
+
ok(
|
|
70
|
+
`notebooklmAccess.enabled=true in ${agentPath}` +
|
|
71
|
+
(result.action === "materialized" ? " (copied from symlink → regular file)" : ""),
|
|
72
|
+
);
|
|
73
|
+
await installNotebooklmMcp(
|
|
74
|
+
{ home: ctx.home, repoRoot: ctx.repoRoot, agentJson: agentPath, dry: false },
|
|
75
|
+
h,
|
|
76
|
+
);
|
|
77
|
+
ok("Restart Cursor Agent so it picks up the notebooklm MCP");
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Host path the compose service bind-mounts as /data/notebooklm. */
|
|
82
|
+
export function notebooklmHome(ctx) {
|
|
83
|
+
return path.join(ctx.home, ".config/notebooklm");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Env for every host `notebooklm` child: NOTEBOOKLM_HOME points at the shared cookie dir. */
|
|
87
|
+
export function notebooklmEnv(ctx) {
|
|
88
|
+
return { ...process.env, NOTEBOOKLM_HOME: notebooklmHome(ctx) };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Persist NOTEBOOKLM_HOME into the process env and wizard.conf (compose already mounts the path).
|
|
93
|
+
* @param {WizardContext} ctx
|
|
94
|
+
*/
|
|
95
|
+
export function injectNotebooklmHome(ctx) {
|
|
96
|
+
const home = notebooklmHome(ctx);
|
|
97
|
+
process.env.NOTEBOOKLM_HOME = home;
|
|
98
|
+
upsertWizardConf(ctx, { NOTEBOOKLM_HOME: home });
|
|
99
|
+
ok(`NOTEBOOKLM_HOME=${home} (process env + wizard.conf)`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {WizardContext} ctx
|
|
104
|
+
* @returns {boolean}
|
|
105
|
+
*/
|
|
106
|
+
export function notebooklmAuthOk(ctx) {
|
|
107
|
+
if (!commandExists("notebooklm")) return false;
|
|
108
|
+
const check = spawnSync("notebooklm", ["auth", "check", "--test", "--json"], {
|
|
109
|
+
env: notebooklmEnv(ctx),
|
|
110
|
+
stdio: "ignore",
|
|
111
|
+
});
|
|
112
|
+
return check.status === 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Install notebooklm-py + Playwright Chromium via uv when the CLI is missing.
|
|
117
|
+
* @param {WizardContext} ctx
|
|
118
|
+
* @returns {Promise<boolean>} true when `notebooklm` is on PATH afterwards
|
|
119
|
+
*/
|
|
120
|
+
export async function ensureNotebooklmCli(ctx) {
|
|
121
|
+
if (commandExists("notebooklm")) return true;
|
|
122
|
+
if (!commandExists("uv")) {
|
|
123
|
+
warn("uv is not on PATH; install uv, then rerun setup to enable NotebookLM");
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
if (ctx.dry) {
|
|
127
|
+
info(`[dry-run] uv tool install ${ctx.notebooklmPkg} && playwright install chromium`);
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
info(`installing ${ctx.notebooklmPkg} (includes Playwright Chromium)`);
|
|
131
|
+
await act(ctx, `uv tool install ${ctx.notebooklmPkg}`, "uv", ["tool", "install", ctx.notebooklmPkg]);
|
|
132
|
+
await act(
|
|
133
|
+
ctx,
|
|
134
|
+
"playwright install chromium for notebooklm-py",
|
|
135
|
+
"uv",
|
|
136
|
+
["tool", "run", "--from", ctx.notebooklmPkg, "python", "-m", "playwright", "install", "chromium"],
|
|
137
|
+
);
|
|
138
|
+
if (!commandExists("notebooklm")) {
|
|
139
|
+
warn("notebooklm still not on PATH after install (check ~/.local/bin)");
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Open the Google sign-in browser via notebooklm-py. Stdio is inherited so the
|
|
147
|
+
* operator can complete login; no separate "are you done?" prompt.
|
|
148
|
+
* @param {WizardContext} ctx
|
|
149
|
+
* @returns {Promise<boolean>}
|
|
150
|
+
*/
|
|
151
|
+
export async function runNotebooklmLogin(ctx) {
|
|
152
|
+
if (ctx.dry) {
|
|
153
|
+
info("[dry-run] notebooklm login (opens a browser; NOTEBOOKLM_HOME set)");
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
if (ctx.yes) {
|
|
157
|
+
warn("skipping notebooklm login under --yes (needs a browser); log in interactively or rerun without --yes");
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
if (!process.stdin.isTTY) {
|
|
161
|
+
warn("skipping notebooklm login (no TTY); run setup interactively once");
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
fs.mkdirSync(notebooklmHome(ctx), { recursive: true, mode: 0o700 });
|
|
165
|
+
info("opening Google sign-in for NotebookLM (complete it in the browser window)…");
|
|
166
|
+
const r = spawnSync("notebooklm", ["login"], {
|
|
167
|
+
env: notebooklmEnv(ctx),
|
|
168
|
+
stdio: "inherit",
|
|
169
|
+
});
|
|
170
|
+
return r.status === 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @param {WizardContext} ctx
|
|
175
|
+
* @param {(c: WizardContext) => void} enableNotebooklm
|
|
176
|
+
*/
|
|
177
|
+
export async function setupNotebooklm(ctx, enableNotebooklm) {
|
|
178
|
+
step("6. NotebookLM sync (optional, default no)");
|
|
179
|
+
if (!(await confirm(ctx, "Enable the NotebookLM sync? It stores a Google session cookie on this machine", "n"))) {
|
|
180
|
+
info("skipped; the service stays off");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (ctx.dry) {
|
|
184
|
+
info("[dry-run] install notebooklm-py if needed, login if auth fails, inject NOTEBOOKLM_HOME, enable notebooklm.yml");
|
|
185
|
+
await offerNotebooklmMcpAccess(ctx);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
fs.mkdirSync(notebooklmHome(ctx), { recursive: true, mode: 0o700 });
|
|
190
|
+
injectNotebooklmHome(ctx);
|
|
191
|
+
|
|
192
|
+
if (!(await ensureNotebooklmCli(ctx))) return;
|
|
193
|
+
|
|
194
|
+
if (notebooklmAuthOk(ctx)) {
|
|
195
|
+
ok("NotebookLM session already valid");
|
|
196
|
+
enableNotebooklm(ctx);
|
|
197
|
+
await offerNotebooklmMcpAccess(ctx);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
info("no valid NotebookLM session yet; starting login…");
|
|
202
|
+
const loggedIn = await runNotebooklmLogin(ctx);
|
|
203
|
+
if (!loggedIn) {
|
|
204
|
+
warn(`login did not finish; ${ctx.notebookFile} is unchanged. Rerun setup when ready`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!notebooklmAuthOk(ctx)) {
|
|
208
|
+
warn(`auth check failed after login; ${ctx.notebookFile} is unchanged`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
ok("NotebookLM login ok");
|
|
212
|
+
enableNotebooklm(ctx);
|
|
213
|
+
await offerNotebooklmMcpAccess(ctx);
|
|
214
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { die } from "./log.mjs";
|
|
4
|
+
import { parseActionFromArgv } from "./wizard-action.mjs";
|
|
5
|
+
|
|
6
|
+
const HELP = `Setup wizard: takes a fresh clone to a running memory stack for the selected provider. Symlinked as ./setup at the repo root.
|
|
7
|
+
Re-runnable: shows current values as defaults and asks before it replaces config or secrets.
|
|
8
|
+
|
|
9
|
+
./setup interactive (@clack/prompts UI, then the rest)
|
|
10
|
+
./setup --yes accept every default, no prompts
|
|
11
|
+
./setup --dry-run [--yes] print the actions; read-only checks run, nothing is written or started
|
|
12
|
+
./setup --yes --no-smoke skip the smoke retain/recall (for scripted re-runs)
|
|
13
|
+
./setup --rebuild rebuild the images even when their sources are unchanged
|
|
14
|
+
./setup --up [--yes] start the stack using existing wizard.conf
|
|
15
|
+
./setup --down [--yes] stop the stack (docker compose down)
|
|
16
|
+
|
|
17
|
+
PM_PROJECT sets the compose project name (default: openmem). MEMORY_PROVIDER selects the
|
|
18
|
+
memory plugin (default: hindsight). PM_WIZARD_ACTION=configure|up|down. Each provider documents its
|
|
19
|
+
own environment variables (model backend, ports) in providers/<id>/. PM_STACK_ROOT and PM_PACKAGE_ROOT
|
|
20
|
+
split the mutable stack directory from the read-only code directory. The last line is
|
|
21
|
+
"wizard: changed=N": files and containers this run altered.
|
|
22
|
+
Test against a scratch HOME:
|
|
23
|
+
HOME=/tmp/wiz-home PM_PROJECT=pm-wizard-test ./setup --yes`;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} repoRoot
|
|
27
|
+
* @param {string[]} argv
|
|
28
|
+
* @returns {import("./run-wizard.mjs").WizardContext}
|
|
29
|
+
*/
|
|
30
|
+
export function parseWizardFlags(repoRoot, argv) {
|
|
31
|
+
const home = process.env.HOME || os.homedir();
|
|
32
|
+
|
|
33
|
+
const ctx = {
|
|
34
|
+
repoRoot,
|
|
35
|
+
// Read-only code root (wizard/, providers/, scripts/); the stack root is repoRoot.
|
|
36
|
+
packageRoot: process.env.PM_PACKAGE_ROOT ? path.resolve(process.env.PM_PACKAGE_ROOT) : repoRoot,
|
|
37
|
+
home,
|
|
38
|
+
project: process.env.PM_PROJECT || "openmem",
|
|
39
|
+
// Shared per-machine config dir: wizard.conf, index.yml, notebooklm.yml. The name predates
|
|
40
|
+
// provider plugins and stays fixed so existing machines keep their config.
|
|
41
|
+
hsDir: path.join(home, ".config/hindsight"),
|
|
42
|
+
indexFile: path.join(home, ".config/hindsight/index.yml"),
|
|
43
|
+
notebookFile: path.join(home, ".config/hindsight/notebooklm.yml"),
|
|
44
|
+
wizardConf: path.join(home, ".config/hindsight/wizard.conf"),
|
|
45
|
+
smokeBank: "wizard-smoke",
|
|
46
|
+
buildHashFile: path.join(repoRoot, ".build-hash"),
|
|
47
|
+
notebooklmPkg: "notebooklm-py[browser]==0.8.5",
|
|
48
|
+
yes: false,
|
|
49
|
+
dry: false,
|
|
50
|
+
smoke: true,
|
|
51
|
+
rebuild: false,
|
|
52
|
+
// True when the Clack UI already asked the provider's questions (WIZARD_SKIP_BACKEND_STEP=1).
|
|
53
|
+
skipBackendStep: process.env.WIZARD_SKIP_BACKEND_STEP === "1",
|
|
54
|
+
action: parseActionFromArgv(argv),
|
|
55
|
+
providerId: "",
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
for (const arg of argv) {
|
|
59
|
+
switch (arg) {
|
|
60
|
+
case "--up":
|
|
61
|
+
case "--down":
|
|
62
|
+
break;
|
|
63
|
+
case "--yes":
|
|
64
|
+
case "-y":
|
|
65
|
+
ctx.yes = true;
|
|
66
|
+
break;
|
|
67
|
+
case "--dry-run":
|
|
68
|
+
ctx.dry = true;
|
|
69
|
+
break;
|
|
70
|
+
case "--no-smoke":
|
|
71
|
+
ctx.smoke = false;
|
|
72
|
+
break;
|
|
73
|
+
case "--rebuild":
|
|
74
|
+
ctx.rebuild = true;
|
|
75
|
+
break;
|
|
76
|
+
case "-h":
|
|
77
|
+
case "--help":
|
|
78
|
+
console.log(HELP);
|
|
79
|
+
process.exit(0);
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
die(`unknown option: ${arg} (see --help)`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return ctx;
|
|
87
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
export function expandHome(p, home = process.env.HOME || os.homedir()) {
|
|
6
|
+
if (p === "~") return home;
|
|
7
|
+
if (p.startsWith("~/")) return path.join(home, p.slice(2));
|
|
8
|
+
return p;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function envValue(filePath, key) {
|
|
12
|
+
if (!fs.existsSync(filePath)) return "";
|
|
13
|
+
const prefix = `${key}=`;
|
|
14
|
+
for (const line of fs.readFileSync(filePath, "utf8").split("\n")) {
|
|
15
|
+
if (line.startsWith(prefix)) return line.slice(prefix.length);
|
|
16
|
+
}
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ensureHsDir(ctx) {
|
|
21
|
+
if (ctx.dry) return;
|
|
22
|
+
fs.mkdirSync(ctx.hsDir, { recursive: true, mode: 0o700 });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function realpathSafe(p) {
|
|
26
|
+
try {
|
|
27
|
+
return fs.realpathSync(p);
|
|
28
|
+
} catch {
|
|
29
|
+
return path.resolve(p);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Mount point for the shared indexer's state. The directory name predates provider
|
|
35
|
+
* plugins and stays fixed so existing state keeps working.
|
|
36
|
+
*/
|
|
37
|
+
export function indexerStateDir(ctx) {
|
|
38
|
+
return path.join(ctx.home, ".cache", "hindsight-project-index");
|
|
39
|
+
}
|