@pleri/olam-cli 0.1.142 → 0.1.144
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/commands/kg-build.d.ts.map +1 -1
- package/dist/commands/kg-build.js +3 -0
- package/dist/commands/kg-build.js.map +1 -1
- package/dist/commands/kg-savings.d.ts +20 -0
- package/dist/commands/kg-savings.d.ts.map +1 -0
- package/dist/commands/kg-savings.js +77 -0
- package/dist/commands/kg-savings.js.map +1 -0
- package/dist/commands/memory/_paths.d.ts.map +1 -1
- package/dist/commands/memory/_paths.js +27 -7
- package/dist/commands/memory/_paths.js.map +1 -1
- package/dist/commands/upgrade.d.ts +24 -0
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +81 -8
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/image-digests.json +7 -7
- package/dist/index.js +245 -140
- package/dist/mcp-server.js +93 -120
- package/host-cp/compose.yaml +1 -1
- package/host-cp/src/agent-runtime-trigger.mjs +192 -0
- package/host-cp/src/server.mjs +73 -0
- package/memory-service-bundle/scripts/ensure-iii-engine.mjs +179 -0
- package/package.json +4 -2
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
|
|
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
|
-
|
|
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
|
|
9068
|
+
const { world } = opts;
|
|
9065
9069
|
const claudeDir = path17.join(world.workspacePath, ".claude");
|
|
9066
9070
|
fs16.mkdirSync(claudeDir, { recursive: true });
|
|
9067
|
-
const
|
|
9068
|
-
|
|
9069
|
-
|
|
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
|
-
|
|
9072
|
-
|
|
9073
|
-
if (taskContext.title)
|
|
9074
|
-
|
|
9075
|
-
}
|
|
9076
|
-
|
|
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
|
-
|
|
9080
|
-
sections.push(taskContext.description);
|
|
9081
|
-
sections.push("");
|
|
9085
|
+
lines.push("### Description", taskContext.description, "");
|
|
9082
9086
|
}
|
|
9083
9087
|
if (taskContext.acceptanceCriteria) {
|
|
9084
|
-
|
|
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
|
-
|
|
9090
|
-
sections.push(taskContext.labels.join(", "));
|
|
9091
|
-
sections.push("");
|
|
9091
|
+
lines.push("### Labels", taskContext.labels.join(", "), "");
|
|
9092
9092
|
}
|
|
9093
9093
|
if (taskContext.assignee) {
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
}
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
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
|
-
|
|
9174
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
15196
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
15227
15197
|
function installRoot(metaUrl = import.meta.url) {
|
|
15228
|
-
const here3 =
|
|
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,48 @@ 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
|
|
25074
|
+
const inspectContainer = deps.inspectContainerImpl ?? defaultInspectContainerLabels;
|
|
25075
|
+
const removeContainer = deps.removeContainerImpl ?? defaultRemoveContainer;
|
|
25076
|
+
const orphanCheck = inspectContainer("olam-host-cp");
|
|
25077
|
+
if (!orphanCheck.ok) {
|
|
25078
|
+
process.stderr.write(
|
|
25079
|
+
`${pc18.red("error")} docker inspect olam-host-cp failed:
|
|
25080
|
+
${orphanCheck.stderr.split("\n").slice(0, 3).join("\n ")}
|
|
25081
|
+
`
|
|
25082
|
+
);
|
|
25083
|
+
return { exitCode: EXIT_GENERIC_ERROR, summary: "orphan inspect failed" };
|
|
25084
|
+
}
|
|
25085
|
+
if (orphanCheck.exists && orphanCheck.service !== "olam-host-cp") {
|
|
25086
|
+
process.stderr.write(
|
|
25087
|
+
`${pc18.yellow("info")} Removing pre-rename orphan container olam-host-cp (old project=${orphanCheck.project || "<none>"}, old service=${orphanCheck.service || "<none>"})
|
|
25088
|
+
`
|
|
25089
|
+
);
|
|
25090
|
+
const rm = removeContainer("olam-host-cp");
|
|
25091
|
+
if (!rm.ok) {
|
|
25092
|
+
process.stderr.write(
|
|
25093
|
+
`${pc18.red("error")} docker rm -f olam-host-cp failed:
|
|
25094
|
+
${rm.stderr.split("\n").slice(0, 3).join("\n ")}
|
|
25095
|
+
`
|
|
25096
|
+
);
|
|
25097
|
+
return { exitCode: EXIT_GENERIC_ERROR, summary: "orphan cleanup failed" };
|
|
25098
|
+
}
|
|
25099
|
+
}
|
|
25100
|
+
const composeSpinner = ora7("docker compose recreate olam-host-cp").start();
|
|
25105
25101
|
const composeResult = composeRunner(
|
|
25106
|
-
["up", "-d", "--force-recreate", "--no-deps", "host-cp"],
|
|
25102
|
+
["up", "-d", "--force-recreate", "--no-deps", "olam-host-cp"],
|
|
25107
25103
|
composeFile,
|
|
25108
25104
|
buildComposeEnv(authSecret, captureGhToken())
|
|
25109
25105
|
);
|
|
25110
25106
|
if (!composeResult.ok) {
|
|
25111
25107
|
composeSpinner.fail("compose recreate failed");
|
|
25112
25108
|
process.stderr.write(
|
|
25113
|
-
`${pc18.red("error")} docker compose up --force-recreate host-cp failed:
|
|
25109
|
+
`${pc18.red("error")} docker compose up --force-recreate olam-host-cp failed:
|
|
25114
25110
|
${composeResult.stderr.split("\n").slice(0, 3).join("\n ")}
|
|
25115
25111
|
`
|
|
25116
25112
|
);
|
|
25117
25113
|
return { exitCode: EXIT_GENERIC_ERROR, summary: "compose recreate failed" };
|
|
25118
25114
|
}
|
|
25119
|
-
composeSpinner.succeed("host-cp recreated");
|
|
25115
|
+
composeSpinner.succeed("olam-host-cp recreated");
|
|
25120
25116
|
const authSpinner = ora7("recreate auth-service").start();
|
|
25121
25117
|
const authRecreate = deps.recreateAuth ?? defaultRecreateAuthForUpgrade;
|
|
25122
25118
|
const authResult = await authRecreate();
|
|
@@ -25205,6 +25201,37 @@ async function defaultRecreateAuthForUpgrade() {
|
|
|
25205
25201
|
};
|
|
25206
25202
|
}
|
|
25207
25203
|
}
|
|
25204
|
+
function defaultInspectContainerLabels(containerName) {
|
|
25205
|
+
const result = spawnSync15(
|
|
25206
|
+
"docker",
|
|
25207
|
+
[
|
|
25208
|
+
"inspect",
|
|
25209
|
+
"--format",
|
|
25210
|
+
'{{index .Config.Labels "com.docker.compose.project"}}|{{index .Config.Labels "com.docker.compose.service"}}',
|
|
25211
|
+
containerName
|
|
25212
|
+
],
|
|
25213
|
+
{ encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }
|
|
25214
|
+
);
|
|
25215
|
+
const stderr = result.stderr ?? "";
|
|
25216
|
+
if (result.status === 0) {
|
|
25217
|
+
const [project = "", service = ""] = (result.stdout ?? "").trim().split("|");
|
|
25218
|
+
return { ok: true, exists: true, project, service, stderr: "" };
|
|
25219
|
+
}
|
|
25220
|
+
if (/No such (object|container)/i.test(stderr)) {
|
|
25221
|
+
return { ok: true, exists: false, project: "", service: "", stderr: "" };
|
|
25222
|
+
}
|
|
25223
|
+
if (result.status === null) {
|
|
25224
|
+
return { ok: true, exists: false, project: "", service: "", stderr: "" };
|
|
25225
|
+
}
|
|
25226
|
+
return { ok: false, exists: false, project: "", service: "", stderr };
|
|
25227
|
+
}
|
|
25228
|
+
function defaultRemoveContainer(containerName) {
|
|
25229
|
+
const result = spawnSync15("docker", ["rm", "-f", containerName], {
|
|
25230
|
+
encoding: "utf-8",
|
|
25231
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
25232
|
+
});
|
|
25233
|
+
return { ok: result.status === 0, stderr: result.stderr ?? "" };
|
|
25234
|
+
}
|
|
25208
25235
|
async function handleUpgrade(opts) {
|
|
25209
25236
|
const cwd = process.cwd();
|
|
25210
25237
|
const rootCheck = validateRepoRoot(cwd);
|
|
@@ -25230,7 +25257,7 @@ async function handleUpgrade(opts) {
|
|
|
25230
25257
|
"bash build-host-cp.sh (host-cp image)",
|
|
25231
25258
|
"smoke (docker create + inspect)",
|
|
25232
25259
|
"atomic 6-tag swap (canonical -> :olam-rollback; :olam-next -> canonical)",
|
|
25233
|
-
"docker compose --force-recreate host-cp + AuthContainerController.start auth",
|
|
25260
|
+
"docker compose --force-recreate olam-host-cp + AuthContainerController.start auth",
|
|
25234
25261
|
"poll /api/version/status until SHAs match"
|
|
25235
25262
|
]
|
|
25236
25263
|
];
|
|
@@ -25343,9 +25370,9 @@ manually inspect images with \`docker images olam-*:olam-rollback\`.`
|
|
|
25343
25370
|
printInfo("Rollback", swapResult.summary);
|
|
25344
25371
|
const composeFile = findComposeFile();
|
|
25345
25372
|
const authSecret = readAuthSecret2();
|
|
25346
|
-
process.stdout.write(` ${pc18.dim("docker compose recreate host-cp".padEnd(34))}`);
|
|
25373
|
+
process.stdout.write(` ${pc18.dim("docker compose recreate olam-host-cp".padEnd(34))}`);
|
|
25347
25374
|
const composeStart = Date.now();
|
|
25348
|
-
const composeResult = runCompose(["up", "-d", "--force-recreate", "--no-deps", "host-cp"], composeFile, buildComposeEnv(authSecret, captureGhToken()));
|
|
25375
|
+
const composeResult = runCompose(["up", "-d", "--force-recreate", "--no-deps", "olam-host-cp"], composeFile, buildComposeEnv(authSecret, captureGhToken()));
|
|
25349
25376
|
const composeDur = `${((Date.now() - composeStart) / 1e3).toFixed(1)}s`;
|
|
25350
25377
|
process.stdout.write(`${composeResult.ok ? pc18.green("\u2713") : pc18.red("\u2717")} ${composeDur}
|
|
25351
25378
|
`);
|
|
@@ -25353,7 +25380,7 @@ manually inspect images with \`docker images olam-*:olam-rollback\`.`
|
|
|
25353
25380
|
printError(
|
|
25354
25381
|
`Rollback compose recreate failed:
|
|
25355
25382
|
${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\`.`
|
|
25383
|
+
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
25384
|
);
|
|
25358
25385
|
process.exitCode = 1;
|
|
25359
25386
|
return;
|
|
@@ -27328,7 +27355,7 @@ import path46 from "node:path";
|
|
|
27328
27355
|
import { createInterface as createInterface3 } from "node:readline";
|
|
27329
27356
|
|
|
27330
27357
|
// src/lib/shell-rc.ts
|
|
27331
|
-
import { copyFileSync as
|
|
27358
|
+
import { copyFileSync as copyFileSync5, existsSync as existsSync46, readFileSync as readFileSync33, renameSync as renameSync5, writeFileSync as writeFileSync21 } from "node:fs";
|
|
27332
27359
|
import path45 from "node:path";
|
|
27333
27360
|
function appendIdempotent(opts) {
|
|
27334
27361
|
const { rcPath, marker, contentLine, clock = () => /* @__PURE__ */ new Date() } = opts;
|
|
@@ -27341,7 +27368,7 @@ function appendIdempotent(opts) {
|
|
|
27341
27368
|
}
|
|
27342
27369
|
const timestamp = clock().toISOString().replace(/[:.]/g, "-");
|
|
27343
27370
|
const backupPath = `${rcPath}.olam-bak.${timestamp}`;
|
|
27344
|
-
|
|
27371
|
+
copyFileSync5(rcPath, backupPath);
|
|
27345
27372
|
const separator = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
27346
27373
|
const trailing = contentLine.endsWith("\n") ? "" : "\n";
|
|
27347
27374
|
const nextContent = `${content}${separator}${contentLine}${trailing}`;
|
|
@@ -28361,8 +28388,8 @@ function registerWorldUpgrade(program2) {
|
|
|
28361
28388
|
init_output();
|
|
28362
28389
|
import { existsSync as existsSync52 } from "node:fs";
|
|
28363
28390
|
import { dirname as dirname27, resolve as resolve13 } from "node:path";
|
|
28364
|
-
import { fileURLToPath as
|
|
28365
|
-
var here = dirname27(
|
|
28391
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
28392
|
+
var here = dirname27(fileURLToPath5(import.meta.url));
|
|
28366
28393
|
var BUNDLE_PATH_CANDIDATES = [
|
|
28367
28394
|
// bundled (`dist/index.js` after bundle-cli.mjs) — sibling
|
|
28368
28395
|
resolve13(here, "mcp-server.js"),
|
|
@@ -29116,7 +29143,7 @@ import { pathToFileURL } from "node:url";
|
|
|
29116
29143
|
// src/commands/memory/_paths.ts
|
|
29117
29144
|
import { homedir as homedir30 } from "node:os";
|
|
29118
29145
|
import { join as join51, dirname as dirname28 } from "node:path";
|
|
29119
|
-
import { fileURLToPath as
|
|
29146
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
29120
29147
|
var OLAM_HOME = join51(homedir30(), ".olam");
|
|
29121
29148
|
var OLAM_BIN_DIR = join51(OLAM_HOME, "bin");
|
|
29122
29149
|
var III_BINARY_PATH = join51(OLAM_BIN_DIR, "iii");
|
|
@@ -29125,14 +29152,17 @@ var MEMORY_LOG_PATH = join51(OLAM_HOME, "memory-service.log");
|
|
|
29125
29152
|
var MEMORY_DATA_DIR = join51(OLAM_HOME, "memory-data");
|
|
29126
29153
|
var MEMORY_REST_PORT = 3111;
|
|
29127
29154
|
var MEMORY_LIVEZ_URL = `http://localhost:${MEMORY_REST_PORT}/agentmemory/livez`;
|
|
29128
|
-
var here2 = dirname28(
|
|
29155
|
+
var here2 = dirname28(fileURLToPath6(import.meta.url));
|
|
29129
29156
|
var candidates = [
|
|
29130
|
-
// Workspace dev: packages/cli/
|
|
29131
|
-
// Workspace built: packages/cli/dist/commands/memory/_paths.js → packages/cli → packages/memory-service
|
|
29157
|
+
// 1. Workspace dev (built): packages/cli/dist/commands/memory/_paths.js → packages/cli → packages/memory-service
|
|
29132
29158
|
join51(here2, "..", "..", "..", "..", "memory-service"),
|
|
29133
|
-
//
|
|
29159
|
+
// 2a. Workspace bundled: packages/cli/dist/index.js → packages/cli → packages/memory-service
|
|
29134
29160
|
join51(here2, "..", "..", "memory-service"),
|
|
29135
|
-
//
|
|
29161
|
+
// 2b. Published tarball: <prefix>/node_modules/@pleri/olam-cli/dist/index.js
|
|
29162
|
+
// → <prefix>/node_modules/@pleri/olam-cli/memory-service-bundle
|
|
29163
|
+
// (copied at publish time by bundle-cli.mjs)
|
|
29164
|
+
join51(here2, "..", "memory-service-bundle"),
|
|
29165
|
+
// 3. CWD fallback
|
|
29136
29166
|
join51(process.cwd(), "packages", "memory-service")
|
|
29137
29167
|
];
|
|
29138
29168
|
var MEMORY_SERVICE_CANDIDATES = candidates;
|
|
@@ -29844,6 +29874,9 @@ function kgServiceStatusUrl() {
|
|
|
29844
29874
|
function kgServiceBuildUrl() {
|
|
29845
29875
|
return url("/build");
|
|
29846
29876
|
}
|
|
29877
|
+
function kgServiceSavingsUrl() {
|
|
29878
|
+
return url("/savings");
|
|
29879
|
+
}
|
|
29847
29880
|
var KgServiceUnreachableError = class extends Error {
|
|
29848
29881
|
cause;
|
|
29849
29882
|
constructor(message, cause) {
|
|
@@ -29895,6 +29928,9 @@ async function status(opts = {}) {
|
|
|
29895
29928
|
async function build(req, opts = {}) {
|
|
29896
29929
|
return postJson(kgServiceBuildUrl(), req, opts.timeoutMs ?? 9e5);
|
|
29897
29930
|
}
|
|
29931
|
+
async function savings(opts = {}) {
|
|
29932
|
+
return getJson(kgServiceSavingsUrl(), opts.timeoutMs);
|
|
29933
|
+
}
|
|
29898
29934
|
|
|
29899
29935
|
// src/commands/kg-build.ts
|
|
29900
29936
|
init_output();
|
|
@@ -30567,19 +30603,26 @@ function defaultUrl(flavor) {
|
|
|
30567
30603
|
function buildHookCommand(opts) {
|
|
30568
30604
|
const url2 = opts.url ?? defaultUrl(opts.flavor);
|
|
30569
30605
|
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
|
|
30606
|
+
const emitContext = `python3 -c 'import json,sys,os
|
|
30571
30607
|
try:
|
|
30572
30608
|
d=json.loads(sys.stdin.read())
|
|
30573
30609
|
if d.get("route") and d["route"] != "grep":
|
|
30574
30610
|
label=d.get("top_match") or d.get("reason","")
|
|
30575
|
-
|
|
30611
|
+
layer=d.get("layer","?")
|
|
30612
|
+
route=d["route"]
|
|
30613
|
+
nodes=d.get("nodes_matched",0)
|
|
30614
|
+
saved=(d.get("savings") or {}).get("saved_tokens_est",0)
|
|
30615
|
+
saved_k=round(saved/1000)
|
|
30616
|
+
q=os.environ.get("KG_QUERY","")[:60]
|
|
30617
|
+
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")
|
|
30618
|
+
print(json.dumps({"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":f"[kg-classifier L{layer}|{route}] {label[:160]}"}}))
|
|
30576
30619
|
except Exception: pass' 2>/dev/null`;
|
|
30620
|
+
const curlPost = `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)`;
|
|
30577
30621
|
return [
|
|
30578
30622
|
`KG_SENTINEL=${KG_HOOK_SENTINEL}`,
|
|
30579
30623
|
`CMD=$(${extractCmd})`,
|
|
30580
|
-
`case "$CMD" in *grep*|*rg\\ *|*ripgrep*|*find\\ *|*fd\\ *|*ack\\ *|*ag\\ *)`,
|
|
30581
|
-
`
|
|
30582
|
-
`echo "$RESP" | ${emitContext}`,
|
|
30624
|
+
`case "$CMD" in *grep*|*rg\\ *|*ripgrep*|*find\\ *|*fd\\ *|*ack\\ *|*ag\\ *) ${curlPost}`,
|
|
30625
|
+
`KG_QUERY="$(echo \\"$CMD\\" | head -c 60 | tr '\\"' ' ')" echo "$RESP" | ${emitContext}`,
|
|
30583
30626
|
`;; esac`
|
|
30584
30627
|
].join("; ");
|
|
30585
30628
|
}
|
|
@@ -30752,6 +30795,67 @@ function registerKgUninstallHookCommand(kg) {
|
|
|
30752
30795
|
});
|
|
30753
30796
|
}
|
|
30754
30797
|
|
|
30798
|
+
// src/commands/kg-savings.ts
|
|
30799
|
+
init_output();
|
|
30800
|
+
function formatTokens(n) {
|
|
30801
|
+
if (n >= 1e6) return `~${(n / 1e6).toFixed(1)}M`;
|
|
30802
|
+
if (n >= 1e3) return `~${Math.round(n / 1e3)}k`;
|
|
30803
|
+
return `~${n}`;
|
|
30804
|
+
}
|
|
30805
|
+
function renderHumanReadable(stats) {
|
|
30806
|
+
if (stats.total_hits === 0) {
|
|
30807
|
+
printInfo("savings", "no KG hits logged yet \u2014 run some grep/find Bash invocations to populate /kg-data/savings.jsonl");
|
|
30808
|
+
return;
|
|
30809
|
+
}
|
|
30810
|
+
printSuccess(`KG hits: ${stats.total_hits} \xB7 estimated savings: ${formatTokens(stats.total_saved_tokens_est)} tokens`);
|
|
30811
|
+
printInfo("first hit", stats.first_hit_at ?? "(unknown)");
|
|
30812
|
+
printInfo("last hit", stats.last_hit_at ?? "(unknown)");
|
|
30813
|
+
const layerPairs = Object.entries(stats.by_layer).map(
|
|
30814
|
+
([k, v]) => [k, v ?? 0]
|
|
30815
|
+
);
|
|
30816
|
+
const layers = [...layerPairs].sort((a, b) => a[0].localeCompare(b[0]));
|
|
30817
|
+
if (layers.length > 0) {
|
|
30818
|
+
printInfo("by layer", layers.map(([k, v]) => `L${k}=${formatTokens(v)}`).join(" \xB7 "));
|
|
30819
|
+
}
|
|
30820
|
+
const workspacePairs = Object.entries(
|
|
30821
|
+
stats.by_workspace
|
|
30822
|
+
).map(([k, v]) => [k, v ?? 0]);
|
|
30823
|
+
const workspaces = [...workspacePairs].sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
30824
|
+
if (workspaces.length > 0) {
|
|
30825
|
+
printInfo(
|
|
30826
|
+
"top workspaces",
|
|
30827
|
+
workspaces.map(([k, v]) => `${k || "(none)"}=${formatTokens(v)}`).join(" \xB7 ")
|
|
30828
|
+
);
|
|
30829
|
+
}
|
|
30830
|
+
if (stats.parse_errors > 0) {
|
|
30831
|
+
printWarning(`savings.jsonl had ${stats.parse_errors} unparseable lines (skipped)`);
|
|
30832
|
+
}
|
|
30833
|
+
printInfo("note", "savings are a heuristic, not a measurement. See SAVINGS_HEURISTIC in server.py for the per-layer model.");
|
|
30834
|
+
}
|
|
30835
|
+
function registerKgSavingsCommand(kg) {
|
|
30836
|
+
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) => {
|
|
30837
|
+
let stats;
|
|
30838
|
+
try {
|
|
30839
|
+
stats = await savings();
|
|
30840
|
+
} catch (err) {
|
|
30841
|
+
if (err instanceof KgServiceUnreachableError) {
|
|
30842
|
+
printError(err.message);
|
|
30843
|
+
printInfo("remedy", "`olam services up` to start kg-service, then re-run");
|
|
30844
|
+
process.exitCode = 3;
|
|
30845
|
+
return;
|
|
30846
|
+
}
|
|
30847
|
+
printError(`kg-service /savings failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
30848
|
+
process.exitCode = 1;
|
|
30849
|
+
return;
|
|
30850
|
+
}
|
|
30851
|
+
if (opts.json) {
|
|
30852
|
+
process.stdout.write(JSON.stringify(stats, null, 2) + "\n");
|
|
30853
|
+
return;
|
|
30854
|
+
}
|
|
30855
|
+
renderHumanReadable(stats);
|
|
30856
|
+
});
|
|
30857
|
+
}
|
|
30858
|
+
|
|
30755
30859
|
// src/commands/kg-build.ts
|
|
30756
30860
|
function resolveWorkspace(arg) {
|
|
30757
30861
|
const cwd = process.cwd();
|
|
@@ -30849,6 +30953,7 @@ function registerKg(program2) {
|
|
|
30849
30953
|
registerKgDoctorCommand(kg);
|
|
30850
30954
|
registerKgInstallHookCommand(kg);
|
|
30851
30955
|
registerKgUninstallHookCommand(kg);
|
|
30956
|
+
registerKgSavingsCommand(kg);
|
|
30852
30957
|
}
|
|
30853
30958
|
|
|
30854
30959
|
// src/commands/seed.ts
|