@pleri/olam-cli 0.1.37 → 0.1.38
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/__tests__/upgrade.test.js +1 -1
- package/dist/__tests__/upgrade.test.js.map +1 -1
- package/dist/commands/__tests__/begin.test.d.ts +7 -0
- package/dist/commands/__tests__/begin.test.d.ts.map +1 -0
- package/dist/commands/__tests__/begin.test.js +72 -0
- package/dist/commands/__tests__/begin.test.js.map +1 -0
- package/dist/commands/__tests__/status.test.d.ts +8 -0
- package/dist/commands/__tests__/status.test.d.ts.map +1 -0
- package/dist/commands/__tests__/status.test.js +62 -0
- package/dist/commands/__tests__/status.test.js.map +1 -0
- package/dist/commands/__tests__/stop.test.d.ts +5 -0
- package/dist/commands/__tests__/stop.test.d.ts.map +1 -0
- package/dist/commands/__tests__/stop.test.js +30 -0
- package/dist/commands/__tests__/stop.test.js.map +1 -0
- package/dist/commands/__tests__/world-upgrade.test.d.ts +8 -0
- package/dist/commands/__tests__/world-upgrade.test.d.ts.map +1 -0
- package/dist/commands/__tests__/world-upgrade.test.js +73 -0
- package/dist/commands/__tests__/world-upgrade.test.js.map +1 -0
- package/dist/commands/auth-upgrade.d.ts.map +1 -1
- package/dist/commands/auth-upgrade.js +4 -2
- package/dist/commands/auth-upgrade.js.map +1 -1
- package/dist/commands/begin.d.ts +27 -0
- package/dist/commands/begin.d.ts.map +1 -0
- package/dist/commands/begin.js +45 -0
- package/dist/commands/begin.js.map +1 -0
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/commands/dispatch.js +5 -0
- package/dist/commands/dispatch.js.map +1 -1
- package/dist/commands/enter.d.ts.map +1 -1
- package/dist/commands/enter.js +6 -0
- package/dist/commands/enter.js.map +1 -1
- package/dist/commands/host-cp.d.ts +8 -0
- package/dist/commands/host-cp.d.ts.map +1 -1
- package/dist/commands/host-cp.js +9 -1
- package/dist/commands/host-cp.js.map +1 -1
- package/dist/commands/observe.d.ts.map +1 -1
- package/dist/commands/observe.js +5 -0
- package/dist/commands/observe.js.map +1 -1
- package/dist/commands/status.d.ts +33 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +98 -4
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/stop.d.ts +10 -0
- package/dist/commands/stop.d.ts.map +1 -0
- package/dist/commands/stop.js +17 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +27 -7
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/commands/world-snapshot.d.ts.map +1 -1
- package/dist/commands/world-snapshot.js +2 -3
- package/dist/commands/world-snapshot.js.map +1 -1
- package/dist/commands/world-upgrade.d.ts +33 -0
- package/dist/commands/world-upgrade.d.ts.map +1 -0
- package/dist/commands/world-upgrade.js +82 -0
- package/dist/commands/world-upgrade.js.map +1 -0
- package/dist/commands/world.d.ts +12 -0
- package/dist/commands/world.d.ts.map +1 -0
- package/dist/commands/world.js +18 -0
- package/dist/commands/world.js.map +1 -0
- package/dist/image-digests.json +3 -3
- package/dist/index.js +2147 -1756
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,6 +9,39 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/output.ts
|
|
13
|
+
import pc from "picocolors";
|
|
14
|
+
function printError(message) {
|
|
15
|
+
console.error(`${pc.red("error")} ${message}`);
|
|
16
|
+
}
|
|
17
|
+
function printSuccess(message) {
|
|
18
|
+
console.log(`${pc.green("ok")} ${message}`);
|
|
19
|
+
}
|
|
20
|
+
function printWarning(message) {
|
|
21
|
+
console.log(`${pc.yellow("warn")} ${message}`);
|
|
22
|
+
}
|
|
23
|
+
function printInfo(label, value) {
|
|
24
|
+
console.log(` ${pc.dim(label.padEnd(14))} ${value}`);
|
|
25
|
+
}
|
|
26
|
+
function printHeader(title) {
|
|
27
|
+
console.log(`
|
|
28
|
+
${pc.bold(title)}`);
|
|
29
|
+
}
|
|
30
|
+
function formatAge(createdAt) {
|
|
31
|
+
const ms = Date.now() - new Date(createdAt).getTime();
|
|
32
|
+
const minutes = Math.floor(ms / 6e4);
|
|
33
|
+
if (minutes < 60) return `${minutes}m`;
|
|
34
|
+
const hours = Math.floor(minutes / 60);
|
|
35
|
+
if (hours < 24) return `${hours}h ${minutes % 60}m`;
|
|
36
|
+
const days = Math.floor(hours / 24);
|
|
37
|
+
return `${days}d ${hours % 24}h`;
|
|
38
|
+
}
|
|
39
|
+
var init_output = __esm({
|
|
40
|
+
"src/output.ts"() {
|
|
41
|
+
"use strict";
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
12
45
|
// ../../node_modules/zod/v3/helpers/util.js
|
|
13
46
|
var util, objectUtil, ZodParsedType, getParsedType;
|
|
14
47
|
var init_util = __esm({
|
|
@@ -421,8 +454,8 @@ var init_parseUtil = __esm({
|
|
|
421
454
|
init_errors();
|
|
422
455
|
init_en();
|
|
423
456
|
makeIssue = (params) => {
|
|
424
|
-
const { data, path:
|
|
425
|
-
const fullPath = [...
|
|
457
|
+
const { data, path: path42, errorMaps, issueData } = params;
|
|
458
|
+
const fullPath = [...path42, ...issueData.path || []];
|
|
426
459
|
const fullIssue = {
|
|
427
460
|
...issueData,
|
|
428
461
|
path: fullPath
|
|
@@ -730,11 +763,11 @@ var init_types = __esm({
|
|
|
730
763
|
init_parseUtil();
|
|
731
764
|
init_util();
|
|
732
765
|
ParseInputLazyPath = class {
|
|
733
|
-
constructor(parent, value,
|
|
766
|
+
constructor(parent, value, path42, key) {
|
|
734
767
|
this._cachedPath = [];
|
|
735
768
|
this.parent = parent;
|
|
736
769
|
this.data = value;
|
|
737
|
-
this._path =
|
|
770
|
+
this._path = path42;
|
|
738
771
|
this._key = key;
|
|
739
772
|
}
|
|
740
773
|
get path() {
|
|
@@ -4221,7 +4254,7 @@ import YAML from "yaml";
|
|
|
4221
4254
|
function bootstrapStepCmd(entry) {
|
|
4222
4255
|
return typeof entry === "string" ? entry : entry.cmd;
|
|
4223
4256
|
}
|
|
4224
|
-
function refineForbiddenKeys(value,
|
|
4257
|
+
function refineForbiddenKeys(value, path42, ctx, rejectSource) {
|
|
4225
4258
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
4226
4259
|
return;
|
|
4227
4260
|
}
|
|
@@ -4229,12 +4262,12 @@ function refineForbiddenKeys(value, path40, ctx, rejectSource) {
|
|
|
4229
4262
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
4230
4263
|
ctx.addIssue({
|
|
4231
4264
|
code: external_exports.ZodIssueCode.custom,
|
|
4232
|
-
path: [...
|
|
4265
|
+
path: [...path42, key],
|
|
4233
4266
|
message: `forbidden key "${key}" (prototype-pollution surface)`
|
|
4234
4267
|
});
|
|
4235
4268
|
continue;
|
|
4236
4269
|
}
|
|
4237
|
-
if (rejectSource &&
|
|
4270
|
+
if (rejectSource && path42.length === 0 && key === "source") {
|
|
4238
4271
|
ctx.addIssue({
|
|
4239
4272
|
code: external_exports.ZodIssueCode.custom,
|
|
4240
4273
|
path: ["source"],
|
|
@@ -4244,30 +4277,30 @@ function refineForbiddenKeys(value, path40, ctx, rejectSource) {
|
|
|
4244
4277
|
}
|
|
4245
4278
|
refineForbiddenKeys(
|
|
4246
4279
|
value[key],
|
|
4247
|
-
[...
|
|
4280
|
+
[...path42, key],
|
|
4248
4281
|
ctx,
|
|
4249
4282
|
false
|
|
4250
4283
|
);
|
|
4251
4284
|
}
|
|
4252
4285
|
}
|
|
4253
|
-
function rejectForbiddenKeys(value,
|
|
4286
|
+
function rejectForbiddenKeys(value, path42, rejectSource) {
|
|
4254
4287
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
4255
4288
|
return;
|
|
4256
4289
|
}
|
|
4257
4290
|
for (const key of Object.keys(value)) {
|
|
4258
4291
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
4259
4292
|
throw new Error(
|
|
4260
|
-
`[manifest] ${
|
|
4293
|
+
`[manifest] ${path42}: forbidden key "${key}" (prototype-pollution surface)`
|
|
4261
4294
|
);
|
|
4262
4295
|
}
|
|
4263
4296
|
if (rejectSource && key === "source") {
|
|
4264
4297
|
throw new Error(
|
|
4265
|
-
`[manifest] ${
|
|
4298
|
+
`[manifest] ${path42}: top-level "source" is loader-stamped \u2014 manifests must not author it`
|
|
4266
4299
|
);
|
|
4267
4300
|
}
|
|
4268
4301
|
rejectForbiddenKeys(
|
|
4269
4302
|
value[key],
|
|
4270
|
-
`${
|
|
4303
|
+
`${path42}.${key}`,
|
|
4271
4304
|
false
|
|
4272
4305
|
);
|
|
4273
4306
|
}
|
|
@@ -5208,8 +5241,8 @@ var init_client = __esm({
|
|
|
5208
5241
|
throw new Error(`failed to report rate-limit for ${accountId} (HTTP ${res.status})`);
|
|
5209
5242
|
}
|
|
5210
5243
|
}
|
|
5211
|
-
async request(method,
|
|
5212
|
-
const url = `${this.baseUrl}${
|
|
5244
|
+
async request(method, path42, body, attempt = 0) {
|
|
5245
|
+
const url = `${this.baseUrl}${path42}`;
|
|
5213
5246
|
const controller = new AbortController();
|
|
5214
5247
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
5215
5248
|
const headers = {};
|
|
@@ -5225,7 +5258,7 @@ var init_client = __esm({
|
|
|
5225
5258
|
} catch (err) {
|
|
5226
5259
|
if (attempt < RETRY_COUNT && isTransient(err)) {
|
|
5227
5260
|
await sleep(RETRY_BACKOFF_MS * (attempt + 1));
|
|
5228
|
-
return this.request(method,
|
|
5261
|
+
return this.request(method, path42, body, attempt + 1);
|
|
5229
5262
|
}
|
|
5230
5263
|
throw err;
|
|
5231
5264
|
} finally {
|
|
@@ -6715,8 +6748,8 @@ var init_provider3 = __esm({
|
|
|
6715
6748
|
// -----------------------------------------------------------------------
|
|
6716
6749
|
// Internal fetch helper
|
|
6717
6750
|
// -----------------------------------------------------------------------
|
|
6718
|
-
async request(
|
|
6719
|
-
const url = `${this.config.workerUrl}${
|
|
6751
|
+
async request(path42, method, body) {
|
|
6752
|
+
const url = `${this.config.workerUrl}${path42}`;
|
|
6720
6753
|
const bearer = await this.config.mintToken();
|
|
6721
6754
|
const headers = {
|
|
6722
6755
|
Authorization: `Bearer ${bearer}`
|
|
@@ -6904,6 +6937,52 @@ var init_docker_host = __esm({
|
|
|
6904
6937
|
}
|
|
6905
6938
|
});
|
|
6906
6939
|
|
|
6940
|
+
// ../core/src/util/open-url.ts
|
|
6941
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
6942
|
+
function openUrl(url) {
|
|
6943
|
+
if (process.env.CI === "true") {
|
|
6944
|
+
return { opened: false, reason: "CI environment detected" };
|
|
6945
|
+
}
|
|
6946
|
+
if (!process.stdout.isTTY) {
|
|
6947
|
+
return { opened: false, reason: "non-interactive shell" };
|
|
6948
|
+
}
|
|
6949
|
+
if (process.platform === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
|
|
6950
|
+
return { opened: false, reason: "headless linux (no DISPLAY/WAYLAND_DISPLAY)" };
|
|
6951
|
+
}
|
|
6952
|
+
let cmd;
|
|
6953
|
+
let args;
|
|
6954
|
+
if (process.platform === "darwin") {
|
|
6955
|
+
cmd = "open";
|
|
6956
|
+
args = [url];
|
|
6957
|
+
} else if (process.platform === "win32") {
|
|
6958
|
+
cmd = "cmd";
|
|
6959
|
+
args = ["/c", "start", "", url];
|
|
6960
|
+
} else {
|
|
6961
|
+
cmd = "xdg-open";
|
|
6962
|
+
args = [url];
|
|
6963
|
+
}
|
|
6964
|
+
try {
|
|
6965
|
+
const result = spawnSync3(cmd, args, { stdio: "ignore", timeout: 5e3 });
|
|
6966
|
+
if (result.error) {
|
|
6967
|
+
return { opened: false, reason: `spawn ${cmd}: ${result.error.message}` };
|
|
6968
|
+
}
|
|
6969
|
+
if (result.status !== 0 && result.status !== null) {
|
|
6970
|
+
return { opened: false, reason: `${cmd} exited ${result.status}` };
|
|
6971
|
+
}
|
|
6972
|
+
return { opened: true };
|
|
6973
|
+
} catch (err) {
|
|
6974
|
+
return {
|
|
6975
|
+
opened: false,
|
|
6976
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
6977
|
+
};
|
|
6978
|
+
}
|
|
6979
|
+
}
|
|
6980
|
+
var init_open_url = __esm({
|
|
6981
|
+
"../core/src/util/open-url.ts"() {
|
|
6982
|
+
"use strict";
|
|
6983
|
+
}
|
|
6984
|
+
});
|
|
6985
|
+
|
|
6907
6986
|
// ../core/src/world/state.ts
|
|
6908
6987
|
var VALID_TRANSITIONS, WorldStateMachine;
|
|
6909
6988
|
var init_state = __esm({
|
|
@@ -7870,8 +7949,8 @@ import { execFileSync as execFileSync3 } from "node:child_process";
|
|
|
7870
7949
|
import * as fs13 from "node:fs";
|
|
7871
7950
|
import * as os9 from "node:os";
|
|
7872
7951
|
import * as path14 from "node:path";
|
|
7873
|
-
function expandHome(p,
|
|
7874
|
-
return p.replace(/^~(?=$|\/|\\)/,
|
|
7952
|
+
function expandHome(p, homedir21) {
|
|
7953
|
+
return p.replace(/^~(?=$|\/|\\)/, homedir21());
|
|
7875
7954
|
}
|
|
7876
7955
|
function sanitizeRepoFilename(name) {
|
|
7877
7956
|
const sanitized = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
@@ -7892,7 +7971,7 @@ ${stderr}`;
|
|
|
7892
7971
|
}
|
|
7893
7972
|
function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
|
|
7894
7973
|
const exec = deps.exec ?? ((cmd, args, opts) => execFileSync3(cmd, args, opts));
|
|
7895
|
-
const
|
|
7974
|
+
const homedir21 = deps.homedir ?? (() => os9.homedir());
|
|
7896
7975
|
const baselineDir = path14.join(workspacePath, ".olam", "baseline");
|
|
7897
7976
|
try {
|
|
7898
7977
|
fs13.mkdirSync(baselineDir, { recursive: true });
|
|
@@ -7907,7 +7986,7 @@ function snapshotBaselineDiff(repos, workspacePath, deps = {}) {
|
|
|
7907
7986
|
if (!repo.path) continue;
|
|
7908
7987
|
const filename = `${sanitizeRepoFilename(repo.name)}.diff`;
|
|
7909
7988
|
const outPath = path14.join(baselineDir, filename);
|
|
7910
|
-
const repoPath = expandHome(repo.path,
|
|
7989
|
+
const repoPath = expandHome(repo.path, homedir21);
|
|
7911
7990
|
if (!fs13.existsSync(repoPath)) {
|
|
7912
7991
|
writeBaselineFile(outPath, `# repo: ${repo.name}
|
|
7913
7992
|
# (skipped: path ${repoPath} does not exist)
|
|
@@ -11861,1688 +11940,1769 @@ var init_context = __esm({
|
|
|
11861
11940
|
}
|
|
11862
11941
|
});
|
|
11863
11942
|
|
|
11864
|
-
// src/
|
|
11865
|
-
var
|
|
11866
|
-
__export(
|
|
11867
|
-
|
|
11868
|
-
|
|
11869
|
-
|
|
11870
|
-
|
|
11943
|
+
// src/commands/host-cp.ts
|
|
11944
|
+
var host_cp_exports = {};
|
|
11945
|
+
__export(host_cp_exports, {
|
|
11946
|
+
HOST_CP_PORT: () => HOST_CP_PORT,
|
|
11947
|
+
authSecretPath: () => authSecretPath,
|
|
11948
|
+
buildComposeEnv: () => buildComposeEnv,
|
|
11949
|
+
callHostCpProxy: () => callHostCpProxy,
|
|
11950
|
+
callHostCpRegistry: () => callHostCpRegistry,
|
|
11951
|
+
captureGhToken: () => captureGhToken,
|
|
11952
|
+
findHostCpContainer: () => findHostCpContainer,
|
|
11953
|
+
gatherProbeFailureDiagnostics: () => gatherProbeFailureDiagnostics,
|
|
11954
|
+
openHostCpUrl: () => openUrl,
|
|
11955
|
+
probeHostCp: () => probeHostCp,
|
|
11956
|
+
r2CredentialsPath: () => r2CredentialsPath,
|
|
11957
|
+
readAuthSecret: () => readAuthSecret2,
|
|
11958
|
+
readPid: () => readPid,
|
|
11959
|
+
readR2Credentials: () => readR2Credentials,
|
|
11960
|
+
readToken: () => readToken,
|
|
11961
|
+
registerHostCp: () => registerHostCp,
|
|
11962
|
+
removePid: () => removePid,
|
|
11963
|
+
removeToken: () => removeToken,
|
|
11964
|
+
runCompose: () => runCompose,
|
|
11965
|
+
startHostCp: () => startHostCp,
|
|
11966
|
+
stopHostCp: () => stopHostCp,
|
|
11967
|
+
writePid: () => writePid,
|
|
11968
|
+
writeToken: () => writeToken
|
|
11871
11969
|
});
|
|
11872
|
-
import
|
|
11873
|
-
import
|
|
11874
|
-
import
|
|
11875
|
-
|
|
11876
|
-
|
|
11877
|
-
|
|
11970
|
+
import * as crypto5 from "node:crypto";
|
|
11971
|
+
import * as fs19 from "node:fs";
|
|
11972
|
+
import * as os12 from "node:os";
|
|
11973
|
+
import * as path22 from "node:path";
|
|
11974
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
11975
|
+
import Dockerode2 from "dockerode";
|
|
11976
|
+
function findComposeFile() {
|
|
11977
|
+
const candidates = [
|
|
11978
|
+
// Bundled path: dist/index.js lives at <pkg>/dist/; host-cp/ is a sibling of dist/
|
|
11979
|
+
path22.resolve(path22.dirname(new URL(import.meta.url).pathname), "../host-cp/compose.yaml"),
|
|
11980
|
+
// Source-mode: cwd is monorepo root
|
|
11981
|
+
path22.resolve(process.cwd(), "packages/host-cp/compose.yaml"),
|
|
11982
|
+
// Source-mode: cwd is one level inside the monorepo
|
|
11983
|
+
path22.resolve(process.cwd(), "../packages/host-cp/compose.yaml")
|
|
11984
|
+
];
|
|
11985
|
+
for (const c of candidates) {
|
|
11986
|
+
if (fs19.existsSync(c)) return c;
|
|
11987
|
+
}
|
|
11988
|
+
return path22.resolve(process.cwd(), "packages/host-cp/compose.yaml");
|
|
11878
11989
|
}
|
|
11879
|
-
function
|
|
11880
|
-
|
|
11881
|
-
const repoRoot = resolve6(installRootDir, "..", "..");
|
|
11882
|
-
return existsSync18(join24(repoRoot, "packages")) && existsSync18(join24(repoRoot, "package.json"));
|
|
11990
|
+
function olamHome() {
|
|
11991
|
+
return process.env.OLAM_HOME ?? path22.join(os12.homedir(), ".olam");
|
|
11883
11992
|
}
|
|
11884
|
-
function
|
|
11885
|
-
|
|
11886
|
-
if (!isDevMode(env, installRootDir)) {
|
|
11887
|
-
throw new MissingBuildScriptError(scriptRelPath);
|
|
11888
|
-
}
|
|
11889
|
-
const repoRoot = resolve6(installRootDir, "..", "..");
|
|
11890
|
-
return join24(repoRoot, scriptRelPath);
|
|
11993
|
+
function tokenPath() {
|
|
11994
|
+
return path22.join(olamHome(), "host-cp.token");
|
|
11891
11995
|
}
|
|
11892
|
-
|
|
11893
|
-
|
|
11894
|
-
"src/install-root.ts"() {
|
|
11895
|
-
"use strict";
|
|
11896
|
-
MissingBuildScriptError = class extends Error {
|
|
11897
|
-
constructor(scriptRelPath) {
|
|
11898
|
-
super(
|
|
11899
|
-
`Build script ${scriptRelPath} is not available in this CLI install.
|
|
11900
|
-
Source-build paths require a monorepo clone:
|
|
11901
|
-
git clone https://github.com/pleri/olam && cd olam
|
|
11902
|
-
OLAM_DEV=1 olam <command> --from-source
|
|
11903
|
-
For published-image upgrades (Phase B+), drop the --from-source flag
|
|
11904
|
-
and the CLI will pull pre-built images from ghcr.io/pleri/* by digest.`
|
|
11905
|
-
);
|
|
11906
|
-
this.name = "MissingBuildScriptError";
|
|
11907
|
-
}
|
|
11908
|
-
};
|
|
11909
|
-
}
|
|
11910
|
-
});
|
|
11911
|
-
|
|
11912
|
-
// src/protocol-version.ts
|
|
11913
|
-
var protocol_version_exports = {};
|
|
11914
|
-
__export(protocol_version_exports, {
|
|
11915
|
-
OLAM_PROTOCOL_VERSIONS_SUPPORTED: () => OLAM_PROTOCOL_VERSIONS_SUPPORTED,
|
|
11916
|
-
checkProtocolOverlap: () => checkProtocolOverlap,
|
|
11917
|
-
inspectImageProtocolVersions: () => inspectImageProtocolVersions,
|
|
11918
|
-
parseProtocolVersionsLabel: () => parseProtocolVersionsLabel
|
|
11919
|
-
});
|
|
11920
|
-
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
11921
|
-
function parseProtocolVersionsLabel(labelValue) {
|
|
11922
|
-
if (!labelValue) return [];
|
|
11923
|
-
const parts = labelValue.split(",").map((s) => s.trim()).filter(Boolean);
|
|
11924
|
-
const versions = /* @__PURE__ */ new Set();
|
|
11925
|
-
for (const part of parts) {
|
|
11926
|
-
const n = Number.parseInt(part, 10);
|
|
11927
|
-
if (Number.isFinite(n) && n > 0 && String(n) === part) {
|
|
11928
|
-
versions.add(n);
|
|
11929
|
-
}
|
|
11930
|
-
}
|
|
11931
|
-
return Array.from(versions).sort((a, b) => a - b);
|
|
11996
|
+
function pidPath() {
|
|
11997
|
+
return path22.join(olamHome(), "host-cp.pid");
|
|
11932
11998
|
}
|
|
11933
|
-
function
|
|
11934
|
-
|
|
11935
|
-
const overlap = imageVersions.filter((v) => cliSet.has(v));
|
|
11936
|
-
if (imageVersions.length === 0) {
|
|
11937
|
-
return {
|
|
11938
|
-
overlap: [],
|
|
11939
|
-
imageVersions: [],
|
|
11940
|
-
cliVersions,
|
|
11941
|
-
compatible: false,
|
|
11942
|
-
remedy: `Devbox image is missing the \`olam.protocol.versions\` LABEL. This CLI requires versions [${cliVersions.join(", ")}]. See docs/architecture/devbox-contract.md \xA71 for the contract; rebuild the image with \`LABEL olam.protocol.versions="1"\`.`
|
|
11943
|
-
};
|
|
11944
|
-
}
|
|
11945
|
-
if (overlap.length === 0) {
|
|
11946
|
-
const imgLow = Math.min(...imageVersions);
|
|
11947
|
-
const imgHigh = Math.max(...imageVersions);
|
|
11948
|
-
const cliLow = Math.min(...cliVersions);
|
|
11949
|
-
const cliHigh = Math.max(...cliVersions);
|
|
11950
|
-
return {
|
|
11951
|
-
overlap: [],
|
|
11952
|
-
imageVersions: [...imageVersions],
|
|
11953
|
-
cliVersions,
|
|
11954
|
-
compatible: false,
|
|
11955
|
-
remedy: `Devbox image protocol versions [${imageVersions.join(", ")}] don't overlap CLI's [${cliVersions.join(", ")}]. ` + (imgHigh < cliLow ? `The image is older than this CLI supports \u2014 rebuild against the contract version ${cliLow}+ at docs/architecture/devbox-contract.md.` : imgLow > cliHigh ? `The image is newer than this CLI supports \u2014 pin a compatible CLI: \`npm install -g @pleri/olam-cli@<version-with-protocol-${imgLow}>\`.` : `Pin a compatible CLI version that overlaps with the image's range.`)
|
|
11956
|
-
};
|
|
11957
|
-
}
|
|
11958
|
-
return {
|
|
11959
|
-
overlap,
|
|
11960
|
-
imageVersions: [...imageVersions],
|
|
11961
|
-
cliVersions,
|
|
11962
|
-
compatible: true,
|
|
11963
|
-
remedy: ""
|
|
11964
|
-
};
|
|
11999
|
+
function authSecretPath() {
|
|
12000
|
+
return path22.join(olamHome(), "auth-secret");
|
|
11965
12001
|
}
|
|
11966
|
-
function
|
|
11967
|
-
const
|
|
11968
|
-
if (
|
|
12002
|
+
function readAuthSecret2() {
|
|
12003
|
+
const filePath = authSecretPath();
|
|
12004
|
+
if (!fs19.existsSync(filePath)) return null;
|
|
12005
|
+
const raw = fs19.readFileSync(filePath, "utf-8").trim();
|
|
12006
|
+
return raw.length > 0 ? raw : null;
|
|
12007
|
+
}
|
|
12008
|
+
function r2CredentialsPath() {
|
|
12009
|
+
return path22.join(olamHome(), "r2-credentials.json");
|
|
12010
|
+
}
|
|
12011
|
+
function readR2Credentials() {
|
|
12012
|
+
const filePath = r2CredentialsPath();
|
|
12013
|
+
if (!fs19.existsSync(filePath)) return null;
|
|
12014
|
+
const raw = fs19.readFileSync(filePath, "utf-8").trim();
|
|
12015
|
+
if (raw.length === 0) return null;
|
|
12016
|
+
try {
|
|
12017
|
+
const parsed = JSON.parse(raw);
|
|
12018
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
12019
|
+
const creds = parsed;
|
|
12020
|
+
if (typeof creds.account_id !== "string" || typeof creds.bucket !== "string" || typeof creds.access_key_id !== "string" || typeof creds.secret_access_key !== "string" || typeof creds.public_url_base !== "string") {
|
|
12021
|
+
return null;
|
|
12022
|
+
}
|
|
11969
12023
|
return {
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
12024
|
+
account_id: creds.account_id,
|
|
12025
|
+
bucket: creds.bucket,
|
|
12026
|
+
access_key_id: creds.access_key_id,
|
|
12027
|
+
secret_access_key: creds.secret_access_key,
|
|
12028
|
+
public_url_base: creds.public_url_base
|
|
11974
12029
|
};
|
|
12030
|
+
} catch {
|
|
12031
|
+
return null;
|
|
11975
12032
|
}
|
|
11976
|
-
const raw = stdout.trim();
|
|
11977
|
-
const value = raw === "<no value>" ? "" : raw;
|
|
11978
|
-
return {
|
|
11979
|
-
imageRef,
|
|
11980
|
-
versions: parseProtocolVersionsLabel(value),
|
|
11981
|
-
inspectFailed: false,
|
|
11982
|
-
inspectError: void 0
|
|
11983
|
-
};
|
|
11984
12033
|
}
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
const
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
12034
|
+
function writeToken() {
|
|
12035
|
+
const token = crypto5.randomBytes(32).toString("hex");
|
|
12036
|
+
const filePath = tokenPath();
|
|
12037
|
+
fs19.mkdirSync(path22.dirname(filePath), { recursive: true });
|
|
12038
|
+
fs19.writeFileSync(filePath, token, { mode: 384 });
|
|
12039
|
+
return token;
|
|
12040
|
+
}
|
|
12041
|
+
function readToken() {
|
|
12042
|
+
const filePath = tokenPath();
|
|
12043
|
+
if (!fs19.existsSync(filePath)) return null;
|
|
12044
|
+
return fs19.readFileSync(filePath, "utf-8").trim();
|
|
12045
|
+
}
|
|
12046
|
+
function removeToken() {
|
|
12047
|
+
const filePath = tokenPath();
|
|
12048
|
+
if (!fs19.existsSync(filePath)) return false;
|
|
12049
|
+
fs19.unlinkSync(filePath);
|
|
12050
|
+
return true;
|
|
12051
|
+
}
|
|
12052
|
+
function writePid(pid) {
|
|
12053
|
+
const filePath = pidPath();
|
|
12054
|
+
fs19.mkdirSync(path22.dirname(filePath), { recursive: true });
|
|
12055
|
+
fs19.writeFileSync(filePath, String(pid), { mode: 420 });
|
|
12056
|
+
}
|
|
12057
|
+
function readPid() {
|
|
12058
|
+
const filePath = pidPath();
|
|
12059
|
+
if (!fs19.existsSync(filePath)) return null;
|
|
12060
|
+
const raw = fs19.readFileSync(filePath, "utf-8").trim();
|
|
12061
|
+
const n = parseInt(raw, 10);
|
|
12062
|
+
return Number.isFinite(n) ? n : null;
|
|
12063
|
+
}
|
|
12064
|
+
function removePid() {
|
|
12065
|
+
const filePath = pidPath();
|
|
12066
|
+
if (!fs19.existsSync(filePath)) return false;
|
|
12067
|
+
fs19.unlinkSync(filePath);
|
|
12068
|
+
return true;
|
|
12069
|
+
}
|
|
12070
|
+
async function findHostCpContainer() {
|
|
12071
|
+
const docker2 = new Dockerode2(resolveDockerHostOptions());
|
|
12072
|
+
const containers = await docker2.listContainers({ all: true });
|
|
12073
|
+
for (const c of containers) {
|
|
12074
|
+
const names = (c.Names ?? []).map((n) => n.replace(/^\//, ""));
|
|
12075
|
+
if (names.includes("olam-host-cp")) {
|
|
12076
|
+
return {
|
|
12077
|
+
id: c.Id.slice(0, 12),
|
|
12078
|
+
name: "olam-host-cp",
|
|
12079
|
+
state: c.State,
|
|
12080
|
+
status: c.Status
|
|
12081
|
+
};
|
|
12082
|
+
}
|
|
12029
12083
|
}
|
|
12030
|
-
return
|
|
12031
|
-
imageRef,
|
|
12032
|
-
allowedByDefault: false,
|
|
12033
|
-
accepted: false,
|
|
12034
|
-
stderrLine: `Error: image '${imageRef}' is outside allowed registries (ghcr.io/pleri/*).
|
|
12035
|
-
To override: re-run with --allow-custom-registry
|
|
12036
|
-
Verify the source and digest before doing so.`
|
|
12037
|
-
};
|
|
12084
|
+
return null;
|
|
12038
12085
|
}
|
|
12039
|
-
function
|
|
12040
|
-
|
|
12041
|
-
|
|
12086
|
+
async function probeHostCp() {
|
|
12087
|
+
const candidateUrl = `http://127.0.0.1:${HOST_CP_PORT}`;
|
|
12088
|
+
let httpOk = false;
|
|
12089
|
+
try {
|
|
12090
|
+
const res = await fetch(`${candidateUrl}/api/bootstrap`, {
|
|
12091
|
+
signal: AbortSignal.timeout(2e3)
|
|
12092
|
+
});
|
|
12093
|
+
httpOk = res.ok;
|
|
12094
|
+
} catch {
|
|
12095
|
+
httpOk = false;
|
|
12042
12096
|
}
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
|
|
12097
|
+
if (httpOk) {
|
|
12098
|
+
let mode = "bare";
|
|
12099
|
+
try {
|
|
12100
|
+
const container = await findHostCpContainer();
|
|
12101
|
+
if (container && container.state === "running") {
|
|
12102
|
+
mode = "container";
|
|
12103
|
+
}
|
|
12104
|
+
} catch {
|
|
12105
|
+
}
|
|
12106
|
+
return { url: candidateUrl, mode };
|
|
12046
12107
|
}
|
|
12047
|
-
|
|
12108
|
+
try {
|
|
12109
|
+
const container = await findHostCpContainer();
|
|
12110
|
+
if (container && container.state === "running") {
|
|
12111
|
+
try {
|
|
12112
|
+
const res = await fetch(`${candidateUrl}/api/bootstrap`, {
|
|
12113
|
+
signal: AbortSignal.timeout(2e3)
|
|
12114
|
+
});
|
|
12115
|
+
if (res.ok) {
|
|
12116
|
+
return { url: candidateUrl, mode: "container" };
|
|
12117
|
+
}
|
|
12118
|
+
} catch {
|
|
12119
|
+
}
|
|
12120
|
+
}
|
|
12121
|
+
} catch {
|
|
12122
|
+
}
|
|
12123
|
+
return null;
|
|
12048
12124
|
}
|
|
12049
|
-
|
|
12050
|
-
|
|
12051
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
|
|
12125
|
+
async function gatherProbeFailureDiagnostics() {
|
|
12126
|
+
let bootstrapStatus = "no response";
|
|
12127
|
+
try {
|
|
12128
|
+
const res = await fetch(`http://127.0.0.1:${HOST_CP_PORT}/api/bootstrap`, {
|
|
12129
|
+
signal: AbortSignal.timeout(2e3)
|
|
12130
|
+
});
|
|
12131
|
+
bootstrapStatus = `HTTP ${res.status}`;
|
|
12132
|
+
} catch (err) {
|
|
12133
|
+
bootstrapStatus = err instanceof Error ? err.message : "connection refused";
|
|
12057
12134
|
}
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
})
|
|
12065
|
-
|
|
12066
|
-
|
|
12067
|
-
|
|
12068
|
-
return {
|
|
12069
|
-
command,
|
|
12070
|
-
instructions: [
|
|
12071
|
-
"Run this command in your terminal to enter the world:",
|
|
12072
|
-
"",
|
|
12073
|
-
` ${command}`,
|
|
12074
|
-
"",
|
|
12075
|
-
"Press Ctrl+C to exit."
|
|
12076
|
-
].join("\n")
|
|
12077
|
-
};
|
|
12135
|
+
let containerStatus = "not found";
|
|
12136
|
+
try {
|
|
12137
|
+
const container = await findHostCpContainer();
|
|
12138
|
+
if (!container) {
|
|
12139
|
+
containerStatus = "not found";
|
|
12140
|
+
} else {
|
|
12141
|
+
containerStatus = `found (state: ${container.state})`;
|
|
12142
|
+
}
|
|
12143
|
+
} catch {
|
|
12144
|
+
containerStatus = "docker not available";
|
|
12078
12145
|
}
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
};
|
|
12146
|
+
return { bootstrapStatus, containerStatus };
|
|
12147
|
+
}
|
|
12148
|
+
async function probeHealth() {
|
|
12149
|
+
try {
|
|
12150
|
+
const res = await fetch(`http://127.0.0.1:${HOST_CP_PORT}/health`, {
|
|
12151
|
+
signal: AbortSignal.timeout(2e3)
|
|
12152
|
+
});
|
|
12153
|
+
if (!res.ok) return null;
|
|
12154
|
+
return await res.json();
|
|
12155
|
+
} catch {
|
|
12156
|
+
return null;
|
|
12091
12157
|
}
|
|
12158
|
+
}
|
|
12159
|
+
function runCompose(args, composeFile, extraEnv = {}) {
|
|
12160
|
+
const result = spawnSync4("docker", ["compose", "-f", composeFile, ...args], {
|
|
12161
|
+
encoding: "utf-8",
|
|
12162
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
12163
|
+
env: { ...process.env, ...extraEnv }
|
|
12164
|
+
});
|
|
12092
12165
|
return {
|
|
12093
|
-
|
|
12094
|
-
|
|
12166
|
+
ok: result.status === 0,
|
|
12167
|
+
stdout: result.stdout ?? "",
|
|
12168
|
+
stderr: result.stderr ?? ""
|
|
12095
12169
|
};
|
|
12096
12170
|
}
|
|
12097
|
-
|
|
12098
|
-
|
|
12099
|
-
|
|
12171
|
+
function buildComposeEnv(authSecret, ghToken) {
|
|
12172
|
+
const env = {};
|
|
12173
|
+
if (authSecret !== null && authSecret.length > 0) {
|
|
12174
|
+
env.OLAM_AUTH_SECRET = authSecret;
|
|
12100
12175
|
}
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
// ../core/src/crystallize/checksum.ts
|
|
12104
|
-
var checksum_exports = {};
|
|
12105
|
-
__export(checksum_exports, {
|
|
12106
|
-
computeGraphChecksum: () => computeGraphChecksum
|
|
12107
|
-
});
|
|
12108
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
12109
|
-
function computeGraphChecksum(nodes, edges) {
|
|
12110
|
-
const sortedNodes = [...nodes].sort((a, b) => a.id.localeCompare(b.id));
|
|
12111
|
-
const sortedEdges = [...edges].sort((a, b) => a.id.localeCompare(b.id));
|
|
12112
|
-
const payload = JSON.stringify({ nodes: sortedNodes, edges: sortedEdges });
|
|
12113
|
-
return createHash2("sha256").update(payload).digest("hex");
|
|
12114
|
-
}
|
|
12115
|
-
var init_checksum = __esm({
|
|
12116
|
-
"../core/src/crystallize/checksum.ts"() {
|
|
12117
|
-
"use strict";
|
|
12176
|
+
if (ghToken != null && ghToken.length > 0) {
|
|
12177
|
+
env.GH_TOKEN = ghToken;
|
|
12118
12178
|
}
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
var machine_schema_exports = {};
|
|
12123
|
-
__export(machine_schema_exports, {
|
|
12124
|
-
MachineConfigSchema: () => MachineConfigSchema,
|
|
12125
|
-
initMachineConfig: () => initMachineConfig,
|
|
12126
|
-
readMachineConfig: () => readMachineConfig,
|
|
12127
|
-
writeMachineConfig: () => writeMachineConfig
|
|
12128
|
-
});
|
|
12129
|
-
import * as fs32 from "node:fs";
|
|
12130
|
-
import * as path36 from "node:path";
|
|
12131
|
-
import * as os19 from "node:os";
|
|
12132
|
-
import { parse as parseYaml4, stringify as stringifyYaml4 } from "yaml";
|
|
12133
|
-
function readMachineConfig(configPath) {
|
|
12134
|
-
const p = configPath ?? DEFAULT_CONFIG_PATH;
|
|
12135
|
-
if (!fs32.existsSync(p)) return null;
|
|
12179
|
+
return env;
|
|
12180
|
+
}
|
|
12181
|
+
function captureGhToken() {
|
|
12136
12182
|
try {
|
|
12137
|
-
const
|
|
12138
|
-
|
|
12139
|
-
|
|
12183
|
+
const result = spawnSync4("gh", ["auth", "token"], {
|
|
12184
|
+
encoding: "utf-8",
|
|
12185
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
12186
|
+
});
|
|
12187
|
+
if (result.status === 0) {
|
|
12188
|
+
const token = (result.stdout ?? "").trim();
|
|
12189
|
+
return token.length > 0 ? token : null;
|
|
12190
|
+
}
|
|
12191
|
+
return null;
|
|
12140
12192
|
} catch {
|
|
12141
12193
|
return null;
|
|
12142
12194
|
}
|
|
12143
12195
|
}
|
|
12144
|
-
function
|
|
12145
|
-
|
|
12146
|
-
fs32.mkdirSync(path36.dirname(p), { recursive: true });
|
|
12147
|
-
fs32.writeFileSync(p, stringifyYaml4({ ...config }), { mode: 420 });
|
|
12196
|
+
async function startHostCp(opts) {
|
|
12197
|
+
return handleStart(opts);
|
|
12148
12198
|
}
|
|
12149
|
-
function
|
|
12150
|
-
const
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
return config;
|
|
12158
|
-
}
|
|
12159
|
-
var MachineConfigSchema, DEFAULT_CONFIG_PATH;
|
|
12160
|
-
var init_machine_schema = __esm({
|
|
12161
|
-
"../core/src/config/machine-schema.ts"() {
|
|
12162
|
-
"use strict";
|
|
12163
|
-
init_zod();
|
|
12164
|
-
MachineConfigSchema = external_exports.object({
|
|
12165
|
-
schema_version: external_exports.literal(1).default(1),
|
|
12166
|
-
channel: external_exports.enum(["stable", "beta", "edge"]).default("stable"),
|
|
12167
|
-
auto_update: external_exports.boolean().default(true),
|
|
12168
|
-
telemetry: external_exports.boolean().default(true),
|
|
12169
|
-
worlds_dir: external_exports.string().default(() => path36.join(os19.homedir(), ".olam", "worlds"))
|
|
12170
|
-
});
|
|
12171
|
-
DEFAULT_CONFIG_PATH = path36.join(os19.homedir(), ".olam", "config.yaml");
|
|
12172
|
-
}
|
|
12173
|
-
});
|
|
12174
|
-
|
|
12175
|
-
// src/index.ts
|
|
12176
|
-
import { Command } from "commander";
|
|
12177
|
-
import * as fs35 from "node:fs";
|
|
12178
|
-
import * as path39 from "node:path";
|
|
12179
|
-
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
12180
|
-
|
|
12181
|
-
// src/commands/init.ts
|
|
12182
|
-
import * as fs5 from "node:fs";
|
|
12183
|
-
import * as path5 from "node:path";
|
|
12184
|
-
import { execSync } from "node:child_process";
|
|
12185
|
-
import pc3 from "picocolors";
|
|
12186
|
-
|
|
12187
|
-
// src/output.ts
|
|
12188
|
-
import pc from "picocolors";
|
|
12189
|
-
function printError(message) {
|
|
12190
|
-
console.error(`${pc.red("error")} ${message}`);
|
|
12191
|
-
}
|
|
12192
|
-
function printSuccess(message) {
|
|
12193
|
-
console.log(`${pc.green("ok")} ${message}`);
|
|
12194
|
-
}
|
|
12195
|
-
function printWarning(message) {
|
|
12196
|
-
console.log(`${pc.yellow("warn")} ${message}`);
|
|
12197
|
-
}
|
|
12198
|
-
function printInfo(label, value) {
|
|
12199
|
-
console.log(` ${pc.dim(label.padEnd(14))} ${value}`);
|
|
12200
|
-
}
|
|
12201
|
-
function printHeader(title) {
|
|
12202
|
-
console.log(`
|
|
12203
|
-
${pc.bold(title)}`);
|
|
12204
|
-
}
|
|
12205
|
-
function formatAge(createdAt) {
|
|
12206
|
-
const ms = Date.now() - new Date(createdAt).getTime();
|
|
12207
|
-
const minutes = Math.floor(ms / 6e4);
|
|
12208
|
-
if (minutes < 60) return `${minutes}m`;
|
|
12209
|
-
const hours = Math.floor(minutes / 60);
|
|
12210
|
-
if (hours < 24) return `${hours}h ${minutes % 60}m`;
|
|
12211
|
-
const days = Math.floor(hours / 24);
|
|
12212
|
-
return `${days}d ${hours % 24}h`;
|
|
12213
|
-
}
|
|
12214
|
-
|
|
12215
|
-
// src/commands/workspace.ts
|
|
12216
|
-
init_workspace();
|
|
12217
|
-
init_loader();
|
|
12218
|
-
import * as fs4 from "node:fs";
|
|
12219
|
-
import * as path4 from "node:path";
|
|
12220
|
-
import pc2 from "picocolors";
|
|
12221
|
-
import { stringify as stringifyYaml2 } from "yaml";
|
|
12222
|
-
function printWorkspaceNotFound(name) {
|
|
12223
|
-
printError(`No workspace named "${name}" under ${workspacesDir()}`);
|
|
12224
|
-
}
|
|
12225
|
-
function parseRepoFlag(raw) {
|
|
12226
|
-
const hashIdx = raw.indexOf("#");
|
|
12227
|
-
const url = hashIdx === -1 ? raw : raw.slice(0, hashIdx);
|
|
12228
|
-
const branch = hashIdx === -1 ? void 0 : raw.slice(hashIdx + 1);
|
|
12229
|
-
if (url.length === 0) throw new Error(`invalid --repo value "${raw}" (empty url)`);
|
|
12230
|
-
if (branch !== void 0 && branch.length === 0) {
|
|
12231
|
-
throw new Error(`invalid --repo value "${raw}" (empty branch after #)`);
|
|
12232
|
-
}
|
|
12233
|
-
const nameFromUrl = url.replace(/\.git$/, "").split(/[\/:]/).filter(Boolean).at(-1) ?? "repo";
|
|
12234
|
-
return branch ? { name: nameFromUrl, url, branch } : { name: nameFromUrl, url };
|
|
12235
|
-
}
|
|
12236
|
-
function registerWorkspace(program2) {
|
|
12237
|
-
const workspace = program2.command("workspace").description("Manage the named catalog of repo bundles that worlds instantiate from");
|
|
12238
|
-
workspace.command("list").description("List all workspaces (name, repoCount, updatedAt)").action(() => {
|
|
12239
|
-
const all = listWorkspaces();
|
|
12240
|
-
if (all.length === 0) {
|
|
12241
|
-
console.log(pc2.dim(`No workspaces under ${workspacesDir()}`));
|
|
12199
|
+
async function handleStart(opts) {
|
|
12200
|
+
const existing = await findHostCpContainer();
|
|
12201
|
+
if (existing && existing.state === "running") {
|
|
12202
|
+
const health = await probeHealth();
|
|
12203
|
+
if (health) {
|
|
12204
|
+
printSuccess(`Host CP already running at http://127.0.0.1:${HOST_CP_PORT}`);
|
|
12205
|
+
printInfo("Container", existing.id);
|
|
12206
|
+
printInfo("Uptime", String(health["uptime_seconds"] ?? "unknown") + "s");
|
|
12242
12207
|
return;
|
|
12243
12208
|
}
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
const when = new Date(ws.updatedAt).toISOString().slice(0, 10);
|
|
12247
|
-
console.log(
|
|
12248
|
-
` ${pc2.bold(ws.name.padEnd(24))} ${String(ws.repos.length).padEnd(3)} repos ${pc2.dim(when)}`
|
|
12249
|
-
);
|
|
12250
|
-
}
|
|
12251
|
-
});
|
|
12252
|
-
workspace.command("show").description("Show a workspace as YAML").argument("<name>", "Workspace name").action((name) => {
|
|
12253
|
-
try {
|
|
12254
|
-
const ws = readWorkspace(name);
|
|
12255
|
-
if (!ws) {
|
|
12256
|
-
printWorkspaceNotFound(name);
|
|
12257
|
-
process.exitCode = 1;
|
|
12258
|
-
return;
|
|
12259
|
-
}
|
|
12260
|
-
process.stdout.write(stringifyYaml2(ws));
|
|
12261
|
-
} catch (err) {
|
|
12262
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
12263
|
-
process.exitCode = 1;
|
|
12264
|
-
}
|
|
12265
|
-
});
|
|
12266
|
-
workspace.command("remove").description("Delete a workspace (does NOT touch worlds that already referenced it)").argument("<name>", "Workspace name").option("--force", "Skip confirmation", false).action((name, _opts) => {
|
|
12267
|
-
try {
|
|
12268
|
-
if (removeWorkspace(name)) {
|
|
12269
|
-
printSuccess(`Removed workspace "${name}"`);
|
|
12270
|
-
} else {
|
|
12271
|
-
printWorkspaceNotFound(name);
|
|
12272
|
-
process.exitCode = 1;
|
|
12273
|
-
}
|
|
12274
|
-
} catch (err) {
|
|
12275
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
12276
|
-
process.exitCode = 1;
|
|
12277
|
-
}
|
|
12278
|
-
});
|
|
12279
|
-
workspace.command("add").description("Create a workspace from --from-config (reads current .olam/config.yaml) or repeated --repo flags").argument("<name>", "Workspace name").option("--from-config", "Seed from the repos in the current project's .olam/config.yaml", false).option("--repo <url>", "Repeatable. Format: <url> or <url>#<branch>", (val, acc) => {
|
|
12280
|
-
acc.push(parseRepoFlag(val));
|
|
12281
|
-
return acc;
|
|
12282
|
-
}, []).option("--default-branch <branch>", "Fallback branch for repos that don't specify one").option("--force", "Overwrite an existing workspace with the same name", false).action((name, opts) => {
|
|
12283
|
-
try {
|
|
12284
|
-
const repos = [...opts.repo];
|
|
12285
|
-
if (opts.fromConfig) {
|
|
12286
|
-
try {
|
|
12287
|
-
const cfg = loadConfig(process.cwd());
|
|
12288
|
-
for (const r of cfg.repos) {
|
|
12289
|
-
repos.push({
|
|
12290
|
-
name: r.name,
|
|
12291
|
-
url: r.url,
|
|
12292
|
-
...r.submodules ? { submodules: true } : {}
|
|
12293
|
-
});
|
|
12294
|
-
}
|
|
12295
|
-
} catch (err) {
|
|
12296
|
-
printError(`--from-config: ${err instanceof Error ? err.message : String(err)}`);
|
|
12297
|
-
process.exitCode = 1;
|
|
12298
|
-
return;
|
|
12299
|
-
}
|
|
12300
|
-
}
|
|
12301
|
-
if (repos.length === 0) {
|
|
12302
|
-
printError("No repos provided \u2014 pass --from-config or at least one --repo");
|
|
12303
|
-
process.exitCode = 1;
|
|
12304
|
-
return;
|
|
12305
|
-
}
|
|
12306
|
-
const ws = {
|
|
12307
|
-
name,
|
|
12308
|
-
repos,
|
|
12309
|
-
...opts.defaultBranch ? { defaults: { branch: opts.defaultBranch } } : {},
|
|
12310
|
-
updatedAt: Date.now()
|
|
12311
|
-
};
|
|
12312
|
-
writeWorkspace(ws, { force: opts.force });
|
|
12313
|
-
const file = path4.join(workspacesDir(), `${name}.yaml`);
|
|
12314
|
-
printSuccess(`Created workspace "${name}" (${repos.length} repo${repos.length === 1 ? "" : "s"})`);
|
|
12315
|
-
printInfo("File", file);
|
|
12316
|
-
printInfo("Next", `olam create --name <world> --workspace ${name} --task "..."`);
|
|
12317
|
-
} catch (err) {
|
|
12318
|
-
if (err instanceof WorkspaceExistsError || err instanceof WorkspaceNameError) {
|
|
12319
|
-
printError(err.message);
|
|
12320
|
-
process.exitCode = 1;
|
|
12321
|
-
return;
|
|
12322
|
-
}
|
|
12323
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
12324
|
-
process.exitCode = 1;
|
|
12325
|
-
}
|
|
12326
|
-
});
|
|
12327
|
-
}
|
|
12328
|
-
function ensureProjectWorkspaceFromConfig(projectRoot, workspaceName) {
|
|
12329
|
-
const existing = (() => {
|
|
12330
|
-
try {
|
|
12331
|
-
return readWorkspace(workspaceName);
|
|
12332
|
-
} catch {
|
|
12333
|
-
return null;
|
|
12334
|
-
}
|
|
12335
|
-
})();
|
|
12336
|
-
if (existing) {
|
|
12337
|
-
return { created: false, file: path4.join(workspacesDir(), `${workspaceName}.yaml`) };
|
|
12209
|
+
printWarning("Host CP container running but /health not responding. Wait a few seconds and retry, or stop+start.");
|
|
12210
|
+
return;
|
|
12338
12211
|
}
|
|
12339
|
-
const cfg = loadConfig(projectRoot);
|
|
12340
|
-
const repos = cfg.repos.map((r) => ({
|
|
12341
|
-
name: r.name,
|
|
12342
|
-
url: r.url,
|
|
12343
|
-
...r.submodules ? { submodules: true } : {}
|
|
12344
|
-
}));
|
|
12345
|
-
const ws = {
|
|
12346
|
-
name: workspaceName,
|
|
12347
|
-
repos,
|
|
12348
|
-
updatedAt: Date.now()
|
|
12349
|
-
};
|
|
12350
|
-
writeWorkspace(ws);
|
|
12351
|
-
const file = path4.join(workspacesDir(), `${workspaceName}.yaml`);
|
|
12352
|
-
return { created: true, file };
|
|
12353
|
-
}
|
|
12354
|
-
|
|
12355
|
-
// src/commands/init.ts
|
|
12356
|
-
function detectProjectType(root) {
|
|
12357
|
-
if (fs5.existsSync(path5.join(root, "Gemfile")) || fs5.existsSync(path5.join(root, "config", "routes.rb"))) return "rails";
|
|
12358
|
-
if (fs5.existsSync(path5.join(root, "package.json"))) return "node";
|
|
12359
|
-
if (fs5.existsSync(path5.join(root, "pyproject.toml")) || fs5.existsSync(path5.join(root, "requirements.txt"))) return "python";
|
|
12360
|
-
return "generic";
|
|
12361
|
-
}
|
|
12362
|
-
function getRepoName(root) {
|
|
12363
12212
|
try {
|
|
12364
|
-
const
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
} catch {
|
|
12371
|
-
}
|
|
12372
|
-
return path5.basename(root);
|
|
12373
|
-
}
|
|
12374
|
-
function findProjectRoot(startDir) {
|
|
12375
|
-
let current = path5.resolve(startDir);
|
|
12376
|
-
while (true) {
|
|
12377
|
-
if (fs5.existsSync(path5.join(current, ".git"))) return current;
|
|
12378
|
-
const parent = path5.dirname(current);
|
|
12379
|
-
if (parent === current) return startDir;
|
|
12380
|
-
current = parent;
|
|
12381
|
-
}
|
|
12382
|
-
}
|
|
12383
|
-
function registerInit(program2) {
|
|
12384
|
-
program2.command("init").description("Initialize olam in the current project").option("--path <path>", "Project root path", process.cwd()).option("--skip-pleri", "Skip Pleri setup").action(async (opts) => {
|
|
12385
|
-
try {
|
|
12386
|
-
const projectRoot = findProjectRoot(opts.path);
|
|
12387
|
-
const olamDir = path5.join(projectRoot, ".olam");
|
|
12388
|
-
if (fs5.existsSync(path5.join(olamDir, "config.yaml"))) {
|
|
12389
|
-
printError(`Already initialized at ${olamDir}/config.yaml`);
|
|
12390
|
-
process.exitCode = 1;
|
|
12391
|
-
return;
|
|
12392
|
-
}
|
|
12393
|
-
const projectType = detectProjectType(projectRoot);
|
|
12394
|
-
const repoName = getRepoName(projectRoot);
|
|
12395
|
-
let remoteUrl;
|
|
12396
|
-
try {
|
|
12397
|
-
remoteUrl = execSync("git remote get-url origin", {
|
|
12398
|
-
cwd: projectRoot,
|
|
12399
|
-
encoding: "utf-8"
|
|
12400
|
-
}).trim();
|
|
12401
|
-
} catch {
|
|
12402
|
-
remoteUrl = `git@github.com:your-org/${repoName}.git`;
|
|
12403
|
-
}
|
|
12404
|
-
fs5.mkdirSync(path5.join(olamDir, "state"), { recursive: true });
|
|
12405
|
-
fs5.mkdirSync(path5.join(olamDir, "thoughts"), { recursive: true });
|
|
12406
|
-
const pleriSection = opts.skipPleri ? "# pleri:\n# base_url: ${PLERI_BASE_URL}\n# plane_id: ${PLERI_PLANE_ID}\n# api_key: ${PLERI_API_KEY}\n" : "pleri:\n base_url: ${PLERI_BASE_URL}\n plane_id: ${PLERI_PLANE_ID}\n api_key: ${PLERI_API_KEY}\n";
|
|
12407
|
-
const config = [
|
|
12408
|
-
"version: 2",
|
|
12409
|
-
"",
|
|
12410
|
-
pleriSection,
|
|
12411
|
-
"repos:",
|
|
12412
|
-
` - name: ${repoName}`,
|
|
12413
|
-
` url: ${remoteUrl}`,
|
|
12414
|
-
` path: ${projectRoot}`,
|
|
12415
|
-
` type: ${projectType}`,
|
|
12416
|
-
" services: []",
|
|
12417
|
-
"",
|
|
12418
|
-
"compute:",
|
|
12419
|
-
" default: docker",
|
|
12420
|
-
"",
|
|
12421
|
-
"cost:",
|
|
12422
|
-
" # All values in USD (Anthropic's billing currency).",
|
|
12423
|
-
" # Convert from your local currency: USD 25 \u2248 SGD 33 / EUR 23 / GBP 20",
|
|
12424
|
-
" # at typical 2026 rates. Dashboard display localization is a future feature.",
|
|
12425
|
-
" max_per_world_usd: 25",
|
|
12426
|
-
" max_daily_usd: 100",
|
|
12427
|
-
" warning_threshold: 0.8",
|
|
12428
|
-
"",
|
|
12429
|
-
"auth:",
|
|
12430
|
-
" mode: oauth",
|
|
12431
|
-
""
|
|
12432
|
-
].join("\n");
|
|
12433
|
-
fs5.writeFileSync(path5.join(olamDir, "config.yaml"), config);
|
|
12434
|
-
const envExample = [
|
|
12435
|
-
"# Pleri credentials",
|
|
12436
|
-
"PLERI_BASE_URL=https://pleri.dev/api",
|
|
12437
|
-
"PLERI_PLANE_ID=",
|
|
12438
|
-
"PLERI_API_KEY=",
|
|
12439
|
-
""
|
|
12440
|
-
].join("\n");
|
|
12441
|
-
fs5.writeFileSync(path5.join(olamDir, ".env.example"), envExample);
|
|
12442
|
-
printHeader("Olam initialized");
|
|
12443
|
-
printInfo("Config", `${olamDir}/config.yaml`);
|
|
12444
|
-
printInfo("Project", `${projectType} (detected)`);
|
|
12445
|
-
printInfo("Repo", repoName);
|
|
12446
|
-
try {
|
|
12447
|
-
const result = ensureProjectWorkspaceFromConfig(projectRoot, repoName);
|
|
12448
|
-
if (result.created) {
|
|
12449
|
-
printInfo("Workspace", `${repoName} \u2192 ${result.file}`);
|
|
12450
|
-
} else {
|
|
12451
|
-
printInfo("Workspace", `${repoName} (already registered)`);
|
|
12452
|
-
}
|
|
12453
|
-
} catch (err) {
|
|
12454
|
-
printError(`Workspace auto-register failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
12455
|
-
}
|
|
12456
|
-
console.log(`
|
|
12457
|
-
${pc3.dim(`Next: olam create --name my-world --workspace ${repoName} --task "..."`)}`);
|
|
12458
|
-
} catch (err) {
|
|
12459
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
12213
|
+
const docker2 = new Dockerode2(resolveDockerHostOptions());
|
|
12214
|
+
await auditPortsForZombies(docker2, [HOST_CP_PORT]);
|
|
12215
|
+
} catch (err) {
|
|
12216
|
+
if (err instanceof PortHeldByZombieError) {
|
|
12217
|
+
printError(`Port ${HOST_CP_PORT} held by zombie container "${err.containerName}" (state: ${err.state}).`);
|
|
12218
|
+
printError(`Run: docker rm ${err.containerName}`);
|
|
12460
12219
|
process.exitCode = 1;
|
|
12220
|
+
return;
|
|
12461
12221
|
}
|
|
12462
|
-
|
|
12463
|
-
}
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
12476
|
-
"world.operate",
|
|
12477
|
-
"world.destroy",
|
|
12478
|
-
// Workspace catalog
|
|
12479
|
-
"workspace.read",
|
|
12480
|
-
"workspace.write",
|
|
12481
|
-
// Auth container + credentials. Phase 6b.2/A5: `auth.manage`
|
|
12482
|
-
// removed — its only consumer (/auth/revoke) was reclassified to
|
|
12483
|
-
// `auth.login` (self-managed auth flow) in A2, and the only
|
|
12484
|
-
// archetype that held it (`bootstrapper`) is also removed below.
|
|
12485
|
-
"auth.login",
|
|
12486
|
-
"auth.read",
|
|
12487
|
-
// PR gate
|
|
12488
|
-
"pr-gate.read",
|
|
12489
|
-
"pr-gate.decide",
|
|
12490
|
-
// Policy
|
|
12491
|
-
"policy.set",
|
|
12492
|
-
// Phase 6b.2/A5: `role.manage` removed — Pylon owns role grants
|
|
12493
|
-
// (`pylon role grant ...` from a user terminal). The `/api/roles*`
|
|
12494
|
-
// route handlers were deleted in A2.
|
|
12495
|
-
// Phase 6b.2/A5: `bootstrap.install` and `bootstrap.cf-deploy`
|
|
12496
|
-
// removed — `/bootstrap` route handler deleted in A2; the
|
|
12497
|
-
// `bootstrapper` archetype is also removed in this commit.
|
|
12498
|
-
// Dev-only (repo clone privileges)
|
|
12499
|
-
"dev.build",
|
|
12500
|
-
"dev.test",
|
|
12501
|
-
"dev.release"
|
|
12502
|
-
];
|
|
12503
|
-
var CAPABILITY_SET = new Set(CAPABILITY_NAMES);
|
|
12504
|
-
function isCapability(value) {
|
|
12505
|
-
return CAPABILITY_SET.has(value);
|
|
12506
|
-
}
|
|
12507
|
-
function assertCapability(value) {
|
|
12508
|
-
if (!isCapability(value)) {
|
|
12509
|
-
throw new Error(
|
|
12510
|
-
`Unknown capability "${value}". Valid capabilities: ${CAPABILITY_NAMES.join(", ")}`
|
|
12222
|
+
throw err;
|
|
12223
|
+
}
|
|
12224
|
+
const token = writeToken();
|
|
12225
|
+
const composeFile = findComposeFile();
|
|
12226
|
+
if (!fs19.existsSync(composeFile)) {
|
|
12227
|
+
printError(`compose.yaml not found at ${composeFile}. Run from the olam project root.`);
|
|
12228
|
+
removeToken();
|
|
12229
|
+
process.exitCode = 1;
|
|
12230
|
+
return;
|
|
12231
|
+
}
|
|
12232
|
+
const authSecret = readAuthSecret2();
|
|
12233
|
+
if (authSecret === null) {
|
|
12234
|
+
printWarning(
|
|
12235
|
+
`${authSecretPath()} not found or empty. host-cp will boot, but credential surfaces (auth fleet, hotswap) will fail with 401 until you run \`olam auth up\` to (re)generate the shared secret.`
|
|
12511
12236
|
);
|
|
12512
12237
|
}
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
{
|
|
12519
|
-
name: "user",
|
|
12520
|
-
description: "Day-to-day world operator. Creates, runs, and tears down worlds; decides PR gates; reads the workspace catalog.",
|
|
12521
|
-
capabilities: [
|
|
12522
|
-
"world.create",
|
|
12523
|
-
"world.operate",
|
|
12524
|
-
"world.destroy",
|
|
12525
|
-
"workspace.read",
|
|
12526
|
-
"auth.login",
|
|
12527
|
-
"auth.read",
|
|
12528
|
-
"pr-gate.read",
|
|
12529
|
-
"pr-gate.decide"
|
|
12530
|
-
]
|
|
12531
|
-
},
|
|
12532
|
-
{
|
|
12533
|
-
name: "workspace-curator",
|
|
12534
|
-
description: "User who also curates the workspace catalog. Typical: team lead scoping their squad's repo bundles.",
|
|
12535
|
-
inherits: ["user"],
|
|
12536
|
-
capabilities: ["workspace.write"]
|
|
12537
|
-
},
|
|
12538
|
-
{
|
|
12539
|
-
name: "policy-admin",
|
|
12540
|
-
description: "User who also sets deployment policy (permission mode, PR-gate defaults). No catalog or role authority.",
|
|
12541
|
-
inherits: ["user"],
|
|
12542
|
-
capabilities: ["policy.set"]
|
|
12543
|
-
},
|
|
12544
|
-
// Phase 6b.2/A5: `bootstrapper` archetype removed. Its capabilities
|
|
12545
|
-
// (bootstrap.install, bootstrap.cf-deploy, auth.manage) are no
|
|
12546
|
-
// longer enforced — `/bootstrap` route + `auth.manage` route
|
|
12547
|
-
// gating are gone (deleted in A2). The trim keeps `admin` valid
|
|
12548
|
-
// (no dangling inherits ref) and lets schema push succeed.
|
|
12549
|
-
{
|
|
12550
|
-
name: "admin",
|
|
12551
|
-
description: "Full operational admin. Unions user + workspace-curator + policy-admin. Pylon owns role grants now (no role.manage).",
|
|
12552
|
-
inherits: ["user", "workspace-curator", "policy-admin"],
|
|
12553
|
-
capabilities: []
|
|
12554
|
-
},
|
|
12555
|
-
{
|
|
12556
|
-
name: "dev",
|
|
12557
|
-
description: "Contributor to Olam itself. Everything an admin has, plus repo-local dev operations (build, test, release).",
|
|
12558
|
-
inherits: ["admin"],
|
|
12559
|
-
capabilities: ["dev.build", "dev.test", "dev.release"]
|
|
12238
|
+
const ghToken = captureGhToken();
|
|
12239
|
+
if (ghToken === null) {
|
|
12240
|
+
printWarning(
|
|
12241
|
+
"GitHub CLI not authenticated; PR badges will not appear in the inbox. Run `gh auth login` then `olam host-cp restart`."
|
|
12242
|
+
);
|
|
12560
12243
|
}
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12244
|
+
const composeEnv = buildComposeEnv(authSecret, ghToken);
|
|
12245
|
+
const PULL_BACKOFF_MS = [0, 1e3, 3e3];
|
|
12246
|
+
let pullOk = false;
|
|
12247
|
+
let lastPullStderr = "";
|
|
12248
|
+
for (let attempt = 0; attempt < PULL_BACKOFF_MS.length; attempt += 1) {
|
|
12249
|
+
if (PULL_BACKOFF_MS[attempt] > 0) {
|
|
12250
|
+
await new Promise((r) => setTimeout(r, PULL_BACKOFF_MS[attempt]));
|
|
12251
|
+
}
|
|
12252
|
+
const pullResult = runCompose(
|
|
12253
|
+
["pull", "--quiet", "docker-socket-proxy"],
|
|
12254
|
+
composeFile,
|
|
12255
|
+
composeEnv
|
|
12256
|
+
);
|
|
12257
|
+
if (pullResult.ok) {
|
|
12258
|
+
pullOk = true;
|
|
12259
|
+
break;
|
|
12260
|
+
}
|
|
12261
|
+
lastPullStderr = pullResult.stderr;
|
|
12262
|
+
}
|
|
12263
|
+
if (!pullOk) {
|
|
12264
|
+
printError("docker compose pull docker-socket-proxy failed after 3 attempts");
|
|
12265
|
+
process.stderr.write(lastPullStderr);
|
|
12266
|
+
removeToken();
|
|
12267
|
+
process.exitCode = 1;
|
|
12268
|
+
return;
|
|
12269
|
+
}
|
|
12270
|
+
const result = runCompose(["up", "-d"], composeFile, composeEnv);
|
|
12271
|
+
if (!result.ok) {
|
|
12272
|
+
printError("docker compose up failed");
|
|
12273
|
+
process.stderr.write(result.stderr);
|
|
12274
|
+
removeToken();
|
|
12275
|
+
process.exitCode = 1;
|
|
12276
|
+
return;
|
|
12277
|
+
}
|
|
12278
|
+
const deadline = Date.now() + 1e4;
|
|
12279
|
+
let healthy = false;
|
|
12280
|
+
while (Date.now() < deadline) {
|
|
12281
|
+
const h = await probeHealth();
|
|
12282
|
+
if (h) {
|
|
12283
|
+
healthy = true;
|
|
12284
|
+
break;
|
|
12285
|
+
}
|
|
12286
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
12287
|
+
}
|
|
12288
|
+
if (!healthy) {
|
|
12289
|
+
printWarning("Host CP started but /health did not respond within 10s. Check `docker compose logs host-cp`.");
|
|
12290
|
+
}
|
|
12291
|
+
const container = await findHostCpContainer();
|
|
12292
|
+
if (container) {
|
|
12293
|
+
writePid(1);
|
|
12294
|
+
}
|
|
12295
|
+
printSuccess(`Host CP running at http://127.0.0.1:${HOST_CP_PORT}`);
|
|
12296
|
+
if (opts.showToken) {
|
|
12297
|
+
printInfo("Token", token);
|
|
12298
|
+
} else {
|
|
12299
|
+
printInfo("Token", `(written to ${tokenPath()}; pass --show-token to print)`);
|
|
12300
|
+
}
|
|
12301
|
+
printInfo("Open", `http://127.0.0.1:${HOST_CP_PORT}`);
|
|
12567
12302
|
}
|
|
12568
|
-
function
|
|
12569
|
-
return
|
|
12303
|
+
async function stopHostCp() {
|
|
12304
|
+
return handleStop();
|
|
12570
12305
|
}
|
|
12571
|
-
|
|
12572
|
-
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
this.name = name;
|
|
12579
|
-
this.known = known;
|
|
12580
|
-
this.name = "UnknownArchetypeError";
|
|
12306
|
+
async function handleStop() {
|
|
12307
|
+
const composeFile = findComposeFile();
|
|
12308
|
+
if (!fs19.existsSync(composeFile)) {
|
|
12309
|
+
printWarning(`compose.yaml not found at ${composeFile}. Cleaning up token + PID anyway.`);
|
|
12310
|
+
removeToken();
|
|
12311
|
+
removePid();
|
|
12312
|
+
return;
|
|
12581
12313
|
}
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
12587
|
-
|
|
12588
|
-
`Archetype inheritance cycle detected: ${path40.join(" \u2192 ")} \u2192 ${path40[0] ?? "?"}`
|
|
12589
|
-
);
|
|
12590
|
-
this.path = path40;
|
|
12591
|
-
this.name = "ArchetypeCycleError";
|
|
12314
|
+
const existing = await findHostCpContainer();
|
|
12315
|
+
if (!existing) {
|
|
12316
|
+
printInfo("Host CP", "not running");
|
|
12317
|
+
removeToken();
|
|
12318
|
+
removePid();
|
|
12319
|
+
return;
|
|
12592
12320
|
}
|
|
12593
|
-
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
|
|
12597
|
-
|
|
12598
|
-
|
|
12321
|
+
const result = runCompose(["down"], composeFile);
|
|
12322
|
+
if (!result.ok) {
|
|
12323
|
+
printError("docker compose down failed");
|
|
12324
|
+
process.stderr.write(result.stderr);
|
|
12325
|
+
process.exitCode = 1;
|
|
12326
|
+
return;
|
|
12327
|
+
}
|
|
12328
|
+
removeToken();
|
|
12329
|
+
removePid();
|
|
12330
|
+
printSuccess("Host CP stopped");
|
|
12599
12331
|
}
|
|
12600
|
-
function
|
|
12601
|
-
|
|
12602
|
-
|
|
12332
|
+
async function buildStatusReport() {
|
|
12333
|
+
const container = await findHostCpContainer();
|
|
12334
|
+
const health = await probeHealth();
|
|
12335
|
+
const tokenFile = tokenPath();
|
|
12336
|
+
const tokenPresent = fs19.existsSync(tokenFile);
|
|
12337
|
+
let tokenModeOk = false;
|
|
12338
|
+
if (tokenPresent) {
|
|
12339
|
+
const mode = fs19.statSync(tokenFile).mode & 511;
|
|
12340
|
+
tokenModeOk = mode === 384;
|
|
12603
12341
|
}
|
|
12604
|
-
const
|
|
12605
|
-
|
|
12606
|
-
|
|
12342
|
+
const pidPresent = fs19.existsSync(pidPath());
|
|
12343
|
+
let stack;
|
|
12344
|
+
if (!container) {
|
|
12345
|
+
stack = "not_started";
|
|
12346
|
+
} else if (container.state === "running" && health) {
|
|
12347
|
+
stack = "running";
|
|
12348
|
+
} else if (container.state === "running") {
|
|
12349
|
+
stack = "partial";
|
|
12350
|
+
} else {
|
|
12351
|
+
stack = "stopped";
|
|
12607
12352
|
}
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12353
|
+
return {
|
|
12354
|
+
stack,
|
|
12355
|
+
container,
|
|
12356
|
+
health,
|
|
12357
|
+
token_present: tokenPresent,
|
|
12358
|
+
token_mode_ok: tokenModeOk,
|
|
12359
|
+
pid_present: pidPresent,
|
|
12360
|
+
url: `http://127.0.0.1:${HOST_CP_PORT}`
|
|
12361
|
+
};
|
|
12362
|
+
}
|
|
12363
|
+
async function handleStatus(opts) {
|
|
12364
|
+
const report = await buildStatusReport();
|
|
12365
|
+
if (opts.json) {
|
|
12366
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
12367
|
+
process.exitCode = report.stack === "running" ? 0 : 1;
|
|
12368
|
+
return;
|
|
12611
12369
|
}
|
|
12612
|
-
|
|
12613
|
-
|
|
12370
|
+
printHeader("Host CP Status");
|
|
12371
|
+
printInfo("Stack", report.stack);
|
|
12372
|
+
printInfo("URL", report.url);
|
|
12373
|
+
if (report.container) {
|
|
12374
|
+
printInfo("Container", `${report.container.id} (${report.container.state})`);
|
|
12375
|
+
printInfo("Status line", report.container.status);
|
|
12376
|
+
} else {
|
|
12377
|
+
printInfo("Container", "not found (run `olam host-cp start`)");
|
|
12378
|
+
}
|
|
12379
|
+
if (report.health) {
|
|
12380
|
+
printInfo("Health", "ok");
|
|
12381
|
+
printInfo("Uptime", String(report.health["uptime_seconds"] ?? "unknown") + "s");
|
|
12382
|
+
const cache = report.health["cache"];
|
|
12383
|
+
if (cache) {
|
|
12384
|
+
printInfo("Cached worlds", String(cache.worlds?.length ?? 0));
|
|
12385
|
+
printInfo("Cache TTL", `${cache.ttl_sec ?? "unknown"}s`);
|
|
12386
|
+
}
|
|
12387
|
+
const sse = report.health["sse"];
|
|
12388
|
+
if (sse) {
|
|
12389
|
+
printInfo("SSE active", `${sse.active ?? 0} / ${sse.cap ?? 0}`);
|
|
12390
|
+
}
|
|
12391
|
+
} else {
|
|
12392
|
+
printInfo("Health", "not responding");
|
|
12614
12393
|
}
|
|
12394
|
+
printInfo("Token file", report.token_present ? report.token_mode_ok ? "present (mode 600)" : "present (BAD MODE \u2014 should be 600)" : "absent");
|
|
12395
|
+
printInfo("PID file", report.pid_present ? "present" : "absent");
|
|
12396
|
+
process.exitCode = report.stack === "running" ? 0 : 1;
|
|
12397
|
+
}
|
|
12398
|
+
function registerHostCp(program2) {
|
|
12399
|
+
const hostCp = program2.command("host-cp").description("Manage the Olam host control plane container");
|
|
12400
|
+
hostCp.command("start").description("Start the host CP container (token regenerated each call)").option("--show-token", "Print the generated token to stdout (default: hide)").action(async (opts) => {
|
|
12401
|
+
await handleStart({ showToken: opts.showToken === true });
|
|
12402
|
+
});
|
|
12403
|
+
hostCp.command("stop").description("Stop the host CP container + remove token + PID files").action(async () => {
|
|
12404
|
+
await handleStop();
|
|
12405
|
+
});
|
|
12406
|
+
hostCp.command("status").description("Show host CP container + health diagnostics").option("--json", "Output as JSON (machine-parseable; sets exit code)").action(async (opts) => {
|
|
12407
|
+
await handleStatus({ json: opts.json === true });
|
|
12408
|
+
});
|
|
12409
|
+
hostCp.command("register").description("Register a world with the running host CP so it appears in the unified UI").requiredOption("--world <id>", "World id (the docker container suffix, e.g. gold-arc-1454)").option("--port <port>", "Override per-world CP port; default: discovered from `olam list`").action(async (opts) => {
|
|
12410
|
+
await handleRegister({ world: opts.world, port: opts.port });
|
|
12411
|
+
});
|
|
12412
|
+
hostCp.command("deregister").description("Remove a world from the host CP registry (does NOT destroy the world)").requiredOption("--world <id>", "World id to remove").action(async (opts) => {
|
|
12413
|
+
await handleDeregister({ world: opts.world });
|
|
12414
|
+
});
|
|
12615
12415
|
}
|
|
12616
|
-
|
|
12617
|
-
// src/commands/install.ts
|
|
12618
|
-
var ROLE_FILE_PATH = path6.join(os3.homedir(), ".olam", "role.yaml");
|
|
12619
|
-
function readRoleFile() {
|
|
12620
|
-
if (!fs6.existsSync(ROLE_FILE_PATH)) return null;
|
|
12416
|
+
async function discoverWorldPort(worldId) {
|
|
12621
12417
|
try {
|
|
12622
|
-
const
|
|
12623
|
-
const
|
|
12624
|
-
if (!
|
|
12625
|
-
const
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
const caps = /^\s+-\s+(\S+)$/.exec(line);
|
|
12629
|
-
if (caps) customCapabilities.push(caps[1]);
|
|
12630
|
-
}
|
|
12631
|
-
const installedAtMatch = /installedAt:\s*(\d+)/.exec(raw);
|
|
12632
|
-
return {
|
|
12633
|
-
archetype,
|
|
12634
|
-
customCapabilities,
|
|
12635
|
-
installedAt: installedAtMatch ? Number(installedAtMatch[1]) : 0
|
|
12636
|
-
};
|
|
12418
|
+
const { loadContext: loadContext2 } = await Promise.resolve().then(() => (init_context(), context_exports));
|
|
12419
|
+
const { ctx } = await loadContext2();
|
|
12420
|
+
if (!ctx) return null;
|
|
12421
|
+
const world = await ctx.worldManager.getWorld(worldId);
|
|
12422
|
+
if (!world) return null;
|
|
12423
|
+
return 19080 + world.portOffset;
|
|
12637
12424
|
} catch {
|
|
12638
12425
|
return null;
|
|
12639
12426
|
}
|
|
12640
12427
|
}
|
|
12641
|
-
function
|
|
12642
|
-
|
|
12643
|
-
|
|
12644
|
-
|
|
12645
|
-
customCapabilities: [...role.customCapabilities],
|
|
12646
|
-
installedAt: role.installedAt
|
|
12647
|
-
});
|
|
12648
|
-
fs6.writeFileSync(ROLE_FILE_PATH, yaml, { mode: 420 });
|
|
12649
|
-
}
|
|
12650
|
-
function nextStepsFor(archetype) {
|
|
12651
|
-
switch (archetype) {
|
|
12652
|
-
case "user":
|
|
12653
|
-
return [
|
|
12654
|
-
'You can now run `olam create --task "..."` against a workspace your admin has curated.',
|
|
12655
|
-
"Register the Claude Code plugin if you haven't: `claude plugin install ./plugin`."
|
|
12656
|
-
];
|
|
12657
|
-
case "workspace-curator":
|
|
12658
|
-
return [
|
|
12659
|
-
"You can now curate the catalog: `olam workspace add <name> --from-config`.",
|
|
12660
|
-
"Share workspace YAML files via your usual dotfiles / rsync flow."
|
|
12661
|
-
];
|
|
12662
|
-
case "policy-admin":
|
|
12663
|
-
return [
|
|
12664
|
-
"Set deployment-wide policy via env vars (OLAM_CLAUDE_PERMISSION_MODE) or wrangler secrets.",
|
|
12665
|
-
"PR-gate defaults can be overridden per-workspace under `policy.set`."
|
|
12666
|
-
];
|
|
12667
|
-
case "bootstrapper":
|
|
12668
|
-
return [
|
|
12669
|
-
"Start the auth container: `olam auth up`, then `olam auth login`.",
|
|
12670
|
-
"For Cloudflare deployments: `cd packages/cloudflare-worker && pnpm wrangler deploy`."
|
|
12671
|
-
];
|
|
12672
|
-
case "admin":
|
|
12673
|
-
return [
|
|
12674
|
-
"Start the auth container: `olam auth up`, then `olam auth login`.",
|
|
12675
|
-
"Curate workspaces: `olam workspace add <name> --from-config`.",
|
|
12676
|
-
"Assign narrower archetypes to teammates (CF side, once slice C lands): use `role.manage`."
|
|
12677
|
-
];
|
|
12678
|
-
case "dev":
|
|
12679
|
-
return [
|
|
12680
|
-
"You have repo-local privileges. Run `npm run build:ci && npm run test:ci`.",
|
|
12681
|
-
"Everything an admin has is available."
|
|
12682
|
-
];
|
|
12683
|
-
default:
|
|
12684
|
-
return [];
|
|
12685
|
-
}
|
|
12428
|
+
async function readHostCpToken2() {
|
|
12429
|
+
const tp = tokenPath();
|
|
12430
|
+
if (!fs19.existsSync(tp)) return null;
|
|
12431
|
+
return fs19.readFileSync(tp, "utf-8").trim();
|
|
12686
12432
|
}
|
|
12687
|
-
function
|
|
12688
|
-
|
|
12689
|
-
|
|
12690
|
-
|
|
12691
|
-
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
}
|
|
12695
|
-
[]
|
|
12696
|
-
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
|
|
12710
|
-
console.log(` ${cap}`);
|
|
12433
|
+
async function callHostCpProxy(method, worldId, path42, body) {
|
|
12434
|
+
const token = await readHostCpToken2();
|
|
12435
|
+
if (!token) return { ok: false, status: 0, error: "no token (host CP not started)" };
|
|
12436
|
+
const url = `http://127.0.0.1:${HOST_CP_PORT}/api/world/${encodeURIComponent(worldId)}${path42}`;
|
|
12437
|
+
try {
|
|
12438
|
+
const headers = {
|
|
12439
|
+
Authorization: `Bearer ${token}`
|
|
12440
|
+
};
|
|
12441
|
+
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
12442
|
+
const res = await fetch(url, {
|
|
12443
|
+
method,
|
|
12444
|
+
headers,
|
|
12445
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
12446
|
+
});
|
|
12447
|
+
if (!res.ok) {
|
|
12448
|
+
const text = await res.text().catch(() => "");
|
|
12449
|
+
let errMsg = text || `HTTP ${res.status}`;
|
|
12450
|
+
try {
|
|
12451
|
+
const parsed = JSON.parse(text);
|
|
12452
|
+
if (parsed && typeof parsed === "object" && "error" in parsed) {
|
|
12453
|
+
errMsg = String(parsed.error);
|
|
12454
|
+
}
|
|
12455
|
+
} catch {
|
|
12711
12456
|
}
|
|
12712
|
-
return;
|
|
12457
|
+
return { ok: false, status: res.status, error: errMsg };
|
|
12713
12458
|
}
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12459
|
+
const data = await res.json().catch(() => null);
|
|
12460
|
+
return { ok: true, status: res.status, data };
|
|
12461
|
+
} catch (err) {
|
|
12462
|
+
return {
|
|
12463
|
+
ok: false,
|
|
12464
|
+
status: 0,
|
|
12465
|
+
error: err instanceof Error ? err.message : String(err)
|
|
12466
|
+
};
|
|
12467
|
+
}
|
|
12468
|
+
}
|
|
12469
|
+
async function callHostCpRegistry(method, body) {
|
|
12470
|
+
const token = await readHostCpToken2();
|
|
12471
|
+
if (!token) return { ok: false, status: 0, error: "no token (host CP not started)" };
|
|
12472
|
+
const url = method === "DELETE" ? `http://127.0.0.1:${HOST_CP_PORT}/api/admin/registry/${encodeURIComponent(body.id)}` : `http://127.0.0.1:${HOST_CP_PORT}/api/admin/registry`;
|
|
12473
|
+
try {
|
|
12474
|
+
const res = await fetch(url, {
|
|
12475
|
+
method,
|
|
12476
|
+
headers: {
|
|
12477
|
+
Authorization: `Bearer ${token}`,
|
|
12478
|
+
...method === "POST" ? { "Content-Type": "application/json" } : {}
|
|
12479
|
+
},
|
|
12480
|
+
...method === "POST" ? { body: JSON.stringify(body) } : {}
|
|
12481
|
+
});
|
|
12482
|
+
if (!res.ok) {
|
|
12483
|
+
const text = await res.text().catch(() => "");
|
|
12484
|
+
return { ok: false, status: res.status, error: text || `HTTP ${res.status}` };
|
|
12721
12485
|
}
|
|
12722
|
-
|
|
12723
|
-
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12486
|
+
return { ok: true, status: res.status };
|
|
12487
|
+
} catch (err) {
|
|
12488
|
+
return {
|
|
12489
|
+
ok: false,
|
|
12490
|
+
status: 0,
|
|
12491
|
+
error: err instanceof Error ? err.message : String(err)
|
|
12492
|
+
};
|
|
12493
|
+
}
|
|
12494
|
+
}
|
|
12495
|
+
async function handleRegister(opts) {
|
|
12496
|
+
printHeader("Register world with host CP");
|
|
12497
|
+
let port = null;
|
|
12498
|
+
if (opts.port) {
|
|
12499
|
+
port = parseInt(opts.port, 10);
|
|
12500
|
+
if (!Number.isFinite(port) || port <= 0) {
|
|
12501
|
+
printError(`Invalid --port value: ${opts.port}`);
|
|
12727
12502
|
process.exitCode = 1;
|
|
12728
12503
|
return;
|
|
12729
12504
|
}
|
|
12730
|
-
|
|
12731
|
-
|
|
12732
|
-
|
|
12733
|
-
extras.push(assertCapability(raw));
|
|
12734
|
-
} catch (err) {
|
|
12735
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
12736
|
-
process.exitCode = 1;
|
|
12737
|
-
return;
|
|
12738
|
-
}
|
|
12739
|
-
}
|
|
12740
|
-
const existing = readRoleFile();
|
|
12741
|
-
if (existing && !opts.force) {
|
|
12505
|
+
} else {
|
|
12506
|
+
port = await discoverWorldPort(opts.world);
|
|
12507
|
+
if (port === null) {
|
|
12742
12508
|
printError(
|
|
12743
|
-
`
|
|
12509
|
+
`Could not discover port for world ${opts.world}. Pass --port explicitly or check that the world exists in \`olam list\`.`
|
|
12744
12510
|
);
|
|
12745
12511
|
process.exitCode = 1;
|
|
12746
12512
|
return;
|
|
12747
12513
|
}
|
|
12748
|
-
|
|
12749
|
-
|
|
12750
|
-
|
|
12751
|
-
|
|
12752
|
-
|
|
12753
|
-
|
|
12754
|
-
process.exitCode = 1;
|
|
12755
|
-
return;
|
|
12756
|
-
}
|
|
12757
|
-
throw err;
|
|
12758
|
-
}
|
|
12759
|
-
for (const extra of extras) resolved.add(extra);
|
|
12760
|
-
writeRoleFile({
|
|
12761
|
-
archetype: archetype.name,
|
|
12762
|
-
customCapabilities: extras,
|
|
12763
|
-
installedAt: Date.now()
|
|
12764
|
-
});
|
|
12765
|
-
printHeader(`Installed as ${pc4.bold(archetype.name)}`);
|
|
12766
|
-
printInfo("File", ROLE_FILE_PATH);
|
|
12767
|
-
printInfo("Description", archetype.description);
|
|
12768
|
-
printInfo("Capabilities", `${resolved.size} total`);
|
|
12769
|
-
if (extras.length > 0) {
|
|
12770
|
-
printInfo("Added beyond preset", extras.join(", "));
|
|
12771
|
-
}
|
|
12772
|
-
const steps = nextStepsFor(archetype.name);
|
|
12773
|
-
if (steps.length > 0) {
|
|
12774
|
-
printHeader("Next steps");
|
|
12775
|
-
for (const step of steps) console.log(` ${pc4.dim("\u2022")} ${step}`);
|
|
12514
|
+
}
|
|
12515
|
+
const result = await callHostCpRegistry("POST", { id: opts.world, port });
|
|
12516
|
+
if (!result.ok) {
|
|
12517
|
+
printError(`Register failed: ${result.error}`);
|
|
12518
|
+
if (result.status === 0) {
|
|
12519
|
+
printInfo("Hint", "Is host CP running? `olam host-cp status`");
|
|
12776
12520
|
}
|
|
12777
|
-
|
|
12778
|
-
|
|
12779
|
-
}
|
|
12780
|
-
|
|
12781
|
-
// src/commands/auth.ts
|
|
12782
|
-
init_auth();
|
|
12783
|
-
import pc8 from "picocolors";
|
|
12784
|
-
import * as readline from "node:readline/promises";
|
|
12785
|
-
import { spawn as spawn3 } from "node:child_process";
|
|
12786
|
-
|
|
12787
|
-
// src/commands/auth-status.ts
|
|
12788
|
-
import * as fs8 from "node:fs";
|
|
12789
|
-
import * as os5 from "node:os";
|
|
12790
|
-
import * as path9 from "node:path";
|
|
12791
|
-
import pc5 from "picocolors";
|
|
12792
|
-
|
|
12793
|
-
// ../auth-logic/dist/effective-state.js
|
|
12794
|
-
function effectiveState(account, now = Date.now()) {
|
|
12795
|
-
const persisted = account.state ?? (account.rateLimited ? "cooldown" : "active");
|
|
12796
|
-
if (persisted === "disabled")
|
|
12797
|
-
return "disabled";
|
|
12798
|
-
if (account.expiresAt != null && account.expiresAt <= now)
|
|
12799
|
-
return "expired";
|
|
12800
|
-
if (persisted === "cooldown" || persisted === "usage-capped") {
|
|
12801
|
-
const reset = account.rateLimitResetsAt ? new Date(account.rateLimitResetsAt).getTime() : 0;
|
|
12802
|
-
if (reset > 0 && reset <= now)
|
|
12803
|
-
return "active";
|
|
12804
|
-
return persisted;
|
|
12521
|
+
process.exitCode = 1;
|
|
12522
|
+
return;
|
|
12805
12523
|
}
|
|
12806
|
-
|
|
12524
|
+
printSuccess(`Registered ${opts.world} \u2192 :${port}`);
|
|
12525
|
+
printInfo("UI", `http://127.0.0.1:${HOST_CP_PORT}/world/${encodeURIComponent(opts.world)}`);
|
|
12807
12526
|
}
|
|
12808
|
-
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12820
|
-
const bLast = b.lastUsed ? new Date(b.lastUsed).getTime() : 0;
|
|
12821
|
-
return aLast - bLast;
|
|
12822
|
-
})[0] ?? null;
|
|
12527
|
+
async function handleDeregister(opts) {
|
|
12528
|
+
printHeader("Deregister world from host CP");
|
|
12529
|
+
const result = await callHostCpRegistry("DELETE", { id: opts.world });
|
|
12530
|
+
if (!result.ok) {
|
|
12531
|
+
printError(`Deregister failed: ${result.error}`);
|
|
12532
|
+
if (result.status === 0) {
|
|
12533
|
+
printInfo("Hint", "Is host CP running? `olam host-cp status`");
|
|
12534
|
+
}
|
|
12535
|
+
process.exitCode = 1;
|
|
12536
|
+
return;
|
|
12537
|
+
}
|
|
12538
|
+
printSuccess(`Deregistered ${opts.world}`);
|
|
12823
12539
|
}
|
|
12540
|
+
var HOST_CP_PORT;
|
|
12541
|
+
var init_host_cp = __esm({
|
|
12542
|
+
"src/commands/host-cp.ts"() {
|
|
12543
|
+
"use strict";
|
|
12544
|
+
init_dist();
|
|
12545
|
+
init_output();
|
|
12546
|
+
init_docker_host();
|
|
12547
|
+
init_open_url();
|
|
12548
|
+
HOST_CP_PORT = 19e3;
|
|
12549
|
+
}
|
|
12550
|
+
});
|
|
12824
12551
|
|
|
12825
|
-
//
|
|
12826
|
-
|
|
12827
|
-
|
|
12828
|
-
|
|
12552
|
+
// src/install-root.ts
|
|
12553
|
+
var install_root_exports = {};
|
|
12554
|
+
__export(install_root_exports, {
|
|
12555
|
+
MissingBuildScriptError: () => MissingBuildScriptError,
|
|
12556
|
+
installRoot: () => installRoot,
|
|
12557
|
+
isDevMode: () => isDevMode,
|
|
12558
|
+
resolveBuildScript: () => resolveBuildScript
|
|
12559
|
+
});
|
|
12560
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
12561
|
+
import { dirname as dirname13, join as join24, resolve as resolve6 } from "node:path";
|
|
12562
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
12563
|
+
function installRoot(metaUrl = import.meta.url) {
|
|
12564
|
+
const here = fileURLToPath3(metaUrl);
|
|
12565
|
+
return resolve6(dirname13(here), "..");
|
|
12566
|
+
}
|
|
12567
|
+
function isDevMode(env = process.env, installRootDir = installRoot()) {
|
|
12568
|
+
if (env.OLAM_DEV !== "1") return false;
|
|
12569
|
+
const repoRoot = resolve6(installRootDir, "..", "..");
|
|
12570
|
+
return existsSync18(join24(repoRoot, "packages")) && existsSync18(join24(repoRoot, "package.json"));
|
|
12571
|
+
}
|
|
12572
|
+
function resolveBuildScript(input) {
|
|
12573
|
+
const { scriptRelPath, env = process.env, installRootDir = installRoot() } = input;
|
|
12574
|
+
if (!isDevMode(env, installRootDir)) {
|
|
12575
|
+
throw new MissingBuildScriptError(scriptRelPath);
|
|
12576
|
+
}
|
|
12577
|
+
const repoRoot = resolve6(installRootDir, "..", "..");
|
|
12578
|
+
return join24(repoRoot, scriptRelPath);
|
|
12829
12579
|
}
|
|
12580
|
+
var MissingBuildScriptError;
|
|
12581
|
+
var init_install_root = __esm({
|
|
12582
|
+
"src/install-root.ts"() {
|
|
12583
|
+
"use strict";
|
|
12584
|
+
MissingBuildScriptError = class extends Error {
|
|
12585
|
+
constructor(scriptRelPath) {
|
|
12586
|
+
super(
|
|
12587
|
+
`Build script ${scriptRelPath} is not available in this CLI install.
|
|
12588
|
+
Source-build paths require a monorepo clone:
|
|
12589
|
+
git clone https://github.com/pleri/olam && cd olam
|
|
12590
|
+
OLAM_DEV=1 olam <command> --from-source
|
|
12591
|
+
For published-image upgrades (Phase B+), drop the --from-source flag
|
|
12592
|
+
and the CLI will pull pre-built images from ghcr.io/pleri/* by digest.`
|
|
12593
|
+
);
|
|
12594
|
+
this.name = "MissingBuildScriptError";
|
|
12595
|
+
}
|
|
12596
|
+
};
|
|
12597
|
+
}
|
|
12598
|
+
});
|
|
12830
12599
|
|
|
12831
|
-
// src/
|
|
12832
|
-
|
|
12833
|
-
|
|
12834
|
-
|
|
12835
|
-
|
|
12836
|
-
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
|
|
12600
|
+
// src/protocol-version.ts
|
|
12601
|
+
var protocol_version_exports = {};
|
|
12602
|
+
__export(protocol_version_exports, {
|
|
12603
|
+
OLAM_PROTOCOL_VERSIONS_SUPPORTED: () => OLAM_PROTOCOL_VERSIONS_SUPPORTED,
|
|
12604
|
+
checkProtocolOverlap: () => checkProtocolOverlap,
|
|
12605
|
+
inspectImageProtocolVersions: () => inspectImageProtocolVersions,
|
|
12606
|
+
parseProtocolVersionsLabel: () => parseProtocolVersionsLabel
|
|
12607
|
+
});
|
|
12608
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
12609
|
+
function parseProtocolVersionsLabel(labelValue) {
|
|
12610
|
+
if (!labelValue) return [];
|
|
12611
|
+
const parts = labelValue.split(",").map((s) => s.trim()).filter(Boolean);
|
|
12612
|
+
const versions = /* @__PURE__ */ new Set();
|
|
12613
|
+
for (const part of parts) {
|
|
12614
|
+
const n = Number.parseInt(part, 10);
|
|
12615
|
+
if (Number.isFinite(n) && n > 0 && String(n) === part) {
|
|
12616
|
+
versions.add(n);
|
|
12617
|
+
}
|
|
12618
|
+
}
|
|
12619
|
+
return Array.from(versions).sort((a, b) => a - b);
|
|
12842
12620
|
}
|
|
12843
|
-
function
|
|
12844
|
-
const
|
|
12845
|
-
|
|
12846
|
-
|
|
12621
|
+
function checkProtocolOverlap(imageVersions, cliVersions = OLAM_PROTOCOL_VERSIONS_SUPPORTED) {
|
|
12622
|
+
const cliSet = new Set(cliVersions);
|
|
12623
|
+
const overlap = imageVersions.filter((v) => cliSet.has(v));
|
|
12624
|
+
if (imageVersions.length === 0) {
|
|
12625
|
+
return {
|
|
12626
|
+
overlap: [],
|
|
12627
|
+
imageVersions: [],
|
|
12628
|
+
cliVersions,
|
|
12629
|
+
compatible: false,
|
|
12630
|
+
remedy: `Devbox image is missing the \`olam.protocol.versions\` LABEL. This CLI requires versions [${cliVersions.join(", ")}]. See docs/architecture/devbox-contract.md \xA71 for the contract; rebuild the image with \`LABEL olam.protocol.versions="1"\`.`
|
|
12631
|
+
};
|
|
12632
|
+
}
|
|
12633
|
+
if (overlap.length === 0) {
|
|
12634
|
+
const imgLow = Math.min(...imageVersions);
|
|
12635
|
+
const imgHigh = Math.max(...imageVersions);
|
|
12636
|
+
const cliLow = Math.min(...cliVersions);
|
|
12637
|
+
const cliHigh = Math.max(...cliVersions);
|
|
12638
|
+
return {
|
|
12639
|
+
overlap: [],
|
|
12640
|
+
imageVersions: [...imageVersions],
|
|
12641
|
+
cliVersions,
|
|
12642
|
+
compatible: false,
|
|
12643
|
+
remedy: `Devbox image protocol versions [${imageVersions.join(", ")}] don't overlap CLI's [${cliVersions.join(", ")}]. ` + (imgHigh < cliLow ? `The image is older than this CLI supports \u2014 rebuild against the contract version ${cliLow}+ at docs/architecture/devbox-contract.md.` : imgLow > cliHigh ? `The image is newer than this CLI supports \u2014 pin a compatible CLI: \`npm install -g @pleri/olam-cli@<version-with-protocol-${imgLow}>\`.` : `Pin a compatible CLI version that overlaps with the image's range.`)
|
|
12644
|
+
};
|
|
12645
|
+
}
|
|
12646
|
+
return {
|
|
12647
|
+
overlap,
|
|
12648
|
+
imageVersions: [...imageVersions],
|
|
12649
|
+
cliVersions,
|
|
12650
|
+
compatible: true,
|
|
12651
|
+
remedy: ""
|
|
12652
|
+
};
|
|
12847
12653
|
}
|
|
12848
|
-
function
|
|
12849
|
-
|
|
12654
|
+
function inspectImageProtocolVersions(imageRef, dockerInspect = realDockerInspect) {
|
|
12655
|
+
const { exitCode, stdout, stderr } = dockerInspect(imageRef);
|
|
12656
|
+
if (exitCode !== 0) {
|
|
12657
|
+
return {
|
|
12658
|
+
imageRef,
|
|
12659
|
+
versions: [],
|
|
12660
|
+
inspectFailed: true,
|
|
12661
|
+
inspectError: stderr.trim() || `docker inspect exited ${exitCode}`
|
|
12662
|
+
};
|
|
12663
|
+
}
|
|
12664
|
+
const raw = stdout.trim();
|
|
12665
|
+
const value = raw === "<no value>" ? "" : raw;
|
|
12666
|
+
return {
|
|
12667
|
+
imageRef,
|
|
12668
|
+
versions: parseProtocolVersionsLabel(value),
|
|
12669
|
+
inspectFailed: false,
|
|
12670
|
+
inspectError: void 0
|
|
12671
|
+
};
|
|
12850
12672
|
}
|
|
12851
|
-
var
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
const state = effectiveState(account, now);
|
|
12862
|
-
const isPicked = picked != null && account.id === picked.id;
|
|
12863
|
-
const req5h = account.usage?.requestCount5h ?? 0;
|
|
12864
|
-
const last429 = account.usage?.last429At ? localHHMM(account.usage.last429At) : "never";
|
|
12865
|
-
let reason;
|
|
12866
|
-
if (isPicked) {
|
|
12867
|
-
reason = "\u2190 selected";
|
|
12868
|
-
} else if (state === "active") {
|
|
12869
|
-
reason = `req5h=${req5h} (higher than candidate)`;
|
|
12870
|
-
} else if (state === "cooldown") {
|
|
12871
|
-
const resetTime = account.rateLimitResetsAt ? localHHMM(account.rateLimitResetsAt) : "?";
|
|
12872
|
-
reason = `cooldown until ${resetTime}`;
|
|
12873
|
-
} else if (state === "expired") {
|
|
12874
|
-
reason = daysAgoStr(account.expiresAt ?? 0, now);
|
|
12875
|
-
} else {
|
|
12876
|
-
reason = "disabled";
|
|
12877
|
-
}
|
|
12878
|
-
return { id: account.id, label: account.accountLabel ?? account.id, state, reason, req5h, last429, isPicked };
|
|
12879
|
-
});
|
|
12880
|
-
rows.sort((a, b) => {
|
|
12881
|
-
if (a.isPicked !== b.isPicked) return a.isPicked ? -1 : 1;
|
|
12882
|
-
return STATE_PRIORITY[a.state] - STATE_PRIORITY[b.state];
|
|
12883
|
-
});
|
|
12884
|
-
const COL = { id: 17, label: 17, state: 11, reason: 28, req5h: 7 };
|
|
12885
|
-
const lines = [];
|
|
12886
|
-
const hdr = "id".padEnd(COL.id) + "label".padEnd(COL.label) + "state".padEnd(COL.state) + "reason".padEnd(COL.reason) + "req5h".padEnd(COL.req5h) + "last429";
|
|
12887
|
-
lines.push(pc5.dim(hdr));
|
|
12888
|
-
lines.push(pc5.dim("-".repeat(hdr.length)));
|
|
12889
|
-
for (const row of rows) {
|
|
12890
|
-
const id = trunc(row.id, 16).padEnd(COL.id);
|
|
12891
|
-
const label = trunc(row.label, 16).padEnd(COL.label);
|
|
12892
|
-
const stateRaw = row.state.padEnd(COL.state);
|
|
12893
|
-
const stateColored = row.state === "active" ? pc5.green(stateRaw) : row.state === "cooldown" ? pc5.yellow(stateRaw) : row.state === "expired" ? pc5.red(stateRaw) : pc5.dim(stateRaw);
|
|
12894
|
-
const reason = row.reason.padEnd(COL.reason);
|
|
12895
|
-
const req5h = String(row.req5h).padEnd(COL.req5h);
|
|
12896
|
-
if (row.isPicked) {
|
|
12897
|
-
lines.push(
|
|
12898
|
-
pc5.bold(id) + pc5.bold(label) + stateColored + pc5.green(reason) + pc5.dim(req5h) + pc5.dim(row.last429)
|
|
12673
|
+
var OLAM_PROTOCOL_VERSIONS_SUPPORTED, realDockerInspect;
|
|
12674
|
+
var init_protocol_version = __esm({
|
|
12675
|
+
"src/protocol-version.ts"() {
|
|
12676
|
+
"use strict";
|
|
12677
|
+
OLAM_PROTOCOL_VERSIONS_SUPPORTED = [1];
|
|
12678
|
+
realDockerInspect = (imageRef) => {
|
|
12679
|
+
const result = spawnSync5(
|
|
12680
|
+
"docker",
|
|
12681
|
+
["inspect", imageRef, "--format", '{{ index .Config.Labels "olam.protocol.versions" }}'],
|
|
12682
|
+
{ encoding: "utf8", timeout: 1e4 }
|
|
12899
12683
|
);
|
|
12900
|
-
|
|
12901
|
-
|
|
12902
|
-
|
|
12684
|
+
return {
|
|
12685
|
+
exitCode: result.status ?? -1,
|
|
12686
|
+
stdout: result.stdout ?? "",
|
|
12687
|
+
stderr: result.stderr ?? ""
|
|
12688
|
+
};
|
|
12689
|
+
};
|
|
12690
|
+
}
|
|
12691
|
+
});
|
|
12692
|
+
|
|
12693
|
+
// src/registry-allowlist.ts
|
|
12694
|
+
var registry_allowlist_exports = {};
|
|
12695
|
+
__export(registry_allowlist_exports, {
|
|
12696
|
+
decideAllowlist: () => decideAllowlist,
|
|
12697
|
+
resolveDevboxImageOverride: () => resolveDevboxImageOverride
|
|
12698
|
+
});
|
|
12699
|
+
function decideAllowlist(input) {
|
|
12700
|
+
const { imageRef, allowCustomRegistry } = input;
|
|
12701
|
+
const allowedByDefault = DEFAULT_ALLOWLIST_PATTERNS.some((re) => re.test(imageRef));
|
|
12702
|
+
if (allowedByDefault) {
|
|
12703
|
+
return {
|
|
12704
|
+
imageRef,
|
|
12705
|
+
allowedByDefault: true,
|
|
12706
|
+
accepted: true,
|
|
12707
|
+
stderrLine: ""
|
|
12708
|
+
};
|
|
12903
12709
|
}
|
|
12904
|
-
if (
|
|
12905
|
-
|
|
12906
|
-
|
|
12907
|
-
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12710
|
+
if (allowCustomRegistry) {
|
|
12711
|
+
return {
|
|
12712
|
+
imageRef,
|
|
12713
|
+
allowedByDefault: false,
|
|
12714
|
+
accepted: true,
|
|
12715
|
+
stderrLine: `Warning: using custom devbox image '${imageRef}'. (--allow-custom-registry was specified.) Verify the source and digest before proceeding.`
|
|
12716
|
+
};
|
|
12911
12717
|
}
|
|
12912
|
-
return { output: lines.join("\n"), exitCode: 0 };
|
|
12913
|
-
}
|
|
12914
|
-
function toSafeAccount(a) {
|
|
12915
12718
|
return {
|
|
12916
|
-
|
|
12917
|
-
|
|
12918
|
-
|
|
12919
|
-
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
state: a.state,
|
|
12923
|
-
usage: a.usage ? { requestCount5h: a.usage.requestCount5h, last429At: a.usage.last429At } : void 0
|
|
12719
|
+
imageRef,
|
|
12720
|
+
allowedByDefault: false,
|
|
12721
|
+
accepted: false,
|
|
12722
|
+
stderrLine: `Error: image '${imageRef}' is outside allowed registries (ghcr.io/pleri/*).
|
|
12723
|
+
To override: re-run with --allow-custom-registry
|
|
12724
|
+
Verify the source and digest before doing so.`
|
|
12924
12725
|
};
|
|
12925
12726
|
}
|
|
12926
|
-
|
|
12927
|
-
|
|
12928
|
-
|
|
12929
|
-
try {
|
|
12930
|
-
status = await fetchStatus();
|
|
12931
|
-
} catch {
|
|
12932
|
-
printError("Failed to contact auth service. Run `olam auth up` first.");
|
|
12933
|
-
process.exitCode = 1;
|
|
12934
|
-
return;
|
|
12727
|
+
function resolveDevboxImageOverride(flagValue, env = process.env) {
|
|
12728
|
+
if (flagValue && flagValue.trim().length > 0) {
|
|
12729
|
+
return flagValue.trim();
|
|
12935
12730
|
}
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
return;
|
|
12731
|
+
const envValue = env.OLAM_DEVBOX_IMAGE;
|
|
12732
|
+
if (envValue && envValue.trim().length > 0) {
|
|
12733
|
+
return envValue.trim();
|
|
12940
12734
|
}
|
|
12941
|
-
|
|
12942
|
-
|
|
12943
|
-
|
|
12735
|
+
return void 0;
|
|
12736
|
+
}
|
|
12737
|
+
var DEFAULT_ALLOWLIST_PATTERNS;
|
|
12738
|
+
var init_registry_allowlist = __esm({
|
|
12739
|
+
"src/registry-allowlist.ts"() {
|
|
12740
|
+
"use strict";
|
|
12741
|
+
DEFAULT_ALLOWLIST_PATTERNS = [
|
|
12742
|
+
// ghcr.io/pleri/<anything>:<tag> or ghcr.io/pleri/<anything>@sha256:<digest>
|
|
12743
|
+
/^ghcr\.io\/pleri\/[^/\s]+(?::[^\s]+|@sha256:[a-f0-9]+)?$/
|
|
12744
|
+
];
|
|
12944
12745
|
}
|
|
12945
|
-
|
|
12946
|
-
|
|
12947
|
-
|
|
12948
|
-
|
|
12949
|
-
|
|
12746
|
+
});
|
|
12747
|
+
|
|
12748
|
+
// ../core/src/world/world-yaml.ts
|
|
12749
|
+
import * as fs21 from "node:fs";
|
|
12750
|
+
import * as path24 from "node:path";
|
|
12751
|
+
import { parse as parseYaml4, stringify as stringifyYaml4 } from "yaml";
|
|
12752
|
+
function writeWorldYaml(worldPath, data) {
|
|
12753
|
+
const olamDir = path24.join(worldPath, ".olam");
|
|
12754
|
+
fs21.mkdirSync(olamDir, { recursive: true });
|
|
12755
|
+
fs21.writeFileSync(path24.join(olamDir, "world.yaml"), stringifyYaml4(data), "utf-8");
|
|
12756
|
+
}
|
|
12757
|
+
function readWorldYaml(worldPath) {
|
|
12758
|
+
const yamlPath = path24.join(worldPath, ".olam", "world.yaml");
|
|
12759
|
+
if (!fs21.existsSync(yamlPath)) return null;
|
|
12760
|
+
try {
|
|
12761
|
+
const raw = fs21.readFileSync(yamlPath, "utf-8");
|
|
12762
|
+
const parsed = parseYaml4(raw);
|
|
12763
|
+
return WorldYamlSchema.parse(parsed);
|
|
12764
|
+
} catch {
|
|
12765
|
+
return null;
|
|
12950
12766
|
}
|
|
12951
12767
|
}
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12962
|
-
import * as crypto5 from "node:crypto";
|
|
12963
|
-
import * as fs19 from "node:fs";
|
|
12964
|
-
import * as os12 from "node:os";
|
|
12965
|
-
import * as path22 from "node:path";
|
|
12966
|
-
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
12967
|
-
import Dockerode2 from "dockerode";
|
|
12968
|
-
init_docker_host();
|
|
12969
|
-
|
|
12970
|
-
// ../core/src/util/open-url.ts
|
|
12971
|
-
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
12972
|
-
function openUrl(url) {
|
|
12973
|
-
if (process.env.CI === "true") {
|
|
12974
|
-
return { opened: false, reason: "CI environment detected" };
|
|
12768
|
+
var WorldYamlSchema;
|
|
12769
|
+
var init_world_yaml = __esm({
|
|
12770
|
+
"../core/src/world/world-yaml.ts"() {
|
|
12771
|
+
"use strict";
|
|
12772
|
+
init_zod();
|
|
12773
|
+
WorldYamlSchema = external_exports.object({
|
|
12774
|
+
world_type: external_exports.enum(["worktree", "clone"]),
|
|
12775
|
+
origin_url: external_exports.string().min(1),
|
|
12776
|
+
cli_version: external_exports.string().nullable()
|
|
12777
|
+
});
|
|
12975
12778
|
}
|
|
12976
|
-
|
|
12977
|
-
|
|
12779
|
+
});
|
|
12780
|
+
|
|
12781
|
+
// ../core/src/world/version-pin.ts
|
|
12782
|
+
var version_pin_exports = {};
|
|
12783
|
+
__export(version_pin_exports, {
|
|
12784
|
+
CLI_VERSION: () => CLI_VERSION,
|
|
12785
|
+
checkVersionPin: () => checkVersionPin,
|
|
12786
|
+
resetWarnedSet: () => resetWarnedSet,
|
|
12787
|
+
stampVersionPin: () => stampVersionPin
|
|
12788
|
+
});
|
|
12789
|
+
function checkVersionPin(worldId, worldPath, cliVersion = CLI_VERSION, _readYaml = readWorldYaml, _warn = (msg) => console.warn(msg)) {
|
|
12790
|
+
const yaml = _readYaml(worldPath);
|
|
12791
|
+
if (!yaml || yaml.cli_version === null) return true;
|
|
12792
|
+
if (yaml.cli_version === cliVersion) return true;
|
|
12793
|
+
if (!warnedThisSession.has(worldId)) {
|
|
12794
|
+
warnedThisSession.add(worldId);
|
|
12795
|
+
_warn(
|
|
12796
|
+
`[olam] World "${worldId}" was created with CLI v${yaml.cli_version}, current CLI is v${cliVersion}. Run \`olam world upgrade ${worldId}\` to refresh the pin.`
|
|
12797
|
+
);
|
|
12978
12798
|
}
|
|
12979
|
-
|
|
12980
|
-
|
|
12799
|
+
return false;
|
|
12800
|
+
}
|
|
12801
|
+
function stampVersionPin(worldPath, cliVersion = CLI_VERSION, _readYaml = readWorldYaml, _writeYaml = writeWorldYaml) {
|
|
12802
|
+
const yaml = _readYaml(worldPath);
|
|
12803
|
+
if (!yaml) return false;
|
|
12804
|
+
if (yaml.cli_version === cliVersion) return false;
|
|
12805
|
+
_writeYaml(worldPath, { ...yaml, cli_version: cliVersion });
|
|
12806
|
+
return true;
|
|
12807
|
+
}
|
|
12808
|
+
function resetWarnedSet() {
|
|
12809
|
+
warnedThisSession.clear();
|
|
12810
|
+
}
|
|
12811
|
+
var CLI_VERSION, warnedThisSession;
|
|
12812
|
+
var init_version_pin = __esm({
|
|
12813
|
+
"../core/src/world/version-pin.ts"() {
|
|
12814
|
+
"use strict";
|
|
12815
|
+
init_world_yaml();
|
|
12816
|
+
CLI_VERSION = process.env["OLAM_CLI_VERSION"] ?? "0.0.0";
|
|
12817
|
+
warnedThisSession = /* @__PURE__ */ new Set();
|
|
12981
12818
|
}
|
|
12982
|
-
|
|
12983
|
-
|
|
12984
|
-
|
|
12985
|
-
|
|
12986
|
-
|
|
12987
|
-
|
|
12988
|
-
|
|
12989
|
-
|
|
12990
|
-
|
|
12991
|
-
|
|
12992
|
-
|
|
12819
|
+
});
|
|
12820
|
+
|
|
12821
|
+
// ../core/src/orchestrator/enter.ts
|
|
12822
|
+
var enter_exports = {};
|
|
12823
|
+
__export(enter_exports, {
|
|
12824
|
+
getEnterCommand: () => getEnterCommand
|
|
12825
|
+
});
|
|
12826
|
+
function getEnterCommand(worldId, containerId, provider, sshHost) {
|
|
12827
|
+
if (provider === "docker") {
|
|
12828
|
+
const command = `docker exec -it ${containerId} claude`;
|
|
12829
|
+
return {
|
|
12830
|
+
command,
|
|
12831
|
+
instructions: [
|
|
12832
|
+
"Run this command in your terminal to enter the world:",
|
|
12833
|
+
"",
|
|
12834
|
+
` ${command}`,
|
|
12835
|
+
"",
|
|
12836
|
+
"Press Ctrl+C to exit."
|
|
12837
|
+
].join("\n")
|
|
12838
|
+
};
|
|
12993
12839
|
}
|
|
12994
|
-
|
|
12995
|
-
const
|
|
12996
|
-
if (result.error) {
|
|
12997
|
-
return { opened: false, reason: `spawn ${cmd}: ${result.error.message}` };
|
|
12998
|
-
}
|
|
12999
|
-
if (result.status !== 0 && result.status !== null) {
|
|
13000
|
-
return { opened: false, reason: `${cmd} exited ${result.status}` };
|
|
13001
|
-
}
|
|
13002
|
-
return { opened: true };
|
|
13003
|
-
} catch (err) {
|
|
12840
|
+
if (provider === "ssh" && sshHost) {
|
|
12841
|
+
const command = `ssh -t ${sshHost} docker exec -it ${containerId} claude`;
|
|
13004
12842
|
return {
|
|
13005
|
-
|
|
13006
|
-
|
|
12843
|
+
command,
|
|
12844
|
+
instructions: [
|
|
12845
|
+
"Run this command to enter the remote world:",
|
|
12846
|
+
"",
|
|
12847
|
+
` ${command}`,
|
|
12848
|
+
"",
|
|
12849
|
+
"Press Ctrl+C to exit."
|
|
12850
|
+
].join("\n")
|
|
13007
12851
|
};
|
|
13008
12852
|
}
|
|
12853
|
+
return {
|
|
12854
|
+
command: "",
|
|
12855
|
+
instructions: `Cannot determine enter command for provider: ${provider}`
|
|
12856
|
+
};
|
|
13009
12857
|
}
|
|
13010
|
-
|
|
13011
|
-
|
|
13012
|
-
|
|
13013
|
-
function findComposeFile() {
|
|
13014
|
-
const candidates = [
|
|
13015
|
-
// Bundled path: dist/index.js lives at <pkg>/dist/; host-cp/ is a sibling of dist/
|
|
13016
|
-
path22.resolve(path22.dirname(new URL(import.meta.url).pathname), "../host-cp/compose.yaml"),
|
|
13017
|
-
// Source-mode: cwd is monorepo root
|
|
13018
|
-
path22.resolve(process.cwd(), "packages/host-cp/compose.yaml"),
|
|
13019
|
-
// Source-mode: cwd is one level inside the monorepo
|
|
13020
|
-
path22.resolve(process.cwd(), "../packages/host-cp/compose.yaml")
|
|
13021
|
-
];
|
|
13022
|
-
for (const c of candidates) {
|
|
13023
|
-
if (fs19.existsSync(c)) return c;
|
|
12858
|
+
var init_enter = __esm({
|
|
12859
|
+
"../core/src/orchestrator/enter.ts"() {
|
|
12860
|
+
"use strict";
|
|
13024
12861
|
}
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13032
|
-
}
|
|
13033
|
-
function
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
return
|
|
13038
|
-
}
|
|
13039
|
-
function readAuthSecret2() {
|
|
13040
|
-
const filePath = authSecretPath();
|
|
13041
|
-
if (!fs19.existsSync(filePath)) return null;
|
|
13042
|
-
const raw = fs19.readFileSync(filePath, "utf-8").trim();
|
|
13043
|
-
return raw.length > 0 ? raw : null;
|
|
13044
|
-
}
|
|
13045
|
-
function writeToken() {
|
|
13046
|
-
const token = crypto5.randomBytes(32).toString("hex");
|
|
13047
|
-
const filePath = tokenPath();
|
|
13048
|
-
fs19.mkdirSync(path22.dirname(filePath), { recursive: true });
|
|
13049
|
-
fs19.writeFileSync(filePath, token, { mode: 384 });
|
|
13050
|
-
return token;
|
|
13051
|
-
}
|
|
13052
|
-
function readToken() {
|
|
13053
|
-
const filePath = tokenPath();
|
|
13054
|
-
if (!fs19.existsSync(filePath)) return null;
|
|
13055
|
-
return fs19.readFileSync(filePath, "utf-8").trim();
|
|
12862
|
+
});
|
|
12863
|
+
|
|
12864
|
+
// ../core/src/crystallize/checksum.ts
|
|
12865
|
+
var checksum_exports = {};
|
|
12866
|
+
__export(checksum_exports, {
|
|
12867
|
+
computeGraphChecksum: () => computeGraphChecksum
|
|
12868
|
+
});
|
|
12869
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
12870
|
+
function computeGraphChecksum(nodes, edges) {
|
|
12871
|
+
const sortedNodes = [...nodes].sort((a, b) => a.id.localeCompare(b.id));
|
|
12872
|
+
const sortedEdges = [...edges].sort((a, b) => a.id.localeCompare(b.id));
|
|
12873
|
+
const payload = JSON.stringify({ nodes: sortedNodes, edges: sortedEdges });
|
|
12874
|
+
return createHash2("sha256").update(payload).digest("hex");
|
|
13056
12875
|
}
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
12876
|
+
var init_checksum = __esm({
|
|
12877
|
+
"../core/src/crystallize/checksum.ts"() {
|
|
12878
|
+
"use strict";
|
|
12879
|
+
}
|
|
12880
|
+
});
|
|
12881
|
+
|
|
12882
|
+
// ../core/src/config/machine-schema.ts
|
|
12883
|
+
var machine_schema_exports = {};
|
|
12884
|
+
__export(machine_schema_exports, {
|
|
12885
|
+
MachineConfigSchema: () => MachineConfigSchema,
|
|
12886
|
+
initMachineConfig: () => initMachineConfig,
|
|
12887
|
+
readMachineConfig: () => readMachineConfig,
|
|
12888
|
+
writeMachineConfig: () => writeMachineConfig
|
|
12889
|
+
});
|
|
12890
|
+
import * as fs34 from "node:fs";
|
|
12891
|
+
import * as path38 from "node:path";
|
|
12892
|
+
import * as os20 from "node:os";
|
|
12893
|
+
import { parse as parseYaml5, stringify as stringifyYaml5 } from "yaml";
|
|
12894
|
+
function readMachineConfig(configPath) {
|
|
12895
|
+
const p = configPath ?? DEFAULT_CONFIG_PATH;
|
|
12896
|
+
if (!fs34.existsSync(p)) return null;
|
|
12897
|
+
try {
|
|
12898
|
+
const raw = fs34.readFileSync(p, "utf-8");
|
|
12899
|
+
const parsed = parseYaml5(raw);
|
|
12900
|
+
return MachineConfigSchema.parse(parsed);
|
|
12901
|
+
} catch {
|
|
12902
|
+
return null;
|
|
12903
|
+
}
|
|
13062
12904
|
}
|
|
13063
|
-
function
|
|
13064
|
-
const
|
|
13065
|
-
|
|
13066
|
-
|
|
12905
|
+
function writeMachineConfig(config, configPath) {
|
|
12906
|
+
const p = configPath ?? DEFAULT_CONFIG_PATH;
|
|
12907
|
+
fs34.mkdirSync(path38.dirname(p), { recursive: true });
|
|
12908
|
+
fs34.writeFileSync(p, stringifyYaml5({ ...config }), { mode: 420 });
|
|
13067
12909
|
}
|
|
13068
|
-
function
|
|
13069
|
-
const
|
|
13070
|
-
|
|
13071
|
-
|
|
13072
|
-
|
|
12910
|
+
function initMachineConfig(opts = {}) {
|
|
12911
|
+
const configPath = opts.configPath ?? DEFAULT_CONFIG_PATH;
|
|
12912
|
+
const existing = readMachineConfig(configPath);
|
|
12913
|
+
if (existing) return existing;
|
|
12914
|
+
const config = MachineConfigSchema.parse({
|
|
12915
|
+
telemetry: opts.telemetry ?? true
|
|
12916
|
+
});
|
|
12917
|
+
writeMachineConfig(config, configPath);
|
|
12918
|
+
return config;
|
|
13073
12919
|
}
|
|
13074
|
-
|
|
13075
|
-
|
|
13076
|
-
|
|
13077
|
-
|
|
13078
|
-
|
|
13079
|
-
|
|
13080
|
-
|
|
13081
|
-
|
|
13082
|
-
|
|
13083
|
-
|
|
13084
|
-
|
|
13085
|
-
|
|
13086
|
-
|
|
12920
|
+
var MachineConfigSchema, DEFAULT_CONFIG_PATH;
|
|
12921
|
+
var init_machine_schema = __esm({
|
|
12922
|
+
"../core/src/config/machine-schema.ts"() {
|
|
12923
|
+
"use strict";
|
|
12924
|
+
init_zod();
|
|
12925
|
+
MachineConfigSchema = external_exports.object({
|
|
12926
|
+
schema_version: external_exports.literal(1).default(1),
|
|
12927
|
+
channel: external_exports.enum(["stable", "beta", "edge"]).default("stable"),
|
|
12928
|
+
auto_update: external_exports.boolean().default(true),
|
|
12929
|
+
telemetry: external_exports.boolean().default(true),
|
|
12930
|
+
worlds_dir: external_exports.string().default(() => path38.join(os20.homedir(), ".olam", "worlds"))
|
|
12931
|
+
});
|
|
12932
|
+
DEFAULT_CONFIG_PATH = path38.join(os20.homedir(), ".olam", "config.yaml");
|
|
13087
12933
|
}
|
|
13088
|
-
|
|
12934
|
+
});
|
|
12935
|
+
|
|
12936
|
+
// src/index.ts
|
|
12937
|
+
import { Command } from "commander";
|
|
12938
|
+
import * as fs37 from "node:fs";
|
|
12939
|
+
import * as path41 from "node:path";
|
|
12940
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
12941
|
+
|
|
12942
|
+
// src/commands/init.ts
|
|
12943
|
+
init_output();
|
|
12944
|
+
import * as fs5 from "node:fs";
|
|
12945
|
+
import * as path5 from "node:path";
|
|
12946
|
+
import { execSync } from "node:child_process";
|
|
12947
|
+
import pc3 from "picocolors";
|
|
12948
|
+
|
|
12949
|
+
// src/commands/workspace.ts
|
|
12950
|
+
init_workspace();
|
|
12951
|
+
init_loader();
|
|
12952
|
+
init_output();
|
|
12953
|
+
import * as fs4 from "node:fs";
|
|
12954
|
+
import * as path4 from "node:path";
|
|
12955
|
+
import pc2 from "picocolors";
|
|
12956
|
+
import { stringify as stringifyYaml2 } from "yaml";
|
|
12957
|
+
function printWorkspaceNotFound(name) {
|
|
12958
|
+
printError(`No workspace named "${name}" under ${workspacesDir()}`);
|
|
13089
12959
|
}
|
|
13090
|
-
|
|
13091
|
-
const
|
|
13092
|
-
|
|
13093
|
-
|
|
13094
|
-
|
|
13095
|
-
|
|
13096
|
-
});
|
|
13097
|
-
httpOk = res.ok;
|
|
13098
|
-
} catch {
|
|
13099
|
-
httpOk = false;
|
|
12960
|
+
function parseRepoFlag(raw) {
|
|
12961
|
+
const hashIdx = raw.indexOf("#");
|
|
12962
|
+
const url = hashIdx === -1 ? raw : raw.slice(0, hashIdx);
|
|
12963
|
+
const branch = hashIdx === -1 ? void 0 : raw.slice(hashIdx + 1);
|
|
12964
|
+
if (url.length === 0) throw new Error(`invalid --repo value "${raw}" (empty url)`);
|
|
12965
|
+
if (branch !== void 0 && branch.length === 0) {
|
|
12966
|
+
throw new Error(`invalid --repo value "${raw}" (empty branch after #)`);
|
|
13100
12967
|
}
|
|
13101
|
-
|
|
13102
|
-
|
|
12968
|
+
const nameFromUrl = url.replace(/\.git$/, "").split(/[\/:]/).filter(Boolean).at(-1) ?? "repo";
|
|
12969
|
+
return branch ? { name: nameFromUrl, url, branch } : { name: nameFromUrl, url };
|
|
12970
|
+
}
|
|
12971
|
+
function registerWorkspace(program2) {
|
|
12972
|
+
const workspace = program2.command("workspace").description("Manage the named catalog of repo bundles that worlds instantiate from");
|
|
12973
|
+
workspace.command("list").description("List all workspaces (name, repoCount, updatedAt)").action(() => {
|
|
12974
|
+
const all = listWorkspaces();
|
|
12975
|
+
if (all.length === 0) {
|
|
12976
|
+
console.log(pc2.dim(`No workspaces under ${workspacesDir()}`));
|
|
12977
|
+
return;
|
|
12978
|
+
}
|
|
12979
|
+
printHeader(`${all.length} workspace(s)`);
|
|
12980
|
+
for (const ws of all) {
|
|
12981
|
+
const when = new Date(ws.updatedAt).toISOString().slice(0, 10);
|
|
12982
|
+
console.log(
|
|
12983
|
+
` ${pc2.bold(ws.name.padEnd(24))} ${String(ws.repos.length).padEnd(3)} repos ${pc2.dim(when)}`
|
|
12984
|
+
);
|
|
12985
|
+
}
|
|
12986
|
+
});
|
|
12987
|
+
workspace.command("show").description("Show a workspace as YAML").argument("<name>", "Workspace name").action((name) => {
|
|
13103
12988
|
try {
|
|
13104
|
-
const
|
|
13105
|
-
if (
|
|
13106
|
-
|
|
12989
|
+
const ws = readWorkspace(name);
|
|
12990
|
+
if (!ws) {
|
|
12991
|
+
printWorkspaceNotFound(name);
|
|
12992
|
+
process.exitCode = 1;
|
|
12993
|
+
return;
|
|
13107
12994
|
}
|
|
13108
|
-
|
|
12995
|
+
process.stdout.write(stringifyYaml2(ws));
|
|
12996
|
+
} catch (err) {
|
|
12997
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
12998
|
+
process.exitCode = 1;
|
|
13109
12999
|
}
|
|
13110
|
-
|
|
13111
|
-
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13000
|
+
});
|
|
13001
|
+
workspace.command("remove").description("Delete a workspace (does NOT touch worlds that already referenced it)").argument("<name>", "Workspace name").option("--force", "Skip confirmation", false).action((name, _opts) => {
|
|
13002
|
+
try {
|
|
13003
|
+
if (removeWorkspace(name)) {
|
|
13004
|
+
printSuccess(`Removed workspace "${name}"`);
|
|
13005
|
+
} else {
|
|
13006
|
+
printWorkspaceNotFound(name);
|
|
13007
|
+
process.exitCode = 1;
|
|
13008
|
+
}
|
|
13009
|
+
} catch (err) {
|
|
13010
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
13011
|
+
process.exitCode = 1;
|
|
13012
|
+
}
|
|
13013
|
+
});
|
|
13014
|
+
workspace.command("add").description("Create a workspace from --from-config (reads current .olam/config.yaml) or repeated --repo flags").argument("<name>", "Workspace name").option("--from-config", "Seed from the repos in the current project's .olam/config.yaml", false).option("--repo <url>", "Repeatable. Format: <url> or <url>#<branch>", (val, acc) => {
|
|
13015
|
+
acc.push(parseRepoFlag(val));
|
|
13016
|
+
return acc;
|
|
13017
|
+
}, []).option("--default-branch <branch>", "Fallback branch for repos that don't specify one").option("--force", "Overwrite an existing workspace with the same name", false).action((name, opts) => {
|
|
13018
|
+
try {
|
|
13019
|
+
const repos = [...opts.repo];
|
|
13020
|
+
if (opts.fromConfig) {
|
|
13021
|
+
try {
|
|
13022
|
+
const cfg = loadConfig(process.cwd());
|
|
13023
|
+
for (const r of cfg.repos) {
|
|
13024
|
+
repos.push({
|
|
13025
|
+
name: r.name,
|
|
13026
|
+
url: r.url,
|
|
13027
|
+
...r.submodules ? { submodules: true } : {}
|
|
13028
|
+
});
|
|
13029
|
+
}
|
|
13030
|
+
} catch (err) {
|
|
13031
|
+
printError(`--from-config: ${err instanceof Error ? err.message : String(err)}`);
|
|
13032
|
+
process.exitCode = 1;
|
|
13033
|
+
return;
|
|
13121
13034
|
}
|
|
13122
|
-
} catch {
|
|
13123
13035
|
}
|
|
13036
|
+
if (repos.length === 0) {
|
|
13037
|
+
printError("No repos provided \u2014 pass --from-config or at least one --repo");
|
|
13038
|
+
process.exitCode = 1;
|
|
13039
|
+
return;
|
|
13040
|
+
}
|
|
13041
|
+
const ws = {
|
|
13042
|
+
name,
|
|
13043
|
+
repos,
|
|
13044
|
+
...opts.defaultBranch ? { defaults: { branch: opts.defaultBranch } } : {},
|
|
13045
|
+
updatedAt: Date.now()
|
|
13046
|
+
};
|
|
13047
|
+
writeWorkspace(ws, { force: opts.force });
|
|
13048
|
+
const file = path4.join(workspacesDir(), `${name}.yaml`);
|
|
13049
|
+
printSuccess(`Created workspace "${name}" (${repos.length} repo${repos.length === 1 ? "" : "s"})`);
|
|
13050
|
+
printInfo("File", file);
|
|
13051
|
+
printInfo("Next", `olam create --name <world> --workspace ${name} --task "..."`);
|
|
13052
|
+
} catch (err) {
|
|
13053
|
+
if (err instanceof WorkspaceExistsError || err instanceof WorkspaceNameError) {
|
|
13054
|
+
printError(err.message);
|
|
13055
|
+
process.exitCode = 1;
|
|
13056
|
+
return;
|
|
13057
|
+
}
|
|
13058
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
13059
|
+
process.exitCode = 1;
|
|
13124
13060
|
}
|
|
13125
|
-
}
|
|
13126
|
-
}
|
|
13127
|
-
return null;
|
|
13061
|
+
});
|
|
13128
13062
|
}
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
|
|
13134
|
-
|
|
13135
|
-
bootstrapStatus = `HTTP ${res.status}`;
|
|
13136
|
-
} catch (err) {
|
|
13137
|
-
bootstrapStatus = err instanceof Error ? err.message : "connection refused";
|
|
13138
|
-
}
|
|
13139
|
-
let containerStatus = "not found";
|
|
13140
|
-
try {
|
|
13141
|
-
const container = await findHostCpContainer();
|
|
13142
|
-
if (!container) {
|
|
13143
|
-
containerStatus = "not found";
|
|
13144
|
-
} else {
|
|
13145
|
-
containerStatus = `found (state: ${container.state})`;
|
|
13063
|
+
function ensureProjectWorkspaceFromConfig(projectRoot, workspaceName) {
|
|
13064
|
+
const existing = (() => {
|
|
13065
|
+
try {
|
|
13066
|
+
return readWorkspace(workspaceName);
|
|
13067
|
+
} catch {
|
|
13068
|
+
return null;
|
|
13146
13069
|
}
|
|
13147
|
-
}
|
|
13148
|
-
|
|
13149
|
-
|
|
13150
|
-
return { bootstrapStatus, containerStatus };
|
|
13151
|
-
}
|
|
13152
|
-
async function probeHealth() {
|
|
13153
|
-
try {
|
|
13154
|
-
const res = await fetch(`http://127.0.0.1:${HOST_CP_PORT}/health`, {
|
|
13155
|
-
signal: AbortSignal.timeout(2e3)
|
|
13156
|
-
});
|
|
13157
|
-
if (!res.ok) return null;
|
|
13158
|
-
return await res.json();
|
|
13159
|
-
} catch {
|
|
13160
|
-
return null;
|
|
13070
|
+
})();
|
|
13071
|
+
if (existing) {
|
|
13072
|
+
return { created: false, file: path4.join(workspacesDir(), `${workspaceName}.yaml`) };
|
|
13161
13073
|
}
|
|
13162
|
-
|
|
13163
|
-
|
|
13164
|
-
|
|
13165
|
-
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
|
|
13169
|
-
|
|
13170
|
-
|
|
13171
|
-
|
|
13172
|
-
stderr: result.stderr ?? ""
|
|
13074
|
+
const cfg = loadConfig(projectRoot);
|
|
13075
|
+
const repos = cfg.repos.map((r) => ({
|
|
13076
|
+
name: r.name,
|
|
13077
|
+
url: r.url,
|
|
13078
|
+
...r.submodules ? { submodules: true } : {}
|
|
13079
|
+
}));
|
|
13080
|
+
const ws = {
|
|
13081
|
+
name: workspaceName,
|
|
13082
|
+
repos,
|
|
13083
|
+
updatedAt: Date.now()
|
|
13173
13084
|
};
|
|
13085
|
+
writeWorkspace(ws);
|
|
13086
|
+
const file = path4.join(workspacesDir(), `${workspaceName}.yaml`);
|
|
13087
|
+
return { created: true, file };
|
|
13174
13088
|
}
|
|
13175
|
-
|
|
13176
|
-
|
|
13177
|
-
|
|
13178
|
-
|
|
13179
|
-
|
|
13180
|
-
if (
|
|
13181
|
-
|
|
13182
|
-
}
|
|
13183
|
-
return env;
|
|
13089
|
+
|
|
13090
|
+
// src/commands/init.ts
|
|
13091
|
+
function detectProjectType(root) {
|
|
13092
|
+
if (fs5.existsSync(path5.join(root, "Gemfile")) || fs5.existsSync(path5.join(root, "config", "routes.rb"))) return "rails";
|
|
13093
|
+
if (fs5.existsSync(path5.join(root, "package.json"))) return "node";
|
|
13094
|
+
if (fs5.existsSync(path5.join(root, "pyproject.toml")) || fs5.existsSync(path5.join(root, "requirements.txt"))) return "python";
|
|
13095
|
+
return "generic";
|
|
13184
13096
|
}
|
|
13185
|
-
function
|
|
13097
|
+
function getRepoName(root) {
|
|
13186
13098
|
try {
|
|
13187
|
-
const
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
});
|
|
13191
|
-
|
|
13192
|
-
|
|
13193
|
-
return token.length > 0 ? token : null;
|
|
13194
|
-
}
|
|
13195
|
-
return null;
|
|
13099
|
+
const url = execSync("git remote get-url origin", {
|
|
13100
|
+
cwd: root,
|
|
13101
|
+
encoding: "utf-8"
|
|
13102
|
+
}).trim();
|
|
13103
|
+
const match2 = url.match(/\/([^/]+?)(?:\.git)?$/);
|
|
13104
|
+
if (match2) return match2[1];
|
|
13196
13105
|
} catch {
|
|
13197
|
-
return null;
|
|
13198
13106
|
}
|
|
13107
|
+
return path5.basename(root);
|
|
13199
13108
|
}
|
|
13200
|
-
|
|
13201
|
-
|
|
13202
|
-
|
|
13203
|
-
|
|
13204
|
-
|
|
13205
|
-
|
|
13206
|
-
|
|
13207
|
-
printInfo("Uptime", String(health["uptime_seconds"] ?? "unknown") + "s");
|
|
13208
|
-
return;
|
|
13209
|
-
}
|
|
13210
|
-
printWarning("Host CP container running but /health not responding. Wait a few seconds and retry, or stop+start.");
|
|
13211
|
-
return;
|
|
13109
|
+
function findProjectRoot(startDir) {
|
|
13110
|
+
let current = path5.resolve(startDir);
|
|
13111
|
+
while (true) {
|
|
13112
|
+
if (fs5.existsSync(path5.join(current, ".git"))) return current;
|
|
13113
|
+
const parent = path5.dirname(current);
|
|
13114
|
+
if (parent === current) return startDir;
|
|
13115
|
+
current = parent;
|
|
13212
13116
|
}
|
|
13213
|
-
|
|
13214
|
-
|
|
13215
|
-
|
|
13216
|
-
|
|
13217
|
-
|
|
13218
|
-
|
|
13219
|
-
|
|
13117
|
+
}
|
|
13118
|
+
function registerInit(program2) {
|
|
13119
|
+
program2.command("init").description("Initialize olam in the current project").option("--path <path>", "Project root path", process.cwd()).option("--skip-pleri", "Skip Pleri setup").action(async (opts) => {
|
|
13120
|
+
try {
|
|
13121
|
+
const projectRoot = findProjectRoot(opts.path);
|
|
13122
|
+
const olamDir = path5.join(projectRoot, ".olam");
|
|
13123
|
+
if (fs5.existsSync(path5.join(olamDir, "config.yaml"))) {
|
|
13124
|
+
printError(`Already initialized at ${olamDir}/config.yaml`);
|
|
13125
|
+
process.exitCode = 1;
|
|
13126
|
+
return;
|
|
13127
|
+
}
|
|
13128
|
+
const projectType = detectProjectType(projectRoot);
|
|
13129
|
+
const repoName = getRepoName(projectRoot);
|
|
13130
|
+
let remoteUrl;
|
|
13131
|
+
try {
|
|
13132
|
+
remoteUrl = execSync("git remote get-url origin", {
|
|
13133
|
+
cwd: projectRoot,
|
|
13134
|
+
encoding: "utf-8"
|
|
13135
|
+
}).trim();
|
|
13136
|
+
} catch {
|
|
13137
|
+
remoteUrl = `git@github.com:your-org/${repoName}.git`;
|
|
13138
|
+
}
|
|
13139
|
+
fs5.mkdirSync(path5.join(olamDir, "state"), { recursive: true });
|
|
13140
|
+
fs5.mkdirSync(path5.join(olamDir, "thoughts"), { recursive: true });
|
|
13141
|
+
const pleriSection = opts.skipPleri ? "# pleri:\n# base_url: ${PLERI_BASE_URL}\n# plane_id: ${PLERI_PLANE_ID}\n# api_key: ${PLERI_API_KEY}\n" : "pleri:\n base_url: ${PLERI_BASE_URL}\n plane_id: ${PLERI_PLANE_ID}\n api_key: ${PLERI_API_KEY}\n";
|
|
13142
|
+
const config = [
|
|
13143
|
+
"version: 2",
|
|
13144
|
+
"",
|
|
13145
|
+
pleriSection,
|
|
13146
|
+
"repos:",
|
|
13147
|
+
` - name: ${repoName}`,
|
|
13148
|
+
` url: ${remoteUrl}`,
|
|
13149
|
+
` path: ${projectRoot}`,
|
|
13150
|
+
` type: ${projectType}`,
|
|
13151
|
+
" services: []",
|
|
13152
|
+
"",
|
|
13153
|
+
"compute:",
|
|
13154
|
+
" default: docker",
|
|
13155
|
+
"",
|
|
13156
|
+
"cost:",
|
|
13157
|
+
" # All values in USD (Anthropic's billing currency).",
|
|
13158
|
+
" # Convert from your local currency: USD 25 \u2248 SGD 33 / EUR 23 / GBP 20",
|
|
13159
|
+
" # at typical 2026 rates. Dashboard display localization is a future feature.",
|
|
13160
|
+
" max_per_world_usd: 25",
|
|
13161
|
+
" max_daily_usd: 100",
|
|
13162
|
+
" warning_threshold: 0.8",
|
|
13163
|
+
"",
|
|
13164
|
+
"auth:",
|
|
13165
|
+
" mode: oauth",
|
|
13166
|
+
""
|
|
13167
|
+
].join("\n");
|
|
13168
|
+
fs5.writeFileSync(path5.join(olamDir, "config.yaml"), config);
|
|
13169
|
+
const envExample = [
|
|
13170
|
+
"# Pleri credentials",
|
|
13171
|
+
"PLERI_BASE_URL=https://pleri.dev/api",
|
|
13172
|
+
"PLERI_PLANE_ID=",
|
|
13173
|
+
"PLERI_API_KEY=",
|
|
13174
|
+
""
|
|
13175
|
+
].join("\n");
|
|
13176
|
+
fs5.writeFileSync(path5.join(olamDir, ".env.example"), envExample);
|
|
13177
|
+
printHeader("Olam initialized");
|
|
13178
|
+
printInfo("Config", `${olamDir}/config.yaml`);
|
|
13179
|
+
printInfo("Project", `${projectType} (detected)`);
|
|
13180
|
+
printInfo("Repo", repoName);
|
|
13181
|
+
try {
|
|
13182
|
+
const result = ensureProjectWorkspaceFromConfig(projectRoot, repoName);
|
|
13183
|
+
if (result.created) {
|
|
13184
|
+
printInfo("Workspace", `${repoName} \u2192 ${result.file}`);
|
|
13185
|
+
} else {
|
|
13186
|
+
printInfo("Workspace", `${repoName} (already registered)`);
|
|
13187
|
+
}
|
|
13188
|
+
} catch (err) {
|
|
13189
|
+
printError(`Workspace auto-register failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
13190
|
+
}
|
|
13191
|
+
console.log(`
|
|
13192
|
+
${pc3.dim(`Next: olam create --name my-world --workspace ${repoName} --task "..."`)}`);
|
|
13193
|
+
} catch (err) {
|
|
13194
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
13220
13195
|
process.exitCode = 1;
|
|
13221
|
-
return;
|
|
13222
13196
|
}
|
|
13223
|
-
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13197
|
+
});
|
|
13198
|
+
}
|
|
13199
|
+
|
|
13200
|
+
// src/commands/install.ts
|
|
13201
|
+
import * as fs6 from "node:fs";
|
|
13202
|
+
import * as os3 from "node:os";
|
|
13203
|
+
import * as path6 from "node:path";
|
|
13204
|
+
import pc4 from "picocolors";
|
|
13205
|
+
import { stringify as stringifyYaml3 } from "yaml";
|
|
13206
|
+
|
|
13207
|
+
// ../core/src/archetypes/capabilities.ts
|
|
13208
|
+
var CAPABILITY_NAMES = [
|
|
13209
|
+
// World operations
|
|
13210
|
+
"world.create",
|
|
13211
|
+
"world.operate",
|
|
13212
|
+
"world.destroy",
|
|
13213
|
+
// Workspace catalog
|
|
13214
|
+
"workspace.read",
|
|
13215
|
+
"workspace.write",
|
|
13216
|
+
// Auth container + credentials. Phase 6b.2/A5: `auth.manage`
|
|
13217
|
+
// removed — its only consumer (/auth/revoke) was reclassified to
|
|
13218
|
+
// `auth.login` (self-managed auth flow) in A2, and the only
|
|
13219
|
+
// archetype that held it (`bootstrapper`) is also removed below.
|
|
13220
|
+
"auth.login",
|
|
13221
|
+
"auth.read",
|
|
13222
|
+
// PR gate
|
|
13223
|
+
"pr-gate.read",
|
|
13224
|
+
"pr-gate.decide",
|
|
13225
|
+
// Policy
|
|
13226
|
+
"policy.set",
|
|
13227
|
+
// Phase 6b.2/A5: `role.manage` removed — Pylon owns role grants
|
|
13228
|
+
// (`pylon role grant ...` from a user terminal). The `/api/roles*`
|
|
13229
|
+
// route handlers were deleted in A2.
|
|
13230
|
+
// Phase 6b.2/A5: `bootstrap.install` and `bootstrap.cf-deploy`
|
|
13231
|
+
// removed — `/bootstrap` route handler deleted in A2; the
|
|
13232
|
+
// `bootstrapper` archetype is also removed in this commit.
|
|
13233
|
+
// Dev-only (repo clone privileges)
|
|
13234
|
+
"dev.build",
|
|
13235
|
+
"dev.test",
|
|
13236
|
+
"dev.release"
|
|
13237
|
+
];
|
|
13238
|
+
var CAPABILITY_SET = new Set(CAPABILITY_NAMES);
|
|
13239
|
+
function isCapability(value) {
|
|
13240
|
+
return CAPABILITY_SET.has(value);
|
|
13241
|
+
}
|
|
13242
|
+
function assertCapability(value) {
|
|
13243
|
+
if (!isCapability(value)) {
|
|
13244
|
+
throw new Error(
|
|
13245
|
+
`Unknown capability "${value}". Valid capabilities: ${CAPABILITY_NAMES.join(", ")}`
|
|
13237
13246
|
);
|
|
13238
13247
|
}
|
|
13239
|
-
|
|
13240
|
-
|
|
13241
|
-
|
|
13242
|
-
|
|
13248
|
+
return value;
|
|
13249
|
+
}
|
|
13250
|
+
|
|
13251
|
+
// ../core/src/archetypes/registry.ts
|
|
13252
|
+
var ARCHETYPES = [
|
|
13253
|
+
{
|
|
13254
|
+
name: "user",
|
|
13255
|
+
description: "Day-to-day world operator. Creates, runs, and tears down worlds; decides PR gates; reads the workspace catalog.",
|
|
13256
|
+
capabilities: [
|
|
13257
|
+
"world.create",
|
|
13258
|
+
"world.operate",
|
|
13259
|
+
"world.destroy",
|
|
13260
|
+
"workspace.read",
|
|
13261
|
+
"auth.login",
|
|
13262
|
+
"auth.read",
|
|
13263
|
+
"pr-gate.read",
|
|
13264
|
+
"pr-gate.decide"
|
|
13265
|
+
]
|
|
13266
|
+
},
|
|
13267
|
+
{
|
|
13268
|
+
name: "workspace-curator",
|
|
13269
|
+
description: "User who also curates the workspace catalog. Typical: team lead scoping their squad's repo bundles.",
|
|
13270
|
+
inherits: ["user"],
|
|
13271
|
+
capabilities: ["workspace.write"]
|
|
13272
|
+
},
|
|
13273
|
+
{
|
|
13274
|
+
name: "policy-admin",
|
|
13275
|
+
description: "User who also sets deployment policy (permission mode, PR-gate defaults). No catalog or role authority.",
|
|
13276
|
+
inherits: ["user"],
|
|
13277
|
+
capabilities: ["policy.set"]
|
|
13278
|
+
},
|
|
13279
|
+
// Phase 6b.2/A5: `bootstrapper` archetype removed. Its capabilities
|
|
13280
|
+
// (bootstrap.install, bootstrap.cf-deploy, auth.manage) are no
|
|
13281
|
+
// longer enforced — `/bootstrap` route + `auth.manage` route
|
|
13282
|
+
// gating are gone (deleted in A2). The trim keeps `admin` valid
|
|
13283
|
+
// (no dangling inherits ref) and lets schema push succeed.
|
|
13284
|
+
{
|
|
13285
|
+
name: "admin",
|
|
13286
|
+
description: "Full operational admin. Unions user + workspace-curator + policy-admin. Pylon owns role grants now (no role.manage).",
|
|
13287
|
+
inherits: ["user", "workspace-curator", "policy-admin"],
|
|
13288
|
+
capabilities: []
|
|
13289
|
+
},
|
|
13290
|
+
{
|
|
13291
|
+
name: "dev",
|
|
13292
|
+
description: "Contributor to Olam itself. Everything an admin has, plus repo-local dev operations (build, test, release).",
|
|
13293
|
+
inherits: ["admin"],
|
|
13294
|
+
capabilities: ["dev.build", "dev.test", "dev.release"]
|
|
13295
|
+
}
|
|
13296
|
+
];
|
|
13297
|
+
var ARCHETYPE_BY_NAME = new Map(
|
|
13298
|
+
ARCHETYPES.map((a) => [a.name, a])
|
|
13299
|
+
);
|
|
13300
|
+
function getArchetype(name) {
|
|
13301
|
+
return ARCHETYPE_BY_NAME.get(name);
|
|
13302
|
+
}
|
|
13303
|
+
function listArchetypeNames() {
|
|
13304
|
+
return ARCHETYPES.map((a) => a.name);
|
|
13305
|
+
}
|
|
13306
|
+
|
|
13307
|
+
// ../core/src/archetypes/expand.ts
|
|
13308
|
+
var UnknownArchetypeError = class extends Error {
|
|
13309
|
+
constructor(name, known) {
|
|
13310
|
+
super(
|
|
13311
|
+
`Unknown archetype "${name}". Known archetypes: ${known.join(", ")}`
|
|
13243
13312
|
);
|
|
13313
|
+
this.name = name;
|
|
13314
|
+
this.known = known;
|
|
13315
|
+
this.name = "UnknownArchetypeError";
|
|
13244
13316
|
}
|
|
13245
|
-
|
|
13246
|
-
|
|
13247
|
-
|
|
13248
|
-
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
}
|
|
13253
|
-
const pullResult = runCompose(
|
|
13254
|
-
["pull", "--quiet", "docker-socket-proxy"],
|
|
13255
|
-
composeFile,
|
|
13256
|
-
composeEnv
|
|
13317
|
+
name;
|
|
13318
|
+
known;
|
|
13319
|
+
};
|
|
13320
|
+
var ArchetypeCycleError = class extends Error {
|
|
13321
|
+
constructor(path42) {
|
|
13322
|
+
super(
|
|
13323
|
+
`Archetype inheritance cycle detected: ${path42.join(" \u2192 ")} \u2192 ${path42[0] ?? "?"}`
|
|
13257
13324
|
);
|
|
13258
|
-
|
|
13259
|
-
|
|
13260
|
-
break;
|
|
13261
|
-
}
|
|
13262
|
-
lastPullStderr = pullResult.stderr;
|
|
13263
|
-
}
|
|
13264
|
-
if (!pullOk) {
|
|
13265
|
-
printError("docker compose pull docker-socket-proxy failed after 3 attempts");
|
|
13266
|
-
process.stderr.write(lastPullStderr);
|
|
13267
|
-
removeToken();
|
|
13268
|
-
process.exitCode = 1;
|
|
13269
|
-
return;
|
|
13270
|
-
}
|
|
13271
|
-
const result = runCompose(["up", "-d"], composeFile, composeEnv);
|
|
13272
|
-
if (!result.ok) {
|
|
13273
|
-
printError("docker compose up failed");
|
|
13274
|
-
process.stderr.write(result.stderr);
|
|
13275
|
-
removeToken();
|
|
13276
|
-
process.exitCode = 1;
|
|
13277
|
-
return;
|
|
13278
|
-
}
|
|
13279
|
-
const deadline = Date.now() + 1e4;
|
|
13280
|
-
let healthy = false;
|
|
13281
|
-
while (Date.now() < deadline) {
|
|
13282
|
-
const h = await probeHealth();
|
|
13283
|
-
if (h) {
|
|
13284
|
-
healthy = true;
|
|
13285
|
-
break;
|
|
13286
|
-
}
|
|
13287
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
13288
|
-
}
|
|
13289
|
-
if (!healthy) {
|
|
13290
|
-
printWarning("Host CP started but /health did not respond within 10s. Check `docker compose logs host-cp`.");
|
|
13291
|
-
}
|
|
13292
|
-
const container = await findHostCpContainer();
|
|
13293
|
-
if (container) {
|
|
13294
|
-
writePid(1);
|
|
13295
|
-
}
|
|
13296
|
-
printSuccess(`Host CP running at http://127.0.0.1:${HOST_CP_PORT}`);
|
|
13297
|
-
if (opts.showToken) {
|
|
13298
|
-
printInfo("Token", token);
|
|
13299
|
-
} else {
|
|
13300
|
-
printInfo("Token", `(written to ${tokenPath()}; pass --show-token to print)`);
|
|
13325
|
+
this.path = path42;
|
|
13326
|
+
this.name = "ArchetypeCycleError";
|
|
13301
13327
|
}
|
|
13302
|
-
|
|
13328
|
+
path;
|
|
13329
|
+
};
|
|
13330
|
+
function expandArchetype(name) {
|
|
13331
|
+
const out = /* @__PURE__ */ new Set();
|
|
13332
|
+
walk(name, out, []);
|
|
13333
|
+
return out;
|
|
13303
13334
|
}
|
|
13304
|
-
|
|
13305
|
-
|
|
13306
|
-
|
|
13307
|
-
printWarning(`compose.yaml not found at ${composeFile}. Cleaning up token + PID anyway.`);
|
|
13308
|
-
removeToken();
|
|
13309
|
-
removePid();
|
|
13310
|
-
return;
|
|
13311
|
-
}
|
|
13312
|
-
const existing = await findHostCpContainer();
|
|
13313
|
-
if (!existing) {
|
|
13314
|
-
printInfo("Host CP", "not running");
|
|
13315
|
-
removeToken();
|
|
13316
|
-
removePid();
|
|
13317
|
-
return;
|
|
13335
|
+
function walk(name, acc, stack) {
|
|
13336
|
+
if (stack.includes(name)) {
|
|
13337
|
+
throw new ArchetypeCycleError([...stack, name]);
|
|
13318
13338
|
}
|
|
13319
|
-
const
|
|
13320
|
-
if (!
|
|
13321
|
-
|
|
13322
|
-
process.stderr.write(result.stderr);
|
|
13323
|
-
process.exitCode = 1;
|
|
13324
|
-
return;
|
|
13339
|
+
const arch2 = getArchetype(name);
|
|
13340
|
+
if (!arch2) {
|
|
13341
|
+
throw new UnknownArchetypeError(name, listArchetypeNames());
|
|
13325
13342
|
}
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
|
|
13329
|
-
}
|
|
13330
|
-
async function buildStatusReport() {
|
|
13331
|
-
const container = await findHostCpContainer();
|
|
13332
|
-
const health = await probeHealth();
|
|
13333
|
-
const tokenFile = tokenPath();
|
|
13334
|
-
const tokenPresent = fs19.existsSync(tokenFile);
|
|
13335
|
-
let tokenModeOk = false;
|
|
13336
|
-
if (tokenPresent) {
|
|
13337
|
-
const mode = fs19.statSync(tokenFile).mode & 511;
|
|
13338
|
-
tokenModeOk = mode === 384;
|
|
13343
|
+
const nextStack = [...stack, name];
|
|
13344
|
+
for (const parent of arch2.inherits ?? []) {
|
|
13345
|
+
walk(parent, acc, nextStack);
|
|
13339
13346
|
}
|
|
13340
|
-
const
|
|
13341
|
-
|
|
13342
|
-
if (!container) {
|
|
13343
|
-
stack = "not_started";
|
|
13344
|
-
} else if (container.state === "running" && health) {
|
|
13345
|
-
stack = "running";
|
|
13346
|
-
} else if (container.state === "running") {
|
|
13347
|
-
stack = "partial";
|
|
13348
|
-
} else {
|
|
13349
|
-
stack = "stopped";
|
|
13347
|
+
for (const cap of arch2.capabilities) {
|
|
13348
|
+
acc.add(cap);
|
|
13350
13349
|
}
|
|
13351
|
-
return {
|
|
13352
|
-
stack,
|
|
13353
|
-
container,
|
|
13354
|
-
health,
|
|
13355
|
-
token_present: tokenPresent,
|
|
13356
|
-
token_mode_ok: tokenModeOk,
|
|
13357
|
-
pid_present: pidPresent,
|
|
13358
|
-
url: `http://127.0.0.1:${HOST_CP_PORT}`
|
|
13359
|
-
};
|
|
13360
13350
|
}
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
13367
|
-
|
|
13368
|
-
|
|
13369
|
-
|
|
13370
|
-
|
|
13371
|
-
|
|
13372
|
-
|
|
13373
|
-
|
|
13374
|
-
|
|
13375
|
-
|
|
13376
|
-
}
|
|
13377
|
-
if (report.health) {
|
|
13378
|
-
printInfo("Health", "ok");
|
|
13379
|
-
printInfo("Uptime", String(report.health["uptime_seconds"] ?? "unknown") + "s");
|
|
13380
|
-
const cache = report.health["cache"];
|
|
13381
|
-
if (cache) {
|
|
13382
|
-
printInfo("Cached worlds", String(cache.worlds?.length ?? 0));
|
|
13383
|
-
printInfo("Cache TTL", `${cache.ttl_sec ?? "unknown"}s`);
|
|
13384
|
-
}
|
|
13385
|
-
const sse = report.health["sse"];
|
|
13386
|
-
if (sse) {
|
|
13387
|
-
printInfo("SSE active", `${sse.active ?? 0} / ${sse.cap ?? 0}`);
|
|
13351
|
+
|
|
13352
|
+
// src/commands/install.ts
|
|
13353
|
+
init_output();
|
|
13354
|
+
var ROLE_FILE_PATH = path6.join(os3.homedir(), ".olam", "role.yaml");
|
|
13355
|
+
function readRoleFile() {
|
|
13356
|
+
if (!fs6.existsSync(ROLE_FILE_PATH)) return null;
|
|
13357
|
+
try {
|
|
13358
|
+
const raw = fs6.readFileSync(ROLE_FILE_PATH, "utf-8");
|
|
13359
|
+
const m = /archetype:\s*(\S+)/.exec(raw);
|
|
13360
|
+
if (!m) return null;
|
|
13361
|
+
const archetype = m[1];
|
|
13362
|
+
const customCapabilities = [];
|
|
13363
|
+
for (const line of raw.split("\n")) {
|
|
13364
|
+
const caps = /^\s+-\s+(\S+)$/.exec(line);
|
|
13365
|
+
if (caps) customCapabilities.push(caps[1]);
|
|
13388
13366
|
}
|
|
13389
|
-
|
|
13390
|
-
|
|
13367
|
+
const installedAtMatch = /installedAt:\s*(\d+)/.exec(raw);
|
|
13368
|
+
return {
|
|
13369
|
+
archetype,
|
|
13370
|
+
customCapabilities,
|
|
13371
|
+
installedAt: installedAtMatch ? Number(installedAtMatch[1]) : 0
|
|
13372
|
+
};
|
|
13373
|
+
} catch {
|
|
13374
|
+
return null;
|
|
13391
13375
|
}
|
|
13392
|
-
printInfo("Token file", report.token_present ? report.token_mode_ok ? "present (mode 600)" : "present (BAD MODE \u2014 should be 600)" : "absent");
|
|
13393
|
-
printInfo("PID file", report.pid_present ? "present" : "absent");
|
|
13394
|
-
process.exitCode = report.stack === "running" ? 0 : 1;
|
|
13395
13376
|
}
|
|
13396
|
-
function
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
|
|
13401
|
-
|
|
13402
|
-
await handleStop();
|
|
13403
|
-
});
|
|
13404
|
-
hostCp.command("status").description("Show host CP container + health diagnostics").option("--json", "Output as JSON (machine-parseable; sets exit code)").action(async (opts) => {
|
|
13405
|
-
await handleStatus({ json: opts.json === true });
|
|
13406
|
-
});
|
|
13407
|
-
hostCp.command("register").description("Register a world with the running host CP so it appears in the unified UI").requiredOption("--world <id>", "World id (the docker container suffix, e.g. gold-arc-1454)").option("--port <port>", "Override per-world CP port; default: discovered from `olam list`").action(async (opts) => {
|
|
13408
|
-
await handleRegister({ world: opts.world, port: opts.port });
|
|
13409
|
-
});
|
|
13410
|
-
hostCp.command("deregister").description("Remove a world from the host CP registry (does NOT destroy the world)").requiredOption("--world <id>", "World id to remove").action(async (opts) => {
|
|
13411
|
-
await handleDeregister({ world: opts.world });
|
|
13377
|
+
function writeRoleFile(role) {
|
|
13378
|
+
fs6.mkdirSync(path6.dirname(ROLE_FILE_PATH), { recursive: true });
|
|
13379
|
+
const yaml = stringifyYaml3({
|
|
13380
|
+
archetype: role.archetype,
|
|
13381
|
+
customCapabilities: [...role.customCapabilities],
|
|
13382
|
+
installedAt: role.installedAt
|
|
13412
13383
|
});
|
|
13384
|
+
fs6.writeFileSync(ROLE_FILE_PATH, yaml, { mode: 420 });
|
|
13413
13385
|
}
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
|
|
13386
|
+
function nextStepsFor(archetype) {
|
|
13387
|
+
switch (archetype) {
|
|
13388
|
+
case "user":
|
|
13389
|
+
return [
|
|
13390
|
+
'You can now run `olam create --task "..."` against a workspace your admin has curated.',
|
|
13391
|
+
"Register the Claude Code plugin if you haven't: `claude plugin install ./plugin`."
|
|
13392
|
+
];
|
|
13393
|
+
case "workspace-curator":
|
|
13394
|
+
return [
|
|
13395
|
+
"You can now curate the catalog: `olam workspace add <name> --from-config`.",
|
|
13396
|
+
"Share workspace YAML files via your usual dotfiles / rsync flow."
|
|
13397
|
+
];
|
|
13398
|
+
case "policy-admin":
|
|
13399
|
+
return [
|
|
13400
|
+
"Set deployment-wide policy via env vars (OLAM_CLAUDE_PERMISSION_MODE) or wrangler secrets.",
|
|
13401
|
+
"PR-gate defaults can be overridden per-workspace under `policy.set`."
|
|
13402
|
+
];
|
|
13403
|
+
case "bootstrapper":
|
|
13404
|
+
return [
|
|
13405
|
+
"Start the auth container: `olam auth up`, then `olam auth login`.",
|
|
13406
|
+
"For Cloudflare deployments: `cd packages/cloudflare-worker && pnpm wrangler deploy`."
|
|
13407
|
+
];
|
|
13408
|
+
case "admin":
|
|
13409
|
+
return [
|
|
13410
|
+
"Start the auth container: `olam auth up`, then `olam auth login`.",
|
|
13411
|
+
"Curate workspaces: `olam workspace add <name> --from-config`.",
|
|
13412
|
+
"Assign narrower archetypes to teammates (CF side, once slice C lands): use `role.manage`."
|
|
13413
|
+
];
|
|
13414
|
+
case "dev":
|
|
13415
|
+
return [
|
|
13416
|
+
"You have repo-local privileges. Run `npm run build:ci && npm run test:ci`.",
|
|
13417
|
+
"Everything an admin has is available."
|
|
13418
|
+
];
|
|
13419
|
+
default:
|
|
13420
|
+
return [];
|
|
13424
13421
|
}
|
|
13425
13422
|
}
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
|
|
13432
|
-
|
|
13433
|
-
|
|
13434
|
-
|
|
13435
|
-
|
|
13436
|
-
|
|
13437
|
-
|
|
13438
|
-
|
|
13439
|
-
|
|
13440
|
-
|
|
13441
|
-
|
|
13442
|
-
|
|
13443
|
-
|
|
13444
|
-
|
|
13445
|
-
|
|
13446
|
-
const
|
|
13447
|
-
|
|
13423
|
+
function registerInstall(program2) {
|
|
13424
|
+
program2.command("install").description("Pick an archetype preset for this Olam install").option("--as <archetype>", "Archetype name (user, workspace-curator, policy-admin, bootstrapper, admin, dev)").option(
|
|
13425
|
+
"--capability <name>",
|
|
13426
|
+
"Additional capability to grant beyond the archetype preset (repeatable)",
|
|
13427
|
+
(value, acc) => {
|
|
13428
|
+
acc.push(value);
|
|
13429
|
+
return acc;
|
|
13430
|
+
},
|
|
13431
|
+
[]
|
|
13432
|
+
).option("--force", "Overwrite an existing role.yaml", false).option("--show", "Print the current install's archetype and exit", false).action((opts) => {
|
|
13433
|
+
if (opts.show) {
|
|
13434
|
+
const existing2 = readRoleFile();
|
|
13435
|
+
if (!existing2) {
|
|
13436
|
+
console.log(pc4.dim(`No install recorded at ${ROLE_FILE_PATH}`));
|
|
13437
|
+
return;
|
|
13438
|
+
}
|
|
13439
|
+
printHeader("Current install");
|
|
13440
|
+
printInfo("Archetype", existing2.archetype);
|
|
13441
|
+
printInfo("File", ROLE_FILE_PATH);
|
|
13442
|
+
const caps = expandArchetype(existing2.archetype);
|
|
13443
|
+
for (const cap of existing2.customCapabilities) caps.add(cap);
|
|
13444
|
+
printHeader(`Effective capabilities (${caps.size})`);
|
|
13445
|
+
for (const cap of [...caps].sort()) {
|
|
13446
|
+
console.log(` ${cap}`);
|
|
13447
|
+
}
|
|
13448
|
+
return;
|
|
13449
|
+
}
|
|
13450
|
+
if (!opts.as) {
|
|
13451
|
+
printError("Missing --as <archetype>. Known archetypes:");
|
|
13452
|
+
for (const arch2 of ARCHETYPES) {
|
|
13453
|
+
console.log(` ${pc4.bold(arch2.name.padEnd(20))} ${pc4.dim(arch2.description)}`);
|
|
13454
|
+
}
|
|
13455
|
+
process.exitCode = 1;
|
|
13456
|
+
return;
|
|
13457
|
+
}
|
|
13458
|
+
const archetype = getArchetype(opts.as);
|
|
13459
|
+
if (!archetype) {
|
|
13460
|
+
printError(
|
|
13461
|
+
`Unknown archetype "${opts.as}". Known: ${listArchetypeNames().join(", ")}`
|
|
13462
|
+
);
|
|
13463
|
+
process.exitCode = 1;
|
|
13464
|
+
return;
|
|
13465
|
+
}
|
|
13466
|
+
const extras = [];
|
|
13467
|
+
for (const raw of opts.capability) {
|
|
13448
13468
|
try {
|
|
13449
|
-
|
|
13450
|
-
|
|
13451
|
-
|
|
13452
|
-
|
|
13453
|
-
|
|
13469
|
+
extras.push(assertCapability(raw));
|
|
13470
|
+
} catch (err) {
|
|
13471
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
13472
|
+
process.exitCode = 1;
|
|
13473
|
+
return;
|
|
13454
13474
|
}
|
|
13455
|
-
return { ok: false, status: res.status, error: errMsg };
|
|
13456
13475
|
}
|
|
13457
|
-
const
|
|
13458
|
-
|
|
13459
|
-
|
|
13460
|
-
|
|
13461
|
-
|
|
13462
|
-
|
|
13463
|
-
|
|
13464
|
-
}
|
|
13465
|
-
|
|
13466
|
-
|
|
13467
|
-
|
|
13468
|
-
|
|
13469
|
-
|
|
13470
|
-
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13476
|
+
const existing = readRoleFile();
|
|
13477
|
+
if (existing && !opts.force) {
|
|
13478
|
+
printError(
|
|
13479
|
+
`Install already recorded as "${existing.archetype}" at ${ROLE_FILE_PATH}. Pass --force to overwrite.`
|
|
13480
|
+
);
|
|
13481
|
+
process.exitCode = 1;
|
|
13482
|
+
return;
|
|
13483
|
+
}
|
|
13484
|
+
let resolved;
|
|
13485
|
+
try {
|
|
13486
|
+
resolved = expandArchetype(archetype.name);
|
|
13487
|
+
} catch (err) {
|
|
13488
|
+
if (err instanceof UnknownArchetypeError) {
|
|
13489
|
+
printError(err.message);
|
|
13490
|
+
process.exitCode = 1;
|
|
13491
|
+
return;
|
|
13492
|
+
}
|
|
13493
|
+
throw err;
|
|
13494
|
+
}
|
|
13495
|
+
for (const extra of extras) resolved.add(extra);
|
|
13496
|
+
writeRoleFile({
|
|
13497
|
+
archetype: archetype.name,
|
|
13498
|
+
customCapabilities: extras,
|
|
13499
|
+
installedAt: Date.now()
|
|
13479
13500
|
});
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
|
|
13501
|
+
printHeader(`Installed as ${pc4.bold(archetype.name)}`);
|
|
13502
|
+
printInfo("File", ROLE_FILE_PATH);
|
|
13503
|
+
printInfo("Description", archetype.description);
|
|
13504
|
+
printInfo("Capabilities", `${resolved.size} total`);
|
|
13505
|
+
if (extras.length > 0) {
|
|
13506
|
+
printInfo("Added beyond preset", extras.join(", "));
|
|
13483
13507
|
}
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
|
|
13508
|
+
const steps = nextStepsFor(archetype.name);
|
|
13509
|
+
if (steps.length > 0) {
|
|
13510
|
+
printHeader("Next steps");
|
|
13511
|
+
for (const step of steps) console.log(` ${pc4.dim("\u2022")} ${step}`);
|
|
13512
|
+
}
|
|
13513
|
+
printSuccess("Done. Run `olam install --show` to see the full capability list.");
|
|
13514
|
+
});
|
|
13515
|
+
}
|
|
13516
|
+
|
|
13517
|
+
// src/commands/auth.ts
|
|
13518
|
+
init_auth();
|
|
13519
|
+
init_output();
|
|
13520
|
+
import pc8 from "picocolors";
|
|
13521
|
+
import * as readline from "node:readline/promises";
|
|
13522
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
13523
|
+
|
|
13524
|
+
// src/commands/auth-status.ts
|
|
13525
|
+
import * as fs8 from "node:fs";
|
|
13526
|
+
import * as os5 from "node:os";
|
|
13527
|
+
import * as path9 from "node:path";
|
|
13528
|
+
import pc5 from "picocolors";
|
|
13529
|
+
|
|
13530
|
+
// ../auth-logic/dist/effective-state.js
|
|
13531
|
+
function effectiveState(account, now = Date.now()) {
|
|
13532
|
+
const persisted = account.state ?? (account.rateLimited ? "cooldown" : "active");
|
|
13533
|
+
if (persisted === "disabled")
|
|
13534
|
+
return "disabled";
|
|
13535
|
+
if (account.expiresAt != null && account.expiresAt <= now)
|
|
13536
|
+
return "expired";
|
|
13537
|
+
if (persisted === "cooldown" || persisted === "usage-capped") {
|
|
13538
|
+
const reset = account.rateLimitResetsAt ? new Date(account.rateLimitResetsAt).getTime() : 0;
|
|
13539
|
+
if (reset > 0 && reset <= now)
|
|
13540
|
+
return "active";
|
|
13541
|
+
return persisted;
|
|
13491
13542
|
}
|
|
13543
|
+
return "active";
|
|
13492
13544
|
}
|
|
13493
|
-
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13545
|
+
|
|
13546
|
+
// ../auth-logic/dist/pick-credential.js
|
|
13547
|
+
function pickCredential(accounts, now = Date.now()) {
|
|
13548
|
+
const active = accounts.filter((a) => effectiveState(a, now) === "active");
|
|
13549
|
+
if (active.length === 0)
|
|
13550
|
+
return null;
|
|
13551
|
+
return [...active].sort((a, b) => {
|
|
13552
|
+
const aCount = a.usage?.requestCount5h ?? 0;
|
|
13553
|
+
const bCount = b.usage?.requestCount5h ?? 0;
|
|
13554
|
+
if (aCount !== bCount)
|
|
13555
|
+
return aCount - bCount;
|
|
13556
|
+
const aLast = a.lastUsed ? new Date(a.lastUsed).getTime() : 0;
|
|
13557
|
+
const bLast = b.lastUsed ? new Date(b.lastUsed).getTime() : 0;
|
|
13558
|
+
return aLast - bLast;
|
|
13559
|
+
})[0] ?? null;
|
|
13560
|
+
}
|
|
13561
|
+
|
|
13562
|
+
// ../auth-logic/dist/next-cooldown-reset.js
|
|
13563
|
+
function nextCooldownReset(accounts, now = Date.now()) {
|
|
13564
|
+
const upcoming = accounts.filter((a) => effectiveState(a, now) === "cooldown").map((a) => a.rateLimitResetsAt ? new Date(a.rateLimitResetsAt).getTime() : 0).filter((ts) => ts > now).sort((a, b) => a - b);
|
|
13565
|
+
return upcoming.length > 0 ? new Date(upcoming[0]).toISOString() : null;
|
|
13566
|
+
}
|
|
13567
|
+
|
|
13568
|
+
// src/commands/auth-status.ts
|
|
13569
|
+
init_auth();
|
|
13570
|
+
init_output();
|
|
13571
|
+
init_exit_codes();
|
|
13572
|
+
var LOCAL_DATA_DIR = path9.join(os5.homedir(), ".olam", "auth-data");
|
|
13573
|
+
function localHHMM(isoStr) {
|
|
13574
|
+
const d = new Date(isoStr);
|
|
13575
|
+
return d.toLocaleTimeString(void 0, {
|
|
13576
|
+
hour: "2-digit",
|
|
13577
|
+
minute: "2-digit",
|
|
13578
|
+
hour12: false
|
|
13579
|
+
});
|
|
13580
|
+
}
|
|
13581
|
+
function daysAgoStr(expiresAt, now) {
|
|
13582
|
+
const diffDays = Math.floor((now - expiresAt) / (1e3 * 60 * 60 * 24));
|
|
13583
|
+
if (diffDays <= 0) return "expired today";
|
|
13584
|
+
return `expired ${diffDays} day${diffDays === 1 ? "" : "s"} ago`;
|
|
13585
|
+
}
|
|
13586
|
+
function trunc(s, maxLen) {
|
|
13587
|
+
return s.length > maxLen ? s.slice(0, maxLen) : s;
|
|
13588
|
+
}
|
|
13589
|
+
var STATE_PRIORITY = {
|
|
13590
|
+
active: 0,
|
|
13591
|
+
cooldown: 1,
|
|
13592
|
+
"usage-capped": 2,
|
|
13593
|
+
disabled: 3,
|
|
13594
|
+
expired: 4
|
|
13595
|
+
};
|
|
13596
|
+
function formatAuthStatus(accounts, now = Date.now()) {
|
|
13597
|
+
const picked = pickCredential(accounts, now);
|
|
13598
|
+
const rows = accounts.map((account) => {
|
|
13599
|
+
const state = effectiveState(account, now);
|
|
13600
|
+
const isPicked = picked != null && account.id === picked.id;
|
|
13601
|
+
const req5h = account.usage?.requestCount5h ?? 0;
|
|
13602
|
+
const last429 = account.usage?.last429At ? localHHMM(account.usage.last429At) : "never";
|
|
13603
|
+
let reason;
|
|
13604
|
+
if (isPicked) {
|
|
13605
|
+
reason = "\u2190 selected";
|
|
13606
|
+
} else if (state === "active") {
|
|
13607
|
+
reason = `req5h=${req5h} (higher than candidate)`;
|
|
13608
|
+
} else if (state === "cooldown") {
|
|
13609
|
+
const resetTime = account.rateLimitResetsAt ? localHHMM(account.rateLimitResetsAt) : "?";
|
|
13610
|
+
reason = `cooldown until ${resetTime}`;
|
|
13611
|
+
} else if (state === "expired") {
|
|
13612
|
+
reason = daysAgoStr(account.expiresAt ?? 0, now);
|
|
13613
|
+
} else {
|
|
13614
|
+
reason = "disabled";
|
|
13502
13615
|
}
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
13616
|
+
return { id: account.id, label: account.accountLabel ?? account.id, state, reason, req5h, last429, isPicked };
|
|
13617
|
+
});
|
|
13618
|
+
rows.sort((a, b) => {
|
|
13619
|
+
if (a.isPicked !== b.isPicked) return a.isPicked ? -1 : 1;
|
|
13620
|
+
return STATE_PRIORITY[a.state] - STATE_PRIORITY[b.state];
|
|
13621
|
+
});
|
|
13622
|
+
const COL = { id: 17, label: 17, state: 11, reason: 28, req5h: 7 };
|
|
13623
|
+
const lines = [];
|
|
13624
|
+
const hdr = "id".padEnd(COL.id) + "label".padEnd(COL.label) + "state".padEnd(COL.state) + "reason".padEnd(COL.reason) + "req5h".padEnd(COL.req5h) + "last429";
|
|
13625
|
+
lines.push(pc5.dim(hdr));
|
|
13626
|
+
lines.push(pc5.dim("-".repeat(hdr.length)));
|
|
13627
|
+
for (const row of rows) {
|
|
13628
|
+
const id = trunc(row.id, 16).padEnd(COL.id);
|
|
13629
|
+
const label = trunc(row.label, 16).padEnd(COL.label);
|
|
13630
|
+
const stateRaw = row.state.padEnd(COL.state);
|
|
13631
|
+
const stateColored = row.state === "active" ? pc5.green(stateRaw) : row.state === "cooldown" ? pc5.yellow(stateRaw) : row.state === "expired" ? pc5.red(stateRaw) : pc5.dim(stateRaw);
|
|
13632
|
+
const reason = row.reason.padEnd(COL.reason);
|
|
13633
|
+
const req5h = String(row.req5h).padEnd(COL.req5h);
|
|
13634
|
+
if (row.isPicked) {
|
|
13635
|
+
lines.push(
|
|
13636
|
+
pc5.bold(id) + pc5.bold(label) + stateColored + pc5.green(reason) + pc5.dim(req5h) + pc5.dim(row.last429)
|
|
13508
13637
|
);
|
|
13509
|
-
|
|
13510
|
-
|
|
13638
|
+
} else {
|
|
13639
|
+
lines.push(id + label + stateColored + reason + pc5.dim(req5h) + pc5.dim(row.last429));
|
|
13511
13640
|
}
|
|
13512
13641
|
}
|
|
13513
|
-
|
|
13514
|
-
|
|
13515
|
-
|
|
13516
|
-
|
|
13517
|
-
|
|
13518
|
-
|
|
13642
|
+
if (picked == null) {
|
|
13643
|
+
const resetIso = nextCooldownReset(accounts, now);
|
|
13644
|
+
lines.push("");
|
|
13645
|
+
lines.push(
|
|
13646
|
+
resetIso ? pc5.yellow(`Next reset: ${localHHMM(resetIso)}`) : pc5.dim("No reset scheduled")
|
|
13647
|
+
);
|
|
13648
|
+
return { output: lines.join("\n"), exitCode: EXIT_AUTH_NEEDS_ATTENTION };
|
|
13649
|
+
}
|
|
13650
|
+
return { output: lines.join("\n"), exitCode: 0 };
|
|
13651
|
+
}
|
|
13652
|
+
function toSafeAccount(a) {
|
|
13653
|
+
return {
|
|
13654
|
+
id: a.id,
|
|
13655
|
+
accountLabel: a.accountLabel,
|
|
13656
|
+
// expiresAt not exposed in summary — state is pre-computed server-side
|
|
13657
|
+
rateLimited: a.rateLimited,
|
|
13658
|
+
rateLimitResetsAt: a.rateLimitResetsAt,
|
|
13659
|
+
lastUsed: a.lastUsed,
|
|
13660
|
+
state: a.state,
|
|
13661
|
+
usage: a.usage ? { requestCount5h: a.usage.requestCount5h, last429At: a.usage.last429At } : void 0
|
|
13662
|
+
};
|
|
13663
|
+
}
|
|
13664
|
+
async function runAuthStatus(getStatus) {
|
|
13665
|
+
const fetchStatus = getStatus ?? (() => new AuthClient().status());
|
|
13666
|
+
let status;
|
|
13667
|
+
try {
|
|
13668
|
+
status = await fetchStatus();
|
|
13669
|
+
} catch {
|
|
13670
|
+
printError("Failed to contact auth service. Run `olam auth up` first.");
|
|
13519
13671
|
process.exitCode = 1;
|
|
13520
13672
|
return;
|
|
13521
13673
|
}
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
}
|
|
13525
|
-
async function handleDeregister(opts) {
|
|
13526
|
-
printHeader("Deregister world from host CP");
|
|
13527
|
-
const result = await callHostCpRegistry("DELETE", { id: opts.world });
|
|
13528
|
-
if (!result.ok) {
|
|
13529
|
-
printError(`Deregister failed: ${result.error}`);
|
|
13530
|
-
if (result.status === 0) {
|
|
13531
|
-
printInfo("Hint", "Is host CP running? `olam host-cp status`");
|
|
13532
|
-
}
|
|
13674
|
+
if (!status.reachable) {
|
|
13675
|
+
printError("Auth container is not reachable. Run `olam auth up` first.");
|
|
13533
13676
|
process.exitCode = 1;
|
|
13534
13677
|
return;
|
|
13535
13678
|
}
|
|
13536
|
-
|
|
13679
|
+
if (status.accounts.length === 0) {
|
|
13680
|
+
console.log(pc5.dim("No credentials found. Run: olam auth login"));
|
|
13681
|
+
return;
|
|
13682
|
+
}
|
|
13683
|
+
const accounts = status.accounts.map(toSafeAccount);
|
|
13684
|
+
const result = formatAuthStatus(accounts);
|
|
13685
|
+
console.log(result.output);
|
|
13686
|
+
if (result.exitCode !== 0) {
|
|
13687
|
+
process.exitCode = result.exitCode;
|
|
13688
|
+
}
|
|
13537
13689
|
}
|
|
13538
13690
|
|
|
13539
13691
|
// src/commands/auth-upgrade.ts
|
|
13692
|
+
init_output();
|
|
13693
|
+
init_host_cp();
|
|
13540
13694
|
init_auth();
|
|
13695
|
+
import * as fs20 from "node:fs";
|
|
13696
|
+
import * as path23 from "node:path";
|
|
13697
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
13698
|
+
import ora2 from "ora";
|
|
13699
|
+
import pc7 from "picocolors";
|
|
13541
13700
|
|
|
13542
13701
|
// src/commands/bootstrap.ts
|
|
13543
13702
|
init_install_root();
|
|
13544
13703
|
init_exit_codes();
|
|
13545
13704
|
init_protocol_version();
|
|
13705
|
+
init_output();
|
|
13546
13706
|
import { spawn as spawn2, spawnSync as spawnSync6 } from "node:child_process";
|
|
13547
13707
|
import { existsSync as existsSync19, readFileSync as readFileSync15 } from "node:fs";
|
|
13548
13708
|
import { join as join25 } from "node:path";
|
|
@@ -14063,9 +14223,9 @@ async function defaultRecreateAuth() {
|
|
|
14063
14223
|
});
|
|
14064
14224
|
const controller = new AuthContainerController();
|
|
14065
14225
|
controller.start();
|
|
14066
|
-
const healthy = await waitForAuthHealth(
|
|
14226
|
+
const healthy = await waitForAuthHealth(6e4);
|
|
14067
14227
|
if (!healthy) {
|
|
14068
|
-
return { ok: false, error: "auth-service /health did not respond within
|
|
14228
|
+
return { ok: false, error: "auth-service /health did not respond within 60s" };
|
|
14069
14229
|
}
|
|
14070
14230
|
return { ok: true };
|
|
14071
14231
|
} catch (err) {
|
|
@@ -14504,9 +14664,9 @@ function formatFreshnessWarning(result, image = DEFAULT_DEVBOX_IMAGE) {
|
|
|
14504
14664
|
"These source files have changed since the image was built; the",
|
|
14505
14665
|
"changes will NOT take effect in fresh worlds until you rebuild:"
|
|
14506
14666
|
];
|
|
14507
|
-
for (const { path:
|
|
14667
|
+
for (const { path: path42, mtimeMs } of result.newerSources) {
|
|
14508
14668
|
const when = new Date(mtimeMs).toISOString();
|
|
14509
|
-
lines.push(` \u2022 ${
|
|
14669
|
+
lines.push(` \u2022 ${path42} (modified ${when})`);
|
|
14510
14670
|
}
|
|
14511
14671
|
lines.push("");
|
|
14512
14672
|
lines.push("Rebuild with:");
|
|
@@ -14663,18 +14823,20 @@ function decideWorkspaceMatch(input) {
|
|
|
14663
14823
|
|
|
14664
14824
|
// src/commands/create.ts
|
|
14665
14825
|
init_context();
|
|
14826
|
+
init_output();
|
|
14827
|
+
init_host_cp();
|
|
14666
14828
|
var HOST_CP_URL = "http://127.0.0.1:19000";
|
|
14667
14829
|
async function readHostCpTokenForCreate() {
|
|
14668
14830
|
try {
|
|
14669
|
-
const { default:
|
|
14670
|
-
const { default:
|
|
14671
|
-
const { default:
|
|
14672
|
-
const tp =
|
|
14673
|
-
process.env.OLAM_HOME ??
|
|
14831
|
+
const { default: fs38 } = await import("node:fs");
|
|
14832
|
+
const { default: os22 } = await import("node:os");
|
|
14833
|
+
const { default: path42 } = await import("node:path");
|
|
14834
|
+
const tp = path42.join(
|
|
14835
|
+
process.env.OLAM_HOME ?? path42.join(os22.homedir(), ".olam"),
|
|
14674
14836
|
"host-cp.token"
|
|
14675
14837
|
);
|
|
14676
|
-
if (!
|
|
14677
|
-
return
|
|
14838
|
+
if (!fs38.existsSync(tp)) return null;
|
|
14839
|
+
return fs38.readFileSync(tp, "utf-8").trim();
|
|
14678
14840
|
} catch {
|
|
14679
14841
|
return null;
|
|
14680
14842
|
}
|
|
@@ -15036,12 +15198,12 @@ function defaultNameFromPrompt(prompt) {
|
|
|
15036
15198
|
}
|
|
15037
15199
|
async function readHostCpToken3() {
|
|
15038
15200
|
try {
|
|
15039
|
-
const { default:
|
|
15040
|
-
const { default:
|
|
15041
|
-
const { default:
|
|
15042
|
-
const tp =
|
|
15043
|
-
if (!
|
|
15044
|
-
const raw =
|
|
15201
|
+
const { default: fs38 } = await import("node:fs");
|
|
15202
|
+
const { default: os22 } = await import("node:os");
|
|
15203
|
+
const { default: path42 } = await import("node:path");
|
|
15204
|
+
const tp = path42.join(os22.homedir(), ".olam", "host-cp.token");
|
|
15205
|
+
if (!fs38.existsSync(tp)) return null;
|
|
15206
|
+
const raw = fs38.readFileSync(tp, "utf-8").trim();
|
|
15045
15207
|
return raw.length > 0 ? raw : null;
|
|
15046
15208
|
} catch {
|
|
15047
15209
|
return null;
|
|
@@ -15084,6 +15246,7 @@ async function fetchHostCpExactMatches(projects) {
|
|
|
15084
15246
|
|
|
15085
15247
|
// src/commands/dispatch.ts
|
|
15086
15248
|
init_context();
|
|
15249
|
+
init_output();
|
|
15087
15250
|
import ora4 from "ora";
|
|
15088
15251
|
import pc10 from "picocolors";
|
|
15089
15252
|
|
|
@@ -15106,6 +15269,10 @@ function registerDispatch(program2) {
|
|
|
15106
15269
|
process.exitCode = 1;
|
|
15107
15270
|
return;
|
|
15108
15271
|
}
|
|
15272
|
+
{
|
|
15273
|
+
const { checkVersionPin: checkVersionPin2 } = await Promise.resolve().then(() => (init_version_pin(), version_pin_exports));
|
|
15274
|
+
checkVersionPin2(worldId, worldMeta.workspacePath);
|
|
15275
|
+
}
|
|
15109
15276
|
if (worldMeta.status !== "running") {
|
|
15110
15277
|
printError(`World "${worldId}" is ${worldMeta.status}. Only running worlds accept dispatches.`);
|
|
15111
15278
|
process.exitCode = 1;
|
|
@@ -15180,6 +15347,7 @@ ${pc10.dim(`Watch live: docker exec -it ${containerName} tmux attach -t claude-m
|
|
|
15180
15347
|
|
|
15181
15348
|
// src/commands/observe.ts
|
|
15182
15349
|
init_context();
|
|
15350
|
+
init_output();
|
|
15183
15351
|
import pc11 from "picocolors";
|
|
15184
15352
|
function registerObserve(program2) {
|
|
15185
15353
|
program2.command("observe").description("Stream thoughts from a world (coming soon)").argument("<world>", "World ID").action(async (worldId) => {
|
|
@@ -15195,6 +15363,10 @@ function registerObserve(program2) {
|
|
|
15195
15363
|
process.exitCode = 1;
|
|
15196
15364
|
return;
|
|
15197
15365
|
}
|
|
15366
|
+
{
|
|
15367
|
+
const { checkVersionPin: checkVersionPin2 } = await Promise.resolve().then(() => (init_version_pin(), version_pin_exports));
|
|
15368
|
+
checkVersionPin2(worldId, world.workspacePath);
|
|
15369
|
+
}
|
|
15198
15370
|
console.log(
|
|
15199
15371
|
pc11.yellow("Observation is coming in a future release.")
|
|
15200
15372
|
);
|
|
@@ -15211,6 +15383,7 @@ ${pc11.dim(`For now: docker exec -it ${containerName} tmux attach -t claude-main
|
|
|
15211
15383
|
|
|
15212
15384
|
// src/commands/list.ts
|
|
15213
15385
|
init_context();
|
|
15386
|
+
init_output();
|
|
15214
15387
|
import pc12 from "picocolors";
|
|
15215
15388
|
function registerList(program2) {
|
|
15216
15389
|
program2.command("list").alias("ls").description("List active worlds").action(async () => {
|
|
@@ -15241,10 +15414,88 @@ function registerList(program2) {
|
|
|
15241
15414
|
}
|
|
15242
15415
|
|
|
15243
15416
|
// src/commands/status.ts
|
|
15244
|
-
|
|
15417
|
+
init_output();
|
|
15418
|
+
import * as fs22 from "node:fs";
|
|
15419
|
+
import * as os13 from "node:os";
|
|
15420
|
+
import * as path25 from "node:path";
|
|
15421
|
+
var CLI_VERSION2 = process.env["OLAM_CLI_VERSION"] ?? "0.0.0";
|
|
15422
|
+
var HOST_CP_PORT2 = 19e3;
|
|
15423
|
+
async function getMachineStatus(_probe, _loadCtx, _readToken) {
|
|
15424
|
+
const probe = _probe ?? (async () => {
|
|
15425
|
+
const { probeHostCp: probeHostCp2 } = await Promise.resolve().then(() => (init_host_cp(), host_cp_exports));
|
|
15426
|
+
return probeHostCp2();
|
|
15427
|
+
});
|
|
15428
|
+
let readToken2;
|
|
15429
|
+
if (_readToken) {
|
|
15430
|
+
readToken2 = _readToken;
|
|
15431
|
+
} else {
|
|
15432
|
+
const { readToken: rt } = await Promise.resolve().then(() => (init_host_cp(), host_cp_exports));
|
|
15433
|
+
readToken2 = rt;
|
|
15434
|
+
}
|
|
15435
|
+
const loadCtx = _loadCtx ?? (async () => {
|
|
15436
|
+
const { loadContext: loadContext2 } = await Promise.resolve().then(() => (init_context(), context_exports));
|
|
15437
|
+
return loadContext2();
|
|
15438
|
+
});
|
|
15439
|
+
const probeResult = await probe();
|
|
15440
|
+
const tokenPresent = readToken2() !== null;
|
|
15441
|
+
const hostCpRunning = probeResult !== null;
|
|
15442
|
+
let worldCount = 0;
|
|
15443
|
+
try {
|
|
15444
|
+
const { ctx } = await loadCtx();
|
|
15445
|
+
if (ctx) {
|
|
15446
|
+
worldCount = ctx.worldManager.listWorlds().length;
|
|
15447
|
+
}
|
|
15448
|
+
} catch {
|
|
15449
|
+
}
|
|
15450
|
+
const manifestPath2 = path25.join(os13.homedir(), ".olam", "cache", "manifest.json");
|
|
15451
|
+
let updateAvailable = null;
|
|
15452
|
+
let lastUpdateCheck = null;
|
|
15453
|
+
if (fs22.existsSync(manifestPath2)) {
|
|
15454
|
+
const mtime = fs22.statSync(manifestPath2).mtime;
|
|
15455
|
+
lastUpdateCheck = mtime.toISOString();
|
|
15456
|
+
try {
|
|
15457
|
+
const manifest = JSON.parse(fs22.readFileSync(manifestPath2, "utf-8"));
|
|
15458
|
+
const latest = manifest["version"];
|
|
15459
|
+
updateAvailable = latest !== void 0 && latest !== CLI_VERSION2;
|
|
15460
|
+
} catch {
|
|
15461
|
+
updateAvailable = null;
|
|
15462
|
+
}
|
|
15463
|
+
}
|
|
15464
|
+
return {
|
|
15465
|
+
version: CLI_VERSION2,
|
|
15466
|
+
port: HOST_CP_PORT2,
|
|
15467
|
+
host_cp: hostCpRunning ? "running" : "stopped",
|
|
15468
|
+
token_present: tokenPresent,
|
|
15469
|
+
worlds: worldCount,
|
|
15470
|
+
update_available: updateAvailable,
|
|
15471
|
+
last_update_check: lastUpdateCheck
|
|
15472
|
+
};
|
|
15473
|
+
}
|
|
15245
15474
|
function registerStatus(program2) {
|
|
15246
|
-
program2.command("status").description("Show
|
|
15247
|
-
|
|
15475
|
+
program2.command("status").description("Show status (machine status when no world given; world details with a world ID)").argument("[world]", "World ID \u2014 omit to show machine status").option("--json", "Output as JSON").action(async (worldId, opts) => {
|
|
15476
|
+
if (!worldId) {
|
|
15477
|
+
const ms = await getMachineStatus();
|
|
15478
|
+
if (opts.json) {
|
|
15479
|
+
process.stdout.write(JSON.stringify(ms, null, 2) + "\n");
|
|
15480
|
+
process.exitCode = ms.host_cp === "running" ? 0 : 1;
|
|
15481
|
+
return;
|
|
15482
|
+
}
|
|
15483
|
+
printHeader("Olam Status");
|
|
15484
|
+
printInfo("CLI version", `@pleri/olam-cli@${ms.version}`);
|
|
15485
|
+
printInfo("Host CP", ms.host_cp === "running" ? `running on port ${ms.port}` : "stopped");
|
|
15486
|
+
printInfo("Worlds", String(ms.worlds));
|
|
15487
|
+
printInfo("Token", ms.token_present ? "present" : "absent");
|
|
15488
|
+
if (ms.last_update_check) {
|
|
15489
|
+
printInfo("Last update check", ms.last_update_check);
|
|
15490
|
+
printInfo("Update available", ms.update_available === true ? "yes" : ms.update_available === false ? "no" : "unknown");
|
|
15491
|
+
} else {
|
|
15492
|
+
printInfo("Update check", "never (run `olam update --check`)");
|
|
15493
|
+
}
|
|
15494
|
+
process.exitCode = ms.host_cp === "running" ? 0 : 1;
|
|
15495
|
+
return;
|
|
15496
|
+
}
|
|
15497
|
+
const { loadContext: loadContext2 } = await Promise.resolve().then(() => (init_context(), context_exports));
|
|
15498
|
+
const { ctx, error } = await loadContext2();
|
|
15248
15499
|
if (!ctx) {
|
|
15249
15500
|
printError(error?.message ?? "Olam is not configured. Run `olam init` first.");
|
|
15250
15501
|
process.exitCode = 1;
|
|
@@ -15285,6 +15536,8 @@ function registerStatus(program2) {
|
|
|
15285
15536
|
|
|
15286
15537
|
// src/commands/destroy.ts
|
|
15287
15538
|
init_context();
|
|
15539
|
+
init_output();
|
|
15540
|
+
init_host_cp();
|
|
15288
15541
|
import ora5 from "ora";
|
|
15289
15542
|
function registerDestroy(program2) {
|
|
15290
15543
|
program2.command("destroy").description("Destroy a world and clean up resources").argument("<world>", "World ID").option("--skip-crystallize", "Skip thought crystallization").action(async (worldId, opts) => {
|
|
@@ -15323,6 +15576,7 @@ function registerDestroy(program2) {
|
|
|
15323
15576
|
|
|
15324
15577
|
// src/commands/enter.ts
|
|
15325
15578
|
init_context();
|
|
15579
|
+
init_output();
|
|
15326
15580
|
import { execSync as execSync7 } from "node:child_process";
|
|
15327
15581
|
import pc13 from "picocolors";
|
|
15328
15582
|
var SAFE_IDENT3 = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
@@ -15382,6 +15636,11 @@ function registerEnter(program2) {
|
|
|
15382
15636
|
process.exitCode = 1;
|
|
15383
15637
|
return;
|
|
15384
15638
|
}
|
|
15639
|
+
const worldMeta = ctx.worldManager.getWorld(worldId);
|
|
15640
|
+
if (worldMeta) {
|
|
15641
|
+
const { checkVersionPin: checkVersionPin2 } = await Promise.resolve().then(() => (init_version_pin(), version_pin_exports));
|
|
15642
|
+
checkVersionPin2(worldId, worldMeta.workspacePath);
|
|
15643
|
+
}
|
|
15385
15644
|
const computeWorld = await ctx.computeProvider.getWorld(worldId);
|
|
15386
15645
|
if (!computeWorld) {
|
|
15387
15646
|
printError(`World "${worldId}" not found.`);
|
|
@@ -15471,11 +15730,12 @@ ${pc13.dim(`Observe dispatch: docker exec -it ${containerName} tmux attach -t ol
|
|
|
15471
15730
|
|
|
15472
15731
|
// src/commands/crystallize.ts
|
|
15473
15732
|
init_context();
|
|
15474
|
-
|
|
15475
|
-
import "node:path";
|
|
15476
|
-
import ora6 from "ora";
|
|
15733
|
+
init_output();
|
|
15477
15734
|
init_exit_codes();
|
|
15478
15735
|
init_world_paths();
|
|
15736
|
+
import * as fs23 from "node:fs";
|
|
15737
|
+
import "node:path";
|
|
15738
|
+
import ora6 from "ora";
|
|
15479
15739
|
function registerCrystallize(program2, options = {}) {
|
|
15480
15740
|
const cmd = program2.command("crystallize").description("Crystallize thoughts from a world to Pleri Plane").argument("<world>", "World ID").action(async (worldId) => {
|
|
15481
15741
|
const { ctx, error } = await loadContext();
|
|
@@ -15505,7 +15765,7 @@ function registerCrystallize(program2, options = {}) {
|
|
|
15505
15765
|
return;
|
|
15506
15766
|
}
|
|
15507
15767
|
const thoughtDbPath = getWorldDbPath(world.workspacePath);
|
|
15508
|
-
if (!
|
|
15768
|
+
if (!fs23.existsSync(thoughtDbPath)) {
|
|
15509
15769
|
printError(`No thoughts captured yet for "${worldId}". Run a dispatch first.`);
|
|
15510
15770
|
process.exitCode = EXIT_GENERIC_ERROR;
|
|
15511
15771
|
return;
|
|
@@ -15620,6 +15880,7 @@ async function decideGate(portOffset, id, payload) {
|
|
|
15620
15880
|
}
|
|
15621
15881
|
|
|
15622
15882
|
// src/commands/pr.ts
|
|
15883
|
+
init_output();
|
|
15623
15884
|
function runningWorlds() {
|
|
15624
15885
|
const registry = new WorldRegistry();
|
|
15625
15886
|
try {
|
|
@@ -15726,7 +15987,12 @@ function registerPr(program2) {
|
|
|
15726
15987
|
decideCommand("reject", "block");
|
|
15727
15988
|
}
|
|
15728
15989
|
|
|
15990
|
+
// src/index.ts
|
|
15991
|
+
init_host_cp();
|
|
15992
|
+
|
|
15729
15993
|
// src/commands/lanes.ts
|
|
15994
|
+
init_host_cp();
|
|
15995
|
+
init_output();
|
|
15730
15996
|
async function handleList(world) {
|
|
15731
15997
|
printHeader(`Lanes in world ${world}`);
|
|
15732
15998
|
const result = await callHostCpProxy("GET", world, "/lanes");
|
|
@@ -16873,11 +17139,11 @@ var qmarksTestNoExtDot = ([$0]) => {
|
|
|
16873
17139
|
return (f) => f.length === len && f !== "." && f !== "..";
|
|
16874
17140
|
};
|
|
16875
17141
|
var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
|
|
16876
|
-
var
|
|
17142
|
+
var path27 = {
|
|
16877
17143
|
win32: { sep: "\\" },
|
|
16878
17144
|
posix: { sep: "/" }
|
|
16879
17145
|
};
|
|
16880
|
-
var sep = defaultPlatform === "win32" ?
|
|
17146
|
+
var sep = defaultPlatform === "win32" ? path27.win32.sep : path27.posix.sep;
|
|
16881
17147
|
minimatch.sep = sep;
|
|
16882
17148
|
var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
|
|
16883
17149
|
minimatch.GLOBSTAR = GLOBSTAR;
|
|
@@ -17707,23 +17973,25 @@ function registerPolicyCheck(program2) {
|
|
|
17707
17973
|
}
|
|
17708
17974
|
|
|
17709
17975
|
// src/commands/upgrade.ts
|
|
17710
|
-
|
|
17711
|
-
|
|
17976
|
+
init_output();
|
|
17977
|
+
init_host_cp();
|
|
17978
|
+
import * as fs26 from "node:fs";
|
|
17979
|
+
import * as path30 from "node:path";
|
|
17712
17980
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
17713
17981
|
import ora7 from "ora";
|
|
17714
17982
|
import pc16 from "picocolors";
|
|
17715
17983
|
|
|
17716
17984
|
// src/commands/upgrade-lock.ts
|
|
17717
|
-
import * as
|
|
17718
|
-
import * as
|
|
17719
|
-
import * as
|
|
17985
|
+
import * as fs24 from "node:fs";
|
|
17986
|
+
import * as os14 from "node:os";
|
|
17987
|
+
import * as path28 from "node:path";
|
|
17720
17988
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
17721
|
-
var LOCK_FILE_PATH =
|
|
17989
|
+
var LOCK_FILE_PATH = path28.join(os14.homedir(), ".olam", ".upgrade.lock");
|
|
17722
17990
|
var STALE_LOCK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
17723
17991
|
function readLockFile(lockPath) {
|
|
17724
17992
|
try {
|
|
17725
|
-
if (!
|
|
17726
|
-
const raw =
|
|
17993
|
+
if (!fs24.existsSync(lockPath)) return null;
|
|
17994
|
+
const raw = fs24.readFileSync(lockPath, "utf-8").trim();
|
|
17727
17995
|
if (raw.length === 0) return null;
|
|
17728
17996
|
const parsed = JSON.parse(raw);
|
|
17729
17997
|
if (typeof parsed.pid !== "number" || typeof parsed.startTs !== "number") return null;
|
|
@@ -17768,16 +18036,16 @@ function isStaleLock(content, nowMs = Date.now()) {
|
|
|
17768
18036
|
return false;
|
|
17769
18037
|
}
|
|
17770
18038
|
function acquireLock(lockPath = LOCK_FILE_PATH, nowMs = Date.now()) {
|
|
17771
|
-
const dir =
|
|
17772
|
-
|
|
18039
|
+
const dir = path28.dirname(lockPath);
|
|
18040
|
+
fs24.mkdirSync(dir, { recursive: true });
|
|
17773
18041
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
17774
18042
|
try {
|
|
17775
|
-
const fd =
|
|
18043
|
+
const fd = fs24.openSync(lockPath, "wx", 420);
|
|
17776
18044
|
try {
|
|
17777
18045
|
const content = { pid: process.pid, startTs: nowMs };
|
|
17778
|
-
|
|
18046
|
+
fs24.writeSync(fd, JSON.stringify(content));
|
|
17779
18047
|
} finally {
|
|
17780
|
-
|
|
18048
|
+
fs24.closeSync(fd);
|
|
17781
18049
|
}
|
|
17782
18050
|
return { acquired: true, lockPath };
|
|
17783
18051
|
} catch (err) {
|
|
@@ -17786,7 +18054,7 @@ function acquireLock(lockPath = LOCK_FILE_PATH, nowMs = Date.now()) {
|
|
|
17786
18054
|
const existing2 = readLockFile(lockPath);
|
|
17787
18055
|
if (isStaleLock(existing2, nowMs)) {
|
|
17788
18056
|
try {
|
|
17789
|
-
|
|
18057
|
+
fs24.unlinkSync(lockPath);
|
|
17790
18058
|
} catch (unlinkErr) {
|
|
17791
18059
|
const ucode = unlinkErr.code;
|
|
17792
18060
|
if (ucode !== "ENOENT") throw unlinkErr;
|
|
@@ -17811,7 +18079,7 @@ function acquireLock(lockPath = LOCK_FILE_PATH, nowMs = Date.now()) {
|
|
|
17811
18079
|
}
|
|
17812
18080
|
function releaseLock(lockPath = LOCK_FILE_PATH) {
|
|
17813
18081
|
try {
|
|
17814
|
-
|
|
18082
|
+
fs24.unlinkSync(lockPath);
|
|
17815
18083
|
} catch (err) {
|
|
17816
18084
|
const code = err.code;
|
|
17817
18085
|
if (code !== "ENOENT") throw err;
|
|
@@ -17829,19 +18097,19 @@ function formatRefusalMessage(result, lockPath = LOCK_FILE_PATH) {
|
|
|
17829
18097
|
}
|
|
17830
18098
|
|
|
17831
18099
|
// src/commands/upgrade-log.ts
|
|
17832
|
-
import * as
|
|
17833
|
-
import * as
|
|
17834
|
-
import * as
|
|
18100
|
+
import * as fs25 from "node:fs";
|
|
18101
|
+
import * as os15 from "node:os";
|
|
18102
|
+
import * as path29 from "node:path";
|
|
17835
18103
|
function getUpgradeLogPath() {
|
|
17836
|
-
const home = process.env["HOME"] ??
|
|
17837
|
-
return
|
|
18104
|
+
const home = process.env["HOME"] ?? os15.homedir();
|
|
18105
|
+
return path29.join(home, ".olam", "upgrade.log");
|
|
17838
18106
|
}
|
|
17839
18107
|
var UPGRADE_LOG_PATH = getUpgradeLogPath();
|
|
17840
18108
|
function appendUpgradeLog(row, logPath = getUpgradeLogPath()) {
|
|
17841
18109
|
try {
|
|
17842
|
-
|
|
18110
|
+
fs25.mkdirSync(path29.dirname(logPath), { recursive: true });
|
|
17843
18111
|
const line = JSON.stringify(row) + "\n";
|
|
17844
|
-
|
|
18112
|
+
fs25.appendFileSync(logPath, line, { mode: 420 });
|
|
17845
18113
|
} catch (err) {
|
|
17846
18114
|
process.stderr.write(
|
|
17847
18115
|
`[upgrade-log] failed to append: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -17850,10 +18118,10 @@ function appendUpgradeLog(row, logPath = getUpgradeLogPath()) {
|
|
|
17850
18118
|
}
|
|
17851
18119
|
}
|
|
17852
18120
|
function readUpgradeLog(limit = 10, logPath = getUpgradeLogPath()) {
|
|
17853
|
-
if (!
|
|
18121
|
+
if (!fs25.existsSync(logPath)) return [];
|
|
17854
18122
|
let raw;
|
|
17855
18123
|
try {
|
|
17856
|
-
raw =
|
|
18124
|
+
raw = fs25.readFileSync(logPath, "utf-8");
|
|
17857
18125
|
} catch (err) {
|
|
17858
18126
|
process.stderr.write(
|
|
17859
18127
|
`[upgrade-log] failed to read: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -17915,6 +18183,7 @@ function formatHistoryJson(rows) {
|
|
|
17915
18183
|
}
|
|
17916
18184
|
|
|
17917
18185
|
// src/commands/upgrade-history.ts
|
|
18186
|
+
init_output();
|
|
17918
18187
|
function parseHistoryOpts(raw) {
|
|
17919
18188
|
const rawLimit = raw.n;
|
|
17920
18189
|
const limit = typeof rawLimit === "number" ? rawLimit : typeof rawLimit === "string" ? Number.parseInt(rawLimit, 10) : 10;
|
|
@@ -17945,12 +18214,12 @@ init_protocol_version();
|
|
|
17945
18214
|
init_install_root();
|
|
17946
18215
|
var AUTH_HEALTH_URL2 = "http://127.0.0.1:9999/health";
|
|
17947
18216
|
function isNodeModulesInSync(cwd) {
|
|
17948
|
-
const lockPath =
|
|
17949
|
-
const markerPath =
|
|
17950
|
-
if (!
|
|
18217
|
+
const lockPath = path30.join(cwd, "package-lock.json");
|
|
18218
|
+
const markerPath = path30.join(cwd, "node_modules", ".package-lock.json");
|
|
18219
|
+
if (!fs26.existsSync(lockPath) || !fs26.existsSync(markerPath)) return false;
|
|
17951
18220
|
try {
|
|
17952
|
-
const lockStat =
|
|
17953
|
-
const markerStat =
|
|
18221
|
+
const lockStat = fs26.statSync(lockPath);
|
|
18222
|
+
const markerStat = fs26.statSync(markerPath);
|
|
17954
18223
|
return markerStat.mtimeMs >= lockStat.mtimeMs;
|
|
17955
18224
|
} catch {
|
|
17956
18225
|
return false;
|
|
@@ -17966,8 +18235,8 @@ function shouldSkipInstall(opts, cwd) {
|
|
|
17966
18235
|
return { skip: false };
|
|
17967
18236
|
}
|
|
17968
18237
|
function validateRepoRoot(cwd) {
|
|
17969
|
-
const marker =
|
|
17970
|
-
if (!
|
|
18238
|
+
const marker = path30.join(cwd, "packages/host-cp/compose.yaml");
|
|
18239
|
+
if (!fs26.existsSync(marker)) {
|
|
17971
18240
|
return {
|
|
17972
18241
|
ok: false,
|
|
17973
18242
|
error: `Not an olam repo root (expected ${marker}).
|
|
@@ -18274,7 +18543,8 @@ function formatVersionMismatch(targetSha, snapshot) {
|
|
|
18274
18543
|
}
|
|
18275
18544
|
return lines.join("\n");
|
|
18276
18545
|
}
|
|
18277
|
-
|
|
18546
|
+
var AUTH_HEALTH_TIMEOUT_MS = 6e4;
|
|
18547
|
+
async function waitForAuthHealthLocal(timeoutMs = AUTH_HEALTH_TIMEOUT_MS) {
|
|
18278
18548
|
const deadline = Date.now() + timeoutMs;
|
|
18279
18549
|
while (Date.now() < deadline) {
|
|
18280
18550
|
try {
|
|
@@ -18299,13 +18569,13 @@ async function recreateAuthService() {
|
|
|
18299
18569
|
});
|
|
18300
18570
|
const controller = new AuthContainerController();
|
|
18301
18571
|
controller.start();
|
|
18302
|
-
const healthy = await waitForAuthHealthLocal(
|
|
18572
|
+
const healthy = await waitForAuthHealthLocal();
|
|
18303
18573
|
const durationMs = Date.now() - start;
|
|
18304
18574
|
if (!healthy) {
|
|
18305
18575
|
return {
|
|
18306
18576
|
ok: false,
|
|
18307
18577
|
durationMs,
|
|
18308
|
-
error:
|
|
18578
|
+
error: `auth-service /health did not respond within ${AUTH_HEALTH_TIMEOUT_MS / 1e3}s after recreate`
|
|
18309
18579
|
};
|
|
18310
18580
|
}
|
|
18311
18581
|
return { ok: true, durationMs };
|
|
@@ -18318,9 +18588,9 @@ async function recreateAuthService() {
|
|
|
18318
18588
|
}
|
|
18319
18589
|
}
|
|
18320
18590
|
function readBundleHash(cwd) {
|
|
18321
|
-
const indexPath =
|
|
18322
|
-
if (!
|
|
18323
|
-
return extractBundleHash(
|
|
18591
|
+
const indexPath = path30.join(cwd, "packages/control-plane/public/index.html");
|
|
18592
|
+
if (!fs26.existsSync(indexPath)) return null;
|
|
18593
|
+
return extractBundleHash(fs26.readFileSync(indexPath, "utf-8"));
|
|
18324
18594
|
}
|
|
18325
18595
|
async function runUpgradePullByDigest(deps = {}) {
|
|
18326
18596
|
const docker2 = deps.docker ?? realDocker;
|
|
@@ -18418,12 +18688,12 @@ async function runUpgradePullByDigest(deps = {}) {
|
|
|
18418
18688
|
}
|
|
18419
18689
|
}
|
|
18420
18690
|
tagSpinner.succeed("Re-tagged 3 images to canonical refs");
|
|
18421
|
-
const composeFile = deps.composeFile ??
|
|
18691
|
+
const composeFile = deps.composeFile ?? path30.join(process.cwd(), "packages/host-cp/compose.yaml");
|
|
18422
18692
|
const authSecret = deps.authSecret ?? readAuthSecret2();
|
|
18423
18693
|
const composeRunner = deps.runComposeImpl ?? runCompose;
|
|
18424
18694
|
const composeSpinner = ora7("docker compose recreate host-cp").start();
|
|
18425
18695
|
const composeResult = composeRunner(
|
|
18426
|
-
["up", "-d", "--force-recreate", "host-cp"],
|
|
18696
|
+
["up", "-d", "--force-recreate", "--no-deps", "host-cp"],
|
|
18427
18697
|
composeFile,
|
|
18428
18698
|
buildComposeEnv(authSecret)
|
|
18429
18699
|
);
|
|
@@ -18467,9 +18737,12 @@ async function defaultRecreateAuthForUpgrade() {
|
|
|
18467
18737
|
});
|
|
18468
18738
|
const controller = new AuthContainerController();
|
|
18469
18739
|
controller.start();
|
|
18470
|
-
const healthy = await waitForAuthHealthLocal(
|
|
18740
|
+
const healthy = await waitForAuthHealthLocal();
|
|
18471
18741
|
if (!healthy) {
|
|
18472
|
-
return {
|
|
18742
|
+
return {
|
|
18743
|
+
ok: false,
|
|
18744
|
+
error: `auth-service /health did not respond within ${AUTH_HEALTH_TIMEOUT_MS / 1e3}s`
|
|
18745
|
+
};
|
|
18473
18746
|
}
|
|
18474
18747
|
return { ok: true };
|
|
18475
18748
|
} catch (err) {
|
|
@@ -18616,11 +18889,11 @@ manually inspect images with \`docker images olam-*:olam-rollback\`.`
|
|
|
18616
18889
|
}
|
|
18617
18890
|
printInfo("Rollback", swapResult.summary);
|
|
18618
18891
|
const cwd = process.cwd();
|
|
18619
|
-
const composeFile =
|
|
18892
|
+
const composeFile = path30.join(cwd, "packages/host-cp/compose.yaml");
|
|
18620
18893
|
const authSecret = readAuthSecret2();
|
|
18621
18894
|
process.stdout.write(` ${pc16.dim("docker compose recreate host-cp".padEnd(34))}`);
|
|
18622
18895
|
const composeStart = Date.now();
|
|
18623
|
-
const composeResult = runCompose(["up", "-d", "--force-recreate", "host-cp"], composeFile, buildComposeEnv(authSecret));
|
|
18896
|
+
const composeResult = runCompose(["up", "-d", "--force-recreate", "--no-deps", "host-cp"], composeFile, buildComposeEnv(authSecret));
|
|
18624
18897
|
const composeDur = `${((Date.now() - composeStart) / 1e3).toFixed(1)}s`;
|
|
18625
18898
|
process.stdout.write(`${composeResult.ok ? pc16.green("\u2713") : pc16.red("\u2717")} ${composeDur}
|
|
18626
18899
|
`);
|
|
@@ -18757,7 +19030,7 @@ ${buildResult.stderr}`);
|
|
|
18757
19030
|
return;
|
|
18758
19031
|
}
|
|
18759
19032
|
const authSecret = readAuthSecret2();
|
|
18760
|
-
const spaDir =
|
|
19033
|
+
const spaDir = path30.join(cwd, "packages/control-plane/app");
|
|
18761
19034
|
const spaResult = runStep2(
|
|
18762
19035
|
"vite build (SPA)",
|
|
18763
19036
|
"npx",
|
|
@@ -18890,7 +19163,7 @@ Recovery options:
|
|
|
18890
19163
|
return;
|
|
18891
19164
|
}
|
|
18892
19165
|
printInfo("Swap", swapResult.summary);
|
|
18893
|
-
const composeFile =
|
|
19166
|
+
const composeFile = path30.join(cwd, "packages/host-cp/compose.yaml");
|
|
18894
19167
|
process.stdout.write(` ${pc16.dim("docker compose recreate".padEnd(34))}`);
|
|
18895
19168
|
const composeStart = Date.now();
|
|
18896
19169
|
const composeResult = runCompose(
|
|
@@ -19023,10 +19296,12 @@ function registerUpgrade(program2) {
|
|
|
19023
19296
|
}
|
|
19024
19297
|
|
|
19025
19298
|
// src/commands/logs.ts
|
|
19299
|
+
init_host_cp();
|
|
19300
|
+
init_context();
|
|
19301
|
+
init_output();
|
|
19026
19302
|
import * as http3 from "node:http";
|
|
19027
19303
|
import pc17 from "picocolors";
|
|
19028
|
-
|
|
19029
|
-
var HOST_CP_PORT2 = 19e3;
|
|
19304
|
+
var HOST_CP_PORT3 = 19e3;
|
|
19030
19305
|
function colorLine(line) {
|
|
19031
19306
|
if (/\bERROR\b/.test(line)) return pc17.red(line);
|
|
19032
19307
|
if (/\bWARN\b/.test(line)) return pc17.yellow(line);
|
|
@@ -19073,7 +19348,7 @@ function registerLogs(program2) {
|
|
|
19073
19348
|
const tailLimit = Math.max(1, parseInt(opts.tail, 10) || 200);
|
|
19074
19349
|
const showService = opts.service === void 0;
|
|
19075
19350
|
const subPath = opts.service ? `/api/logs/${encodeURIComponent(opts.service)}` : "/api/logs";
|
|
19076
|
-
const url = `http://127.0.0.1:${
|
|
19351
|
+
const url = `http://127.0.0.1:${HOST_CP_PORT3}/api/world/${encodeURIComponent(worldId)}${subPath}`;
|
|
19077
19352
|
let lineCount = 0;
|
|
19078
19353
|
let done = false;
|
|
19079
19354
|
let resolveStream;
|
|
@@ -19150,6 +19425,7 @@ function registerLogs(program2) {
|
|
|
19150
19425
|
|
|
19151
19426
|
// src/commands/ps.ts
|
|
19152
19427
|
init_context();
|
|
19428
|
+
init_output();
|
|
19153
19429
|
import pc18 from "picocolors";
|
|
19154
19430
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
19155
19431
|
var SAFE_IDENT4 = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
@@ -19287,20 +19563,21 @@ ${pc18.dim(`world: ${worldId} sort: ${sortKey} refresh: 5s Ctrl-C to exit`)}
|
|
|
19287
19563
|
}
|
|
19288
19564
|
|
|
19289
19565
|
// src/commands/keys.ts
|
|
19290
|
-
|
|
19291
|
-
import * as
|
|
19292
|
-
import * as
|
|
19566
|
+
init_output();
|
|
19567
|
+
import * as fs27 from "node:fs";
|
|
19568
|
+
import * as os16 from "node:os";
|
|
19569
|
+
import * as path31 from "node:path";
|
|
19293
19570
|
import YAML4 from "yaml";
|
|
19294
19571
|
function olamHome2() {
|
|
19295
|
-
return process.env.OLAM_HOME ??
|
|
19572
|
+
return process.env.OLAM_HOME ?? path31.join(os16.homedir(), ".olam");
|
|
19296
19573
|
}
|
|
19297
19574
|
function keysFilePath() {
|
|
19298
|
-
return
|
|
19575
|
+
return path31.join(olamHome2(), "keys.yaml");
|
|
19299
19576
|
}
|
|
19300
19577
|
function readKeysFile() {
|
|
19301
19578
|
const filePath = keysFilePath();
|
|
19302
|
-
if (!
|
|
19303
|
-
const raw =
|
|
19579
|
+
if (!fs27.existsSync(filePath)) return null;
|
|
19580
|
+
const raw = fs27.readFileSync(filePath, "utf-8").trim();
|
|
19304
19581
|
if (raw.length === 0) return null;
|
|
19305
19582
|
try {
|
|
19306
19583
|
const parsed = YAML4.parse(raw);
|
|
@@ -19316,13 +19593,13 @@ function readKeysFile() {
|
|
|
19316
19593
|
}
|
|
19317
19594
|
function writeKeysFile(keys) {
|
|
19318
19595
|
const dir = olamHome2();
|
|
19319
|
-
if (!
|
|
19320
|
-
|
|
19596
|
+
if (!fs27.existsSync(dir)) {
|
|
19597
|
+
fs27.mkdirSync(dir, { recursive: true });
|
|
19321
19598
|
}
|
|
19322
19599
|
const filePath = keysFilePath();
|
|
19323
19600
|
const content = YAML4.stringify(keys);
|
|
19324
|
-
|
|
19325
|
-
|
|
19601
|
+
fs27.writeFileSync(filePath, content, { encoding: "utf-8", mode: 384 });
|
|
19602
|
+
fs27.chmodSync(filePath, 384);
|
|
19326
19603
|
}
|
|
19327
19604
|
function redact(value) {
|
|
19328
19605
|
if (value.length <= 8) return value + "...";
|
|
@@ -19365,7 +19642,7 @@ function registerKeys(program2) {
|
|
|
19365
19642
|
}
|
|
19366
19643
|
const { [key]: _removed, ...rest } = existing;
|
|
19367
19644
|
if (Object.keys(rest).length === 0) {
|
|
19368
|
-
|
|
19645
|
+
fs27.unlinkSync(keysFilePath());
|
|
19369
19646
|
} else {
|
|
19370
19647
|
writeKeysFile(rest);
|
|
19371
19648
|
}
|
|
@@ -19388,26 +19665,26 @@ function registerKeys(program2) {
|
|
|
19388
19665
|
}
|
|
19389
19666
|
|
|
19390
19667
|
// src/commands/world-snapshot.ts
|
|
19391
|
-
import * as
|
|
19392
|
-
import * as
|
|
19668
|
+
import * as fs29 from "node:fs";
|
|
19669
|
+
import * as path33 from "node:path";
|
|
19393
19670
|
import { execSync as execSync9 } from "node:child_process";
|
|
19394
19671
|
import pc19 from "picocolors";
|
|
19395
19672
|
|
|
19396
19673
|
// ../core/src/world/snapshot.ts
|
|
19397
19674
|
import * as crypto6 from "node:crypto";
|
|
19398
|
-
import * as
|
|
19399
|
-
import * as
|
|
19400
|
-
import * as
|
|
19675
|
+
import * as fs28 from "node:fs";
|
|
19676
|
+
import * as os17 from "node:os";
|
|
19677
|
+
import * as path32 from "node:path";
|
|
19401
19678
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
19402
19679
|
function snapshotsDir() {
|
|
19403
|
-
return process.env["OLAM_SNAPSHOTS_DIR"] ??
|
|
19680
|
+
return process.env["OLAM_SNAPSHOTS_DIR"] ?? path32.join(os17.homedir(), ".olam", "snapshots");
|
|
19404
19681
|
}
|
|
19405
19682
|
function snapshotKindDir(worldId, kind) {
|
|
19406
|
-
return
|
|
19683
|
+
return path32.join(snapshotsDir(), worldId, kind);
|
|
19407
19684
|
}
|
|
19408
19685
|
function snapshotTarPath(worldId, kind, repoName, hash) {
|
|
19409
19686
|
const base = repoName ? `${repoName}-${hash}` : hash;
|
|
19410
|
-
return
|
|
19687
|
+
return path32.join(snapshotKindDir(worldId, kind), `${base}.tar.gz`);
|
|
19411
19688
|
}
|
|
19412
19689
|
function manifestPath(tarPath) {
|
|
19413
19690
|
return tarPath.replace(/\.tar\.gz$/, ".manifest.json");
|
|
@@ -19424,16 +19701,16 @@ function hashBuffers(entries) {
|
|
|
19424
19701
|
return hash.digest("hex").slice(0, 12);
|
|
19425
19702
|
}
|
|
19426
19703
|
function computeGemsFingerprint(repoDir) {
|
|
19427
|
-
const lockfile =
|
|
19428
|
-
if (!
|
|
19429
|
-
return hashBuffers([{ path: "Gemfile.lock", content:
|
|
19704
|
+
const lockfile = path32.join(repoDir, "Gemfile.lock");
|
|
19705
|
+
if (!fs28.existsSync(lockfile)) return null;
|
|
19706
|
+
return hashBuffers([{ path: "Gemfile.lock", content: fs28.readFileSync(lockfile) }]);
|
|
19430
19707
|
}
|
|
19431
19708
|
function computeNodeFingerprint(repoDir) {
|
|
19432
19709
|
const candidates = ["yarn.lock", "pnpm-lock.yaml", "package-lock.json"];
|
|
19433
19710
|
for (const name of candidates) {
|
|
19434
|
-
const lockfile =
|
|
19435
|
-
if (
|
|
19436
|
-
return hashBuffers([{ path: name, content:
|
|
19711
|
+
const lockfile = path32.join(repoDir, name);
|
|
19712
|
+
if (fs28.existsSync(lockfile)) {
|
|
19713
|
+
return hashBuffers([{ path: name, content: fs28.readFileSync(lockfile) }]);
|
|
19437
19714
|
}
|
|
19438
19715
|
}
|
|
19439
19716
|
return null;
|
|
@@ -19443,59 +19720,59 @@ function computePgFingerprint(repoDirs) {
|
|
|
19443
19720
|
const entries = [];
|
|
19444
19721
|
for (const repoDir of repoDirs) {
|
|
19445
19722
|
for (const pattern of patterns) {
|
|
19446
|
-
const filePath =
|
|
19447
|
-
if (
|
|
19448
|
-
entries.push({ path: filePath, content:
|
|
19723
|
+
const filePath = path32.join(repoDir, pattern);
|
|
19724
|
+
if (fs28.existsSync(filePath)) {
|
|
19725
|
+
entries.push({ path: filePath, content: fs28.readFileSync(filePath) });
|
|
19449
19726
|
}
|
|
19450
19727
|
}
|
|
19451
19728
|
}
|
|
19452
19729
|
return entries.length > 0 ? hashBuffers(entries) : null;
|
|
19453
19730
|
}
|
|
19454
19731
|
function packTarball(srcDir, destPath, opts = {}) {
|
|
19455
|
-
|
|
19732
|
+
fs28.mkdirSync(path32.dirname(destPath), { recursive: true });
|
|
19456
19733
|
const tmp = `${destPath}.tmp`;
|
|
19457
19734
|
const args = [];
|
|
19458
19735
|
if (opts.followSymlinks) args.push("-h");
|
|
19459
19736
|
args.push("-czf", tmp, "-C", srcDir, ".");
|
|
19460
19737
|
try {
|
|
19461
19738
|
execFileSync4("tar", args, { stdio: "pipe" });
|
|
19462
|
-
|
|
19739
|
+
fs28.renameSync(tmp, destPath);
|
|
19463
19740
|
} catch (err) {
|
|
19464
19741
|
try {
|
|
19465
|
-
|
|
19742
|
+
fs28.rmSync(tmp, { force: true });
|
|
19466
19743
|
} catch {
|
|
19467
19744
|
}
|
|
19468
19745
|
throw err;
|
|
19469
19746
|
}
|
|
19470
19747
|
}
|
|
19471
19748
|
function writeManifest(manifest, tarPath) {
|
|
19472
|
-
|
|
19749
|
+
fs28.writeFileSync(manifestPath(tarPath), JSON.stringify(manifest, null, 2), "utf-8");
|
|
19473
19750
|
}
|
|
19474
19751
|
function readManifest(tarPath) {
|
|
19475
19752
|
const mPath = manifestPath(tarPath);
|
|
19476
|
-
if (!
|
|
19753
|
+
if (!fs28.existsSync(mPath)) return null;
|
|
19477
19754
|
try {
|
|
19478
|
-
return JSON.parse(
|
|
19755
|
+
return JSON.parse(fs28.readFileSync(mPath, "utf-8"));
|
|
19479
19756
|
} catch {
|
|
19480
19757
|
return null;
|
|
19481
19758
|
}
|
|
19482
19759
|
}
|
|
19483
19760
|
function listSnapshots(worldIdFilter) {
|
|
19484
19761
|
const root = snapshotsDir();
|
|
19485
|
-
if (!
|
|
19762
|
+
if (!fs28.existsSync(root)) return [];
|
|
19486
19763
|
const now = Date.now();
|
|
19487
19764
|
const results = [];
|
|
19488
|
-
const worlds = worldIdFilter ? [worldIdFilter] :
|
|
19765
|
+
const worlds = worldIdFilter ? [worldIdFilter] : fs28.readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
19489
19766
|
for (const worldId of worlds) {
|
|
19490
|
-
const worldDir =
|
|
19491
|
-
if (!
|
|
19767
|
+
const worldDir = path32.join(root, worldId);
|
|
19768
|
+
if (!fs28.existsSync(worldDir) || !fs28.statSync(worldDir).isDirectory()) continue;
|
|
19492
19769
|
for (const kind of ["gems", "node", "pg"]) {
|
|
19493
|
-
const kindDir =
|
|
19494
|
-
if (!
|
|
19495
|
-
const tarballs =
|
|
19770
|
+
const kindDir = path32.join(worldDir, kind);
|
|
19771
|
+
if (!fs28.existsSync(kindDir)) continue;
|
|
19772
|
+
const tarballs = fs28.readdirSync(kindDir).filter((f) => f.endsWith(".tar.gz"));
|
|
19496
19773
|
for (const tarFile of tarballs) {
|
|
19497
|
-
const tarPath =
|
|
19498
|
-
const stat =
|
|
19774
|
+
const tarPath = path32.join(kindDir, tarFile);
|
|
19775
|
+
const stat = fs28.statSync(tarPath);
|
|
19499
19776
|
const manifest = readManifest(tarPath);
|
|
19500
19777
|
if (!manifest) continue;
|
|
19501
19778
|
results.push({ manifest, tarPath, ageMs: now - stat.mtimeMs });
|
|
@@ -19505,9 +19782,19 @@ function listSnapshots(worldIdFilter) {
|
|
|
19505
19782
|
return results.sort((a, b) => a.manifest.createdAt.localeCompare(b.manifest.createdAt));
|
|
19506
19783
|
}
|
|
19507
19784
|
|
|
19785
|
+
// src/commands/world-snapshot.ts
|
|
19786
|
+
init_output();
|
|
19787
|
+
|
|
19788
|
+
// src/commands/world.ts
|
|
19789
|
+
function getOrCreateWorldCommand(program2) {
|
|
19790
|
+
const existing = program2.commands.find((c) => c.name() === "world");
|
|
19791
|
+
if (existing) return existing;
|
|
19792
|
+
return program2.command("world").description("World management subcommands");
|
|
19793
|
+
}
|
|
19794
|
+
|
|
19508
19795
|
// src/commands/world-snapshot.ts
|
|
19509
19796
|
function registerWorldSnapshot(program2) {
|
|
19510
|
-
const world = program2
|
|
19797
|
+
const world = getOrCreateWorldCommand(program2);
|
|
19511
19798
|
const snapshot = world.command("snapshot").description("Manage world snapshots for fast boot");
|
|
19512
19799
|
snapshot.command("create <worldId>").description("Capture installed state from a running world into tarballs").option(
|
|
19513
19800
|
"--kind <kind>",
|
|
@@ -19574,17 +19861,17 @@ function resolveKinds(arg) {
|
|
|
19574
19861
|
return [];
|
|
19575
19862
|
}
|
|
19576
19863
|
async function captureGems(worldId, workspacePath, repo) {
|
|
19577
|
-
const repoDir =
|
|
19864
|
+
const repoDir = path33.join(workspacePath, repo);
|
|
19578
19865
|
const fingerprint = computeGemsFingerprint(repoDir);
|
|
19579
19866
|
if (!fingerprint) {
|
|
19580
19867
|
return { ok: false, tarPath: "", msg: "no Gemfile.lock \u2014 layer does not apply" };
|
|
19581
19868
|
}
|
|
19582
19869
|
const tarPath = snapshotTarPath(worldId, "gems", repo, fingerprint);
|
|
19583
|
-
const vendorBundle =
|
|
19584
|
-
if (
|
|
19870
|
+
const vendorBundle = path33.join(repoDir, "vendor", "bundle");
|
|
19871
|
+
if (fs29.existsSync(vendorBundle)) {
|
|
19585
19872
|
try {
|
|
19586
19873
|
packTarball(vendorBundle, tarPath);
|
|
19587
|
-
const stat =
|
|
19874
|
+
const stat = fs29.statSync(tarPath);
|
|
19588
19875
|
const manifest = {
|
|
19589
19876
|
kind: "gems",
|
|
19590
19877
|
worldId,
|
|
@@ -19617,10 +19904,10 @@ async function captureGems(worldId, workspacePath, repo) {
|
|
|
19617
19904
|
`docker exec ${containerName} sh -c 'mkdir -p "$(dirname ${tmpTar})" && tar -czf ${tmpTar}.tmp -C ${bundlePath} . && mv ${tmpTar}.tmp ${tmpTar}'`,
|
|
19618
19905
|
{ stdio: "pipe", timeout: 12e4 }
|
|
19619
19906
|
);
|
|
19620
|
-
|
|
19907
|
+
fs29.mkdirSync(path33.dirname(tarPath), { recursive: true });
|
|
19621
19908
|
execSync9(`docker cp ${containerName}:${tmpTar} "${tarPath}"`, { stdio: "pipe", timeout: 12e4 });
|
|
19622
19909
|
execSync9(`docker exec ${containerName} rm -f ${tmpTar}`, { stdio: "pipe" });
|
|
19623
|
-
const stat =
|
|
19910
|
+
const stat = fs29.statSync(tarPath);
|
|
19624
19911
|
const manifest = {
|
|
19625
19912
|
kind: "gems",
|
|
19626
19913
|
worldId,
|
|
@@ -19637,19 +19924,19 @@ async function captureGems(worldId, workspacePath, repo) {
|
|
|
19637
19924
|
}
|
|
19638
19925
|
}
|
|
19639
19926
|
async function captureNode(worldId, workspacePath, repo) {
|
|
19640
|
-
const repoDir =
|
|
19927
|
+
const repoDir = path33.join(workspacePath, repo);
|
|
19641
19928
|
const fingerprint = computeNodeFingerprint(repoDir);
|
|
19642
19929
|
if (!fingerprint) {
|
|
19643
19930
|
return { ok: false, tarPath: "", msg: "no lockfile \u2014 layer does not apply" };
|
|
19644
19931
|
}
|
|
19645
|
-
const nodeModules =
|
|
19646
|
-
if (!
|
|
19932
|
+
const nodeModules = path33.join(repoDir, "node_modules");
|
|
19933
|
+
if (!fs29.existsSync(nodeModules)) {
|
|
19647
19934
|
return { ok: false, tarPath: "", msg: "node_modules not installed yet" };
|
|
19648
19935
|
}
|
|
19649
19936
|
const tarPath = snapshotTarPath(worldId, "node", repo, fingerprint);
|
|
19650
19937
|
try {
|
|
19651
19938
|
packTarball(nodeModules, tarPath);
|
|
19652
|
-
const stat =
|
|
19939
|
+
const stat = fs29.statSync(tarPath);
|
|
19653
19940
|
const manifest = {
|
|
19654
19941
|
kind: "node",
|
|
19655
19942
|
worldId,
|
|
@@ -19666,7 +19953,7 @@ async function captureNode(worldId, workspacePath, repo) {
|
|
|
19666
19953
|
}
|
|
19667
19954
|
}
|
|
19668
19955
|
async function capturePg(worldId, workspacePath, repoNames) {
|
|
19669
|
-
const repoDirs = repoNames.map((r) =>
|
|
19956
|
+
const repoDirs = repoNames.map((r) => path33.join(workspacePath, r));
|
|
19670
19957
|
const fingerprint = computePgFingerprint(repoDirs);
|
|
19671
19958
|
if (!fingerprint) {
|
|
19672
19959
|
return { ok: false, tarPath: "", msg: "no Gemfile.lock / schema.rb \u2014 layer does not apply" };
|
|
@@ -19681,13 +19968,13 @@ async function capturePg(worldId, workspacePath, repoNames) {
|
|
|
19681
19968
|
}
|
|
19682
19969
|
try {
|
|
19683
19970
|
execSync9(`docker stop ${containerName}`, { stdio: "pipe", timeout: 3e4 });
|
|
19684
|
-
|
|
19971
|
+
fs29.mkdirSync(path33.dirname(tarPath), { recursive: true });
|
|
19685
19972
|
execSync9(
|
|
19686
|
-
`docker run --rm -v "${volumeName2}:/pgdata:ro" -v "${
|
|
19973
|
+
`docker run --rm -v "${volumeName2}:/pgdata:ro" -v "${path33.dirname(tarPath)}:/dest" alpine sh -c 'tar -czf /dest/${path33.basename(tarPath)}.tmp -C /pgdata . && mv /dest/${path33.basename(tarPath)}.tmp /dest/${path33.basename(tarPath)}'`,
|
|
19687
19974
|
{ stdio: "pipe", timeout: 18e4 }
|
|
19688
19975
|
);
|
|
19689
19976
|
execSync9(`docker start ${containerName}`, { stdio: "pipe", timeout: 3e4 });
|
|
19690
|
-
const stat =
|
|
19977
|
+
const stat = fs29.statSync(tarPath);
|
|
19691
19978
|
const manifest = {
|
|
19692
19979
|
kind: "pg",
|
|
19693
19980
|
worldId,
|
|
@@ -19761,35 +20048,36 @@ function formatAge2(ms) {
|
|
|
19761
20048
|
|
|
19762
20049
|
// src/commands/refresh.ts
|
|
19763
20050
|
init_context();
|
|
19764
|
-
|
|
19765
|
-
import * as
|
|
19766
|
-
import * as
|
|
20051
|
+
init_output();
|
|
20052
|
+
import * as fs31 from "node:fs";
|
|
20053
|
+
import * as os18 from "node:os";
|
|
20054
|
+
import * as path35 from "node:path";
|
|
19767
20055
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
19768
20056
|
import ora8 from "ora";
|
|
19769
20057
|
|
|
19770
20058
|
// src/commands/refresh-helpers.ts
|
|
19771
|
-
import * as
|
|
19772
|
-
import * as
|
|
20059
|
+
import * as fs30 from "node:fs";
|
|
20060
|
+
import * as path34 from "node:path";
|
|
19773
20061
|
function collectCpSourceFiles(standaloneDir) {
|
|
19774
|
-
if (!
|
|
20062
|
+
if (!fs30.existsSync(standaloneDir)) {
|
|
19775
20063
|
throw new Error(`CP standalone dir not found: ${standaloneDir}`);
|
|
19776
20064
|
}
|
|
19777
20065
|
const entries = [];
|
|
19778
|
-
const topLevel =
|
|
19779
|
-
const stat =
|
|
20066
|
+
const topLevel = fs30.readdirSync(standaloneDir).filter((f) => {
|
|
20067
|
+
const stat = fs30.statSync(path34.join(standaloneDir, f));
|
|
19780
20068
|
return stat.isFile() && f.endsWith(".mjs") && !f.endsWith(".test.mjs");
|
|
19781
20069
|
}).sort();
|
|
19782
20070
|
for (const f of topLevel) {
|
|
19783
|
-
entries.push({ srcPath:
|
|
20071
|
+
entries.push({ srcPath: path34.join(standaloneDir, f), destRelPath: f });
|
|
19784
20072
|
}
|
|
19785
|
-
const libDir =
|
|
19786
|
-
if (
|
|
19787
|
-
const libFiles =
|
|
19788
|
-
const stat =
|
|
20073
|
+
const libDir = path34.join(standaloneDir, "lib");
|
|
20074
|
+
if (fs30.existsSync(libDir) && fs30.statSync(libDir).isDirectory()) {
|
|
20075
|
+
const libFiles = fs30.readdirSync(libDir).filter((f) => {
|
|
20076
|
+
const stat = fs30.statSync(path34.join(libDir, f));
|
|
19789
20077
|
return stat.isFile() && f.endsWith(".mjs") && !f.endsWith(".test.mjs");
|
|
19790
20078
|
}).sort();
|
|
19791
20079
|
for (const f of libFiles) {
|
|
19792
|
-
entries.push({ srcPath:
|
|
20080
|
+
entries.push({ srcPath: path34.join(libDir, f), destRelPath: `lib/${f}` });
|
|
19793
20081
|
}
|
|
19794
20082
|
}
|
|
19795
20083
|
return entries;
|
|
@@ -19847,16 +20135,16 @@ async function refreshWorld(worldId, portOffset, standaloneDir, opts) {
|
|
|
19847
20135
|
error: err instanceof Error ? err.message : String(err)
|
|
19848
20136
|
};
|
|
19849
20137
|
}
|
|
19850
|
-
const stagingDir =
|
|
19851
|
-
|
|
20138
|
+
const stagingDir = fs31.mkdtempSync(
|
|
20139
|
+
path35.join(os18.tmpdir(), `olam-refresh-${worldId}-`)
|
|
19852
20140
|
);
|
|
19853
20141
|
try {
|
|
19854
20142
|
const hasLib = entries.some((e) => e.destRelPath.startsWith("lib/"));
|
|
19855
20143
|
if (hasLib) {
|
|
19856
|
-
|
|
20144
|
+
fs31.mkdirSync(path35.join(stagingDir, "lib"), { recursive: true });
|
|
19857
20145
|
}
|
|
19858
20146
|
for (const { srcPath, destRelPath } of entries) {
|
|
19859
|
-
|
|
20147
|
+
fs31.copyFileSync(srcPath, path35.join(stagingDir, destRelPath));
|
|
19860
20148
|
}
|
|
19861
20149
|
const cpResult = docker([
|
|
19862
20150
|
"cp",
|
|
@@ -19871,7 +20159,7 @@ async function refreshWorld(worldId, portOffset, standaloneDir, opts) {
|
|
|
19871
20159
|
};
|
|
19872
20160
|
}
|
|
19873
20161
|
} finally {
|
|
19874
|
-
|
|
20162
|
+
fs31.rmSync(stagingDir, { recursive: true, force: true });
|
|
19875
20163
|
}
|
|
19876
20164
|
if (opts.restart) {
|
|
19877
20165
|
const restartResult = docker([
|
|
@@ -19908,11 +20196,11 @@ function registerRefresh(program2) {
|
|
|
19908
20196
|
process.exitCode = 1;
|
|
19909
20197
|
return;
|
|
19910
20198
|
}
|
|
19911
|
-
const standaloneDir =
|
|
20199
|
+
const standaloneDir = path35.join(
|
|
19912
20200
|
process.cwd(),
|
|
19913
20201
|
"packages/control-plane/standalone"
|
|
19914
20202
|
);
|
|
19915
|
-
if (!
|
|
20203
|
+
if (!fs31.existsSync(standaloneDir)) {
|
|
19916
20204
|
printError(
|
|
19917
20205
|
`CP standalone source not found at ${standaloneDir}.
|
|
19918
20206
|
Run \`olam refresh\` from the olam repo root.`
|
|
@@ -19991,9 +20279,9 @@ Run \`olam refresh\` from the olam repo root.`
|
|
|
19991
20279
|
}
|
|
19992
20280
|
|
|
19993
20281
|
// src/commands/diagnose.ts
|
|
19994
|
-
import * as
|
|
19995
|
-
import * as
|
|
19996
|
-
import * as
|
|
20282
|
+
import * as fs32 from "node:fs";
|
|
20283
|
+
import * as os19 from "node:os";
|
|
20284
|
+
import * as path36 from "node:path";
|
|
19997
20285
|
import { execFileSync as execFileSync5, execSync as execSync10 } from "node:child_process";
|
|
19998
20286
|
import pc20 from "picocolors";
|
|
19999
20287
|
|
|
@@ -20028,9 +20316,9 @@ function stripSecrets(input) {
|
|
|
20028
20316
|
}
|
|
20029
20317
|
|
|
20030
20318
|
// src/commands/diagnose.ts
|
|
20031
|
-
var DIAGNOSTICS_DIR =
|
|
20032
|
-
var LOG_DIR =
|
|
20033
|
-
var CACHE_DIR =
|
|
20319
|
+
var DIAGNOSTICS_DIR = path36.join(os19.homedir(), ".olam", "diagnostics");
|
|
20320
|
+
var LOG_DIR = path36.join(os19.homedir(), ".olam", "log");
|
|
20321
|
+
var CACHE_DIR = path36.join(os19.homedir(), ".olam", "cache");
|
|
20034
20322
|
var LOG_TAIL_LINES = 200;
|
|
20035
20323
|
function safeExec(cmd) {
|
|
20036
20324
|
try {
|
|
@@ -20044,10 +20332,10 @@ function defaultZip(zipPath, files) {
|
|
|
20044
20332
|
}
|
|
20045
20333
|
async function buildDiagnosticsZip(_exec = (cmd) => safeExec(cmd), _outDir = DIAGNOSTICS_DIR, _logDir = LOG_DIR, _zip = defaultZip) {
|
|
20046
20334
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 23);
|
|
20047
|
-
const zipPath =
|
|
20048
|
-
const tmpDir =
|
|
20335
|
+
const zipPath = path36.join(_outDir, `olam-diag-${ts}.zip`);
|
|
20336
|
+
const tmpDir = fs32.mkdtempSync(path36.join(os19.tmpdir(), "olam-diag-"));
|
|
20049
20337
|
try {
|
|
20050
|
-
|
|
20338
|
+
fs32.mkdirSync(_outDir, { recursive: true });
|
|
20051
20339
|
const entries = [];
|
|
20052
20340
|
const version = process.env["OLAM_CLI_VERSION"] ?? "unknown";
|
|
20053
20341
|
const nodeVersion = process.version;
|
|
@@ -20058,19 +20346,19 @@ platform: ${platform}
|
|
|
20058
20346
|
`;
|
|
20059
20347
|
_writeEntry(tmpDir, "version.txt", stripSecrets(versionContent), entries);
|
|
20060
20348
|
const osContent = [
|
|
20061
|
-
`os: ${
|
|
20062
|
-
`arch: ${
|
|
20063
|
-
`uptime: ${
|
|
20349
|
+
`os: ${os19.type()} ${os19.release()}`,
|
|
20350
|
+
`arch: ${os19.arch()}`,
|
|
20351
|
+
`uptime: ${os19.uptime()}s`
|
|
20064
20352
|
].join("\n") + "\n";
|
|
20065
20353
|
_writeEntry(tmpDir, "os-info.txt", stripSecrets(osContent), entries);
|
|
20066
|
-
const depsFile =
|
|
20067
|
-
if (
|
|
20068
|
-
const deps =
|
|
20354
|
+
const depsFile = path36.join(CACHE_DIR, "deps.json");
|
|
20355
|
+
if (fs32.existsSync(depsFile)) {
|
|
20356
|
+
const deps = fs32.readFileSync(depsFile, "utf-8");
|
|
20069
20357
|
_writeEntry(tmpDir, "deps.json", stripSecrets(deps), entries);
|
|
20070
20358
|
}
|
|
20071
20359
|
const latestLog = _latestLog(_logDir);
|
|
20072
20360
|
if (latestLog) {
|
|
20073
|
-
const lines =
|
|
20361
|
+
const lines = fs32.readFileSync(latestLog, "utf-8").split("\n");
|
|
20074
20362
|
const tail = lines.slice(-LOG_TAIL_LINES).join("\n");
|
|
20075
20363
|
_writeEntry(tmpDir, "log-tail.txt", stripSecrets(tail), entries);
|
|
20076
20364
|
}
|
|
@@ -20082,38 +20370,38 @@ platform: ${platform}
|
|
|
20082
20370
|
if (authAudit) {
|
|
20083
20371
|
_writeEntry(tmpDir, "audit-auth-callers.txt", stripSecrets(authAudit), entries);
|
|
20084
20372
|
}
|
|
20085
|
-
const fileArgs = entries.map((e) =>
|
|
20373
|
+
const fileArgs = entries.map((e) => path36.join(tmpDir, e));
|
|
20086
20374
|
try {
|
|
20087
20375
|
_zip(zipPath, fileArgs);
|
|
20088
20376
|
} catch (err) {
|
|
20089
20377
|
throw new Error(`zip command produced no output file. zip stderr: ${err.message}`);
|
|
20090
20378
|
}
|
|
20091
|
-
if (!
|
|
20379
|
+
if (!fs32.existsSync(zipPath)) {
|
|
20092
20380
|
throw new Error("zip command produced no output file.");
|
|
20093
20381
|
}
|
|
20094
20382
|
return { zipPath, entries };
|
|
20095
20383
|
} finally {
|
|
20096
20384
|
try {
|
|
20097
|
-
|
|
20385
|
+
fs32.rmSync(tmpDir, { recursive: true, force: true });
|
|
20098
20386
|
} catch {
|
|
20099
20387
|
}
|
|
20100
20388
|
}
|
|
20101
20389
|
}
|
|
20102
20390
|
function _writeEntry(dir, name, content, entries) {
|
|
20103
|
-
|
|
20391
|
+
fs32.writeFileSync(path36.join(dir, name), content, { mode: 420 });
|
|
20104
20392
|
entries.push(name);
|
|
20105
20393
|
}
|
|
20106
20394
|
function _latestLog(logDir) {
|
|
20107
|
-
if (!
|
|
20108
|
-
const files =
|
|
20109
|
-
return files.length > 0 ?
|
|
20395
|
+
if (!fs32.existsSync(logDir)) return null;
|
|
20396
|
+
const files = fs32.readdirSync(logDir).filter((f) => f.startsWith("host-")).sort().reverse();
|
|
20397
|
+
return files.length > 0 ? path36.join(logDir, files[0]) : null;
|
|
20110
20398
|
}
|
|
20111
20399
|
async function buildTelemetryPayload() {
|
|
20112
20400
|
const channel = "stable";
|
|
20113
|
-
const manifestFile =
|
|
20401
|
+
const manifestFile = path36.join(CACHE_DIR, "manifest.json");
|
|
20114
20402
|
let manifestAgeHours = null;
|
|
20115
|
-
if (
|
|
20116
|
-
const mtime =
|
|
20403
|
+
if (fs32.existsSync(manifestFile)) {
|
|
20404
|
+
const mtime = fs32.statSync(manifestFile).mtime.getTime();
|
|
20117
20405
|
manifestAgeHours = Math.round((Date.now() - mtime) / 36e5);
|
|
20118
20406
|
}
|
|
20119
20407
|
return {
|
|
@@ -20156,24 +20444,24 @@ function registerDiagnose(program2) {
|
|
|
20156
20444
|
}
|
|
20157
20445
|
|
|
20158
20446
|
// src/commands/update.ts
|
|
20159
|
-
import * as
|
|
20160
|
-
import * as
|
|
20161
|
-
import * as
|
|
20447
|
+
import * as fs35 from "node:fs";
|
|
20448
|
+
import * as os21 from "node:os";
|
|
20449
|
+
import * as path39 from "node:path";
|
|
20162
20450
|
import { execSync as execSync11 } from "node:child_process";
|
|
20163
20451
|
import pc21 from "picocolors";
|
|
20164
20452
|
|
|
20165
20453
|
// src/lib/symlink-reconcile.ts
|
|
20166
|
-
import * as
|
|
20167
|
-
import * as
|
|
20454
|
+
import * as fs33 from "node:fs";
|
|
20455
|
+
import * as path37 from "node:path";
|
|
20168
20456
|
var realFs = {
|
|
20169
|
-
readdirSync: (p) =>
|
|
20170
|
-
existsSync: (p) =>
|
|
20171
|
-
lstatSync: (p) =>
|
|
20172
|
-
readlinkSync: (p) =>
|
|
20173
|
-
symlinkSync: (t, l) =>
|
|
20174
|
-
unlinkSync: (p) =>
|
|
20457
|
+
readdirSync: (p) => fs33.readdirSync(p),
|
|
20458
|
+
existsSync: (p) => fs33.existsSync(p),
|
|
20459
|
+
lstatSync: (p) => fs33.lstatSync(p),
|
|
20460
|
+
readlinkSync: (p) => fs33.readlinkSync(p),
|
|
20461
|
+
symlinkSync: (t, l) => fs33.symlinkSync(t, l),
|
|
20462
|
+
unlinkSync: (p) => fs33.unlinkSync(p),
|
|
20175
20463
|
mkdirSync: (p, o) => {
|
|
20176
|
-
|
|
20464
|
+
fs33.mkdirSync(p, o);
|
|
20177
20465
|
}
|
|
20178
20466
|
};
|
|
20179
20467
|
function reconcileSkillSymlinks(npmSkillsDir, claudeSkillsDir, _fs = realFs) {
|
|
@@ -20183,8 +20471,8 @@ function reconcileSkillSymlinks(npmSkillsDir, claudeSkillsDir, _fs = realFs) {
|
|
|
20183
20471
|
_fs.mkdirSync(claudeSkillsDir, { recursive: true });
|
|
20184
20472
|
const sourceSkills = _fs.existsSync(npmSkillsDir) ? _fs.readdirSync(npmSkillsDir).filter((d) => d.startsWith("olam-")) : [];
|
|
20185
20473
|
for (const skill of sourceSkills) {
|
|
20186
|
-
const linkPath =
|
|
20187
|
-
const target =
|
|
20474
|
+
const linkPath = path37.join(claudeSkillsDir, skill);
|
|
20475
|
+
const target = path37.join(npmSkillsDir, skill);
|
|
20188
20476
|
if (!_fs.existsSync(linkPath)) {
|
|
20189
20477
|
try {
|
|
20190
20478
|
_fs.symlinkSync(target, linkPath);
|
|
@@ -20197,7 +20485,7 @@ function reconcileSkillSymlinks(npmSkillsDir, claudeSkillsDir, _fs = realFs) {
|
|
|
20197
20485
|
}
|
|
20198
20486
|
const deployedEntries = _fs.existsSync(claudeSkillsDir) ? _fs.readdirSync(claudeSkillsDir).filter((d) => d.startsWith("olam-")) : [];
|
|
20199
20487
|
for (const entry of deployedEntries) {
|
|
20200
|
-
const linkPath =
|
|
20488
|
+
const linkPath = path37.join(claudeSkillsDir, entry);
|
|
20201
20489
|
let isSymlink = false;
|
|
20202
20490
|
try {
|
|
20203
20491
|
isSymlink = _fs.lstatSync(linkPath).isSymbolicLink();
|
|
@@ -20222,9 +20510,9 @@ function reconcileSkillSymlinks(npmSkillsDir, claudeSkillsDir, _fs = realFs) {
|
|
|
20222
20510
|
|
|
20223
20511
|
// src/commands/update.ts
|
|
20224
20512
|
var PACKAGE_NAME = "@pleri/olam-cli";
|
|
20225
|
-
var CACHE_DIR2 =
|
|
20226
|
-
var LOG_DIR2 =
|
|
20227
|
-
var LAST_STABLE_FILE =
|
|
20513
|
+
var CACHE_DIR2 = path39.join(os21.homedir(), ".olam", "cache");
|
|
20514
|
+
var LOG_DIR2 = path39.join(os21.homedir(), ".olam", "log");
|
|
20515
|
+
var LAST_STABLE_FILE = path39.join(CACHE_DIR2, "last-stable.txt");
|
|
20228
20516
|
function defaultExec(cmd) {
|
|
20229
20517
|
try {
|
|
20230
20518
|
const stdout = execSync11(cmd, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -20246,22 +20534,22 @@ function getCurrentVersion(_exec = defaultExec) {
|
|
|
20246
20534
|
}
|
|
20247
20535
|
function readLastStable(file = LAST_STABLE_FILE) {
|
|
20248
20536
|
try {
|
|
20249
|
-
const v =
|
|
20537
|
+
const v = fs35.readFileSync(file, "utf-8").trim();
|
|
20250
20538
|
return v || null;
|
|
20251
20539
|
} catch {
|
|
20252
20540
|
return null;
|
|
20253
20541
|
}
|
|
20254
20542
|
}
|
|
20255
20543
|
function writeLastStable(version, file = LAST_STABLE_FILE) {
|
|
20256
|
-
|
|
20257
|
-
|
|
20544
|
+
fs35.mkdirSync(path39.dirname(file), { recursive: true });
|
|
20545
|
+
fs35.writeFileSync(file, version, { mode: 420 });
|
|
20258
20546
|
}
|
|
20259
20547
|
function logUpdateFailure(stderr) {
|
|
20260
20548
|
const ts = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
20261
|
-
const logFile =
|
|
20549
|
+
const logFile = path39.join(LOG_DIR2, `update-${ts}.log`);
|
|
20262
20550
|
try {
|
|
20263
|
-
|
|
20264
|
-
|
|
20551
|
+
fs35.mkdirSync(LOG_DIR2, { recursive: true });
|
|
20552
|
+
fs35.appendFileSync(logFile, `[update-failure ${(/* @__PURE__ */ new Date()).toISOString()}]
|
|
20265
20553
|
${stderr}
|
|
20266
20554
|
`, "utf-8");
|
|
20267
20555
|
} catch {
|
|
@@ -20348,8 +20636,8 @@ async function doUpdate(opts, _exec = defaultExec, _reconcile = reconcileSkillSy
|
|
|
20348
20636
|
let symlinkResult = { added: [], removed: [] };
|
|
20349
20637
|
if (npmRootResult.exitCode === 0) {
|
|
20350
20638
|
const npmRoot = npmRootResult.stdout.trim();
|
|
20351
|
-
const npmSkillsDir =
|
|
20352
|
-
const claudeSkillsDir =
|
|
20639
|
+
const npmSkillsDir = path39.join(npmRoot, "@pleri", "olam-cli", "plugin", "skills");
|
|
20640
|
+
const claudeSkillsDir = path39.join(os21.homedir(), ".claude", "skills");
|
|
20353
20641
|
const rec = _reconcile(npmSkillsDir, claudeSkillsDir);
|
|
20354
20642
|
symlinkResult = { added: rec.added, removed: rec.removed };
|
|
20355
20643
|
if (!quiet && (rec.added.length > 0 || rec.removed.length > 0)) {
|
|
@@ -20395,8 +20683,8 @@ async function doRollback(_exec = defaultExec, _reconcile = reconcileSkillSymlin
|
|
|
20395
20683
|
if (npmRootResult.exitCode === 0) {
|
|
20396
20684
|
const npmRoot = npmRootResult.stdout.trim();
|
|
20397
20685
|
_reconcile(
|
|
20398
|
-
|
|
20399
|
-
|
|
20686
|
+
path39.join(npmRoot, "@pleri", "olam-cli", "plugin", "skills"),
|
|
20687
|
+
path39.join(os21.homedir(), ".claude", "skills")
|
|
20400
20688
|
);
|
|
20401
20689
|
}
|
|
20402
20690
|
return { action: "rolled-back", restoredVersion: prev, exitCode: 0 };
|
|
@@ -20448,19 +20736,119 @@ function registerUpdate(program2) {
|
|
|
20448
20736
|
});
|
|
20449
20737
|
}
|
|
20450
20738
|
|
|
20739
|
+
// src/commands/begin.ts
|
|
20740
|
+
init_host_cp();
|
|
20741
|
+
init_open_url();
|
|
20742
|
+
import pc22 from "picocolors";
|
|
20743
|
+
async function doBegin(opts, _startFn = startHostCp, _openFn = openUrl) {
|
|
20744
|
+
const prevExitCode = process.exitCode;
|
|
20745
|
+
await _startFn({ showToken: opts.showToken });
|
|
20746
|
+
const failed = process.exitCode !== void 0 && process.exitCode !== 0 && process.exitCode !== prevExitCode;
|
|
20747
|
+
if (failed) return false;
|
|
20748
|
+
if (opts.browser) {
|
|
20749
|
+
const token = readToken();
|
|
20750
|
+
if (token) {
|
|
20751
|
+
const base = `http://127.0.0.1:${HOST_CP_PORT}/`;
|
|
20752
|
+
console.log(pc22.dim(`Opening ${base}`));
|
|
20753
|
+
_openFn(`${base}?token=${token}`);
|
|
20754
|
+
}
|
|
20755
|
+
}
|
|
20756
|
+
return true;
|
|
20757
|
+
}
|
|
20758
|
+
function registerBegin(program2) {
|
|
20759
|
+
program2.command("begin").description("Start the Olam host control plane (alias: olam host-cp start)").option("--show-token", "Print token to stdout").option("--no-browser", "Skip opening browser").action(async (opts) => {
|
|
20760
|
+
await doBegin({
|
|
20761
|
+
showToken: opts.showToken === true,
|
|
20762
|
+
browser: opts.browser !== false
|
|
20763
|
+
});
|
|
20764
|
+
});
|
|
20765
|
+
}
|
|
20766
|
+
|
|
20767
|
+
// src/commands/stop.ts
|
|
20768
|
+
init_host_cp();
|
|
20769
|
+
async function doStop(_stopFn = stopHostCp) {
|
|
20770
|
+
await _stopFn();
|
|
20771
|
+
}
|
|
20772
|
+
function registerStop(program2) {
|
|
20773
|
+
program2.command("stop").description("Stop the Olam host control plane (alias: olam host-cp stop)").action(async () => {
|
|
20774
|
+
await doStop();
|
|
20775
|
+
});
|
|
20776
|
+
}
|
|
20777
|
+
|
|
20778
|
+
// src/commands/world-upgrade.ts
|
|
20779
|
+
init_output();
|
|
20780
|
+
init_version_pin();
|
|
20781
|
+
async function doWorldUpgrade(opts) {
|
|
20782
|
+
const version = opts.version ?? CLI_VERSION;
|
|
20783
|
+
const stamp = opts.stampPin ?? ((p, v) => stampVersionPin(p, v));
|
|
20784
|
+
let targets;
|
|
20785
|
+
if (opts.all) {
|
|
20786
|
+
targets = opts.getWorlds();
|
|
20787
|
+
} else if (opts.slug) {
|
|
20788
|
+
const w = opts.getWorld(opts.slug);
|
|
20789
|
+
if (!w) return [];
|
|
20790
|
+
targets = [w];
|
|
20791
|
+
} else {
|
|
20792
|
+
return [];
|
|
20793
|
+
}
|
|
20794
|
+
return targets.map((w) => {
|
|
20795
|
+
const stamped = opts.dryRun ? false : stamp(w.workspacePath, version);
|
|
20796
|
+
return { worldId: w.id, stamped, dryRun: opts.dryRun === true };
|
|
20797
|
+
});
|
|
20798
|
+
}
|
|
20799
|
+
function registerWorldUpgrade(program2) {
|
|
20800
|
+
const world = getOrCreateWorldCommand(program2);
|
|
20801
|
+
world.command("upgrade [slug]").description("Refresh cli_version pin in world.yaml to current CLI version").option("--all", "Upgrade all worlds").option("--dry-run", "Preview which worlds would be upgraded without writing").action(async (slug, opts) => {
|
|
20802
|
+
const { loadContext: loadContext2 } = await Promise.resolve().then(() => (init_context(), context_exports));
|
|
20803
|
+
const { ctx, error } = await loadContext2();
|
|
20804
|
+
if (!ctx) {
|
|
20805
|
+
printError(error?.message ?? "Olam is not configured. Run `olam init` first.");
|
|
20806
|
+
process.exitCode = 1;
|
|
20807
|
+
return;
|
|
20808
|
+
}
|
|
20809
|
+
if (!slug && !opts.all) {
|
|
20810
|
+
printError("Specify a world slug or --all.");
|
|
20811
|
+
process.exitCode = 1;
|
|
20812
|
+
return;
|
|
20813
|
+
}
|
|
20814
|
+
const results = await doWorldUpgrade({
|
|
20815
|
+
slug,
|
|
20816
|
+
all: opts.all,
|
|
20817
|
+
dryRun: opts.dryRun,
|
|
20818
|
+
getWorlds: () => ctx.worldManager.listWorlds(),
|
|
20819
|
+
getWorld: (id) => ctx.worldManager.getWorld(id)
|
|
20820
|
+
});
|
|
20821
|
+
if (results.length === 0) {
|
|
20822
|
+
printError(`World "${slug}" not found.`);
|
|
20823
|
+
process.exitCode = 1;
|
|
20824
|
+
return;
|
|
20825
|
+
}
|
|
20826
|
+
printHeader(`World upgrade ${opts.dryRun ? "(dry run)" : ""}`);
|
|
20827
|
+
for (const r of results) {
|
|
20828
|
+
if (opts.dryRun) {
|
|
20829
|
+
printInfo(r.worldId, "would upgrade");
|
|
20830
|
+
} else if (r.stamped) {
|
|
20831
|
+
printInfo(r.worldId, `stamped \u2192 v${CLI_VERSION}`);
|
|
20832
|
+
} else {
|
|
20833
|
+
printInfo(r.worldId, `already at v${CLI_VERSION} (no-op)`);
|
|
20834
|
+
}
|
|
20835
|
+
}
|
|
20836
|
+
});
|
|
20837
|
+
}
|
|
20838
|
+
|
|
20451
20839
|
// src/pleri-config.ts
|
|
20452
|
-
import * as
|
|
20453
|
-
import * as
|
|
20840
|
+
import * as fs36 from "node:fs";
|
|
20841
|
+
import * as path40 from "node:path";
|
|
20454
20842
|
function isPleriConfigured(configDir = process.env.OLAM_CONFIG_DIR ?? ".olam") {
|
|
20455
20843
|
if (process.env.PLERI_BASE_URL) {
|
|
20456
20844
|
return true;
|
|
20457
20845
|
}
|
|
20458
|
-
const configPath =
|
|
20459
|
-
if (!
|
|
20846
|
+
const configPath = path40.join(configDir, "config.yaml");
|
|
20847
|
+
if (!fs36.existsSync(configPath)) {
|
|
20460
20848
|
return false;
|
|
20461
20849
|
}
|
|
20462
20850
|
try {
|
|
20463
|
-
const contents =
|
|
20851
|
+
const contents = fs36.readFileSync(configPath, "utf8");
|
|
20464
20852
|
return /^[^#\n]*\bpleri:/m.test(contents);
|
|
20465
20853
|
} catch {
|
|
20466
20854
|
return false;
|
|
@@ -20471,14 +20859,14 @@ function isPleriConfigured(configDir = process.env.OLAM_CONFIG_DIR ?? ".olam") {
|
|
|
20471
20859
|
var program = new Command();
|
|
20472
20860
|
function readCliVersion() {
|
|
20473
20861
|
try {
|
|
20474
|
-
const here =
|
|
20862
|
+
const here = path41.dirname(fileURLToPath4(import.meta.url));
|
|
20475
20863
|
for (const candidate of [
|
|
20476
|
-
|
|
20477
|
-
|
|
20478
|
-
|
|
20864
|
+
path41.join(here, "package.json"),
|
|
20865
|
+
path41.join(here, "..", "package.json"),
|
|
20866
|
+
path41.join(here, "..", "..", "package.json")
|
|
20479
20867
|
]) {
|
|
20480
|
-
if (
|
|
20481
|
-
const pkg = JSON.parse(
|
|
20868
|
+
if (fs37.existsSync(candidate)) {
|
|
20869
|
+
const pkg = JSON.parse(fs37.readFileSync(candidate, "utf-8"));
|
|
20482
20870
|
if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
|
|
20483
20871
|
}
|
|
20484
20872
|
}
|
|
@@ -20512,4 +20900,7 @@ registerRefresh(program);
|
|
|
20512
20900
|
registerBootstrap(program);
|
|
20513
20901
|
registerDiagnose(program);
|
|
20514
20902
|
registerUpdate(program);
|
|
20903
|
+
registerBegin(program);
|
|
20904
|
+
registerStop(program);
|
|
20905
|
+
registerWorldUpgrade(program);
|
|
20515
20906
|
program.parse();
|