@pleri/olam-cli 0.1.125 → 0.1.126

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.
@@ -0,0 +1,98 @@
1
+ /**
2
+ * In-world MCP registration helper.
3
+ *
4
+ * After `olam create` spawns a fresh world container, we register the
5
+ * agentmemory MCP server inside that world's claude config so the
6
+ * in-world claude session reaches the host-side agent-memory service
7
+ * without manual `claude mcp add` from the operator.
8
+ *
9
+ * Architecture:
10
+ *
11
+ * host docker exec world container
12
+ * ---- ---------- ---------------
13
+ * olam create ─► docker exec ─► claude mcp add agentmemory
14
+ * --scope user
15
+ * --env AGENTMEMORY_URL=...
16
+ * --env AGENTMEMORY_SECRET=...
17
+ * -- agentmemory-mcp
18
+ *
19
+ * The `agentmemory-mcp` bin is pre-baked into the devbox image (B2), so
20
+ * there's no `npx -y` cold-start at first claude invocation (T6).
21
+ *
22
+ * Idempotency: re-running against an already-registered world is a noop.
23
+ * We detect via `claude mcp list` exit code + stderr signature (same
24
+ * "No <scope> MCP server found" pattern reused from the host-side
25
+ * `olam memory uninstall` helper).
26
+ *
27
+ * Graceful skip: when the world wasn't spawned with AGENTMEMORY_* env
28
+ * (because the host memory service was absent at spawn time per B1),
29
+ * we skip registration with an informative log. The world keeps working
30
+ * — the @agentmemory/mcp shim falls back to local InMemoryKV.
31
+ *
32
+ * Plan reference: docs/plans/olam-agent-memory-distributed/phase-b-tasks.md B3
33
+ */
34
+ import { type SpawnSyncReturns } from 'node:child_process';
35
+ /** Inject points for tests. */
36
+ export interface DockerExecDeps {
37
+ /** Wrap spawnSync so tests can stub claude/docker calls. */
38
+ spawn: (cmd: string, args: string[], opts: {
39
+ encoding: 'utf8';
40
+ stdio: ['ignore', 'pipe', 'pipe'];
41
+ }) => SpawnSyncReturns<string>;
42
+ /** Optional logger; defaults to console.log. */
43
+ log?: (msg: string) => void;
44
+ }
45
+ export declare const DEFAULT_DOCKER_EXEC_DEPS: DockerExecDeps;
46
+ /**
47
+ * Result shape — `outcome` is the load-bearing field. Callers use it to
48
+ * decide whether to print a success line vs. a graceful-skip notice.
49
+ */
50
+ export type RegisterOutcome = {
51
+ outcome: 'registered';
52
+ scope: 'user';
53
+ } | {
54
+ outcome: 'already-registered';
55
+ scope: 'user';
56
+ } | {
57
+ outcome: 'skipped-no-env';
58
+ reason: string;
59
+ } | {
60
+ outcome: 'failed';
61
+ rc: number;
62
+ detail: string;
63
+ };
64
+ export interface RegisterAgentMemoryMcpOpts {
65
+ /** Full container name, e.g. `olam-<world-id>-devbox`. */
66
+ containerName: string;
67
+ /** The AGENTMEMORY_URL value that was injected at world spawn (B1). */
68
+ agentmemoryUrl: string;
69
+ /** The AGENTMEMORY_SECRET value that was injected at world spawn (B1). */
70
+ agentmemorySecret: string;
71
+ }
72
+ /**
73
+ * Probe whether `agentmemory` is already registered inside the world's
74
+ * claude. Uses `claude mcp list` which prints a structured list; we
75
+ * just look for the name in the output. Returns:
76
+ * - `'present'` — already registered (idempotent skip path)
77
+ * - `'absent'` — not registered yet (proceed to add)
78
+ * - `'unknown'` — `claude mcp list` failed for any reason; we don't
79
+ * pretend to know the state, caller decides.
80
+ */
81
+ export declare function probeMcpListed(containerName: string, deps: DockerExecDeps): 'present' | 'absent' | 'unknown';
82
+ /**
83
+ * Register the agentmemory MCP server inside a world container.
84
+ *
85
+ * - Skips silently when `agentmemorySecret` is empty (the world was
86
+ * spawned without B1's env injection because the host memory service
87
+ * was absent at spawn time).
88
+ * - Skips idempotently when `claude mcp list` already shows the server.
89
+ * - Otherwise shells to `docker exec <world> claude mcp add agentmemory
90
+ * --scope user --env ... -- agentmemory-mcp`.
91
+ *
92
+ * The bearer secret transits as an argv element of the spawned `claude`
93
+ * process inside the container (same shape as the host-side
94
+ * `olam memory install`). Documented in the operator-facing
95
+ * docs/architecture/agent-memory.md security model.
96
+ */
97
+ export declare function registerAgentMemoryMcp(opts: RegisterAgentMemoryMcpOpts, deps?: DockerExecDeps): RegisterOutcome;
98
+ //# sourceMappingURL=world-mcp-register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world-mcp-register.d.ts","sourceRoot":"","sources":["../../src/lib/world-mcp-register.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtE,+BAA+B;AAC/B,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,KAAK,EAAE,CACL,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,KAC1D,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC9B,gDAAgD;IAChD,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,eAAO,MAAM,wBAAwB,EAAE,cAGtC,CAAC;AAKF;;;GAGG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,OAAO,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,OAAO,EAAE,oBAAoB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,OAAO,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,OAAO,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,MAAM,WAAW,0BAA0B;IACzC,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,cAAc,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,cAAc,GACnB,SAAS,GAAG,QAAQ,GAAG,SAAS,CAYlC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,0BAA0B,EAChC,IAAI,GAAE,cAAyC,GAC9C,eAAe,CAoDjB"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * In-world MCP registration helper.
3
+ *
4
+ * After `olam create` spawns a fresh world container, we register the
5
+ * agentmemory MCP server inside that world's claude config so the
6
+ * in-world claude session reaches the host-side agent-memory service
7
+ * without manual `claude mcp add` from the operator.
8
+ *
9
+ * Architecture:
10
+ *
11
+ * host docker exec world container
12
+ * ---- ---------- ---------------
13
+ * olam create ─► docker exec ─► claude mcp add agentmemory
14
+ * --scope user
15
+ * --env AGENTMEMORY_URL=...
16
+ * --env AGENTMEMORY_SECRET=...
17
+ * -- agentmemory-mcp
18
+ *
19
+ * The `agentmemory-mcp` bin is pre-baked into the devbox image (B2), so
20
+ * there's no `npx -y` cold-start at first claude invocation (T6).
21
+ *
22
+ * Idempotency: re-running against an already-registered world is a noop.
23
+ * We detect via `claude mcp list` exit code + stderr signature (same
24
+ * "No <scope> MCP server found" pattern reused from the host-side
25
+ * `olam memory uninstall` helper).
26
+ *
27
+ * Graceful skip: when the world wasn't spawned with AGENTMEMORY_* env
28
+ * (because the host memory service was absent at spawn time per B1),
29
+ * we skip registration with an informative log. The world keeps working
30
+ * — the @agentmemory/mcp shim falls back to local InMemoryKV.
31
+ *
32
+ * Plan reference: docs/plans/olam-agent-memory-distributed/phase-b-tasks.md B3
33
+ */
34
+ import { spawnSync } from 'node:child_process';
35
+ export const DEFAULT_DOCKER_EXEC_DEPS = {
36
+ spawn: spawnSync,
37
+ log: (msg) => console.log(msg),
38
+ };
39
+ const MCP_NAME = 'agentmemory';
40
+ const MCP_BIN = 'agentmemory-mcp';
41
+ /**
42
+ * Probe whether `agentmemory` is already registered inside the world's
43
+ * claude. Uses `claude mcp list` which prints a structured list; we
44
+ * just look for the name in the output. Returns:
45
+ * - `'present'` — already registered (idempotent skip path)
46
+ * - `'absent'` — not registered yet (proceed to add)
47
+ * - `'unknown'` — `claude mcp list` failed for any reason; we don't
48
+ * pretend to know the state, caller decides.
49
+ */
50
+ export function probeMcpListed(containerName, deps) {
51
+ const result = deps.spawn('docker', ['exec', containerName, 'claude', 'mcp', 'list'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
52
+ if (result.status !== 0)
53
+ return 'unknown';
54
+ const combined = `${result.stdout ?? ''}\n${result.stderr ?? ''}`;
55
+ // `claude mcp list` prints one row per registered server. Match on
56
+ // the name at the start of a line to avoid false positives from
57
+ // descriptive prose.
58
+ return new RegExp(`^\\s*${MCP_NAME}\\b`, 'm').test(combined) ? 'present' : 'absent';
59
+ }
60
+ /**
61
+ * Register the agentmemory MCP server inside a world container.
62
+ *
63
+ * - Skips silently when `agentmemorySecret` is empty (the world was
64
+ * spawned without B1's env injection because the host memory service
65
+ * was absent at spawn time).
66
+ * - Skips idempotently when `claude mcp list` already shows the server.
67
+ * - Otherwise shells to `docker exec <world> claude mcp add agentmemory
68
+ * --scope user --env ... -- agentmemory-mcp`.
69
+ *
70
+ * The bearer secret transits as an argv element of the spawned `claude`
71
+ * process inside the container (same shape as the host-side
72
+ * `olam memory install`). Documented in the operator-facing
73
+ * docs/architecture/agent-memory.md security model.
74
+ */
75
+ export function registerAgentMemoryMcp(opts, deps = DEFAULT_DOCKER_EXEC_DEPS) {
76
+ if (!opts.agentmemorySecret || opts.agentmemorySecret.length === 0) {
77
+ return {
78
+ outcome: 'skipped-no-env',
79
+ reason: 'world has no AGENTMEMORY_SECRET (memory service was absent or --skip-memory active at spawn time)',
80
+ };
81
+ }
82
+ const probe = probeMcpListed(opts.containerName, deps);
83
+ if (probe === 'present') {
84
+ return { outcome: 'already-registered', scope: 'user' };
85
+ }
86
+ const args = [
87
+ 'exec',
88
+ opts.containerName,
89
+ 'claude',
90
+ 'mcp',
91
+ 'add',
92
+ MCP_NAME,
93
+ '--scope',
94
+ 'user',
95
+ '--env',
96
+ `AGENTMEMORY_URL=${opts.agentmemoryUrl}`,
97
+ '--env',
98
+ `AGENTMEMORY_SECRET=${opts.agentmemorySecret}`,
99
+ '--',
100
+ MCP_BIN,
101
+ ];
102
+ // Log without leaking the secret. Same redaction pattern as
103
+ // packages/cli/src/commands/memory/install.ts.
104
+ const redacted = args.map((a) => a.startsWith('AGENTMEMORY_SECRET=') ? 'AGENTMEMORY_SECRET=<redacted>' : a);
105
+ deps.log?.(`Wiring agent-memory MCP in ${opts.containerName}: docker ${redacted.join(' ')}`);
106
+ const result = deps.spawn('docker', args, {
107
+ encoding: 'utf8',
108
+ stdio: ['ignore', 'pipe', 'pipe'],
109
+ });
110
+ if (result.status === 0) {
111
+ return { outcome: 'registered', scope: 'user' };
112
+ }
113
+ // Defensive redaction of any echo-back of the env value.
114
+ const detail = (result.stderr?.trim() || result.stdout?.trim() || '(no output)').replace(/AGENTMEMORY_SECRET=\S+/g, 'AGENTMEMORY_SECRET=<redacted>');
115
+ return { outcome: 'failed', rc: result.status ?? 1, detail };
116
+ }
117
+ //# sourceMappingURL=world-mcp-register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"world-mcp-register.js","sourceRoot":"","sources":["../../src/lib/world-mcp-register.ts"],"names":[],"mappings":"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"}
@@ -23218,6 +23218,18 @@ function readHostCpToken() {
23218
23218
  return "";
23219
23219
  }
23220
23220
  }
23221
+ function readMemorySecret() {
23222
+ const fromEnv = process.env["OLAM_MEMORY_SECRET"];
23223
+ if (fromEnv && fromEnv.length > 0)
23224
+ return fromEnv;
23225
+ const file = path7.join(os5.homedir(), ".olam", "memory-secret");
23226
+ try {
23227
+ return fs5.readFileSync(file, "utf-8").trim();
23228
+ } catch {
23229
+ return "";
23230
+ }
23231
+ }
23232
+ var AGENTMEMORY_HOST_INTERNAL_URL = "http://host.docker.internal:3111";
23221
23233
  function sanitizeContainerName(name) {
23222
23234
  return name.replace(/[^a-zA-Z0-9_.-]/g, "-");
23223
23235
  }
@@ -23347,6 +23359,7 @@ var createWorldContainer = async (docker, worldId, worldName, image, env, resour
23347
23359
  const labels = olamLabels(worldId, worldName);
23348
23360
  const authSecret = readAuthSecret();
23349
23361
  const hostCpToken = readHostCpToken();
23362
+ const memorySecret = readMemorySecret();
23350
23363
  const worldEnv = {
23351
23364
  OLAM_WORLD_ID: worldId,
23352
23365
  OLAM_WORLD_NAME: worldName,
@@ -23363,6 +23376,14 @@ var createWorldContainer = async (docker, worldId, worldName, image, env, resour
23363
23376
  OLAM_HOST_CP_URL: "http://host.docker.internal:19000",
23364
23377
  ...authSecret ? { OLAM_AUTH_SECRET: authSecret } : {},
23365
23378
  ...hostCpToken ? { OLAM_HOST_CP_TOKEN: hostCpToken } : {},
23379
+ // Phase B1 — agent-memory wiring. Both vars are injected together
23380
+ // (or neither): without a secret the URL is useless. The in-world
23381
+ // @agentmemory/mcp shim (Phase B2/B3) reads these env vars to find
23382
+ // the host's REST endpoint.
23383
+ ...memorySecret ? {
23384
+ AGENTMEMORY_URL: AGENTMEMORY_HOST_INTERNAL_URL,
23385
+ AGENTMEMORY_SECRET: memorySecret
23386
+ } : {},
23366
23387
  ...env
23367
23388
  };
23368
23389
  const envList = Object.entries(worldEnv).map(([k, v]) => `${k}=${v}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pleri/olam-cli",
3
- "version": "0.1.125",
3
+ "version": "0.1.126",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "olam": "./bin/olam.cjs"