@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,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step "9. Coding-agent plugin", Hindsight half: ensure ~/.hindsight/coding-agent.json
|
|
3
|
+
* exists (seeded from the tracked template when missing) and check that its optInPaths
|
|
4
|
+
* cover the indexed roots. Machine-local keys such as notebooklmAccess must live in a
|
|
5
|
+
* real file — never in the repo template — so we do not replace a materialised config
|
|
6
|
+
* with a symlink (that used to wipe notebooklmAccess.enabled after step 6).
|
|
7
|
+
* The meta wizard installs the plugin package itself from the manifest.
|
|
8
|
+
*/
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { realpathSync } from "node:fs";
|
|
12
|
+
import { installNotebooklmMcp } from "./notebooklm-mcp-install.mjs";
|
|
13
|
+
import { setNotebooklmAccessEnabled } from "../../../wizard/lib/notebooklm-agent-access.mjs";
|
|
14
|
+
import { ensureState } from "./state.mjs";
|
|
15
|
+
|
|
16
|
+
/** @typedef {import("../../../wizard/lib/provider-wizard/helpers.mjs").ProviderWizardHelpers} Helpers */
|
|
17
|
+
|
|
18
|
+
function realpathSafe(p) {
|
|
19
|
+
try {
|
|
20
|
+
return realpathSync(p);
|
|
21
|
+
} catch {
|
|
22
|
+
return path.resolve(p);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @param {string} filePath */
|
|
27
|
+
function readJsonSafe(filePath) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** True when notebooklm.yml opts the sync service in. */
|
|
36
|
+
function notebooklmSyncEnabled(ctx) {
|
|
37
|
+
const yml = ctx.notebookFile || path.join(ctx.home, ".config/hindsight/notebooklm.yml");
|
|
38
|
+
if (!fs.existsSync(yml)) return false;
|
|
39
|
+
return /^enabled:\s*true\b/m.test(fs.readFileSync(yml, "utf8"));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Seed or keep a real coding-agent.json. Never leave (or create) a symlink to the shipped
|
|
44
|
+
* template — machine keys such as notebooklmAccess must not write into the repo.
|
|
45
|
+
*
|
|
46
|
+
* @param {Record<string, any>} ctx
|
|
47
|
+
* @param {Helpers} h
|
|
48
|
+
*/
|
|
49
|
+
async function linkAgentConfig(ctx, h) {
|
|
50
|
+
const rel = ctx.memoryProvider.codingAgent?.configTemplate;
|
|
51
|
+
if (!rel) return;
|
|
52
|
+
const target = path.join(ctx.repoRoot, rel);
|
|
53
|
+
let stat;
|
|
54
|
+
try {
|
|
55
|
+
stat = fs.lstatSync(ctx.agentJson);
|
|
56
|
+
} catch {
|
|
57
|
+
stat = null;
|
|
58
|
+
}
|
|
59
|
+
if (stat?.isSymbolicLink()) {
|
|
60
|
+
// Copy current contents into a regular file so later writes never hit the template.
|
|
61
|
+
if (ctx.dry) {
|
|
62
|
+
h.info(`[dry-run] materialize ${ctx.agentJson} (symlink → regular file)`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const raw = fs.readFileSync(ctx.agentJson, "utf8");
|
|
66
|
+
fs.unlinkSync(ctx.agentJson);
|
|
67
|
+
fs.mkdirSync(path.dirname(ctx.agentJson), { recursive: true });
|
|
68
|
+
fs.writeFileSync(ctx.agentJson, raw.endsWith("\n") ? raw : `${raw}\n`);
|
|
69
|
+
h.ok(`materialized ${ctx.agentJson} (was symlink → regular file)`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (stat?.isFile()) {
|
|
73
|
+
// Keep the real file. Replacing it with a symlink to the shipped template drops
|
|
74
|
+
// notebooklmAccess.enabled and breaks the NotebookLM MCP on the next Cursor start.
|
|
75
|
+
h.ok(`${ctx.agentJson} is a machine-local file (not replaced with a symlink)`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Missing: copy the template into a real file so later notebooklmAccess writes never
|
|
79
|
+
// land in the repo.
|
|
80
|
+
if (ctx.dry) {
|
|
81
|
+
h.info(`[dry-run] create ${ctx.agentJson} from ${rel} (regular file, not a symlink)`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
fs.mkdirSync(path.dirname(ctx.agentJson), { recursive: true });
|
|
85
|
+
const seeded = readJsonSafe(target) || {
|
|
86
|
+
serverMode: "self-hosted",
|
|
87
|
+
apiUrl: "http://127.0.0.1:8888",
|
|
88
|
+
optInOnly: true,
|
|
89
|
+
optInPaths: [],
|
|
90
|
+
mapPathToBank: {},
|
|
91
|
+
notebooklmAccess: { enabled: false, banks: ["*"] },
|
|
92
|
+
};
|
|
93
|
+
fs.writeFileSync(ctx.agentJson, `${JSON.stringify(seeded, null, 2)}\n`);
|
|
94
|
+
h.ok(`created ${ctx.agentJson} from ${rel}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* If NotebookLM sync is on, keep notebooklmAccess.enabled and the Cursor MCP entry.
|
|
99
|
+
* Materialises a symlink into a real file first so the flag never writes into the template.
|
|
100
|
+
*
|
|
101
|
+
* @param {Record<string, any>} ctx
|
|
102
|
+
* @param {Helpers} h
|
|
103
|
+
*/
|
|
104
|
+
async function ensureNotebooklmAccess(ctx, h) {
|
|
105
|
+
if (!notebooklmSyncEnabled(ctx)) return;
|
|
106
|
+
const templatePath = path.join(ctx.repoRoot, "config/coding-agent.json");
|
|
107
|
+
if (ctx.dry) {
|
|
108
|
+
h.info(`[dry-run] keep notebooklmAccess.enabled=true (NotebookLM sync is on)`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const result = setNotebooklmAccessEnabled(ctx.agentJson, templatePath, true);
|
|
112
|
+
h.ok(
|
|
113
|
+
`notebooklmAccess.enabled=true in ${ctx.agentJson}` +
|
|
114
|
+
(result.action === "materialized" ? " (materialized symlink → regular file)" : ""),
|
|
115
|
+
);
|
|
116
|
+
await installNotebooklmMcp(
|
|
117
|
+
{ home: ctx.home, repoRoot: ctx.repoRoot, agentJson: ctx.agentJson, dry: false },
|
|
118
|
+
h,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {Record<string, any>} ctx
|
|
124
|
+
* @param {Helpers} h
|
|
125
|
+
* @returns {string[]}
|
|
126
|
+
*/
|
|
127
|
+
function indexRoots(ctx, h) {
|
|
128
|
+
const source = fs.existsSync(ctx.indexFile)
|
|
129
|
+
? ctx.indexFile
|
|
130
|
+
: path.join(ctx.repoRoot, "config/index.yml");
|
|
131
|
+
const roots = [];
|
|
132
|
+
for (const line of fs.readFileSync(source, "utf8").split("\n")) {
|
|
133
|
+
const m = line.match(/^\s*- path:\s*"?([^"]+)"?/);
|
|
134
|
+
if (m) roots.push(h.expandHome(m[1].trim(), ctx.home));
|
|
135
|
+
}
|
|
136
|
+
return roots;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @param {Record<string, any>} ctx
|
|
141
|
+
* @param {Helpers} h
|
|
142
|
+
*/
|
|
143
|
+
async function checkOptIn(ctx, h) {
|
|
144
|
+
// Prefer the live agent config (real file or symlink target). Never write
|
|
145
|
+
// machine paths into the shipped template unless that is what agentJson resolves to.
|
|
146
|
+
let target = ctx.agentJson;
|
|
147
|
+
try {
|
|
148
|
+
if (fs.lstatSync(ctx.agentJson).isSymbolicLink()) {
|
|
149
|
+
target = realpathSafe(fs.readlinkSync(ctx.agentJson));
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
target = path.join(ctx.repoRoot, "config/coding-agent.json");
|
|
153
|
+
}
|
|
154
|
+
if (!fs.existsSync(target)) {
|
|
155
|
+
target = path.join(ctx.repoRoot, "config/coding-agent.json");
|
|
156
|
+
}
|
|
157
|
+
const cfg = JSON.parse(fs.readFileSync(target, "utf8"));
|
|
158
|
+
if (!Array.isArray(cfg.optInPaths)) cfg.optInPaths = [];
|
|
159
|
+
const opt = cfg.optInPaths.map((p) => path.resolve(h.expandHome(p, ctx.home)));
|
|
160
|
+
const missing = [];
|
|
161
|
+
for (const root of indexRoots(ctx, h)) {
|
|
162
|
+
const abs = path.resolve(root);
|
|
163
|
+
const covered = opt.some((p) => abs === p || abs.startsWith(`${p.replace(/\/$/, "")}/`));
|
|
164
|
+
if (!covered) missing.push(root);
|
|
165
|
+
}
|
|
166
|
+
if (!missing.length) {
|
|
167
|
+
h.ok(`optInPaths in ${target} covers the indexed roots`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
h.warn("optInPaths does not cover:");
|
|
171
|
+
for (const m of missing) h.info(` ${m}`);
|
|
172
|
+
if (ctx.dry) {
|
|
173
|
+
h.info(`[dry-run] offer to add these to optInPaths in ${target}`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const tracked = path.join(ctx.repoRoot, "config/coding-agent.json");
|
|
177
|
+
const prompt =
|
|
178
|
+
realpathSafe(target) === realpathSafe(tracked)
|
|
179
|
+
? "Add them to optInPaths in the tracked file config/coding-agent.json (shows in git status)?"
|
|
180
|
+
: `Add them to optInPaths in ${target}?`;
|
|
181
|
+
if (await h.confirm(ctx, prompt, "n")) {
|
|
182
|
+
for (const root of missing) {
|
|
183
|
+
if (!cfg.optInPaths.includes(root)) cfg.optInPaths.push(root);
|
|
184
|
+
}
|
|
185
|
+
fs.writeFileSync(target, `${JSON.stringify(cfg, null, 2)}\n`);
|
|
186
|
+
h.ok(`updated ${target}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @param {Record<string, any>} ctx
|
|
192
|
+
* @param {Helpers} h
|
|
193
|
+
*/
|
|
194
|
+
export async function setupCodingAgent(ctx, h) {
|
|
195
|
+
ensureState(ctx);
|
|
196
|
+
await linkAgentConfig(ctx, h);
|
|
197
|
+
await checkOptIn(ctx, h);
|
|
198
|
+
// Step 6 may have set notebooklmAccess on a real file; linkAgentConfig used to
|
|
199
|
+
// symlink over it. Re-assert after linking, then register the MCP.
|
|
200
|
+
await ensureNotebooklmAccess(ctx, h);
|
|
201
|
+
if (!notebooklmSyncEnabled(ctx)) {
|
|
202
|
+
await installNotebooklmMcp(ctx, h);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hindsight's slice of ~/.config/personal-memory/config.toml: [stack] ports and the
|
|
3
|
+
* [model] table. The meta wizard owns [memory], [stack].project, [wizard] and [tasks].
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Flat Hindsight defaults from a merged TOML config.
|
|
8
|
+
* @param {Record<string, unknown>} cfg
|
|
9
|
+
*/
|
|
10
|
+
export function defaultsFromConfig(cfg) {
|
|
11
|
+
const stack = /** @type {Record<string, unknown>} */ (cfg.stack || {});
|
|
12
|
+
const model = /** @type {Record<string, unknown>} */ (cfg.model || {});
|
|
13
|
+
const lemond = /** @type {Record<string, unknown>} */ (model.lemond || {});
|
|
14
|
+
const ollama = /** @type {Record<string, unknown>} */ (model.ollama || {});
|
|
15
|
+
const meridian = /** @type {Record<string, unknown>} */ (model.meridian || {});
|
|
16
|
+
return {
|
|
17
|
+
apiPort: stack.api_port != null ? String(stack.api_port) : "",
|
|
18
|
+
cpPort: stack.control_plane_port != null ? String(stack.control_plane_port) : "",
|
|
19
|
+
modelBackend: String(model.backend || ""),
|
|
20
|
+
modelRetain: String(model.retain || ""),
|
|
21
|
+
modelReflect: String(model.reflect || ""),
|
|
22
|
+
modelApiBase: String(model.api_base || ""),
|
|
23
|
+
lemondHost: String(lemond.host || ""),
|
|
24
|
+
ollamaHost: String(ollama.host || ""),
|
|
25
|
+
meridianHost: String(meridian.host || ""),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Apply config defaults into process.env when env/CLI did not set them.
|
|
31
|
+
* @param {Record<string, unknown>} cfg
|
|
32
|
+
*/
|
|
33
|
+
export function applyConfigEnv(cfg) {
|
|
34
|
+
const d = defaultsFromConfig(cfg);
|
|
35
|
+
if (d.apiPort && !process.env.HINDSIGHT_PORT) process.env.HINDSIGHT_PORT = d.apiPort;
|
|
36
|
+
if (d.cpPort && !process.env.HINDSIGHT_CP_PORT) process.env.HINDSIGHT_CP_PORT = d.cpPort;
|
|
37
|
+
if (d.modelBackend && !process.env.PM_BACKEND) process.env.PM_BACKEND = d.modelBackend;
|
|
38
|
+
if (d.modelRetain && !process.env.PM_DEFAULT_MODEL) {
|
|
39
|
+
process.env.PM_DEFAULT_MODEL = d.modelRetain;
|
|
40
|
+
}
|
|
41
|
+
if (d.modelReflect && !process.env.PM_REFLECT_MODEL) {
|
|
42
|
+
process.env.PM_REFLECT_MODEL = d.modelReflect;
|
|
43
|
+
}
|
|
44
|
+
if (d.modelApiBase && !process.env.MEMORY_API_BASE) {
|
|
45
|
+
process.env.MEMORY_API_BASE = d.modelApiBase;
|
|
46
|
+
}
|
|
47
|
+
if (d.lemondHost && !process.env.LEMOND_BASE) process.env.LEMOND_BASE = d.lemondHost;
|
|
48
|
+
if (d.meridianHost && !process.env.MERIDIAN_BASE) process.env.MERIDIAN_BASE = d.meridianHost;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Tables merged into config.toml when the wizard saves it.
|
|
53
|
+
* @param {Record<string, any>} ctx
|
|
54
|
+
* @returns {Record<string, Record<string, unknown>>}
|
|
55
|
+
*/
|
|
56
|
+
export function configToml(ctx) {
|
|
57
|
+
const model = {
|
|
58
|
+
backend: ctx.backend,
|
|
59
|
+
retain: ctx.model,
|
|
60
|
+
reflect: ctx.reflectModel || "",
|
|
61
|
+
api_base: ctx.apiBase || "",
|
|
62
|
+
};
|
|
63
|
+
if (ctx.backend === "lemond" || ctx.lemondBase) {
|
|
64
|
+
model.lemond = { host: ctx.lemondBase };
|
|
65
|
+
}
|
|
66
|
+
if (ctx.backend === "ollama" || ctx.ollamaBase) {
|
|
67
|
+
model.ollama = { host: ctx.ollamaBase };
|
|
68
|
+
}
|
|
69
|
+
if (ctx.backend === "meridian" && ctx.meridianBase) {
|
|
70
|
+
model.meridian = { host: ctx.meridianBase };
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
stack: {
|
|
74
|
+
api_port: Number(ctx.portApi || 8888),
|
|
75
|
+
control_plane_port: Number(ctx.portCp || 9999),
|
|
76
|
+
},
|
|
77
|
+
model,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step "4. Secrets and env", Hindsight half: ~/.config/hindsight/hindsight.env from the
|
|
3
|
+
* shipped example, then the stack setup script (litellm.env, .env, compose.override.yaml),
|
|
4
|
+
* then wiring hindsight.env to the LiteLLM proxy.
|
|
5
|
+
*/
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { ensureState } from "./state.mjs";
|
|
9
|
+
|
|
10
|
+
/** @typedef {import("../../../wizard/lib/provider-wizard/helpers.mjs").ProviderWizardHelpers} Helpers */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* True when hindsight.env points Hindsight at the LiteLLM proxy with the master key.
|
|
14
|
+
* @param {Record<string, any>} ctx
|
|
15
|
+
* @param {Helpers} h
|
|
16
|
+
*/
|
|
17
|
+
export function llmWired(ctx, h) {
|
|
18
|
+
if (h.envValue(ctx.envFile, "HINDSIGHT_API_LLM_BASE_URL") !== "http://litellm:4000/v1") {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const key = h.envValue(ctx.envFile, "HINDSIGHT_API_LLM_API_KEY");
|
|
22
|
+
const master = h.envValue(ctx.litellmEnv, "LITELLM_MASTER_KEY");
|
|
23
|
+
return Boolean(key && master && key === master);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Arguments for the meta wizard's stack setup script.
|
|
28
|
+
* @param {Record<string, any>} ctx
|
|
29
|
+
*/
|
|
30
|
+
export function stackEnv(ctx) {
|
|
31
|
+
ensureState(ctx);
|
|
32
|
+
let backend = ctx.backend;
|
|
33
|
+
// Own-route backends run from litellm.yaml; setup.mjs only knows lemond and ollama.
|
|
34
|
+
if (backend === "custom" || backend === "meridian") backend = "lemond";
|
|
35
|
+
/** @type {Record<string, string>} */
|
|
36
|
+
const env = { MEMORY_BACKEND: backend };
|
|
37
|
+
if (ctx.ollamaInCompose) env.MEMORY_OLLAMA_IN_COMPOSE = "1";
|
|
38
|
+
return {
|
|
39
|
+
description:
|
|
40
|
+
"scripts/setup.sh: write .env, litellm.env (mode 600 when new), compose.override.yaml",
|
|
41
|
+
env,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {Record<string, any>} ctx
|
|
47
|
+
* @param {Helpers} h
|
|
48
|
+
*/
|
|
49
|
+
export async function setupEnv(ctx, h) {
|
|
50
|
+
ensureState(ctx);
|
|
51
|
+
const example = path.join(ctx.repoRoot, "config/hindsight.env.example");
|
|
52
|
+
if (fs.existsSync(ctx.envFile)) h.ok(`kept ${ctx.envFile}`);
|
|
53
|
+
else if (ctx.dry) h.info(`[dry-run] create ${ctx.envFile} from config/hindsight.env.example (mode 600)`);
|
|
54
|
+
else {
|
|
55
|
+
fs.copyFileSync(example, ctx.envFile);
|
|
56
|
+
fs.chmodSync(ctx.envFile, 0o600);
|
|
57
|
+
h.ok(`created ${ctx.envFile} (mode 600)`);
|
|
58
|
+
}
|
|
59
|
+
await h.runStackSetup(ctx);
|
|
60
|
+
if (ctx.dry) {
|
|
61
|
+
h.info(
|
|
62
|
+
`[dry-run] run setup.sh --migrate-env unless ${ctx.envFile} already uses the LiteLLM proxy and master key`,
|
|
63
|
+
);
|
|
64
|
+
} else if (llmWired(ctx, h)) h.ok("Hindsight calls the LiteLLM proxy with the master key");
|
|
65
|
+
else if (await h.confirm(ctx, `Point ${ctx.envFile} at the LiteLLM proxy now (backs up the file, prints no values)?`, "y")) {
|
|
66
|
+
await h.runStackSetup(ctx, {
|
|
67
|
+
description: "migrate hindsight.env",
|
|
68
|
+
args: ["--migrate-env"],
|
|
69
|
+
env: {},
|
|
70
|
+
});
|
|
71
|
+
} else h.warn("Hindsight is not wired to the LiteLLM proxy; the stack will not work until you migrate");
|
|
72
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { configToml } from "./config.mjs";
|
|
4
|
+
import { renderMeridianRoutes } from "./meridian.mjs";
|
|
5
|
+
import { OLLAMA_HOST_API_BASE } from "./state.mjs";
|
|
6
|
+
|
|
7
|
+
/** @typedef {import("../../../wizard/lib/provider-wizard/helpers.mjs").ProviderWizardHelpers} Helpers */
|
|
8
|
+
|
|
9
|
+
/** Shipped litellm.<backend>.yaml api_base for the host-side model server. */
|
|
10
|
+
function shippedApiBase(backend) {
|
|
11
|
+
if (backend === "ollama") return OLLAMA_HOST_API_BASE;
|
|
12
|
+
if (backend === "lemond") return "http://host.docker.internal:13305/v1";
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {Record<string, any>} ctx
|
|
18
|
+
* @param {string} alias
|
|
19
|
+
* @param {string} litellmModel
|
|
20
|
+
*/
|
|
21
|
+
function renderRoute(ctx, alias, litellmModel) {
|
|
22
|
+
let out = ` - model_name: ${alias}\n litellm_params:\n model: ${litellmModel}\n api_base: ${ctx.apiBase}\n`;
|
|
23
|
+
if (ctx.backend === "ollama") {
|
|
24
|
+
out += " extra_body:\n think: false\n";
|
|
25
|
+
} else {
|
|
26
|
+
out += " api_key: os.environ/LEMOND_API_KEY\n extra_body:\n chat_template_kwargs:\n enable_thinking: false\n";
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {Record<string, any>} ctx
|
|
33
|
+
* @param {string} litellmModel
|
|
34
|
+
*/
|
|
35
|
+
function renderOverride(ctx, litellmModel) {
|
|
36
|
+
return (
|
|
37
|
+
"# Written by ./setup. Replaces config/litellm.*.yaml while this file exists.\n" +
|
|
38
|
+
"# Delete it, or rerun ./setup with the shipped model, to use the shipped routes again.\n" +
|
|
39
|
+
"# Thinking is off for all three aliases. Non-ollama servers get chat_template_kwargs; a server that rejects it needs a different route.\n" +
|
|
40
|
+
"model_list:\n" +
|
|
41
|
+
renderRoute(ctx, "memory-retain", litellmModel) +
|
|
42
|
+
renderRoute(ctx, "memory-reflect", litellmModel) +
|
|
43
|
+
renderRoute(ctx, "memory-consolidate", litellmModel) +
|
|
44
|
+
"general_settings:\n master_key: os.environ/LITELLM_MASTER_KEY\n"
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {Record<string, any>} ctx
|
|
50
|
+
* @param {string} litellmModel
|
|
51
|
+
* @param {string} reflectLitellmModel
|
|
52
|
+
*/
|
|
53
|
+
function renderShipped(ctx, litellmModel, reflectLitellmModel) {
|
|
54
|
+
const src = path.join(ctx.repoRoot, "config", `litellm.${ctx.backend}.yaml`);
|
|
55
|
+
const lines = fs.readFileSync(src, "utf8").split("\n");
|
|
56
|
+
const out = [
|
|
57
|
+
`# Written by ./setup from config/litellm.${ctx.backend}.yaml. Replaces the shipped routes while this file exists.`,
|
|
58
|
+
"# Delete it, or rerun ./setup with the shipped models, to use the shipped routes again.",
|
|
59
|
+
];
|
|
60
|
+
let name = "";
|
|
61
|
+
for (const line of lines) {
|
|
62
|
+
const mn = line.match(/^ - model_name: (\S+)/);
|
|
63
|
+
if (mn) name = mn[1];
|
|
64
|
+
if (/^ api_base: /.test(line)) {
|
|
65
|
+
out.push(` api_base: ${ctx.apiBase}`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (/^ model: /.test(line)) {
|
|
69
|
+
if (name !== "memory-reflect") out.push(` model: ${litellmModel}`);
|
|
70
|
+
else if (reflectLitellmModel) out.push(` model: ${reflectLitellmModel}`);
|
|
71
|
+
else out.push(line);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
out.push(line);
|
|
75
|
+
}
|
|
76
|
+
return `${out.join("\n")}\n`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {Record<string, any>} ctx
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
export function litellmConfig(ctx) {
|
|
84
|
+
if (ctx.backend === "meridian") return renderMeridianRoutes(ctx);
|
|
85
|
+
let prefix = "openai/";
|
|
86
|
+
if (ctx.backend === "ollama") prefix = "ollama_chat/";
|
|
87
|
+
if (ctx.backend === "custom") return renderOverride(ctx, `${prefix}${ctx.model}`);
|
|
88
|
+
let reflect = "";
|
|
89
|
+
if (ctx.reflectModel) reflect = `${prefix}${ctx.reflectModel}`;
|
|
90
|
+
return renderShipped(ctx, `${prefix}${ctx.model}`, reflect);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {Record<string, any>} ctx
|
|
95
|
+
* @param {Helpers} h
|
|
96
|
+
*/
|
|
97
|
+
export async function removeOverrideIfAsked(ctx, h) {
|
|
98
|
+
if (!fs.existsSync(ctx.litellmOverride)) return;
|
|
99
|
+
if (ctx.dry) {
|
|
100
|
+
h.info(`[dry-run] move ${ctx.litellmOverride} aside so the shipped ${ctx.backend} routes apply`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!(await h.confirm(ctx, `Move ${ctx.litellmOverride} aside so the shipped ${ctx.backend} routes apply?`, "y"))) {
|
|
104
|
+
h.warn("kept the override; it still takes precedence over the shipped routes");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const bak = `${ctx.litellmOverride}.bak-${timestamp()}`;
|
|
108
|
+
fs.renameSync(ctx.litellmOverride, bak);
|
|
109
|
+
h.ok("moved the override aside");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {Record<string, any>} ctx
|
|
114
|
+
* @param {Helpers} h
|
|
115
|
+
*/
|
|
116
|
+
export async function writeOverride(ctx, h) {
|
|
117
|
+
if (ctx.dry && ctx.backend === "meridian") {
|
|
118
|
+
const backup = fs.existsSync(ctx.litellmOverride) ? " (backs up the existing file)" : "";
|
|
119
|
+
h.info(
|
|
120
|
+
`[dry-run] write ${ctx.litellmOverride}${backup}: Meridian routes, retain and consolidate -> anthropic/${ctx.model}, reflect -> anthropic/${ctx.reflectModel || ctx.model}, at ${ctx.apiBase}`,
|
|
121
|
+
);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (ctx.dry) {
|
|
125
|
+
h.info(
|
|
126
|
+
`[dry-run] write ${ctx.litellmOverride} from config/litellm.${ctx.backend}.yaml: retain and consolidate -> ${ctx.model}, reflect -> ${ctx.reflectModel || "shipped"}, at ${ctx.apiBase}`,
|
|
127
|
+
);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
h.ensureConfigDir(ctx);
|
|
131
|
+
const content = litellmConfig(ctx);
|
|
132
|
+
const tmp = path.join(ctx.hsDir, `.litellm.yaml.${process.pid}`);
|
|
133
|
+
fs.writeFileSync(tmp, content, { mode: 0o644 });
|
|
134
|
+
if (fs.existsSync(ctx.litellmOverride)) {
|
|
135
|
+
const existing = fs.readFileSync(ctx.litellmOverride);
|
|
136
|
+
if (Buffer.compare(existing, Buffer.from(content)) === 0) {
|
|
137
|
+
fs.unlinkSync(tmp);
|
|
138
|
+
h.ok(`${ctx.litellmOverride} already has this route`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (!(await h.confirm(ctx, `Replace ${ctx.litellmOverride} (the old file is kept as .bak)?`, "y"))) {
|
|
142
|
+
fs.unlinkSync(tmp);
|
|
143
|
+
h.warn("kept the existing override; it still takes precedence");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
fs.copyFileSync(ctx.litellmOverride, `${ctx.litellmOverride}.bak-${timestamp()}`);
|
|
147
|
+
}
|
|
148
|
+
fs.renameSync(tmp, ctx.litellmOverride);
|
|
149
|
+
h.ok(`wrote ${ctx.litellmOverride}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {Record<string, any>} ctx
|
|
154
|
+
* @param {Helpers} h
|
|
155
|
+
*/
|
|
156
|
+
export async function applyRoute(ctx, h) {
|
|
157
|
+
let shipped = "";
|
|
158
|
+
if (ctx.backend === "lemond") shipped = ctx.lemondDefaultModel;
|
|
159
|
+
if (ctx.backend === "ollama") shipped = ctx.ollamaDefaultModel;
|
|
160
|
+
const defaultBase = shippedApiBase(ctx.backend);
|
|
161
|
+
const apiMatchesShipped = !ctx.apiBase || ctx.apiBase === defaultBase;
|
|
162
|
+
const ownRoute = ctx.backend === "custom" || ctx.backend === "meridian";
|
|
163
|
+
if (!ownRoute && ctx.model === shipped && !ctx.reflectModel && apiMatchesShipped) {
|
|
164
|
+
await removeOverrideIfAsked(ctx, h);
|
|
165
|
+
} else {
|
|
166
|
+
await writeOverride(ctx, h);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function timestamp() {
|
|
171
|
+
const d = new Date();
|
|
172
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
173
|
+
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Record the backend choice in wizard.conf (and config.toml).
|
|
178
|
+
* @param {Record<string, any>} ctx
|
|
179
|
+
* @param {Helpers} h
|
|
180
|
+
*/
|
|
181
|
+
export function saveConf(ctx, h) {
|
|
182
|
+
if (ctx.dry) {
|
|
183
|
+
h.info(`[dry-run] record backend ${ctx.backend} in ${ctx.wizardConf}`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
/** @type {Record<string, string>} */
|
|
187
|
+
const keys = {
|
|
188
|
+
MEMORY_BACKEND: ctx.backend,
|
|
189
|
+
MEMORY_MODEL: ctx.model,
|
|
190
|
+
MEMORY_API_BASE: ctx.apiBase,
|
|
191
|
+
MEMORY_REFLECT_MODEL: ctx.reflectModel,
|
|
192
|
+
MEMORY_OLLAMA_IN_COMPOSE: ctx.ollamaInCompose ? "1" : "0",
|
|
193
|
+
};
|
|
194
|
+
if (ctx.backend === "meridian") keys.MEMORY_MERIDIAN_URL = ctx.meridianBase;
|
|
195
|
+
h.saveWizardConf(ctx, keys, { pmSections: configToml(ctx) });
|
|
196
|
+
}
|