@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,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,54 @@
|
|
|
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
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* GET `url` and report the HTTP status (0 when nothing answered) and the parsed JSON body.
|
|
37
|
+
* @param {string} url
|
|
38
|
+
* @param {{ headers?: Record<string, string>, timeoutMs?: number }} [opts]
|
|
39
|
+
* @returns {Promise<{ status: number, json: any }>}
|
|
40
|
+
*/
|
|
41
|
+
export async function fetchStatus(url, { headers = {}, timeoutMs = TIMEOUT_MS } = {}) {
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch(url, { headers, signal: AbortSignal.timeout(timeoutMs) });
|
|
44
|
+
let json = null;
|
|
45
|
+
try {
|
|
46
|
+
json = await res.json();
|
|
47
|
+
} catch {
|
|
48
|
+
json = null;
|
|
49
|
+
}
|
|
50
|
+
return { status: res.status, json };
|
|
51
|
+
} catch {
|
|
52
|
+
return { status: 0, json: null };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
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 fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { ensureLitellmEnv } from "../../../wizard/lib/litellm-env.mjs";
|
|
10
|
+
import { envValue } from "../../../wizard/lib/paths.mjs";
|
|
11
|
+
|
|
12
|
+
// Moved to wizard/lib/litellm-env.mjs (mem0 shares it); re-exported for existing importers.
|
|
13
|
+
export { ensureLitellmEnv };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {{ repo: string, home: string, env: NodeJS.ProcessEnv }} opts
|
|
17
|
+
* @returns {{ entries: [string, string][], drop: string[], report: string[] }}
|
|
18
|
+
* entries: KEY=value lines to add to the stack .env; drop: keys to remove first;
|
|
19
|
+
* report: KEY=value strings echoed in the "wrote .env" line
|
|
20
|
+
*/
|
|
21
|
+
export function prepareStackEnv({ repo, home, env }) {
|
|
22
|
+
const litellmEnv =
|
|
23
|
+
env.LITELLM_ENV_FILE || path.join(home, ".config/hindsight/litellm.env");
|
|
24
|
+
const overrideConfig = path.join(home, ".config/hindsight/litellm.yaml");
|
|
25
|
+
const backend = env.MEMORY_BACKEND || "lemond";
|
|
26
|
+
|
|
27
|
+
if (!["lemond", "ollama"].includes(backend)) {
|
|
28
|
+
throw new Error(`MEMORY_BACKEND must be lemond or ollama (got: ${backend})`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (ensureLitellmEnv(litellmEnv)) console.log(`created ${litellmEnv} (mode 600)`);
|
|
32
|
+
|
|
33
|
+
const litellmConfig = fs.existsSync(overrideConfig)
|
|
34
|
+
? overrideConfig
|
|
35
|
+
: path.join(repo, "config", `litellm.${backend}.yaml`);
|
|
36
|
+
|
|
37
|
+
const profiles = ["hindsight"];
|
|
38
|
+
if (env.MEMORY_OLLAMA_IN_COMPOSE === "1") profiles.push("ollama");
|
|
39
|
+
const notebookYml = path.join(home, ".config/hindsight/notebooklm.yml");
|
|
40
|
+
if (
|
|
41
|
+
fs.existsSync(notebookYml) &&
|
|
42
|
+
/^enabled:\s*true\b/m.test(fs.readFileSync(notebookYml, "utf8"))
|
|
43
|
+
) {
|
|
44
|
+
profiles.push("notebooklm");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @type {[string, string][]} */
|
|
48
|
+
const entries = [
|
|
49
|
+
["LITELLM_CONFIG", litellmConfig],
|
|
50
|
+
["COMPOSE_PROFILES", profiles.join(",")],
|
|
51
|
+
];
|
|
52
|
+
const drop = ["LITELLM_CONFIG", "COMPOSE_PROFILES"];
|
|
53
|
+
if (env.HINDSIGHT_PORT) {
|
|
54
|
+
drop.push("HINDSIGHT_PORT");
|
|
55
|
+
entries.push(["HINDSIGHT_PORT", env.HINDSIGHT_PORT]);
|
|
56
|
+
}
|
|
57
|
+
if (env.HINDSIGHT_CP_PORT) {
|
|
58
|
+
drop.push("HINDSIGHT_CP_PORT");
|
|
59
|
+
entries.push(["HINDSIGHT_CP_PORT", env.HINDSIGHT_CP_PORT]);
|
|
60
|
+
}
|
|
61
|
+
return { entries, drop, report: [`LITELLM_CONFIG=${litellmConfig}`] };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Point hindsight.env at the LiteLLM proxy (backs the file up first; prints no values).
|
|
66
|
+
* @param {{ repo: string, home: string, env: NodeJS.ProcessEnv }} opts
|
|
67
|
+
*/
|
|
68
|
+
export function migrateEnv({ home, env }) {
|
|
69
|
+
const hindsightEnv =
|
|
70
|
+
env.HINDSIGHT_ENV_FILE || path.join(home, ".config/hindsight/hindsight.env");
|
|
71
|
+
const litellmEnv =
|
|
72
|
+
env.LITELLM_ENV_FILE || path.join(home, ".config/hindsight/litellm.env");
|
|
73
|
+
if (!fs.existsSync(hindsightEnv)) {
|
|
74
|
+
throw new Error(`no ${hindsightEnv} to migrate`);
|
|
75
|
+
}
|
|
76
|
+
const key = envValue(litellmEnv, "LITELLM_MASTER_KEY");
|
|
77
|
+
if (!key) {
|
|
78
|
+
throw new Error(`LITELLM_MASTER_KEY missing from ${litellmEnv}`);
|
|
79
|
+
}
|
|
80
|
+
const backup = `${hindsightEnv}.bak-${Date.now()}`;
|
|
81
|
+
fs.copyFileSync(hindsightEnv, backup);
|
|
82
|
+
const llmLines = [
|
|
83
|
+
"HINDSIGHT_API_LLM_PROVIDER=openai",
|
|
84
|
+
"HINDSIGHT_API_LLM_BASE_URL=http://litellm:4000/v1",
|
|
85
|
+
"HINDSIGHT_API_LLM_MODEL=memory-reflect",
|
|
86
|
+
`HINDSIGHT_API_LLM_API_KEY=${key}`,
|
|
87
|
+
"HINDSIGHT_API_RETAIN_LLM_MODEL=memory-retain",
|
|
88
|
+
"HINDSIGHT_API_REFLECT_LLM_MODEL=memory-reflect",
|
|
89
|
+
"HINDSIGHT_API_CONSOLIDATION_LLM_MODEL=memory-consolidate",
|
|
90
|
+
"HINDSIGHT_API_MENTAL_MODEL_REFRESH_LLM_MODEL=memory-reflect",
|
|
91
|
+
];
|
|
92
|
+
const keys = llmLines.map((l) => l.split("=")[0]);
|
|
93
|
+
const keyRe = new RegExp(`^(${keys.join("|")})=`);
|
|
94
|
+
const rest = fs
|
|
95
|
+
.readFileSync(hindsightEnv, "utf8")
|
|
96
|
+
.split("\n")
|
|
97
|
+
.filter((l) => l && !keyRe.test(l));
|
|
98
|
+
fs.writeFileSync(hindsightEnv, `${[...rest, ...llmLines].join("\n")}\n`, { mode: 0o600 });
|
|
99
|
+
console.log(
|
|
100
|
+
`migrated ${hindsightEnv} to the LiteLLM proxy (backup: ${backup}). Values not printed.`,
|
|
101
|
+
);
|
|
102
|
+
console.log("Next: docker compose up -d (recreates hindsight with the new env)");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Printed after the .env is written when hindsight.env still calls a model server directly.
|
|
107
|
+
* @param {{ home: string, env: NodeJS.ProcessEnv }} opts
|
|
108
|
+
* @returns {string[]}
|
|
109
|
+
*/
|
|
110
|
+
export function setupNotices({ home, env }) {
|
|
111
|
+
const hindsightEnv =
|
|
112
|
+
env.HINDSIGHT_ENV_FILE || path.join(home, ".config/hindsight/hindsight.env");
|
|
113
|
+
if (
|
|
114
|
+
fs.existsSync(hindsightEnv) &&
|
|
115
|
+
/^HINDSIGHT_API_LLM_BASE_URL=http:\/\/host\.docker\.internal:/m.test(
|
|
116
|
+
fs.readFileSync(hindsightEnv, "utf8"),
|
|
117
|
+
)
|
|
118
|
+
) {
|
|
119
|
+
return [
|
|
120
|
+
`NOTE: ${hindsightEnv} still calls the model server directly.`,
|
|
121
|
+
" Run: node scripts/setup.mjs --migrate-env (backs up the file, rewrites only LLM lines)",
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
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", "meridian"];
|
|
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
|
+
/** Meridian's own env file from docs/meridian-litellm.md (read for MERIDIAN_API_KEY/HOST/PORT). */
|
|
27
|
+
meridianEnvFile: path.join(ctx.home || "", ".config/meridian/meridian.env"),
|
|
28
|
+
lemondBase: "http://127.0.0.1:13305",
|
|
29
|
+
ollamaBase: "http://127.0.0.1:11434",
|
|
30
|
+
lemondDefaultModel: LEMOND_DEFAULT_MODEL,
|
|
31
|
+
ollamaDefaultModel: OLLAMA_DEFAULT_MODEL,
|
|
32
|
+
customDefaultBase: "http://host.docker.internal:8080/v1",
|
|
33
|
+
backend: "",
|
|
34
|
+
model: "",
|
|
35
|
+
reflectModel: "",
|
|
36
|
+
apiBase: "",
|
|
37
|
+
lemondJson: null,
|
|
38
|
+
lemondNames: [],
|
|
39
|
+
ollamaJson: null,
|
|
40
|
+
ollamaNames: [],
|
|
41
|
+
/** True when Ollama runs as the compose profile `ollama` (no host install). */
|
|
42
|
+
ollamaInCompose: false,
|
|
43
|
+
portApi: "",
|
|
44
|
+
portCp: "",
|
|
45
|
+
hindsightUrl: "",
|
|
46
|
+
/** Meridian URL as seen from the host, and the key for litellm.env (backend meridian). */
|
|
47
|
+
meridianBase: "",
|
|
48
|
+
meridianKey: "",
|
|
49
|
+
};
|
|
50
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
51
|
+
if (ctx[key] === undefined) ctx[key] = value;
|
|
52
|
+
}
|
|
53
|
+
return ctx;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {Record<string, any>} ctx
|
|
58
|
+
* @param {import("../../../wizard/lib/provider-wizard/helpers.mjs").ProviderWizardHelpers} h
|
|
59
|
+
* @param {string} key wizard.conf key
|
|
60
|
+
*/
|
|
61
|
+
export function confGet(ctx, h, key) {
|
|
62
|
+
return h.envValue(ctx.wizardConf, key);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The stored value for `key`, but only when wizard.conf was written for the same backend.
|
|
67
|
+
*/
|
|
68
|
+
export function storedOr(ctx, h, backend, key, fallback) {
|
|
69
|
+
if (confGet(ctx, h, "MEMORY_BACKEND") !== backend) return fallback;
|
|
70
|
+
return confGet(ctx, h, key) || fallback;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** PM_BACKEND, then the saved MEMORY_BACKEND, then lemond. */
|
|
74
|
+
export function backendDefault(ctx, h) {
|
|
75
|
+
const pm = process.env.PM_BACKEND;
|
|
76
|
+
if (BACKENDS.includes(pm)) return pm;
|
|
77
|
+
const saved = confGet(ctx, h, "MEMORY_BACKEND");
|
|
78
|
+
if (BACKENDS.includes(saved)) return saved;
|
|
79
|
+
return "lemond";
|
|
80
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
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 { MERIDIAN_GUIDE, MERIDIAN_TERMS_URL } from "./meridian.mjs";
|
|
5
|
+
import { ensureState } from "./state.mjs";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Lines the meta wizard prints in its summary, in the order it prints them.
|
|
9
|
+
* @param {Record<string, any>} ctx
|
|
10
|
+
* @returns {{ running: string[], configFiles: string[], notes: string[], logServices: string[] }}
|
|
11
|
+
*/
|
|
12
|
+
export function summary(ctx) {
|
|
13
|
+
ensureState(ctx);
|
|
14
|
+
const modelWhere =
|
|
15
|
+
ctx.backend === "ollama" && ctx.ollamaInCompose
|
|
16
|
+
? "compose Ollama (profile ollama)"
|
|
17
|
+
: "the LiteLLM proxy (internal network)";
|
|
18
|
+
const running = [
|
|
19
|
+
` Hindsight API ${ctx.hindsightUrl} (docs: ${ctx.hindsightUrl}/docs)`,
|
|
20
|
+
` control plane http://127.0.0.1:${ctx.portCp}`,
|
|
21
|
+
` model ${ctx.backend}, ${ctx.model} via ${modelWhere}`,
|
|
22
|
+
];
|
|
23
|
+
if (ctx.backend === "meridian") {
|
|
24
|
+
running.push(` reflect model ${ctx.reflectModel || ctx.model}`);
|
|
25
|
+
running.push(
|
|
26
|
+
` Meridian ${ctx.meridianBase || "(host URL not recorded)"} on the host (not managed by setup; LiteLLM calls ${ctx.apiBase})`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
if (ctx.ollamaInCompose) {
|
|
30
|
+
running.push(` Ollama http://127.0.0.1:11434 (compose service; LiteLLM uses http://ollama:11434)`);
|
|
31
|
+
}
|
|
32
|
+
const configFiles = [` ${ctx.envFile}`];
|
|
33
|
+
if (fs.existsSync(ctx.litellmOverride)) {
|
|
34
|
+
configFiles.push(` ${ctx.litellmOverride} (model routes; replaces config/litellm.*.yaml)`);
|
|
35
|
+
} else if (ctx.backend === "custom" || ctx.backend === "meridian") {
|
|
36
|
+
configFiles.push(` ${ctx.litellmOverride} (missing: ${ctx.backend} backend needs it)`);
|
|
37
|
+
} else configFiles.push(` config/litellm.${ctx.backend}.yaml (shipped routes)`);
|
|
38
|
+
const litellmUp = composeCommandText(ctx, "up", "-d", "litellm");
|
|
39
|
+
const notes = [
|
|
40
|
+
`Control plane (banks, ops, LLM queue): http://127.0.0.1:${ctx.portCp}`,
|
|
41
|
+
`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}`,
|
|
42
|
+
`Change later: rerun ./setup. For a route-only edit, run: ${litellmUp}`,
|
|
43
|
+
];
|
|
44
|
+
if (ctx.backend === "meridian") {
|
|
45
|
+
configFiles.push(` ${ctx.litellmEnv} (MERIDIAN_API_KEY)`);
|
|
46
|
+
notes.push(
|
|
47
|
+
`Meridian must keep running on this host with MERIDIAN_HOST=0.0.0.0 so the LiteLLM container reaches it; see ${MERIDIAN_GUIDE}.`,
|
|
48
|
+
`Claude subscription use through Meridian is governed by Anthropic's Consumer Terms: ${MERIDIAN_TERMS_URL}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (ctx.smokeFailed) {
|
|
52
|
+
notes.unshift(
|
|
53
|
+
`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}`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return { running, configFiles, notes, logServices: ["hindsight", "litellm"] };
|
|
57
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
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 === "meridian") {
|
|
73
|
+
if (!fs.existsSync(ctx.litellmOverride)) errors.push("meridian backend without litellm.yaml override");
|
|
74
|
+
if (!h.envValue(ctx.litellmEnv, "MERIDIAN_API_KEY")) errors.push("litellm.env missing MERIDIAN_API_KEY");
|
|
75
|
+
checks.push("Meridian route and key");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (backend !== "custom" && model !== shipped && !reflect && !fs.existsSync(ctx.litellmOverride)) {
|
|
79
|
+
errors.push("custom model without litellm.yaml override");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -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
|
+
}
|