@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,254 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Context management (smart compaction) per agent: `[context_proxy.agents]` in config.toml (a missing key
|
|
4
|
+
* follows `[context_proxy] enabled`; with neither set, opencode2 is on and the others off), the agent wiring (install-coding-agent) and the compose
|
|
5
|
+
* `context-proxy` service. Shared by the setup wizard step, install-coding-agent and `openmem context`.
|
|
6
|
+
*/
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { contextRoot } from "./coding-agent/context-store.mjs";
|
|
12
|
+
import { proxyCodeVersion, summarizeEvents } from "./coding-agent/session-db.mjs";
|
|
13
|
+
import { agentsPointedAtContextProxy, ensureContextProxyKey, probeContextProxy } from "./coding-agent/install-context-proxy.mjs";
|
|
14
|
+
import { isMainModule } from "./is-main.mjs";
|
|
15
|
+
import { resolveStackRoot } from "./stack.mjs";
|
|
16
|
+
import {
|
|
17
|
+
CONTEXT_AGENT_KEYS,
|
|
18
|
+
contextAgentsFromPmConfig,
|
|
19
|
+
contextProxyFromPmConfig,
|
|
20
|
+
loadPmConfig,
|
|
21
|
+
updatePmConfigContextAgents,
|
|
22
|
+
} from "../wizard/lib/pm-config.mjs";
|
|
23
|
+
|
|
24
|
+
/** Compose service and profile name (compose.yaml). */
|
|
25
|
+
export const CONTEXT_PROXY_SERVICE = "context-proxy";
|
|
26
|
+
/**
|
|
27
|
+
* Agents whose "on" needs the proxy service. Codex goes through LiteLLM's ChatGPT-subscription pass-through,
|
|
28
|
+
* which the pinned dev image has (PR #41522; docs/compatibility.md, footnote 9).
|
|
29
|
+
*/
|
|
30
|
+
export const SERVICE_AGENT_KEYS = ["claude-code", "opencode1", "codex"];
|
|
31
|
+
/** How each config key is served. */
|
|
32
|
+
export const CONTEXT_AGENT_ROUTE = {
|
|
33
|
+
"claude-code": "context-proxy",
|
|
34
|
+
opencode1: "context-proxy",
|
|
35
|
+
codex: "context-proxy (ChatGPT login, /backend-api/codex)",
|
|
36
|
+
opencode2: "native plugin",
|
|
37
|
+
};
|
|
38
|
+
const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
39
|
+
const BIN = "openmem context";
|
|
40
|
+
|
|
41
|
+
const HELP = `${BIN}: turn context management (smart compaction) on or off per agent
|
|
42
|
+
|
|
43
|
+
Usage:
|
|
44
|
+
${BIN} on [--dry-run] [--agents a,b] save [context_proxy.agents] <agent> = true, re-run
|
|
45
|
+
install-coding-agent (starts the context-proxy service
|
|
46
|
+
when a proxy-routed agent is on)
|
|
47
|
+
${BIN} off [--dry-run] [--agents a,b] save <agent> = false, re-run install-coding-agent (stops
|
|
48
|
+
the service when no proxy-routed agent is on)
|
|
49
|
+
${BIN} status [--json] per-agent values, proxy health, agents pointed at it, and
|
|
50
|
+
the last 24 h of trims and fail-opens from the session store
|
|
51
|
+
|
|
52
|
+
Agents: ${CONTEXT_AGENT_KEYS.join(", ")} (default: all). claude-code, opencode1 and codex go through
|
|
53
|
+
the context-proxy service (codex keeps its ChatGPT login); opencode2 trims in its own plugin (on when
|
|
54
|
+
config sets neither its key nor enabled).
|
|
55
|
+
Docs: docs/context-management.md`;
|
|
56
|
+
|
|
57
|
+
/** True when any agent that needs the service resolves on. */
|
|
58
|
+
export function proxyServiceNeeded(agents) {
|
|
59
|
+
return SERVICE_AGENT_KEYS.some((k) => agents[k]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Key file and store directories the compose service needs before `up`
|
|
64
|
+
* (env_file, and a bind mount with create_host_path: false).
|
|
65
|
+
*/
|
|
66
|
+
export function prepareContextProxyFiles(home, env = process.env) {
|
|
67
|
+
ensureContextProxyKey(home);
|
|
68
|
+
for (const sub of ["sessions", "blobs"]) fs.mkdirSync(path.join(contextRoot(home, env), sub), { recursive: true, mode: 0o700 });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* True when compose reports a container for the service.
|
|
73
|
+
* @param {string[]} baseArgs `docker` arguments up to (not including) the compose subcommand
|
|
74
|
+
* @param {{ cwd?: string, env?: Record<string, string | undefined> }} [opts]
|
|
75
|
+
* @param {typeof spawnSync} [spawn]
|
|
76
|
+
*/
|
|
77
|
+
export function contextProxyRunning(baseArgs, opts = {}, spawn = spawnSync) {
|
|
78
|
+
const r = spawn("docker", [...baseArgs, "ps", "-q", CONTEXT_PROXY_SERVICE], { encoding: "utf8", ...opts });
|
|
79
|
+
return r.status === 0 && String(r.stdout || "").trim() !== "";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** `docker` arguments for compose calls on the context-proxy profile from the stack root. */
|
|
83
|
+
export function contextComposeArgs({ project, stackRoot }) {
|
|
84
|
+
return ["compose", "-p", project, ...(stackRoot ? ["--project-directory", stackRoot] : []), "--profile", CONTEXT_PROXY_SERVICE];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Start the service (`on`) or stop it when it runs and no agent points at it (`!on`). Skipped when the
|
|
89
|
+
* stack root has no compose.yaml. Returns what happened.
|
|
90
|
+
* @param {{ home: string, stackRoot?: string, project: string, on: boolean, dry?: boolean, env?: NodeJS.ProcessEnv,
|
|
91
|
+
* spawn?: typeof spawnSync, log: { info: (m: string) => void, warn: (m: string) => void } }} opts
|
|
92
|
+
* @returns {"started"|"stopped"|"kept"|"not-running"|"no-stack"|"failed"|"dry-run"}
|
|
93
|
+
*/
|
|
94
|
+
export function ensureContextProxyService({ home, stackRoot, project, on, dry = false, env = process.env, spawn = spawnSync, log }) {
|
|
95
|
+
if (!stackRoot || !fs.existsSync(path.join(stackRoot, "compose.yaml"))) return "no-stack";
|
|
96
|
+
const base = contextComposeArgs({ project, stackRoot });
|
|
97
|
+
const opts = { cwd: stackRoot, env: { ...env } };
|
|
98
|
+
const run = (args) => {
|
|
99
|
+
const text = `docker ${args.join(" ")}`;
|
|
100
|
+
if (dry) {
|
|
101
|
+
log.info(`[dry-run] ${text}`);
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
log.info(text);
|
|
105
|
+
return spawn("docker", args, { stdio: "inherit", ...opts }).status ?? 1;
|
|
106
|
+
};
|
|
107
|
+
if (on) {
|
|
108
|
+
if (!dry) prepareContextProxyFiles(home, env);
|
|
109
|
+
return run([...base, "up", "-d", CONTEXT_PROXY_SERVICE]) === 0 ? (dry ? "dry-run" : "started") : "failed";
|
|
110
|
+
}
|
|
111
|
+
if (!contextProxyRunning(base, opts, spawn)) return "not-running";
|
|
112
|
+
const pointed = dry ? [] : agentsPointedAtContextProxy(home);
|
|
113
|
+
if (pointed.length) {
|
|
114
|
+
log.warn(`context-proxy left running: ${pointed.join(", ")} still point at it`);
|
|
115
|
+
return "kept";
|
|
116
|
+
}
|
|
117
|
+
return run([...base, "stop", CONTEXT_PROXY_SERVICE]) === 0 ? (dry ? "dry-run" : "stopped") : "failed";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Proxy code version of the checkout the compose service mounts (stack root, else this package), or null. */
|
|
121
|
+
function checkoutProxyVersion(stackRoot) {
|
|
122
|
+
for (const root of [stackRoot, PKG_ROOT].filter(Boolean)) {
|
|
123
|
+
try { return proxyCodeVersion(path.join(root, "proxy", "openmem_context")); } catch { /* not there */ }
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Current state: per-agent values, port, whether the proxy answers, which agents point at it, and the
|
|
130
|
+
* session store's recent events (summarizeEvents) with the checkout's proxy code version to compare.
|
|
131
|
+
* @param {{ home: string, stackRoot?: string, fetch?: typeof fetch, env?: NodeJS.ProcessEnv, now?: number }} opts
|
|
132
|
+
*/
|
|
133
|
+
export async function contextStatus({ home, stackRoot, fetch, env = process.env, now }) {
|
|
134
|
+
const cfg = loadPmConfig(home, stackRoot || home).merged;
|
|
135
|
+
const { port } = contextProxyFromPmConfig(cfg);
|
|
136
|
+
const { agents, saved, enabledSaved } = contextAgentsFromPmConfig(cfg);
|
|
137
|
+
return {
|
|
138
|
+
enabled: contextProxyFromPmConfig(cfg).enabled,
|
|
139
|
+
enabledSaved,
|
|
140
|
+
agents: Object.fromEntries(CONTEXT_AGENT_KEYS.map((k) => [k, { on: agents[k], saved: saved.includes(k), route: CONTEXT_AGENT_ROUTE[k] }])),
|
|
141
|
+
port,
|
|
142
|
+
proxyUp: await probeContextProxy(port, fetch ? { fetch } : {}),
|
|
143
|
+
pointedAgents: agentsPointedAtContextProxy(home),
|
|
144
|
+
events: summarizeEvents(path.join(contextRoot(home, env), "sessions"), now === undefined ? {} : { now }),
|
|
145
|
+
checkoutProxyVersion: checkoutProxyVersion(stackRoot),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Text lines for the events part of `status`. */
|
|
150
|
+
export function eventLines(s) {
|
|
151
|
+
const e = s.events;
|
|
152
|
+
const lines = [`last ${e.hours} h (${e.sessions} session${e.sessions === 1 ? "" : "s"}): ${e.trims} trim${e.trims === 1 ? "" : "s"}`
|
|
153
|
+
+ (e.trims ? `, ~${e.tokensSaved} tokens saved (estimated ${e.tokensBefore} -> ${e.tokensAfter}), ${e.dropped} messages dropped` : "")
|
|
154
|
+
+ `, ${e.failOpens} fail-open${e.failOpens === 1 ? "" : "s"} (request sent untrimmed)`];
|
|
155
|
+
for (const x of e.lastErrors) lines.push(` ${x.at} ${x.error} at ${x.location} (${x.version}, session ${x.session})`);
|
|
156
|
+
lines.push(...usageLines(e.usage));
|
|
157
|
+
const running = e.latest.proxy;
|
|
158
|
+
if (running && s.checkoutProxyVersion) {
|
|
159
|
+
lines.push(`proxy code: ${running === s.checkoutProxyVersion ? `${running}, matches the checkout`
|
|
160
|
+
: `${running} in the last event, checkout is ${s.checkoutProxyVersion}; restart the context-proxy service to load it`}`);
|
|
161
|
+
}
|
|
162
|
+
lines.push("not recorded here: fail-opens before the session id is known or from the session database itself (proxy stderr only)");
|
|
163
|
+
return lines;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Prompt-cache lines from summarizeEvents' `usage`; none when no usage event was recorded. */
|
|
167
|
+
function usageLines(u) {
|
|
168
|
+
if (!u?.requests) return [];
|
|
169
|
+
const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
170
|
+
const ratio = u.hitRatio === null ? "n/a" : `${(u.hitRatio * 100).toFixed(1)}%`;
|
|
171
|
+
const lines = [`prompt cache: ${plural(u.requests, "request")} (${u.withCacheCounts} with cache counts), hit ratio ${ratio}`
|
|
172
|
+
+ ` (read ${u.cacheReadTokens} / read + creation ${u.cacheCreationTokens} + uncached ${u.inputTokens})`];
|
|
173
|
+
const avg = (g) => (g.withCacheCounts ? Math.round(g.cacheCreationTokens / g.withCacheCounts) : "n/a");
|
|
174
|
+
lines.push(` cache creation: ${u.newCut.cacheCreationTokens} on ${plural(u.newCut.requests, "request")} making a new cut`
|
|
175
|
+
+ ` (avg ${avg(u.newCut)}), ${u.other.cacheCreationTokens} on ${plural(u.other.requests, "other")} (avg ${avg(u.other)})`);
|
|
176
|
+
const counts = Object.entries(u.breakpoints).map(([n, k]) => `${n}: ${k}`).join(", ");
|
|
177
|
+
if (counts) {
|
|
178
|
+
lines.push(` cache_control breakpoints per request: ${counts}; top layouts `
|
|
179
|
+
+ u.breakpointLayouts.map((x) => `${x.at} (${x.requests})`).join(", "));
|
|
180
|
+
}
|
|
181
|
+
return lines;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function parseArgs(argv) {
|
|
185
|
+
const flags = { action: argv[0], dry: false, json: false, agents: [], help: false, unknown: [] };
|
|
186
|
+
for (let i = 1; i < argv.length; i++) {
|
|
187
|
+
const a = argv[i];
|
|
188
|
+
if (a === "--dry-run") flags.dry = true;
|
|
189
|
+
else if (a === "--json") flags.json = true;
|
|
190
|
+
else if (a === "--agents") flags.agents = String(argv[++i] || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
191
|
+
else if (a === "--help" || a === "-h") flags.help = true;
|
|
192
|
+
else flags.unknown.push(a);
|
|
193
|
+
}
|
|
194
|
+
if (["--help", "-h", "help"].includes(flags.action)) flags.help = true;
|
|
195
|
+
return flags;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* `openmem context on|off|status`. Returns the exit code.
|
|
200
|
+
* @param {{ argv: string[], home: string, stackRoot: string, env?: NodeJS.ProcessEnv,
|
|
201
|
+
* log?: { info: (m: string) => void, warn: (m: string) => void },
|
|
202
|
+
* spawn?: typeof spawnSync, fetch?: typeof fetch }} opts
|
|
203
|
+
*/
|
|
204
|
+
export async function runContextCommand(opts) {
|
|
205
|
+
const { home, stackRoot } = opts;
|
|
206
|
+
const env = opts.env || process.env;
|
|
207
|
+
const log = opts.log || { info: (m) => console.log(m), warn: (m) => console.warn(m) };
|
|
208
|
+
const spawn = opts.spawn || spawnSync;
|
|
209
|
+
const flags = parseArgs(opts.argv);
|
|
210
|
+
if (flags.help) {
|
|
211
|
+
log.info(HELP);
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
const badAgents = flags.agents.filter((a) => !CONTEXT_AGENT_KEYS.includes(a));
|
|
215
|
+
if (!["on", "off", "status"].includes(flags.action) || flags.unknown.length || badAgents.length) {
|
|
216
|
+
if (badAgents.length) log.warn(`unknown agent: ${badAgents.join(", ")} (known: ${CONTEXT_AGENT_KEYS.join(", ")})`);
|
|
217
|
+
log.warn(HELP);
|
|
218
|
+
return 2;
|
|
219
|
+
}
|
|
220
|
+
if (flags.action === "status") {
|
|
221
|
+
const s = await contextStatus({ home, stackRoot, fetch: opts.fetch, env });
|
|
222
|
+
if (flags.json) log.info(JSON.stringify(s, null, 2));
|
|
223
|
+
else {
|
|
224
|
+
for (const [k, a] of Object.entries(s.agents)) {
|
|
225
|
+
log.info(`${k.padEnd(12)} ${a.on ? "on " : "off"} ${a.saved ? "saved" : s.enabledSaved ? `follows enabled = ${s.enabled}` : "default"} (${a.route})`);
|
|
226
|
+
}
|
|
227
|
+
log.info(`context proxy http://127.0.0.1:${s.port}: ${s.proxyUp ? "up" : "not answering"}`);
|
|
228
|
+
log.info(`agents pointed at the proxy: ${s.pointedAgents.length ? s.pointedAgents.join(", ") : "none"}`);
|
|
229
|
+
for (const line of eventLines(s)) log.info(line);
|
|
230
|
+
}
|
|
231
|
+
return 0;
|
|
232
|
+
}
|
|
233
|
+
const on = flags.action === "on";
|
|
234
|
+
const keys = flags.agents.length ? flags.agents : CONTEXT_AGENT_KEYS;
|
|
235
|
+
const saved = updatePmConfigContextAgents(home, Object.fromEntries(keys.map((k) => [k, on])), { dry: flags.dry });
|
|
236
|
+
const summary = keys.map((k) => `${k} = ${on}`).join(", ");
|
|
237
|
+
log.info(`${saved.result === "dry-run" ? "[dry-run] would save" : "saved"} [context_proxy.agents] ${summary}`);
|
|
238
|
+
// install-coding-agent follows the saved values: it wires agents and starts or stops the service.
|
|
239
|
+
const args = [path.join(PKG_ROOT, "cli", "install-coding-agent.mjs"), "--yes"];
|
|
240
|
+
if (flags.dry) args.push("--dry-run");
|
|
241
|
+
return spawn(process.execPath, args, { stdio: "inherit", env }).status ?? 1;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (isMainModule(import.meta.url)) {
|
|
245
|
+
const home = process.env.HOME || "";
|
|
246
|
+
const sr = resolveStackRoot({ pkgRoot: PKG_ROOT });
|
|
247
|
+
runContextCommand({ argv: process.argv.slice(2), home, stackRoot: sr.root }).then(
|
|
248
|
+
(code) => process.exit(code),
|
|
249
|
+
(err) => {
|
|
250
|
+
console.error(err);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
},
|
|
253
|
+
);
|
|
254
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Run indexer/feed_sync.py (RSS + OpenAlex → markdown cache). */
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { isMainModule } from "./is-main.mjs";
|
|
7
|
+
|
|
8
|
+
const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
+
const script = path.join(PKG_ROOT, "indexer", "feed_sync.py");
|
|
10
|
+
|
|
11
|
+
export function pythonCandidates(platform = process.platform, env = process.env) {
|
|
12
|
+
if (env.PYTHON) return [[env.PYTHON, []]];
|
|
13
|
+
return platform === "win32" ? [["py", ["-3"]], ["python", []]] : [["python3", []], ["python", []]];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (isMainModule(import.meta.url)) {
|
|
17
|
+
let missing = true;
|
|
18
|
+
for (const [command, prefix] of pythonCandidates()) {
|
|
19
|
+
const result = spawnSync(command, [...prefix, script, ...process.argv.slice(2)], { stdio: "inherit", cwd: PKG_ROOT });
|
|
20
|
+
if (result.error?.code === "ENOENT") continue;
|
|
21
|
+
missing = false;
|
|
22
|
+
if (result.error) console.error(`feed-sync: ${result.error.message}`);
|
|
23
|
+
process.exit(result.status ?? 1);
|
|
24
|
+
}
|
|
25
|
+
if (missing) console.error("feed-sync: Python 3 not found; install Python 3 with PyYAML or set PYTHON to its executable");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Install coding-agent session memory for Claude Code, Codex, OpenCode, Cursor CLI.
|
|
4
|
+
* All providers: unified MCP. Session hooks come from OpenMem ([agents] integration =
|
|
5
|
+
* "openmem", default) or the provider's own plugin ("provider", Hindsight only); never both.
|
|
6
|
+
* Optional: rtk command rewriting when rtk is on PATH, and the local context proxy.
|
|
7
|
+
*/
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { CODING_AGENTS, DEFAULT_AGENT_IDS } from "./coding-agent/agents.mjs";
|
|
13
|
+
import { AGENT_IDS, PROVIDER_CAPABILITIES } from "./coding-agent/capabilities.mjs";
|
|
14
|
+
import { installSessionShim, removeSessionShim, removeUpstreamHooks } from "./coding-agent/install-shim.mjs";
|
|
15
|
+
import { installAgentAccess } from "./coding-agent/install-access.mjs";
|
|
16
|
+
import { contextProxyStartCommand, installContextProxy, probeContextProxy } from "./coding-agent/install-context-proxy.mjs";
|
|
17
|
+
import { upsertEnvFile } from "./coding-agent/file-io.mjs";
|
|
18
|
+
import { installRtk } from "./coding-agent/install-rtk.mjs";
|
|
19
|
+
import { removeUpstreamAccess } from "./coding-agent/remove-upstream-access.mjs";
|
|
20
|
+
import { opencodeMajorVersion } from "./coding-agent/opencode-config.mjs";
|
|
21
|
+
import { prepareAgentRuntime } from "./coding-agent/runtime.mjs";
|
|
22
|
+
import { SHIM_AGENT_IDS } from "./coding-agent/hook-harnesses.mjs";
|
|
23
|
+
import { resolveMemoryProvider } from "./install-memory-mcp.mjs";
|
|
24
|
+
import { isMainModule } from "./is-main.mjs";
|
|
25
|
+
import { resolveHindsightApiUrl } from "./mcp/context.mjs";
|
|
26
|
+
import { resolveStackRoot } from "./stack.mjs";
|
|
27
|
+
import { runInstallMemoryMcp } from "./install-memory-mcp.mjs";
|
|
28
|
+
import { ensureContextProxyService, proxyServiceNeeded } from "./context-management.mjs";
|
|
29
|
+
import {
|
|
30
|
+
agentIntegrationFromPmConfig,
|
|
31
|
+
contextAgentsFromPmConfig,
|
|
32
|
+
contextProxyFromPmConfig,
|
|
33
|
+
contextProxyStackEnvFromPmConfig,
|
|
34
|
+
loadPmConfig,
|
|
35
|
+
updatePmConfigTable,
|
|
36
|
+
} from "../wizard/lib/pm-config.mjs";
|
|
37
|
+
|
|
38
|
+
const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
39
|
+
const BIN = "openmem-install-coding-agent";
|
|
40
|
+
/** Providers that ship their own coding-agent plugin. */
|
|
41
|
+
const PROVIDER_PLUGINS = ["hindsight"];
|
|
42
|
+
|
|
43
|
+
const HELP = `${BIN}: wire session memory for coding agents
|
|
44
|
+
|
|
45
|
+
Usage:
|
|
46
|
+
npx openmem install-coding-agent [--yes] [--dry-run] [--agents a,b]
|
|
47
|
+
[--provider-plugin | --no-provider-plugin] [--no-rtk]
|
|
48
|
+
[--context-proxy | --no-context-proxy]
|
|
49
|
+
|
|
50
|
+
Default agents: ${DEFAULT_AGENT_IDS.join(", ")}
|
|
51
|
+
All known agents: ${CODING_AGENTS.map((a) => a.id).join(", ")}
|
|
52
|
+
|
|
53
|
+
Every provider gets install-mcp. Session hooks are exclusive:
|
|
54
|
+
default openmem hooks (SessionStart / prompt / Stop); removes upstream Hindsight
|
|
55
|
+
hooks, its "hindsight" MCP server and its hindsight-coding-agent skill
|
|
56
|
+
--provider-plugin the provider's own plugin (Hindsight: hindsight-coding-agents); removes openmem hooks
|
|
57
|
+
--no-provider-plugin switch back to openmem hooks
|
|
58
|
+
The choice is saved as [agents] integration in ~/.config/personal-memory/config.toml.
|
|
59
|
+
--hindsight-plugin was renamed to --provider-plugin.
|
|
60
|
+
|
|
61
|
+
rtk on PATH: runs rtk init for each supported agent (skip with --no-rtk).
|
|
62
|
+
Context proxy: without a flag, [context_proxy.agents] claude-code / opencode1 / codex (missing keys follow
|
|
63
|
+
[context_proxy] enabled) choose which agents go through the local context proxy. With a stack root,
|
|
64
|
+
the installer starts the context-proxy service when one of them is on and stops it when none is.
|
|
65
|
+
--context-proxy / --no-context-proxy override every agent for this run and save [context_proxy] enabled.
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
function parseArgs(argv) {
|
|
69
|
+
const flags = {
|
|
70
|
+
yes: false,
|
|
71
|
+
dry: false,
|
|
72
|
+
help: false,
|
|
73
|
+
providerPlugin: undefined,
|
|
74
|
+
rtk: true,
|
|
75
|
+
contextProxy: undefined,
|
|
76
|
+
agents: [...DEFAULT_AGENT_IDS],
|
|
77
|
+
unknown: [],
|
|
78
|
+
};
|
|
79
|
+
for (let i = 0; i < argv.length; i++) {
|
|
80
|
+
const a = argv[i];
|
|
81
|
+
if (a === "--yes" || a === "-y") flags.yes = true;
|
|
82
|
+
else if (a === "--dry-run") flags.dry = true;
|
|
83
|
+
else if (a === "--help" || a === "-h") flags.help = true;
|
|
84
|
+
else if (a === "--provider-plugin") flags.providerPlugin = true;
|
|
85
|
+
else if (a === "--no-provider-plugin") flags.providerPlugin = false;
|
|
86
|
+
else if (a === "--no-rtk") flags.rtk = false;
|
|
87
|
+
else if (a === "--context-proxy") flags.contextProxy = true;
|
|
88
|
+
else if (a === "--no-context-proxy") flags.contextProxy = false;
|
|
89
|
+
else if (a === "--agents") flags.agents = String(argv[++i] || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
90
|
+
else flags.unknown.push(a);
|
|
91
|
+
}
|
|
92
|
+
return flags;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function h() {
|
|
96
|
+
return {
|
|
97
|
+
info: (m) => console.log(m),
|
|
98
|
+
ok: (m) => console.log(m),
|
|
99
|
+
warn: (m) => console.warn(m),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Resolve integration and proxy settings from flags, then config; persist flag choices. */
|
|
104
|
+
function resolveSettings({ home, stackRoot, flags, write, log }) {
|
|
105
|
+
const cfg = loadPmConfig(home, stackRoot || home).merged;
|
|
106
|
+
const providerFlag = flags.providerPlugin;
|
|
107
|
+
const integration = providerFlag === undefined ? agentIntegrationFromPmConfig(cfg) : providerFlag ? "provider" : "openmem";
|
|
108
|
+
const proxy = contextProxyFromPmConfig(cfg);
|
|
109
|
+
const contextAgents = contextAgentsFromPmConfig(cfg).agents;
|
|
110
|
+
if (flags.contextProxy !== undefined) {
|
|
111
|
+
proxy.enabled = flags.contextProxy;
|
|
112
|
+
for (const k of Object.keys(contextAgents)) contextAgents[k] = flags.contextProxy;
|
|
113
|
+
}
|
|
114
|
+
if (providerFlag !== undefined) {
|
|
115
|
+
const r = updatePmConfigTable(home, "agents", { integration }, { dry: !write });
|
|
116
|
+
log.info(`${r === "dry-run" ? "dry-run: would save" : "saved"} [agents] integration = "${integration}"`);
|
|
117
|
+
}
|
|
118
|
+
if (flags.contextProxy !== undefined) {
|
|
119
|
+
const r = updatePmConfigTable(home, "context_proxy", { enabled: proxy.enabled }, { dry: !write });
|
|
120
|
+
log.info(`${r === "dry-run" ? "dry-run: would save" : "saved"} [context_proxy] enabled = ${proxy.enabled}`);
|
|
121
|
+
}
|
|
122
|
+
return { integration, proxy, contextAgents, cfg };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Copy config.toml's proxy settings into the stack .env compose reads, when that file exists. */
|
|
126
|
+
function syncProxyStackEnv({ stackRoot, cfg, log }) {
|
|
127
|
+
const entries = contextProxyStackEnvFromPmConfig(cfg);
|
|
128
|
+
const file = stackRoot ? path.join(stackRoot, ".env") : "";
|
|
129
|
+
if (!file || !Object.keys(entries).length || !fs.existsSync(file)) return;
|
|
130
|
+
upsertEnvFile(file, entries);
|
|
131
|
+
log.ok(`context proxy settings → ${file} (${Object.keys(entries).join(", ")})`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Warn loudly when the proxy is not answering: Claude Code fails every request until it runs. */
|
|
135
|
+
async function warnIfProxyDown({ port, startCommand, log, fetch }) {
|
|
136
|
+
if (await probeContextProxy(port, { fetch })) {
|
|
137
|
+
log.ok(`context proxy is up on http://127.0.0.1:${port}`);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
const bar = "!".repeat(72);
|
|
141
|
+
log.warn([
|
|
142
|
+
bar,
|
|
143
|
+
`! Context proxy is NOT running on http://127.0.0.1:${port}.`,
|
|
144
|
+
"! Claude Code (and OpenCode 1) now send every request there and will fail until it runs.",
|
|
145
|
+
`! Start it: ${startCommand}`,
|
|
146
|
+
"! Or disable: npx openmem install-coding-agent --yes --no-context-proxy",
|
|
147
|
+
bar,
|
|
148
|
+
].join("\n"));
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function runInstallCodingAgent(opts) {
|
|
153
|
+
const home = opts.home;
|
|
154
|
+
const env = opts.env || process.env;
|
|
155
|
+
const flags = opts.flags;
|
|
156
|
+
const log = opts.h || h();
|
|
157
|
+
for (const agentId of flags.agents) {
|
|
158
|
+
if (!AGENT_IDS.includes(agentId)) {
|
|
159
|
+
log.warn(`Unknown agent "${agentId}" (known: ${AGENT_IDS.join(", ")})`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const provider = resolveMemoryProvider(home, env);
|
|
163
|
+
const caps = PROVIDER_CAPABILITIES[provider] || {};
|
|
164
|
+
log.info(`Provider ${provider}: capabilities ${JSON.stringify(caps)}`);
|
|
165
|
+
|
|
166
|
+
const dry = flags.dry || !flags.yes;
|
|
167
|
+
const { integration, proxy, contextAgents, cfg } = resolveSettings({ home, stackRoot: opts.stackRoot, flags, write: !dry, log });
|
|
168
|
+
const project = env.PM_PROJECT || String(cfg.stack?.project || "") || "openmem";
|
|
169
|
+
const startCommand = contextProxyStartCommand({ stackRoot: opts.stackRoot, project });
|
|
170
|
+
// OpenCode 2 never uses the proxy; its plugin reads [context_proxy.agents] opencode2 itself.
|
|
171
|
+
const agentEnabled = { "claude-code": contextAgents["claude-code"], opencode: contextAgents.opencode1, codex: contextAgents.codex };
|
|
172
|
+
const serviceOn = proxyServiceNeeded(contextAgents);
|
|
173
|
+
const service = (dryRun) => ensureContextProxyService({
|
|
174
|
+
home, stackRoot: opts.stackRoot, project, on: serviceOn, dry: dryRun, env, spawn: opts.docker, log,
|
|
175
|
+
});
|
|
176
|
+
const useProviderPlugin = integration === "provider" && PROVIDER_PLUGINS.includes(provider);
|
|
177
|
+
if (integration === "provider" && !useProviderPlugin) {
|
|
178
|
+
log.warn(`Provider ${provider} has no coding-agent plugin; installing openmem hooks instead`);
|
|
179
|
+
}
|
|
180
|
+
const shimAgents = flags.agents.filter((id) => SHIM_AGENT_IDS.includes(id) || id === "opencode");
|
|
181
|
+
const opencodeMajor = flags.agents.includes("opencode") ? opencodeMajorVersion({ env }) : 1;
|
|
182
|
+
const runtimeRoot = prepareAgentRuntime({
|
|
183
|
+
home, repoRoot: opts.repoRoot, stackRoot: opts.stackRoot, dry, h: log,
|
|
184
|
+
});
|
|
185
|
+
const upstreamCmd = () => {
|
|
186
|
+
const apiUrl = resolveHindsightApiUrl({ home, stackRoot: opts.stackRoot, env });
|
|
187
|
+
return ["npx", "-y", "@vectorize-io/hindsight-coding-agents", "install", ...flags.agents, "--server", "self-hosted", "--api-url", apiUrl];
|
|
188
|
+
};
|
|
189
|
+
if (flags.dry) {
|
|
190
|
+
log.info("dry-run: would run install-mcp --yes");
|
|
191
|
+
if (useProviderPlugin) {
|
|
192
|
+
removeSessionShim({ home, agents: shimAgents, dry: true, h: log });
|
|
193
|
+
} else {
|
|
194
|
+
installSessionShim({ home, repoRoot: runtimeRoot, agents: shimAgents, dry: true, h: log, opencodeMajor });
|
|
195
|
+
removeUpstreamHooks({ home, agents: shimAgents, dry: true, h: log });
|
|
196
|
+
removeUpstreamAccess({ home, agents: flags.agents, dry: true, h: log });
|
|
197
|
+
}
|
|
198
|
+
installAgentAccess({ home, runtimeRoot, stackRoot: opts.stackRoot, agents: flags.agents, dry: true, h: log });
|
|
199
|
+
if (flags.rtk) installRtk({ agents: flags.agents, env, dry: true, h: log, opencodeMajor });
|
|
200
|
+
installContextProxy({ home, agents: flags.agents, enabled: proxy.enabled, agentEnabled, port: proxy.port, dry: true, h: log, opencodeMajor });
|
|
201
|
+
service(true);
|
|
202
|
+
if (useProviderPlugin) log.info(`dry-run: would also run ${upstreamCmd().join(" ")}`);
|
|
203
|
+
return { provider, integration, ran: false };
|
|
204
|
+
}
|
|
205
|
+
if (!flags.yes) {
|
|
206
|
+
log.warn("Re-run with --yes to install MCP + session hooks");
|
|
207
|
+
return { provider, integration, ran: false };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
await runInstallMemoryMcp({
|
|
211
|
+
home,
|
|
212
|
+
repoRoot: runtimeRoot,
|
|
213
|
+
stackRoot: opts.stackRoot,
|
|
214
|
+
flags: { yes: true, dry: false, notebooklm: false, notebooklmOnly: false, providerOnly: false, noUnifiedMcp: false, help: false, unknown: [] },
|
|
215
|
+
env,
|
|
216
|
+
h: log,
|
|
217
|
+
});
|
|
218
|
+
if (useProviderPlugin) {
|
|
219
|
+
removeSessionShim({ home, agents: shimAgents, h: log });
|
|
220
|
+
} else {
|
|
221
|
+
installSessionShim({ home, repoRoot: runtimeRoot, agents: shimAgents, dry: false, h: log, opencodeMajor });
|
|
222
|
+
removeUpstreamHooks({ home, agents: shimAgents, h: log });
|
|
223
|
+
removeUpstreamAccess({ home, agents: flags.agents, h: log });
|
|
224
|
+
}
|
|
225
|
+
installAgentAccess({
|
|
226
|
+
home, runtimeRoot, stackRoot: opts.stackRoot, agents: flags.agents, dry: false, h: log,
|
|
227
|
+
});
|
|
228
|
+
const rtk = flags.rtk ? installRtk({ agents: flags.agents, env, h: log, spawn: opts.spawn, opencodeMajor }) : { rtk: null, ran: [] };
|
|
229
|
+
// On: start the service before agents point at it. Off: stop it after they are unwired.
|
|
230
|
+
if (serviceOn) service(false);
|
|
231
|
+
const proxyOut = installContextProxy({ home, agents: flags.agents, enabled: proxy.enabled, agentEnabled, port: proxy.port, h: log, opencodeMajor });
|
|
232
|
+
if (!serviceOn) service(false);
|
|
233
|
+
const proxyUsed = flags.agents.some((id) => id in agentEnabled && agentEnabled[id] && !proxyOut.pluginTrimmed.includes(id));
|
|
234
|
+
if (proxyUsed) {
|
|
235
|
+
syncProxyStackEnv({ stackRoot: opts.stackRoot, cfg, log });
|
|
236
|
+
await warnIfProxyDown({ port: proxy.port, startCommand, log, fetch: opts.fetch });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let status = 0;
|
|
240
|
+
if (useProviderPlugin) {
|
|
241
|
+
const cmd = upstreamCmd();
|
|
242
|
+
log.info(`Installing upstream Hindsight plugin: ${cmd.join(" ")}`);
|
|
243
|
+
const r = (opts.spawn || spawnSync)(cmd[0], cmd.slice(1), { stdio: "inherit", env });
|
|
244
|
+
status = r.status ?? 1;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const agentId of flags.agents) {
|
|
248
|
+
if (shimAgents.includes(agentId)) {
|
|
249
|
+
const hooks = useProviderPlugin ? "upstream Hindsight hooks" : "openmem session hooks";
|
|
250
|
+
const extras = [
|
|
251
|
+
rtk.ran.includes(agentId) ? "rtk" : "",
|
|
252
|
+
proxyOut.changed.includes(agentId) && agentEnabled[agentId] ? "context proxy" : "",
|
|
253
|
+
!useProviderPlugin && proxyOut.pluginTrimmed.includes(agentId) ? "native context trimming (OpenCode 2; context proxy not used)" : "",
|
|
254
|
+
].filter(Boolean);
|
|
255
|
+
log.info(` ${agentId}: ${hooks} + unified MCP${extras.length ? ` + ${extras.join(" + ")}` : ""}`);
|
|
256
|
+
} else {
|
|
257
|
+
const row = CODING_AGENTS.find((a) => a.id === agentId);
|
|
258
|
+
log.info(` ${agentId}: ${row?.sessionPath || "unified MCP memory_*"}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (agentEnabled["claude-code"] && flags.agents.includes("claude-code")) {
|
|
262
|
+
log.info(`Context proxy enabled: Claude Code now sends every request to http://127.0.0.1:${proxy.port}; start the proxy before using Claude Code: ${startCommand}`);
|
|
263
|
+
}
|
|
264
|
+
return { provider, integration, ran: true, status };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function main() {
|
|
268
|
+
const flags = parseArgs(process.argv.slice(2));
|
|
269
|
+
if (flags.unknown.includes("--hindsight-plugin")) {
|
|
270
|
+
console.error("install-coding-agent: --hindsight-plugin was renamed to --provider-plugin");
|
|
271
|
+
process.exit(2);
|
|
272
|
+
}
|
|
273
|
+
if (flags.help || flags.unknown.length) {
|
|
274
|
+
console.log(HELP);
|
|
275
|
+
process.exit(flags.unknown.length ? 2 : 0);
|
|
276
|
+
}
|
|
277
|
+
const home = process.env.HOME || "";
|
|
278
|
+
const sr = resolveStackRoot({ pkgRoot: PKG_ROOT });
|
|
279
|
+
const out = await runInstallCodingAgent({
|
|
280
|
+
home,
|
|
281
|
+
repoRoot: PKG_ROOT,
|
|
282
|
+
stackRoot: sr.root,
|
|
283
|
+
flags,
|
|
284
|
+
env: process.env,
|
|
285
|
+
h: h(),
|
|
286
|
+
});
|
|
287
|
+
return typeof out.status === "number" ? out.status : 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (isMainModule(import.meta.url)) {
|
|
291
|
+
main().then(
|
|
292
|
+
(code) => process.exit(code ?? 0),
|
|
293
|
+
(err) => {
|
|
294
|
+
console.error(err);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
}
|