@bcmyguest/openmem 0.0.0-stage → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +136 -2
  3. package/bin/hindsight-project-index +530 -0
  4. package/cli/coding-agent/access-policy.mjs +38 -0
  5. package/cli/coding-agent/agents.mjs +34 -0
  6. package/cli/coding-agent/bank-from-cwd.mjs +140 -0
  7. package/cli/coding-agent/capabilities.mjs +23 -0
  8. package/cli/coding-agent/hook-harnesses.mjs +87 -0
  9. package/cli/coding-agent/hook-runner.mjs +260 -0
  10. package/cli/coding-agent/install-access.mjs +93 -0
  11. package/cli/coding-agent/install-shim.mjs +97 -0
  12. package/cli/coding-agent/opencode-config.mjs +91 -0
  13. package/cli/coding-agent/opencode-plugin.mjs +96 -0
  14. package/cli/coding-agent/runtime.mjs +39 -0
  15. package/cli/coding-agent/session-facade.mjs +62 -0
  16. package/cli/cognee-mcp.mjs +260 -0
  17. package/cli/feed-sync.mjs +11 -0
  18. package/cli/install-coding-agent.mjs +165 -0
  19. package/cli/install-memory-mcp.mjs +426 -0
  20. package/cli/is-main.mjs +18 -0
  21. package/cli/mcp/context.mjs +61 -0
  22. package/cli/mcp/export/cognee-dataset-name.mjs +12 -0
  23. package/cli/mcp/export/cognee.mjs +119 -0
  24. package/cli/mcp/export/hindsight.mjs +48 -0
  25. package/cli/mcp/export/index.mjs +26 -0
  26. package/cli/mcp/export/mem0.mjs +31 -0
  27. package/cli/mcp/llm-synthesize.mjs +50 -0
  28. package/cli/mcp/mem0-scope-id.mjs +22 -0
  29. package/cli/mcp/memory-facade.mjs +455 -0
  30. package/cli/mcp/memory-reflect-consolidate.mjs +120 -0
  31. package/cli/mcp/notebooklm-banks.mjs +21 -0
  32. package/cli/mcp/notebooklm-hindsight-rest.mjs +45 -0
  33. package/cli/mcp/notebooklm-provider.mjs +225 -0
  34. package/cli/mcp/pmm-records.mjs +23 -0
  35. package/cli/mcp/portability.mjs +158 -0
  36. package/cli/mcp/stdio.mjs +65 -0
  37. package/cli/mcp/unified-mcp-install.mjs +65 -0
  38. package/cli/mem0-mcp.mjs +108 -0
  39. package/cli/notebooklm-mcp.mjs +236 -0
  40. package/cli/openmem-mcp.mjs +35 -0
  41. package/cli/openmem.mjs +205 -0
  42. package/cli/roots.mjs +85 -0
  43. package/cli/stack.mjs +436 -0
  44. package/cli/switch-memory-provider.mjs +358 -0
  45. package/cli/wiring-report.mjs +125 -0
  46. package/compose.bench.yaml +12 -0
  47. package/compose.yaml +164 -0
  48. package/config/coding-agent.json +13 -0
  49. package/config/hindsight.env.example +24 -0
  50. package/config/index.yml +54 -0
  51. package/config/litellm.lemond.yaml +40 -0
  52. package/config/litellm.ollama.yaml +33 -0
  53. package/config/notebooklm.yml +33 -0
  54. package/config/openmem.toml.example +62 -0
  55. package/docs/OPENMEM.md +23 -0
  56. package/docs/architecture.md +219 -0
  57. package/docs/assets/README.md +10 -0
  58. package/docs/assets/setup-demo.gif +0 -0
  59. package/docs/coding-agent-hooks.md +51 -0
  60. package/docs/coding-agent-scopes.md +22 -0
  61. package/docs/coding-agents.md +51 -0
  62. package/docs/cognee.md +37 -0
  63. package/docs/concepts.md +112 -0
  64. package/docs/hindsight.md +74 -0
  65. package/docs/index-sources-roadmap.md +38 -0
  66. package/docs/index-sources.md +39 -0
  67. package/docs/indexer-sinks.md +122 -0
  68. package/docs/indexing.md +57 -0
  69. package/docs/manual-stack-setup.md +74 -0
  70. package/docs/mcp-install-guide.md +4 -0
  71. package/docs/mcp-setup.md +101 -0
  72. package/docs/mem0.md +103 -0
  73. package/docs/memory-portability.md +73 -0
  74. package/docs/memory-providers.md +246 -0
  75. package/docs/model-configuration.md +84 -0
  76. package/docs/notebooklm.md +83 -0
  77. package/docs/npx-install.md +170 -0
  78. package/docs/openmem-config.md +194 -0
  79. package/docs/provider-wizard-hooks.md +222 -0
  80. package/docs/quickstart.md +101 -0
  81. package/docs/setup-wizard.md +116 -0
  82. package/docs/sources-feeds.md +52 -0
  83. package/docs/sources-readwise.md +23 -0
  84. package/docs/sources-zotero.md +22 -0
  85. package/glama.json +4 -0
  86. package/indexer/Dockerfile +38 -0
  87. package/indexer/code_outline.py +146 -0
  88. package/indexer/feed_sync.py +107 -0
  89. package/indexer/index_config.py +290 -0
  90. package/indexer/pytest.ini +5 -0
  91. package/indexer/sinks/__init__.py +148 -0
  92. package/indexer/sinks/_http.py +44 -0
  93. package/indexer/sinks/cognee.py +329 -0
  94. package/indexer/sinks/hindsight.py +107 -0
  95. package/indexer/sinks/mem0.py +247 -0
  96. package/indexer/watch.py +240 -0
  97. package/package.json +95 -4
  98. package/providers/_template/compose.yaml +52 -0
  99. package/providers/cognee/OPERATOR.md +82 -0
  100. package/providers/cognee/compose.yaml +89 -0
  101. package/providers/cognee/lib/clack.mjs +157 -0
  102. package/providers/cognee/lib/coding-agent.mjs +26 -0
  103. package/providers/cognee/lib/cognee-mcp-install.mjs +153 -0
  104. package/providers/cognee/lib/config.mjs +98 -0
  105. package/providers/cognee/lib/env-file.mjs +194 -0
  106. package/providers/cognee/lib/env-setup.mjs +83 -0
  107. package/providers/cognee/lib/llm-env.mjs +382 -0
  108. package/providers/cognee/lib/ports.mjs +25 -0
  109. package/providers/cognee/lib/smoke.mjs +246 -0
  110. package/providers/cognee/lib/stack-env.mjs +134 -0
  111. package/providers/cognee/lib/state.mjs +101 -0
  112. package/providers/cognee/lib/summary.mjs +56 -0
  113. package/providers/cognee/lib/validate.mjs +97 -0
  114. package/providers/cognee/manifest.json +56 -0
  115. package/providers/cognee/wizard.mjs +134 -0
  116. package/providers/graphiti/manifest.json +15 -0
  117. package/providers/hindsight/lib/clack.mjs +245 -0
  118. package/providers/hindsight/lib/coding-agent.mjs +204 -0
  119. package/providers/hindsight/lib/config.mjs +73 -0
  120. package/providers/hindsight/lib/env-setup.mjs +71 -0
  121. package/providers/hindsight/lib/litellm-routes.mjs +187 -0
  122. package/providers/hindsight/lib/model-backend.mjs +214 -0
  123. package/providers/hindsight/lib/notebooklm-mcp-install.mjs +127 -0
  124. package/providers/hindsight/lib/ollama-compose.mjs +51 -0
  125. package/providers/hindsight/lib/ports.mjs +30 -0
  126. package/providers/hindsight/lib/probe.mjs +33 -0
  127. package/providers/hindsight/lib/stack-env.mjs +132 -0
  128. package/providers/hindsight/lib/state.mjs +75 -0
  129. package/providers/hindsight/lib/summary.mjs +43 -0
  130. package/providers/hindsight/lib/validate.mjs +75 -0
  131. package/providers/hindsight/manifest.json +58 -0
  132. package/providers/hindsight/wizard.mjs +149 -0
  133. package/providers/letta/manifest.json +29 -0
  134. package/providers/mem0/OPERATOR.md +124 -0
  135. package/providers/mem0/compose.yaml +57 -0
  136. package/providers/mem0/lib/coding-agent.mjs +58 -0
  137. package/providers/mem0/lib/config.mjs +45 -0
  138. package/providers/mem0/lib/configure.mjs +135 -0
  139. package/providers/mem0/lib/dotenv.mjs +71 -0
  140. package/providers/mem0/lib/env-file.mjs +110 -0
  141. package/providers/mem0/lib/http.mjs +69 -0
  142. package/providers/mem0/lib/mcp-install.mjs +165 -0
  143. package/providers/mem0/lib/secret.mjs +30 -0
  144. package/providers/mem0/lib/smoke.mjs +179 -0
  145. package/providers/mem0/lib/stack-env.mjs +216 -0
  146. package/providers/mem0/lib/state.mjs +67 -0
  147. package/providers/mem0/lib/summary.mjs +60 -0
  148. package/providers/mem0/lib/url.mjs +106 -0
  149. package/providers/mem0/lib/validate.mjs +158 -0
  150. package/providers/mem0/manifest.json +47 -0
  151. package/providers/mem0/wizard.mjs +133 -0
  152. package/providers/zep/manifest.json +29 -0
  153. package/scripts/check-pack.mjs +218 -0
  154. package/scripts/gen-masks.mjs +83 -0
  155. package/scripts/gen-masks.sh +4 -0
  156. package/scripts/promotion-provider-smoke.mjs +34 -0
  157. package/scripts/run-setup.sh +30 -0
  158. package/scripts/setup.mjs +94 -0
  159. package/scripts/setup.sh +5 -0
  160. package/scripts/spin-up.sh +45 -0
  161. package/scripts/wizard.sh +5 -0
  162. package/server.json +34 -0
  163. package/skills/openmem/SKILL.md +29 -0
  164. package/sync/Dockerfile +27 -0
  165. package/sync/memory_backend.py +87 -0
  166. package/sync/notebooklm_sync.py +664 -0
  167. package/wizard/bin/pm.mjs +97 -0
  168. package/wizard/bin/setup.mjs +33 -0
  169. package/wizard/lib/compose-version.mjs +32 -0
  170. package/wizard/lib/compose.mjs +70 -0
  171. package/wizard/lib/curl.mjs +103 -0
  172. package/wizard/lib/docker-build.mjs +148 -0
  173. package/wizard/lib/exec.mjs +41 -0
  174. package/wizard/lib/indexer.mjs +6 -0
  175. package/wizard/lib/load-machine-env.mjs +4 -0
  176. package/wizard/lib/log.mjs +32 -0
  177. package/wizard/lib/notebooklm-agent-access.mjs +92 -0
  178. package/wizard/lib/notebooklm.mjs +214 -0
  179. package/wizard/lib/parse-args.mjs +87 -0
  180. package/wizard/lib/paths.mjs +39 -0
  181. package/wizard/lib/pm-config.mjs +163 -0
  182. package/wizard/lib/ports.mjs +94 -0
  183. package/wizard/lib/prompts.mjs +57 -0
  184. package/wizard/lib/provider-wizard/helpers.mjs +98 -0
  185. package/wizard/lib/provider-wizard/run-steps.mjs +129 -0
  186. package/wizard/lib/providers/registry.mjs +262 -0
  187. package/wizard/lib/run-clack.mjs +108 -0
  188. package/wizard/lib/run-wizard.mjs +755 -0
  189. package/wizard/lib/validate-setup.mjs +62 -0
  190. package/wizard/lib/wizard-action.mjs +34 -0
  191. package/wizard/lib/wizard-conf.mjs +71 -0
@@ -0,0 +1,51 @@
1
+ /**
2
+ * After the stack is healthy: wait for compose Ollama and pull models the LiteLLM routes need.
3
+ */
4
+ import { ollamaModelNames } from "./probe.mjs";
5
+ import { ensureState } from "./state.mjs";
6
+
7
+ /** @typedef {import("../../../wizard/lib/provider-wizard/helpers.mjs").ProviderWizardHelpers} Helpers */
8
+
9
+ const HOST_TAGS = "http://127.0.0.1:11434/api/tags";
10
+
11
+ /**
12
+ * @param {Record<string, any>} ctx
13
+ * @param {Helpers} h
14
+ */
15
+ export async function ensureComposeOllamaModels(ctx, h) {
16
+ ensureState(ctx);
17
+ if (ctx.backend !== "ollama" || !ctx.ollamaInCompose) return;
18
+ if (ctx.dry) {
19
+ h.info(`[dry-run] wait for compose Ollama, pull ${ctx.model}`);
20
+ return;
21
+ }
22
+
23
+ h.info("waiting for compose Ollama on 127.0.0.1:11434…");
24
+ let body = null;
25
+ for (let i = 0; i < 45; i++) {
26
+ body = h.curlJson(HOST_TAGS);
27
+ if (body) break;
28
+ if (i > 0 && i % 5 === 0) h.info(`… still waiting for Ollama (${i * 2}s)`);
29
+ await new Promise((r) => setTimeout(r, 2000));
30
+ }
31
+ if (!body) {
32
+ h.warn("compose Ollama did not answer on 127.0.0.1:11434; skip model pull");
33
+ return;
34
+ }
35
+ h.ok("compose Ollama is reachable on 127.0.0.1:11434");
36
+
37
+ const present = new Set(ollamaModelNames(body));
38
+ const needed = new Set();
39
+ if (ctx.model) needed.add(ctx.model);
40
+ if (ctx.reflectModel) needed.add(ctx.reflectModel);
41
+ for (const name of needed) {
42
+ if (present.has(name)) {
43
+ h.ok(`Ollama already has ${name}`);
44
+ continue;
45
+ }
46
+ const args = h.composeArgs(ctx, "exec", "-T", "ollama", "ollama", "pull", name);
47
+ await h.act(ctx, `pull ${name} into compose Ollama`, "docker", args, {
48
+ cwd: ctx.repoRoot,
49
+ });
50
+ }
51
+ }
@@ -0,0 +1,30 @@
1
+ /** Hindsight's two host ports: the API and the control plane. */
2
+ import path from "node:path";
3
+ import { ensureState } from "./state.mjs";
4
+
5
+ /** @typedef {import("../../../wizard/lib/provider-wizard/helpers.mjs").ProviderWizardHelpers} Helpers */
6
+
7
+ const ENV_VARS = ["HINDSIGHT_PORT", "HINDSIGHT_CP_PORT"];
8
+
9
+ /**
10
+ * Pick the API and control-plane ports (env, then the stack .env, then 8888/9999) and
11
+ * export them in process.env for compose.
12
+ * @param {Record<string, any>} ctx
13
+ * @param {Helpers} h
14
+ */
15
+ export async function resolvePorts(ctx, h) {
16
+ ensureState(ctx);
17
+ const stackEnv = path.join(ctx.repoRoot, ".env");
18
+ const api =
19
+ process.env.HINDSIGHT_PORT || h.envValue(stackEnv, "HINDSIGHT_PORT") || "8888";
20
+ const cp =
21
+ process.env.HINDSIGHT_CP_PORT || h.envValue(stackEnv, "HINDSIGHT_CP_PORT") || "9999";
22
+ if (!/^\d+$/.test(api) || !/^\d+$/.test(cp)) {
23
+ h.die("HINDSIGHT_PORT and HINDSIGHT_CP_PORT must be port numbers");
24
+ }
25
+ ctx.portApi = String(await h.choosePort(ctx, "Hindsight API", Number(api), { envVars: ENV_VARS }));
26
+ ctx.portCp = String(await h.choosePort(ctx, "control plane", Number(cp), { envVars: ENV_VARS }));
27
+ process.env.HINDSIGHT_PORT = ctx.portApi;
28
+ process.env.HINDSIGHT_CP_PORT = ctx.portCp;
29
+ ctx.hindsightUrl = `http://127.0.0.1:${ctx.portApi}`;
30
+ }
@@ -0,0 +1,33 @@
1
+ const TIMEOUT_MS = 8000;
2
+
3
+ /**
4
+ * @param {string} url
5
+ * @returns {Promise<object|null>}
6
+ */
7
+ export async function fetchJson(url) {
8
+ try {
9
+ const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
10
+ if (!res.ok) return null;
11
+ return await res.json();
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+
17
+ /** @param {object|null} body OpenAI /v1/models or similar */
18
+ export function openAiModelIds(body) {
19
+ if (!body || !Array.isArray(body.data)) return [];
20
+ return body.data.map((m) => m.id).filter(Boolean);
21
+ }
22
+
23
+ /** @param {object|null} body Ollama /api/tags */
24
+ export function ollamaModelNames(body) {
25
+ if (!body || !Array.isArray(body.models)) return [];
26
+ return body.models.map((m) => m.name).filter(Boolean);
27
+ }
28
+
29
+ export function normalizeOpenAiBase(base) {
30
+ let b = base.trim().replace(/\/+$/, "");
31
+ if (!b.endsWith("/v1")) b += "/v1";
32
+ return b;
33
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Hindsight's contribution to scripts/setup.mjs: the LiteLLM proxy secrets file, the
3
+ * LITELLM_CONFIG and port entries of the stack .env, and the hindsight.env migration.
4
+ * Runs as a plain Node script step (no wizard context), so it only reads process
5
+ * environment and the paths it is given.
6
+ */
7
+ import crypto from "node:crypto";
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ import { envValue } from "../../../wizard/lib/paths.mjs";
11
+
12
+ /**
13
+ * @param {{ repo: string, home: string, env: NodeJS.ProcessEnv }} opts
14
+ * @returns {{ entries: [string, string][], drop: string[], report: string[] }}
15
+ * entries: KEY=value lines to add to the stack .env; drop: keys to remove first;
16
+ * report: KEY=value strings echoed in the "wrote .env" line
17
+ */
18
+ export function prepareStackEnv({ repo, home, env }) {
19
+ const litellmEnv =
20
+ env.LITELLM_ENV_FILE || path.join(home, ".config/hindsight/litellm.env");
21
+ const overrideConfig = path.join(home, ".config/hindsight/litellm.yaml");
22
+ const backend = env.MEMORY_BACKEND || "lemond";
23
+
24
+ if (!["lemond", "ollama"].includes(backend)) {
25
+ throw new Error(`MEMORY_BACKEND must be lemond or ollama (got: ${backend})`);
26
+ }
27
+
28
+ if (!fs.existsSync(litellmEnv)) {
29
+ const key = crypto.randomBytes(24).toString("hex");
30
+ const body =
31
+ "# LiteLLM proxy secrets. Read by the litellm compose service. Do not commit.\n" +
32
+ `LITELLM_MASTER_KEY=sk-${key}\n` +
33
+ "# lemond ignores the key; any non-empty value works.\n" +
34
+ "LEMOND_API_KEY=placeholder\n" +
35
+ "# Hosted models: add the provider key here, e.g. ANTHROPIC_API_KEY=..., and reference it as os.environ/ANTHROPIC_API_KEY.\n";
36
+ fs.writeFileSync(litellmEnv, body, { mode: 0o600 });
37
+ console.log(`created ${litellmEnv} (mode 600)`);
38
+ }
39
+
40
+ const litellmConfig = fs.existsSync(overrideConfig)
41
+ ? overrideConfig
42
+ : path.join(repo, "config", `litellm.${backend}.yaml`);
43
+
44
+ const profiles = ["hindsight"];
45
+ if (env.MEMORY_OLLAMA_IN_COMPOSE === "1") profiles.push("ollama");
46
+ const notebookYml = path.join(home, ".config/hindsight/notebooklm.yml");
47
+ if (
48
+ fs.existsSync(notebookYml) &&
49
+ /^enabled:\s*true\b/m.test(fs.readFileSync(notebookYml, "utf8"))
50
+ ) {
51
+ profiles.push("notebooklm");
52
+ }
53
+
54
+ /** @type {[string, string][]} */
55
+ const entries = [
56
+ ["LITELLM_CONFIG", litellmConfig],
57
+ ["COMPOSE_PROFILES", profiles.join(",")],
58
+ ];
59
+ const drop = ["LITELLM_CONFIG", "COMPOSE_PROFILES"];
60
+ if (env.HINDSIGHT_PORT) {
61
+ drop.push("HINDSIGHT_PORT");
62
+ entries.push(["HINDSIGHT_PORT", env.HINDSIGHT_PORT]);
63
+ }
64
+ if (env.HINDSIGHT_CP_PORT) {
65
+ drop.push("HINDSIGHT_CP_PORT");
66
+ entries.push(["HINDSIGHT_CP_PORT", env.HINDSIGHT_CP_PORT]);
67
+ }
68
+ return { entries, drop, report: [`LITELLM_CONFIG=${litellmConfig}`] };
69
+ }
70
+
71
+ /**
72
+ * Point hindsight.env at the LiteLLM proxy (backs the file up first; prints no values).
73
+ * @param {{ repo: string, home: string, env: NodeJS.ProcessEnv }} opts
74
+ */
75
+ export function migrateEnv({ home, env }) {
76
+ const hindsightEnv =
77
+ env.HINDSIGHT_ENV_FILE || path.join(home, ".config/hindsight/hindsight.env");
78
+ const litellmEnv =
79
+ env.LITELLM_ENV_FILE || path.join(home, ".config/hindsight/litellm.env");
80
+ if (!fs.existsSync(hindsightEnv)) {
81
+ throw new Error(`no ${hindsightEnv} to migrate`);
82
+ }
83
+ const key = envValue(litellmEnv, "LITELLM_MASTER_KEY");
84
+ if (!key) {
85
+ throw new Error(`LITELLM_MASTER_KEY missing from ${litellmEnv}`);
86
+ }
87
+ const backup = `${hindsightEnv}.bak-${Date.now()}`;
88
+ fs.copyFileSync(hindsightEnv, backup);
89
+ const llmLines = [
90
+ "HINDSIGHT_API_LLM_PROVIDER=openai",
91
+ "HINDSIGHT_API_LLM_BASE_URL=http://litellm:4000/v1",
92
+ "HINDSIGHT_API_LLM_MODEL=memory-reflect",
93
+ `HINDSIGHT_API_LLM_API_KEY=${key}`,
94
+ "HINDSIGHT_API_RETAIN_LLM_MODEL=memory-retain",
95
+ "HINDSIGHT_API_REFLECT_LLM_MODEL=memory-reflect",
96
+ "HINDSIGHT_API_CONSOLIDATION_LLM_MODEL=memory-consolidate",
97
+ "HINDSIGHT_API_MENTAL_MODEL_REFRESH_LLM_MODEL=memory-reflect",
98
+ ];
99
+ const keys = llmLines.map((l) => l.split("=")[0]);
100
+ const keyRe = new RegExp(`^(${keys.join("|")})=`);
101
+ const rest = fs
102
+ .readFileSync(hindsightEnv, "utf8")
103
+ .split("\n")
104
+ .filter((l) => l && !keyRe.test(l));
105
+ fs.writeFileSync(hindsightEnv, `${[...rest, ...llmLines].join("\n")}\n`, { mode: 0o600 });
106
+ console.log(
107
+ `migrated ${hindsightEnv} to the LiteLLM proxy (backup: ${backup}). Values not printed.`,
108
+ );
109
+ console.log("Next: docker compose up -d (recreates hindsight with the new env)");
110
+ }
111
+
112
+ /**
113
+ * Printed after the .env is written when hindsight.env still calls a model server directly.
114
+ * @param {{ home: string, env: NodeJS.ProcessEnv }} opts
115
+ * @returns {string[]}
116
+ */
117
+ export function setupNotices({ home, env }) {
118
+ const hindsightEnv =
119
+ env.HINDSIGHT_ENV_FILE || path.join(home, ".config/hindsight/hindsight.env");
120
+ if (
121
+ fs.existsSync(hindsightEnv) &&
122
+ /^HINDSIGHT_API_LLM_BASE_URL=http:\/\/host\.docker\.internal:/m.test(
123
+ fs.readFileSync(hindsightEnv, "utf8"),
124
+ )
125
+ ) {
126
+ return [
127
+ `NOTE: ${hindsightEnv} still calls the model server directly.`,
128
+ " Run: node scripts/setup.mjs --migrate-env (backs up the file, rewrites only LLM lines)",
129
+ ];
130
+ }
131
+ return [];
132
+ }
@@ -0,0 +1,75 @@
1
+ import path from "node:path";
2
+
3
+ /** Models the shipped LiteLLM routes (config/litellm.<backend>.yaml) use for retain/consolidate. */
4
+ export const LEMOND_DEFAULT_MODEL = "Gemma-4-E2B-it-GGUF";
5
+ export const OLLAMA_DEFAULT_MODEL = "gemma4:e2b";
6
+
7
+ /** LiteLLM api_base when Ollama runs on the host (default). */
8
+ export const OLLAMA_HOST_API_BASE = "http://host.docker.internal:11434";
9
+ /** LiteLLM api_base when Ollama is the compose `ollama` service. */
10
+ export const OLLAMA_COMPOSE_API_BASE = "http://ollama:11434";
11
+
12
+ export const BACKENDS = ["lemond", "ollama", "custom"];
13
+
14
+ /**
15
+ * Fill the Hindsight fields of the wizard context. Idempotent: a field that is already
16
+ * set (a test) is kept.
17
+ * @param {Record<string, any>} ctx
18
+ */
19
+ export function ensureState(ctx) {
20
+ const hs = ctx.hsDir || path.join(ctx.home || "", ".config/hindsight");
21
+ const defaults = {
22
+ envFile: path.join(hs, "hindsight.env"),
23
+ litellmEnv: path.join(hs, "litellm.env"),
24
+ litellmOverride: path.join(hs, "litellm.yaml"),
25
+ agentJson: path.join(ctx.home || "", ".hindsight/coding-agent.json"),
26
+ lemondBase: "http://127.0.0.1:13305",
27
+ ollamaBase: "http://127.0.0.1:11434",
28
+ lemondDefaultModel: LEMOND_DEFAULT_MODEL,
29
+ ollamaDefaultModel: OLLAMA_DEFAULT_MODEL,
30
+ customDefaultBase: "http://host.docker.internal:8080/v1",
31
+ backend: "",
32
+ model: "",
33
+ reflectModel: "",
34
+ apiBase: "",
35
+ lemondJson: null,
36
+ lemondNames: [],
37
+ ollamaJson: null,
38
+ ollamaNames: [],
39
+ /** True when Ollama runs as the compose profile `ollama` (no host install). */
40
+ ollamaInCompose: false,
41
+ portApi: "",
42
+ portCp: "",
43
+ hindsightUrl: "",
44
+ };
45
+ for (const [key, value] of Object.entries(defaults)) {
46
+ if (ctx[key] === undefined) ctx[key] = value;
47
+ }
48
+ return ctx;
49
+ }
50
+
51
+ /**
52
+ * @param {Record<string, any>} ctx
53
+ * @param {import("../../../wizard/lib/provider-wizard/helpers.mjs").ProviderWizardHelpers} h
54
+ * @param {string} key wizard.conf key
55
+ */
56
+ export function confGet(ctx, h, key) {
57
+ return h.envValue(ctx.wizardConf, key);
58
+ }
59
+
60
+ /**
61
+ * The stored value for `key`, but only when wizard.conf was written for the same backend.
62
+ */
63
+ export function storedOr(ctx, h, backend, key, fallback) {
64
+ if (confGet(ctx, h, "MEMORY_BACKEND") !== backend) return fallback;
65
+ return confGet(ctx, h, key) || fallback;
66
+ }
67
+
68
+ /** PM_BACKEND, then the saved MEMORY_BACKEND, then lemond. */
69
+ export function backendDefault(ctx, h) {
70
+ const pm = process.env.PM_BACKEND;
71
+ if (BACKENDS.includes(pm)) return pm;
72
+ const saved = confGet(ctx, h, "MEMORY_BACKEND");
73
+ if (BACKENDS.includes(saved)) return saved;
74
+ return "lemond";
75
+ }
@@ -0,0 +1,43 @@
1
+ /** Hindsight's contribution to the wizard's closing summary. */
2
+ import fs from "node:fs";
3
+ import { composeCommandText } from "../../../wizard/lib/compose.mjs";
4
+ import { ensureState } from "./state.mjs";
5
+
6
+ /**
7
+ * Lines the meta wizard prints in its summary, in the order it prints them.
8
+ * @param {Record<string, any>} ctx
9
+ * @returns {{ running: string[], configFiles: string[], notes: string[], logServices: string[] }}
10
+ */
11
+ export function summary(ctx) {
12
+ ensureState(ctx);
13
+ const modelWhere =
14
+ ctx.backend === "ollama" && ctx.ollamaInCompose
15
+ ? "compose Ollama (profile ollama)"
16
+ : "the LiteLLM proxy (internal network)";
17
+ const running = [
18
+ ` Hindsight API ${ctx.hindsightUrl} (docs: ${ctx.hindsightUrl}/docs)`,
19
+ ` control plane http://127.0.0.1:${ctx.portCp}`,
20
+ ` model ${ctx.backend}, ${ctx.model} via ${modelWhere}`,
21
+ ];
22
+ if (ctx.ollamaInCompose) {
23
+ running.push(` Ollama http://127.0.0.1:11434 (compose service; LiteLLM uses http://ollama:11434)`);
24
+ }
25
+ const configFiles = [` ${ctx.envFile}`];
26
+ if (fs.existsSync(ctx.litellmOverride)) {
27
+ configFiles.push(` ${ctx.litellmOverride} (model routes; replaces config/litellm.*.yaml)`);
28
+ } else if (ctx.backend === "custom") {
29
+ configFiles.push(` ${ctx.litellmOverride} (missing: custom backend needs it)`);
30
+ } else configFiles.push(` config/litellm.${ctx.backend}.yaml (shipped routes)`);
31
+ const litellmUp = composeCommandText(ctx, "up", "-d", "litellm");
32
+ const notes = [
33
+ `Control plane (banks, ops, LLM queue): http://127.0.0.1:${ctx.portCp}`,
34
+ `Reflect needs a capable model (memory-reflect alias). On a weak or remote LLM, edit ${ctx.litellmOverride} and point memory-reflect at Meridian, an OpenAI-compatible host, or a stronger local model — then: ${litellmUp}`,
35
+ `Change later: rerun ./setup. For a route-only edit, run: ${litellmUp}`,
36
+ ];
37
+ if (ctx.smokeFailed) {
38
+ notes.unshift(
39
+ `Smoke retain/recall did not finish — stack is up; fix the model/LLM queue via http://127.0.0.1:${ctx.portCp} then re-run setup or: curl the wizard-smoke retain against ${ctx.hindsightUrl}`,
40
+ );
41
+ }
42
+ return { running, configFiles, notes, logServices: ["hindsight", "litellm"] };
43
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Hindsight checks that run after the meta wizard's provider-neutral validation:
3
+ * wizard.conf backend keys, hindsight.env, litellm.env, the LiteLLM wiring, the stack
4
+ * .env's Hindsight keys, and a model override for a non-shipped model.
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
+ * Appends to ctx.validationErrors and ctx.validationChecks.
14
+ * @param {Record<string, any>} ctx
15
+ * @param {Helpers} h
16
+ */
17
+ export function validateHindsight(ctx, h) {
18
+ ensureState(ctx);
19
+ const errors = (ctx.validationErrors ||= []);
20
+ const checks = (ctx.validationChecks ||= []);
21
+ const conf = (key) => h.envValue(ctx.wizardConf, key);
22
+
23
+ if (fs.existsSync(ctx.wizardConf)) {
24
+ for (const key of ["MEMORY_BACKEND", "MEMORY_MODEL"]) {
25
+ if (!conf(key)) errors.push(`wizard.conf missing ${key}`);
26
+ else checks.push(`wizard.conf ${key} set`);
27
+ }
28
+ }
29
+
30
+ if (!fs.existsSync(ctx.envFile)) errors.push(`missing ${ctx.envFile}`);
31
+ else {
32
+ if (fs.statSync(ctx.envFile).mode & 0o077) {
33
+ errors.push(`${ctx.envFile} must not be world/group readable (want mode 600)`);
34
+ }
35
+ checks.push("hindsight.env present");
36
+ }
37
+
38
+ if (!fs.existsSync(ctx.litellmEnv)) errors.push(`missing ${ctx.litellmEnv}`);
39
+ else {
40
+ if (fs.statSync(ctx.litellmEnv).mode & 0o077) {
41
+ errors.push(`${ctx.litellmEnv} must not be world/group readable (want mode 600)`);
42
+ }
43
+ if (!h.envValue(ctx.litellmEnv, "LITELLM_MASTER_KEY")) {
44
+ errors.push("litellm.env missing LITELLM_MASTER_KEY");
45
+ }
46
+ checks.push("litellm.env present");
47
+ }
48
+
49
+ const stackEnv = path.join(ctx.repoRoot, ".env");
50
+ if (fs.existsSync(stackEnv)) {
51
+ for (const key of ["LITELLM_CONFIG", "HINDSIGHT_PORT"]) {
52
+ if (!h.envValue(stackEnv, key)) errors.push(`repo .env missing ${key}`);
53
+ }
54
+ checks.push("repo .env ports and litellm config");
55
+ }
56
+
57
+ const llmBase = h.envValue(ctx.envFile, "HINDSIGHT_API_LLM_BASE_URL");
58
+ const llmKey = h.envValue(ctx.envFile, "HINDSIGHT_API_LLM_API_KEY");
59
+ const master = h.envValue(ctx.litellmEnv, "LITELLM_MASTER_KEY");
60
+ if (llmBase !== "http://litellm:4000/v1") {
61
+ errors.push("hindsight.env not wired to LiteLLM proxy (HINDSIGHT_API_LLM_BASE_URL)");
62
+ }
63
+ if (!llmKey || !master || llmKey !== master) {
64
+ errors.push("hindsight.env LLM API key must match LITELLM_MASTER_KEY");
65
+ }
66
+ checks.push("LiteLLM proxy wiring");
67
+
68
+ const backend = conf("MEMORY_BACKEND");
69
+ const model = conf("MEMORY_MODEL");
70
+ const shipped = backend === "ollama" ? ctx.ollamaDefaultModel : ctx.lemondDefaultModel;
71
+ const reflect = conf("MEMORY_REFLECT_MODEL");
72
+ if (backend !== "custom" && model !== shipped && !reflect && !fs.existsSync(ctx.litellmOverride)) {
73
+ errors.push("custom model without litellm.yaml override");
74
+ }
75
+ }
@@ -0,0 +1,58 @@
1
+ {
2
+ "id": "hindsight",
3
+ "label": "Hindsight",
4
+ "status": "supported",
5
+ "compose": {
6
+ "services": ["litellm", "hindsight", "indexer"],
7
+ "healthService": "hindsight",
8
+ "healthPath": "/health",
9
+ "hostPortEnv": "HINDSIGHT_PORT",
10
+ "defaultHostPort": 8888,
11
+ "logServices": ["hindsight", "litellm"],
12
+ "profile": "hindsight",
13
+ "files": []
14
+ },
15
+ "indexer": {
16
+ "apiUrlEnv": "HINDSIGHT_URL",
17
+ "sink": "hindsight",
18
+ "sinkDriver": "hindsight-rest-v1"
19
+ },
20
+ "codingAgent": {
21
+ "pluginPackage": "@vectorize-io/hindsight-coding-agents",
22
+ "configTemplate": "config/coding-agent.json"
23
+ },
24
+ "settings": {
25
+ "nativeDocs": "https://github.com/vectorize-io/hindsight",
26
+ "nativeConfigPaths": [
27
+ "~/.config/hindsight/hindsight.env",
28
+ "~/.config/hindsight/litellm.yaml",
29
+ "config/litellm.lemond.yaml"
30
+ ],
31
+ "wizardOwns": [
32
+ "compose stack and ports",
33
+ "index.yml sources",
34
+ "LiteLLM proxy wiring for Hindsight LLM aliases",
35
+ "coding-agent.json symlink"
36
+ ]
37
+ },
38
+ "wizardHooks": {
39
+ "module": "wizard.mjs",
40
+ "delegates": {}
41
+ },
42
+ "wizard": {
43
+ "smokeBank": "wizard-smoke",
44
+ "retainMethod": "POST",
45
+ "retainPath": "/v1/default/banks/{bank}/memories",
46
+ "retainBody": "{\"items\":[{\"content\":\"Wizard smoke test: the project codename is heron.\"}],\"async\":false}",
47
+ "recallMethod": "POST",
48
+ "recallPath": "/v1/default/banks/{bank}/memories/recall",
49
+ "recallBody": "{\"query\":\"What is the project codename?\"}",
50
+ "recallMustMatch": "heron",
51
+ "smokeRetainTimeoutSec": 90,
52
+ "smokeRecallTimeoutSec": 60,
53
+ "retainHint": "Open {controlPlane} (ops queue) and {api}/docs. LLM is shared (one retain at a time): cancel other banks' pending retains, or stop coding-agent/NotebookLM sync while smoking. If Ollama shows size_vram=0 it is on CPU — use GPU/ROCm or a faster model in ~/.config/hindsight/litellm.yaml, then: {compose} up -d litellm. Logs: {logs}",
54
+ "recallHint": "Open {controlPlane}; check memory-retain/reflect routes ({compose} logs litellm). Logs: {logs}",
55
+ "deleteMethod": "DELETE",
56
+ "deletePath": "/v1/default/banks/{bank}"
57
+ }
58
+ }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Hindsight sub-wizard (hook API v2). Owns everything Hindsight-specific that the meta
3
+ * wizard used to do inline: the lemond / Ollama / custom model flow and LiteLLM routes,
4
+ * hindsight.env and litellm.env, the HINDSIGHT_PORT / HINDSIGHT_CP_PORT pair, Hindsight
5
+ * validation, and the summary lines. See lib/ for the implementations and
6
+ * docs/provider-wizard-hooks.md for the hook contract.
7
+ *
8
+ * This module must not import wizard/lib/run-wizard.mjs; it receives `helpers`.
9
+ */
10
+ import { applyConfigEnv, configToml as buildConfigToml } from "./lib/config.mjs";
11
+ import { setupCodingAgent as linkCodingAgent } from "./lib/coding-agent.mjs";
12
+ import { clackSetup as askInClack } from "./lib/clack.mjs";
13
+ import { setupEnv as setupHindsightEnv, stackEnv as hindsightStackEnv } from "./lib/env-setup.mjs";
14
+ import { chooseModelBackend } from "./lib/model-backend.mjs";
15
+ import { ensureComposeOllamaModels } from "./lib/ollama-compose.mjs";
16
+ import { resolvePorts as resolveHindsightPorts } from "./lib/ports.mjs";
17
+ import { backendDefault, confGet, ensureState } from "./lib/state.mjs";
18
+ import { summary as hindsightSummary } from "./lib/summary.mjs";
19
+ import { validateHindsight } from "./lib/validate.mjs";
20
+
21
+ // Called by scripts/setup.mjs (the stack .env writer), not by the wizard.
22
+ export { migrateEnv, prepareStackEnv, setupNotices } from "./lib/stack-env.mjs";
23
+
24
+ /** @typedef {import('../../wizard/lib/provider-wizard/helpers.mjs').ProviderWizardHelpers} Helpers */
25
+
26
+ /**
27
+ * Quiet: fill Hindsight's context fields, map [stack]/[model] TOML defaults into the
28
+ * environment (including LEMOND_BASE from [model].lemond.host).
29
+ * @param {Record<string, any>} ctx
30
+ * @param {Helpers} h
31
+ */
32
+ export async function init(ctx, h) {
33
+ ensureState(ctx);
34
+ applyConfigEnv(ctx.pmMerged || {});
35
+ if (process.env.LEMOND_BASE) ctx.lemondBase = process.env.LEMOND_BASE;
36
+ }
37
+
38
+ /**
39
+ * @param {Record<string, any>} ctx
40
+ * @param {Helpers} h
41
+ */
42
+ export async function afterProvider(ctx, h) {
43
+ h.info("Hindsight uses LiteLLM aliases (memory-retain / reflect / consolidate).");
44
+ }
45
+
46
+ /**
47
+ * Step "3. Model backend": lemond, Ollama or a custom OpenAI-compatible URL behind the
48
+ * LiteLLM proxy.
49
+ * @param {Record<string, any>} ctx
50
+ * @param {Helpers} h
51
+ */
52
+ export async function configureModelRouting(ctx, h) {
53
+ await chooseModelBackend(ctx, h);
54
+ }
55
+
56
+ /** Hindsight's API and control-plane ports. */
57
+ export const resolvePorts = resolveHindsightPorts;
58
+
59
+ /**
60
+ * Quiet: description and environment for the stack setup script (scripts/setup.mjs).
61
+ * @param {Record<string, any>} ctx
62
+ */
63
+ export function stackEnv(ctx) {
64
+ return hindsightStackEnv(ctx);
65
+ }
66
+
67
+ /** hindsight.env, litellm.env wiring. */
68
+ export const setupEnv = setupHindsightEnv;
69
+
70
+ /**
71
+ * Quiet: reload the model choice from wizard.conf for `--up`.
72
+ * @param {Record<string, any>} ctx
73
+ * @param {Helpers} h
74
+ */
75
+ export async function hydrate(ctx, h) {
76
+ ensureState(ctx);
77
+ ctx.backend = confGet(ctx, h, "MEMORY_BACKEND") || backendDefault(ctx, h);
78
+ ctx.model = confGet(ctx, h, "MEMORY_MODEL") || ctx.lemondDefaultModel;
79
+ ctx.apiBase = confGet(ctx, h, "MEMORY_API_BASE") || "";
80
+ ctx.reflectModel = confGet(ctx, h, "MEMORY_REFLECT_MODEL") || "";
81
+ ctx.ollamaInCompose = confGet(ctx, h, "MEMORY_OLLAMA_IN_COMPOSE") === "1";
82
+ applyComposeProfiles(ctx);
83
+ }
84
+
85
+ /**
86
+ * Ensure compose profile `ollama` is on the up command when MEMORY_OLLAMA_IN_COMPOSE=1.
87
+ * @param {Record<string, any>} ctx
88
+ * @param {Helpers} _h
89
+ */
90
+ export async function beforeStack(ctx, _h) {
91
+ ensureState(ctx);
92
+ applyComposeProfiles(ctx);
93
+ }
94
+
95
+ /**
96
+ * Pull models into compose Ollama after the stack is up (host has no ollama CLI).
97
+ * @param {Record<string, any>} ctx
98
+ * @param {Helpers} h
99
+ */
100
+ export async function afterStackHealthy(ctx, h) {
101
+ await ensureComposeOllamaModels(ctx, h);
102
+ }
103
+
104
+ /** @param {Record<string, any>} ctx */
105
+ function applyComposeProfiles(ctx) {
106
+ const extra = [...(ctx.extraComposeProfiles || [])];
107
+ if (ctx.ollamaInCompose && !extra.includes("ollama")) extra.push("ollama");
108
+ ctx.extraComposeProfiles = extra;
109
+ }
110
+
111
+ /**
112
+ * Quiet: tables merged into config.toml.
113
+ * @param {Record<string, any>} ctx
114
+ */
115
+ export function configToml(ctx) {
116
+ ensureState(ctx);
117
+ return buildConfigToml(ctx);
118
+ }
119
+
120
+ /**
121
+ * Quiet: lines for the closing summary.
122
+ * @param {Record<string, any>} ctx
123
+ */
124
+ export function summary(ctx) {
125
+ return hindsightSummary(ctx);
126
+ }
127
+
128
+ /**
129
+ * Appends to ctx.validationErrors / ctx.validationChecks after the shared checks.
130
+ * @param {Record<string, any>} ctx
131
+ * @param {Helpers} h
132
+ */
133
+ export async function extendValidation(ctx, h) {
134
+ validateHindsight(ctx, h);
135
+ }
136
+
137
+ /** Link ~/.hindsight/coding-agent.json and check optInPaths. */
138
+ export const setupCodingAgent = linkCodingAgent;
139
+
140
+ /**
141
+ * Clack-only: ask the model questions, return the environment that makes the wizard
142
+ * skip its own (see WIZARD_SKIP_BACKEND_STEP).
143
+ * @param {Record<string, any>} ctx
144
+ * @param {typeof import("@clack/prompts")} ui
145
+ * @param {Helpers} h
146
+ */
147
+ export async function clackSetup(ctx, ui, h) {
148
+ return askInClack(ctx, ui, h);
149
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "id": "letta",
3
+ "label": "Letta",
4
+ "status": "planned",
5
+ "notes": "Placeholder manifest. Letta (formerly MemGPT) server integration is not wired; track via issues before changing status.",
6
+ "settings": {
7
+ "nativeDocs": "https://docs.letta.com/",
8
+ "nativeConfigPaths": [],
9
+ "wizardOwns": []
10
+ },
11
+ "compose": {
12
+ "profile": "letta",
13
+ "files": [],
14
+ "services": [],
15
+ "healthService": null,
16
+ "healthPath": null,
17
+ "hostPortEnv": null,
18
+ "defaultHostPort": null
19
+ },
20
+ "indexer": {
21
+ "apiUrlEnv": "LETTA_API_URL",
22
+ "sink": "letta"
23
+ },
24
+ "codingAgent": {
25
+ "pluginPackage": null,
26
+ "configTemplate": null
27
+ },
28
+ "wizard": {}
29
+ }