@pleri/olam-cli 0.1.142 → 0.1.143

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/dist/index.js CHANGED
@@ -3,12 +3,6 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
7
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
8
- }) : x)(function(x) {
9
- if (typeof require !== "undefined") return require.apply(this, arguments);
10
- throw Error('Dynamic require of "' + x + '" is not supported');
11
- });
12
6
  var __esm = (fn, res) => function __init() {
13
7
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
14
8
  };
@@ -8933,7 +8927,7 @@ function carryUncommittedEdits(repos, workspacePath, deps = {}) {
8933
8927
  const exec = deps.exec ?? ((cmd, args, opts) => execFileSync4(cmd, args, opts));
8934
8928
  const homedir36 = deps.homedir ?? (() => os9.homedir());
8935
8929
  const existsSync64 = deps.existsSync ?? ((p) => fs15.existsSync(p));
8936
- const copyFileSync9 = deps.copyFileSync ?? ((src, dest) => fs15.copyFileSync(src, dest));
8930
+ const copyFileSync8 = deps.copyFileSync ?? ((src, dest) => fs15.copyFileSync(src, dest));
8937
8931
  const mkdirSync36 = deps.mkdirSync ?? ((dirPath, opts) => {
8938
8932
  fs15.mkdirSync(dirPath, opts);
8939
8933
  });
@@ -9009,7 +9003,7 @@ function carryUncommittedEdits(repos, workspacePath, deps = {}) {
9009
9003
  continue;
9010
9004
  try {
9011
9005
  mkdirSync36(path16.dirname(dest), { recursive: true });
9012
- copyFileSync9(src, dest);
9006
+ copyFileSync8(src, dest);
9013
9007
  } catch (err) {
9014
9008
  const msg = err instanceof Error ? err.message : String(err);
9015
9009
  console.warn(`[carry] ${plan.name}: copy untracked ${rel} failed: ${msg}`);
@@ -9056,125 +9050,103 @@ ${stderr.trim()}` : "";
9056
9050
  }
9057
9051
  });
9058
9052
 
9053
+ // ../core/dist/world/templates/_generated.js
9054
+ var GH_PR_CREATE, LANE_ORCHESTRATION, WORLD_CLAUDE_MD;
9055
+ var init_generated = __esm({
9056
+ "../core/dist/world/templates/_generated.js"() {
9057
+ "use strict";
9058
+ GH_PR_CREATE = '# Creating PRs from inside an Olam world\n\n## The problem\n\nCalling `gh pr create` directly inside an Olam container **hangs forever** on a\nTTY confirmation prompt that the agent cannot answer. This is a known, reproduced issue.\n\n## The fix\n\nAlways use this exact pattern:\n\n```bash\necho y | timeout 30 gh pr create \\\n --base main \\\n --head <branch> \\\n --title "<title>" \\\n --body-file /tmp/pr-body.md\n```\n\n## Why each part matters\n\n- **`echo y |`** \u2014 answers the "Submit?" confirmation prompt up front so `gh` never pauses.\n- **`timeout 30`** \u2014 hard deadline. If `gh` hangs anyway, the command exits non-zero so the\n agent can react instead of stalling the world indefinitely.\n- **`--body-file /tmp/pr-body.md`** \u2014 write the PR body to a temp file first. Avoids\n shell-escaping bugs that occur with `--body "..."` for long or multi-line bodies.\n- **`--head` and `--base` explicitly** \u2014 removes all interactive prompts; `gh` has nothing to ask.\n\n## Troubleshooting\n\n**"no commits" or "branch not found" error:**\n\n```bash\ngit push -u origin <branch>\n# then retry:\necho y | timeout 30 gh pr create --base main --head <branch> --title "..." --body-file /tmp/pr-body.md\n```\n\n**Timeout exceeded (command exits 124):**\n\n- Check if the branch was pushed: `git log origin/<branch> --oneline -1`\n- Check GitHub CLI auth: `gh auth status`\n- Retry once; if it hangs again, open the PR via the GitHub web UI.\n';
9059
+ LANE_ORCHESTRATION = '# Lane Orchestration\n\nOlam worlds support parallel work via **lanes** \u2014 independent Claude Code sessions\nrunning in isolated git worktrees inside the same container.\n\nUse lanes when a task has 2+ independent scopes that can work simultaneously\nwithout file conflicts.\n\n## Container API\n\nBase URL: `http://localhost:8080`\n\n### Create a lane\n\n```bash\ncurl -s -X POST \\\n -H \'Content-Type: application/json\' \\\n -d \'{"name":"auth-fix","task":"Fix auth bugs","scope":["src/auth/**"],"avoids":["tests/**"]}\' \\\n http://localhost:8080/lanes/create\n```\n\n### List lanes\n\n```bash\ncurl -s http://localhost:8080/lanes\n```\n\n### Dispatch a prompt to a lane\n\n```bash\ncurl -s -X POST \\\n -H \'Content-Type: application/json\' \\\n -d \'{"prompt":"Start by reviewing..."}\' \\\n http://localhost:8080/lanes/auth-fix/dispatch\n```\n\n### Check lane status\n\n```bash\ncurl -s http://localhost:8080/lanes/auth-fix\n```\n\n### Merge a lane\n\n```bash\ncurl -s -X POST http://localhost:8080/lanes/auth-fix/merge\n```\n\n### Destroy a lane\n\n```bash\ncurl -s -X DELETE http://localhost:8080/lanes/auth-fix\n```\n\n## When to use lanes\n\n- Task has 2+ independent scopes (different directories/modules)\n- Work can be done simultaneously without file conflicts\n- Estimated effort > ~30 tool calls per scope\n\n## When NOT to use lanes\n\n- Simple tasks touching few files\n- Sequential work where each step depends on the previous\n- Everything is in one directory\n\n## Orchestration workflow\n\n1. Analyze the task \u2014 identify independent work streams\n2. Create lanes with non-overlapping scope globs\n3. Dispatch initial prompts to each lane\n4. **Monitor progress** \u2014 poll lane status every 30 seconds:\n ```bash\n while true; do\n STATUS=$(curl -s http://localhost:8080/lanes)\n RUNNING=$(echo "$STATUS" | jq \'[.lanes[] | select(.status == "running")] | length\')\n echo "Running lanes: $RUNNING"\n if [ "$RUNNING" = "0" ]; then echo "All lanes complete!"; break; fi\n sleep 30\n done\n ```\n5. **When a lane completes** (status is "completed" or no longer "running"):\n - Check its output: `curl -s http://localhost:8080/lanes/<name>`\n - If satisfactory, merge it: `curl -s -X POST http://localhost:8080/lanes/<name>/merge`\n - The merge automatically triggers a Codex adversarial review\n6. **After ALL lanes are merged**:\n - Run tests: `npm test` or equivalent\n - Review combined changes: `git diff main...HEAD --stat`\n - Create a PR: see `~/.olam/docs/gh-pr-create.md`\n7. Clean up: `curl -s -X DELETE http://localhost:8080/lanes/<name>`\n\n## Act on completion immediately\n\nWhen all lanes are done, immediately start merging and creating the PR.\nDo not wait for the user to tell you to merge. The workflow is:\n**lanes complete \u2192 merge each \u2192 run tests \u2192 create PR \u2192 report to user.**\n';
9060
+ WORLD_CLAUDE_MD = '# Olam World: {{worldName}}\n\n{{taskBlock}}\n\n## Environment\n- World ID: {{worldId}}\n- Branch: {{branch}}{{servicesLine}}{{pleriPlaneLine}}\n\n## Repos in this World\n{{reposList}}\n\n{{planFileBlock}}## Instructions\n1. Work on the task described above\n2. Write tests first (TDD)\n3. Commit frequently to the world branch\n4. When done, push and the work will be available for review\n5. Your `AskUserQuestion` calls are intercepted and surfaced to the operator\'s host CP automatically. The operator answers in the SPA; claude resumes when keys are injected.\n\n## Sandbox & network \u2014 what is actually true\n\nThe olam-devbox container has FULL outbound internet. Confirm any time:\n\n```bash\ncurl -sI https://registry.npmjs.org/ # \u2192 HTTP 200\ncurl -sI https://github.com/ # \u2192 HTTP 200\n```\n\nRules:\n\n- **Never claim "the sandbox blocks X" without proving it.** Capture the actual `npm install` / `curl` error verbatim. A flaky postinstall is not a network block.\n- **Retry transient failures at least once** before falling back to a manual workaround. `npm install <pkg>` flakes happen; one retry catches >90%.\n- **If you genuinely need a workaround**, say so explicitly with the error message and the retry count, NOT "sandbox network blocked it." Truthful attribution = trustable telemetry.\n- **Playwright `chromium` downloads ~140 MB** from playwright.azureedge.net. Disk-space failures (not network) are the usual cause. Check `df -h` before blaming the network.\n\n## Halt semantics \u2014 when to stop and surface\n\nStop and call `AskUserQuestion` (which surfaces in the host CP) when you hit:\n\n- **Halt-N (new pattern)**: a new dep family, framework, or architectural pattern not in the existing codebase. Don\'t silently adopt it \u2014 surface the choice.\n- **Halt-B (business deal-breaker)**: a breaking constraint that makes the task\'s goal unsatisfiable as stated. Surface the conflict; let the operator re-scope.\n- **Halt-C (complexity blowup)**: the diff is exceeding 1.5x of what the task implied. Surface for re-scoping before continuing.\n\nDon\'t halt on every uncertainty \u2014 only on these three classes. Style/naming/error-handling choices are non-load-bearing; pick the simplest option that works and continue.\n\n## Lane Orchestration (advanced \u2014 read on demand)\n\nThis world supports parallel work lanes via the container API at http://localhost:8080.\nUse only when the task has 2+ independent scopes that can run simultaneously.\n\n**Full lane API + workflow**: see `~/.olam/docs/lane-orchestration.md` (mounted into the world).\n\n## Creating PRs from inside this world\n\n`gh pr create` hangs on a TTY prompt inside the container. Always use:\n\n```bash\necho y | timeout 30 gh pr create --base main --head <branch> --title "..." --body-file /tmp/pr-body.md\n```\n\n**Why + full troubleshooting**: see `~/.olam/docs/gh-pr-create.md`.\n\n## Available Skills & Plugins\n\nYour Claude Code session includes the invoker\'s plugins, rules, and skills.\nThese are copied from the host at world creation time.\nUse `/codex:review` or `/codex:adversarial-review` for code reviews.\nUse `/codex:rescue` to delegate tasks to Codex.\n{{extraContextBlock}}';
9061
+ }
9062
+ });
9063
+
9059
9064
  // ../core/dist/world/context-injection.js
9060
9065
  import * as fs16 from "node:fs";
9061
9066
  import * as path17 from "node:path";
9062
- import { fileURLToPath as fileURLToPath3 } from "node:url";
9063
9067
  function injectWorldContext(opts) {
9064
- const { world, task, linearTicketId, claudeMdExtra, taskContext, services, pleriPlaneUrl } = opts;
9068
+ const { world } = opts;
9065
9069
  const claudeDir = path17.join(world.workspacePath, ".claude");
9066
9070
  fs16.mkdirSync(claudeDir, { recursive: true });
9067
- const sections = [];
9068
- sections.push(`# Olam World: ${world.name}`);
9069
- sections.push("");
9071
+ const content = WORLD_CLAUDE_MD.replace("{{worldName}}", world.name).replace("{{worldId}}", world.id).replace("{{branch}}", world.branch).replace("{{taskBlock}}", buildTaskBlock(opts)).replace("{{reposList}}", buildReposList(world)).replace("{{servicesLine}}", buildServicesLine(opts.services)).replace("{{pleriPlaneLine}}", buildPleriPlaneLine(opts.pleriPlaneUrl)).replace("{{planFileBlock}}", buildPlanFileBlock(world)).replace("{{extraContextBlock}}", buildExtraContextBlock(opts.claudeMdExtra));
9072
+ fs16.writeFileSync(path17.join(claudeDir, "CLAUDE.md"), content);
9073
+ writeOlamDocs(world.workspacePath);
9074
+ }
9075
+ function buildTaskBlock(opts) {
9076
+ const { task, linearTicketId, taskContext, world } = opts;
9070
9077
  if (taskContext) {
9071
- sections.push("## Task");
9072
- sections.push(`**Source:** ${formatTaskSource(taskContext)}`);
9073
- if (taskContext.title) {
9074
- sections.push(`**Title:** ${taskContext.title}`);
9075
- }
9076
- sections.push(`**Branch:** ${world.branch}`);
9077
- sections.push("");
9078
+ const lines = ["## Task"];
9079
+ lines.push(`**Source:** ${formatTaskSource(taskContext)}`);
9080
+ if (taskContext.title)
9081
+ lines.push(`**Title:** ${taskContext.title}`);
9082
+ lines.push(`**Branch:** ${world.branch}`);
9083
+ lines.push("");
9078
9084
  if (taskContext.description) {
9079
- sections.push("### Description");
9080
- sections.push(taskContext.description);
9081
- sections.push("");
9085
+ lines.push("### Description", taskContext.description, "");
9082
9086
  }
9083
9087
  if (taskContext.acceptanceCriteria) {
9084
- sections.push("### Acceptance Criteria");
9085
- sections.push(taskContext.acceptanceCriteria);
9086
- sections.push("");
9088
+ lines.push("### Acceptance Criteria", taskContext.acceptanceCriteria, "");
9087
9089
  }
9088
9090
  if (taskContext.labels && taskContext.labels.length > 0) {
9089
- sections.push(`### Labels`);
9090
- sections.push(taskContext.labels.join(", "));
9091
- sections.push("");
9091
+ lines.push("### Labels", taskContext.labels.join(", "), "");
9092
9092
  }
9093
9093
  if (taskContext.assignee) {
9094
- sections.push(`### Assignee`);
9095
- sections.push(taskContext.assignee);
9096
- sections.push("");
9097
- }
9098
- } else if (task) {
9099
- sections.push("## Task");
9100
- sections.push(task);
9101
- sections.push("");
9102
- }
9103
- if (linearTicketId && !taskContext) {
9104
- sections.push("## Linear Ticket");
9105
- sections.push(`- ID: ${linearTicketId}`);
9106
- sections.push("- (Fetch full details with Linear MCP tools)");
9107
- sections.push("");
9108
- }
9109
- sections.push("## Environment");
9110
- sections.push(`- World ID: ${world.id}`);
9111
- sections.push(`- Branch: ${world.branch}`);
9112
- if (services && services.length > 0) {
9113
- const svcList = services.map((s) => `${s.name} (${s.port})`).join(", ");
9114
- sections.push(`- Services: ${svcList}`);
9115
- }
9116
- if (pleriPlaneUrl) {
9117
- sections.push(`- Pleri Plane: ${pleriPlaneUrl}`);
9118
- }
9119
- sections.push("");
9120
- sections.push("## Repos in this World");
9121
- for (const repoName of world.repos) {
9122
- sections.push(`- \`${repoName}\` \u2192 \`/home/olam/workspace/${repoName}\``);
9123
- }
9124
- sections.push("");
9125
- if (hasPlanFile(world)) {
9126
- sections.push("## Plan");
9127
- sections.push("A plan file has been provided at `docs/plans/`. Review it before starting work.");
9128
- sections.push("");
9129
- }
9130
- sections.push("## Instructions");
9131
- sections.push("1. Work on the task described above");
9132
- sections.push("2. Write tests first (TDD)");
9133
- sections.push("3. Commit frequently to the world branch");
9134
- sections.push("4. When done, push and the work will be available for review");
9135
- sections.push("5. Your `AskUserQuestion` calls are intercepted and surfaced to the operator's host CP automatically. The operator answers in the SPA; claude resumes when keys are injected.");
9136
- sections.push("");
9137
- sections.push("## Lane Orchestration (advanced \u2014 read on demand)");
9138
- sections.push("");
9139
- sections.push("This world supports parallel work lanes via the container API at http://localhost:8080.");
9140
- sections.push("Use only when the task has 2+ independent scopes that can run simultaneously.");
9141
- sections.push("");
9142
- sections.push("**Full lane API + workflow**: see `~/.olam/docs/lane-orchestration.md` (mounted into the world).");
9143
- sections.push("");
9144
- sections.push("## Creating PRs from inside this world");
9145
- sections.push("");
9146
- sections.push("`gh pr create` hangs on a TTY prompt inside the container. Always use:");
9147
- sections.push("");
9148
- sections.push("```bash");
9149
- sections.push('echo y | timeout 30 gh pr create --base main --head <branch> --title "..." --body-file /tmp/pr-body.md');
9150
- sections.push("```");
9151
- sections.push("");
9152
- sections.push("**Why + full troubleshooting**: see `~/.olam/docs/gh-pr-create.md`.");
9153
- sections.push("");
9154
- sections.push("## Available Skills & Plugins");
9155
- sections.push("");
9156
- sections.push("Your Claude Code session includes the invoker's plugins, rules, and skills.");
9157
- sections.push("These are copied from the host at world creation time.");
9158
- sections.push("Use `/codex:review` or `/codex:adversarial-review` for code reviews.");
9159
- sections.push("Use `/codex:rescue` to delegate tasks to Codex.");
9160
- sections.push("");
9161
- if (claudeMdExtra) {
9162
- sections.push("## Additional Context");
9163
- sections.push(claudeMdExtra);
9164
- sections.push("");
9165
- }
9166
- const content = sections.join("\n");
9167
- fs16.writeFileSync(path17.join(claudeDir, "CLAUDE.md"), content);
9168
- writeOlamDocs(world.workspacePath);
9094
+ lines.push("### Assignee", taskContext.assignee, "");
9095
+ }
9096
+ return lines.join("\n").trimEnd();
9097
+ }
9098
+ if (task) {
9099
+ return `## Task
9100
+ ${task}`;
9101
+ }
9102
+ if (linearTicketId) {
9103
+ return [
9104
+ "## Linear Ticket",
9105
+ `- ID: ${linearTicketId}`,
9106
+ "- (Fetch full details with Linear MCP tools)"
9107
+ ].join("\n");
9108
+ }
9109
+ return "## Task\n_(no task description provided)_";
9110
+ }
9111
+ function buildReposList(world) {
9112
+ return world.repos.map((repoName) => `- \`${repoName}\` \u2192 \`/home/olam/workspace/${repoName}\``).join("\n");
9113
+ }
9114
+ function buildServicesLine(services) {
9115
+ if (!services || services.length === 0)
9116
+ return "";
9117
+ const svcList = services.map((s) => `${s.name} (${s.port})`).join(", ");
9118
+ return `
9119
+ - Services: ${svcList}`;
9120
+ }
9121
+ function buildPleriPlaneLine(pleriPlaneUrl) {
9122
+ if (!pleriPlaneUrl)
9123
+ return "";
9124
+ return `
9125
+ - Pleri Plane: ${pleriPlaneUrl}`;
9126
+ }
9127
+ function buildPlanFileBlock(world) {
9128
+ if (!hasPlanFile(world))
9129
+ return "";
9130
+ return [
9131
+ "## Plan",
9132
+ "A plan file has been provided at `docs/plans/`. Review it before starting work.",
9133
+ "",
9134
+ ""
9135
+ ].join("\n");
9136
+ }
9137
+ function buildExtraContextBlock(extra) {
9138
+ if (!extra)
9139
+ return "";
9140
+ return `
9141
+
9142
+ ## Additional Context
9143
+ ${extra}`;
9169
9144
  }
9170
9145
  function writeOlamDocs(workspacePath) {
9171
9146
  const docsDir = path17.join(workspacePath, ".olam", "docs");
9172
9147
  fs16.mkdirSync(docsDir, { recursive: true });
9173
- for (const filename of ["lane-orchestration.md", "gh-pr-create.md"]) {
9174
- const src = path17.join(TEMPLATES_DIR, filename);
9175
- const dest = path17.join(docsDir, filename);
9176
- fs16.copyFileSync(src, dest);
9177
- }
9148
+ fs16.writeFileSync(path17.join(docsDir, "gh-pr-create.md"), GH_PR_CREATE);
9149
+ fs16.writeFileSync(path17.join(docsDir, "lane-orchestration.md"), LANE_ORCHESTRATION);
9178
9150
  }
9179
9151
  function formatTaskSource(ctx) {
9180
9152
  if (ctx.source === "linear" && ctx.ticketId) {
@@ -9195,11 +9167,10 @@ function hasPlanFile(world) {
9195
9167
  return false;
9196
9168
  }
9197
9169
  }
9198
- var TEMPLATES_DIR;
9199
9170
  var init_context_injection = __esm({
9200
9171
  "../core/dist/world/context-injection.js"() {
9201
9172
  "use strict";
9202
- TEMPLATES_DIR = fileURLToPath3(new URL("./templates", import.meta.url));
9173
+ init_generated();
9203
9174
  }
9204
9175
  });
9205
9176
 
@@ -11306,8 +11277,7 @@ ${stderr.split("\n").slice(0, 3).join(" ")}`);
11306
11277
  if (!olamUserPresent) {
11307
11278
  const imageName = (() => {
11308
11279
  try {
11309
- const { execSync: execSync14 } = __require("node:child_process");
11310
- return execSync14(`docker inspect ${containerName} --format '{{.Config.Image}}'`, {
11280
+ return execSync5(`docker inspect ${containerName} --format '{{.Config.Image}}'`, {
11311
11281
  encoding: "utf8",
11312
11282
  timeout: 5e3
11313
11283
  }).trim() || "(unknown)";
@@ -13687,7 +13657,7 @@ var init_session_aggregator = __esm({
13687
13657
  import * as http from "node:http";
13688
13658
  import * as fs24 from "node:fs";
13689
13659
  import * as path26 from "node:path";
13690
- import { fileURLToPath as fileURLToPath4 } from "node:url";
13660
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
13691
13661
  function jsonResponse(res, data, status2 = 200) {
13692
13662
  res.writeHead(status2, { "Content-Type": "application/json; charset=utf-8" });
13693
13663
  res.end(JSON.stringify(data));
@@ -13868,7 +13838,7 @@ function findSessionInWorld(registry, sessionId) {
13868
13838
  }
13869
13839
  function createDashboardServer(opts) {
13870
13840
  const { port: port2, registry } = opts;
13871
- const thisDir = path26.dirname(fileURLToPath4(import.meta.url));
13841
+ const thisDir = path26.dirname(fileURLToPath3(import.meta.url));
13872
13842
  const defaultPublicDir = path26.resolve(thisDir, "../../../control-plane/public");
13873
13843
  const publicDir = opts.publicDir ?? defaultPublicDir;
13874
13844
  let hasPublicDir = fs24.existsSync(publicDir);
@@ -15223,9 +15193,9 @@ __export(install_root_exports, {
15223
15193
  });
15224
15194
  import { existsSync as existsSync24 } from "node:fs";
15225
15195
  import { dirname as dirname18, join as join30, resolve as resolve10 } from "node:path";
15226
- import { fileURLToPath as fileURLToPath5 } from "node:url";
15196
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
15227
15197
  function installRoot(metaUrl = import.meta.url) {
15228
- const here3 = fileURLToPath5(metaUrl);
15198
+ const here3 = fileURLToPath4(metaUrl);
15229
15199
  return resolve10(dirname18(here3), "..");
15230
15200
  }
15231
15201
  function isDevMode(env = process.env, installRootDir = installRoot()) {
@@ -25101,22 +25071,22 @@ async function runUpgradePullByDigest(deps = {}) {
25101
25071
  return { exitCode: EXIT_GENERIC_ERROR, summary: "socket-proxy recreate failed" };
25102
25072
  }
25103
25073
  proxySpinner.succeed("docker-socket-proxy recreated");
25104
- const composeSpinner = ora7("docker compose recreate host-cp").start();
25074
+ const composeSpinner = ora7("docker compose recreate olam-host-cp").start();
25105
25075
  const composeResult = composeRunner(
25106
- ["up", "-d", "--force-recreate", "--no-deps", "host-cp"],
25076
+ ["up", "-d", "--force-recreate", "--no-deps", "olam-host-cp"],
25107
25077
  composeFile,
25108
25078
  buildComposeEnv(authSecret, captureGhToken())
25109
25079
  );
25110
25080
  if (!composeResult.ok) {
25111
25081
  composeSpinner.fail("compose recreate failed");
25112
25082
  process.stderr.write(
25113
- `${pc18.red("error")} docker compose up --force-recreate host-cp failed:
25083
+ `${pc18.red("error")} docker compose up --force-recreate olam-host-cp failed:
25114
25084
  ${composeResult.stderr.split("\n").slice(0, 3).join("\n ")}
25115
25085
  `
25116
25086
  );
25117
25087
  return { exitCode: EXIT_GENERIC_ERROR, summary: "compose recreate failed" };
25118
25088
  }
25119
- composeSpinner.succeed("host-cp recreated");
25089
+ composeSpinner.succeed("olam-host-cp recreated");
25120
25090
  const authSpinner = ora7("recreate auth-service").start();
25121
25091
  const authRecreate = deps.recreateAuth ?? defaultRecreateAuthForUpgrade;
25122
25092
  const authResult = await authRecreate();
@@ -25230,7 +25200,7 @@ async function handleUpgrade(opts) {
25230
25200
  "bash build-host-cp.sh (host-cp image)",
25231
25201
  "smoke (docker create + inspect)",
25232
25202
  "atomic 6-tag swap (canonical -> :olam-rollback; :olam-next -> canonical)",
25233
- "docker compose --force-recreate host-cp + AuthContainerController.start auth",
25203
+ "docker compose --force-recreate olam-host-cp + AuthContainerController.start auth",
25234
25204
  "poll /api/version/status until SHAs match"
25235
25205
  ]
25236
25206
  ];
@@ -25343,9 +25313,9 @@ manually inspect images with \`docker images olam-*:olam-rollback\`.`
25343
25313
  printInfo("Rollback", swapResult.summary);
25344
25314
  const composeFile = findComposeFile();
25345
25315
  const authSecret = readAuthSecret2();
25346
- process.stdout.write(` ${pc18.dim("docker compose recreate host-cp".padEnd(34))}`);
25316
+ process.stdout.write(` ${pc18.dim("docker compose recreate olam-host-cp".padEnd(34))}`);
25347
25317
  const composeStart = Date.now();
25348
- const composeResult = runCompose(["up", "-d", "--force-recreate", "--no-deps", "host-cp"], composeFile, buildComposeEnv(authSecret, captureGhToken()));
25318
+ const composeResult = runCompose(["up", "-d", "--force-recreate", "--no-deps", "olam-host-cp"], composeFile, buildComposeEnv(authSecret, captureGhToken()));
25349
25319
  const composeDur = `${((Date.now() - composeStart) / 1e3).toFixed(1)}s`;
25350
25320
  process.stdout.write(`${composeResult.ok ? pc18.green("\u2713") : pc18.red("\u2717")} ${composeDur}
25351
25321
  `);
@@ -25353,7 +25323,7 @@ manually inspect images with \`docker images olam-*:olam-rollback\`.`
25353
25323
  printError(
25354
25324
  `Rollback compose recreate failed:
25355
25325
  ${composeResult.stderr}
25356
- Canonical tags are at :olam-rollback (good); container restart pending. Manually: \`docker compose -f packages/host-cp/compose.yaml up -d --force-recreate host-cp\`.`
25326
+ Canonical tags are at :olam-rollback (good); container restart pending. Manually: \`docker compose -f packages/host-cp/compose.yaml up -d --force-recreate olam-host-cp\`.`
25357
25327
  );
25358
25328
  process.exitCode = 1;
25359
25329
  return;
@@ -27328,7 +27298,7 @@ import path46 from "node:path";
27328
27298
  import { createInterface as createInterface3 } from "node:readline";
27329
27299
 
27330
27300
  // src/lib/shell-rc.ts
27331
- import { copyFileSync as copyFileSync6, existsSync as existsSync46, readFileSync as readFileSync33, renameSync as renameSync5, writeFileSync as writeFileSync21 } from "node:fs";
27301
+ import { copyFileSync as copyFileSync5, existsSync as existsSync46, readFileSync as readFileSync33, renameSync as renameSync5, writeFileSync as writeFileSync21 } from "node:fs";
27332
27302
  import path45 from "node:path";
27333
27303
  function appendIdempotent(opts) {
27334
27304
  const { rcPath, marker, contentLine, clock = () => /* @__PURE__ */ new Date() } = opts;
@@ -27341,7 +27311,7 @@ function appendIdempotent(opts) {
27341
27311
  }
27342
27312
  const timestamp = clock().toISOString().replace(/[:.]/g, "-");
27343
27313
  const backupPath = `${rcPath}.olam-bak.${timestamp}`;
27344
- copyFileSync6(rcPath, backupPath);
27314
+ copyFileSync5(rcPath, backupPath);
27345
27315
  const separator = content.length === 0 || content.endsWith("\n") ? "" : "\n";
27346
27316
  const trailing = contentLine.endsWith("\n") ? "" : "\n";
27347
27317
  const nextContent = `${content}${separator}${contentLine}${trailing}`;
@@ -28361,8 +28331,8 @@ function registerWorldUpgrade(program2) {
28361
28331
  init_output();
28362
28332
  import { existsSync as existsSync52 } from "node:fs";
28363
28333
  import { dirname as dirname27, resolve as resolve13 } from "node:path";
28364
- import { fileURLToPath as fileURLToPath6 } from "node:url";
28365
- var here = dirname27(fileURLToPath6(import.meta.url));
28334
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
28335
+ var here = dirname27(fileURLToPath5(import.meta.url));
28366
28336
  var BUNDLE_PATH_CANDIDATES = [
28367
28337
  // bundled (`dist/index.js` after bundle-cli.mjs) — sibling
28368
28338
  resolve13(here, "mcp-server.js"),
@@ -29116,7 +29086,7 @@ import { pathToFileURL } from "node:url";
29116
29086
  // src/commands/memory/_paths.ts
29117
29087
  import { homedir as homedir30 } from "node:os";
29118
29088
  import { join as join51, dirname as dirname28 } from "node:path";
29119
- import { fileURLToPath as fileURLToPath7 } from "node:url";
29089
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
29120
29090
  var OLAM_HOME = join51(homedir30(), ".olam");
29121
29091
  var OLAM_BIN_DIR = join51(OLAM_HOME, "bin");
29122
29092
  var III_BINARY_PATH = join51(OLAM_BIN_DIR, "iii");
@@ -29125,14 +29095,17 @@ var MEMORY_LOG_PATH = join51(OLAM_HOME, "memory-service.log");
29125
29095
  var MEMORY_DATA_DIR = join51(OLAM_HOME, "memory-data");
29126
29096
  var MEMORY_REST_PORT = 3111;
29127
29097
  var MEMORY_LIVEZ_URL = `http://localhost:${MEMORY_REST_PORT}/agentmemory/livez`;
29128
- var here2 = dirname28(fileURLToPath7(import.meta.url));
29098
+ var here2 = dirname28(fileURLToPath6(import.meta.url));
29129
29099
  var candidates = [
29130
- // Workspace dev: packages/cli/src/commands/memory/_paths.ts (run via tsx) — unlikely
29131
- // Workspace built: packages/cli/dist/commands/memory/_paths.js → packages/cli → packages/memory-service
29100
+ // 1. Workspace dev (built): packages/cli/dist/commands/memory/_paths.js packages/cli packages/memory-service
29132
29101
  join51(here2, "..", "..", "..", "..", "memory-service"),
29133
- // Bundled: dist/index.js (esbuild) → packages/cli → packages/memory-service
29102
+ // 2a. Workspace bundled: packages/cli/dist/index.js → packages/cli → packages/memory-service
29134
29103
  join51(here2, "..", "..", "memory-service"),
29135
- // Fallback: cwd-relative
29104
+ // 2b. Published tarball: <prefix>/node_modules/@pleri/olam-cli/dist/index.js
29105
+ // → <prefix>/node_modules/@pleri/olam-cli/memory-service-bundle
29106
+ // (copied at publish time by bundle-cli.mjs)
29107
+ join51(here2, "..", "memory-service-bundle"),
29108
+ // 3. CWD fallback
29136
29109
  join51(process.cwd(), "packages", "memory-service")
29137
29110
  ];
29138
29111
  var MEMORY_SERVICE_CANDIDATES = candidates;
@@ -29844,6 +29817,9 @@ function kgServiceStatusUrl() {
29844
29817
  function kgServiceBuildUrl() {
29845
29818
  return url("/build");
29846
29819
  }
29820
+ function kgServiceSavingsUrl() {
29821
+ return url("/savings");
29822
+ }
29847
29823
  var KgServiceUnreachableError = class extends Error {
29848
29824
  cause;
29849
29825
  constructor(message, cause) {
@@ -29895,6 +29871,9 @@ async function status(opts = {}) {
29895
29871
  async function build(req, opts = {}) {
29896
29872
  return postJson(kgServiceBuildUrl(), req, opts.timeoutMs ?? 9e5);
29897
29873
  }
29874
+ async function savings(opts = {}) {
29875
+ return getJson(kgServiceSavingsUrl(), opts.timeoutMs);
29876
+ }
29898
29877
 
29899
29878
  // src/commands/kg-build.ts
29900
29879
  init_output();
@@ -30567,19 +30546,26 @@ function defaultUrl(flavor) {
30567
30546
  function buildHookCommand(opts) {
30568
30547
  const url2 = opts.url ?? defaultUrl(opts.flavor);
30569
30548
  const extractCmd = `python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('tool_input',d).get('command',''))" 2>/dev/null`;
30570
- const emitContext = `python3 -c 'import json,sys
30549
+ const emitContext = `python3 -c 'import json,sys,os
30571
30550
  try:
30572
30551
  d=json.loads(sys.stdin.read())
30573
30552
  if d.get("route") and d["route"] != "grep":
30574
30553
  label=d.get("top_match") or d.get("reason","")
30575
- print(json.dumps({"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":f"[kg-classifier L{d.get(\\"layer\\",\\"?\\")}|{d[\\"route\\"]}] {label[:160]}"}}))
30554
+ layer=d.get("layer","?")
30555
+ route=d["route"]
30556
+ nodes=d.get("nodes_matched",0)
30557
+ saved=(d.get("savings") or {}).get("saved_tokens_est",0)
30558
+ saved_k=round(saved/1000)
30559
+ q=os.environ.get("KG_QUERY","")[:60]
30560
+ sys.stderr.write(f"\\x1b[32m\\u2713 KG hit\\x1b[0m \\"{q}\\" \\u2192 L{layer}/{route} \\u00b7 {nodes} nodes \\u00b7 ~{saved_k}k tokens saved\\n")
30561
+ print(json.dumps({"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":f"[kg-classifier L{layer}|{route}] {label[:160]}"}}))
30576
30562
  except Exception: pass' 2>/dev/null`;
30577
30563
  return [
30578
30564
  `KG_SENTINEL=${KG_HOOK_SENTINEL}`,
30579
30565
  `CMD=$(${extractCmd})`,
30580
30566
  `case "$CMD" in *grep*|*rg\\ *|*ripgrep*|*find\\ *|*fd\\ *|*ack\\ *|*ag\\ *)`,
30581
30567
  `RESP=$(curl -s --max-time 1 -X POST -H 'Content-Type: application/json' -d "{\\"q\\":\\"$(echo \\"$CMD\\" | head -c 200 | tr '\\"' ' ')\\"}" ${url2} 2>/dev/null)`,
30582
- `echo "$RESP" | ${emitContext}`,
30568
+ `KG_QUERY="$(echo \\"$CMD\\" | head -c 60 | tr '\\"' ' ')" echo "$RESP" | ${emitContext}`,
30583
30569
  `;; esac`
30584
30570
  ].join("; ");
30585
30571
  }
@@ -30752,6 +30738,67 @@ function registerKgUninstallHookCommand(kg) {
30752
30738
  });
30753
30739
  }
30754
30740
 
30741
+ // src/commands/kg-savings.ts
30742
+ init_output();
30743
+ function formatTokens(n) {
30744
+ if (n >= 1e6) return `~${(n / 1e6).toFixed(1)}M`;
30745
+ if (n >= 1e3) return `~${Math.round(n / 1e3)}k`;
30746
+ return `~${n}`;
30747
+ }
30748
+ function renderHumanReadable(stats) {
30749
+ if (stats.total_hits === 0) {
30750
+ printInfo("savings", "no KG hits logged yet \u2014 run some grep/find Bash invocations to populate /kg-data/savings.jsonl");
30751
+ return;
30752
+ }
30753
+ printSuccess(`KG hits: ${stats.total_hits} \xB7 estimated savings: ${formatTokens(stats.total_saved_tokens_est)} tokens`);
30754
+ printInfo("first hit", stats.first_hit_at ?? "(unknown)");
30755
+ printInfo("last hit", stats.last_hit_at ?? "(unknown)");
30756
+ const layerPairs = Object.entries(stats.by_layer).map(
30757
+ ([k, v]) => [k, v ?? 0]
30758
+ );
30759
+ const layers = [...layerPairs].sort((a, b) => a[0].localeCompare(b[0]));
30760
+ if (layers.length > 0) {
30761
+ printInfo("by layer", layers.map(([k, v]) => `L${k}=${formatTokens(v)}`).join(" \xB7 "));
30762
+ }
30763
+ const workspacePairs = Object.entries(
30764
+ stats.by_workspace
30765
+ ).map(([k, v]) => [k, v ?? 0]);
30766
+ const workspaces = [...workspacePairs].sort((a, b) => b[1] - a[1]).slice(0, 5);
30767
+ if (workspaces.length > 0) {
30768
+ printInfo(
30769
+ "top workspaces",
30770
+ workspaces.map(([k, v]) => `${k || "(none)"}=${formatTokens(v)}`).join(" \xB7 ")
30771
+ );
30772
+ }
30773
+ if (stats.parse_errors > 0) {
30774
+ printWarning(`savings.jsonl had ${stats.parse_errors} unparseable lines (skipped)`);
30775
+ }
30776
+ printInfo("note", "savings are a heuristic, not a measurement. See SAVINGS_HEURISTIC in server.py for the per-layer model.");
30777
+ }
30778
+ function registerKgSavingsCommand(kg) {
30779
+ kg.command("savings").description("Show cumulative KG-hit savings tallied by the kg-service container").option("--json", "emit raw response JSON instead of human-readable summary").action(async (opts) => {
30780
+ let stats;
30781
+ try {
30782
+ stats = await savings();
30783
+ } catch (err) {
30784
+ if (err instanceof KgServiceUnreachableError) {
30785
+ printError(err.message);
30786
+ printInfo("remedy", "`olam services up` to start kg-service, then re-run");
30787
+ process.exitCode = 3;
30788
+ return;
30789
+ }
30790
+ printError(`kg-service /savings failed: ${err instanceof Error ? err.message : String(err)}`);
30791
+ process.exitCode = 1;
30792
+ return;
30793
+ }
30794
+ if (opts.json) {
30795
+ process.stdout.write(JSON.stringify(stats, null, 2) + "\n");
30796
+ return;
30797
+ }
30798
+ renderHumanReadable(stats);
30799
+ });
30800
+ }
30801
+
30755
30802
  // src/commands/kg-build.ts
30756
30803
  function resolveWorkspace(arg) {
30757
30804
  const cwd = process.cwd();
@@ -30849,6 +30896,7 @@ function registerKg(program2) {
30849
30896
  registerKgDoctorCommand(kg);
30850
30897
  registerKgInstallHookCommand(kg);
30851
30898
  registerKgUninstallHookCommand(kg);
30899
+ registerKgSavingsCommand(kg);
30852
30900
  }
30853
30901
 
30854
30902
  // src/commands/seed.ts