@pleri/olam-cli 0.1.150 → 0.1.152
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/doctor.d.ts +29 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +39 -11
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/setup-phase-5b-project-sweep.d.ts.map +1 -1
- package/dist/commands/setup-phase-5b-project-sweep.js +5 -6
- package/dist/commands/setup-phase-5b-project-sweep.js.map +1 -1
- package/dist/commands/substrate.d.ts +9 -0
- package/dist/commands/substrate.d.ts.map +1 -1
- package/dist/commands/substrate.js +18 -7
- package/dist/commands/substrate.js.map +1 -1
- package/dist/image-digests.json +7 -7
- package/dist/index.js +1514 -1477
- package/dist/lib/health-probes.d.ts +23 -0
- package/dist/lib/health-probes.d.ts.map +1 -1
- package/dist/lib/health-probes.js +51 -0
- package/dist/lib/health-probes.js.map +1 -1
- package/dist/lib/upgrade-kubernetes.d.ts +1 -1
- package/dist/lib/upgrade-kubernetes.d.ts.map +1 -1
- package/dist/lib/upgrade-kubernetes.js +24 -17
- package/dist/lib/upgrade-kubernetes.js.map +1 -1
- package/host-cp/k8s/manifests/50-deployment.yaml +8 -1
- package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +4 -1
- package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +4 -1
- package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +4 -1
- package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +4 -1
- package/host-cp/src/docker-events.mjs +19 -4
- package/host-cp/src/server.mjs +18 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15629,15 +15629,15 @@ var init_registry_allowlist = __esm({
|
|
|
15629
15629
|
|
|
15630
15630
|
// ../core/dist/world/world-yaml.js
|
|
15631
15631
|
import * as fs34 from "node:fs";
|
|
15632
|
-
import * as
|
|
15632
|
+
import * as path36 from "node:path";
|
|
15633
15633
|
import { parse as parseYaml4, stringify as stringifyYaml4 } from "yaml";
|
|
15634
15634
|
function writeWorldYaml(worldPath, data) {
|
|
15635
|
-
const olamDir =
|
|
15635
|
+
const olamDir = path36.join(worldPath, ".olam");
|
|
15636
15636
|
fs34.mkdirSync(olamDir, { recursive: true });
|
|
15637
|
-
fs34.writeFileSync(
|
|
15637
|
+
fs34.writeFileSync(path36.join(olamDir, "world.yaml"), stringifyYaml4(data), "utf-8");
|
|
15638
15638
|
}
|
|
15639
15639
|
function readWorldYaml(worldPath) {
|
|
15640
|
-
const yamlPath =
|
|
15640
|
+
const yamlPath = path36.join(worldPath, ".olam", "world.yaml");
|
|
15641
15641
|
if (!fs34.existsSync(yamlPath))
|
|
15642
15642
|
return null;
|
|
15643
15643
|
try {
|
|
@@ -21011,7 +21011,7 @@ function registerServices(program2) {
|
|
|
21011
21011
|
|
|
21012
21012
|
// src/lib/auth-refresh-kubernetes.ts
|
|
21013
21013
|
import * as fs32 from "node:fs";
|
|
21014
|
-
import * as
|
|
21014
|
+
import * as path34 from "node:path";
|
|
21015
21015
|
import pc10 from "picocolors";
|
|
21016
21016
|
import { stringify as yamlStringify2 } from "yaml";
|
|
21017
21017
|
|
|
@@ -21019,842 +21019,1233 @@ import { stringify as yamlStringify2 } from "yaml";
|
|
|
21019
21019
|
init_output();
|
|
21020
21020
|
import * as fs31 from "node:fs";
|
|
21021
21021
|
import "node:os";
|
|
21022
|
-
import * as
|
|
21022
|
+
import * as path33 from "node:path";
|
|
21023
21023
|
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
|
|
21024
21024
|
import ora3 from "ora";
|
|
21025
21025
|
import pc9 from "picocolors";
|
|
21026
21026
|
|
|
21027
|
-
// src/lib/
|
|
21028
|
-
import {
|
|
21029
|
-
import
|
|
21030
|
-
import
|
|
21031
|
-
import
|
|
21032
|
-
|
|
21033
|
-
|
|
21034
|
-
var
|
|
21035
|
-
var
|
|
21036
|
-
|
|
21037
|
-
|
|
21038
|
-
|
|
21039
|
-
|
|
21040
|
-
|
|
21041
|
-
|
|
21042
|
-
|
|
21043
|
-
|
|
21044
|
-
|
|
21045
|
-
|
|
21046
|
-
|
|
21047
|
-
|
|
21048
|
-
socket.destroy();
|
|
21049
|
-
resolve23(result);
|
|
21050
|
-
}
|
|
21051
|
-
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
21052
|
-
socket.once("connect", () => {
|
|
21053
|
-
clearTimeout(timer);
|
|
21054
|
-
finish(true);
|
|
21055
|
-
});
|
|
21056
|
-
socket.once("error", () => {
|
|
21057
|
-
clearTimeout(timer);
|
|
21058
|
-
finish(false);
|
|
21059
|
-
});
|
|
21060
|
-
socket.once("timeout", () => {
|
|
21061
|
-
clearTimeout(timer);
|
|
21062
|
-
finish(false);
|
|
21063
|
-
});
|
|
21064
|
-
socket.connect(port2, host);
|
|
21027
|
+
// src/lib/health-probes.ts
|
|
21028
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
21029
|
+
import { existsSync as existsSync28, readdirSync as readdirSync7, readFileSync as readFileSync22, statSync as statSync6 } from "node:fs";
|
|
21030
|
+
import { homedir as homedir18 } from "node:os";
|
|
21031
|
+
import path30 from "node:path";
|
|
21032
|
+
|
|
21033
|
+
// src/lib/kg-caps.ts
|
|
21034
|
+
var SOFT_CAP_BYTES = 15e8;
|
|
21035
|
+
var HARD_CAP_BYTES = 5e9;
|
|
21036
|
+
|
|
21037
|
+
// src/lib/health-probes.ts
|
|
21038
|
+
var HEALTH_TIMEOUT_MS = 5e3;
|
|
21039
|
+
var COLIMA_010_WARN_TEXT = (
|
|
21040
|
+
// eslint-disable-next-line @typescript-eslint/quotes
|
|
21041
|
+
"Colima 0.10.1's --kubernetes mode is broken upstream. Either switch to OLAM_HOST_CP_ENGINE=docker (recommended) or downgrade Colima to 0.9.x. Track abiosoft/colima issues for fix."
|
|
21042
|
+
);
|
|
21043
|
+
var COLIMA_010_RANGE = /^v?0\.10\.\d+$/;
|
|
21044
|
+
var defaultDockerExec2 = (cmd, args) => {
|
|
21045
|
+
const r = spawnSync12(cmd, [...args], {
|
|
21046
|
+
encoding: "utf-8",
|
|
21047
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
21065
21048
|
});
|
|
21066
|
-
|
|
21067
|
-
|
|
21068
|
-
|
|
21069
|
-
|
|
21070
|
-
return () => {
|
|
21071
|
-
try {
|
|
21072
|
-
fs29.unlinkSync(lockPath);
|
|
21073
|
-
} catch {
|
|
21074
|
-
}
|
|
21049
|
+
return {
|
|
21050
|
+
status: r.status,
|
|
21051
|
+
stdout: r.stdout ?? "",
|
|
21052
|
+
stderr: r.stderr ?? ""
|
|
21075
21053
|
};
|
|
21076
|
-
}
|
|
21077
|
-
|
|
21078
|
-
|
|
21079
|
-
|
|
21080
|
-
|
|
21081
|
-
|
|
21082
|
-
const stateDir = deps.stateDir ?? OLAM_STATE_DIR;
|
|
21083
|
-
return path30.join(stateDir, "port-forward.lock");
|
|
21084
|
-
}
|
|
21085
|
-
function writePidFile(pidPath2, pid) {
|
|
21086
|
-
const dir = path30.dirname(pidPath2);
|
|
21087
|
-
if (!fs29.existsSync(dir)) {
|
|
21088
|
-
fs29.mkdirSync(dir, { recursive: true });
|
|
21054
|
+
};
|
|
21055
|
+
var defaultFetch = (input2, init) => fetch(input2, init);
|
|
21056
|
+
async function probeDockerDaemon(dockerExec = defaultDockerExec2) {
|
|
21057
|
+
const r = dockerExec("docker", ["info", "--format", "{{.ServerVersion}}"]);
|
|
21058
|
+
if (r.status === 0 && r.stdout.trim().length > 0) {
|
|
21059
|
+
return { ok: true, message: `docker ${r.stdout.trim()} reachable` };
|
|
21089
21060
|
}
|
|
21090
|
-
|
|
21091
|
-
|
|
21092
|
-
|
|
21061
|
+
return {
|
|
21062
|
+
ok: false,
|
|
21063
|
+
message: "docker daemon not reachable",
|
|
21064
|
+
remedy: "Start Docker Desktop (or your docker daemon) and re-run."
|
|
21065
|
+
};
|
|
21093
21066
|
}
|
|
21094
|
-
async function
|
|
21095
|
-
const
|
|
21096
|
-
|
|
21097
|
-
|
|
21098
|
-
|
|
21099
|
-
|
|
21100
|
-
|
|
21101
|
-
|
|
21102
|
-
|
|
21103
|
-
try {
|
|
21104
|
-
releaseLock2 = acquireAdvisoryLock(lockPath);
|
|
21105
|
-
} catch (err) {
|
|
21106
|
-
const code = err.code;
|
|
21107
|
-
if (code === "EEXIST") {
|
|
21108
|
-
return { spawned: false, reason: "lock-held" };
|
|
21067
|
+
async function probeImagePresence(refs, dockerExec = defaultDockerExec2) {
|
|
21068
|
+
for (const ref of refs) {
|
|
21069
|
+
const r = dockerExec("docker", ["image", "inspect", "--format", "{{.Id}}", ref]);
|
|
21070
|
+
if (r.status !== 0) {
|
|
21071
|
+
return {
|
|
21072
|
+
ok: false,
|
|
21073
|
+
message: `image ${ref} not present locally`,
|
|
21074
|
+
remedy: "Run `olam bootstrap` to pull the published image, or build locally."
|
|
21075
|
+
};
|
|
21109
21076
|
}
|
|
21110
|
-
throw err;
|
|
21111
21077
|
}
|
|
21078
|
+
return { ok: true, message: `${refs.length} image(s) present` };
|
|
21079
|
+
}
|
|
21080
|
+
async function probeHostCpHealth(fetchImpl = defaultFetch) {
|
|
21081
|
+
const controller = new AbortController();
|
|
21082
|
+
const timeout = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
|
|
21112
21083
|
try {
|
|
21113
|
-
const
|
|
21114
|
-
|
|
21115
|
-
|
|
21084
|
+
const res = await fetchImpl("http://127.0.0.1:19000/health", { signal: controller.signal });
|
|
21085
|
+
const body = await res.text().catch(() => "");
|
|
21086
|
+
if (res.status !== 200) {
|
|
21087
|
+
return {
|
|
21088
|
+
ok: false,
|
|
21089
|
+
message: `host-cp /health returned ${res.status}`,
|
|
21090
|
+
remedy: "Restart host-cp via `olam host-cp start`; verify port 19000 is bound to 127.0.0.1."
|
|
21091
|
+
};
|
|
21116
21092
|
}
|
|
21117
|
-
|
|
21118
|
-
"
|
|
21119
|
-
[
|
|
21120
|
-
"--context",
|
|
21121
|
-
context,
|
|
21122
|
-
"port-forward",
|
|
21123
|
-
"-n",
|
|
21124
|
-
namespace,
|
|
21125
|
-
target,
|
|
21126
|
-
`${localPort}:${remotePort}`
|
|
21127
|
-
],
|
|
21128
|
-
{
|
|
21129
|
-
stdio: ["ignore", "ignore", "ignore"],
|
|
21130
|
-
detached: true
|
|
21131
|
-
}
|
|
21132
|
-
);
|
|
21133
|
-
if (typeof child.pid !== "number" || child.pid <= 0) {
|
|
21134
|
-
return { spawned: false, reason: "lock-held" };
|
|
21093
|
+
if (isHostCpHealthEnvelope(body)) {
|
|
21094
|
+
return { ok: true, message: "host-cp /health returned 200" };
|
|
21135
21095
|
}
|
|
21136
|
-
|
|
21137
|
-
|
|
21138
|
-
|
|
21139
|
-
|
|
21140
|
-
|
|
21141
|
-
|
|
21142
|
-
|
|
21143
|
-
});
|
|
21144
|
-
return {
|
|
21096
|
+
return {
|
|
21097
|
+
ok: false,
|
|
21098
|
+
message: "host-cp /health returned 200 but body did not match the host-cp envelope (possible port collision on :19000)",
|
|
21099
|
+
remedy: "Check `lsof -iTCP:19000`; another process may have bound the port. Restart host-cp via `olam host-cp start`."
|
|
21100
|
+
};
|
|
21101
|
+
} catch (err) {
|
|
21102
|
+
const e = err;
|
|
21103
|
+
const reason = e.name === "AbortError" ? `timeout (${HEALTH_TIMEOUT_MS}ms)` : e.message;
|
|
21104
|
+
return {
|
|
21105
|
+
ok: false,
|
|
21106
|
+
message: `host-cp /health unreachable: ${reason}`,
|
|
21107
|
+
remedy: "Restart host-cp via `olam host-cp start`; verify port 19000 is bound to 127.0.0.1."
|
|
21108
|
+
};
|
|
21145
21109
|
} finally {
|
|
21146
|
-
|
|
21110
|
+
clearTimeout(timeout);
|
|
21147
21111
|
}
|
|
21148
21112
|
}
|
|
21149
|
-
function
|
|
21150
|
-
|
|
21151
|
-
|
|
21152
|
-
|
|
21153
|
-
|
|
21113
|
+
function isHostCpHealthEnvelope(body) {
|
|
21114
|
+
let parsed;
|
|
21115
|
+
try {
|
|
21116
|
+
parsed = JSON.parse(body);
|
|
21117
|
+
} catch {
|
|
21118
|
+
return false;
|
|
21119
|
+
}
|
|
21120
|
+
if (!parsed || typeof parsed !== "object") return false;
|
|
21121
|
+
const obj = parsed;
|
|
21122
|
+
if (typeof obj.phase !== "string") return false;
|
|
21123
|
+
if (!obj.cache || typeof obj.cache !== "object") return false;
|
|
21124
|
+
const mode = obj.mode;
|
|
21125
|
+
if (!mode || typeof mode !== "object") return false;
|
|
21126
|
+
if (typeof mode.deployment !== "string") return false;
|
|
21127
|
+
return true;
|
|
21154
21128
|
}
|
|
21155
|
-
async function
|
|
21156
|
-
const
|
|
21157
|
-
|
|
21158
|
-
|
|
21159
|
-
|
|
21160
|
-
|
|
21129
|
+
async function probeAuthVault(olamHomeOverride) {
|
|
21130
|
+
const vaultPath = resolveAccountsPath(olamHomeOverride);
|
|
21131
|
+
if (!existsSync28(vaultPath)) {
|
|
21132
|
+
return {
|
|
21133
|
+
ok: false,
|
|
21134
|
+
message: `auth vault missing at ${vaultPath}`,
|
|
21135
|
+
remedy: "Run `olam auth up && olam auth login` to provision the vault."
|
|
21136
|
+
};
|
|
21161
21137
|
}
|
|
21162
|
-
|
|
21163
|
-
const pidPath2 = peripheralPidPath(peripheral.name, stateDir);
|
|
21164
|
-
let releaseLock2 = null;
|
|
21138
|
+
let parsed;
|
|
21165
21139
|
try {
|
|
21166
|
-
|
|
21140
|
+
parsed = JSON.parse(readFileSync22(vaultPath, "utf-8"));
|
|
21167
21141
|
} catch (err) {
|
|
21168
|
-
|
|
21169
|
-
|
|
21170
|
-
|
|
21171
|
-
|
|
21172
|
-
|
|
21142
|
+
return {
|
|
21143
|
+
ok: false,
|
|
21144
|
+
message: `auth vault malformed: ${err.message}`,
|
|
21145
|
+
remedy: "Inspect the accounts.json file; restore from backup or re-run `olam auth up`."
|
|
21146
|
+
};
|
|
21173
21147
|
}
|
|
21174
|
-
|
|
21175
|
-
|
|
21176
|
-
|
|
21177
|
-
|
|
21178
|
-
|
|
21179
|
-
|
|
21180
|
-
|
|
21181
|
-
|
|
21182
|
-
|
|
21183
|
-
|
|
21184
|
-
|
|
21185
|
-
|
|
21186
|
-
|
|
21187
|
-
|
|
21188
|
-
|
|
21189
|
-
|
|
21190
|
-
{
|
|
21191
|
-
stdio: ["ignore", "ignore", "ignore"],
|
|
21192
|
-
detached: true
|
|
21193
|
-
}
|
|
21194
|
-
);
|
|
21195
|
-
if (typeof child.pid !== "number" || child.pid <= 0) {
|
|
21196
|
-
return false;
|
|
21197
|
-
}
|
|
21198
|
-
writePidFile(pidPath2, child.pid);
|
|
21199
|
-
child.unref();
|
|
21200
|
-
child.once("exit", () => {
|
|
21201
|
-
try {
|
|
21202
|
-
fs29.unlinkSync(pidPath2);
|
|
21203
|
-
} catch {
|
|
21204
|
-
}
|
|
21205
|
-
});
|
|
21206
|
-
return true;
|
|
21207
|
-
} finally {
|
|
21208
|
-
releaseLock2();
|
|
21148
|
+
if (!Array.isArray(parsed)) {
|
|
21149
|
+
return {
|
|
21150
|
+
ok: false,
|
|
21151
|
+
message: "auth vault malformed: expected JSON array at top level",
|
|
21152
|
+
remedy: "Restore the vault from backup or re-run `olam auth up && olam auth login`."
|
|
21153
|
+
};
|
|
21154
|
+
}
|
|
21155
|
+
const accounts = parsed.filter((a) => a !== null && typeof a === "object");
|
|
21156
|
+
const now = Date.now();
|
|
21157
|
+
const activeClaude = accounts.filter((a) => a.provider === "claude" && effectiveState2(a, now) === "active");
|
|
21158
|
+
if (activeClaude.length === 0) {
|
|
21159
|
+
return {
|
|
21160
|
+
ok: false,
|
|
21161
|
+
message: `no active claude credentials (vault has ${accounts.length} account(s))`,
|
|
21162
|
+
remedy: "Run `olam auth login` to add a credential, or wait for the next reset."
|
|
21163
|
+
};
|
|
21209
21164
|
}
|
|
21165
|
+
return { ok: true, message: `${activeClaude.length} active claude credential(s)` };
|
|
21210
21166
|
}
|
|
21211
|
-
|
|
21212
|
-
|
|
21213
|
-
|
|
21214
|
-
);
|
|
21167
|
+
function resolveAccountsPath(olamHomeOverride) {
|
|
21168
|
+
const explicit = process.env.OLAM_AUTH_DATA_PATH;
|
|
21169
|
+
if (explicit) return explicit;
|
|
21170
|
+
const olamHome5 = olamHomeOverride ?? process.env.OLAM_HOME ?? path30.join(homedir18(), ".olam");
|
|
21171
|
+
return path30.join(olamHome5, "auth-data", "accounts.json");
|
|
21215
21172
|
}
|
|
21216
|
-
|
|
21217
|
-
|
|
21218
|
-
|
|
21219
|
-
|
|
21220
|
-
|
|
21221
|
-
|
|
21222
|
-
|
|
21173
|
+
function effectiveState2(account, now) {
|
|
21174
|
+
const persisted = account.state ?? (account.rateLimited ? "cooldown" : "active");
|
|
21175
|
+
if (persisted === "disabled") return "disabled";
|
|
21176
|
+
if (typeof account.expiresAt === "number" && account.expiresAt <= now) return "expired";
|
|
21177
|
+
if (persisted === "cooldown") {
|
|
21178
|
+
const reset = account.rateLimitResetsAt ? new Date(account.rateLimitResetsAt).getTime() : 0;
|
|
21179
|
+
if (reset > 0 && reset <= now) return "active";
|
|
21180
|
+
}
|
|
21181
|
+
return persisted;
|
|
21223
21182
|
}
|
|
21224
|
-
function
|
|
21183
|
+
async function probeKgStorage(olamHomeOverride) {
|
|
21184
|
+
const olamHome5 = olamHomeOverride ?? process.env.OLAM_HOME ?? path30.join(homedir18(), ".olam");
|
|
21185
|
+
const kgRoot3 = path30.join(olamHome5, "kg");
|
|
21186
|
+
const worldsRoot3 = path30.join(olamHome5, "worlds");
|
|
21187
|
+
const kgBytes = enumerateGraphifyOut(kgRoot3, "pristine");
|
|
21188
|
+
const overlayBytes = enumerateGraphifyOut(worldsRoot3, "overlay");
|
|
21189
|
+
const totalBytes = kgBytes + overlayBytes;
|
|
21190
|
+
if (totalBytes >= HARD_CAP_BYTES) {
|
|
21191
|
+
return {
|
|
21192
|
+
ok: false,
|
|
21193
|
+
message: `KG storage ${formatBytes(totalBytes)} exceeds 5 GB hard cap`,
|
|
21194
|
+
remedy: "Prune stale pristines: `rm -rf ~/.olam/kg/<workspace>` after operator review."
|
|
21195
|
+
};
|
|
21196
|
+
}
|
|
21197
|
+
if (totalBytes >= SOFT_CAP_BYTES) {
|
|
21198
|
+
return {
|
|
21199
|
+
ok: true,
|
|
21200
|
+
message: `KG storage ${formatBytes(totalBytes)} (soft-warn \u2014 exceeds 1.5 GB; consider pruning)`
|
|
21201
|
+
};
|
|
21202
|
+
}
|
|
21203
|
+
return { ok: true, message: `KG storage ${formatBytes(totalBytes)} (under cap)` };
|
|
21204
|
+
}
|
|
21205
|
+
function enumerateGraphifyOut(root, layout) {
|
|
21206
|
+
if (!existsSync28(root)) return 0;
|
|
21207
|
+
let total = 0;
|
|
21208
|
+
let entries;
|
|
21225
21209
|
try {
|
|
21226
|
-
|
|
21227
|
-
const cfg = readConfig();
|
|
21228
|
-
return cfg.install_id;
|
|
21210
|
+
entries = readdirSync7(root, { withFileTypes: true });
|
|
21229
21211
|
} catch {
|
|
21230
|
-
return
|
|
21212
|
+
return 0;
|
|
21213
|
+
}
|
|
21214
|
+
for (const entry of entries) {
|
|
21215
|
+
if (!entry.isDirectory()) continue;
|
|
21216
|
+
if (layout === "pristine") {
|
|
21217
|
+
total += dirSizeBytes(path30.join(root, entry.name, "graphify-out"));
|
|
21218
|
+
} else {
|
|
21219
|
+
const worldDir = path30.join(root, entry.name);
|
|
21220
|
+
let clones;
|
|
21221
|
+
try {
|
|
21222
|
+
clones = readdirSync7(worldDir, { withFileTypes: true });
|
|
21223
|
+
} catch {
|
|
21224
|
+
continue;
|
|
21225
|
+
}
|
|
21226
|
+
for (const clone of clones) {
|
|
21227
|
+
if (!clone.isDirectory()) continue;
|
|
21228
|
+
total += dirSizeBytes(path30.join(worldDir, clone.name, "graphify-out"));
|
|
21229
|
+
}
|
|
21230
|
+
}
|
|
21231
21231
|
}
|
|
21232
|
+
return total;
|
|
21232
21233
|
}
|
|
21233
|
-
function
|
|
21234
|
-
|
|
21235
|
-
|
|
21236
|
-
|
|
21237
|
-
|
|
21238
|
-
|
|
21234
|
+
function dirSizeBytes(dir) {
|
|
21235
|
+
if (!existsSync28(dir)) return 0;
|
|
21236
|
+
let total = 0;
|
|
21237
|
+
const stack = [dir];
|
|
21238
|
+
while (stack.length > 0) {
|
|
21239
|
+
const cur = stack.pop();
|
|
21240
|
+
let entries;
|
|
21239
21241
|
try {
|
|
21240
|
-
|
|
21241
|
-
`[WARN] olam instrumentation emit failed: ${err instanceof Error ? err.message : String(err)}
|
|
21242
|
-
`
|
|
21243
|
-
);
|
|
21242
|
+
entries = readdirSync7(cur, { withFileTypes: true });
|
|
21244
21243
|
} catch {
|
|
21244
|
+
continue;
|
|
21245
|
+
}
|
|
21246
|
+
for (const entry of entries) {
|
|
21247
|
+
const full = path30.join(cur, entry.name);
|
|
21248
|
+
if (entry.isSymbolicLink()) continue;
|
|
21249
|
+
if (entry.isDirectory()) {
|
|
21250
|
+
stack.push(full);
|
|
21251
|
+
continue;
|
|
21252
|
+
}
|
|
21253
|
+
try {
|
|
21254
|
+
total += statSync6(full).size;
|
|
21255
|
+
} catch {
|
|
21256
|
+
}
|
|
21245
21257
|
}
|
|
21246
21258
|
}
|
|
21259
|
+
return total;
|
|
21247
21260
|
}
|
|
21248
|
-
function
|
|
21249
|
-
|
|
21250
|
-
|
|
21251
|
-
|
|
21252
|
-
|
|
21253
|
-
ts: nowIso(opts.now),
|
|
21254
|
-
substrate: payload.substrate,
|
|
21255
|
-
...payload.flavor !== void 0 ? { flavor: payload.flavor } : {}
|
|
21256
|
-
};
|
|
21257
|
-
writeLine(opts.stderr ?? process.stderr, event);
|
|
21261
|
+
function formatBytes(n) {
|
|
21262
|
+
if (n < 1024) return `${n} B`;
|
|
21263
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
21264
|
+
if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
21265
|
+
return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
21258
21266
|
}
|
|
21259
|
-
function
|
|
21260
|
-
const
|
|
21261
|
-
|
|
21262
|
-
|
|
21263
|
-
|
|
21264
|
-
|
|
21265
|
-
|
|
21266
|
-
|
|
21267
|
-
|
|
21268
|
-
|
|
21269
|
-
|
|
21267
|
+
async function probeEngine(fetchImpl = defaultFetch) {
|
|
21268
|
+
const controller = new AbortController();
|
|
21269
|
+
const timeout = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
|
|
21270
|
+
try {
|
|
21271
|
+
const res = await fetchImpl("http://127.0.0.1:19000/health", { signal: controller.signal });
|
|
21272
|
+
await res.text().catch(() => "");
|
|
21273
|
+
if (res.status !== 200) {
|
|
21274
|
+
return {
|
|
21275
|
+
ok: false,
|
|
21276
|
+
message: `host-cp /health returned ${res.status}; engine unknown`,
|
|
21277
|
+
remedy: "Restart host-cp via `olam host-cp start`; verify port 19000 is bound to 127.0.0.1."
|
|
21278
|
+
};
|
|
21279
|
+
}
|
|
21280
|
+
const engineHeader = res.headers.get("x-olam-engine") ?? res.headers.get("X-Olam-Engine");
|
|
21281
|
+
if (!engineHeader) {
|
|
21282
|
+
return {
|
|
21283
|
+
ok: true,
|
|
21284
|
+
message: "engine unknown (X-Olam-Engine header absent \u2014 older host-cp?)"
|
|
21285
|
+
};
|
|
21286
|
+
}
|
|
21287
|
+
return {
|
|
21288
|
+
ok: true,
|
|
21289
|
+
message: `engine ${engineHeader}`,
|
|
21290
|
+
engineName: engineHeader
|
|
21291
|
+
};
|
|
21292
|
+
} catch (err) {
|
|
21293
|
+
const e = err;
|
|
21294
|
+
const reason = e.name === "AbortError" ? `timeout (${HEALTH_TIMEOUT_MS}ms)` : e.message;
|
|
21295
|
+
return {
|
|
21296
|
+
ok: false,
|
|
21297
|
+
message: `engine probe unreachable: ${reason}`,
|
|
21298
|
+
remedy: "Restart host-cp via `olam host-cp start`; verify port 19000 is bound to 127.0.0.1."
|
|
21299
|
+
};
|
|
21300
|
+
} finally {
|
|
21301
|
+
clearTimeout(timeout);
|
|
21302
|
+
}
|
|
21270
21303
|
}
|
|
21271
|
-
|
|
21272
|
-
|
|
21273
|
-
|
|
21274
|
-
|
|
21275
|
-
|
|
21276
|
-
|
|
21277
|
-
var SECURITY_SENSITIVE_FIELDS = [
|
|
21278
|
-
"securityContext",
|
|
21279
|
-
"resources.limits",
|
|
21280
|
-
"capabilities",
|
|
21281
|
-
"readOnlyRootFilesystem",
|
|
21282
|
-
"rules"
|
|
21283
|
-
// RBAC Role rules
|
|
21284
|
-
];
|
|
21285
|
-
function seedManifestsFromBundle(forceRefresh, deps = {}) {
|
|
21286
|
-
const existsSyncImpl = deps.existsSync ?? fs30.existsSync;
|
|
21287
|
-
const cpSyncImpl = deps.cpSync ?? fs30.cpSync;
|
|
21288
|
-
const mkdirSyncImpl = deps.mkdirSync ?? fs30.mkdirSync;
|
|
21289
|
-
const rootDir = deps.installRootDir ?? installRoot();
|
|
21290
|
-
const sourcePath = path31.join(rootDir, "host-cp", "k8s", "manifests");
|
|
21291
|
-
const OLAM_HOME3 = path31.join(
|
|
21292
|
-
process.env.HOME ?? process.env.USERPROFILE ?? "/tmp",
|
|
21293
|
-
".olam"
|
|
21294
|
-
);
|
|
21295
|
-
const targetPath = deps.targetManifestsDir ?? path31.join(OLAM_HOME3, "k8s", "manifests");
|
|
21296
|
-
if (!forceRefresh && existsSyncImpl(targetPath)) {
|
|
21304
|
+
async function probeColimaVersion(dockerExec = defaultDockerExec2) {
|
|
21305
|
+
const r = dockerExec("colima", ["version"]);
|
|
21306
|
+
if (r.status === null || r.status !== 0 && /not found|ENOENT|not.found/i.test(r.stderr)) {
|
|
21307
|
+
return { ok: true, message: "colima not installed (not in use)" };
|
|
21308
|
+
}
|
|
21309
|
+
if (r.status !== 0) {
|
|
21297
21310
|
return {
|
|
21298
|
-
|
|
21299
|
-
|
|
21300
|
-
reason: `~/.olam/k8s/manifests/ already exists \u2014 skipping seed (use --force-refresh-manifests to overwrite)`
|
|
21311
|
+
ok: true,
|
|
21312
|
+
message: `colima present but version probe failed: ${(r.stderr || "").trim()}`
|
|
21301
21313
|
};
|
|
21302
21314
|
}
|
|
21303
|
-
|
|
21315
|
+
const tokens = r.stdout.split(/\s+/);
|
|
21316
|
+
const version = tokens.find((t) => /^v?\d+\.\d+\.\d+/.test(t)) ?? "";
|
|
21317
|
+
if (!version) {
|
|
21304
21318
|
return {
|
|
21305
|
-
|
|
21306
|
-
|
|
21307
|
-
reason: `Bundled manifests not found at ${sourcePath} \u2014 skipping seed (dev/monorepo context).
|
|
21308
|
-
If running from a published install, re-run npm install to restore the bundle.`
|
|
21319
|
+
ok: true,
|
|
21320
|
+
message: `colima present but version unrecognised: ${r.stdout.trim().slice(0, 80)}`
|
|
21309
21321
|
};
|
|
21310
21322
|
}
|
|
21311
|
-
|
|
21312
|
-
mkdirSyncImpl(path31.dirname(targetPath), { recursive: true });
|
|
21313
|
-
cpSyncImpl(sourcePath, targetPath, { recursive: true });
|
|
21314
|
-
} catch (err) {
|
|
21323
|
+
if (COLIMA_010_RANGE.test(version)) {
|
|
21315
21324
|
return {
|
|
21316
|
-
|
|
21317
|
-
|
|
21318
|
-
|
|
21325
|
+
ok: true,
|
|
21326
|
+
warn: true,
|
|
21327
|
+
message: `colima ${version} detected \u2014 known kubernetes-mode regression`,
|
|
21328
|
+
remedy: COLIMA_010_WARN_TEXT
|
|
21319
21329
|
};
|
|
21320
21330
|
}
|
|
21331
|
+
return { ok: true, message: `colima ${version} (clear)` };
|
|
21332
|
+
}
|
|
21333
|
+
async function probeK8sApiReachable(exec = defaultDockerExec2) {
|
|
21334
|
+
const r = exec("kubectl", ["version", "--output=json", "--request-timeout=2s"]);
|
|
21335
|
+
if (r.status === 0 && r.stdout.length > 0) {
|
|
21336
|
+
let parsed;
|
|
21337
|
+
try {
|
|
21338
|
+
parsed = JSON.parse(r.stdout);
|
|
21339
|
+
} catch {
|
|
21340
|
+
parsed = null;
|
|
21341
|
+
}
|
|
21342
|
+
const obj = parsed && typeof parsed === "object" ? parsed : {};
|
|
21343
|
+
const serverInfo = obj.serverVersion;
|
|
21344
|
+
const gitVersion = typeof serverInfo?.gitVersion === "string" ? serverInfo.gitVersion : "unknown";
|
|
21345
|
+
return { ok: true, message: `api server ${gitVersion} reachable` };
|
|
21346
|
+
}
|
|
21321
21347
|
return {
|
|
21322
|
-
|
|
21323
|
-
|
|
21324
|
-
|
|
21325
|
-
message: forceRefresh ? `Manifests force-refreshed from ${sourcePath} \u2192 ${targetPath}` : `Manifests seeded from ${sourcePath} \u2192 ${targetPath}`
|
|
21348
|
+
ok: false,
|
|
21349
|
+
message: "api server not reachable",
|
|
21350
|
+
remedy: "Confirm the cluster is up (e.g. `k3d cluster list`) and your context points at it."
|
|
21326
21351
|
};
|
|
21327
21352
|
}
|
|
21328
|
-
function
|
|
21329
|
-
|
|
21330
|
-
|
|
21331
|
-
let current = obj;
|
|
21332
|
-
for (const k of keys) {
|
|
21333
|
-
if (typeof current !== "object" || current === null) return "";
|
|
21334
|
-
current = current[k];
|
|
21335
|
-
}
|
|
21336
|
-
if (current === void 0) return "";
|
|
21337
|
-
return JSON.stringify(current);
|
|
21338
|
-
}
|
|
21339
|
-
function extractSecurityFieldsFromParsed(parsed) {
|
|
21340
|
-
const result = {};
|
|
21341
|
-
for (const field of SECURITY_SENSITIVE_FIELDS) {
|
|
21342
|
-
result[field] = extractField(parsed, field);
|
|
21343
|
-
}
|
|
21344
|
-
return result;
|
|
21345
|
-
}
|
|
21346
|
-
function diffManifestSecurityFields(oldContent, newContent) {
|
|
21347
|
-
let oldParsed;
|
|
21348
|
-
let newParsed;
|
|
21349
|
-
try {
|
|
21350
|
-
oldParsed = JSON.parse(oldContent);
|
|
21351
|
-
} catch {
|
|
21352
|
-
oldParsed = { _raw: oldContent };
|
|
21353
|
-
}
|
|
21354
|
-
try {
|
|
21355
|
-
newParsed = JSON.parse(newContent);
|
|
21356
|
-
} catch {
|
|
21357
|
-
newParsed = { _raw: newContent };
|
|
21358
|
-
}
|
|
21359
|
-
const oldFields = extractSecurityFieldsFromParsed(oldParsed);
|
|
21360
|
-
const newFields = extractSecurityFieldsFromParsed(newParsed);
|
|
21361
|
-
const changedFields = [];
|
|
21362
|
-
for (const field of SECURITY_SENSITIVE_FIELDS) {
|
|
21363
|
-
if (oldFields[field] !== newFields[field]) {
|
|
21364
|
-
changedFields.push(field);
|
|
21365
|
-
}
|
|
21366
|
-
}
|
|
21367
|
-
return {
|
|
21368
|
-
hasSecurityRegression: changedFields.length > 0,
|
|
21369
|
-
changedFields
|
|
21370
|
-
};
|
|
21371
|
-
}
|
|
21372
|
-
function appendAuditEntry(entry, auditLogPath, writeFileSyncImpl) {
|
|
21373
|
-
const line = JSON.stringify(entry) + "\n";
|
|
21374
|
-
try {
|
|
21375
|
-
writeFileSyncImpl(auditLogPath, line, {
|
|
21376
|
-
encoding: "utf8",
|
|
21377
|
-
flag: "a",
|
|
21378
|
-
mode: 384
|
|
21379
|
-
});
|
|
21380
|
-
} catch (err) {
|
|
21381
|
-
throw new Error(`audit log unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
21382
|
-
}
|
|
21383
|
-
}
|
|
21384
|
-
async function runManifestRefresh(manifestsDir, acceptRegression, deps = {}, peripheral) {
|
|
21385
|
-
const auditLogPath = deps.auditLogPath ?? MANIFEST_REFRESH_AUDIT_LOG;
|
|
21386
|
-
const readdirSync25 = deps.readdirSync ?? fs30.readdirSync;
|
|
21387
|
-
const readFileSync74 = deps.readFileSync ?? fs30.readFileSync;
|
|
21388
|
-
const writeFileSyncImpl = deps.writeFileSync ?? fs30.writeFileSync;
|
|
21389
|
-
const existsSync99 = deps.existsSync ?? fs30.existsSync;
|
|
21390
|
-
const now = deps.now ? deps.now() : /* @__PURE__ */ new Date();
|
|
21391
|
-
const targetDir = peripheral ? path31.join(manifestsDir, peripheral) : manifestsDir;
|
|
21392
|
-
if (!existsSync99(targetDir)) {
|
|
21353
|
+
async function probeK8sImagePresence(refs, exec = defaultDockerExec2) {
|
|
21354
|
+
const r = exec("kubectl", ["get", "nodes", "-o", "json", "--request-timeout=2s"]);
|
|
21355
|
+
if (r.status !== 0) {
|
|
21393
21356
|
return {
|
|
21394
21357
|
ok: false,
|
|
21395
|
-
message:
|
|
21358
|
+
message: "unable to list cluster nodes for image presence check",
|
|
21359
|
+
remedy: "Confirm the cluster is up and your context points at it; re-run `olam doctor`."
|
|
21396
21360
|
};
|
|
21397
21361
|
}
|
|
21398
|
-
let
|
|
21362
|
+
let parsed;
|
|
21399
21363
|
try {
|
|
21400
|
-
|
|
21401
|
-
|
|
21402
|
-
} catch (err) {
|
|
21364
|
+
parsed = JSON.parse(r.stdout);
|
|
21365
|
+
} catch {
|
|
21403
21366
|
return {
|
|
21404
21367
|
ok: false,
|
|
21405
|
-
message:
|
|
21368
|
+
message: "cluster node list returned malformed output",
|
|
21369
|
+
remedy: "Restart your cluster; if persistent, re-create it."
|
|
21406
21370
|
};
|
|
21407
21371
|
}
|
|
21408
|
-
const
|
|
21409
|
-
|
|
21410
|
-
const filePath = path31.join(targetDir, file);
|
|
21411
|
-
let content;
|
|
21412
|
-
try {
|
|
21413
|
-
content = readFileSync74(filePath, "utf8");
|
|
21414
|
-
} catch {
|
|
21415
|
-
continue;
|
|
21416
|
-
}
|
|
21417
|
-
const diff = diffManifestSecurityFields("{}", content);
|
|
21418
|
-
for (const f of diff.changedFields) {
|
|
21419
|
-
if (!allChangedFields.includes(f)) allChangedFields.push(f);
|
|
21420
|
-
}
|
|
21421
|
-
}
|
|
21422
|
-
const hasSecurityRegression = allChangedFields.length > 0;
|
|
21423
|
-
if (hasSecurityRegression && !acceptRegression) {
|
|
21372
|
+
const nodes = parsed && typeof parsed === "object" ? parsed.items : void 0;
|
|
21373
|
+
if (!Array.isArray(nodes) || nodes.length === 0) {
|
|
21424
21374
|
return {
|
|
21425
21375
|
ok: false,
|
|
21426
|
-
message:
|
|
21427
|
-
|
|
21376
|
+
message: "cluster has no nodes",
|
|
21377
|
+
remedy: "Start the cluster and try again."
|
|
21428
21378
|
};
|
|
21429
21379
|
}
|
|
21430
|
-
const
|
|
21431
|
-
|
|
21432
|
-
|
|
21433
|
-
|
|
21434
|
-
|
|
21435
|
-
|
|
21436
|
-
|
|
21437
|
-
|
|
21438
|
-
|
|
21439
|
-
|
|
21440
|
-
appendAuditEntry(entry, auditLogPath, writeFileSyncImpl);
|
|
21441
|
-
} catch (err) {
|
|
21442
|
-
return {
|
|
21443
|
-
ok: false,
|
|
21444
|
-
message: err instanceof Error ? err.message : String(err)
|
|
21445
|
-
};
|
|
21380
|
+
const presentImages = /* @__PURE__ */ new Set();
|
|
21381
|
+
for (const node of nodes) {
|
|
21382
|
+
const status2 = node.status;
|
|
21383
|
+
const images = status2?.images;
|
|
21384
|
+
if (!Array.isArray(images)) continue;
|
|
21385
|
+
for (const img of images) {
|
|
21386
|
+
const names = img.names;
|
|
21387
|
+
if (!Array.isArray(names)) continue;
|
|
21388
|
+
for (const n of names) presentImages.add(n);
|
|
21389
|
+
}
|
|
21446
21390
|
}
|
|
21447
|
-
|
|
21448
|
-
|
|
21449
|
-
|
|
21450
|
-
|
|
21451
|
-
}
|
|
21452
|
-
|
|
21453
|
-
|
|
21454
|
-
|
|
21455
|
-
var K8S_NAMESPACE2 = "olam";
|
|
21456
|
-
var HOST_CP_SECRET_NAME = "olam-host-cp-secret";
|
|
21457
|
-
var HOST_CP_DEPLOYMENT_NAME = "olam-host-cp";
|
|
21458
|
-
var PORT_FORWARD_TARGET = "service/olam-host-cp";
|
|
21459
|
-
var HOST_CP_HEALTH_URL = "http://127.0.0.1:19000/health";
|
|
21460
|
-
var SUBSTRATE_AUDIT_LOG = path32.join(OLAM_STATE_DIR, "substrate-audit.jsonl");
|
|
21461
|
-
var PLACEHOLDER_VALUES = /* @__PURE__ */ new Set(["OLAM_AUTH_SECRET", "GH_TOKEN"]);
|
|
21462
|
-
var REQUIRED_SECRET_KEYS = ["OLAM_AUTH_SECRET", "GH_TOKEN"];
|
|
21463
|
-
var PERIPHERAL_SECRETS = [
|
|
21464
|
-
{ name: "auth-service", secretName: "olam-auth-service-secret", keys: ["OLAM_AUTH_DB_SECRET"] },
|
|
21465
|
-
{ name: "mcp-auth-service", secretName: "olam-mcp-auth-service-secret", keys: ["OLAM_MCP_AUTH_JWT_SECRET"] },
|
|
21466
|
-
{ name: "kg-service", secretName: "olam-kg-service-secret", keys: ["OLAM_KG_BEARER_TOKEN"] },
|
|
21467
|
-
{ name: "memory-service", secretName: "olam-memory-service-secret", keys: ["OLAM_MEMORY_BEARER_SECRET"] }
|
|
21468
|
-
];
|
|
21469
|
-
var K8S_DNS_SUFFIX = "olam.svc.cluster.local";
|
|
21470
|
-
function buildK8sDnsUrl(k8sServiceName, port2) {
|
|
21471
|
-
return `http://${k8sServiceName}.${K8S_DNS_SUFFIX}:${port2}`;
|
|
21472
|
-
}
|
|
21473
|
-
function appendSubstrateAuditEntry(entry, stderr) {
|
|
21474
|
-
try {
|
|
21475
|
-
const line = JSON.stringify(entry) + "\n";
|
|
21476
|
-
const dir = path32.dirname(SUBSTRATE_AUDIT_LOG);
|
|
21477
|
-
if (!fs31.existsSync(dir)) fs31.mkdirSync(dir, { recursive: true });
|
|
21478
|
-
fs31.writeFileSync(SUBSTRATE_AUDIT_LOG, line, { encoding: "utf8", flag: "a", mode: 384 });
|
|
21479
|
-
} catch (err) {
|
|
21480
|
-
stderr.write(
|
|
21481
|
-
`${pc9.yellow("[warn]")} could not write substrate audit log: ${err instanceof Error ? err.message : String(err)}
|
|
21482
|
-
`
|
|
21483
|
-
);
|
|
21391
|
+
for (const ref of refs) {
|
|
21392
|
+
if (!hasImageRef(presentImages, ref)) {
|
|
21393
|
+
return {
|
|
21394
|
+
ok: false,
|
|
21395
|
+
message: `image ${ref} not present on any cluster node`,
|
|
21396
|
+
remedy: "Run `olam bootstrap` to import the published image into the cluster."
|
|
21397
|
+
};
|
|
21398
|
+
}
|
|
21484
21399
|
}
|
|
21400
|
+
return { ok: true, message: `${refs.length} image(s) present on cluster` };
|
|
21485
21401
|
}
|
|
21486
|
-
function
|
|
21487
|
-
|
|
21402
|
+
function hasImageRef(present, ref) {
|
|
21403
|
+
if (present.has(ref)) return true;
|
|
21404
|
+
const variants = [`docker.io/library/${ref}`, `docker.io/${ref}`];
|
|
21405
|
+
return variants.some((v) => present.has(v));
|
|
21488
21406
|
}
|
|
21489
|
-
|
|
21490
|
-
|
|
21491
|
-
|
|
21492
|
-
|
|
21493
|
-
|
|
21494
|
-
|
|
21495
|
-
{ pattern: /\.civo\.com(:\d+)?$/, provider: "Civo" }
|
|
21496
|
-
];
|
|
21497
|
-
async function detectManagedK8sProvider(deps) {
|
|
21498
|
-
if (deps.getClusterServerUrlImpl) {
|
|
21499
|
-
const url3 = await deps.getClusterServerUrlImpl();
|
|
21500
|
-
if (!url3) return null;
|
|
21501
|
-
for (const { pattern, provider } of MANAGED_K8S_URL_PATTERNS) {
|
|
21502
|
-
if (pattern.test(url3)) return provider;
|
|
21503
|
-
}
|
|
21504
|
-
return null;
|
|
21505
|
-
}
|
|
21506
|
-
const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
|
|
21507
|
-
const result = await wrap(
|
|
21508
|
-
["config", "view", "--minify", "-o", "jsonpath={.clusters[0].cluster.server}"],
|
|
21509
|
-
{ timeout: 5e3 }
|
|
21510
|
-
);
|
|
21511
|
-
if (!result.ok || !result.stdout.trim()) return null;
|
|
21512
|
-
const url2 = result.stdout.trim();
|
|
21513
|
-
for (const { pattern, provider } of MANAGED_K8S_URL_PATTERNS) {
|
|
21514
|
-
if (pattern.test(url2)) return provider;
|
|
21515
|
-
}
|
|
21516
|
-
return null;
|
|
21407
|
+
function defaultDetectK8sClusterType(kubectlContext) {
|
|
21408
|
+
const ctx = kubectlContext ?? readCurrentKubectlContext();
|
|
21409
|
+
if (!ctx) return "unknown";
|
|
21410
|
+
if (ctx.startsWith("colima")) return "colima";
|
|
21411
|
+
if (ctx.startsWith("k3d-")) return "k3d";
|
|
21412
|
+
return "unknown";
|
|
21517
21413
|
}
|
|
21518
|
-
|
|
21519
|
-
|
|
21520
|
-
|
|
21414
|
+
function readCurrentKubectlContext() {
|
|
21415
|
+
const r = spawnSync12("kubectl", ["config", "current-context"], {
|
|
21416
|
+
encoding: "utf-8",
|
|
21417
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
21418
|
+
});
|
|
21419
|
+
if (r.status !== 0) return void 0;
|
|
21420
|
+
return r.stdout?.trim() || void 0;
|
|
21421
|
+
}
|
|
21422
|
+
function buildDockerSocketRemedy(clusterType) {
|
|
21423
|
+
if (clusterType === "colima") {
|
|
21424
|
+
return "Recreate the Colima k3s cluster with the docker socket bind-mount:\n colima stop\n colima start --kubernetes \\\n --mount /var/run/docker.sock:/var/run/docker.sock:w \\\n --mount ~/.config/gh:/host/.config/gh:r\nThen run: olam upgrade";
|
|
21521
21425
|
}
|
|
21522
|
-
|
|
21523
|
-
const result = await wrap(
|
|
21524
|
-
[
|
|
21525
|
-
"--context",
|
|
21526
|
-
context,
|
|
21527
|
-
"exec",
|
|
21528
|
-
"deploy/olam-host-cp",
|
|
21529
|
-
"-n",
|
|
21530
|
-
"olam",
|
|
21531
|
-
"--",
|
|
21532
|
-
"test",
|
|
21533
|
-
"-S",
|
|
21534
|
-
"/var/run/docker.sock"
|
|
21535
|
-
],
|
|
21536
|
-
{ timeout: 1e4 }
|
|
21537
|
-
);
|
|
21538
|
-
return result.ok;
|
|
21426
|
+
return "Recreate the k3d cluster with the docker socket bind-mount:\n k3d cluster create olam-host \\\n --volume /var/run/docker.sock:/var/run/docker.sock@server:* \\\n --volume ~/.config/gh:/host/.config/gh \\\n --wait --timeout 90s\nThen run: olam upgrade";
|
|
21539
21427
|
}
|
|
21540
|
-
|
|
21541
|
-
|
|
21542
|
-
|
|
21543
|
-
|
|
21544
|
-
|
|
21545
|
-
|
|
21546
|
-
|
|
21428
|
+
|
|
21429
|
+
// src/lib/port-forward.ts
|
|
21430
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
21431
|
+
import * as fs29 from "node:fs";
|
|
21432
|
+
import * as net3 from "node:net";
|
|
21433
|
+
import * as path31 from "node:path";
|
|
21434
|
+
import * as os16 from "node:os";
|
|
21435
|
+
var PORT_FORWARD_PORT = 19e3;
|
|
21436
|
+
var PORT_FORWARD_LOCK_PATH = path31.join(OLAM_STATE_DIR, "port-forward.lock");
|
|
21437
|
+
var PORT_FORWARD_PID_PATH = path31.join(OLAM_STATE_DIR, "port-forward.pid");
|
|
21438
|
+
var TCP_PROBE_TIMEOUT_MS = 2e3;
|
|
21439
|
+
async function probePortForwardLiveness(deps = {}) {
|
|
21440
|
+
const probe2 = deps.tcpProbeImpl ?? realTcpProbe;
|
|
21441
|
+
return probe2("127.0.0.1", PORT_FORWARD_PORT, TCP_PROBE_TIMEOUT_MS);
|
|
21547
21442
|
}
|
|
21548
|
-
|
|
21549
|
-
|
|
21550
|
-
|
|
21551
|
-
|
|
21552
|
-
|
|
21553
|
-
|
|
21554
|
-
|
|
21555
|
-
|
|
21556
|
-
|
|
21557
|
-
"-n",
|
|
21558
|
-
K8S_NAMESPACE2,
|
|
21559
|
-
"-o",
|
|
21560
|
-
"json"
|
|
21561
|
-
],
|
|
21562
|
-
{ timeout: 15e3 }
|
|
21563
|
-
);
|
|
21564
|
-
if (!result.ok) {
|
|
21565
|
-
return `Secret "${HOST_CP_SECRET_NAME}" not found in namespace "${K8S_NAMESPACE2}".
|
|
21566
|
-
Create it first: kubectl --context ${context} apply -f <your-secret.yaml>
|
|
21567
|
-
${result.stderr.split("\n")[0] ?? ""}`;
|
|
21568
|
-
}
|
|
21569
|
-
let secretJson;
|
|
21570
|
-
try {
|
|
21571
|
-
secretJson = JSON.parse(result.stdout);
|
|
21572
|
-
} catch {
|
|
21573
|
-
return `Failed to parse Secret JSON: ${result.stdout.slice(0, 200)}`;
|
|
21574
|
-
}
|
|
21575
|
-
const data = secretJson.data ?? {};
|
|
21576
|
-
for (const key of REQUIRED_SECRET_KEYS) {
|
|
21577
|
-
if (!(key in data)) {
|
|
21578
|
-
return `Secret "${HOST_CP_SECRET_NAME}" is missing required key "${key}".
|
|
21579
|
-
Keys found: ${Object.keys(data).join(", ") || "(none)"}
|
|
21580
|
-
Add the key and re-run.`;
|
|
21443
|
+
function realTcpProbe(host, port2, timeoutMs) {
|
|
21444
|
+
return new Promise((resolve23) => {
|
|
21445
|
+
const socket = new net3.Socket();
|
|
21446
|
+
let done = false;
|
|
21447
|
+
function finish(result) {
|
|
21448
|
+
if (done) return;
|
|
21449
|
+
done = true;
|
|
21450
|
+
socket.destroy();
|
|
21451
|
+
resolve23(result);
|
|
21581
21452
|
}
|
|
21582
|
-
const
|
|
21583
|
-
|
|
21584
|
-
|
|
21585
|
-
|
|
21586
|
-
|
|
21453
|
+
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
21454
|
+
socket.once("connect", () => {
|
|
21455
|
+
clearTimeout(timer);
|
|
21456
|
+
finish(true);
|
|
21457
|
+
});
|
|
21458
|
+
socket.once("error", () => {
|
|
21459
|
+
clearTimeout(timer);
|
|
21460
|
+
finish(false);
|
|
21461
|
+
});
|
|
21462
|
+
socket.once("timeout", () => {
|
|
21463
|
+
clearTimeout(timer);
|
|
21464
|
+
finish(false);
|
|
21465
|
+
});
|
|
21466
|
+
socket.connect(port2, host);
|
|
21467
|
+
});
|
|
21468
|
+
}
|
|
21469
|
+
function acquireAdvisoryLock(lockPath) {
|
|
21470
|
+
const fd = fs29.openSync(lockPath, fs29.constants.O_CREAT | fs29.constants.O_EXCL | fs29.constants.O_WRONLY);
|
|
21471
|
+
fs29.closeSync(fd);
|
|
21472
|
+
return () => {
|
|
21473
|
+
try {
|
|
21474
|
+
fs29.unlinkSync(lockPath);
|
|
21475
|
+
} catch {
|
|
21587
21476
|
}
|
|
21477
|
+
};
|
|
21478
|
+
}
|
|
21479
|
+
function pidFilePath(deps) {
|
|
21480
|
+
const stateDir = deps.stateDir ?? OLAM_STATE_DIR;
|
|
21481
|
+
return path31.join(stateDir, "port-forward.pid");
|
|
21482
|
+
}
|
|
21483
|
+
function lockFilePath(deps) {
|
|
21484
|
+
const stateDir = deps.stateDir ?? OLAM_STATE_DIR;
|
|
21485
|
+
return path31.join(stateDir, "port-forward.lock");
|
|
21486
|
+
}
|
|
21487
|
+
function writePidFile(pidPath2, pid) {
|
|
21488
|
+
const dir = path31.dirname(pidPath2);
|
|
21489
|
+
if (!fs29.existsSync(dir)) {
|
|
21490
|
+
fs29.mkdirSync(dir, { recursive: true });
|
|
21588
21491
|
}
|
|
21589
|
-
|
|
21492
|
+
const tmp = `${pidPath2}.tmp.${process.pid}`;
|
|
21493
|
+
fs29.writeFileSync(tmp, String(pid) + "\n", { encoding: "utf8", mode: 384 });
|
|
21494
|
+
fs29.renameSync(tmp, pidPath2);
|
|
21590
21495
|
}
|
|
21591
|
-
async function
|
|
21592
|
-
const
|
|
21593
|
-
const
|
|
21594
|
-
const
|
|
21595
|
-
|
|
21496
|
+
async function spawnPortForward(context, namespace, target, localPort = PORT_FORWARD_PORT, remotePort = PORT_FORWARD_PORT, deps = {}) {
|
|
21497
|
+
const lockPath = lockFilePath(deps);
|
|
21498
|
+
const pidPath2 = pidFilePath(deps);
|
|
21499
|
+
const spawnImpl = deps.spawnImpl ?? spawn5;
|
|
21500
|
+
const stateDir = deps.stateDir ?? OLAM_STATE_DIR;
|
|
21501
|
+
if (!fs29.existsSync(stateDir)) {
|
|
21502
|
+
fs29.mkdirSync(stateDir, { recursive: true });
|
|
21503
|
+
}
|
|
21504
|
+
let releaseLock2 = null;
|
|
21596
21505
|
try {
|
|
21597
|
-
|
|
21506
|
+
releaseLock2 = acquireAdvisoryLock(lockPath);
|
|
21598
21507
|
} catch (err) {
|
|
21599
|
-
|
|
21508
|
+
const code = err.code;
|
|
21509
|
+
if (code === "EEXIST") {
|
|
21510
|
+
return { spawned: false, reason: "lock-held" };
|
|
21511
|
+
}
|
|
21512
|
+
throw err;
|
|
21600
21513
|
}
|
|
21601
|
-
let parsed;
|
|
21602
21514
|
try {
|
|
21603
|
-
|
|
21604
|
-
|
|
21605
|
-
|
|
21515
|
+
const live = await probePortForwardLiveness(deps);
|
|
21516
|
+
if (live) {
|
|
21517
|
+
return { spawned: false, reason: "live" };
|
|
21518
|
+
}
|
|
21519
|
+
const child = spawnImpl(
|
|
21520
|
+
"kubectl",
|
|
21521
|
+
[
|
|
21522
|
+
"--context",
|
|
21523
|
+
context,
|
|
21524
|
+
"port-forward",
|
|
21525
|
+
"-n",
|
|
21526
|
+
namespace,
|
|
21527
|
+
target,
|
|
21528
|
+
`${localPort}:${remotePort}`
|
|
21529
|
+
],
|
|
21530
|
+
{
|
|
21531
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
21532
|
+
detached: true
|
|
21533
|
+
}
|
|
21534
|
+
);
|
|
21535
|
+
if (typeof child.pid !== "number" || child.pid <= 0) {
|
|
21536
|
+
return { spawned: false, reason: "lock-held" };
|
|
21537
|
+
}
|
|
21538
|
+
writePidFile(pidPath2, child.pid);
|
|
21539
|
+
child.unref();
|
|
21540
|
+
child.once("exit", () => {
|
|
21541
|
+
try {
|
|
21542
|
+
fs29.unlinkSync(pidPath2);
|
|
21543
|
+
} catch {
|
|
21544
|
+
}
|
|
21545
|
+
});
|
|
21546
|
+
return { spawned: true, pid: child.pid };
|
|
21547
|
+
} finally {
|
|
21548
|
+
releaseLock2();
|
|
21606
21549
|
}
|
|
21607
|
-
|
|
21608
|
-
|
|
21609
|
-
|
|
21550
|
+
}
|
|
21551
|
+
function peripheralPidPath(name, stateDir) {
|
|
21552
|
+
return path31.join(stateDir, `port-forward-${name}.pid`);
|
|
21553
|
+
}
|
|
21554
|
+
function peripheralLockPath(name, stateDir) {
|
|
21555
|
+
return path31.join(stateDir, `port-forward-${name}.lock`);
|
|
21556
|
+
}
|
|
21557
|
+
async function spawnPeripheralPortForward(peripheral, context, namespace, deps = {}) {
|
|
21558
|
+
const stateDir = deps.stateDir ?? OLAM_STATE_DIR;
|
|
21559
|
+
const spawnImpl = deps.spawnImpl ?? spawn5;
|
|
21560
|
+
const tcpProbeImpl = deps.tcpProbeImpl ?? realTcpProbe;
|
|
21561
|
+
if (!fs29.existsSync(stateDir)) {
|
|
21562
|
+
fs29.mkdirSync(stateDir, { recursive: true });
|
|
21610
21563
|
}
|
|
21611
|
-
|
|
21612
|
-
const
|
|
21613
|
-
|
|
21614
|
-
|
|
21615
|
-
|
|
21616
|
-
)
|
|
21617
|
-
|
|
21618
|
-
|
|
21564
|
+
const lockPath = peripheralLockPath(peripheral.name, stateDir);
|
|
21565
|
+
const pidPath2 = peripheralPidPath(peripheral.name, stateDir);
|
|
21566
|
+
let releaseLock2 = null;
|
|
21567
|
+
try {
|
|
21568
|
+
releaseLock2 = acquireAdvisoryLock(lockPath);
|
|
21569
|
+
} catch (err) {
|
|
21570
|
+
const code = err.code;
|
|
21571
|
+
if (code === "EEXIST") {
|
|
21572
|
+
return false;
|
|
21573
|
+
}
|
|
21574
|
+
throw err;
|
|
21619
21575
|
}
|
|
21620
|
-
|
|
21621
|
-
|
|
21622
|
-
|
|
21623
|
-
|
|
21624
|
-
|
|
21625
|
-
const
|
|
21576
|
+
try {
|
|
21577
|
+
const alive = await tcpProbeImpl("127.0.0.1", peripheral.port, TCP_PROBE_TIMEOUT_MS);
|
|
21578
|
+
if (alive) {
|
|
21579
|
+
return false;
|
|
21580
|
+
}
|
|
21581
|
+
const child = spawnImpl(
|
|
21582
|
+
"kubectl",
|
|
21626
21583
|
[
|
|
21627
21584
|
"--context",
|
|
21628
21585
|
context,
|
|
21629
|
-
"
|
|
21630
|
-
"secret",
|
|
21631
|
-
secretName,
|
|
21586
|
+
"port-forward",
|
|
21632
21587
|
"-n",
|
|
21633
|
-
|
|
21634
|
-
|
|
21635
|
-
|
|
21588
|
+
namespace,
|
|
21589
|
+
`service/${peripheral.k8sServiceName}`,
|
|
21590
|
+
`${peripheral.port}:${peripheral.port}`
|
|
21636
21591
|
],
|
|
21637
|
-
{
|
|
21592
|
+
{
|
|
21593
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
21594
|
+
detached: true
|
|
21595
|
+
}
|
|
21638
21596
|
);
|
|
21639
|
-
if (
|
|
21640
|
-
return
|
|
21641
|
-
Create it first: kubectl --context ${context} apply -f <your-${name}-secret.yaml>
|
|
21642
|
-
${result.stderr.split("\n")[0] ?? ""}`;
|
|
21643
|
-
}
|
|
21644
|
-
let secretJson;
|
|
21645
|
-
try {
|
|
21646
|
-
secretJson = JSON.parse(result.stdout);
|
|
21647
|
-
} catch {
|
|
21648
|
-
return `Failed to parse Secret JSON for "${secretName}" (${name}): ${result.stdout.slice(0, 200)}`;
|
|
21597
|
+
if (typeof child.pid !== "number" || child.pid <= 0) {
|
|
21598
|
+
return false;
|
|
21649
21599
|
}
|
|
21650
|
-
|
|
21651
|
-
|
|
21652
|
-
|
|
21653
|
-
|
|
21654
|
-
|
|
21655
|
-
|
|
21656
|
-
}
|
|
21657
|
-
const b64 = data[key] ?? "";
|
|
21658
|
-
const decoded = Buffer.from(b64, "base64").toString("utf8");
|
|
21659
|
-
if (decoded.startsWith("REPLACE_ME_")) {
|
|
21660
|
-
return `Peripheral Secret "${secretName}" (${name}) key "${key}" still holds its placeholder value.
|
|
21661
|
-
Replace the placeholder with a real value, then re-run.`;
|
|
21600
|
+
writePidFile(pidPath2, child.pid);
|
|
21601
|
+
child.unref();
|
|
21602
|
+
child.once("exit", () => {
|
|
21603
|
+
try {
|
|
21604
|
+
fs29.unlinkSync(pidPath2);
|
|
21605
|
+
} catch {
|
|
21662
21606
|
}
|
|
21663
|
-
}
|
|
21607
|
+
});
|
|
21608
|
+
return true;
|
|
21609
|
+
} finally {
|
|
21610
|
+
releaseLock2();
|
|
21664
21611
|
}
|
|
21665
|
-
return null;
|
|
21666
21612
|
}
|
|
21667
|
-
async function
|
|
21668
|
-
|
|
21669
|
-
|
|
21670
|
-
[
|
|
21671
|
-
"--context",
|
|
21672
|
-
context,
|
|
21673
|
-
"wait",
|
|
21674
|
-
"--for=condition=Available",
|
|
21675
|
-
"deployment/coredns",
|
|
21676
|
-
"-n",
|
|
21677
|
-
"kube-system",
|
|
21678
|
-
"--timeout=30s"
|
|
21679
|
-
],
|
|
21680
|
-
{ timeout: 35e3 }
|
|
21613
|
+
async function spawnAllPeripheralPortForwards(context = "olam", namespace = "olam", deps = {}) {
|
|
21614
|
+
await Promise.all(
|
|
21615
|
+
PERIPHERALS.map((p) => spawnPeripheralPortForward(p, context, namespace, deps))
|
|
21681
21616
|
);
|
|
21682
|
-
return result.ok;
|
|
21683
21617
|
}
|
|
21684
|
-
|
|
21685
|
-
|
|
21618
|
+
|
|
21619
|
+
// src/lib/instrumentation.ts
|
|
21620
|
+
var INSTRUMENTATION_SCHEMA = "olam.instr.v1";
|
|
21621
|
+
var INSTRUMENTATION_PREFIX = "OLAM_INSTR ";
|
|
21622
|
+
function nowIso(now) {
|
|
21623
|
+
const d = now ? now() : /* @__PURE__ */ new Date();
|
|
21624
|
+
return d.toISOString();
|
|
21625
|
+
}
|
|
21626
|
+
function resolveInstallId(opts) {
|
|
21686
21627
|
try {
|
|
21687
|
-
|
|
21688
|
-
|
|
21689
|
-
|
|
21690
|
-
const engine = res.headers.get("x-olam-engine") ?? res.headers.get("X-Olam-Engine");
|
|
21691
|
-
return { ok: engine === "kubernetes", engineHeader: engine };
|
|
21628
|
+
if (opts.installIdProvider) return opts.installIdProvider();
|
|
21629
|
+
const cfg = readConfig();
|
|
21630
|
+
return cfg.install_id;
|
|
21692
21631
|
} catch {
|
|
21693
|
-
return
|
|
21632
|
+
return "unknown";
|
|
21694
21633
|
}
|
|
21695
21634
|
}
|
|
21696
|
-
|
|
21697
|
-
|
|
21698
|
-
|
|
21699
|
-
|
|
21700
|
-
|
|
21701
|
-
{
|
|
21702
|
-
|
|
21703
|
-
const seedResult = seedImpl(opts.forceRefreshManifests === true, deps.seedManifestsDeps ?? {});
|
|
21704
|
-
if (seedResult.seeded) {
|
|
21705
|
-
stdout.write(`${pc9.dim("[seed]")} ${seedResult.message}
|
|
21706
|
-
`);
|
|
21707
|
-
} else if (!seedResult.skipped) {
|
|
21708
|
-
stderr.write(`${pc9.red("error:")} Manifest seed failed: ${seedResult.error}
|
|
21709
|
-
`);
|
|
21710
|
-
return { exitCode: 1, summary: "manifest seed failed" };
|
|
21711
|
-
}
|
|
21712
|
-
}
|
|
21713
|
-
{
|
|
21714
|
-
const managedProvider = await detectManagedK8sProvider(deps);
|
|
21715
|
-
if (managedProvider !== null) {
|
|
21635
|
+
function writeLine(stderr, payload) {
|
|
21636
|
+
try {
|
|
21637
|
+
const line = `${INSTRUMENTATION_PREFIX}${JSON.stringify(payload)}
|
|
21638
|
+
`;
|
|
21639
|
+
stderr.write(line);
|
|
21640
|
+
} catch (err) {
|
|
21641
|
+
try {
|
|
21716
21642
|
stderr.write(
|
|
21717
|
-
|
|
21718
|
-
Detected managed-k8s context: ${managedProvider}
|
|
21719
|
-
hostPath /var/run/docker.sock is not accessible on managed clusters \u2014 the docker
|
|
21720
|
-
socket does not reach the operator's host daemon on cloud-managed nodes.
|
|
21721
|
-
See Decision #18 and docs/operator/kubernetes-substrate-beta.md
|
|
21722
|
-
for supported cluster types and the active retraction of managed-k8s support.
|
|
21643
|
+
`[WARN] olam instrumentation emit failed: ${err instanceof Error ? err.message : String(err)}
|
|
21723
21644
|
`
|
|
21724
21645
|
);
|
|
21725
|
-
|
|
21646
|
+
} catch {
|
|
21726
21647
|
}
|
|
21727
21648
|
}
|
|
21728
|
-
|
|
21729
|
-
|
|
21730
|
-
|
|
21731
|
-
|
|
21732
|
-
|
|
21733
|
-
|
|
21734
|
-
|
|
21735
|
-
|
|
21736
|
-
|
|
21737
|
-
|
|
21738
|
-
|
|
21739
|
-
|
|
21740
|
-
|
|
21741
|
-
|
|
21742
|
-
|
|
21743
|
-
|
|
21744
|
-
|
|
21745
|
-
|
|
21649
|
+
}
|
|
21650
|
+
function emitSubstrateSet(payload, opts = {}) {
|
|
21651
|
+
const event = {
|
|
21652
|
+
schema: INSTRUMENTATION_SCHEMA,
|
|
21653
|
+
event: "substrate.set",
|
|
21654
|
+
install_id: resolveInstallId(opts),
|
|
21655
|
+
ts: nowIso(opts.now),
|
|
21656
|
+
substrate: payload.substrate,
|
|
21657
|
+
...payload.flavor !== void 0 ? { flavor: payload.flavor } : {}
|
|
21658
|
+
};
|
|
21659
|
+
writeLine(opts.stderr ?? process.stderr, event);
|
|
21660
|
+
}
|
|
21661
|
+
function emitUpgradeComplete(payload, opts = {}) {
|
|
21662
|
+
const event = {
|
|
21663
|
+
schema: INSTRUMENTATION_SCHEMA,
|
|
21664
|
+
event: "upgrade.complete",
|
|
21665
|
+
install_id: resolveInstallId(opts),
|
|
21666
|
+
ts: nowIso(opts.now),
|
|
21667
|
+
substrate: payload.substrate,
|
|
21668
|
+
duration_ms: payload.duration_ms,
|
|
21669
|
+
...payload.flavor !== void 0 ? { flavor: payload.flavor } : {}
|
|
21670
|
+
};
|
|
21671
|
+
writeLine(opts.stderr ?? process.stderr, event);
|
|
21672
|
+
}
|
|
21673
|
+
|
|
21674
|
+
// src/lib/manifest-refresh.ts
|
|
21675
|
+
import * as fs30 from "node:fs";
|
|
21676
|
+
import * as path32 from "node:path";
|
|
21677
|
+
init_install_root();
|
|
21678
|
+
var MANIFEST_REFRESH_AUDIT_LOG = path32.join(OLAM_STATE_DIR, "manifest-refresh-audit.jsonl");
|
|
21679
|
+
var SECURITY_SENSITIVE_FIELDS = [
|
|
21680
|
+
"securityContext",
|
|
21681
|
+
"resources.limits",
|
|
21682
|
+
"capabilities",
|
|
21683
|
+
"readOnlyRootFilesystem",
|
|
21684
|
+
"rules"
|
|
21685
|
+
// RBAC Role rules
|
|
21686
|
+
];
|
|
21687
|
+
function seedManifestsFromBundle(forceRefresh, deps = {}) {
|
|
21688
|
+
const existsSyncImpl = deps.existsSync ?? fs30.existsSync;
|
|
21689
|
+
const cpSyncImpl = deps.cpSync ?? fs30.cpSync;
|
|
21690
|
+
const mkdirSyncImpl = deps.mkdirSync ?? fs30.mkdirSync;
|
|
21691
|
+
const rootDir = deps.installRootDir ?? installRoot();
|
|
21692
|
+
const sourcePath = path32.join(rootDir, "host-cp", "k8s", "manifests");
|
|
21693
|
+
const OLAM_HOME3 = path32.join(
|
|
21694
|
+
process.env.HOME ?? process.env.USERPROFILE ?? "/tmp",
|
|
21695
|
+
".olam"
|
|
21696
|
+
);
|
|
21697
|
+
const targetPath = deps.targetManifestsDir ?? path32.join(OLAM_HOME3, "k8s", "manifests");
|
|
21698
|
+
if (!forceRefresh && existsSyncImpl(targetPath)) {
|
|
21699
|
+
return {
|
|
21700
|
+
seeded: false,
|
|
21701
|
+
skipped: true,
|
|
21702
|
+
reason: `~/.olam/k8s/manifests/ already exists \u2014 skipping seed (use --force-refresh-manifests to overwrite)`
|
|
21703
|
+
};
|
|
21746
21704
|
}
|
|
21747
|
-
|
|
21748
|
-
|
|
21749
|
-
|
|
21750
|
-
|
|
21751
|
-
|
|
21752
|
-
|
|
21753
|
-
|
|
21754
|
-
`${pc9.red("error:")} kubectl cluster-info failed (5s timeout).
|
|
21755
|
-
Ensure your kubectl context is correct and the cluster is reachable.
|
|
21756
|
-
Set OLAM_K8S_CONTEXT_ACK=<context-name> to your active kubectl context.
|
|
21757
|
-
`
|
|
21758
|
-
);
|
|
21759
|
-
return { exitCode: 1, summary: "kubernetes api not reachable" };
|
|
21705
|
+
if (!existsSyncImpl(sourcePath)) {
|
|
21706
|
+
return {
|
|
21707
|
+
seeded: false,
|
|
21708
|
+
skipped: true,
|
|
21709
|
+
reason: `Bundled manifests not found at ${sourcePath} \u2014 skipping seed (dev/monorepo context).
|
|
21710
|
+
If running from a published install, re-run npm install to restore the bundle.`
|
|
21711
|
+
};
|
|
21760
21712
|
}
|
|
21761
|
-
|
|
21762
|
-
|
|
21763
|
-
|
|
21764
|
-
|
|
21765
|
-
|
|
21766
|
-
|
|
21767
|
-
|
|
21768
|
-
|
|
21769
|
-
|
|
21770
|
-
See GATES.md G-001 for the context-allowlist requirement.
|
|
21771
|
-
`
|
|
21772
|
-
);
|
|
21773
|
-
return { exitCode: 1, summary: "context ack not set" };
|
|
21713
|
+
try {
|
|
21714
|
+
mkdirSyncImpl(path32.dirname(targetPath), { recursive: true });
|
|
21715
|
+
cpSyncImpl(sourcePath, targetPath, { recursive: true });
|
|
21716
|
+
} catch (err) {
|
|
21717
|
+
return {
|
|
21718
|
+
seeded: false,
|
|
21719
|
+
skipped: false,
|
|
21720
|
+
error: `Failed to seed manifests from ${sourcePath} to ${targetPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
21721
|
+
};
|
|
21774
21722
|
}
|
|
21775
|
-
|
|
21776
|
-
|
|
21777
|
-
|
|
21778
|
-
|
|
21779
|
-
|
|
21780
|
-
|
|
21781
|
-
|
|
21782
|
-
|
|
21783
|
-
|
|
21784
|
-
|
|
21785
|
-
|
|
21786
|
-
|
|
21787
|
-
|
|
21788
|
-
|
|
21789
|
-
if (!refreshResult.ok) {
|
|
21790
|
-
step14Spinner.fail("Manifest refresh refused");
|
|
21791
|
-
stderr.write(`${pc9.red("error:")} ${refreshResult.message}
|
|
21792
|
-
`);
|
|
21793
|
-
return { exitCode: 1, summary: "manifest refresh refused" };
|
|
21794
|
-
}
|
|
21795
|
-
step14Spinner.succeed(refreshResult.message);
|
|
21723
|
+
return {
|
|
21724
|
+
seeded: true,
|
|
21725
|
+
source: sourcePath,
|
|
21726
|
+
target: targetPath,
|
|
21727
|
+
message: forceRefresh ? `Manifests force-refreshed from ${sourcePath} \u2192 ${targetPath}` : `Manifests seeded from ${sourcePath} \u2192 ${targetPath}`
|
|
21728
|
+
};
|
|
21729
|
+
}
|
|
21730
|
+
function extractField(obj, fieldKey) {
|
|
21731
|
+
if (typeof obj !== "object" || obj === null) return "";
|
|
21732
|
+
const keys = fieldKey.split(".");
|
|
21733
|
+
let current = obj;
|
|
21734
|
+
for (const k of keys) {
|
|
21735
|
+
if (typeof current !== "object" || current === null) return "";
|
|
21736
|
+
current = current[k];
|
|
21796
21737
|
}
|
|
21797
|
-
|
|
21798
|
-
|
|
21799
|
-
|
|
21800
|
-
|
|
21801
|
-
|
|
21802
|
-
|
|
21803
|
-
|
|
21738
|
+
if (current === void 0) return "";
|
|
21739
|
+
return JSON.stringify(current);
|
|
21740
|
+
}
|
|
21741
|
+
function extractSecurityFieldsFromParsed(parsed) {
|
|
21742
|
+
const result = {};
|
|
21743
|
+
for (const field of SECURITY_SENSITIVE_FIELDS) {
|
|
21744
|
+
result[field] = extractField(parsed, field);
|
|
21804
21745
|
}
|
|
21805
|
-
|
|
21806
|
-
|
|
21807
|
-
|
|
21808
|
-
|
|
21809
|
-
|
|
21810
|
-
|
|
21811
|
-
|
|
21812
|
-
|
|
21813
|
-
|
|
21814
|
-
);
|
|
21815
|
-
const step25Spinner = ora3("Patching ConfigMap with K8s DNS URLs (C1/D4)").start();
|
|
21816
|
-
const configMapError = await applyConfigMapSubstitution(pinnedContext, manifestsDir, deps);
|
|
21817
|
-
if (configMapError !== null) {
|
|
21818
|
-
step25Spinner.fail("ConfigMap substitution failed");
|
|
21819
|
-
stderr.write(`${pc9.red("error:")} ${configMapError}
|
|
21820
|
-
`);
|
|
21821
|
-
return { exitCode: 1, summary: "configmap substitution failed" };
|
|
21746
|
+
return result;
|
|
21747
|
+
}
|
|
21748
|
+
function diffManifestSecurityFields(oldContent, newContent) {
|
|
21749
|
+
let oldParsed;
|
|
21750
|
+
let newParsed;
|
|
21751
|
+
try {
|
|
21752
|
+
oldParsed = JSON.parse(oldContent);
|
|
21753
|
+
} catch {
|
|
21754
|
+
oldParsed = { _raw: oldContent };
|
|
21822
21755
|
}
|
|
21823
|
-
|
|
21824
|
-
|
|
21825
|
-
|
|
21826
|
-
|
|
21827
|
-
step26Spinner.fail("Peripheral Secret pre-check failed");
|
|
21828
|
-
stderr.write(`${pc9.red("error:")} ${peripheralSecretError}
|
|
21829
|
-
`);
|
|
21830
|
-
return { exitCode: 1, summary: "peripheral secret pre-check failed" };
|
|
21756
|
+
try {
|
|
21757
|
+
newParsed = JSON.parse(newContent);
|
|
21758
|
+
} catch {
|
|
21759
|
+
newParsed = { _raw: newContent };
|
|
21831
21760
|
}
|
|
21832
|
-
|
|
21833
|
-
const
|
|
21834
|
-
const
|
|
21835
|
-
|
|
21836
|
-
|
|
21837
|
-
|
|
21838
|
-
|
|
21761
|
+
const oldFields = extractSecurityFieldsFromParsed(oldParsed);
|
|
21762
|
+
const newFields = extractSecurityFieldsFromParsed(newParsed);
|
|
21763
|
+
const changedFields = [];
|
|
21764
|
+
for (const field of SECURITY_SENSITIVE_FIELDS) {
|
|
21765
|
+
if (oldFields[field] !== newFields[field]) {
|
|
21766
|
+
changedFields.push(field);
|
|
21767
|
+
}
|
|
21839
21768
|
}
|
|
21840
|
-
|
|
21841
|
-
|
|
21842
|
-
|
|
21843
|
-
|
|
21844
|
-
|
|
21845
|
-
|
|
21846
|
-
|
|
21847
|
-
|
|
21848
|
-
|
|
21849
|
-
|
|
21850
|
-
|
|
21851
|
-
|
|
21769
|
+
return {
|
|
21770
|
+
hasSecurityRegression: changedFields.length > 0,
|
|
21771
|
+
changedFields
|
|
21772
|
+
};
|
|
21773
|
+
}
|
|
21774
|
+
function appendAuditEntry(entry, auditLogPath, writeFileSyncImpl) {
|
|
21775
|
+
const line = JSON.stringify(entry) + "\n";
|
|
21776
|
+
try {
|
|
21777
|
+
writeFileSyncImpl(auditLogPath, line, {
|
|
21778
|
+
encoding: "utf8",
|
|
21779
|
+
flag: "a",
|
|
21780
|
+
mode: 384
|
|
21781
|
+
});
|
|
21782
|
+
} catch (err) {
|
|
21783
|
+
throw new Error(`audit log unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
21784
|
+
}
|
|
21785
|
+
}
|
|
21786
|
+
async function runManifestRefresh(manifestsDir, acceptRegression, deps = {}, peripheral) {
|
|
21787
|
+
const auditLogPath = deps.auditLogPath ?? MANIFEST_REFRESH_AUDIT_LOG;
|
|
21788
|
+
const readdirSync25 = deps.readdirSync ?? fs30.readdirSync;
|
|
21789
|
+
const readFileSync74 = deps.readFileSync ?? fs30.readFileSync;
|
|
21790
|
+
const writeFileSyncImpl = deps.writeFileSync ?? fs30.writeFileSync;
|
|
21791
|
+
const existsSync99 = deps.existsSync ?? fs30.existsSync;
|
|
21792
|
+
const now = deps.now ? deps.now() : /* @__PURE__ */ new Date();
|
|
21793
|
+
const targetDir = peripheral ? path32.join(manifestsDir, peripheral) : manifestsDir;
|
|
21794
|
+
if (!existsSync99(targetDir)) {
|
|
21795
|
+
return {
|
|
21796
|
+
ok: false,
|
|
21797
|
+
message: peripheral ? `peripheral manifests directory not found: ${targetDir}` : `manifests directory not found: ${targetDir}`
|
|
21798
|
+
};
|
|
21799
|
+
}
|
|
21800
|
+
let files;
|
|
21801
|
+
try {
|
|
21802
|
+
const entries = readdirSync25(targetDir, { withFileTypes: true });
|
|
21803
|
+
files = entries.filter((e) => e.isFile() && (e.name.endsWith(".yaml") || e.name.endsWith(".json"))).map((e) => e.name);
|
|
21804
|
+
} catch (err) {
|
|
21805
|
+
return {
|
|
21806
|
+
ok: false,
|
|
21807
|
+
message: `failed to read manifests dir: ${err instanceof Error ? err.message : String(err)}`
|
|
21808
|
+
};
|
|
21809
|
+
}
|
|
21810
|
+
const allChangedFields = [];
|
|
21811
|
+
for (const file of files) {
|
|
21812
|
+
const filePath = path32.join(targetDir, file);
|
|
21813
|
+
let content;
|
|
21814
|
+
try {
|
|
21815
|
+
content = readFileSync74(filePath, "utf8");
|
|
21816
|
+
} catch {
|
|
21817
|
+
continue;
|
|
21818
|
+
}
|
|
21819
|
+
const diff = diffManifestSecurityFields("{}", content);
|
|
21820
|
+
for (const f of diff.changedFields) {
|
|
21821
|
+
if (!allChangedFields.includes(f)) allChangedFields.push(f);
|
|
21822
|
+
}
|
|
21823
|
+
}
|
|
21824
|
+
const hasSecurityRegression = allChangedFields.length > 0;
|
|
21825
|
+
if (hasSecurityRegression && !acceptRegression) {
|
|
21826
|
+
return {
|
|
21827
|
+
ok: false,
|
|
21828
|
+
message: `manifest refresh refused: security-sensitive fields changed (${allChangedFields.join(", ")}).
|
|
21829
|
+
Re-run with --accept-security-regression to acknowledge and proceed.`
|
|
21830
|
+
};
|
|
21831
|
+
}
|
|
21832
|
+
const entry = {
|
|
21833
|
+
ts: now.toISOString(),
|
|
21834
|
+
manifests_path: targetDir,
|
|
21835
|
+
security_regression: hasSecurityRegression,
|
|
21836
|
+
changed_fields: allChangedFields,
|
|
21837
|
+
accepted: acceptRegression,
|
|
21838
|
+
operator_pid: process.pid,
|
|
21839
|
+
...peripheral !== void 0 ? { peripheral } : {}
|
|
21840
|
+
};
|
|
21841
|
+
try {
|
|
21842
|
+
appendAuditEntry(entry, auditLogPath, writeFileSyncImpl);
|
|
21843
|
+
} catch (err) {
|
|
21844
|
+
return {
|
|
21845
|
+
ok: false,
|
|
21846
|
+
message: err instanceof Error ? err.message : String(err)
|
|
21847
|
+
};
|
|
21848
|
+
}
|
|
21849
|
+
return {
|
|
21850
|
+
ok: true,
|
|
21851
|
+
message: hasSecurityRegression ? `Security-sensitive fields changed (${allChangedFields.join(", ")}); accepted via --accept-security-regression. Audit entry written.` : "Manifest refresh completed (no security-sensitive field changes). Audit entry written."
|
|
21852
|
+
};
|
|
21853
|
+
}
|
|
21854
|
+
|
|
21855
|
+
// src/lib/upgrade-kubernetes.ts
|
|
21856
|
+
var OLAM_K8S_MANIFESTS_DIR = path33.join(OLAM_HOME, "k8s", "manifests");
|
|
21857
|
+
var K8S_NAMESPACE2 = "olam";
|
|
21858
|
+
var HOST_CP_SECRET_NAME = "olam-host-cp-secret";
|
|
21859
|
+
var HOST_CP_DEPLOYMENT_NAME = "olam-host-cp";
|
|
21860
|
+
var PORT_FORWARD_TARGET = "service/olam-host-cp";
|
|
21861
|
+
var HOST_CP_HEALTH_URL = "http://127.0.0.1:19000/health";
|
|
21862
|
+
var SUBSTRATE_AUDIT_LOG = path33.join(OLAM_STATE_DIR, "substrate-audit.jsonl");
|
|
21863
|
+
var PLACEHOLDER_VALUES = /* @__PURE__ */ new Set(["OLAM_AUTH_SECRET", "GH_TOKEN"]);
|
|
21864
|
+
var REQUIRED_SECRET_KEYS = ["OLAM_AUTH_SECRET", "GH_TOKEN"];
|
|
21865
|
+
var PERIPHERAL_SECRETS = [
|
|
21866
|
+
{ name: "auth-service", secretName: "olam-auth-service-secret", keys: ["OLAM_AUTH_DB_SECRET"] },
|
|
21867
|
+
{ name: "mcp-auth-service", secretName: "olam-mcp-auth-service-secret", keys: ["OLAM_MCP_AUTH_JWT_SECRET"] },
|
|
21868
|
+
{ name: "kg-service", secretName: "olam-kg-service-secret", keys: ["OLAM_KG_BEARER_TOKEN"] },
|
|
21869
|
+
{ name: "memory-service", secretName: "olam-memory-service-secret", keys: ["OLAM_MEMORY_BEARER_SECRET"] }
|
|
21870
|
+
];
|
|
21871
|
+
var K8S_DNS_SUFFIX = "olam.svc.cluster.local";
|
|
21872
|
+
function buildK8sDnsUrl(k8sServiceName, port2) {
|
|
21873
|
+
return `http://${k8sServiceName}.${K8S_DNS_SUFFIX}:${port2}`;
|
|
21874
|
+
}
|
|
21875
|
+
function appendSubstrateAuditEntry(entry, stderr) {
|
|
21876
|
+
try {
|
|
21877
|
+
const line = JSON.stringify(entry) + "\n";
|
|
21878
|
+
const dir = path33.dirname(SUBSTRATE_AUDIT_LOG);
|
|
21879
|
+
if (!fs31.existsSync(dir)) fs31.mkdirSync(dir, { recursive: true });
|
|
21880
|
+
fs31.writeFileSync(SUBSTRATE_AUDIT_LOG, line, { encoding: "utf8", flag: "a", mode: 384 });
|
|
21881
|
+
} catch (err) {
|
|
21882
|
+
stderr.write(
|
|
21883
|
+
`${pc9.yellow("[warn]")} could not write substrate audit log: ${err instanceof Error ? err.message : String(err)}
|
|
21884
|
+
`
|
|
21885
|
+
);
|
|
21886
|
+
}
|
|
21887
|
+
}
|
|
21888
|
+
function isContextAllowed(context) {
|
|
21889
|
+
return typeof context === "string" && context.length > 0;
|
|
21890
|
+
}
|
|
21891
|
+
var MANAGED_K8S_URL_PATTERNS = [
|
|
21892
|
+
{ pattern: /\.amazonaws\.com(:\d+)?$/, provider: "EKS (Amazon)" },
|
|
21893
|
+
{ pattern: /\.googleapis\.com(:\d+)?$/, provider: "GKE (Google)" },
|
|
21894
|
+
{ pattern: /\.gke\.io(:\d+)?$/, provider: "GKE (Google)" },
|
|
21895
|
+
{ pattern: /\.azmk8s\.io(:\d+)?$/, provider: "AKS (Azure)" },
|
|
21896
|
+
{ pattern: /\.k8s\.ondigitalocean\.com(:\d+)?$/, provider: "DOKS (DigitalOcean)" },
|
|
21897
|
+
{ pattern: /\.civo\.com(:\d+)?$/, provider: "Civo" }
|
|
21898
|
+
];
|
|
21899
|
+
async function detectManagedK8sProvider(deps) {
|
|
21900
|
+
if (deps.getClusterServerUrlImpl) {
|
|
21901
|
+
const url3 = await deps.getClusterServerUrlImpl();
|
|
21902
|
+
if (!url3) return null;
|
|
21903
|
+
for (const { pattern, provider } of MANAGED_K8S_URL_PATTERNS) {
|
|
21904
|
+
if (pattern.test(url3)) return provider;
|
|
21905
|
+
}
|
|
21906
|
+
return null;
|
|
21907
|
+
}
|
|
21908
|
+
const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
|
|
21909
|
+
const result = await wrap(
|
|
21910
|
+
["config", "view", "--minify", "-o", "jsonpath={.clusters[0].cluster.server}"],
|
|
21911
|
+
{ timeout: 5e3 }
|
|
21912
|
+
);
|
|
21913
|
+
if (!result.ok || !result.stdout.trim()) return null;
|
|
21914
|
+
const url2 = result.stdout.trim();
|
|
21915
|
+
for (const { pattern, provider } of MANAGED_K8S_URL_PATTERNS) {
|
|
21916
|
+
if (pattern.test(url2)) return provider;
|
|
21917
|
+
}
|
|
21918
|
+
return null;
|
|
21919
|
+
}
|
|
21920
|
+
async function checkDockerSocketAccessible(context, deps) {
|
|
21921
|
+
if (deps.checkDockerSocketImpl) {
|
|
21922
|
+
return deps.checkDockerSocketImpl(context);
|
|
21923
|
+
}
|
|
21924
|
+
const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
|
|
21925
|
+
const result = await wrap(
|
|
21926
|
+
[
|
|
21927
|
+
"--context",
|
|
21928
|
+
context,
|
|
21929
|
+
"exec",
|
|
21930
|
+
"deploy/olam-host-cp",
|
|
21931
|
+
"-n",
|
|
21932
|
+
"olam",
|
|
21933
|
+
"--",
|
|
21934
|
+
"test",
|
|
21935
|
+
"-S",
|
|
21936
|
+
"/var/run/docker.sock"
|
|
21937
|
+
],
|
|
21938
|
+
{ timeout: 1e4 }
|
|
21939
|
+
);
|
|
21940
|
+
return result.ok;
|
|
21941
|
+
}
|
|
21942
|
+
async function probeKubernetesApiReachable(context, deps) {
|
|
21943
|
+
const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
|
|
21944
|
+
const result = await wrap(
|
|
21945
|
+
["--context", context, "cluster-info"],
|
|
21946
|
+
{ timeout: 5e3 }
|
|
21947
|
+
);
|
|
21948
|
+
return result.ok;
|
|
21949
|
+
}
|
|
21950
|
+
async function checkSecretPreCondition(context, deps) {
|
|
21951
|
+
const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
|
|
21952
|
+
const result = await wrap(
|
|
21953
|
+
[
|
|
21954
|
+
"--context",
|
|
21955
|
+
context,
|
|
21956
|
+
"get",
|
|
21957
|
+
"secret",
|
|
21958
|
+
HOST_CP_SECRET_NAME,
|
|
21959
|
+
"-n",
|
|
21960
|
+
K8S_NAMESPACE2,
|
|
21961
|
+
"-o",
|
|
21962
|
+
"json"
|
|
21963
|
+
],
|
|
21964
|
+
{ timeout: 15e3 }
|
|
21965
|
+
);
|
|
21966
|
+
if (!result.ok) {
|
|
21967
|
+
return `Secret "${HOST_CP_SECRET_NAME}" not found in namespace "${K8S_NAMESPACE2}".
|
|
21968
|
+
Create it first: kubectl --context ${context} apply -f <your-secret.yaml>
|
|
21969
|
+
${result.stderr.split("\n")[0] ?? ""}`;
|
|
21970
|
+
}
|
|
21971
|
+
let secretJson;
|
|
21972
|
+
try {
|
|
21973
|
+
secretJson = JSON.parse(result.stdout);
|
|
21974
|
+
} catch {
|
|
21975
|
+
return `Failed to parse Secret JSON: ${result.stdout.slice(0, 200)}`;
|
|
21976
|
+
}
|
|
21977
|
+
const data = secretJson.data ?? {};
|
|
21978
|
+
for (const key of REQUIRED_SECRET_KEYS) {
|
|
21979
|
+
if (!(key in data)) {
|
|
21980
|
+
return `Secret "${HOST_CP_SECRET_NAME}" is missing required key "${key}".
|
|
21981
|
+
Keys found: ${Object.keys(data).join(", ") || "(none)"}
|
|
21982
|
+
Add the key and re-run.`;
|
|
21983
|
+
}
|
|
21984
|
+
const b64 = data[key] ?? "";
|
|
21985
|
+
const decoded = Buffer.from(b64, "base64").toString("utf8");
|
|
21986
|
+
if (PLACEHOLDER_VALUES.has(decoded)) {
|
|
21987
|
+
return `Secret "${HOST_CP_SECRET_NAME}" key "${key}" still holds its placeholder value.
|
|
21988
|
+
Replace the placeholder with a real value, then re-run.`;
|
|
21989
|
+
}
|
|
21990
|
+
}
|
|
21991
|
+
return null;
|
|
21992
|
+
}
|
|
21993
|
+
async function applyConfigMapSubstitution(context, manifestsDir, deps) {
|
|
21994
|
+
const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
|
|
21995
|
+
const readFileSync74 = deps.readFileSyncImpl ?? fs31.readFileSync;
|
|
21996
|
+
const configMapPath = path33.join(manifestsDir, "30-configmap.yaml");
|
|
21997
|
+
let rawYaml;
|
|
21998
|
+
try {
|
|
21999
|
+
rawYaml = readFileSync74(configMapPath, "utf8");
|
|
22000
|
+
} catch (err) {
|
|
22001
|
+
return `Failed to read ConfigMap at ${configMapPath}: ${err instanceof Error ? err.message : String(err)}`;
|
|
22002
|
+
}
|
|
22003
|
+
let parsed;
|
|
22004
|
+
try {
|
|
22005
|
+
parsed = yamlParse(rawYaml);
|
|
22006
|
+
} catch (err) {
|
|
22007
|
+
return `Failed to parse ConfigMap YAML at ${configMapPath}: ${err instanceof Error ? err.message : String(err)}`;
|
|
22008
|
+
}
|
|
22009
|
+
const data = parsed["data"] ?? {};
|
|
22010
|
+
for (const peripheral of PERIPHERALS) {
|
|
22011
|
+
data[peripheral.configMapKeyInHostCp] = buildK8sDnsUrl(peripheral.k8sServiceName, peripheral.port);
|
|
22012
|
+
}
|
|
22013
|
+
parsed["data"] = data;
|
|
22014
|
+
const patchedYaml = yamlStringify(parsed);
|
|
22015
|
+
const result = await wrap(
|
|
22016
|
+
["--context", context, "apply", "-f", "-"],
|
|
22017
|
+
{ timeout: 3e4, stdin: patchedYaml }
|
|
22018
|
+
);
|
|
22019
|
+
if (!result.ok) {
|
|
22020
|
+
return `kubectl apply (ConfigMap substitution) failed: ${result.stderr.split("\n")[0] ?? ""}`;
|
|
22021
|
+
}
|
|
22022
|
+
return null;
|
|
22023
|
+
}
|
|
22024
|
+
async function checkPeripheralSecrets(context, deps) {
|
|
22025
|
+
const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
|
|
22026
|
+
for (const { name, secretName, keys } of PERIPHERAL_SECRETS) {
|
|
22027
|
+
const result = await wrap(
|
|
22028
|
+
[
|
|
22029
|
+
"--context",
|
|
22030
|
+
context,
|
|
22031
|
+
"get",
|
|
22032
|
+
"secret",
|
|
22033
|
+
secretName,
|
|
22034
|
+
"-n",
|
|
22035
|
+
K8S_NAMESPACE2,
|
|
22036
|
+
"-o",
|
|
22037
|
+
"json"
|
|
22038
|
+
],
|
|
22039
|
+
{ timeout: 15e3 }
|
|
22040
|
+
);
|
|
22041
|
+
if (!result.ok) {
|
|
22042
|
+
return `Peripheral Secret "${secretName}" (${name}) not found in namespace "${K8S_NAMESPACE2}".
|
|
22043
|
+
Create it first: kubectl --context ${context} apply -f <your-${name}-secret.yaml>
|
|
22044
|
+
${result.stderr.split("\n")[0] ?? ""}`;
|
|
22045
|
+
}
|
|
22046
|
+
let secretJson;
|
|
22047
|
+
try {
|
|
22048
|
+
secretJson = JSON.parse(result.stdout);
|
|
22049
|
+
} catch {
|
|
22050
|
+
return `Failed to parse Secret JSON for "${secretName}" (${name}): ${result.stdout.slice(0, 200)}`;
|
|
22051
|
+
}
|
|
22052
|
+
const data = secretJson.data ?? {};
|
|
22053
|
+
for (const key of keys) {
|
|
22054
|
+
if (!(key in data)) {
|
|
22055
|
+
return `Peripheral Secret "${secretName}" (${name}) is missing required key "${key}".
|
|
22056
|
+
Keys found: ${Object.keys(data).join(", ") || "(none)"}
|
|
22057
|
+
Add the key and re-run.`;
|
|
22058
|
+
}
|
|
22059
|
+
const b64 = data[key] ?? "";
|
|
22060
|
+
const decoded = Buffer.from(b64, "base64").toString("utf8");
|
|
22061
|
+
if (decoded.startsWith("REPLACE_ME_")) {
|
|
22062
|
+
return `Peripheral Secret "${secretName}" (${name}) key "${key}" still holds its placeholder value.
|
|
22063
|
+
Replace the placeholder with a real value, then re-run.`;
|
|
22064
|
+
}
|
|
22065
|
+
}
|
|
22066
|
+
}
|
|
22067
|
+
return null;
|
|
22068
|
+
}
|
|
22069
|
+
async function waitForCoreDns(context, deps) {
|
|
22070
|
+
const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
|
|
22071
|
+
const result = await wrap(
|
|
22072
|
+
[
|
|
22073
|
+
"--context",
|
|
22074
|
+
context,
|
|
22075
|
+
"wait",
|
|
22076
|
+
"--for=condition=Available",
|
|
22077
|
+
"deployment/coredns",
|
|
22078
|
+
"-n",
|
|
22079
|
+
"kube-system",
|
|
22080
|
+
"--timeout=30s"
|
|
22081
|
+
],
|
|
22082
|
+
{ timeout: 35e3 }
|
|
22083
|
+
);
|
|
22084
|
+
return result.ok;
|
|
22085
|
+
}
|
|
22086
|
+
async function verifyHealthHeader(deps) {
|
|
22087
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
22088
|
+
try {
|
|
22089
|
+
const res = await fetchImpl(HOST_CP_HEALTH_URL, {
|
|
22090
|
+
signal: AbortSignal.timeout(5e3)
|
|
22091
|
+
});
|
|
22092
|
+
const engine = res.headers.get("x-olam-engine") ?? res.headers.get("X-Olam-Engine");
|
|
22093
|
+
return { ok: engine === "kubernetes", engineHeader: engine };
|
|
22094
|
+
} catch {
|
|
22095
|
+
return { ok: false, engineHeader: null };
|
|
22096
|
+
}
|
|
22097
|
+
}
|
|
22098
|
+
async function runUpgradeKubernetes(opts = {}, deps = {}) {
|
|
22099
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
22100
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
22101
|
+
const manifestsDir = deps.manifestsDir ?? OLAM_K8S_MANIFESTS_DIR;
|
|
22102
|
+
const startMs = Date.now();
|
|
22103
|
+
{
|
|
22104
|
+
const seedImpl = deps.seedManifestsImpl ?? seedManifestsFromBundle;
|
|
22105
|
+
const seedResult = seedImpl(opts.forceRefreshManifests === true, deps.seedManifestsDeps ?? {});
|
|
22106
|
+
if (seedResult.seeded) {
|
|
22107
|
+
stdout.write(`${pc9.dim("[seed]")} ${seedResult.message}
|
|
22108
|
+
`);
|
|
22109
|
+
} else if (!seedResult.skipped) {
|
|
22110
|
+
stderr.write(`${pc9.red("error:")} Manifest seed failed: ${seedResult.error}
|
|
22111
|
+
`);
|
|
22112
|
+
return { exitCode: 1, summary: "manifest seed failed" };
|
|
22113
|
+
}
|
|
22114
|
+
}
|
|
22115
|
+
{
|
|
22116
|
+
const managedProvider = await detectManagedK8sProvider(deps);
|
|
22117
|
+
if (managedProvider !== null) {
|
|
22118
|
+
stderr.write(
|
|
22119
|
+
`${pc9.red("error:")} olam upgrade supports local k3d/k3s clusters only.
|
|
22120
|
+
Detected managed-k8s context: ${managedProvider}
|
|
22121
|
+
hostPath /var/run/docker.sock is not accessible on managed clusters \u2014 the docker
|
|
22122
|
+
socket does not reach the operator's host daemon on cloud-managed nodes.
|
|
22123
|
+
See Decision #18 and docs/operator/kubernetes-substrate-beta.md
|
|
22124
|
+
for supported cluster types and the active retraction of managed-k8s support.
|
|
22125
|
+
`
|
|
22126
|
+
);
|
|
22127
|
+
return { exitCode: 1, summary: "managed-k8s context detected (Decision #18 retraction)" };
|
|
22128
|
+
}
|
|
22129
|
+
}
|
|
22130
|
+
{
|
|
22131
|
+
const preflightContext = process.env.OLAM_K8S_CONTEXT_ACK ?? "default";
|
|
22132
|
+
const socketAccessible = await checkDockerSocketAccessible(preflightContext, deps);
|
|
22133
|
+
if (!socketAccessible) {
|
|
22134
|
+
const clusterType = defaultDetectK8sClusterType(preflightContext);
|
|
22135
|
+
const remedyText = buildDockerSocketRemedy(clusterType).split("\n").map((line) => ` ${line}`).join("\n");
|
|
22136
|
+
stderr.write(
|
|
22137
|
+
`${pc9.yellow("[WARN]")} docker socket not accessible at /var/run/docker.sock inside the host-cp pod.
|
|
22138
|
+
This means the cluster was not created with the docker socket bind-mount,
|
|
22139
|
+
or host-cp is not yet deployed (first install \u2014 this warning is expected).
|
|
22140
|
+
For subsequent upgrades:
|
|
22141
|
+
${remedyText}
|
|
22142
|
+
Run olam doctor to check probe 28 (probeDockerSocketBindMount).
|
|
22143
|
+
`
|
|
22144
|
+
);
|
|
22145
|
+
}
|
|
22146
|
+
}
|
|
22147
|
+
const step0Spinner = ora3("Probing Kubernetes API reachability").start();
|
|
22148
|
+
const context = process.env.OLAM_K8S_CONTEXT_ACK ?? "";
|
|
22149
|
+
const probeContext = context.length > 0 ? context : "default";
|
|
22150
|
+
const reachable = await probeKubernetesApiReachable(probeContext, deps);
|
|
22151
|
+
if (!reachable) {
|
|
22152
|
+
step0Spinner.fail("Kubernetes API not reachable");
|
|
22153
|
+
stderr.write(
|
|
22154
|
+
`${pc9.red("error:")} kubectl cluster-info failed (5s timeout).
|
|
22155
|
+
Ensure your kubectl context is correct and the cluster is reachable.
|
|
22156
|
+
Set OLAM_K8S_CONTEXT_ACK=<context-name> to your active kubectl context.
|
|
22157
|
+
`
|
|
22158
|
+
);
|
|
22159
|
+
return { exitCode: 1, summary: "kubernetes api not reachable" };
|
|
22160
|
+
}
|
|
22161
|
+
step0Spinner.succeed("Kubernetes API reachable");
|
|
22162
|
+
const step1Spinner = ora3("Verifying kubectl context (D10)").start();
|
|
22163
|
+
const ackValue = process.env.OLAM_K8S_CONTEXT_ACK ?? "";
|
|
22164
|
+
if (ackValue.length === 0 || !isContextAllowed(ackValue)) {
|
|
22165
|
+
step1Spinner.fail("Context ACK missing or disallowed");
|
|
22166
|
+
stderr.write(
|
|
22167
|
+
`${pc9.red("error:")} OLAM_K8S_CONTEXT_ACK is not set or empty.
|
|
22168
|
+
Set it to your kubectl context name (byte-for-byte match):
|
|
22169
|
+
export OLAM_K8S_CONTEXT_ACK=<your-context-name>
|
|
22170
|
+
See GATES.md G-001 for the context-allowlist requirement.
|
|
22171
|
+
`
|
|
22172
|
+
);
|
|
22173
|
+
return { exitCode: 1, summary: "context ack not set" };
|
|
22174
|
+
}
|
|
22175
|
+
const pinnedContext = ackValue;
|
|
22176
|
+
process.stderr.write(
|
|
22177
|
+
`${pc9.yellow("[WARN]")} OLAM_K8S_UNSAFE_CONTEXT_ACK ctx=${pinnedContext}
|
|
22178
|
+
`
|
|
22179
|
+
);
|
|
22180
|
+
step1Spinner.succeed(`Context pinned: ${pc9.bold(pinnedContext)}`);
|
|
22181
|
+
if (opts.forceRefreshManifests) {
|
|
22182
|
+
const step14Spinner = ora3("Refreshing manifests (D14)").start();
|
|
22183
|
+
const refreshImpl = deps.manifestRefreshImpl ?? runManifestRefresh;
|
|
22184
|
+
const refreshResult = await refreshImpl(
|
|
22185
|
+
manifestsDir,
|
|
22186
|
+
opts.acceptSecurityRegression === true,
|
|
22187
|
+
{}
|
|
22188
|
+
);
|
|
22189
|
+
if (!refreshResult.ok) {
|
|
22190
|
+
step14Spinner.fail("Manifest refresh refused");
|
|
22191
|
+
stderr.write(`${pc9.red("error:")} ${refreshResult.message}
|
|
22192
|
+
`);
|
|
22193
|
+
return { exitCode: 1, summary: "manifest refresh refused" };
|
|
22194
|
+
}
|
|
22195
|
+
step14Spinner.succeed(refreshResult.message);
|
|
22196
|
+
}
|
|
22197
|
+
const step2Spinner = ora3(`Checking Secret ${HOST_CP_SECRET_NAME} (D12)`).start();
|
|
22198
|
+
const secretError = await checkSecretPreCondition(pinnedContext, deps);
|
|
22199
|
+
if (secretError !== null) {
|
|
22200
|
+
step2Spinner.fail("Secret pre-check failed");
|
|
22201
|
+
stderr.write(`${pc9.red("error:")} ${secretError}
|
|
22202
|
+
`);
|
|
22203
|
+
return { exitCode: 1, summary: "secret pre-check failed" };
|
|
22204
|
+
}
|
|
22205
|
+
step2Spinner.succeed(`Secret ${HOST_CP_SECRET_NAME} verified`);
|
|
22206
|
+
appendSubstrateAuditEntry(
|
|
22207
|
+
{
|
|
22208
|
+
ts: new Date(deps.nowImpl ? deps.nowImpl() : Date.now()).toISOString(),
|
|
22209
|
+
op: "upgrade",
|
|
22210
|
+
substrate: "kubernetes",
|
|
22211
|
+
phase2: { flag_removed: true }
|
|
22212
|
+
},
|
|
22213
|
+
stderr
|
|
22214
|
+
);
|
|
22215
|
+
const step26Spinner = ora3("Checking peripheral Secrets (C2)").start();
|
|
22216
|
+
const peripheralSecretError = await checkPeripheralSecrets(pinnedContext, deps);
|
|
22217
|
+
if (peripheralSecretError !== null) {
|
|
22218
|
+
step26Spinner.fail("Peripheral Secret pre-check failed");
|
|
22219
|
+
stderr.write(`${pc9.red("error:")} ${peripheralSecretError}
|
|
22220
|
+
`);
|
|
22221
|
+
return { exitCode: 1, summary: "peripheral secret pre-check failed" };
|
|
22222
|
+
}
|
|
22223
|
+
step26Spinner.succeed("All peripheral Secrets verified");
|
|
22224
|
+
const step27Spinner = ora3("Waiting for CoreDNS (C3, 30s timeout)").start();
|
|
22225
|
+
const coreDnsOk = await waitForCoreDns(pinnedContext, deps);
|
|
22226
|
+
if (!coreDnsOk) {
|
|
22227
|
+
step27Spinner.warn("CoreDNS not Available within 30s \u2014 continuing (DNS may be degraded)");
|
|
22228
|
+
} else {
|
|
22229
|
+
step27Spinner.succeed("CoreDNS Available");
|
|
22230
|
+
}
|
|
22231
|
+
const step3Spinner = ora3("Applying manifests").start();
|
|
22232
|
+
const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
|
|
22233
|
+
const applyResult = await wrap(
|
|
22234
|
+
["--context", pinnedContext, "apply", "-f", manifestsDir],
|
|
22235
|
+
{ timeout: 12e4 }
|
|
22236
|
+
);
|
|
22237
|
+
if (!applyResult.ok) {
|
|
22238
|
+
step3Spinner.fail("kubectl apply failed");
|
|
22239
|
+
stderr.write(
|
|
22240
|
+
`${pc9.red("error:")} kubectl apply failed (exit ${applyResult.exitCode}):
|
|
22241
|
+
${applyResult.stderr.split("\n").slice(0, 5).join("\n ")}
|
|
22242
|
+
`
|
|
21852
22243
|
);
|
|
21853
22244
|
return { exitCode: 1, summary: "kubectl apply failed" };
|
|
21854
22245
|
}
|
|
21855
22246
|
const peripheralNames = [...PERIPHERALS.map((p) => p.name)].sort();
|
|
21856
22247
|
for (const name of peripheralNames) {
|
|
21857
|
-
const peripheralManifestsDir =
|
|
22248
|
+
const peripheralManifestsDir = path33.join(manifestsDir, name);
|
|
21858
22249
|
const peripheralApplyResult = await wrap(
|
|
21859
22250
|
["--context", pinnedContext, "apply", "-f", peripheralManifestsDir],
|
|
21860
22251
|
{ timeout: 12e4 }
|
|
@@ -21870,6 +22261,15 @@ async function runUpgradeKubernetes(opts = {}, deps = {}) {
|
|
|
21870
22261
|
}
|
|
21871
22262
|
}
|
|
21872
22263
|
step3Spinner.succeed("All manifests applied (host-cp + 4 peripherals)");
|
|
22264
|
+
const step35Spinner = ora3("Re-applying patched ConfigMap with K8s DNS URLs (C1/D4 \u2014 B7 fix)").start();
|
|
22265
|
+
const configMapError = await applyConfigMapSubstitution(pinnedContext, manifestsDir, deps);
|
|
22266
|
+
if (configMapError !== null) {
|
|
22267
|
+
step35Spinner.fail("ConfigMap substitution failed");
|
|
22268
|
+
stderr.write(`${pc9.red("error:")} ${configMapError}
|
|
22269
|
+
`);
|
|
22270
|
+
return { exitCode: 1, summary: "configmap substitution failed" };
|
|
22271
|
+
}
|
|
22272
|
+
step35Spinner.succeed("ConfigMap patched with K8s DNS URLs (survives bulk apply)");
|
|
21873
22273
|
const step4Spinner = ora3("Waiting for rollout (all 5 deployments, 90s each)").start();
|
|
21874
22274
|
const deploymentNames = [
|
|
21875
22275
|
HOST_CP_DEPLOYMENT_NAME,
|
|
@@ -22009,10 +22409,10 @@ async function applyK8sAuthRefresh(pinnedContext, deps = {}) {
|
|
|
22009
22409
|
const stdout = deps.stdout ?? process.stdout;
|
|
22010
22410
|
const stderr = deps.stderr ?? process.stderr;
|
|
22011
22411
|
const olamHome5 = deps.olamHomeOverride ?? OLAM_HOME;
|
|
22012
|
-
const authSecretFile =
|
|
22412
|
+
const authSecretFile = path34.join(olamHome5, "auth-secret");
|
|
22013
22413
|
const readFn = deps.readFileSyncImpl ?? ((p, enc) => fs32.readFileSync(p, enc));
|
|
22014
22414
|
const writeFn = deps.writeFileSyncImpl ?? ((p, data, opts) => {
|
|
22015
|
-
fs32.mkdirSync(
|
|
22415
|
+
fs32.mkdirSync(path34.dirname(p), { recursive: true });
|
|
22016
22416
|
fs32.writeFileSync(p, data, { encoding: "utf8", mode: opts.mode });
|
|
22017
22417
|
});
|
|
22018
22418
|
const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
|
|
@@ -22262,15 +22662,15 @@ ${pc11.dim("Next: olam create --name my-world")}`);
|
|
|
22262
22662
|
|
|
22263
22663
|
// src/commands/create.ts
|
|
22264
22664
|
init_manager();
|
|
22265
|
-
import { spawnSync as
|
|
22266
|
-
import { existsSync as
|
|
22665
|
+
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
22666
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
22267
22667
|
import { dirname as dirname26, resolve as resolve14 } from "node:path";
|
|
22268
22668
|
import ora5 from "ora";
|
|
22269
22669
|
import pc12 from "picocolors";
|
|
22270
22670
|
|
|
22271
22671
|
// ../core/dist/world/devbox-freshness.js
|
|
22272
22672
|
import { execSync as execSync7 } from "node:child_process";
|
|
22273
|
-
import { existsSync as
|
|
22673
|
+
import { existsSync as existsSync32, statSync as statSync7 } from "node:fs";
|
|
22274
22674
|
import { join as join38 } from "node:path";
|
|
22275
22675
|
var DEFAULT_DEVBOX_IMAGE = "olam-devbox:base";
|
|
22276
22676
|
var DEVBOX_BAKED_SOURCES = [
|
|
@@ -22361,9 +22761,9 @@ function defaultDockerInspect(image) {
|
|
|
22361
22761
|
}
|
|
22362
22762
|
function defaultStatMtime(absPath) {
|
|
22363
22763
|
try {
|
|
22364
|
-
if (!
|
|
22764
|
+
if (!existsSync32(absPath))
|
|
22365
22765
|
return null;
|
|
22366
|
-
return
|
|
22766
|
+
return statSync7(absPath).mtimeMs;
|
|
22367
22767
|
} catch {
|
|
22368
22768
|
return null;
|
|
22369
22769
|
}
|
|
@@ -22500,12 +22900,12 @@ init_output();
|
|
|
22500
22900
|
init_host_cp();
|
|
22501
22901
|
|
|
22502
22902
|
// src/lib/memory-secret.ts
|
|
22503
|
-
import { existsSync as
|
|
22504
|
-
import { homedir as
|
|
22903
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync23, readFileSync as readFileSync27, statSync as statSync8, writeFileSync as writeFileSync18, renameSync as renameSync6, chmodSync as chmodSync3 } from "node:fs";
|
|
22904
|
+
import { homedir as homedir19 } from "node:os";
|
|
22505
22905
|
import { join as join39, dirname as dirname25 } from "node:path";
|
|
22506
22906
|
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
22507
|
-
var MEMORY_SECRET_PATH = join39(
|
|
22508
|
-
var CLOUD_MEMORY_SECRET_PATH = join39(
|
|
22907
|
+
var MEMORY_SECRET_PATH = join39(homedir19(), ".olam", "memory-secret");
|
|
22908
|
+
var CLOUD_MEMORY_SECRET_PATH = join39(homedir19(), ".olam", "cloud-memory-secret");
|
|
22509
22909
|
var SECRET_LEN_BYTES = 32;
|
|
22510
22910
|
function generateSecret() {
|
|
22511
22911
|
return randomBytes6(SECRET_LEN_BYTES).toString("hex");
|
|
@@ -22518,15 +22918,15 @@ function writeSecretAtPath(path89, value) {
|
|
|
22518
22918
|
renameSync6(tmp, path89);
|
|
22519
22919
|
}
|
|
22520
22920
|
function readSecretAtPathOrNull(path89) {
|
|
22521
|
-
if (!
|
|
22522
|
-
const mode =
|
|
22921
|
+
if (!existsSync33(path89)) return null;
|
|
22922
|
+
const mode = statSync8(path89).mode & 511;
|
|
22523
22923
|
if (mode !== 384) {
|
|
22524
22924
|
process.stderr.write(
|
|
22525
22925
|
`warn: ${path89} has mode 0${mode.toString(8)}; expected 0600. Run 'olam memory secret rotate' to regenerate.
|
|
22526
22926
|
`
|
|
22527
22927
|
);
|
|
22528
22928
|
}
|
|
22529
|
-
return
|
|
22929
|
+
return readFileSync27(path89, "utf8").trim();
|
|
22530
22930
|
}
|
|
22531
22931
|
function readSecretAtPath(path89) {
|
|
22532
22932
|
const v = readSecretAtPathOrNull(path89);
|
|
@@ -22556,16 +22956,16 @@ function rotateMemorySecret(path89 = MEMORY_SECRET_PATH) {
|
|
|
22556
22956
|
return fresh;
|
|
22557
22957
|
}
|
|
22558
22958
|
function hasMemorySecret(path89 = MEMORY_SECRET_PATH) {
|
|
22559
|
-
return
|
|
22959
|
+
return existsSync33(path89);
|
|
22560
22960
|
}
|
|
22561
22961
|
function writeCloudMemorySecret(value, path89 = CLOUD_MEMORY_SECRET_PATH) {
|
|
22562
22962
|
writeSecretAtPath(path89, value);
|
|
22563
22963
|
}
|
|
22564
22964
|
|
|
22565
22965
|
// src/lib/world-mcp-register.ts
|
|
22566
|
-
import { spawnSync as
|
|
22966
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
22567
22967
|
var DEFAULT_DOCKER_EXEC_DEPS = {
|
|
22568
|
-
spawn:
|
|
22968
|
+
spawn: spawnSync13,
|
|
22569
22969
|
log: (msg) => console.log(msg)
|
|
22570
22970
|
};
|
|
22571
22971
|
var MCP_NAME = "agentmemory";
|
|
@@ -22629,13 +23029,13 @@ function registerAgentMemoryMcp(opts, deps = DEFAULT_DOCKER_EXEC_DEPS) {
|
|
|
22629
23029
|
// src/lib/build-if-stale.ts
|
|
22630
23030
|
init_install_root();
|
|
22631
23031
|
import * as fs33 from "node:fs";
|
|
22632
|
-
import * as
|
|
22633
|
-
import { spawnSync as
|
|
23032
|
+
import * as path35 from "node:path";
|
|
23033
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
22634
23034
|
import ora4 from "ora";
|
|
22635
23035
|
|
|
22636
23036
|
// src/lib/bundle-source.ts
|
|
22637
23037
|
init_install_root();
|
|
22638
|
-
import { existsSync as
|
|
23038
|
+
import { existsSync as existsSync34 } from "node:fs";
|
|
22639
23039
|
import { join as join40, resolve as resolve13 } from "node:path";
|
|
22640
23040
|
var BundleSourceNotFoundError = class extends Error {
|
|
22641
23041
|
constructor(sourceModePath, installModePath) {
|
|
@@ -22659,7 +23059,7 @@ function resolveBundleSource(env = process.env, installRootDir = installRoot())
|
|
|
22659
23059
|
const repoRoot2 = resolve13(installRootDir, "..", "..");
|
|
22660
23060
|
const sourcePath2 = join40(repoRoot2, "packages", "intelligence", "dist", "agent-stream");
|
|
22661
23061
|
const installPath2 = join40(installRootDir, "dist", "agent-stream");
|
|
22662
|
-
if (!
|
|
23062
|
+
if (!existsSync34(sourcePath2)) {
|
|
22663
23063
|
throw new BundleSourceNotFoundError(sourcePath2, installPath2);
|
|
22664
23064
|
}
|
|
22665
23065
|
return { mode: "source", path: sourcePath2 };
|
|
@@ -22667,7 +23067,7 @@ function resolveBundleSource(env = process.env, installRootDir = installRoot())
|
|
|
22667
23067
|
const installPath = join40(installRootDir, "dist", "agent-stream");
|
|
22668
23068
|
const repoRoot = resolve13(installRootDir, "..", "..");
|
|
22669
23069
|
const sourcePath = join40(repoRoot, "packages", "intelligence", "dist", "agent-stream");
|
|
22670
|
-
if (!
|
|
23070
|
+
if (!existsSync34(installPath)) {
|
|
22671
23071
|
throw new BundleSourceNotFoundError(sourcePath, installPath);
|
|
22672
23072
|
}
|
|
22673
23073
|
return { mode: "install", path: installPath };
|
|
@@ -22686,11 +23086,11 @@ function maxMtimeMs(dir) {
|
|
|
22686
23086
|
return;
|
|
22687
23087
|
}
|
|
22688
23088
|
for (const entry of entries) {
|
|
22689
|
-
const full =
|
|
23089
|
+
const full = path35.join(d, entry.name);
|
|
22690
23090
|
if (entry.isDirectory()) {
|
|
22691
23091
|
if (entry.name === "__tests__" || entry.name === "node_modules") continue;
|
|
22692
23092
|
scan(full);
|
|
22693
|
-
} else if (entry.isFile() && TS_EXTS.has(
|
|
23093
|
+
} else if (entry.isFile() && TS_EXTS.has(path35.extname(entry.name))) {
|
|
22694
23094
|
try {
|
|
22695
23095
|
const mt = fs33.statSync(full).mtimeMs;
|
|
22696
23096
|
if (mt > max) max = mt;
|
|
@@ -22715,7 +23115,7 @@ function minDistMtimeMs(dir) {
|
|
|
22715
23115
|
for (const entry of entries) {
|
|
22716
23116
|
if (entry.isFile() && entry.name.endsWith(".js")) {
|
|
22717
23117
|
try {
|
|
22718
|
-
const mt = fs33.statSync(
|
|
23118
|
+
const mt = fs33.statSync(path35.join(dir, entry.name)).mtimeMs;
|
|
22719
23119
|
if (mt < min) min = mt;
|
|
22720
23120
|
found = true;
|
|
22721
23121
|
} catch {
|
|
@@ -22740,8 +23140,8 @@ function buildIfStale(repoRoot, env = process.env) {
|
|
|
22740
23140
|
if (!isDevMode(env)) {
|
|
22741
23141
|
return { ok: true, built: false, message: "install-mode: skipping build check" };
|
|
22742
23142
|
}
|
|
22743
|
-
const srcDir =
|
|
22744
|
-
const distDir =
|
|
23143
|
+
const srcDir = path35.join(repoRoot, "packages", "intelligence", "src", "agent-stream");
|
|
23144
|
+
const distDir = path35.join(repoRoot, "packages", "intelligence", "dist", "agent-stream");
|
|
22745
23145
|
const srcMtime = maxMtimeMs(srcDir);
|
|
22746
23146
|
const distMtime = minDistMtimeMs(distDir);
|
|
22747
23147
|
if (distMtime > 0 && srcMtime <= distMtime) {
|
|
@@ -22753,7 +23153,7 @@ function buildIfStale(repoRoot, env = process.env) {
|
|
|
22753
23153
|
}
|
|
22754
23154
|
const reason = distMtime === 0 ? "dist is absent" : `source newer by ${Math.round((srcMtime - distMtime) / 1e3)}s`;
|
|
22755
23155
|
const spinner = ora4(`Rebuilding agent-stream bundle\u2026 (${reason})`).start();
|
|
22756
|
-
const result =
|
|
23156
|
+
const result = spawnSync14(
|
|
22757
23157
|
"npm",
|
|
22758
23158
|
["run", "build", "--workspace=@olam/intelligence"],
|
|
22759
23159
|
{
|
|
@@ -22817,7 +23217,7 @@ function registerCreate(program2) {
|
|
|
22817
23217
|
if (decision.stderrLine) {
|
|
22818
23218
|
process.stderr.write(decision.stderrLine + "\n");
|
|
22819
23219
|
}
|
|
22820
|
-
|
|
23220
|
+
spawnSync15("docker", ["pull", overrideRef], { stdio: "pipe" });
|
|
22821
23221
|
const { inspectImageProtocolVersions: inspectImageProtocolVersions2, checkProtocolOverlap: checkProtocolOverlap2 } = await Promise.resolve().then(() => (init_protocol_version(), protocol_version_exports));
|
|
22822
23222
|
const inspect = inspectImageProtocolVersions2(overrideRef);
|
|
22823
23223
|
if (inspect.inspectFailed) {
|
|
@@ -22934,7 +23334,7 @@ function registerCreate(program2) {
|
|
|
22934
23334
|
throw err;
|
|
22935
23335
|
}
|
|
22936
23336
|
const spinner2 = ora5("Rebuilding olam-devbox:latest\u2026").start();
|
|
22937
|
-
const rebuild =
|
|
23337
|
+
const rebuild = spawnSync15(
|
|
22938
23338
|
"bash",
|
|
22939
23339
|
[buildScript],
|
|
22940
23340
|
{ cwd: repoRoot, stdio: "inherit" }
|
|
@@ -23215,7 +23615,7 @@ ${pc12.cyan("Host CP UI:")} ${worldUrl}`);
|
|
|
23215
23615
|
function resolveRepoRoot(start) {
|
|
23216
23616
|
let cur = start;
|
|
23217
23617
|
while (true) {
|
|
23218
|
-
if (
|
|
23618
|
+
if (existsSync36(resolve14(cur, "packages")) && existsSync36(resolve14(cur, "package.json"))) {
|
|
23219
23619
|
return cur;
|
|
23220
23620
|
}
|
|
23221
23621
|
const parent = dirname26(cur);
|
|
@@ -23449,7 +23849,7 @@ init_output();
|
|
|
23449
23849
|
import * as fs35 from "node:fs";
|
|
23450
23850
|
import * as http3 from "node:http";
|
|
23451
23851
|
import * as os18 from "node:os";
|
|
23452
|
-
import * as
|
|
23852
|
+
import * as path37 from "node:path";
|
|
23453
23853
|
var CLI_VERSION2 = process.env["OLAM_CLI_VERSION"] ?? "0.0.0";
|
|
23454
23854
|
var HOST_CP_PORT2 = 19e3;
|
|
23455
23855
|
var STATE_ENUM = [
|
|
@@ -23540,7 +23940,7 @@ async function getMachineStatus(_probe, _loadCtx, _readToken) {
|
|
|
23540
23940
|
}
|
|
23541
23941
|
} catch {
|
|
23542
23942
|
}
|
|
23543
|
-
const manifestPath2 =
|
|
23943
|
+
const manifestPath2 = path37.join(os18.homedir(), ".olam", "cache", "manifest.json");
|
|
23544
23944
|
let updateAvailable = null;
|
|
23545
23945
|
let lastUpdateCheck = null;
|
|
23546
23946
|
if (fs35.existsSync(manifestPath2)) {
|
|
@@ -23686,7 +24086,7 @@ init_context();
|
|
|
23686
24086
|
init_output();
|
|
23687
24087
|
import fs36 from "node:fs";
|
|
23688
24088
|
import os19 from "node:os";
|
|
23689
|
-
import
|
|
24089
|
+
import path38 from "node:path";
|
|
23690
24090
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
23691
24091
|
function registerClean(program2) {
|
|
23692
24092
|
program2.command("clean").description("Reap orphan world filesystem state under ~/.olam/worlds/").option("--apply", "Actually delete the orphans (default is dry-run)", false).option(
|
|
@@ -23710,7 +24110,7 @@ async function runClean(opts) {
|
|
|
23710
24110
|
printError(error?.message ?? "Olam is not configured. Run `olam init` first.");
|
|
23711
24111
|
return 1;
|
|
23712
24112
|
}
|
|
23713
|
-
const worldsDir =
|
|
24113
|
+
const worldsDir = path38.join(os19.homedir(), ".olam", "worlds");
|
|
23714
24114
|
if (!fs36.existsSync(worldsDir)) {
|
|
23715
24115
|
if (opts.json) {
|
|
23716
24116
|
process.stdout.write(`${JSON.stringify({ worldsDir, entries: [] })}
|
|
@@ -23727,7 +24127,7 @@ async function runClean(opts) {
|
|
|
23727
24127
|
const worktreeMap = collectWorktrees(worldsDir);
|
|
23728
24128
|
const entries = [];
|
|
23729
24129
|
for (const id of fs36.readdirSync(worldsDir).sort()) {
|
|
23730
|
-
const fullPath =
|
|
24130
|
+
const fullPath = path38.join(worldsDir, id);
|
|
23731
24131
|
const stat = safeStat(fullPath);
|
|
23732
24132
|
if (!stat || !stat.isDirectory()) continue;
|
|
23733
24133
|
entries.push(classifyWorld({ id, fullPath, liveIds, worktreeMap }));
|
|
@@ -23741,7 +24141,7 @@ async function runClean(opts) {
|
|
|
23741
24141
|
const deletable = entries.filter((e) => isDeletable(e, opts));
|
|
23742
24142
|
const reclaimable = deletable.reduce((acc, e) => acc + e.bytes, 0);
|
|
23743
24143
|
process.stdout.write(`
|
|
23744
|
-
Reclaimable: ${
|
|
24144
|
+
Reclaimable: ${formatBytes2(reclaimable)} across ${deletable.length} entries`);
|
|
23745
24145
|
if (entries.length > deletable.length) {
|
|
23746
24146
|
process.stdout.write(
|
|
23747
24147
|
` (preserved ${entries.length - deletable.length}: live registry + ${opts.includeDirty ? "none" : "dirty worktrees"})`
|
|
@@ -23759,7 +24159,7 @@ async function runClean(opts) {
|
|
|
23759
24159
|
if (!opts.yes) {
|
|
23760
24160
|
process.stdout.write(
|
|
23761
24161
|
`
|
|
23762
|
-
About to delete ${deletable.length} entries (${
|
|
24162
|
+
About to delete ${deletable.length} entries (${formatBytes2(reclaimable)}). Pass --yes to skip this prompt.
|
|
23763
24163
|
`
|
|
23764
24164
|
);
|
|
23765
24165
|
const confirmed = await confirmInteractive();
|
|
@@ -23782,7 +24182,7 @@ async function runClean(opts) {
|
|
|
23782
24182
|
}
|
|
23783
24183
|
process.stdout.write(
|
|
23784
24184
|
`
|
|
23785
|
-
Reaped ${removedCount}/${deletable.length} entries \xB7 ${
|
|
24185
|
+
Reaped ${removedCount}/${deletable.length} entries \xB7 ${formatBytes2(removedBytes)} reclaimed.
|
|
23786
24186
|
`
|
|
23787
24187
|
);
|
|
23788
24188
|
return removedCount === deletable.length ? 0 : 1;
|
|
@@ -23793,7 +24193,7 @@ function classifyWorld(args) {
|
|
|
23793
24193
|
if (liveIds.has(id)) {
|
|
23794
24194
|
return { id, path: fullPath, bytes, category: "active", note: "in registry" };
|
|
23795
24195
|
}
|
|
23796
|
-
const worktreeChild =
|
|
24196
|
+
const worktreeChild = path38.join(fullPath, "olam");
|
|
23797
24197
|
const worktreeInfo = worktreeMap.get(worktreeChild);
|
|
23798
24198
|
if (worktreeInfo) {
|
|
23799
24199
|
if (worktreeInfo.dirty > 0 || worktreeInfo.unpushed > 0) {
|
|
@@ -23850,8 +24250,8 @@ function reapEntry(entry) {
|
|
|
23850
24250
|
function collectWorktrees(worldsDir) {
|
|
23851
24251
|
const out = /* @__PURE__ */ new Map();
|
|
23852
24252
|
for (const id of fs36.readdirSync(worldsDir)) {
|
|
23853
|
-
const child =
|
|
23854
|
-
const gitMarker =
|
|
24253
|
+
const child = path38.join(worldsDir, id, "olam");
|
|
24254
|
+
const gitMarker = path38.join(child, ".git");
|
|
23855
24255
|
if (!fs36.existsSync(gitMarker)) continue;
|
|
23856
24256
|
const gitDir = resolveGitDirForWorktree(child);
|
|
23857
24257
|
if (!gitDir) continue;
|
|
@@ -23863,7 +24263,7 @@ function collectWorktrees(worldsDir) {
|
|
|
23863
24263
|
return out;
|
|
23864
24264
|
}
|
|
23865
24265
|
function resolveGitDirForWorktree(worktreePath) {
|
|
23866
|
-
const gitMarker =
|
|
24266
|
+
const gitMarker = path38.join(worktreePath, ".git");
|
|
23867
24267
|
try {
|
|
23868
24268
|
const top = execFileSync8("git", ["rev-parse", "--show-toplevel"], {
|
|
23869
24269
|
cwd: worktreePath,
|
|
@@ -23877,7 +24277,7 @@ function resolveGitDirForWorktree(worktreePath) {
|
|
|
23877
24277
|
stdio: "pipe"
|
|
23878
24278
|
}).trim();
|
|
23879
24279
|
if (!common) return top;
|
|
23880
|
-
return
|
|
24280
|
+
return path38.dirname(path38.resolve(worktreePath, common));
|
|
23881
24281
|
} catch {
|
|
23882
24282
|
return null;
|
|
23883
24283
|
}
|
|
@@ -23944,14 +24344,14 @@ function computeBytes(p) {
|
|
|
23944
24344
|
} catch {
|
|
23945
24345
|
continue;
|
|
23946
24346
|
}
|
|
23947
|
-
for (const name of entries) stack.push(
|
|
24347
|
+
for (const name of entries) stack.push(path38.join(cur, name));
|
|
23948
24348
|
} else {
|
|
23949
24349
|
total += st.size;
|
|
23950
24350
|
}
|
|
23951
24351
|
}
|
|
23952
24352
|
return total;
|
|
23953
24353
|
}
|
|
23954
|
-
function
|
|
24354
|
+
function formatBytes2(n) {
|
|
23955
24355
|
if (n < 1024) return `${n}B`;
|
|
23956
24356
|
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
|
|
23957
24357
|
if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)}MB`;
|
|
@@ -23970,7 +24370,7 @@ function printTable(entries) {
|
|
|
23970
24370
|
`);
|
|
23971
24371
|
for (const e of entries) {
|
|
23972
24372
|
process.stdout.write(
|
|
23973
|
-
` ${e.category.padEnd(10)} ${
|
|
24373
|
+
` ${e.category.padEnd(10)} ${formatBytes2(e.bytes).padStart(10)} ${e.id.padEnd(28)} ${e.note ?? ""}
|
|
23974
24374
|
`
|
|
23975
24375
|
);
|
|
23976
24376
|
}
|
|
@@ -23998,7 +24398,7 @@ init_output();
|
|
|
23998
24398
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
23999
24399
|
import * as fs37 from "node:fs";
|
|
24000
24400
|
import * as os20 from "node:os";
|
|
24001
|
-
import * as
|
|
24401
|
+
import * as path39 from "node:path";
|
|
24002
24402
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
24003
24403
|
import pc16 from "picocolors";
|
|
24004
24404
|
var NPM_PACKAGE = "@pleri/olam-cli";
|
|
@@ -24020,7 +24420,7 @@ var realExec = (cmd, args) => {
|
|
|
24020
24420
|
};
|
|
24021
24421
|
function surveyOlam(opts = {}) {
|
|
24022
24422
|
const exec = opts.exec ?? realExec;
|
|
24023
|
-
const home = opts.olamHome ??
|
|
24423
|
+
const home = opts.olamHome ?? path39.join(os20.homedir(), ".olam");
|
|
24024
24424
|
const containers = listDocker(exec, ["ps", "-a", "--format", "{{.Names}}"]).filter(
|
|
24025
24425
|
(n) => n.startsWith("olam-")
|
|
24026
24426
|
);
|
|
@@ -24034,11 +24434,11 @@ function surveyOlam(opts = {}) {
|
|
|
24034
24434
|
(n) => n.startsWith("olam-") && n !== "olam-host-cp-internal-default"
|
|
24035
24435
|
);
|
|
24036
24436
|
const worlds = [];
|
|
24037
|
-
const worldsDir =
|
|
24437
|
+
const worldsDir = path39.join(home, "worlds");
|
|
24038
24438
|
if (fs37.existsSync(worldsDir)) {
|
|
24039
24439
|
for (const entry of fs37.readdirSync(worldsDir, { withFileTypes: true })) {
|
|
24040
24440
|
if (!entry.isDirectory()) continue;
|
|
24041
|
-
const wPath =
|
|
24441
|
+
const wPath = path39.join(worldsDir, entry.name);
|
|
24042
24442
|
const bytes = dirSize(wPath);
|
|
24043
24443
|
const { dirty, note } = inspectWorldGitState(wPath);
|
|
24044
24444
|
worlds.push({ id: entry.name, bytes, dirty, note, path: wPath });
|
|
@@ -24047,7 +24447,7 @@ function surveyOlam(opts = {}) {
|
|
|
24047
24447
|
const homeBytesNonWorld = fs37.existsSync(home) ? Math.max(0, dirSize(home) - worlds.reduce((acc, w) => acc + w.bytes, 0)) : 0;
|
|
24048
24448
|
const npmRoot = exec("npm", ["root", "-g"]);
|
|
24049
24449
|
const npmGlobalRoot = npmRoot.exitCode === 0 ? npmRoot.stdout.trim() : null;
|
|
24050
|
-
const npmInstalled = npmGlobalRoot !== null && fs37.existsSync(
|
|
24450
|
+
const npmInstalled = npmGlobalRoot !== null && fs37.existsSync(path39.join(npmGlobalRoot, NPM_PACKAGE));
|
|
24051
24451
|
return {
|
|
24052
24452
|
containers,
|
|
24053
24453
|
images,
|
|
@@ -24077,7 +24477,7 @@ function dirSize(p) {
|
|
|
24077
24477
|
continue;
|
|
24078
24478
|
}
|
|
24079
24479
|
for (const e of entries) {
|
|
24080
|
-
const full =
|
|
24480
|
+
const full = path39.join(cur, e.name);
|
|
24081
24481
|
try {
|
|
24082
24482
|
if (e.isDirectory()) stack.push(full);
|
|
24083
24483
|
else if (e.isFile()) total += fs37.statSync(full).size;
|
|
@@ -24095,8 +24495,8 @@ function inspectWorldGitState(worldPath) {
|
|
|
24095
24495
|
try {
|
|
24096
24496
|
for (const entry of fs37.readdirSync(worldPath, { withFileTypes: true })) {
|
|
24097
24497
|
if (!entry.isDirectory()) continue;
|
|
24098
|
-
const repo =
|
|
24099
|
-
const gitDir =
|
|
24498
|
+
const repo = path39.join(worldPath, entry.name);
|
|
24499
|
+
const gitDir = path39.join(repo, ".git");
|
|
24100
24500
|
if (!fs37.existsSync(gitDir)) continue;
|
|
24101
24501
|
try {
|
|
24102
24502
|
const status2 = execFileSync9("git", ["status", "--porcelain"], {
|
|
@@ -24166,8 +24566,8 @@ function renderSurvey(survey, opts) {
|
|
|
24166
24566
|
lines.push(
|
|
24167
24567
|
` worlds: ${survey.worlds.length} (${dirtyWorlds.length} dirty, ${cleanWorlds.length} clean)`
|
|
24168
24568
|
);
|
|
24169
|
-
lines.push(` ~/.olam/* (non-world): ${
|
|
24170
|
-
lines.push(` worlds total: ${
|
|
24569
|
+
lines.push(` ~/.olam/* (non-world): ${formatBytes3(survey.homeBytesNonWorld)}`);
|
|
24570
|
+
lines.push(` worlds total: ${formatBytes3(worldBytes)}`);
|
|
24171
24571
|
lines.push(
|
|
24172
24572
|
` npm package installed: ${survey.npmInstalled ? `yes (at ${survey.npmGlobalRoot}/${NPM_PACKAGE})` : "no"}`
|
|
24173
24573
|
);
|
|
@@ -24175,14 +24575,14 @@ function renderSurvey(survey, opts) {
|
|
|
24175
24575
|
lines.push("");
|
|
24176
24576
|
lines.push(pc16.yellow(` \u26A0 dirty worlds (${dirtyWorlds.length}) \u2014 pass --include-dirty to destroy:`));
|
|
24177
24577
|
for (const w of dirtyWorlds) {
|
|
24178
|
-
lines.push(` - ${w.id} ${
|
|
24578
|
+
lines.push(` - ${w.id} ${formatBytes3(w.bytes)} (${w.note})`);
|
|
24179
24579
|
}
|
|
24180
24580
|
}
|
|
24181
24581
|
lines.push("");
|
|
24182
24582
|
lines.push(pc16.dim(" Run with --yes to actually destroy. Default is dry-run."));
|
|
24183
24583
|
return lines.join("\n");
|
|
24184
24584
|
}
|
|
24185
|
-
function
|
|
24585
|
+
function formatBytes3(n) {
|
|
24186
24586
|
if (n < 1024) return `${n} B`;
|
|
24187
24587
|
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
24188
24588
|
if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
@@ -24190,7 +24590,7 @@ function formatBytes2(n) {
|
|
|
24190
24590
|
}
|
|
24191
24591
|
async function executeImplode(survey, opts) {
|
|
24192
24592
|
const exec = opts.exec ?? realExec;
|
|
24193
|
-
const home = opts.olamHome ??
|
|
24593
|
+
const home = opts.olamHome ?? path39.join(os20.homedir(), ".olam");
|
|
24194
24594
|
const removed = [];
|
|
24195
24595
|
const skipped = [];
|
|
24196
24596
|
const errors = [];
|
|
@@ -24254,7 +24654,7 @@ async function executeImplode(survey, opts) {
|
|
|
24254
24654
|
skipped.push(`fs:~/.olam/auth-data (--keep-vault)`);
|
|
24255
24655
|
continue;
|
|
24256
24656
|
}
|
|
24257
|
-
const target =
|
|
24657
|
+
const target = path39.join(home, entry.name);
|
|
24258
24658
|
try {
|
|
24259
24659
|
fs37.rmSync(target, { recursive: true, force: true });
|
|
24260
24660
|
removed.push(`fs:${target}`);
|
|
@@ -25928,11 +26328,11 @@ var qmarksTestNoExtDot = ([$0]) => {
|
|
|
25928
26328
|
return (f) => f.length === len && f !== "." && f !== "..";
|
|
25929
26329
|
};
|
|
25930
26330
|
var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
|
|
25931
|
-
var
|
|
26331
|
+
var path41 = {
|
|
25932
26332
|
win32: { sep: "\\" },
|
|
25933
26333
|
posix: { sep: "/" }
|
|
25934
26334
|
};
|
|
25935
|
-
var sep2 = defaultPlatform === "win32" ?
|
|
26335
|
+
var sep2 = defaultPlatform === "win32" ? path41.win32.sep : path41.posix.sep;
|
|
25936
26336
|
minimatch.sep = sep2;
|
|
25937
26337
|
var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
|
|
25938
26338
|
minimatch.GLOBSTAR = GLOBSTAR;
|
|
@@ -26758,7 +27158,7 @@ function registerPolicyCheck(program2) {
|
|
|
26758
27158
|
}
|
|
26759
27159
|
|
|
26760
27160
|
// src/commands/worldspec/compile.ts
|
|
26761
|
-
import { existsSync as
|
|
27161
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync25, readFileSync as readFileSync30, writeFileSync as writeFileSync20 } from "node:fs";
|
|
26762
27162
|
import { resolve as resolvePath } from "node:path";
|
|
26763
27163
|
import YAML5 from "yaml";
|
|
26764
27164
|
|
|
@@ -27561,13 +27961,13 @@ function registerWorldspecCompile(parent) {
|
|
|
27561
27961
|
const sourcePolicies = [];
|
|
27562
27962
|
for (const p of paths) {
|
|
27563
27963
|
const abs = resolvePath(process.cwd(), p);
|
|
27564
|
-
if (!
|
|
27964
|
+
if (!existsSync41(abs)) {
|
|
27565
27965
|
printError(`worldspec not found at ${abs}`);
|
|
27566
27966
|
process.exit(EXIT_WORLDSPEC_FILE_NOT_FOUND);
|
|
27567
27967
|
}
|
|
27568
27968
|
let yaml;
|
|
27569
27969
|
try {
|
|
27570
|
-
yaml = YAML5.parse(
|
|
27970
|
+
yaml = YAML5.parse(readFileSync30(abs, "utf8"));
|
|
27571
27971
|
} catch (err) {
|
|
27572
27972
|
printError(
|
|
27573
27973
|
`${p}: YAML parse error: ${err.message}`
|
|
@@ -27652,7 +28052,7 @@ function getPkgVersion() {
|
|
|
27652
28052
|
// src/commands/worldspec/init.ts
|
|
27653
28053
|
init_exit_codes();
|
|
27654
28054
|
init_output();
|
|
27655
|
-
import { existsSync as
|
|
28055
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync26, readFileSync as readFileSync31, writeFileSync as writeFileSync21 } from "node:fs";
|
|
27656
28056
|
import { execSync as execSync10 } from "node:child_process";
|
|
27657
28057
|
import { basename as basename3, resolve as resolvePath2 } from "node:path";
|
|
27658
28058
|
function registerWorldspecInit(parent) {
|
|
@@ -27670,7 +28070,7 @@ function registerWorldspecInit(parent) {
|
|
|
27670
28070
|
const cwd = process.cwd();
|
|
27671
28071
|
const targetDir = resolvePath2(cwd, ".olam/worldspecs");
|
|
27672
28072
|
const targetFile = resolvePath2(targetDir, "default.yaml");
|
|
27673
|
-
if (
|
|
28073
|
+
if (existsSync42(targetFile) && !opts.force) {
|
|
27674
28074
|
printError(
|
|
27675
28075
|
`${targetFile} already exists; pass --force to overwrite`
|
|
27676
28076
|
);
|
|
@@ -27704,14 +28104,14 @@ function registerWorldspecInit(parent) {
|
|
|
27704
28104
|
});
|
|
27705
28105
|
}
|
|
27706
28106
|
function detectProjectShape(root, useAdbYaml) {
|
|
27707
|
-
const hasGemfile =
|
|
27708
|
-
const hasNodePackageJson =
|
|
27709
|
-
const hasAdbYaml =
|
|
28107
|
+
const hasGemfile = existsSync42(resolvePath2(root, "Gemfile"));
|
|
28108
|
+
const hasNodePackageJson = existsSync42(resolvePath2(root, "package.json"));
|
|
28109
|
+
const hasAdbYaml = existsSync42(resolvePath2(root, ".adb.yaml"));
|
|
27710
28110
|
let rubyVersion = null;
|
|
27711
28111
|
const rubyVersionPath = resolvePath2(root, ".ruby-version");
|
|
27712
|
-
if (
|
|
28112
|
+
if (existsSync42(rubyVersionPath)) {
|
|
27713
28113
|
try {
|
|
27714
|
-
const raw =
|
|
28114
|
+
const raw = readFileSync31(rubyVersionPath, "utf8").trim();
|
|
27715
28115
|
const match2 = raw.match(/(\d+\.\d+\.\d+)/);
|
|
27716
28116
|
if (match2) rubyVersion = match2[1] ?? null;
|
|
27717
28117
|
} catch {
|
|
@@ -29121,7 +29521,7 @@ function registerWorldspecSchema(parent) {
|
|
|
29121
29521
|
}
|
|
29122
29522
|
|
|
29123
29523
|
// src/commands/worldspec/validate.ts
|
|
29124
|
-
import { existsSync as
|
|
29524
|
+
import { existsSync as existsSync43, readFileSync as readFileSync32 } from "node:fs";
|
|
29125
29525
|
import { resolve as resolvePath4 } from "node:path";
|
|
29126
29526
|
init_exit_codes();
|
|
29127
29527
|
init_output();
|
|
@@ -29138,13 +29538,13 @@ function registerWorldspecValidate(parent) {
|
|
|
29138
29538
|
"human"
|
|
29139
29539
|
).action(async (pathArg, opts) => {
|
|
29140
29540
|
const absPath = resolvePath4(process.cwd(), pathArg);
|
|
29141
|
-
if (!
|
|
29541
|
+
if (!existsSync43(absPath)) {
|
|
29142
29542
|
printError(`worldspec not found at ${absPath}`);
|
|
29143
29543
|
process.exit(EXIT_WORLDSPEC_FILE_NOT_FOUND);
|
|
29144
29544
|
}
|
|
29145
29545
|
let yamlSource;
|
|
29146
29546
|
try {
|
|
29147
|
-
yamlSource =
|
|
29547
|
+
yamlSource = readFileSync32(absPath, "utf8");
|
|
29148
29548
|
} catch (err) {
|
|
29149
29549
|
printError(
|
|
29150
29550
|
`failed to read ${absPath}: ${err.message}`
|
|
@@ -29191,17 +29591,17 @@ function registerWorldspec(program2) {
|
|
|
29191
29591
|
init_output();
|
|
29192
29592
|
init_host_cp();
|
|
29193
29593
|
import * as fs41 from "node:fs";
|
|
29194
|
-
import * as
|
|
29195
|
-
import { spawnSync as
|
|
29594
|
+
import * as path44 from "node:path";
|
|
29595
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
29196
29596
|
import ora9 from "ora";
|
|
29197
29597
|
import pc20 from "picocolors";
|
|
29198
29598
|
|
|
29199
29599
|
// src/commands/upgrade-lock.ts
|
|
29200
29600
|
import * as fs39 from "node:fs";
|
|
29201
29601
|
import * as os21 from "node:os";
|
|
29202
|
-
import * as
|
|
29203
|
-
import { spawnSync as
|
|
29204
|
-
var LOCK_FILE_PATH =
|
|
29602
|
+
import * as path42 from "node:path";
|
|
29603
|
+
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
29604
|
+
var LOCK_FILE_PATH = path42.join(os21.homedir(), ".olam", ".upgrade.lock");
|
|
29205
29605
|
var STALE_LOCK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
29206
29606
|
function readLockFile(lockPath) {
|
|
29207
29607
|
try {
|
|
@@ -29225,7 +29625,7 @@ function isPidAlive2(pid) {
|
|
|
29225
29625
|
}
|
|
29226
29626
|
var PS_UNAVAILABLE = "__ps_unavailable__";
|
|
29227
29627
|
function getPidCommand(pid) {
|
|
29228
|
-
const result =
|
|
29628
|
+
const result = spawnSync16("ps", ["-p", String(pid), "-o", "comm="], {
|
|
29229
29629
|
encoding: "utf-8",
|
|
29230
29630
|
stdio: ["ignore", "pipe", "ignore"]
|
|
29231
29631
|
});
|
|
@@ -29251,7 +29651,7 @@ function isStaleLock(content, nowMs = Date.now()) {
|
|
|
29251
29651
|
return false;
|
|
29252
29652
|
}
|
|
29253
29653
|
function acquireLock(lockPath = LOCK_FILE_PATH, nowMs = Date.now()) {
|
|
29254
|
-
const dir =
|
|
29654
|
+
const dir = path42.dirname(lockPath);
|
|
29255
29655
|
fs39.mkdirSync(dir, { recursive: true });
|
|
29256
29656
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
29257
29657
|
try {
|
|
@@ -29314,15 +29714,15 @@ function formatRefusalMessage(result, lockPath = LOCK_FILE_PATH) {
|
|
|
29314
29714
|
// src/commands/upgrade-log.ts
|
|
29315
29715
|
import * as fs40 from "node:fs";
|
|
29316
29716
|
import * as os22 from "node:os";
|
|
29317
|
-
import * as
|
|
29717
|
+
import * as path43 from "node:path";
|
|
29318
29718
|
function getUpgradeLogPath() {
|
|
29319
29719
|
const home = process.env["HOME"] ?? os22.homedir();
|
|
29320
|
-
return
|
|
29720
|
+
return path43.join(home, ".olam", "upgrade.log");
|
|
29321
29721
|
}
|
|
29322
29722
|
var UPGRADE_LOG_PATH = getUpgradeLogPath();
|
|
29323
29723
|
function appendUpgradeLog(row, logPath = getUpgradeLogPath()) {
|
|
29324
29724
|
try {
|
|
29325
|
-
fs40.mkdirSync(
|
|
29725
|
+
fs40.mkdirSync(path43.dirname(logPath), { recursive: true });
|
|
29326
29726
|
const line = JSON.stringify(row) + "\n";
|
|
29327
29727
|
fs40.appendFileSync(logPath, line, { mode: 420 });
|
|
29328
29728
|
} catch (err) {
|
|
@@ -29429,8 +29829,8 @@ init_protocol_version();
|
|
|
29429
29829
|
init_install_root();
|
|
29430
29830
|
var AUTH_HEALTH_URL2 = "http://127.0.0.1:9999/health";
|
|
29431
29831
|
function isNodeModulesInSync(cwd) {
|
|
29432
|
-
const lockPath =
|
|
29433
|
-
const markerPath =
|
|
29832
|
+
const lockPath = path44.join(cwd, "package-lock.json");
|
|
29833
|
+
const markerPath = path44.join(cwd, "node_modules", ".package-lock.json");
|
|
29434
29834
|
if (!fs41.existsSync(lockPath) || !fs41.existsSync(markerPath)) return false;
|
|
29435
29835
|
try {
|
|
29436
29836
|
const lockStat = fs41.statSync(lockPath);
|
|
@@ -29450,7 +29850,7 @@ function shouldSkipInstall(opts, cwd) {
|
|
|
29450
29850
|
return { skip: false };
|
|
29451
29851
|
}
|
|
29452
29852
|
function validateRepoRoot(cwd) {
|
|
29453
|
-
const marker =
|
|
29853
|
+
const marker = path44.join(cwd, "packages/host-cp/compose.yaml");
|
|
29454
29854
|
if (!fs41.existsSync(marker)) {
|
|
29455
29855
|
return {
|
|
29456
29856
|
ok: false,
|
|
@@ -29486,7 +29886,7 @@ function extractBundleHash(indexHtml) {
|
|
|
29486
29886
|
function runStep2(label, cmd, args, opts = {}) {
|
|
29487
29887
|
const start = Date.now();
|
|
29488
29888
|
process.stdout.write(` ${pc20.dim(label.padEnd(34))}`);
|
|
29489
|
-
const result =
|
|
29889
|
+
const result = spawnSync17(cmd, [...args], {
|
|
29490
29890
|
encoding: "utf-8",
|
|
29491
29891
|
stdio: ["ignore", "pipe", "pipe"],
|
|
29492
29892
|
cwd: opts.cwd ?? process.cwd(),
|
|
@@ -29505,7 +29905,7 @@ function runStep2(label, cmd, args, opts = {}) {
|
|
|
29505
29905
|
};
|
|
29506
29906
|
}
|
|
29507
29907
|
function isGitDirty(cwd) {
|
|
29508
|
-
const result =
|
|
29908
|
+
const result = spawnSync17("git", ["status", "--porcelain"], {
|
|
29509
29909
|
encoding: "utf-8",
|
|
29510
29910
|
stdio: ["ignore", "pipe", "pipe"],
|
|
29511
29911
|
cwd
|
|
@@ -29513,7 +29913,7 @@ function isGitDirty(cwd) {
|
|
|
29513
29913
|
return (result.stdout ?? "").trim().length > 0;
|
|
29514
29914
|
}
|
|
29515
29915
|
function hasGitUpstream(cwd) {
|
|
29516
|
-
const result =
|
|
29916
|
+
const result = spawnSync17("git", ["rev-parse", "--abbrev-ref", "@{u}"], {
|
|
29517
29917
|
encoding: "utf-8",
|
|
29518
29918
|
stdio: ["ignore", "pipe", "pipe"],
|
|
29519
29919
|
cwd
|
|
@@ -29521,7 +29921,7 @@ function hasGitUpstream(cwd) {
|
|
|
29521
29921
|
return result.status === 0;
|
|
29522
29922
|
}
|
|
29523
29923
|
function captureHeadSha(cwd) {
|
|
29524
|
-
const result =
|
|
29924
|
+
const result = spawnSync17("git", ["rev-parse", "HEAD"], {
|
|
29525
29925
|
encoding: "utf-8",
|
|
29526
29926
|
stdio: ["ignore", "pipe", "pipe"],
|
|
29527
29927
|
cwd
|
|
@@ -29536,7 +29936,7 @@ function abbreviateSha(sha) {
|
|
|
29536
29936
|
}
|
|
29537
29937
|
function imageExists(tag) {
|
|
29538
29938
|
try {
|
|
29539
|
-
const result =
|
|
29939
|
+
const result = spawnSync17("docker", ["image", "inspect", "--format", "{{.Id}}", tag], {
|
|
29540
29940
|
encoding: "utf-8",
|
|
29541
29941
|
stdio: ["ignore", "pipe", "ignore"]
|
|
29542
29942
|
});
|
|
@@ -29552,7 +29952,7 @@ function checkRollbackSetExists(plan) {
|
|
|
29552
29952
|
}
|
|
29553
29953
|
var SMOKE_DOCKER_TIMEOUT_MS = 3e4;
|
|
29554
29954
|
function smokeImage(image, targetSha) {
|
|
29555
|
-
const createResult =
|
|
29955
|
+
const createResult = spawnSync17("docker", ["create", "--name", `olam-smoke-${Date.now()}`, image], {
|
|
29556
29956
|
encoding: "utf-8",
|
|
29557
29957
|
stdio: ["ignore", "pipe", "pipe"],
|
|
29558
29958
|
timeout: SMOKE_DOCKER_TIMEOUT_MS
|
|
@@ -29566,7 +29966,7 @@ function smokeImage(image, targetSha) {
|
|
|
29566
29966
|
};
|
|
29567
29967
|
}
|
|
29568
29968
|
const containerId = (createResult.stdout ?? "").trim();
|
|
29569
|
-
const inspectResult =
|
|
29969
|
+
const inspectResult = spawnSync17(
|
|
29570
29970
|
"docker",
|
|
29571
29971
|
["inspect", "--format", '{{index .Config.Labels "olam.build.sha"}}', image],
|
|
29572
29972
|
{
|
|
@@ -29576,7 +29976,7 @@ function smokeImage(image, targetSha) {
|
|
|
29576
29976
|
}
|
|
29577
29977
|
);
|
|
29578
29978
|
if (containerId.length > 0) {
|
|
29579
|
-
|
|
29979
|
+
spawnSync17("docker", ["rm", "-f", containerId], {
|
|
29580
29980
|
encoding: "utf-8",
|
|
29581
29981
|
stdio: ["ignore", "ignore", "ignore"],
|
|
29582
29982
|
timeout: SMOKE_DOCKER_TIMEOUT_MS
|
|
@@ -29618,7 +30018,7 @@ var PRODUCTION_SWAP_PLAN = [
|
|
|
29618
30018
|
];
|
|
29619
30019
|
function dockerTag(source, dest) {
|
|
29620
30020
|
try {
|
|
29621
|
-
const result =
|
|
30021
|
+
const result = spawnSync17("docker", ["tag", source, dest], {
|
|
29622
30022
|
encoding: "utf-8",
|
|
29623
30023
|
stdio: ["ignore", "ignore", "pipe"]
|
|
29624
30024
|
});
|
|
@@ -29782,11 +30182,11 @@ async function waitForAuthHealthLocal(timeoutMs = AUTH_HEALTH_TIMEOUT_MS) {
|
|
|
29782
30182
|
async function recreateAuthService() {
|
|
29783
30183
|
const start = Date.now();
|
|
29784
30184
|
try {
|
|
29785
|
-
|
|
30185
|
+
spawnSync17("docker", ["stop", "olam-auth"], {
|
|
29786
30186
|
encoding: "utf-8",
|
|
29787
30187
|
stdio: ["ignore", "ignore", "ignore"]
|
|
29788
30188
|
});
|
|
29789
|
-
|
|
30189
|
+
spawnSync17("docker", ["rm", "olam-auth"], {
|
|
29790
30190
|
encoding: "utf-8",
|
|
29791
30191
|
stdio: ["ignore", "ignore", "ignore"]
|
|
29792
30192
|
});
|
|
@@ -29811,7 +30211,7 @@ async function recreateAuthService() {
|
|
|
29811
30211
|
}
|
|
29812
30212
|
}
|
|
29813
30213
|
function readBundleHash(cwd) {
|
|
29814
|
-
const indexPath =
|
|
30214
|
+
const indexPath = path44.join(cwd, "packages/control-plane/public/index.html");
|
|
29815
30215
|
if (!fs41.existsSync(indexPath)) return null;
|
|
29816
30216
|
return extractBundleHash(fs41.readFileSync(indexPath, "utf-8"));
|
|
29817
30217
|
}
|
|
@@ -30084,11 +30484,11 @@ async function defaultRecreateMcpAuthForUpgrade() {
|
|
|
30084
30484
|
}
|
|
30085
30485
|
async function defaultRecreateAuthForUpgrade() {
|
|
30086
30486
|
try {
|
|
30087
|
-
|
|
30487
|
+
spawnSync17("docker", ["stop", "olam-auth"], {
|
|
30088
30488
|
encoding: "utf-8",
|
|
30089
30489
|
stdio: ["ignore", "ignore", "ignore"]
|
|
30090
30490
|
});
|
|
30091
|
-
|
|
30491
|
+
spawnSync17("docker", ["rm", "olam-auth"], {
|
|
30092
30492
|
encoding: "utf-8",
|
|
30093
30493
|
stdio: ["ignore", "ignore", "ignore"]
|
|
30094
30494
|
});
|
|
@@ -30110,7 +30510,7 @@ async function defaultRecreateAuthForUpgrade() {
|
|
|
30110
30510
|
}
|
|
30111
30511
|
}
|
|
30112
30512
|
function defaultInspectContainerLabels(containerName) {
|
|
30113
|
-
const result =
|
|
30513
|
+
const result = spawnSync17(
|
|
30114
30514
|
"docker",
|
|
30115
30515
|
[
|
|
30116
30516
|
"inspect",
|
|
@@ -30134,7 +30534,7 @@ function defaultInspectContainerLabels(containerName) {
|
|
|
30134
30534
|
return { ok: false, exists: false, project: "", service: "", stderr };
|
|
30135
30535
|
}
|
|
30136
30536
|
function defaultRemoveContainer(containerName) {
|
|
30137
|
-
const result =
|
|
30537
|
+
const result = spawnSync17("docker", ["rm", "-f", containerName], {
|
|
30138
30538
|
encoding: "utf-8",
|
|
30139
30539
|
stdio: ["ignore", "pipe", "pipe"]
|
|
30140
30540
|
});
|
|
@@ -30417,7 +30817,7 @@ ${buildResult.stderr}`);
|
|
|
30417
30817
|
return;
|
|
30418
30818
|
}
|
|
30419
30819
|
const authSecret = readAuthSecret2();
|
|
30420
|
-
const spaDir =
|
|
30820
|
+
const spaDir = path44.join(cwd, "packages/control-plane/app");
|
|
30421
30821
|
const spaResult = runStep2(
|
|
30422
30822
|
"vite build (SPA)",
|
|
30423
30823
|
"npx",
|
|
@@ -30465,7 +30865,7 @@ ${spaResult.stderr}`);
|
|
|
30465
30865
|
process.stdout.write(` ${pc20.dim(step.label.padEnd(34))}
|
|
30466
30866
|
`);
|
|
30467
30867
|
const start = Date.now();
|
|
30468
|
-
const result =
|
|
30868
|
+
const result = spawnSync17("bash", [scriptPath], {
|
|
30469
30869
|
stdio: "inherit",
|
|
30470
30870
|
cwd,
|
|
30471
30871
|
env: { ...process.env, ...olamTagEnv }
|
|
@@ -30810,7 +31210,7 @@ function registerLogs(program2) {
|
|
|
30810
31210
|
init_context();
|
|
30811
31211
|
init_output();
|
|
30812
31212
|
import pc22 from "picocolors";
|
|
30813
|
-
import { spawnSync as
|
|
31213
|
+
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
30814
31214
|
var SAFE_IDENT4 = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
30815
31215
|
function parseDockerTop(stdout) {
|
|
30816
31216
|
const trimmed = stdout.trim();
|
|
@@ -30910,7 +31310,7 @@ function registerPs(program2) {
|
|
|
30910
31310
|
const containerName = `olam-${worldId}-devbox`;
|
|
30911
31311
|
let watchInterval;
|
|
30912
31312
|
function fetchAndPrint() {
|
|
30913
|
-
const result =
|
|
31313
|
+
const result = spawnSync18(
|
|
30914
31314
|
"docker",
|
|
30915
31315
|
["top", containerName, "pid", "user", "pcpu", "pmem", "stime", "stat", "cmd"],
|
|
30916
31316
|
{ encoding: "utf-8", timeout: 3e3 }
|
|
@@ -30949,13 +31349,13 @@ ${pc22.dim(`world: ${worldId} sort: ${sortKey} refresh: 5s Ctrl-C to exit`)}
|
|
|
30949
31349
|
init_output();
|
|
30950
31350
|
import * as fs42 from "node:fs";
|
|
30951
31351
|
import * as os23 from "node:os";
|
|
30952
|
-
import * as
|
|
31352
|
+
import * as path45 from "node:path";
|
|
30953
31353
|
import YAML6 from "yaml";
|
|
30954
31354
|
function olamHome3() {
|
|
30955
|
-
return process.env.OLAM_HOME ??
|
|
31355
|
+
return process.env.OLAM_HOME ?? path45.join(os23.homedir(), ".olam");
|
|
30956
31356
|
}
|
|
30957
31357
|
function keysFilePath() {
|
|
30958
|
-
return
|
|
31358
|
+
return path45.join(olamHome3(), "keys.yaml");
|
|
30959
31359
|
}
|
|
30960
31360
|
function readKeysFile() {
|
|
30961
31361
|
const filePath = keysFilePath();
|
|
@@ -31051,7 +31451,7 @@ function registerKeys(program2) {
|
|
|
31051
31451
|
init_snapshot();
|
|
31052
31452
|
init_output();
|
|
31053
31453
|
import * as fs43 from "node:fs";
|
|
31054
|
-
import * as
|
|
31454
|
+
import * as path46 from "node:path";
|
|
31055
31455
|
import { execSync as execSync11 } from "node:child_process";
|
|
31056
31456
|
import pc23 from "picocolors";
|
|
31057
31457
|
|
|
@@ -31074,9 +31474,9 @@ function emitDeprecationWarning(subcommand) {
|
|
|
31074
31474
|
}
|
|
31075
31475
|
function bumpDeprecationCounter() {
|
|
31076
31476
|
if (process.env[INTERNAL_SENTINEL_ENV] === "1") return;
|
|
31077
|
-
const counterPath =
|
|
31477
|
+
const counterPath = path46.join(snapshotsDir(), ".deprecation-counter");
|
|
31078
31478
|
try {
|
|
31079
|
-
fs43.mkdirSync(
|
|
31479
|
+
fs43.mkdirSync(path46.dirname(counterPath), { recursive: true });
|
|
31080
31480
|
let current = 0;
|
|
31081
31481
|
if (fs43.existsSync(counterPath)) {
|
|
31082
31482
|
const raw = fs43.readFileSync(counterPath, "utf-8").trim();
|
|
@@ -31159,13 +31559,13 @@ function resolveKinds(arg) {
|
|
|
31159
31559
|
return [];
|
|
31160
31560
|
}
|
|
31161
31561
|
async function captureGems(worldId, workspacePath, repo) {
|
|
31162
|
-
const repoDir =
|
|
31562
|
+
const repoDir = path46.join(workspacePath, repo);
|
|
31163
31563
|
const fingerprint = computeGemsFingerprint(repoDir);
|
|
31164
31564
|
if (!fingerprint) {
|
|
31165
31565
|
return { ok: false, tarPath: "", msg: "no Gemfile.lock \u2014 layer does not apply" };
|
|
31166
31566
|
}
|
|
31167
31567
|
const tarPath = snapshotTarPath(worldId, "gems", repo, fingerprint);
|
|
31168
|
-
const vendorBundle =
|
|
31568
|
+
const vendorBundle = path46.join(repoDir, "vendor", "bundle");
|
|
31169
31569
|
if (fs43.existsSync(vendorBundle)) {
|
|
31170
31570
|
try {
|
|
31171
31571
|
packTarball(vendorBundle, tarPath);
|
|
@@ -31202,7 +31602,7 @@ async function captureGems(worldId, workspacePath, repo) {
|
|
|
31202
31602
|
`docker exec ${containerName} sh -c 'mkdir -p "$(dirname ${tmpTar})" && tar -czf ${tmpTar}.tmp -C ${bundlePath} . && mv ${tmpTar}.tmp ${tmpTar}'`,
|
|
31203
31603
|
{ stdio: "pipe", timeout: 12e4 }
|
|
31204
31604
|
);
|
|
31205
|
-
fs43.mkdirSync(
|
|
31605
|
+
fs43.mkdirSync(path46.dirname(tarPath), { recursive: true });
|
|
31206
31606
|
execSync11(`docker cp ${containerName}:${tmpTar} "${tarPath}"`, { stdio: "pipe", timeout: 12e4 });
|
|
31207
31607
|
execSync11(`docker exec ${containerName} rm -f ${tmpTar}`, { stdio: "pipe" });
|
|
31208
31608
|
const stat = fs43.statSync(tarPath);
|
|
@@ -31222,12 +31622,12 @@ async function captureGems(worldId, workspacePath, repo) {
|
|
|
31222
31622
|
}
|
|
31223
31623
|
}
|
|
31224
31624
|
async function captureNode(worldId, workspacePath, repo) {
|
|
31225
|
-
const repoDir =
|
|
31625
|
+
const repoDir = path46.join(workspacePath, repo);
|
|
31226
31626
|
const fingerprint = computeNodeFingerprint(repoDir);
|
|
31227
31627
|
if (!fingerprint) {
|
|
31228
31628
|
return { ok: false, tarPath: "", msg: "no lockfile \u2014 layer does not apply" };
|
|
31229
31629
|
}
|
|
31230
|
-
const nodeModules =
|
|
31630
|
+
const nodeModules = path46.join(repoDir, "node_modules");
|
|
31231
31631
|
if (!fs43.existsSync(nodeModules)) {
|
|
31232
31632
|
return { ok: false, tarPath: "", msg: "node_modules not installed yet" };
|
|
31233
31633
|
}
|
|
@@ -31251,7 +31651,7 @@ async function captureNode(worldId, workspacePath, repo) {
|
|
|
31251
31651
|
}
|
|
31252
31652
|
}
|
|
31253
31653
|
async function capturePg(worldId, workspacePath, repoNames) {
|
|
31254
|
-
const repoDirs = repoNames.map((r) =>
|
|
31654
|
+
const repoDirs = repoNames.map((r) => path46.join(workspacePath, r));
|
|
31255
31655
|
const fingerprint = computePgFingerprint(repoDirs);
|
|
31256
31656
|
if (!fingerprint) {
|
|
31257
31657
|
return { ok: false, tarPath: "", msg: "no Gemfile.lock / schema.rb \u2014 layer does not apply" };
|
|
@@ -31266,9 +31666,9 @@ async function capturePg(worldId, workspacePath, repoNames) {
|
|
|
31266
31666
|
}
|
|
31267
31667
|
try {
|
|
31268
31668
|
execSync11(`docker stop ${containerName}`, { stdio: "pipe", timeout: 3e4 });
|
|
31269
|
-
fs43.mkdirSync(
|
|
31669
|
+
fs43.mkdirSync(path46.dirname(tarPath), { recursive: true });
|
|
31270
31670
|
execSync11(
|
|
31271
|
-
`docker run --rm -v "${volumeName2}:/pgdata:ro" -v "${
|
|
31671
|
+
`docker run --rm -v "${volumeName2}:/pgdata:ro" -v "${path46.dirname(tarPath)}:/dest" alpine sh -c 'tar -czf /dest/${path46.basename(tarPath)}.tmp -C /pgdata . && mv /dest/${path46.basename(tarPath)}.tmp /dest/${path46.basename(tarPath)}'`,
|
|
31272
31672
|
{ stdio: "pipe", timeout: 18e4 }
|
|
31273
31673
|
);
|
|
31274
31674
|
execSync11(`docker start ${containerName}`, { stdio: "pipe", timeout: 3e4 });
|
|
@@ -31303,7 +31703,7 @@ async function handleEvict(opts) {
|
|
|
31303
31703
|
const allTars = [];
|
|
31304
31704
|
const walk3 = (d) => {
|
|
31305
31705
|
for (const entry of fs43.readdirSync(d, { withFileTypes: true })) {
|
|
31306
|
-
const full =
|
|
31706
|
+
const full = path46.join(d, entry.name);
|
|
31307
31707
|
if (entry.isDirectory()) {
|
|
31308
31708
|
walk3(full);
|
|
31309
31709
|
} else if (entry.name.endsWith(".tar.gz")) {
|
|
@@ -31315,7 +31715,7 @@ async function handleEvict(opts) {
|
|
|
31315
31715
|
walk3(root);
|
|
31316
31716
|
const total = allTars.reduce((a, t) => a + t.size, 0);
|
|
31317
31717
|
if (total <= maxBytes) {
|
|
31318
|
-
console.log(`${
|
|
31718
|
+
console.log(`${formatBytes4(total)} total \u2264 ${formatBytes4(maxBytes)} cap; nothing to evict.`);
|
|
31319
31719
|
return;
|
|
31320
31720
|
}
|
|
31321
31721
|
allTars.sort((a, b) => a.mtime - b.mtime);
|
|
@@ -31326,19 +31726,19 @@ async function handleEvict(opts) {
|
|
|
31326
31726
|
toEvict.push(t);
|
|
31327
31727
|
remaining -= t.size;
|
|
31328
31728
|
}
|
|
31329
|
-
printHeader(`Would evict ${toEvict.length} snapshot(s) (${
|
|
31729
|
+
printHeader(`Would evict ${toEvict.length} snapshot(s) (${formatBytes4(total - remaining)})`);
|
|
31330
31730
|
console.log();
|
|
31331
31731
|
for (const t of toEvict) {
|
|
31332
31732
|
const age = formatAge2(Date.now() - t.mtime);
|
|
31333
|
-
console.log(` ${pc23.dim(
|
|
31733
|
+
console.log(` ${pc23.dim(formatBytes4(t.size).padStart(8))} ${age.padStart(10)} ${t.path}`);
|
|
31334
31734
|
}
|
|
31335
31735
|
console.log();
|
|
31336
|
-
console.log(pc23.dim(`Total after eviction: ${
|
|
31736
|
+
console.log(pc23.dim(`Total after eviction: ${formatBytes4(remaining)} (cap: ${formatBytes4(maxBytes)})`));
|
|
31337
31737
|
return;
|
|
31338
31738
|
}
|
|
31339
31739
|
const freed = evictOldSnapshotsWithFlock(maxBytes);
|
|
31340
31740
|
if (freed > 0) {
|
|
31341
|
-
printSuccess(`Evicted ${
|
|
31741
|
+
printSuccess(`Evicted ${formatBytes4(freed)} (cap: ${formatBytes4(maxBytes)})`);
|
|
31342
31742
|
} else {
|
|
31343
31743
|
console.log(pc23.dim("No eviction needed (or another process held the lock)."));
|
|
31344
31744
|
}
|
|
@@ -31361,7 +31761,7 @@ function handleList2(worldIdFilter) {
|
|
|
31361
31761
|
lastWorldId = manifest.worldId;
|
|
31362
31762
|
}
|
|
31363
31763
|
const repo = manifest.repo ?? "(all repos)";
|
|
31364
|
-
const size =
|
|
31764
|
+
const size = formatBytes4(manifest.sizeBytes);
|
|
31365
31765
|
const age = formatAge2(entry.ageMs);
|
|
31366
31766
|
const kindColor = manifest.kind === "gems" ? pc23.magenta(manifest.kind) : manifest.kind === "node" ? pc23.cyan(manifest.kind) : pc23.yellow(manifest.kind);
|
|
31367
31767
|
console.log(
|
|
@@ -31382,7 +31782,7 @@ async function loadWorldMeta(worldId) {
|
|
|
31382
31782
|
}
|
|
31383
31783
|
return { workspacePath: world.workspacePath, repoNames: [...world.repos] };
|
|
31384
31784
|
}
|
|
31385
|
-
function
|
|
31785
|
+
function formatBytes4(bytes) {
|
|
31386
31786
|
if (bytes < 1024) return `${bytes}B`;
|
|
31387
31787
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
31388
31788
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
@@ -31402,33 +31802,33 @@ init_context();
|
|
|
31402
31802
|
init_output();
|
|
31403
31803
|
import * as fs45 from "node:fs";
|
|
31404
31804
|
import * as os24 from "node:os";
|
|
31405
|
-
import * as
|
|
31406
|
-
import { spawnSync as
|
|
31805
|
+
import * as path48 from "node:path";
|
|
31806
|
+
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
31407
31807
|
import ora10 from "ora";
|
|
31408
31808
|
|
|
31409
31809
|
// src/commands/refresh-helpers.ts
|
|
31410
31810
|
import * as fs44 from "node:fs";
|
|
31411
|
-
import * as
|
|
31811
|
+
import * as path47 from "node:path";
|
|
31412
31812
|
function collectCpSourceFiles(standaloneDir) {
|
|
31413
31813
|
if (!fs44.existsSync(standaloneDir)) {
|
|
31414
31814
|
throw new Error(`CP standalone dir not found: ${standaloneDir}`);
|
|
31415
31815
|
}
|
|
31416
31816
|
const entries = [];
|
|
31417
31817
|
const topLevel = fs44.readdirSync(standaloneDir).filter((f) => {
|
|
31418
|
-
const stat = fs44.statSync(
|
|
31818
|
+
const stat = fs44.statSync(path47.join(standaloneDir, f));
|
|
31419
31819
|
return stat.isFile() && f.endsWith(".mjs") && !f.endsWith(".test.mjs");
|
|
31420
31820
|
}).sort();
|
|
31421
31821
|
for (const f of topLevel) {
|
|
31422
|
-
entries.push({ srcPath:
|
|
31822
|
+
entries.push({ srcPath: path47.join(standaloneDir, f), destRelPath: f });
|
|
31423
31823
|
}
|
|
31424
|
-
const libDir =
|
|
31824
|
+
const libDir = path47.join(standaloneDir, "lib");
|
|
31425
31825
|
if (fs44.existsSync(libDir) && fs44.statSync(libDir).isDirectory()) {
|
|
31426
31826
|
const libFiles = fs44.readdirSync(libDir).filter((f) => {
|
|
31427
|
-
const stat = fs44.statSync(
|
|
31827
|
+
const stat = fs44.statSync(path47.join(libDir, f));
|
|
31428
31828
|
return stat.isFile() && f.endsWith(".mjs") && !f.endsWith(".test.mjs");
|
|
31429
31829
|
}).sort();
|
|
31430
31830
|
for (const f of libFiles) {
|
|
31431
|
-
entries.push({ srcPath:
|
|
31831
|
+
entries.push({ srcPath: path47.join(libDir, f), destRelPath: `lib/${f}` });
|
|
31432
31832
|
}
|
|
31433
31833
|
}
|
|
31434
31834
|
return entries;
|
|
@@ -31445,9 +31845,9 @@ function validateServerMjsPresent(entries) {
|
|
|
31445
31845
|
var HOST_CP_BASE_PORT = 19080;
|
|
31446
31846
|
var RESTART_TIMEOUT_S = 30;
|
|
31447
31847
|
var HEALTH_POLL_MS = 500;
|
|
31448
|
-
var
|
|
31848
|
+
var HEALTH_TIMEOUT_MS2 = 3e4;
|
|
31449
31849
|
function docker(args) {
|
|
31450
|
-
const result =
|
|
31850
|
+
const result = spawnSync19("docker", args, {
|
|
31451
31851
|
encoding: "utf-8",
|
|
31452
31852
|
stdio: ["ignore", "pipe", "pipe"]
|
|
31453
31853
|
});
|
|
@@ -31457,7 +31857,7 @@ function docker(args) {
|
|
|
31457
31857
|
stderr: result.stderr ?? ""
|
|
31458
31858
|
};
|
|
31459
31859
|
}
|
|
31460
|
-
async function waitForCpHealth(port2, timeoutMs =
|
|
31860
|
+
async function waitForCpHealth(port2, timeoutMs = HEALTH_TIMEOUT_MS2) {
|
|
31461
31861
|
const deadline = Date.now() + timeoutMs;
|
|
31462
31862
|
while (Date.now() < deadline) {
|
|
31463
31863
|
try {
|
|
@@ -31487,15 +31887,15 @@ async function refreshWorld(worldId, portOffset, standaloneDir, opts) {
|
|
|
31487
31887
|
};
|
|
31488
31888
|
}
|
|
31489
31889
|
const stagingDir = fs45.mkdtempSync(
|
|
31490
|
-
|
|
31890
|
+
path48.join(os24.tmpdir(), `olam-refresh-${worldId}-`)
|
|
31491
31891
|
);
|
|
31492
31892
|
try {
|
|
31493
31893
|
const hasLib = entries.some((e) => e.destRelPath.startsWith("lib/"));
|
|
31494
31894
|
if (hasLib) {
|
|
31495
|
-
fs45.mkdirSync(
|
|
31895
|
+
fs45.mkdirSync(path48.join(stagingDir, "lib"), { recursive: true });
|
|
31496
31896
|
}
|
|
31497
31897
|
for (const { srcPath, destRelPath } of entries) {
|
|
31498
|
-
fs45.copyFileSync(srcPath,
|
|
31898
|
+
fs45.copyFileSync(srcPath, path48.join(stagingDir, destRelPath));
|
|
31499
31899
|
}
|
|
31500
31900
|
const cpResult = docker([
|
|
31501
31901
|
"cp",
|
|
@@ -31529,7 +31929,7 @@ async function refreshWorld(worldId, portOffset, standaloneDir, opts) {
|
|
|
31529
31929
|
const cpPort = HOST_CP_BASE_PORT + portOffset;
|
|
31530
31930
|
const healthy = await waitForCpHealth(cpPort);
|
|
31531
31931
|
if (!healthy) {
|
|
31532
|
-
return { worldId, ok: true, error: `healthy but /health at :${cpPort} didn't respond within ${
|
|
31932
|
+
return { worldId, ok: true, error: `healthy but /health at :${cpPort} didn't respond within ${HEALTH_TIMEOUT_MS2 / 1e3}s` };
|
|
31533
31933
|
}
|
|
31534
31934
|
}
|
|
31535
31935
|
return { worldId, ok: true };
|
|
@@ -31547,7 +31947,7 @@ function registerRefresh(program2) {
|
|
|
31547
31947
|
process.exitCode = 1;
|
|
31548
31948
|
return;
|
|
31549
31949
|
}
|
|
31550
|
-
const standaloneDir =
|
|
31950
|
+
const standaloneDir = path48.join(
|
|
31551
31951
|
process.cwd(),
|
|
31552
31952
|
"packages/control-plane/standalone"
|
|
31553
31953
|
);
|
|
@@ -31625,637 +32025,256 @@ Run \`olam refresh\` from the olam repo root.`
|
|
|
31625
32025
|
}
|
|
31626
32026
|
if (results.some((r) => !r.ok)) {
|
|
31627
32027
|
process.exitCode = 1;
|
|
31628
|
-
}
|
|
31629
|
-
});
|
|
31630
|
-
}
|
|
31631
|
-
|
|
31632
|
-
// src/commands/restart.ts
|
|
31633
|
-
init_context();
|
|
31634
|
-
init_output();
|
|
31635
|
-
import { existsSync as
|
|
31636
|
-
import { dirname as dirname31, resolve as resolve15 } from "node:path";
|
|
31637
|
-
import { spawnSync as
|
|
31638
|
-
var RESTART_TIMEOUT_S2 = 30;
|
|
31639
|
-
function docker2(args) {
|
|
31640
|
-
const result =
|
|
31641
|
-
encoding: "utf-8",
|
|
31642
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
31643
|
-
});
|
|
31644
|
-
return {
|
|
31645
|
-
ok: result.status === 0 && result.error === void 0,
|
|
31646
|
-
stderr: result.stderr ?? ""
|
|
31647
|
-
};
|
|
31648
|
-
}
|
|
31649
|
-
function resolveRepoRoot2(start) {
|
|
31650
|
-
let cur = start;
|
|
31651
|
-
while (true) {
|
|
31652
|
-
if (existsSync50(resolve15(cur, "packages")) && existsSync50(resolve15(cur, "package.json"))) {
|
|
31653
|
-
return cur;
|
|
31654
|
-
}
|
|
31655
|
-
const parent = dirname31(cur);
|
|
31656
|
-
if (parent === cur) return start;
|
|
31657
|
-
cur = parent;
|
|
31658
|
-
}
|
|
31659
|
-
}
|
|
31660
|
-
function registerRestart(program2) {
|
|
31661
|
-
program2.command("restart").description("Restart a world container (auto-builds agent-stream bundle when stale)").argument("<world>", "World ID or name").action(async (worldIdOrName) => {
|
|
31662
|
-
const { ctx, error } = await loadContext();
|
|
31663
|
-
if (!ctx) {
|
|
31664
|
-
printError(error?.message ?? "Olam is not configured. Run `olam init` first.");
|
|
31665
|
-
process.exitCode = 1;
|
|
31666
|
-
return;
|
|
31667
|
-
}
|
|
31668
|
-
const world = ctx.worldManager.getWorld(worldIdOrName);
|
|
31669
|
-
if (!world) {
|
|
31670
|
-
printError(`World "${worldIdOrName}" not found. Run \`olam list\` to see worlds.`);
|
|
31671
|
-
process.exitCode = 1;
|
|
31672
|
-
return;
|
|
31673
|
-
}
|
|
31674
|
-
printHeader(`olam restart: ${world.name}`);
|
|
31675
|
-
try {
|
|
31676
|
-
const bundleSource = resolveBundleSource();
|
|
31677
|
-
if (bundleSource.mode === "install") {
|
|
31678
|
-
process.stderr.write("bundle source: install-mode; skipping rebuild\n");
|
|
31679
|
-
} else {
|
|
31680
|
-
const repoRoot = resolveRepoRoot2(process.cwd());
|
|
31681
|
-
const buildResult = buildIfStale(repoRoot);
|
|
31682
|
-
if (!buildResult.ok) {
|
|
31683
|
-
printError(
|
|
31684
|
-
"Agent-stream bundle build failed. Not restarting with a stale bundle.\nFix the build error and re-run `olam restart`."
|
|
31685
|
-
);
|
|
31686
|
-
process.exitCode = 1;
|
|
31687
|
-
return;
|
|
31688
|
-
}
|
|
31689
|
-
printInfo("Bundle", buildResult.message);
|
|
31690
|
-
}
|
|
31691
|
-
} catch (err) {
|
|
31692
|
-
if (err instanceof BundleSourceNotFoundError) {
|
|
31693
|
-
process.stderr.write(
|
|
31694
|
-
`Warning: agent-stream bundle path unresolved; container uses image-baked dist.
|
|
31695
|
-
`
|
|
31696
|
-
);
|
|
31697
|
-
} else {
|
|
31698
|
-
throw err;
|
|
31699
|
-
}
|
|
31700
|
-
}
|
|
31701
|
-
const containerName = `olam-${world.id}-devbox`;
|
|
31702
|
-
printInfo("Container", containerName);
|
|
31703
|
-
const result = docker2(["restart", "--time", String(RESTART_TIMEOUT_S2), containerName]);
|
|
31704
|
-
if (!result.ok) {
|
|
31705
|
-
printError(
|
|
31706
|
-
`docker restart failed: ${result.stderr.trim() || "(no stderr)"}`
|
|
31707
|
-
);
|
|
31708
|
-
process.exitCode = 1;
|
|
31709
|
-
return;
|
|
31710
|
-
}
|
|
31711
|
-
printInfo("Status", "restarted");
|
|
31712
|
-
});
|
|
31713
|
-
}
|
|
31714
|
-
|
|
31715
|
-
// src/commands/diagnose.ts
|
|
31716
|
-
import * as fs46 from "node:fs";
|
|
31717
|
-
import * as os25 from "node:os";
|
|
31718
|
-
import * as path48 from "node:path";
|
|
31719
|
-
import { execFileSync as execFileSync11, execSync as execSync12 } from "node:child_process";
|
|
31720
|
-
import pc24 from "picocolors";
|
|
31721
|
-
|
|
31722
|
-
// ../core/dist/diagnose/secret-stripper.js
|
|
31723
|
-
var SECRET_PATTERNS = [
|
|
31724
|
-
{ name: "anthropic-key", re: /sk-ant-[A-Za-z0-9\-_]{8,}/g },
|
|
31725
|
-
{ name: "github-pat", re: /gh[pourstu]_[A-Za-z0-9]{20,}/g },
|
|
31726
|
-
{ name: "github-pat-fgp", re: /github_pat_[A-Za-z0-9_]{22,}/g },
|
|
31727
|
-
{ name: "aws-access-key", re: /AKIA[A-Z0-9]{16}/g },
|
|
31728
|
-
{ name: "bearer-header", re: /Bearer\s+[A-Za-z0-9._\-+/=]{16,}/g },
|
|
31729
|
-
{ name: "slack-token", re: /xox[bpsa]-[A-Za-z0-9-]{10,}/g },
|
|
31730
|
-
{ name: "host-cp-token", re: /(?:host[-_.]?cp[-_.]?token|"token")\s*[:=]\s*"?([A-Za-z0-9._\-]{16,})"?/gi },
|
|
31731
|
-
{ name: "generic-hex-key", re: /\bkey\s*[:=]\s*[0-9a-f]{32,}/gi },
|
|
31732
|
-
{ name: "db-url", re: /(?:postgres|mysql|redis|mongodb):\/\/[^:]+:[^@]+@[^\s'"]+/gi },
|
|
31733
|
-
{ name: "env-secret", re: /(?:API_KEY|SECRET_KEY|AUTH_TOKEN|ACCESS_TOKEN|PRIVATE_KEY|DATABASE_URL|REDIS_URL)\s*[=:]\s*\S+/gi },
|
|
31734
|
-
{ name: "pem-key", re: /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC )?PRIVATE KEY-----/g }
|
|
31735
|
-
];
|
|
31736
|
-
var REDACTED = "[REDACTED]";
|
|
31737
|
-
function stripSecrets(input2) {
|
|
31738
|
-
let out = input2;
|
|
31739
|
-
for (const { re } of SECRET_PATTERNS) {
|
|
31740
|
-
re.lastIndex = 0;
|
|
31741
|
-
out = out.replace(re, (match2) => {
|
|
31742
|
-
const colonOrEq = match2.indexOf(":") !== -1 ? match2.indexOf(":") : match2.indexOf("=");
|
|
31743
|
-
if (colonOrEq !== -1 && (match2.includes("token") || match2.includes("key") || match2.includes("KEY"))) {
|
|
31744
|
-
return match2.slice(0, colonOrEq + 1) + " " + REDACTED;
|
|
31745
|
-
}
|
|
31746
|
-
return REDACTED;
|
|
31747
|
-
});
|
|
31748
|
-
}
|
|
31749
|
-
return out;
|
|
31750
|
-
}
|
|
31751
|
-
|
|
31752
|
-
// src/commands/diagnose.ts
|
|
31753
|
-
var DIAGNOSTICS_DIR = path48.join(os25.homedir(), ".olam", "diagnostics");
|
|
31754
|
-
var LOG_DIR = path48.join(os25.homedir(), ".olam", "log");
|
|
31755
|
-
var CACHE_DIR = path48.join(os25.homedir(), ".olam", "cache");
|
|
31756
|
-
var LOG_TAIL_LINES = 200;
|
|
31757
|
-
function safeExec(cmd) {
|
|
31758
|
-
try {
|
|
31759
|
-
return execSync12(cmd, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
31760
|
-
} catch {
|
|
31761
|
-
return "";
|
|
31762
|
-
}
|
|
31763
|
-
}
|
|
31764
|
-
function defaultZip(zipPath, files) {
|
|
31765
|
-
execFileSync11("zip", ["-j", zipPath, ...files]);
|
|
31766
|
-
}
|
|
31767
|
-
async function buildDiagnosticsZip(_exec = (cmd) => safeExec(cmd), _outDir = DIAGNOSTICS_DIR, _logDir = LOG_DIR, _zip = defaultZip) {
|
|
31768
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 23);
|
|
31769
|
-
const zipPath = path48.join(_outDir, `olam-diag-${ts}.zip`);
|
|
31770
|
-
const tmpDir = fs46.mkdtempSync(path48.join(os25.tmpdir(), "olam-diag-"));
|
|
31771
|
-
try {
|
|
31772
|
-
fs46.mkdirSync(_outDir, { recursive: true });
|
|
31773
|
-
const entries = [];
|
|
31774
|
-
const version = process.env["OLAM_CLI_VERSION"] ?? "unknown";
|
|
31775
|
-
const nodeVersion = process.version;
|
|
31776
|
-
const platform = `${process.platform}/${process.arch}`;
|
|
31777
|
-
const versionContent = `olam: ${version}
|
|
31778
|
-
node: ${nodeVersion}
|
|
31779
|
-
platform: ${platform}
|
|
31780
|
-
`;
|
|
31781
|
-
_writeEntry(tmpDir, "version.txt", stripSecrets(versionContent), entries);
|
|
31782
|
-
const osContent = [
|
|
31783
|
-
`os: ${os25.type()} ${os25.release()}`,
|
|
31784
|
-
`arch: ${os25.arch()}`,
|
|
31785
|
-
`uptime: ${os25.uptime()}s`
|
|
31786
|
-
].join("\n") + "\n";
|
|
31787
|
-
_writeEntry(tmpDir, "os-info.txt", stripSecrets(osContent), entries);
|
|
31788
|
-
const depsFile = path48.join(CACHE_DIR, "deps.json");
|
|
31789
|
-
if (fs46.existsSync(depsFile)) {
|
|
31790
|
-
const deps = fs46.readFileSync(depsFile, "utf-8");
|
|
31791
|
-
_writeEntry(tmpDir, "deps.json", stripSecrets(deps), entries);
|
|
31792
|
-
}
|
|
31793
|
-
const latestLog = _latestLog(_logDir);
|
|
31794
|
-
if (latestLog) {
|
|
31795
|
-
const lines = fs46.readFileSync(latestLog, "utf-8").split("\n");
|
|
31796
|
-
const tail = lines.slice(-LOG_TAIL_LINES).join("\n");
|
|
31797
|
-
_writeEntry(tmpDir, "log-tail.txt", stripSecrets(tail), entries);
|
|
31798
|
-
}
|
|
31799
|
-
const statusOut = _exec("olam status --json");
|
|
31800
|
-
if (statusOut) {
|
|
31801
|
-
_writeEntry(tmpDir, "status.json", stripSecrets(statusOut), entries);
|
|
31802
|
-
}
|
|
31803
|
-
const authAudit = _exec("npm run audit:auth-callers --if-present 2>&1");
|
|
31804
|
-
if (authAudit) {
|
|
31805
|
-
_writeEntry(tmpDir, "audit-auth-callers.txt", stripSecrets(authAudit), entries);
|
|
31806
|
-
}
|
|
31807
|
-
const fileArgs = entries.map((e) => path48.join(tmpDir, e));
|
|
31808
|
-
try {
|
|
31809
|
-
_zip(zipPath, fileArgs);
|
|
31810
|
-
} catch (err) {
|
|
31811
|
-
throw new Error(`zip command produced no output file. zip stderr: ${err.message}`);
|
|
31812
|
-
}
|
|
31813
|
-
if (!fs46.existsSync(zipPath)) {
|
|
31814
|
-
throw new Error("zip command produced no output file.");
|
|
31815
|
-
}
|
|
31816
|
-
return { zipPath, entries };
|
|
31817
|
-
} finally {
|
|
31818
|
-
try {
|
|
31819
|
-
fs46.rmSync(tmpDir, { recursive: true, force: true });
|
|
31820
|
-
} catch {
|
|
31821
|
-
}
|
|
31822
|
-
}
|
|
31823
|
-
}
|
|
31824
|
-
function _writeEntry(dir, name, content, entries) {
|
|
31825
|
-
fs46.writeFileSync(path48.join(dir, name), content, { mode: 420 });
|
|
31826
|
-
entries.push(name);
|
|
31827
|
-
}
|
|
31828
|
-
function _latestLog(logDir) {
|
|
31829
|
-
if (!fs46.existsSync(logDir)) return null;
|
|
31830
|
-
const files = fs46.readdirSync(logDir).filter((f) => f.startsWith("host-")).sort().reverse();
|
|
31831
|
-
return files.length > 0 ? path48.join(logDir, files[0]) : null;
|
|
31832
|
-
}
|
|
31833
|
-
async function buildTelemetryPayload() {
|
|
31834
|
-
const channel = "stable";
|
|
31835
|
-
const manifestFile = path48.join(CACHE_DIR, "manifest.json");
|
|
31836
|
-
let manifestAgeHours = null;
|
|
31837
|
-
if (fs46.existsSync(manifestFile)) {
|
|
31838
|
-
const mtime = fs46.statSync(manifestFile).mtime.getTime();
|
|
31839
|
-
manifestAgeHours = Math.round((Date.now() - mtime) / 36e5);
|
|
31840
|
-
}
|
|
31841
|
-
return {
|
|
31842
|
-
version: process.env["OLAM_CLI_VERSION"] ?? "unknown",
|
|
31843
|
-
os: process.platform,
|
|
31844
|
-
arch: process.arch,
|
|
31845
|
-
channel,
|
|
31846
|
-
manifest_age_hours: manifestAgeHours
|
|
31847
|
-
// No userid, no project, no path — privacy per design doc
|
|
31848
|
-
};
|
|
31849
|
-
}
|
|
31850
|
-
function registerDiagnose(program2) {
|
|
31851
|
-
program2.command("diagnose").description("Bundle diagnostics into a zip file for sharing with maintainers").option("--show-telemetry", "Print the telemetry payload that would be sent (no endpoint yet)").option("--no-telemetry", "Suppress telemetry payload (future opt-out)").option("--upload", "Share zip with maintainers (endpoint not yet provisioned)").option("--quiet", "Suppress progress output").action(async (opts) => {
|
|
31852
|
-
if (opts.showTelemetry) {
|
|
31853
|
-
const payload = await buildTelemetryPayload();
|
|
31854
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
31855
|
-
return;
|
|
31856
|
-
}
|
|
31857
|
-
if (!opts.quiet) {
|
|
31858
|
-
console.log(pc24.cyan("Building diagnostics zip\u2026"));
|
|
31859
|
-
}
|
|
31860
|
-
try {
|
|
31861
|
-
const { zipPath, entries } = await buildDiagnosticsZip();
|
|
31862
|
-
if (!opts.quiet) {
|
|
31863
|
-
console.log(pc24.green(`Done: ${zipPath}`));
|
|
31864
|
-
console.log(pc24.dim(`Contents: ${entries.join(", ")}`));
|
|
31865
|
-
}
|
|
31866
|
-
if (opts.upload) {
|
|
31867
|
-
console.log(pc24.yellow(
|
|
31868
|
-
`Telemetry endpoint not yet provisioned. Share the zip manually:
|
|
31869
|
-
File: ${zipPath}
|
|
31870
|
-
See docs/runbooks/share-diagnostics.md for instructions.`
|
|
31871
|
-
));
|
|
31872
|
-
}
|
|
31873
|
-
} catch (err) {
|
|
31874
|
-
console.error(pc24.red(`Diagnose failed: ${err.message}`));
|
|
31875
|
-
process.exitCode = 1;
|
|
31876
|
-
}
|
|
31877
|
-
});
|
|
31878
|
-
}
|
|
31879
|
-
|
|
31880
|
-
// src/lib/health-probes.ts
|
|
31881
|
-
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
31882
|
-
import { existsSync as existsSync52, readdirSync as readdirSync13, readFileSync as readFileSync38, statSync as statSync15 } from "node:fs";
|
|
31883
|
-
import { homedir as homedir25 } from "node:os";
|
|
31884
|
-
import path49 from "node:path";
|
|
31885
|
-
|
|
31886
|
-
// src/lib/kg-caps.ts
|
|
31887
|
-
var SOFT_CAP_BYTES = 15e8;
|
|
31888
|
-
var HARD_CAP_BYTES = 5e9;
|
|
31889
|
-
|
|
31890
|
-
// src/lib/health-probes.ts
|
|
31891
|
-
var HEALTH_TIMEOUT_MS2 = 5e3;
|
|
31892
|
-
var COLIMA_010_WARN_TEXT = (
|
|
31893
|
-
// eslint-disable-next-line @typescript-eslint/quotes
|
|
31894
|
-
"Colima 0.10.1's --kubernetes mode is broken upstream. Either switch to OLAM_HOST_CP_ENGINE=docker (recommended) or downgrade Colima to 0.9.x. Track abiosoft/colima issues for fix."
|
|
31895
|
-
);
|
|
31896
|
-
var COLIMA_010_RANGE = /^v?0\.10\.\d+$/;
|
|
31897
|
-
var defaultDockerExec2 = (cmd, args) => {
|
|
31898
|
-
const r = spawnSync20(cmd, [...args], {
|
|
32028
|
+
}
|
|
32029
|
+
});
|
|
32030
|
+
}
|
|
32031
|
+
|
|
32032
|
+
// src/commands/restart.ts
|
|
32033
|
+
init_context();
|
|
32034
|
+
init_output();
|
|
32035
|
+
import { existsSync as existsSync51 } from "node:fs";
|
|
32036
|
+
import { dirname as dirname31, resolve as resolve15 } from "node:path";
|
|
32037
|
+
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
32038
|
+
var RESTART_TIMEOUT_S2 = 30;
|
|
32039
|
+
function docker2(args) {
|
|
32040
|
+
const result = spawnSync20("docker", args, {
|
|
31899
32041
|
encoding: "utf-8",
|
|
31900
32042
|
stdio: ["ignore", "pipe", "pipe"]
|
|
31901
32043
|
});
|
|
31902
32044
|
return {
|
|
31903
|
-
|
|
31904
|
-
|
|
31905
|
-
stderr: r.stderr ?? ""
|
|
31906
|
-
};
|
|
31907
|
-
};
|
|
31908
|
-
var defaultFetch = (input2, init) => fetch(input2, init);
|
|
31909
|
-
async function probeDockerDaemon(dockerExec = defaultDockerExec2) {
|
|
31910
|
-
const r = dockerExec("docker", ["info", "--format", "{{.ServerVersion}}"]);
|
|
31911
|
-
if (r.status === 0 && r.stdout.trim().length > 0) {
|
|
31912
|
-
return { ok: true, message: `docker ${r.stdout.trim()} reachable` };
|
|
31913
|
-
}
|
|
31914
|
-
return {
|
|
31915
|
-
ok: false,
|
|
31916
|
-
message: "docker daemon not reachable",
|
|
31917
|
-
remedy: "Start Docker Desktop (or your docker daemon) and re-run."
|
|
32045
|
+
ok: result.status === 0 && result.error === void 0,
|
|
32046
|
+
stderr: result.stderr ?? ""
|
|
31918
32047
|
};
|
|
31919
32048
|
}
|
|
31920
|
-
|
|
31921
|
-
|
|
31922
|
-
|
|
31923
|
-
if (
|
|
31924
|
-
return
|
|
31925
|
-
ok: false,
|
|
31926
|
-
message: `image ${ref} not present locally`,
|
|
31927
|
-
remedy: "Run `olam bootstrap` to pull the published image, or build locally."
|
|
31928
|
-
};
|
|
32049
|
+
function resolveRepoRoot2(start) {
|
|
32050
|
+
let cur = start;
|
|
32051
|
+
while (true) {
|
|
32052
|
+
if (existsSync51(resolve15(cur, "packages")) && existsSync51(resolve15(cur, "package.json"))) {
|
|
32053
|
+
return cur;
|
|
31929
32054
|
}
|
|
32055
|
+
const parent = dirname31(cur);
|
|
32056
|
+
if (parent === cur) return start;
|
|
32057
|
+
cur = parent;
|
|
31930
32058
|
}
|
|
31931
|
-
return { ok: true, message: `${refs.length} image(s) present` };
|
|
31932
32059
|
}
|
|
31933
|
-
|
|
31934
|
-
|
|
31935
|
-
|
|
31936
|
-
|
|
31937
|
-
|
|
31938
|
-
|
|
31939
|
-
|
|
31940
|
-
return {
|
|
31941
|
-
ok: false,
|
|
31942
|
-
message: `host-cp /health returned ${res.status}`,
|
|
31943
|
-
remedy: "Restart host-cp via `olam host-cp start`; verify port 19000 is bound to 127.0.0.1."
|
|
31944
|
-
};
|
|
32060
|
+
function registerRestart(program2) {
|
|
32061
|
+
program2.command("restart").description("Restart a world container (auto-builds agent-stream bundle when stale)").argument("<world>", "World ID or name").action(async (worldIdOrName) => {
|
|
32062
|
+
const { ctx, error } = await loadContext();
|
|
32063
|
+
if (!ctx) {
|
|
32064
|
+
printError(error?.message ?? "Olam is not configured. Run `olam init` first.");
|
|
32065
|
+
process.exitCode = 1;
|
|
32066
|
+
return;
|
|
31945
32067
|
}
|
|
31946
|
-
|
|
31947
|
-
|
|
32068
|
+
const world = ctx.worldManager.getWorld(worldIdOrName);
|
|
32069
|
+
if (!world) {
|
|
32070
|
+
printError(`World "${worldIdOrName}" not found. Run \`olam list\` to see worlds.`);
|
|
32071
|
+
process.exitCode = 1;
|
|
32072
|
+
return;
|
|
31948
32073
|
}
|
|
31949
|
-
|
|
31950
|
-
|
|
31951
|
-
|
|
31952
|
-
|
|
31953
|
-
|
|
31954
|
-
|
|
31955
|
-
|
|
31956
|
-
|
|
31957
|
-
|
|
31958
|
-
|
|
31959
|
-
|
|
31960
|
-
|
|
31961
|
-
|
|
31962
|
-
|
|
31963
|
-
|
|
31964
|
-
|
|
31965
|
-
}
|
|
31966
|
-
function isHostCpHealthEnvelope(body) {
|
|
31967
|
-
let parsed;
|
|
31968
|
-
try {
|
|
31969
|
-
parsed = JSON.parse(body);
|
|
31970
|
-
} catch {
|
|
31971
|
-
return false;
|
|
31972
|
-
}
|
|
31973
|
-
if (!parsed || typeof parsed !== "object") return false;
|
|
31974
|
-
const obj = parsed;
|
|
31975
|
-
if (typeof obj.phase !== "string") return false;
|
|
31976
|
-
if (!obj.cache || typeof obj.cache !== "object") return false;
|
|
31977
|
-
const mode = obj.mode;
|
|
31978
|
-
if (!mode || typeof mode !== "object") return false;
|
|
31979
|
-
if (typeof mode.deployment !== "string") return false;
|
|
31980
|
-
return true;
|
|
31981
|
-
}
|
|
31982
|
-
async function probeAuthVault(olamHomeOverride) {
|
|
31983
|
-
const vaultPath = resolveAccountsPath(olamHomeOverride);
|
|
31984
|
-
if (!existsSync52(vaultPath)) {
|
|
31985
|
-
return {
|
|
31986
|
-
ok: false,
|
|
31987
|
-
message: `auth vault missing at ${vaultPath}`,
|
|
31988
|
-
remedy: "Run `olam auth up && olam auth login` to provision the vault."
|
|
31989
|
-
};
|
|
31990
|
-
}
|
|
31991
|
-
let parsed;
|
|
31992
|
-
try {
|
|
31993
|
-
parsed = JSON.parse(readFileSync38(vaultPath, "utf-8"));
|
|
31994
|
-
} catch (err) {
|
|
31995
|
-
return {
|
|
31996
|
-
ok: false,
|
|
31997
|
-
message: `auth vault malformed: ${err.message}`,
|
|
31998
|
-
remedy: "Inspect the accounts.json file; restore from backup or re-run `olam auth up`."
|
|
31999
|
-
};
|
|
32000
|
-
}
|
|
32001
|
-
if (!Array.isArray(parsed)) {
|
|
32002
|
-
return {
|
|
32003
|
-
ok: false,
|
|
32004
|
-
message: "auth vault malformed: expected JSON array at top level",
|
|
32005
|
-
remedy: "Restore the vault from backup or re-run `olam auth up && olam auth login`."
|
|
32006
|
-
};
|
|
32007
|
-
}
|
|
32008
|
-
const accounts = parsed.filter((a) => a !== null && typeof a === "object");
|
|
32009
|
-
const now = Date.now();
|
|
32010
|
-
const activeClaude = accounts.filter((a) => a.provider === "claude" && effectiveState2(a, now) === "active");
|
|
32011
|
-
if (activeClaude.length === 0) {
|
|
32012
|
-
return {
|
|
32013
|
-
ok: false,
|
|
32014
|
-
message: `no active claude credentials (vault has ${accounts.length} account(s))`,
|
|
32015
|
-
remedy: "Run `olam auth login` to add a credential, or wait for the next reset."
|
|
32016
|
-
};
|
|
32017
|
-
}
|
|
32018
|
-
return { ok: true, message: `${activeClaude.length} active claude credential(s)` };
|
|
32019
|
-
}
|
|
32020
|
-
function resolveAccountsPath(olamHomeOverride) {
|
|
32021
|
-
const explicit = process.env.OLAM_AUTH_DATA_PATH;
|
|
32022
|
-
if (explicit) return explicit;
|
|
32023
|
-
const olamHome5 = olamHomeOverride ?? process.env.OLAM_HOME ?? path49.join(homedir25(), ".olam");
|
|
32024
|
-
return path49.join(olamHome5, "auth-data", "accounts.json");
|
|
32025
|
-
}
|
|
32026
|
-
function effectiveState2(account, now) {
|
|
32027
|
-
const persisted = account.state ?? (account.rateLimited ? "cooldown" : "active");
|
|
32028
|
-
if (persisted === "disabled") return "disabled";
|
|
32029
|
-
if (typeof account.expiresAt === "number" && account.expiresAt <= now) return "expired";
|
|
32030
|
-
if (persisted === "cooldown") {
|
|
32031
|
-
const reset = account.rateLimitResetsAt ? new Date(account.rateLimitResetsAt).getTime() : 0;
|
|
32032
|
-
if (reset > 0 && reset <= now) return "active";
|
|
32033
|
-
}
|
|
32034
|
-
return persisted;
|
|
32035
|
-
}
|
|
32036
|
-
async function probeKgStorage(olamHomeOverride) {
|
|
32037
|
-
const olamHome5 = olamHomeOverride ?? process.env.OLAM_HOME ?? path49.join(homedir25(), ".olam");
|
|
32038
|
-
const kgRoot3 = path49.join(olamHome5, "kg");
|
|
32039
|
-
const worldsRoot3 = path49.join(olamHome5, "worlds");
|
|
32040
|
-
const kgBytes = enumerateGraphifyOut(kgRoot3, "pristine");
|
|
32041
|
-
const overlayBytes = enumerateGraphifyOut(worldsRoot3, "overlay");
|
|
32042
|
-
const totalBytes = kgBytes + overlayBytes;
|
|
32043
|
-
if (totalBytes >= HARD_CAP_BYTES) {
|
|
32044
|
-
return {
|
|
32045
|
-
ok: false,
|
|
32046
|
-
message: `KG storage ${formatBytes4(totalBytes)} exceeds 5 GB hard cap`,
|
|
32047
|
-
remedy: "Prune stale pristines: `rm -rf ~/.olam/kg/<workspace>` after operator review."
|
|
32048
|
-
};
|
|
32049
|
-
}
|
|
32050
|
-
if (totalBytes >= SOFT_CAP_BYTES) {
|
|
32051
|
-
return {
|
|
32052
|
-
ok: true,
|
|
32053
|
-
message: `KG storage ${formatBytes4(totalBytes)} (soft-warn \u2014 exceeds 1.5 GB; consider pruning)`
|
|
32054
|
-
};
|
|
32055
|
-
}
|
|
32056
|
-
return { ok: true, message: `KG storage ${formatBytes4(totalBytes)} (under cap)` };
|
|
32057
|
-
}
|
|
32058
|
-
function enumerateGraphifyOut(root, layout) {
|
|
32059
|
-
if (!existsSync52(root)) return 0;
|
|
32060
|
-
let total = 0;
|
|
32061
|
-
let entries;
|
|
32062
|
-
try {
|
|
32063
|
-
entries = readdirSync13(root, { withFileTypes: true });
|
|
32064
|
-
} catch {
|
|
32065
|
-
return 0;
|
|
32066
|
-
}
|
|
32067
|
-
for (const entry of entries) {
|
|
32068
|
-
if (!entry.isDirectory()) continue;
|
|
32069
|
-
if (layout === "pristine") {
|
|
32070
|
-
total += dirSizeBytes(path49.join(root, entry.name, "graphify-out"));
|
|
32071
|
-
} else {
|
|
32072
|
-
const worldDir = path49.join(root, entry.name);
|
|
32073
|
-
let clones;
|
|
32074
|
-
try {
|
|
32075
|
-
clones = readdirSync13(worldDir, { withFileTypes: true });
|
|
32076
|
-
} catch {
|
|
32077
|
-
continue;
|
|
32074
|
+
printHeader(`olam restart: ${world.name}`);
|
|
32075
|
+
try {
|
|
32076
|
+
const bundleSource = resolveBundleSource();
|
|
32077
|
+
if (bundleSource.mode === "install") {
|
|
32078
|
+
process.stderr.write("bundle source: install-mode; skipping rebuild\n");
|
|
32079
|
+
} else {
|
|
32080
|
+
const repoRoot = resolveRepoRoot2(process.cwd());
|
|
32081
|
+
const buildResult = buildIfStale(repoRoot);
|
|
32082
|
+
if (!buildResult.ok) {
|
|
32083
|
+
printError(
|
|
32084
|
+
"Agent-stream bundle build failed. Not restarting with a stale bundle.\nFix the build error and re-run `olam restart`."
|
|
32085
|
+
);
|
|
32086
|
+
process.exitCode = 1;
|
|
32087
|
+
return;
|
|
32088
|
+
}
|
|
32089
|
+
printInfo("Bundle", buildResult.message);
|
|
32078
32090
|
}
|
|
32079
|
-
|
|
32080
|
-
|
|
32081
|
-
|
|
32091
|
+
} catch (err) {
|
|
32092
|
+
if (err instanceof BundleSourceNotFoundError) {
|
|
32093
|
+
process.stderr.write(
|
|
32094
|
+
`Warning: agent-stream bundle path unresolved; container uses image-baked dist.
|
|
32095
|
+
`
|
|
32096
|
+
);
|
|
32097
|
+
} else {
|
|
32098
|
+
throw err;
|
|
32082
32099
|
}
|
|
32083
32100
|
}
|
|
32084
|
-
|
|
32085
|
-
|
|
32086
|
-
|
|
32087
|
-
|
|
32088
|
-
|
|
32089
|
-
|
|
32090
|
-
|
|
32091
|
-
|
|
32092
|
-
|
|
32093
|
-
let entries;
|
|
32094
|
-
try {
|
|
32095
|
-
entries = readdirSync13(cur, { withFileTypes: true });
|
|
32096
|
-
} catch {
|
|
32097
|
-
continue;
|
|
32101
|
+
const containerName = `olam-${world.id}-devbox`;
|
|
32102
|
+
printInfo("Container", containerName);
|
|
32103
|
+
const result = docker2(["restart", "--time", String(RESTART_TIMEOUT_S2), containerName]);
|
|
32104
|
+
if (!result.ok) {
|
|
32105
|
+
printError(
|
|
32106
|
+
`docker restart failed: ${result.stderr.trim() || "(no stderr)"}`
|
|
32107
|
+
);
|
|
32108
|
+
process.exitCode = 1;
|
|
32109
|
+
return;
|
|
32098
32110
|
}
|
|
32099
|
-
|
|
32100
|
-
|
|
32101
|
-
|
|
32102
|
-
|
|
32103
|
-
|
|
32104
|
-
|
|
32105
|
-
|
|
32106
|
-
|
|
32107
|
-
|
|
32108
|
-
|
|
32111
|
+
printInfo("Status", "restarted");
|
|
32112
|
+
});
|
|
32113
|
+
}
|
|
32114
|
+
|
|
32115
|
+
// src/commands/diagnose.ts
|
|
32116
|
+
import * as fs46 from "node:fs";
|
|
32117
|
+
import * as os25 from "node:os";
|
|
32118
|
+
import * as path49 from "node:path";
|
|
32119
|
+
import { execFileSync as execFileSync11, execSync as execSync12 } from "node:child_process";
|
|
32120
|
+
import pc24 from "picocolors";
|
|
32121
|
+
|
|
32122
|
+
// ../core/dist/diagnose/secret-stripper.js
|
|
32123
|
+
var SECRET_PATTERNS = [
|
|
32124
|
+
{ name: "anthropic-key", re: /sk-ant-[A-Za-z0-9\-_]{8,}/g },
|
|
32125
|
+
{ name: "github-pat", re: /gh[pourstu]_[A-Za-z0-9]{20,}/g },
|
|
32126
|
+
{ name: "github-pat-fgp", re: /github_pat_[A-Za-z0-9_]{22,}/g },
|
|
32127
|
+
{ name: "aws-access-key", re: /AKIA[A-Z0-9]{16}/g },
|
|
32128
|
+
{ name: "bearer-header", re: /Bearer\s+[A-Za-z0-9._\-+/=]{16,}/g },
|
|
32129
|
+
{ name: "slack-token", re: /xox[bpsa]-[A-Za-z0-9-]{10,}/g },
|
|
32130
|
+
{ name: "host-cp-token", re: /(?:host[-_.]?cp[-_.]?token|"token")\s*[:=]\s*"?([A-Za-z0-9._\-]{16,})"?/gi },
|
|
32131
|
+
{ name: "generic-hex-key", re: /\bkey\s*[:=]\s*[0-9a-f]{32,}/gi },
|
|
32132
|
+
{ name: "db-url", re: /(?:postgres|mysql|redis|mongodb):\/\/[^:]+:[^@]+@[^\s'"]+/gi },
|
|
32133
|
+
{ name: "env-secret", re: /(?:API_KEY|SECRET_KEY|AUTH_TOKEN|ACCESS_TOKEN|PRIVATE_KEY|DATABASE_URL|REDIS_URL)\s*[=:]\s*\S+/gi },
|
|
32134
|
+
{ name: "pem-key", re: /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC )?PRIVATE KEY-----/g }
|
|
32135
|
+
];
|
|
32136
|
+
var REDACTED = "[REDACTED]";
|
|
32137
|
+
function stripSecrets(input2) {
|
|
32138
|
+
let out = input2;
|
|
32139
|
+
for (const { re } of SECRET_PATTERNS) {
|
|
32140
|
+
re.lastIndex = 0;
|
|
32141
|
+
out = out.replace(re, (match2) => {
|
|
32142
|
+
const colonOrEq = match2.indexOf(":") !== -1 ? match2.indexOf(":") : match2.indexOf("=");
|
|
32143
|
+
if (colonOrEq !== -1 && (match2.includes("token") || match2.includes("key") || match2.includes("KEY"))) {
|
|
32144
|
+
return match2.slice(0, colonOrEq + 1) + " " + REDACTED;
|
|
32109
32145
|
}
|
|
32110
|
-
|
|
32146
|
+
return REDACTED;
|
|
32147
|
+
});
|
|
32111
32148
|
}
|
|
32112
|
-
return
|
|
32149
|
+
return out;
|
|
32113
32150
|
}
|
|
32114
|
-
|
|
32115
|
-
|
|
32116
|
-
|
|
32117
|
-
|
|
32118
|
-
|
|
32151
|
+
|
|
32152
|
+
// src/commands/diagnose.ts
|
|
32153
|
+
var DIAGNOSTICS_DIR = path49.join(os25.homedir(), ".olam", "diagnostics");
|
|
32154
|
+
var LOG_DIR = path49.join(os25.homedir(), ".olam", "log");
|
|
32155
|
+
var CACHE_DIR = path49.join(os25.homedir(), ".olam", "cache");
|
|
32156
|
+
var LOG_TAIL_LINES = 200;
|
|
32157
|
+
function safeExec(cmd) {
|
|
32158
|
+
try {
|
|
32159
|
+
return execSync12(cmd, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
32160
|
+
} catch {
|
|
32161
|
+
return "";
|
|
32162
|
+
}
|
|
32119
32163
|
}
|
|
32120
|
-
|
|
32121
|
-
|
|
32122
|
-
|
|
32164
|
+
function defaultZip(zipPath, files) {
|
|
32165
|
+
execFileSync11("zip", ["-j", zipPath, ...files]);
|
|
32166
|
+
}
|
|
32167
|
+
async function buildDiagnosticsZip(_exec = (cmd) => safeExec(cmd), _outDir = DIAGNOSTICS_DIR, _logDir = LOG_DIR, _zip = defaultZip) {
|
|
32168
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 23);
|
|
32169
|
+
const zipPath = path49.join(_outDir, `olam-diag-${ts}.zip`);
|
|
32170
|
+
const tmpDir = fs46.mkdtempSync(path49.join(os25.tmpdir(), "olam-diag-"));
|
|
32123
32171
|
try {
|
|
32124
|
-
|
|
32125
|
-
|
|
32126
|
-
|
|
32127
|
-
|
|
32128
|
-
|
|
32129
|
-
|
|
32130
|
-
|
|
32131
|
-
|
|
32172
|
+
fs46.mkdirSync(_outDir, { recursive: true });
|
|
32173
|
+
const entries = [];
|
|
32174
|
+
const version = process.env["OLAM_CLI_VERSION"] ?? "unknown";
|
|
32175
|
+
const nodeVersion = process.version;
|
|
32176
|
+
const platform = `${process.platform}/${process.arch}`;
|
|
32177
|
+
const versionContent = `olam: ${version}
|
|
32178
|
+
node: ${nodeVersion}
|
|
32179
|
+
platform: ${platform}
|
|
32180
|
+
`;
|
|
32181
|
+
_writeEntry(tmpDir, "version.txt", stripSecrets(versionContent), entries);
|
|
32182
|
+
const osContent = [
|
|
32183
|
+
`os: ${os25.type()} ${os25.release()}`,
|
|
32184
|
+
`arch: ${os25.arch()}`,
|
|
32185
|
+
`uptime: ${os25.uptime()}s`
|
|
32186
|
+
].join("\n") + "\n";
|
|
32187
|
+
_writeEntry(tmpDir, "os-info.txt", stripSecrets(osContent), entries);
|
|
32188
|
+
const depsFile = path49.join(CACHE_DIR, "deps.json");
|
|
32189
|
+
if (fs46.existsSync(depsFile)) {
|
|
32190
|
+
const deps = fs46.readFileSync(depsFile, "utf-8");
|
|
32191
|
+
_writeEntry(tmpDir, "deps.json", stripSecrets(deps), entries);
|
|
32132
32192
|
}
|
|
32133
|
-
const
|
|
32134
|
-
if (
|
|
32135
|
-
|
|
32136
|
-
|
|
32137
|
-
|
|
32138
|
-
};
|
|
32193
|
+
const latestLog = _latestLog(_logDir);
|
|
32194
|
+
if (latestLog) {
|
|
32195
|
+
const lines = fs46.readFileSync(latestLog, "utf-8").split("\n");
|
|
32196
|
+
const tail = lines.slice(-LOG_TAIL_LINES).join("\n");
|
|
32197
|
+
_writeEntry(tmpDir, "log-tail.txt", stripSecrets(tail), entries);
|
|
32139
32198
|
}
|
|
32140
|
-
|
|
32141
|
-
|
|
32142
|
-
|
|
32143
|
-
|
|
32144
|
-
|
|
32145
|
-
|
|
32146
|
-
|
|
32147
|
-
|
|
32148
|
-
|
|
32149
|
-
|
|
32150
|
-
|
|
32151
|
-
|
|
32152
|
-
|
|
32199
|
+
const statusOut = _exec("olam status --json");
|
|
32200
|
+
if (statusOut) {
|
|
32201
|
+
_writeEntry(tmpDir, "status.json", stripSecrets(statusOut), entries);
|
|
32202
|
+
}
|
|
32203
|
+
const authAudit = _exec("npm run audit:auth-callers --if-present 2>&1");
|
|
32204
|
+
if (authAudit) {
|
|
32205
|
+
_writeEntry(tmpDir, "audit-auth-callers.txt", stripSecrets(authAudit), entries);
|
|
32206
|
+
}
|
|
32207
|
+
const fileArgs = entries.map((e) => path49.join(tmpDir, e));
|
|
32208
|
+
try {
|
|
32209
|
+
_zip(zipPath, fileArgs);
|
|
32210
|
+
} catch (err) {
|
|
32211
|
+
throw new Error(`zip command produced no output file. zip stderr: ${err.message}`);
|
|
32212
|
+
}
|
|
32213
|
+
if (!fs46.existsSync(zipPath)) {
|
|
32214
|
+
throw new Error("zip command produced no output file.");
|
|
32215
|
+
}
|
|
32216
|
+
return { zipPath, entries };
|
|
32153
32217
|
} finally {
|
|
32154
|
-
clearTimeout(timeout);
|
|
32155
|
-
}
|
|
32156
|
-
}
|
|
32157
|
-
async function probeColimaVersion(dockerExec = defaultDockerExec2) {
|
|
32158
|
-
const r = dockerExec("colima", ["version"]);
|
|
32159
|
-
if (r.status === null || r.status !== 0 && /not found|ENOENT|not.found/i.test(r.stderr)) {
|
|
32160
|
-
return { ok: true, message: "colima not installed (not in use)" };
|
|
32161
|
-
}
|
|
32162
|
-
if (r.status !== 0) {
|
|
32163
|
-
return {
|
|
32164
|
-
ok: true,
|
|
32165
|
-
message: `colima present but version probe failed: ${(r.stderr || "").trim()}`
|
|
32166
|
-
};
|
|
32167
|
-
}
|
|
32168
|
-
const tokens = r.stdout.split(/\s+/);
|
|
32169
|
-
const version = tokens.find((t) => /^v?\d+\.\d+\.\d+/.test(t)) ?? "";
|
|
32170
|
-
if (!version) {
|
|
32171
|
-
return {
|
|
32172
|
-
ok: true,
|
|
32173
|
-
message: `colima present but version unrecognised: ${r.stdout.trim().slice(0, 80)}`
|
|
32174
|
-
};
|
|
32175
|
-
}
|
|
32176
|
-
if (COLIMA_010_RANGE.test(version)) {
|
|
32177
|
-
return {
|
|
32178
|
-
ok: true,
|
|
32179
|
-
warn: true,
|
|
32180
|
-
message: `colima ${version} detected \u2014 known kubernetes-mode regression`,
|
|
32181
|
-
remedy: COLIMA_010_WARN_TEXT
|
|
32182
|
-
};
|
|
32183
|
-
}
|
|
32184
|
-
return { ok: true, message: `colima ${version} (clear)` };
|
|
32185
|
-
}
|
|
32186
|
-
async function probeK8sApiReachable(exec = defaultDockerExec2) {
|
|
32187
|
-
const r = exec("kubectl", ["version", "--output=json", "--request-timeout=2s"]);
|
|
32188
|
-
if (r.status === 0 && r.stdout.length > 0) {
|
|
32189
|
-
let parsed;
|
|
32190
32218
|
try {
|
|
32191
|
-
|
|
32219
|
+
fs46.rmSync(tmpDir, { recursive: true, force: true });
|
|
32192
32220
|
} catch {
|
|
32193
|
-
parsed = null;
|
|
32194
32221
|
}
|
|
32195
|
-
|
|
32196
|
-
|
|
32197
|
-
|
|
32198
|
-
|
|
32222
|
+
}
|
|
32223
|
+
}
|
|
32224
|
+
function _writeEntry(dir, name, content, entries) {
|
|
32225
|
+
fs46.writeFileSync(path49.join(dir, name), content, { mode: 420 });
|
|
32226
|
+
entries.push(name);
|
|
32227
|
+
}
|
|
32228
|
+
function _latestLog(logDir) {
|
|
32229
|
+
if (!fs46.existsSync(logDir)) return null;
|
|
32230
|
+
const files = fs46.readdirSync(logDir).filter((f) => f.startsWith("host-")).sort().reverse();
|
|
32231
|
+
return files.length > 0 ? path49.join(logDir, files[0]) : null;
|
|
32232
|
+
}
|
|
32233
|
+
async function buildTelemetryPayload() {
|
|
32234
|
+
const channel = "stable";
|
|
32235
|
+
const manifestFile = path49.join(CACHE_DIR, "manifest.json");
|
|
32236
|
+
let manifestAgeHours = null;
|
|
32237
|
+
if (fs46.existsSync(manifestFile)) {
|
|
32238
|
+
const mtime = fs46.statSync(manifestFile).mtime.getTime();
|
|
32239
|
+
manifestAgeHours = Math.round((Date.now() - mtime) / 36e5);
|
|
32199
32240
|
}
|
|
32200
32241
|
return {
|
|
32201
|
-
|
|
32202
|
-
|
|
32203
|
-
|
|
32242
|
+
version: process.env["OLAM_CLI_VERSION"] ?? "unknown",
|
|
32243
|
+
os: process.platform,
|
|
32244
|
+
arch: process.arch,
|
|
32245
|
+
channel,
|
|
32246
|
+
manifest_age_hours: manifestAgeHours
|
|
32247
|
+
// No userid, no project, no path — privacy per design doc
|
|
32204
32248
|
};
|
|
32205
32249
|
}
|
|
32206
|
-
|
|
32207
|
-
|
|
32208
|
-
|
|
32209
|
-
|
|
32210
|
-
|
|
32211
|
-
|
|
32212
|
-
remedy: "Confirm the cluster is up and your context points at it; re-run `olam doctor`."
|
|
32213
|
-
};
|
|
32214
|
-
}
|
|
32215
|
-
let parsed;
|
|
32216
|
-
try {
|
|
32217
|
-
parsed = JSON.parse(r.stdout);
|
|
32218
|
-
} catch {
|
|
32219
|
-
return {
|
|
32220
|
-
ok: false,
|
|
32221
|
-
message: "cluster node list returned malformed output",
|
|
32222
|
-
remedy: "Restart your cluster; if persistent, re-create it."
|
|
32223
|
-
};
|
|
32224
|
-
}
|
|
32225
|
-
const nodes = parsed && typeof parsed === "object" ? parsed.items : void 0;
|
|
32226
|
-
if (!Array.isArray(nodes) || nodes.length === 0) {
|
|
32227
|
-
return {
|
|
32228
|
-
ok: false,
|
|
32229
|
-
message: "cluster has no nodes",
|
|
32230
|
-
remedy: "Start the cluster and try again."
|
|
32231
|
-
};
|
|
32232
|
-
}
|
|
32233
|
-
const presentImages = /* @__PURE__ */ new Set();
|
|
32234
|
-
for (const node of nodes) {
|
|
32235
|
-
const status2 = node.status;
|
|
32236
|
-
const images = status2?.images;
|
|
32237
|
-
if (!Array.isArray(images)) continue;
|
|
32238
|
-
for (const img of images) {
|
|
32239
|
-
const names = img.names;
|
|
32240
|
-
if (!Array.isArray(names)) continue;
|
|
32241
|
-
for (const n of names) presentImages.add(n);
|
|
32250
|
+
function registerDiagnose(program2) {
|
|
32251
|
+
program2.command("diagnose").description("Bundle diagnostics into a zip file for sharing with maintainers").option("--show-telemetry", "Print the telemetry payload that would be sent (no endpoint yet)").option("--no-telemetry", "Suppress telemetry payload (future opt-out)").option("--upload", "Share zip with maintainers (endpoint not yet provisioned)").option("--quiet", "Suppress progress output").action(async (opts) => {
|
|
32252
|
+
if (opts.showTelemetry) {
|
|
32253
|
+
const payload = await buildTelemetryPayload();
|
|
32254
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
32255
|
+
return;
|
|
32242
32256
|
}
|
|
32243
|
-
|
|
32244
|
-
|
|
32245
|
-
if (!hasImageRef(presentImages, ref)) {
|
|
32246
|
-
return {
|
|
32247
|
-
ok: false,
|
|
32248
|
-
message: `image ${ref} not present on any cluster node`,
|
|
32249
|
-
remedy: "Run `olam bootstrap` to import the published image into the cluster."
|
|
32250
|
-
};
|
|
32257
|
+
if (!opts.quiet) {
|
|
32258
|
+
console.log(pc24.cyan("Building diagnostics zip\u2026"));
|
|
32251
32259
|
}
|
|
32252
|
-
|
|
32253
|
-
|
|
32254
|
-
|
|
32255
|
-
|
|
32256
|
-
|
|
32257
|
-
|
|
32258
|
-
|
|
32260
|
+
try {
|
|
32261
|
+
const { zipPath, entries } = await buildDiagnosticsZip();
|
|
32262
|
+
if (!opts.quiet) {
|
|
32263
|
+
console.log(pc24.green(`Done: ${zipPath}`));
|
|
32264
|
+
console.log(pc24.dim(`Contents: ${entries.join(", ")}`));
|
|
32265
|
+
}
|
|
32266
|
+
if (opts.upload) {
|
|
32267
|
+
console.log(pc24.yellow(
|
|
32268
|
+
`Telemetry endpoint not yet provisioned. Share the zip manually:
|
|
32269
|
+
File: ${zipPath}
|
|
32270
|
+
See docs/runbooks/share-diagnostics.md for instructions.`
|
|
32271
|
+
));
|
|
32272
|
+
}
|
|
32273
|
+
} catch (err) {
|
|
32274
|
+
console.error(pc24.red(`Diagnose failed: ${err.message}`));
|
|
32275
|
+
process.exitCode = 1;
|
|
32276
|
+
}
|
|
32277
|
+
});
|
|
32259
32278
|
}
|
|
32260
32279
|
|
|
32261
32280
|
// src/lib/bundle-freshness.ts
|
|
@@ -32497,7 +32516,8 @@ async function runDoctor(opts, deps = {}) {
|
|
|
32497
32516
|
const nodeMemResult = probeNodeMemory(dockerExec, k8sCtx);
|
|
32498
32517
|
rows.push({ name: "node memory", result: nodeMemResult, position: 27 });
|
|
32499
32518
|
const socketCheckImpl = deps.dockerSocketCheckImpl;
|
|
32500
|
-
const
|
|
32519
|
+
const detectClusterTypeImpl = deps.detectK8sClusterTypeImpl;
|
|
32520
|
+
const socketResult = await probeDockerSocketBindMount(k8sCtx, wrapImpl, socketCheckImpl, detectClusterTypeImpl);
|
|
32501
32521
|
rows.push({ name: "docker socket bind-mount", result: socketResult, position: 28 });
|
|
32502
32522
|
} else {
|
|
32503
32523
|
const bundleResult = await probeBundleFreshness({
|
|
@@ -32731,19 +32751,26 @@ async function defaultDockerSocketCheck(kubectlContext, wrapImpl) {
|
|
|
32731
32751
|
);
|
|
32732
32752
|
return result.ok;
|
|
32733
32753
|
}
|
|
32734
|
-
async function probeDockerSocketBindMount(kubectlContext, wrapImpl, checkImpl) {
|
|
32754
|
+
async function probeDockerSocketBindMount(kubectlContext, wrapImpl, checkImpl, detectClusterTypeImpl) {
|
|
32735
32755
|
const check = checkImpl ?? defaultDockerSocketCheck;
|
|
32736
32756
|
const accessible = await check(kubectlContext, wrapImpl);
|
|
32737
32757
|
if (accessible) {
|
|
32738
32758
|
return { ok: true, message: "docker socket accessible at /var/run/docker.sock" };
|
|
32739
32759
|
}
|
|
32760
|
+
const clusterType = (detectClusterTypeImpl ?? defaultDetectK8sClusterType)(kubectlContext);
|
|
32740
32761
|
return {
|
|
32741
32762
|
ok: true,
|
|
32742
32763
|
warn: true,
|
|
32743
32764
|
message: "docker socket not accessible at /var/run/docker.sock",
|
|
32744
|
-
remedy:
|
|
32765
|
+
remedy: buildDockerSocketRemedy(clusterType)
|
|
32745
32766
|
};
|
|
32746
32767
|
}
|
|
32768
|
+
async function runK8sPreflight(opts, deps = {}) {
|
|
32769
|
+
const dockerExec = deps.dockerExec;
|
|
32770
|
+
const result = await probeK8sApiReachable(dockerExec);
|
|
32771
|
+
const rows = [{ name: "api server", result, position: 1 }];
|
|
32772
|
+
return emit(makeReport(rows), opts);
|
|
32773
|
+
}
|
|
32747
32774
|
function makeReport(rows) {
|
|
32748
32775
|
const failureCount = rows.filter((r) => !r.result.ok).length;
|
|
32749
32776
|
const warnCount = rows.filter((r) => isWarn(r.result)).length;
|
|
@@ -32968,6 +32995,7 @@ async function handleSet(target, deps = {}) {
|
|
|
32968
32995
|
const write = deps.writeConfig ?? writeConfig;
|
|
32969
32996
|
const emit2 = deps.emit ?? emitSubstrateSet;
|
|
32970
32997
|
const runDoctorFn = deps.runDoctor ?? runDoctor;
|
|
32998
|
+
const k8sPreflightFn = deps.runK8sPreflightFn ?? runK8sPreflight;
|
|
32971
32999
|
const countWorlds = deps.countWorlds ?? defaultCountWorlds;
|
|
32972
33000
|
if (target !== "compose" && target !== "kubernetes") {
|
|
32973
33001
|
stderr.write(
|
|
@@ -33011,7 +33039,11 @@ async function handleSet(target, deps = {}) {
|
|
|
33011
33039
|
`);
|
|
33012
33040
|
let doctorResult;
|
|
33013
33041
|
try {
|
|
33014
|
-
|
|
33042
|
+
if (engine === "kubernetes") {
|
|
33043
|
+
doctorResult = await k8sPreflightFn({ json: true });
|
|
33044
|
+
} else {
|
|
33045
|
+
doctorResult = await runDoctorFn({ json: true }, { engine });
|
|
33046
|
+
}
|
|
33015
33047
|
} catch (err) {
|
|
33016
33048
|
stderr.write(
|
|
33017
33049
|
`${pc26.red("error:")} pre-flight crashed: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -33507,6 +33539,11 @@ async function runProjectSweepPhase(opts, deps, sweepDeps = {}) {
|
|
|
33507
33539
|
// gitUrl is required by schema; file:// is in the URL_PATTERN allow-list.
|
|
33508
33540
|
gitUrl: `file://${projectsRoot}`,
|
|
33509
33541
|
trustMethod: "none",
|
|
33542
|
+
// Extra passthrough fields (rootPath, matchedCount, etc.) are
|
|
33543
|
+
// accepted via TrustAuditEntrySchema's `.passthrough()` — the
|
|
33544
|
+
// inferred type carries an index signature for unknown keys, so
|
|
33545
|
+
// TS accepts these without a cast. Schema discriminated-union to
|
|
33546
|
+
// land in follow-up plan-hard pass; see Phase D CP3 finding ADV-D-03.
|
|
33510
33547
|
rootPath: projectsRoot,
|
|
33511
33548
|
matchedCount: matches2.length,
|
|
33512
33549
|
builtCount,
|