@mifune/agro 0.19.0 → 0.20.0-rc.2
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/agro.js +626 -141
- package/package.json +1 -1
package/dist/agro.js
CHANGED
|
@@ -7,7 +7,7 @@ var __export = (target, all) => {
|
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { dirname as dirname10, join as
|
|
10
|
+
import { dirname as dirname10, join as join20, resolve as resolve13 } from "node:path";
|
|
11
11
|
import { existsSync as existsSync23, mkdtempSync as mkdtempSync5, readFileSync as readFileSync13, rmSync as rmSync6 } from "node:fs";
|
|
12
12
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
13
13
|
|
|
@@ -844,6 +844,49 @@ import { resolve as resolve3, sep } from "node:path";
|
|
|
844
844
|
// src/lib/agro-config.ts
|
|
845
845
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
846
846
|
import { basename as basename3, dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
847
|
+
|
|
848
|
+
// src/lib/runtimes/catalog.ts
|
|
849
|
+
var CATALOG = [
|
|
850
|
+
{
|
|
851
|
+
id: "docker",
|
|
852
|
+
title: "Docker container",
|
|
853
|
+
tier: "container",
|
|
854
|
+
state: "active",
|
|
855
|
+
provisionable: true,
|
|
856
|
+
docsPath: "docs/runtimes/overview.md"
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
id: "microsandbox",
|
|
860
|
+
title: "MicroSandbox",
|
|
861
|
+
tier: "microvm",
|
|
862
|
+
state: "planned",
|
|
863
|
+
provisionable: false,
|
|
864
|
+
notProvisionableReason: (bin) => `microsandbox is not a provisionable runtime yet; see https://github.com/mifunedev/agro/issues/592. Inside a sandbox run \`${bin} tool install microsandbox\`.`,
|
|
865
|
+
docsPath: "docs/runtimes/microsandbox.md"
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
id: "openshell",
|
|
869
|
+
title: "NVIDIA OpenShell",
|
|
870
|
+
tier: "container",
|
|
871
|
+
state: "active",
|
|
872
|
+
provisionable: true,
|
|
873
|
+
docsPath: "docs/runtimes/openshell.md"
|
|
874
|
+
}
|
|
875
|
+
];
|
|
876
|
+
var RUNTIME_CATALOG = Object.freeze(
|
|
877
|
+
CATALOG.map((entry) => Object.freeze({ ...entry }))
|
|
878
|
+
);
|
|
879
|
+
var PROVISIONABLE_RUNTIMES = Object.freeze(
|
|
880
|
+
CATALOG.filter((entry) => entry.provisionable).map((entry) => entry.id)
|
|
881
|
+
);
|
|
882
|
+
function findRuntime(id) {
|
|
883
|
+
return RUNTIME_CATALOG.find((r) => r.id === id);
|
|
884
|
+
}
|
|
885
|
+
function runtimeIds() {
|
|
886
|
+
return RUNTIME_CATALOG.map((r) => r.id);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/lib/agro-config.ts
|
|
847
890
|
var AGRO_CONFIG_FILE = NAMES.configFile;
|
|
848
891
|
var AGRO_CONFIG_MODE = 420;
|
|
849
892
|
var RESERVED_HOME_PATHS = [
|
|
@@ -865,7 +908,7 @@ var RESERVED_HOME_PATHS = [
|
|
|
865
908
|
"/usr",
|
|
866
909
|
"/var"
|
|
867
910
|
];
|
|
868
|
-
var SANDBOX_RUNTIMES =
|
|
911
|
+
var SANDBOX_RUNTIMES = PROVISIONABLE_RUNTIMES;
|
|
869
912
|
function configCheckout(config) {
|
|
870
913
|
const value = config.checkout ?? config.repo;
|
|
871
914
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
@@ -894,6 +937,13 @@ function defaultAgroConfig(name) {
|
|
|
894
937
|
composeOverrides: []
|
|
895
938
|
};
|
|
896
939
|
}
|
|
940
|
+
function entryRuntime(root) {
|
|
941
|
+
try {
|
|
942
|
+
return readAgroConfig(agroConfigPath(root)).runtime ?? "docker";
|
|
943
|
+
} catch {
|
|
944
|
+
return "docker";
|
|
945
|
+
}
|
|
946
|
+
}
|
|
897
947
|
function readAgroConfig(path4) {
|
|
898
948
|
if (!existsSync4(path4)) return defaultAgroConfig(basename3(dirname2(resolve2(path4))));
|
|
899
949
|
let raw;
|
|
@@ -1283,8 +1333,8 @@ function redact(token) {
|
|
|
1283
1333
|
async function ask(question) {
|
|
1284
1334
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1285
1335
|
try {
|
|
1286
|
-
return await new Promise((
|
|
1287
|
-
rl.question(` ${question} `, (answer) =>
|
|
1336
|
+
return await new Promise((resolve14) => {
|
|
1337
|
+
rl.question(` ${question} `, (answer) => resolve14(answer.trim()));
|
|
1288
1338
|
});
|
|
1289
1339
|
} finally {
|
|
1290
1340
|
rl.close();
|
|
@@ -1296,7 +1346,7 @@ async function askSecret(question) {
|
|
|
1296
1346
|
process.stdin.setRawMode(true);
|
|
1297
1347
|
process.stdin.resume();
|
|
1298
1348
|
const bytes = [];
|
|
1299
|
-
return await new Promise((
|
|
1349
|
+
return await new Promise((resolve14) => {
|
|
1300
1350
|
const cleanup = () => {
|
|
1301
1351
|
try {
|
|
1302
1352
|
process.stdin.setRawMode(false);
|
|
@@ -1314,7 +1364,7 @@ async function askSecret(question) {
|
|
|
1314
1364
|
} else if (byte === 13 || byte === 10) {
|
|
1315
1365
|
cleanup();
|
|
1316
1366
|
process.stdout.write("\n");
|
|
1317
|
-
|
|
1367
|
+
resolve14(Buffer.from(bytes).toString("utf8").trim());
|
|
1318
1368
|
return;
|
|
1319
1369
|
} else if (byte === 127 || byte === 8) {
|
|
1320
1370
|
if (bytes.length > 0) {
|
|
@@ -1364,10 +1414,10 @@ async function askSecretPiped(question) {
|
|
|
1364
1414
|
};
|
|
1365
1415
|
process.on("SIGINT", onSigint);
|
|
1366
1416
|
try {
|
|
1367
|
-
return await new Promise((
|
|
1417
|
+
return await new Promise((resolve14) => {
|
|
1368
1418
|
rl.question(` ${question} `, (answer) => {
|
|
1369
1419
|
process.stdout.write("\n");
|
|
1370
|
-
|
|
1420
|
+
resolve14(answer.trim());
|
|
1371
1421
|
});
|
|
1372
1422
|
});
|
|
1373
1423
|
} finally {
|
|
@@ -1752,6 +1802,9 @@ printf 'free\\n'
|
|
|
1752
1802
|
exit 0
|
|
1753
1803
|
`;
|
|
1754
1804
|
|
|
1805
|
+
// agro-asset:/home/runner/work/agro/agro/.devcontainer/openshell-policy.yaml
|
|
1806
|
+
var openshell_policy_default = "version: 1\n\nfilesystem_policy:\n include_workdir: true\n read_only:\n - /bin\n - /usr\n - /lib\n - /etc\n - /opt\n - /proc\n - /var/log\n - /dev/urandom\n read_write:\n - /home/sandbox\n - /tmp\n - /dev/null\n\nprocess:\n run_as_user: sandbox\n run_as_group: sandbox\n\nnetwork_policies:\n github:\n endpoints:\n - host: github.com\n port: 443\n protocol: rest\n enforcement: enforce\n access: read-write\n - host: api.github.com\n port: 443\n protocol: rest\n enforcement: enforce\n access: read-write\n - host: release-assets.githubusercontent.com\n port: 443\n protocol: rest\n enforcement: enforce\n access: read-only\n binaries:\n - path: /usr/bin/git\n - path: /usr/bin/gh\n - path: /usr/bin/curl\n npm:\n endpoints:\n - host: registry.npmjs.org\n port: 443\n protocol: rest\n enforcement: enforce\n access: read-only\n allow_encoded_slash: true\n binaries:\n - path: /usr/local/bin/node\n anthropic:\n endpoints:\n - host: api.anthropic.com\n port: 443\n protocol: rest\n enforcement: enforce\n access: read-write\n binaries:\n - path: /home/sandbox/.local/lib/node_modules/@anthropic-ai/claude-code/bin/*\n";
|
|
1807
|
+
|
|
1755
1808
|
// src/lib/registry.ts
|
|
1756
1809
|
var SANDBOX_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
1757
1810
|
var DEFAULT_NAME_PREFIX = "agro-sbx-";
|
|
@@ -1808,10 +1861,12 @@ function nextDefaultName(run = spawnRunner) {
|
|
|
1808
1861
|
if (!taken.has(candidate)) return candidate;
|
|
1809
1862
|
}
|
|
1810
1863
|
}
|
|
1811
|
-
function
|
|
1812
|
-
|
|
1864
|
+
function openshellFiles(entry) {
|
|
1865
|
+
return [{ dest: join4(entry, "openshell-policy.yaml"), body: openshell_policy_default, mode: 420 }];
|
|
1866
|
+
}
|
|
1867
|
+
function dockerFiles(entry, opts) {
|
|
1813
1868
|
const scripts = join4(resolveProjectLayout(entry).controlDir, "scripts");
|
|
1814
|
-
|
|
1869
|
+
return [
|
|
1815
1870
|
{
|
|
1816
1871
|
dest: join4(entry, ".devcontainer", "docker-compose.yml"),
|
|
1817
1872
|
body: (opts.checkout ?? opts.repo) === void 0 ? docker_compose_image_only_default : docker_compose_default,
|
|
@@ -1823,6 +1878,10 @@ function materialize(root, opts = {}) {
|
|
|
1823
1878
|
{ dest: join4(scripts, "paths.sh"), body: paths_default, mode: 420 },
|
|
1824
1879
|
{ dest: join4(scripts, "check-host-port.sh"), body: check_host_port_default, mode: 493 }
|
|
1825
1880
|
];
|
|
1881
|
+
}
|
|
1882
|
+
function materialize(root, opts = {}) {
|
|
1883
|
+
const entry = resolve5(root);
|
|
1884
|
+
const files = entryRuntime(entry) === "openshell" ? openshellFiles(entry) : dockerFiles(entry, opts);
|
|
1826
1885
|
for (const file of files) {
|
|
1827
1886
|
const dest = resolve5(file.dest);
|
|
1828
1887
|
if (!dest.startsWith(entry + sep2)) {
|
|
@@ -1981,12 +2040,28 @@ function stripQuotes2(s) {
|
|
|
1981
2040
|
// src/commands/lifecycle.ts
|
|
1982
2041
|
import { existsSync as existsSync10, mkdtempSync, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1983
2042
|
import { tmpdir } from "node:os";
|
|
1984
|
-
import { basename as
|
|
2043
|
+
import { basename as basename6, join as join6, sep as sep3 } from "node:path";
|
|
2044
|
+
|
|
2045
|
+
// src/lib/execution/index.ts
|
|
2046
|
+
import { basename as basename4, resolve as resolve7 } from "node:path";
|
|
2047
|
+
|
|
2048
|
+
// src/lib/version.ts
|
|
2049
|
+
var AGRO_VERSION = true ? "0.20.0-rc.2" : "0.0.0-dev";
|
|
2050
|
+
var OFFICIAL_IMAGE = "ghcr.io/mifunedev/agro";
|
|
2051
|
+
var RELEASE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z][a-z0-9]*\.[0-9]+)?$/;
|
|
2052
|
+
function parseReleaseVersion(value) {
|
|
2053
|
+
const bare = value.startsWith("v") ? value.slice(1) : value;
|
|
2054
|
+
return RELEASE_VERSION.test(bare) ? bare : void 0;
|
|
2055
|
+
}
|
|
2056
|
+
function officialImageRef(version) {
|
|
2057
|
+
return `${OFFICIAL_IMAGE}:${RELEASE_VERSION.test(version) ? version : "latest"}`;
|
|
2058
|
+
}
|
|
1985
2059
|
|
|
1986
2060
|
// src/lib/execution/detect.ts
|
|
1987
2061
|
import { existsSync as existsSync9 } from "node:fs";
|
|
1988
2062
|
var SANDBOX_MARKER_FILE = "/etc/agro/sandbox";
|
|
1989
2063
|
var DOCKERENV_FILE = "/.dockerenv";
|
|
2064
|
+
var EXECUTION_TARGET_ENV = "AGRO_EXECUTION_TARGET";
|
|
1990
2065
|
function detectSandbox(env = process.env, fileExists = existsSync9) {
|
|
1991
2066
|
const override = agroEnvValue(env, "EXECUTION_TARGET");
|
|
1992
2067
|
if (override === "local") return { inside: true, rule: "override" };
|
|
@@ -2224,6 +2299,246 @@ function defaultIdentity() {
|
|
|
2224
2299
|
return { name: info2.username, uid: info2.uid };
|
|
2225
2300
|
}
|
|
2226
2301
|
|
|
2302
|
+
// src/lib/execution/openshell-target.ts
|
|
2303
|
+
import { join as join5 } from "node:path";
|
|
2304
|
+
var OPENSHELL_BIN = "openshell";
|
|
2305
|
+
var OPENSHELL_POLICY_FILE = "openshell-policy.yaml";
|
|
2306
|
+
var OPENSHELL_MAIN = "/usr/local/bin/openshell-main.sh";
|
|
2307
|
+
var OPENSHELL_WORKDIR = "/home/sandbox/harness";
|
|
2308
|
+
var OPENSHELL_USER = "sandbox";
|
|
2309
|
+
var OPENSHELL_HOME = "/home/sandbox";
|
|
2310
|
+
var OPENSHELL_PINNED_VERSION = "v0.1.2";
|
|
2311
|
+
var OPENSHELL_LOCAL_GATEWAY = "https://127.0.0.1:17670";
|
|
2312
|
+
var OPENSHELL_INSTALL_URL = "https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh";
|
|
2313
|
+
var LOGIN_SHELLS = /* @__PURE__ */ new Set(["zsh", "bash"]);
|
|
2314
|
+
var NOT_FOUND = /sandbox (?:'[^']*' )?not found/;
|
|
2315
|
+
var OPENSHELL_PHASES = [
|
|
2316
|
+
"Unspecified",
|
|
2317
|
+
"Provisioning",
|
|
2318
|
+
"Ready",
|
|
2319
|
+
"Error",
|
|
2320
|
+
"Deleting",
|
|
2321
|
+
"Unknown",
|
|
2322
|
+
"Stopping",
|
|
2323
|
+
"Stopped",
|
|
2324
|
+
"Starting",
|
|
2325
|
+
"Completed"
|
|
2326
|
+
];
|
|
2327
|
+
var OPENSHELL_PHASE_STATUS = Object.freeze({
|
|
2328
|
+
Ready: "ready",
|
|
2329
|
+
Provisioning: "starting",
|
|
2330
|
+
Starting: "starting",
|
|
2331
|
+
Stopping: "stopped",
|
|
2332
|
+
Stopped: "stopped",
|
|
2333
|
+
Deleting: "stopped",
|
|
2334
|
+
Completed: "stopped",
|
|
2335
|
+
Error: "failed",
|
|
2336
|
+
Unknown: "failed",
|
|
2337
|
+
Unspecified: "failed"
|
|
2338
|
+
});
|
|
2339
|
+
function createArgv(opts) {
|
|
2340
|
+
return [
|
|
2341
|
+
"sandbox",
|
|
2342
|
+
"create",
|
|
2343
|
+
"--name",
|
|
2344
|
+
opts.name,
|
|
2345
|
+
"--from",
|
|
2346
|
+
opts.image,
|
|
2347
|
+
"--policy",
|
|
2348
|
+
join5(opts.entryRoot, OPENSHELL_POLICY_FILE),
|
|
2349
|
+
"--env",
|
|
2350
|
+
`${EXECUTION_TARGET_ENV}=local`,
|
|
2351
|
+
"--env",
|
|
2352
|
+
`SANDBOX_NAME=${opts.name}`,
|
|
2353
|
+
"--env",
|
|
2354
|
+
`HOME=${OPENSHELL_HOME}`,
|
|
2355
|
+
"--detach",
|
|
2356
|
+
"--",
|
|
2357
|
+
OPENSHELL_MAIN
|
|
2358
|
+
];
|
|
2359
|
+
}
|
|
2360
|
+
function attachArgv(name, request = { argv: ["zsh"] }) {
|
|
2361
|
+
return [
|
|
2362
|
+
"sandbox",
|
|
2363
|
+
"exec",
|
|
2364
|
+
"-n",
|
|
2365
|
+
name,
|
|
2366
|
+
"--tty",
|
|
2367
|
+
"--workdir",
|
|
2368
|
+
request.cwd ?? OPENSHELL_WORKDIR,
|
|
2369
|
+
...envFlags(request.env),
|
|
2370
|
+
"--",
|
|
2371
|
+
...asLoginShell(request.argv)
|
|
2372
|
+
];
|
|
2373
|
+
}
|
|
2374
|
+
function execArgv(name, request) {
|
|
2375
|
+
return [
|
|
2376
|
+
"sandbox",
|
|
2377
|
+
"exec",
|
|
2378
|
+
"-n",
|
|
2379
|
+
name,
|
|
2380
|
+
...request.cwd !== void 0 ? ["--workdir", request.cwd] : [],
|
|
2381
|
+
...envFlags(request.env),
|
|
2382
|
+
...request.timeoutMs !== void 0 ? ["--timeout", String(Math.ceil(request.timeoutMs / 1e3))] : [],
|
|
2383
|
+
"--",
|
|
2384
|
+
...request.argv
|
|
2385
|
+
];
|
|
2386
|
+
}
|
|
2387
|
+
function getArgv(name) {
|
|
2388
|
+
return ["sandbox", "get", name, "-o", "json"];
|
|
2389
|
+
}
|
|
2390
|
+
function deleteArgv(name) {
|
|
2391
|
+
return ["sandbox", "delete", name];
|
|
2392
|
+
}
|
|
2393
|
+
function parseOpenShellPhase(json) {
|
|
2394
|
+
let parsed;
|
|
2395
|
+
try {
|
|
2396
|
+
parsed = JSON.parse(json);
|
|
2397
|
+
} catch {
|
|
2398
|
+
return "Unknown";
|
|
2399
|
+
}
|
|
2400
|
+
const phase = typeof parsed === "object" && parsed !== null ? parsed.phase : void 0;
|
|
2401
|
+
return OPENSHELL_PHASES.find((known) => known === phase) ?? "Unknown";
|
|
2402
|
+
}
|
|
2403
|
+
function openshellSandboxMissing(output) {
|
|
2404
|
+
return NOT_FOUND.test(output);
|
|
2405
|
+
}
|
|
2406
|
+
var OPENSHELL_VERB_HINTS = Object.freeze({
|
|
2407
|
+
stop: (name) => `openshell sandbox stop ${name}`,
|
|
2408
|
+
restart: (name) => `openshell sandbox stop ${name} && openshell sandbox start ${name}`,
|
|
2409
|
+
logs: (name) => `openshell logs ${name}`,
|
|
2410
|
+
ps: (name) => `openshell sandbox get ${name}`,
|
|
2411
|
+
config: (name) => `openshell policy get ${name}`
|
|
2412
|
+
});
|
|
2413
|
+
function openshellInstallHint() {
|
|
2414
|
+
return [
|
|
2415
|
+
"install the OpenShell CLI on the host, then run the command again:",
|
|
2416
|
+
` curl -LsSf ${OPENSHELL_INSTALL_URL} | OPENSHELL_VERSION=${OPENSHELL_PINNED_VERSION} sh`,
|
|
2417
|
+
"review-first alternative \u2014 download the script, inspect it, then run it:",
|
|
2418
|
+
` curl -LsSf -o openshell-install.sh ${OPENSHELL_INSTALL_URL}`,
|
|
2419
|
+
" less openshell-install.sh",
|
|
2420
|
+
` OPENSHELL_VERSION=${OPENSHELL_PINNED_VERSION} sh openshell-install.sh`
|
|
2421
|
+
].join("\n");
|
|
2422
|
+
}
|
|
2423
|
+
function openshellGatewayHint() {
|
|
2424
|
+
return [
|
|
2425
|
+
"the OpenShell gateway is not connected; start the local gateway and register it:",
|
|
2426
|
+
" systemctl --user restart openshell-gateway",
|
|
2427
|
+
` openshell gateway add ${OPENSHELL_LOCAL_GATEWAY} --local --name openshell`,
|
|
2428
|
+
"then confirm with: openshell status"
|
|
2429
|
+
].join("\n");
|
|
2430
|
+
}
|
|
2431
|
+
function openshellCleanupHint(name) {
|
|
2432
|
+
return `the OpenShell gateway can keep a failed sandbox record in the Error phase; if ${name} is not another sandbox, remove it with: openshell sandbox delete ${name}`;
|
|
2433
|
+
}
|
|
2434
|
+
function openshellPreflight(run) {
|
|
2435
|
+
const version = run(OPENSHELL_BIN, ["--version"], { stdio: "capture" });
|
|
2436
|
+
if (version.error?.code === "ENOENT") {
|
|
2437
|
+
return { ok: false, reason: "missing-binary", hint: openshellInstallHint() };
|
|
2438
|
+
}
|
|
2439
|
+
assertSpawned(version, `${OPENSHELL_BIN} --version`);
|
|
2440
|
+
const status = run(OPENSHELL_BIN, ["status", "-o", "json"], { stdio: "capture" });
|
|
2441
|
+
assertSpawned(status, `${OPENSHELL_BIN} status`);
|
|
2442
|
+
if (status.status !== 0 || !gatewayConnected(status.stdout ?? "")) {
|
|
2443
|
+
return { ok: false, reason: "gateway-unreachable", hint: openshellGatewayHint() };
|
|
2444
|
+
}
|
|
2445
|
+
return { ok: true };
|
|
2446
|
+
}
|
|
2447
|
+
var OpenShellExecutionTarget = class {
|
|
2448
|
+
kind = "openshell";
|
|
2449
|
+
contractVersion = 1;
|
|
2450
|
+
workspace;
|
|
2451
|
+
name;
|
|
2452
|
+
entryRoot;
|
|
2453
|
+
image;
|
|
2454
|
+
run;
|
|
2455
|
+
env;
|
|
2456
|
+
constructor(opts) {
|
|
2457
|
+
this.name = opts.name;
|
|
2458
|
+
this.entryRoot = opts.entryRoot;
|
|
2459
|
+
this.image = opts.image;
|
|
2460
|
+
this.run = opts.run ?? spawnRunner;
|
|
2461
|
+
this.env = opts.env;
|
|
2462
|
+
this.workspace = { hostRoot: opts.entryRoot, targetRoot: OPENSHELL_WORKDIR };
|
|
2463
|
+
}
|
|
2464
|
+
async provision() {
|
|
2465
|
+
const argv = createArgv({ name: this.name, image: this.image, entryRoot: this.entryRoot });
|
|
2466
|
+
this.runInherited(argv, "sandbox create");
|
|
2467
|
+
}
|
|
2468
|
+
async destroy() {
|
|
2469
|
+
this.runInherited(deleteArgv(this.name), "sandbox delete");
|
|
2470
|
+
}
|
|
2471
|
+
async status() {
|
|
2472
|
+
const r = this.run(OPENSHELL_BIN, getArgv(this.name), { stdio: "capture", ...this.childEnv() });
|
|
2473
|
+
assertSpawned(r, this.what("sandbox get"));
|
|
2474
|
+
if (r.status !== 0) {
|
|
2475
|
+
if (openshellSandboxMissing(`${r.stdout ?? ""}
|
|
2476
|
+
${r.stderr ?? ""}`)) return "absent";
|
|
2477
|
+
throw new ExecutionExitError(this.what("sandbox get"), r.status ?? 1);
|
|
2478
|
+
}
|
|
2479
|
+
return OPENSHELL_PHASE_STATUS[parseOpenShellPhase(r.stdout ?? "")];
|
|
2480
|
+
}
|
|
2481
|
+
async capabilities() {
|
|
2482
|
+
return /* @__PURE__ */ new Set(["exec", "pty"]);
|
|
2483
|
+
}
|
|
2484
|
+
async exec(request) {
|
|
2485
|
+
requireSandboxUser(request);
|
|
2486
|
+
const inherit = request.stdio === "inherit";
|
|
2487
|
+
const r = this.run(OPENSHELL_BIN, execArgv(this.name, request), {
|
|
2488
|
+
stdio: inherit ? "inherit" : "capture",
|
|
2489
|
+
...this.childEnv()
|
|
2490
|
+
});
|
|
2491
|
+
assertSpawned(r, this.what("sandbox exec"));
|
|
2492
|
+
return {
|
|
2493
|
+
exitCode: r.status ?? 1,
|
|
2494
|
+
stdout: r.stdout ?? "",
|
|
2495
|
+
stderr: r.stderr ?? ""
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
attach(request) {
|
|
2499
|
+
requireSandboxUser(request);
|
|
2500
|
+
const r = this.run(OPENSHELL_BIN, attachArgv(this.name, request), { stdio: "inherit", ...this.childEnv() });
|
|
2501
|
+
assertSpawned(r, this.what("sandbox exec"));
|
|
2502
|
+
return r.status ?? 1;
|
|
2503
|
+
}
|
|
2504
|
+
describe() {
|
|
2505
|
+
return `openshell sandbox ${this.name} (entry ${this.entryRoot})`;
|
|
2506
|
+
}
|
|
2507
|
+
runInherited(argv, verb) {
|
|
2508
|
+
const r = this.run(OPENSHELL_BIN, argv, { stdio: "inherit", ...this.childEnv() });
|
|
2509
|
+
assertSpawned(r, this.what(verb));
|
|
2510
|
+
const code = r.status ?? 1;
|
|
2511
|
+
if (code !== 0) throw new ExecutionExitError(this.what(verb), code);
|
|
2512
|
+
}
|
|
2513
|
+
childEnv() {
|
|
2514
|
+
return this.env ? { env: this.env } : {};
|
|
2515
|
+
}
|
|
2516
|
+
what(verb) {
|
|
2517
|
+
return `${OPENSHELL_BIN} ${verb} ${this.name}`;
|
|
2518
|
+
}
|
|
2519
|
+
};
|
|
2520
|
+
function asLoginShell(argv) {
|
|
2521
|
+
return argv.length === 1 && LOGIN_SHELLS.has(argv[0]) ? [argv[0], "-l"] : [...argv];
|
|
2522
|
+
}
|
|
2523
|
+
function envFlags(env) {
|
|
2524
|
+
return Object.entries(env ?? {}).flatMap(([k, v]) => ["--env", `${k}=${v}`]);
|
|
2525
|
+
}
|
|
2526
|
+
function gatewayConnected(json) {
|
|
2527
|
+
try {
|
|
2528
|
+
const parsed = JSON.parse(json);
|
|
2529
|
+
return typeof parsed === "object" && parsed !== null && parsed.status === "connected";
|
|
2530
|
+
} catch {
|
|
2531
|
+
return false;
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
function requireSandboxUser(request) {
|
|
2535
|
+
if (request.user !== void 0 && request.user !== OPENSHELL_USER) {
|
|
2536
|
+
throw new Error(
|
|
2537
|
+
`the openshell runtime runs every command as "${OPENSHELL_USER}" and cannot run as "${request.user}"`
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2227
2542
|
// src/lib/execution/status.ts
|
|
2228
2543
|
var RUNTIME_ABSENT_STATUS = "no container runtime";
|
|
2229
2544
|
async function resolveTargetStatus(target) {
|
|
@@ -2247,19 +2562,23 @@ function resolveExecutionTarget(opts) {
|
|
|
2247
2562
|
...opts.env ? { env: opts.env } : {}
|
|
2248
2563
|
});
|
|
2249
2564
|
}
|
|
2565
|
+
if (entryRuntime(opts.projectRoot) === "openshell") {
|
|
2566
|
+
const config = readAgroConfig(agroConfigPath(opts.projectRoot));
|
|
2567
|
+
return new OpenShellExecutionTarget({
|
|
2568
|
+
name: opts.container ?? nonEmpty(config.name) ?? basename4(resolve7(opts.projectRoot)),
|
|
2569
|
+
entryRoot: opts.projectRoot,
|
|
2570
|
+
image: openshellImage(config),
|
|
2571
|
+
...opts.run ? { run: opts.run } : {},
|
|
2572
|
+
...opts.env ? { env: opts.env } : {}
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2250
2575
|
return new DockerComposeExecutionTarget(opts);
|
|
2251
2576
|
}
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
var AGRO_VERSION = true ? "0.19.0" : "0.0.0-dev";
|
|
2255
|
-
var OFFICIAL_IMAGE = "ghcr.io/mifunedev/agro";
|
|
2256
|
-
var RELEASE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z][a-z0-9]*\.[0-9]+)?$/;
|
|
2257
|
-
function parseReleaseVersion(value) {
|
|
2258
|
-
const bare = value.startsWith("v") ? value.slice(1) : value;
|
|
2259
|
-
return RELEASE_VERSION.test(bare) ? bare : void 0;
|
|
2577
|
+
function openshellImage(config) {
|
|
2578
|
+
return nonEmpty(config.image?.ref) ?? officialImageRef(AGRO_VERSION);
|
|
2260
2579
|
}
|
|
2261
|
-
function
|
|
2262
|
-
return
|
|
2580
|
+
function nonEmpty(value) {
|
|
2581
|
+
return value === void 0 || value === "" ? void 0 : value;
|
|
2263
2582
|
}
|
|
2264
2583
|
|
|
2265
2584
|
// src/lib/config-render.ts
|
|
@@ -2325,13 +2644,103 @@ function renderComposeEnv(config) {
|
|
|
2325
2644
|
`;
|
|
2326
2645
|
}
|
|
2327
2646
|
|
|
2647
|
+
// src/lib/runtimes/verbs.ts
|
|
2648
|
+
import { basename as basename5, resolve as resolve8 } from "node:path";
|
|
2649
|
+
var viaCompose = Object.freeze({ action: "route", via: "compose" });
|
|
2650
|
+
var viaTarget = Object.freeze({ action: "route", via: "target" });
|
|
2651
|
+
function refuse(hint) {
|
|
2652
|
+
return Object.freeze({ action: "refuse", hint });
|
|
2653
|
+
}
|
|
2654
|
+
var LIFECYCLE_COMMANDS = Object.freeze({
|
|
2655
|
+
shell: "shell",
|
|
2656
|
+
stop: "stop",
|
|
2657
|
+
restart: "restart",
|
|
2658
|
+
logs: "logs",
|
|
2659
|
+
ps: "ps",
|
|
2660
|
+
destroy: "destroy",
|
|
2661
|
+
config: "compose config",
|
|
2662
|
+
upgrade: "sandbox upgrade"
|
|
2663
|
+
});
|
|
2664
|
+
var DOCKER_VERBS = Object.freeze({
|
|
2665
|
+
shell: viaTarget,
|
|
2666
|
+
stop: viaCompose,
|
|
2667
|
+
restart: viaCompose,
|
|
2668
|
+
logs: viaCompose,
|
|
2669
|
+
ps: viaCompose,
|
|
2670
|
+
destroy: viaCompose,
|
|
2671
|
+
config: viaCompose,
|
|
2672
|
+
upgrade: viaTarget
|
|
2673
|
+
});
|
|
2674
|
+
var OPENSHELL_VERBS = Object.freeze({
|
|
2675
|
+
shell: viaTarget,
|
|
2676
|
+
stop: refuse(OPENSHELL_VERB_HINTS.stop),
|
|
2677
|
+
restart: refuse(OPENSHELL_VERB_HINTS.restart),
|
|
2678
|
+
logs: refuse(OPENSHELL_VERB_HINTS.logs),
|
|
2679
|
+
ps: refuse(OPENSHELL_VERB_HINTS.ps),
|
|
2680
|
+
destroy: viaTarget,
|
|
2681
|
+
config: refuse(OPENSHELL_VERB_HINTS.config),
|
|
2682
|
+
upgrade: refuse(
|
|
2683
|
+
(name) => `${activeBin()} destroy ${name}, then ${activeBin()} sandbox install openshell --name ${name} --image=<ref>`
|
|
2684
|
+
)
|
|
2685
|
+
});
|
|
2686
|
+
var RUNTIME_VERBS = Object.freeze({
|
|
2687
|
+
docker: DOCKER_VERBS,
|
|
2688
|
+
openshell: OPENSHELL_VERBS
|
|
2689
|
+
});
|
|
2690
|
+
function configuredEntryName(root) {
|
|
2691
|
+
try {
|
|
2692
|
+
const name = readAgroConfig(agroConfigPath(root)).name;
|
|
2693
|
+
return name === "" ? void 0 : name;
|
|
2694
|
+
} catch {
|
|
2695
|
+
return void 0;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
function entryDeclaredName(root) {
|
|
2699
|
+
return configuredEntryName(root) ?? basename5(resolve8(root));
|
|
2700
|
+
}
|
|
2701
|
+
var RUNTIME_NAMING = Object.freeze({
|
|
2702
|
+
docker: Object.freeze({
|
|
2703
|
+
entryName: () => void 0,
|
|
2704
|
+
shellFailure: (bin, name) => `container \`${name}\` not running? start it with \`${bin} sandbox install docker\``
|
|
2705
|
+
}),
|
|
2706
|
+
openshell: Object.freeze({
|
|
2707
|
+
entryName: entryDeclaredName,
|
|
2708
|
+
shellFailure: (bin, name) => `sandbox \`${name}\` did not accept the shell; check its status with \`${bin} sandbox list\``
|
|
2709
|
+
})
|
|
2710
|
+
});
|
|
2711
|
+
function entrySandboxName(root) {
|
|
2712
|
+
return RUNTIME_NAMING[entryRuntime(root)].entryName(root);
|
|
2713
|
+
}
|
|
2714
|
+
function shellFailureHint(runtime, bin, name) {
|
|
2715
|
+
return RUNTIME_NAMING[runtime].shellFailure(bin, name);
|
|
2716
|
+
}
|
|
2717
|
+
var RuntimeUnsupportedError = class extends Error {
|
|
2718
|
+
runtime;
|
|
2719
|
+
verb;
|
|
2720
|
+
hint;
|
|
2721
|
+
constructor(runtime, verb, hint) {
|
|
2722
|
+
const command = LIFECYCLE_COMMANDS[verb];
|
|
2723
|
+
super(`${activeBin()} ${command}: the ${runtime} runtime does not support ${command}; run: ${hint}`);
|
|
2724
|
+
this.name = "RuntimeUnsupportedError";
|
|
2725
|
+
this.runtime = runtime;
|
|
2726
|
+
this.verb = verb;
|
|
2727
|
+
this.hint = hint;
|
|
2728
|
+
}
|
|
2729
|
+
};
|
|
2730
|
+
function routeVerb(root, verb, name) {
|
|
2731
|
+
const runtime = entryRuntime(root);
|
|
2732
|
+
const policy = RUNTIME_VERBS[runtime][verb];
|
|
2733
|
+
if (policy.action === "refuse") throw new RuntimeUnsupportedError(runtime, verb, policy.hint(name));
|
|
2734
|
+
return { runtime, via: policy.via };
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2328
2737
|
// src/commands/lifecycle.ts
|
|
2329
2738
|
var COMPOSE_ENV_DIR_PREFIX = "oh-compose-env-";
|
|
2330
2739
|
function composeEnvDir() {
|
|
2331
|
-
return mkdtempSync(
|
|
2740
|
+
return mkdtempSync(join6(tmpdir(), COMPOSE_ENV_DIR_PREFIX));
|
|
2332
2741
|
}
|
|
2333
2742
|
function writeComposeEnv(root, dir) {
|
|
2334
|
-
const file =
|
|
2743
|
+
const file = join6(dir, "compose.env");
|
|
2335
2744
|
writeFileSync5(file, renderComposeEnv(readAgroConfig(agroConfigPath(root))), {
|
|
2336
2745
|
mode: 384,
|
|
2337
2746
|
encoding: "utf8"
|
|
@@ -2389,7 +2798,7 @@ async function maybePromptDockerSocket(root, io, bin) {
|
|
|
2389
2798
|
if (dockerSocketConfigured(root)) return;
|
|
2390
2799
|
const interactive = process.stdin.isTTY === true || io.ask !== void 0;
|
|
2391
2800
|
if (!interactive) return;
|
|
2392
|
-
const envDir =
|
|
2801
|
+
const envDir = join6(root, ".devcontainer");
|
|
2393
2802
|
if (!existsSync10(envDir)) return;
|
|
2394
2803
|
const askFn = io.ask ?? ask;
|
|
2395
2804
|
const answer = (await askFn(
|
|
@@ -2470,25 +2879,27 @@ async function runSandbox(opts, io) {
|
|
|
2470
2879
|
function configuredContainerName(root) {
|
|
2471
2880
|
return fromProcessEnv("SANDBOX_NAME") ?? configuredString(root, "name");
|
|
2472
2881
|
}
|
|
2882
|
+
function sandboxName(root) {
|
|
2883
|
+
return entrySandboxName(root) ?? configuredContainerName(root) ?? DEFAULT_CONTAINER_NAME;
|
|
2884
|
+
}
|
|
2473
2885
|
function runShell(opts, io) {
|
|
2474
2886
|
const run = opts.run ?? spawnRunner;
|
|
2475
2887
|
const root = sandboxRoot(opts);
|
|
2476
|
-
const name =
|
|
2888
|
+
const name = sandboxName(root);
|
|
2889
|
+
const { runtime } = routeVerb(root, "shell", name);
|
|
2477
2890
|
const target = resolveExecutionTarget({ projectRoot: root, container: name, run });
|
|
2478
2891
|
let code;
|
|
2479
2892
|
try {
|
|
2480
2893
|
code = target.attach({ argv: ["zsh"], user: "sandbox" });
|
|
2481
2894
|
} catch (err) {
|
|
2482
2895
|
if (err instanceof ExecutionSpawnError && err.code === "ENOENT") {
|
|
2483
|
-
throw new Error(
|
|
2896
|
+
throw new Error(`${runtime} is required for \`${opts.bin} shell\` but was not found on PATH`);
|
|
2484
2897
|
}
|
|
2485
2898
|
throw err;
|
|
2486
2899
|
}
|
|
2487
2900
|
if (code !== 0) {
|
|
2488
|
-
io.stderr(
|
|
2489
|
-
|
|
2490
|
-
`
|
|
2491
|
-
);
|
|
2901
|
+
io.stderr(`${shellFailureHint(runtime, opts.bin, name)}
|
|
2902
|
+
`);
|
|
2492
2903
|
}
|
|
2493
2904
|
return code;
|
|
2494
2905
|
}
|
|
@@ -2505,6 +2916,7 @@ function composeVerbs() {
|
|
|
2505
2916
|
function runComposeVerb(verb, opts, extra = []) {
|
|
2506
2917
|
const run = opts.run ?? spawnRunner;
|
|
2507
2918
|
const root = sandboxRoot(opts);
|
|
2919
|
+
routeVerb(root, verb, sandboxName(root));
|
|
2508
2920
|
const script = requireLifecycleScript(root, "docker-compose.sh");
|
|
2509
2921
|
return withComposeEnvFile(root, (extraArgs) => {
|
|
2510
2922
|
const r = run("bash", [script, ...extraArgs, ...COMPOSE_VERBS[verb], ...extra], {
|
|
@@ -2517,6 +2929,7 @@ function runComposeVerb(verb, opts, extra = []) {
|
|
|
2517
2929
|
function runComposeConfig(opts, extra = []) {
|
|
2518
2930
|
const run = opts.run ?? spawnRunner;
|
|
2519
2931
|
const root = sandboxRoot(opts);
|
|
2932
|
+
routeVerb(root, "config", sandboxName(root));
|
|
2520
2933
|
const script = requireLifecycleScript(root, "docker-compose.sh");
|
|
2521
2934
|
return withComposeEnvFile(root, (extraArgs) => {
|
|
2522
2935
|
const r = run("bash", [script, ...extraArgs, "config", ...extra], { stdio: "inherit" });
|
|
@@ -2527,7 +2940,7 @@ function runComposeConfig(opts, extra = []) {
|
|
|
2527
2940
|
function namedVolumes(root) {
|
|
2528
2941
|
let text;
|
|
2529
2942
|
try {
|
|
2530
|
-
text = readFileSync7(
|
|
2943
|
+
text = readFileSync7(join6(root, ".devcontainer", "docker-compose.yml"), "utf8");
|
|
2531
2944
|
} catch {
|
|
2532
2945
|
return [];
|
|
2533
2946
|
}
|
|
@@ -2543,11 +2956,23 @@ function namedVolumes(root) {
|
|
|
2543
2956
|
return names;
|
|
2544
2957
|
}
|
|
2545
2958
|
function destroyConfirmationPhrase(root) {
|
|
2546
|
-
return
|
|
2959
|
+
return sandboxName(root);
|
|
2960
|
+
}
|
|
2961
|
+
async function destroyThroughTarget(root, name, run) {
|
|
2962
|
+
const target = resolveExecutionTarget({ projectRoot: root, container: name, run });
|
|
2963
|
+
if (target.destroy === void 0) throw new Error(`${target.describe()} cannot destroy its sandbox`);
|
|
2964
|
+
try {
|
|
2965
|
+
await target.destroy();
|
|
2966
|
+
return 0;
|
|
2967
|
+
} catch (err) {
|
|
2968
|
+
if (err instanceof ExecutionExitError) return err.exitCode;
|
|
2969
|
+
throw err;
|
|
2970
|
+
}
|
|
2547
2971
|
}
|
|
2548
2972
|
async function runDestroy(opts, io) {
|
|
2549
2973
|
const root = sandboxRoot(opts);
|
|
2550
2974
|
const name = destroyConfirmationPhrase(root);
|
|
2975
|
+
const { via } = routeVerb(root, "destroy", name);
|
|
2551
2976
|
if (opts.yes !== true) {
|
|
2552
2977
|
const interactive = process.stdin.isTTY === true || io.ask !== void 0;
|
|
2553
2978
|
if (!interactive) {
|
|
@@ -2562,7 +2987,14 @@ async function runDestroy(opts, io) {
|
|
|
2562
2987
|
${bold(`${opts.bin} destroy \u2014 ${name}`)}
|
|
2563
2988
|
|
|
2564
2989
|
`);
|
|
2565
|
-
if (
|
|
2990
|
+
if (via === "target") {
|
|
2991
|
+
io.stdout(
|
|
2992
|
+
`This deletes the sandbox \`${name}\` and everything inside it \u2014 every agent CLI
|
|
2993
|
+
login, the gh CLI token, and the SSH keys. Sign-in starts over.
|
|
2994
|
+
|
|
2995
|
+
`
|
|
2996
|
+
);
|
|
2997
|
+
} else if (homePath !== void 0 && homePath !== "") {
|
|
2566
2998
|
io.stdout("`docker compose down -v` removes the containers.\n");
|
|
2567
2999
|
io.stdout(
|
|
2568
3000
|
`This sandbox keeps its home on a host bind at ${homePath}, which \`down -v\`
|
|
@@ -2593,7 +3025,7 @@ stay there. Delete that directory yourself if you also want them gone.
|
|
|
2593
3025
|
return 1;
|
|
2594
3026
|
}
|
|
2595
3027
|
}
|
|
2596
|
-
const code = runComposeVerb("destroy", { ...opts, name:
|
|
3028
|
+
const code = via === "target" ? await destroyThroughTarget(root, name, opts.run ?? spawnRunner) : runComposeVerb("destroy", { ...opts, name: basename6(root) });
|
|
2597
3029
|
if (code === 0 && root.startsWith(registryRoot() + sep3)) {
|
|
2598
3030
|
rmSync(root, { recursive: true, force: true });
|
|
2599
3031
|
io.stdout(`removed the sandbox entry ${root}
|
|
@@ -2883,13 +3315,13 @@ function sameRemote(url, owner, name) {
|
|
|
2883
3315
|
// src/commands/secret.ts
|
|
2884
3316
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
2885
3317
|
import { existsSync as existsSync12 } from "node:fs";
|
|
2886
|
-
import { join as
|
|
3318
|
+
import { join as join8 } from "node:path";
|
|
2887
3319
|
|
|
2888
3320
|
// src/lib/gitignore.ts
|
|
2889
3321
|
import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2890
|
-
import { dirname as dirname5, join as
|
|
3322
|
+
import { dirname as dirname5, join as join7 } from "node:path";
|
|
2891
3323
|
function appendGitignoreLines(root, lines) {
|
|
2892
|
-
const target =
|
|
3324
|
+
const target = join7(root, ".gitignore");
|
|
2893
3325
|
const existing = existsSync11(target) ? readFileSync8(target, "utf8") : "";
|
|
2894
3326
|
const present = new Set(existing.split("\n").map((line) => line.trimEnd()));
|
|
2895
3327
|
const added = [];
|
|
@@ -2921,7 +3353,7 @@ function isGitIgnored(root, path4) {
|
|
|
2921
3353
|
return result.status === 0;
|
|
2922
3354
|
}
|
|
2923
3355
|
function ignoreSecretsFile(root) {
|
|
2924
|
-
if (!existsSync12(
|
|
3356
|
+
if (!existsSync12(join8(root, ".git"))) return;
|
|
2925
3357
|
if (isGitIgnored(root, ".env")) return;
|
|
2926
3358
|
appendGitignoreLines(root, [".env"]);
|
|
2927
3359
|
}
|
|
@@ -2980,10 +3412,10 @@ async function runSecretList(opts, io) {
|
|
|
2980
3412
|
// src/commands/langfuse.ts
|
|
2981
3413
|
import { copyFileSync as copyFileSync2, existsSync as existsSync14, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync10, rmSync as rmSync2, unlinkSync as unlinkSync4 } from "node:fs";
|
|
2982
3414
|
import { homedir as homedir2, tmpdir as tmpdir2 } from "node:os";
|
|
2983
|
-
import { dirname as dirname7, join as
|
|
3415
|
+
import { dirname as dirname7, join as join11, relative } from "node:path";
|
|
2984
3416
|
|
|
2985
3417
|
// src/lib/tracing/harness-writers.ts
|
|
2986
|
-
import { join as
|
|
3418
|
+
import { join as join9 } from "node:path";
|
|
2987
3419
|
|
|
2988
3420
|
// src/lib/tracing/writer.ts
|
|
2989
3421
|
import { chmodSync, existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
|
|
@@ -3028,13 +3460,13 @@ function isPlainObject(value) {
|
|
|
3028
3460
|
// src/lib/tracing/harness-writers.ts
|
|
3029
3461
|
var CODEX_TRACING_TAGS = ["codex"];
|
|
3030
3462
|
function claudeCodeSettingsPath(home) {
|
|
3031
|
-
return
|
|
3463
|
+
return join9(home, ".claude", "settings.json");
|
|
3032
3464
|
}
|
|
3033
3465
|
function piTracingConfigPath(home) {
|
|
3034
|
-
return
|
|
3466
|
+
return join9(home, ".pi", "agent", "langfuse.json");
|
|
3035
3467
|
}
|
|
3036
3468
|
function codexTracingConfigPath(home) {
|
|
3037
|
-
return
|
|
3469
|
+
return join9(home, ".codex", "langfuse.json");
|
|
3038
3470
|
}
|
|
3039
3471
|
var claudeCodeTracingWriter = (home, settings) => {
|
|
3040
3472
|
const path4 = claudeCodeSettingsPath(home);
|
|
@@ -3278,7 +3710,7 @@ function harnessBinPath(prefix) {
|
|
|
3278
3710
|
}
|
|
3279
3711
|
|
|
3280
3712
|
// src/lib/tracing/providers/langfuse.ts
|
|
3281
|
-
import { join as
|
|
3713
|
+
import { join as join10 } from "node:path";
|
|
3282
3714
|
var LANGFUSE_FRAGMENT_KEYS = [
|
|
3283
3715
|
"LANGFUSE_PUBLIC_KEY",
|
|
3284
3716
|
"LANGFUSE_SECRET_KEY",
|
|
@@ -3286,7 +3718,7 @@ var LANGFUSE_FRAGMENT_KEYS = [
|
|
|
3286
3718
|
"LANGFUSE_TRACING_ENVIRONMENT"
|
|
3287
3719
|
];
|
|
3288
3720
|
function langfuseFragmentPath(home) {
|
|
3289
|
-
return
|
|
3721
|
+
return join10(home, ".config", "agro", "langfuse.env");
|
|
3290
3722
|
}
|
|
3291
3723
|
function loadLangfuseCredentials(root) {
|
|
3292
3724
|
const publicKey = readSecret(root, "LANGFUSE_PUBLIC_KEY");
|
|
@@ -3450,7 +3882,7 @@ function displayPath(home, path4) {
|
|
|
3450
3882
|
return rel.startsWith("..") ? path4 : `~/${rel}`;
|
|
3451
3883
|
}
|
|
3452
3884
|
function withTempHome(fn) {
|
|
3453
|
-
const dir = mkdtempSync2(
|
|
3885
|
+
const dir = mkdtempSync2(join11(tmpdir2(), "agro-langfuse-"));
|
|
3454
3886
|
try {
|
|
3455
3887
|
return fn(dir);
|
|
3456
3888
|
} finally {
|
|
@@ -3459,10 +3891,10 @@ function withTempHome(fn) {
|
|
|
3459
3891
|
}
|
|
3460
3892
|
function probeGeneratedFile(home, render) {
|
|
3461
3893
|
const rel = withTempHome((scratch) => relative(scratch, render(scratch).path));
|
|
3462
|
-
const path4 =
|
|
3894
|
+
const path4 = join11(home, rel);
|
|
3463
3895
|
if (!existsSync14(path4)) return { path: path4, state: "missing" };
|
|
3464
3896
|
const outcome = withTempHome((scratch) => {
|
|
3465
|
-
const copy =
|
|
3897
|
+
const copy = join11(scratch, rel);
|
|
3466
3898
|
mkdirSync6(dirname7(copy), { recursive: true });
|
|
3467
3899
|
copyFileSync2(path4, copy);
|
|
3468
3900
|
return render(scratch).outcome;
|
|
@@ -3525,7 +3957,7 @@ async function runLangfuseApply(opts, io) {
|
|
|
3525
3957
|
return renderAll(ctx, resolved, io, opts.bin, "apply");
|
|
3526
3958
|
}
|
|
3527
3959
|
function zshenvWarning(home) {
|
|
3528
|
-
const zshenv =
|
|
3960
|
+
const zshenv = join11(home, ".zshenv");
|
|
3529
3961
|
if (!existsSync14(zshenv)) return void 0;
|
|
3530
3962
|
if (readFileSync10(zshenv, "utf8").includes("langfuse.env")) return void 0;
|
|
3531
3963
|
return `~/.zshenv exists but does not source ${LANGFUSE_FRAGMENT_LABEL} \u2014 non-interactive shells will carry no Langfuse credentials; add the source block from .agro/install/.zshenv
|
|
@@ -3806,45 +4238,18 @@ async function runLangfuseSetup(opts, io) {
|
|
|
3806
4238
|
// src/commands/sandbox.ts
|
|
3807
4239
|
import { existsSync as existsSync16, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync3, statSync as statSync4 } from "node:fs";
|
|
3808
4240
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
3809
|
-
import { join as
|
|
3810
|
-
|
|
3811
|
-
// src/lib/runtimes/catalog.ts
|
|
3812
|
-
var RUNTIME_CATALOG = Object.freeze([
|
|
3813
|
-
Object.freeze({
|
|
3814
|
-
id: "docker",
|
|
3815
|
-
title: "Docker container",
|
|
3816
|
-
tier: "container",
|
|
3817
|
-
state: "active",
|
|
3818
|
-
provisionable: true,
|
|
3819
|
-
docsPath: "docs/runtimes/overview.md"
|
|
3820
|
-
}),
|
|
3821
|
-
Object.freeze({
|
|
3822
|
-
id: "microsandbox",
|
|
3823
|
-
title: "MicroSandbox",
|
|
3824
|
-
tier: "microvm",
|
|
3825
|
-
state: "planned",
|
|
3826
|
-
provisionable: false,
|
|
3827
|
-
notProvisionableReason: (bin) => `microsandbox is not a provisionable runtime yet; see https://github.com/mifunedev/agro/issues/592. Inside a sandbox run \`${bin} tool install microsandbox\`.`,
|
|
3828
|
-
docsPath: "docs/runtimes/microsandbox.md"
|
|
3829
|
-
})
|
|
3830
|
-
]);
|
|
3831
|
-
function findRuntime(id) {
|
|
3832
|
-
return RUNTIME_CATALOG.find((r) => r.id === id);
|
|
3833
|
-
}
|
|
3834
|
-
function runtimeIds() {
|
|
3835
|
-
return RUNTIME_CATALOG.map((r) => r.id);
|
|
3836
|
-
}
|
|
4241
|
+
import { join as join13, resolve as resolve9 } from "node:path";
|
|
3837
4242
|
|
|
3838
4243
|
// src/lib/star-prompt.ts
|
|
3839
4244
|
import { existsSync as existsSync15, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
3840
|
-
import { join as
|
|
4245
|
+
import { join as join12 } from "node:path";
|
|
3841
4246
|
var STAR_PROMPT_LINE = "\u2B50 If AGRO helps, star https://github.com/mifunedev/agro";
|
|
3842
4247
|
var MARKER = "star-prompt-shown";
|
|
3843
4248
|
function maybePrintStarPrompt(io, env = process.env, isTTY = process.stdout.isTTY === true, stateHome = resolveUserStateHome(env)) {
|
|
3844
4249
|
if (env.AGRO_NO_STAR_PROMPT === "1") return;
|
|
3845
4250
|
if (env.CI !== void 0 && env.CI !== "") return;
|
|
3846
4251
|
if (!isTTY) return;
|
|
3847
|
-
const marker =
|
|
4252
|
+
const marker = join12(stateHome, MARKER);
|
|
3848
4253
|
if (existsSync15(marker)) return;
|
|
3849
4254
|
io.stdout(`${STAR_PROMPT_LINE}
|
|
3850
4255
|
`);
|
|
@@ -3859,7 +4264,7 @@ function maybePrintStarPrompt(io, env = process.env, isTTY = process.stdout.isTT
|
|
|
3859
4264
|
// src/commands/sandbox-list.ts
|
|
3860
4265
|
async function entryStatus(root, name, run) {
|
|
3861
4266
|
try {
|
|
3862
|
-
const target = resolveExecutionTarget({ projectRoot: root, container: name, run });
|
|
4267
|
+
const target = resolveExecutionTarget({ projectRoot: root, container: entrySandboxName(root) ?? name, run });
|
|
3863
4268
|
return await target.status();
|
|
3864
4269
|
} catch {
|
|
3865
4270
|
return "unknown";
|
|
@@ -3921,10 +4326,10 @@ function gitIdentity(run, key) {
|
|
|
3921
4326
|
return (result.stdout ?? "").trim();
|
|
3922
4327
|
}
|
|
3923
4328
|
function isBuildCapable(repo) {
|
|
3924
|
-
return repo !== void 0 && existsSync16(
|
|
4329
|
+
return repo !== void 0 && existsSync16(join13(repo, ".devcontainer", "Dockerfile"));
|
|
3925
4330
|
}
|
|
3926
4331
|
function prepareHomeMount(value) {
|
|
3927
|
-
const path4 =
|
|
4332
|
+
const path4 = resolve9(value);
|
|
3928
4333
|
if (existsSync16(path4)) {
|
|
3929
4334
|
if (!statSync4(path4).isDirectory()) {
|
|
3930
4335
|
throw new Error(`--home-mount path exists and is not a directory: ${path4}`);
|
|
@@ -3968,18 +4373,18 @@ function mergeSettings(...sources) {
|
|
|
3968
4373
|
for (const source of present) overlaySettings(merged, source);
|
|
3969
4374
|
return merged;
|
|
3970
4375
|
}
|
|
3971
|
-
function
|
|
4376
|
+
function nonEmpty2(value) {
|
|
3972
4377
|
return value === void 0 || value === "" ? void 0 : value;
|
|
3973
4378
|
}
|
|
3974
|
-
function seedConfig(name, checkout, seed, run) {
|
|
4379
|
+
function seedConfig(name, checkout, seed, run, runtime = "docker") {
|
|
3975
4380
|
const config = overlaySettings(defaultAgroConfig(name), seed);
|
|
3976
4381
|
config.name = name;
|
|
3977
|
-
config.runtime =
|
|
4382
|
+
config.runtime = runtime;
|
|
3978
4383
|
if (checkout !== void 0) config.checkout = checkout;
|
|
3979
|
-
config.timezone =
|
|
4384
|
+
config.timezone = nonEmpty2(seed?.timezone) ?? hostTimezone();
|
|
3980
4385
|
config.git = {
|
|
3981
|
-
userName:
|
|
3982
|
-
userEmail:
|
|
4386
|
+
userName: nonEmpty2(seed?.git?.userName) ?? gitIdentity(run, "user.name"),
|
|
4387
|
+
userEmail: nonEmpty2(seed?.git?.userEmail) ?? gitIdentity(run, "user.email")
|
|
3983
4388
|
};
|
|
3984
4389
|
config.image = {
|
|
3985
4390
|
...config.image,
|
|
@@ -4020,7 +4425,7 @@ async function runWizard(config, io) {
|
|
|
4020
4425
|
"Host path for /home/sandbox (blank keeps the Docker-managed volume)",
|
|
4021
4426
|
config.storage?.homePath ?? ""
|
|
4022
4427
|
);
|
|
4023
|
-
if (homePath !== "") config.storage = { ...config.storage, homePath:
|
|
4428
|
+
if (homePath !== "") config.storage = { ...config.storage, homePath: resolve9(homePath) };
|
|
4024
4429
|
}
|
|
4025
4430
|
async function runSandboxInstall(opts, io) {
|
|
4026
4431
|
const run = opts.run ?? spawnRunner;
|
|
@@ -4037,7 +4442,8 @@ async function runSandboxInstall(opts, io) {
|
|
|
4037
4442
|
`);
|
|
4038
4443
|
return 1;
|
|
4039
4444
|
}
|
|
4040
|
-
|
|
4445
|
+
if (runtime.id === "openshell") return await installOpenShell(opts, io, run);
|
|
4446
|
+
const checkout = opts.checkout === void 0 ? void 0 : resolve9(opts.checkout);
|
|
4041
4447
|
if (checkout !== void 0 && !existsSync16(checkout)) {
|
|
4042
4448
|
io.stderr(`${opts.bin} sandbox install: --checkout directory does not exist: ${checkout}
|
|
4043
4449
|
`);
|
|
@@ -4055,9 +4461,9 @@ async function runSandboxInstall(opts, io) {
|
|
|
4055
4461
|
const config = seedConfig(name, checkout, mergeSettings(readEntryConfig(name), repoSeed), run);
|
|
4056
4462
|
const buildRequested = config.image?.mode === "build" && opts.noBuild !== true && opts.image !== true && opts.imageRef === void 0;
|
|
4057
4463
|
if (buildRequested && !isBuildCapable(configCheckout(config))) {
|
|
4058
|
-
const target = configCheckout(config) ??
|
|
4464
|
+
const target = configCheckout(config) ?? resolve9(opts.checkout ?? ".");
|
|
4059
4465
|
io.stderr(
|
|
4060
|
-
`${opts.bin} sandbox install: image.mode is "build" but ${
|
|
4466
|
+
`${opts.bin} sandbox install: image.mode is "build" but ${join13(target, ".devcontainer", "Dockerfile")} does not exist \u2014 point --checkout <dir> at a harness checkout that has .devcontainer/Dockerfile, or set image.mode to "image" to run the prebuilt image
|
|
4061
4467
|
`
|
|
4062
4468
|
);
|
|
4063
4469
|
return 1;
|
|
@@ -4101,7 +4507,7 @@ async function runSandboxInstall(opts, io) {
|
|
|
4101
4507
|
...useNoBuild ? { noBuild: true } : {}
|
|
4102
4508
|
};
|
|
4103
4509
|
if (opts.printArgv === true) {
|
|
4104
|
-
const preview = mkdtempSync3(
|
|
4510
|
+
const preview = mkdtempSync3(join13(tmpdir3(), "oh-sandbox-preview-"));
|
|
4105
4511
|
try {
|
|
4106
4512
|
writeAgroConfig(preview, config);
|
|
4107
4513
|
materialize(preview, { ...configCheckout(config) !== void 0 ? { checkout: configCheckout(config) } : {} });
|
|
@@ -4122,16 +4528,77 @@ async function runSandboxInstall(opts, io) {
|
|
|
4122
4528
|
}
|
|
4123
4529
|
return code;
|
|
4124
4530
|
}
|
|
4531
|
+
async function installOpenShell(opts, io, run) {
|
|
4532
|
+
const fail2 = (message) => {
|
|
4533
|
+
io.stderr(`${opts.bin} sandbox install: ${message}
|
|
4534
|
+
`);
|
|
4535
|
+
return 1;
|
|
4536
|
+
};
|
|
4537
|
+
const unsupported = (what) => fail2(`${what} is not supported on the openshell runtime`);
|
|
4538
|
+
if (opts.checkout !== void 0) return unsupported("--checkout");
|
|
4539
|
+
if (opts.homeMount !== void 0) return unsupported("--home-mount");
|
|
4540
|
+
const name = opts.name ?? nextDefaultName(run);
|
|
4541
|
+
try {
|
|
4542
|
+
assertSandboxName(name);
|
|
4543
|
+
} catch (error) {
|
|
4544
|
+
return fail2(error instanceof Error ? error.message : String(error));
|
|
4545
|
+
}
|
|
4546
|
+
const config = seedConfig(name, void 0, readEntryConfig(name), run, "openshell");
|
|
4547
|
+
if (configCheckout(config) !== void 0) return unsupported("checkout");
|
|
4548
|
+
if (nonEmpty2(config.storage?.homePath) !== void 0) return unsupported("storage.homePath");
|
|
4549
|
+
const imageChosen = opts.image === true || opts.imageRef !== void 0;
|
|
4550
|
+
if (config.image?.mode === "build" && !imageChosen) {
|
|
4551
|
+
return unsupported('image.mode "build"');
|
|
4552
|
+
}
|
|
4553
|
+
config.image = {
|
|
4554
|
+
...config.image,
|
|
4555
|
+
mode: "image",
|
|
4556
|
+
...opts.imageRef !== void 0 ? { ref: opts.imageRef } : {}
|
|
4557
|
+
};
|
|
4558
|
+
const root = entryRoot(name);
|
|
4559
|
+
const image = openshellImage(config);
|
|
4560
|
+
if (opts.printArgv === true) {
|
|
4561
|
+
io.stdout(`${[OPENSHELL_BIN, ...createArgv({ name, image, entryRoot: root })].join(" ")}
|
|
4562
|
+
`);
|
|
4563
|
+
return 0;
|
|
4564
|
+
}
|
|
4565
|
+
if (runningInsideSandbox()) return fail2(new HostOnlyError(`\`${opts.bin} sandbox\``).message);
|
|
4566
|
+
try {
|
|
4567
|
+
const preflight = openshellPreflight(run);
|
|
4568
|
+
if (!preflight.ok) return fail2(preflight.hint);
|
|
4569
|
+
} catch (error) {
|
|
4570
|
+
return fail2(error instanceof Error ? error.message : String(error));
|
|
4571
|
+
}
|
|
4572
|
+
const created = !existsSync16(root);
|
|
4573
|
+
mkdirSync8(root, { recursive: true });
|
|
4574
|
+
writeAgroConfig(root, config);
|
|
4575
|
+
materialize(root);
|
|
4576
|
+
try {
|
|
4577
|
+
await new OpenShellExecutionTarget({ name, entryRoot: root, image, run }).provision();
|
|
4578
|
+
} catch (error) {
|
|
4579
|
+
if (created) rmSync3(root, { recursive: true, force: true });
|
|
4580
|
+
io.stderr(`${opts.bin} sandbox install: ${error instanceof Error ? error.message : String(error)}
|
|
4581
|
+
`);
|
|
4582
|
+
if (!(error instanceof ExecutionExitError)) return 1;
|
|
4583
|
+
io.stderr(`${opts.bin} sandbox install: ${openshellCleanupHint(name)}
|
|
4584
|
+
`);
|
|
4585
|
+
return error.exitCode;
|
|
4586
|
+
}
|
|
4587
|
+
io.stdout(`next: ${opts.bin} shell ${name}
|
|
4588
|
+
`);
|
|
4589
|
+
maybePrintStarPrompt(io);
|
|
4590
|
+
return 0;
|
|
4591
|
+
}
|
|
4125
4592
|
|
|
4126
4593
|
// src/services/sandbox-upgrade.ts
|
|
4127
4594
|
import { closeSync as closeSync2, existsSync as existsSync17, openSync as openSync2, readFileSync as readFileSync11, unlinkSync as unlinkSync5, writeSync } from "node:fs";
|
|
4128
|
-
import { join as
|
|
4595
|
+
import { join as join14 } from "node:path";
|
|
4129
4596
|
function errorMessage(error) {
|
|
4130
4597
|
return error instanceof Error ? error.message : String(error);
|
|
4131
4598
|
}
|
|
4132
4599
|
function conflictingDotenvImage(root, imageRef) {
|
|
4133
|
-
const rootEnv =
|
|
4134
|
-
const fallbackEnv =
|
|
4600
|
+
const rootEnv = join14(root, ".env");
|
|
4601
|
+
const fallbackEnv = join14(root, ".devcontainer", ".env");
|
|
4135
4602
|
const file = existsSync17(rootEnv) ? rootEnv : fallbackEnv;
|
|
4136
4603
|
if (!existsSync17(file)) return void 0;
|
|
4137
4604
|
for (const line of readFileSync11(file, "utf8").split(/\r?\n/)) {
|
|
@@ -4169,7 +4636,8 @@ async function runSandboxUpgrade(opts, io) {
|
|
|
4169
4636
|
`);
|
|
4170
4637
|
return 1;
|
|
4171
4638
|
}
|
|
4172
|
-
|
|
4639
|
+
routeVerb(root, "upgrade", entrySandboxName(root) ?? opts.name);
|
|
4640
|
+
const lock = join14(root, ".sandbox-upgrade.lock");
|
|
4173
4641
|
let fd;
|
|
4174
4642
|
try {
|
|
4175
4643
|
fd = openSync2(lock, "wx", 384);
|
|
@@ -4284,6 +4752,10 @@ identity, SSH (with its host port), the host Docker socket, and the host path
|
|
|
4284
4752
|
for /home/sandbox. With --checkout it seeds those answers from that
|
|
4285
4753
|
checkout's ${stateNames(bin).configFile}.
|
|
4286
4754
|
|
|
4755
|
+
\`install openshell\` never prompts. The command needs the host \`openshell\` CLI
|
|
4756
|
+
and a connected gateway, and runs the prebuilt image. The command refuses
|
|
4757
|
+
--checkout, --home-mount, and image.mode "build".
|
|
4758
|
+
|
|
4287
4759
|
Flags:
|
|
4288
4760
|
--name <name> Registry entry name (default: the lowest free ${DEFAULT_NAME_PREFIX}<n>)
|
|
4289
4761
|
--checkout <dir> Bind this checkout into the sandbox and seed the defaults
|
|
@@ -4306,7 +4778,7 @@ Flags:
|
|
|
4306
4778
|
${stateNames(bin).configFile} image.ref > ${officialImageRef(AGRO_VERSION)}
|
|
4307
4779
|
--image=<ref> Run a custom image (implies --image); conflicts with --version
|
|
4308
4780
|
--no-build Suppress the local build and reuse an existing image
|
|
4309
|
-
--print-argv Print the docker compose argv that would run, then exit
|
|
4781
|
+
--print-argv Print the docker compose or openshell argv that would run, then exit
|
|
4310
4782
|
without writing an entry
|
|
4311
4783
|
--json Machine-readable output (list)
|
|
4312
4784
|
|
|
@@ -4499,7 +4971,7 @@ async function runSandboxCommand(rest, bin) {
|
|
|
4499
4971
|
// src/commands/harness.ts
|
|
4500
4972
|
import { existsSync as existsSync20 } from "node:fs";
|
|
4501
4973
|
import { homedir as homedir3 } from "node:os";
|
|
4502
|
-
import { delimiter as delimiter2, join as
|
|
4974
|
+
import { delimiter as delimiter2, join as join17 } from "node:path";
|
|
4503
4975
|
|
|
4504
4976
|
// src/lib/docs.ts
|
|
4505
4977
|
var SOURCE_DOCS_BASE = "https://github.com/mifunedev/agro/blob/main/";
|
|
@@ -4513,7 +4985,7 @@ function sourceDocsUrl(docsPath) {
|
|
|
4513
4985
|
|
|
4514
4986
|
// src/lib/host-config.ts
|
|
4515
4987
|
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync12, renameSync as renameSync4, unlinkSync as unlinkSync6, writeFileSync as writeFileSync9 } from "node:fs";
|
|
4516
|
-
import { basename as
|
|
4988
|
+
import { basename as basename7, dirname as dirname8, isAbsolute, join as join15, resolve as resolve10 } from "node:path";
|
|
4517
4989
|
var HOST_CONFIG_MODE = 420;
|
|
4518
4990
|
var HOST_CONFIG_FILE = "config.json";
|
|
4519
4991
|
var WORKSPACES_SUBDIR = "workspaces";
|
|
@@ -4525,7 +4997,7 @@ function hostStateHome(env, home) {
|
|
|
4525
4997
|
return resolveUserStateHome(env, home);
|
|
4526
4998
|
}
|
|
4527
4999
|
function hostConfigPath(env, home) {
|
|
4528
|
-
return
|
|
5000
|
+
return join15(hostStateHome(env, home), HOST_CONFIG_FILE);
|
|
4529
5001
|
}
|
|
4530
5002
|
function assertWorkspaceName(name) {
|
|
4531
5003
|
if (!SANDBOX_NAME_PATTERN.test(name)) {
|
|
@@ -4535,14 +5007,14 @@ function assertWorkspaceName(name) {
|
|
|
4535
5007
|
}
|
|
4536
5008
|
}
|
|
4537
5009
|
function workspacesRoot(env, home) {
|
|
4538
|
-
return
|
|
5010
|
+
return join15(hostStateHome(env, home), WORKSPACES_SUBDIR);
|
|
4539
5011
|
}
|
|
4540
5012
|
function workspaceRoot(name, env, home) {
|
|
4541
5013
|
assertWorkspaceName(name);
|
|
4542
|
-
return
|
|
5014
|
+
return join15(workspacesRoot(env, home), name);
|
|
4543
5015
|
}
|
|
4544
5016
|
function defaultHarnessRoot(env, home) {
|
|
4545
|
-
return
|
|
5017
|
+
return join15(workspacesRoot(env, home), DEFAULT_WORKSPACE_NAME);
|
|
4546
5018
|
}
|
|
4547
5019
|
function validateHostConfig(value) {
|
|
4548
5020
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -4619,7 +5091,7 @@ function readHostConfig(env, home) {
|
|
|
4619
5091
|
try {
|
|
4620
5092
|
parsed = JSON.parse(raw);
|
|
4621
5093
|
} catch {
|
|
4622
|
-
throw new Error(`${
|
|
5094
|
+
throw new Error(`${basename7(path4)} is not valid JSON: ${path4}`);
|
|
4623
5095
|
}
|
|
4624
5096
|
return validateHostConfig(parsed);
|
|
4625
5097
|
}
|
|
@@ -4642,15 +5114,15 @@ function writeHostConfig(config, env, home) {
|
|
|
4642
5114
|
}
|
|
4643
5115
|
}
|
|
4644
5116
|
function resolveHarnessRoot(explicit, env, home) {
|
|
4645
|
-
if (typeof explicit === "string" && explicit !== "") return
|
|
5117
|
+
if (typeof explicit === "string" && explicit !== "") return resolve10(explicit);
|
|
4646
5118
|
const configured = readHostConfig(env, home).harnessRoot;
|
|
4647
|
-
if (typeof configured === "string" && configured !== "") return
|
|
5119
|
+
if (typeof configured === "string" && configured !== "") return resolve10(configured);
|
|
4648
5120
|
return defaultHarnessRoot(env, home);
|
|
4649
5121
|
}
|
|
4650
5122
|
function recordHarnessRoot(root, env, home) {
|
|
4651
5123
|
const config = readHostConfig(env, home);
|
|
4652
5124
|
const recorded = config.harnessRoot;
|
|
4653
|
-
if (typeof recorded === "string" && recorded !== "" &&
|
|
5125
|
+
if (typeof recorded === "string" && recorded !== "" && resolve10(recorded) === root) return;
|
|
4654
5126
|
writeHostConfig({ ...config, harnessRoot: root }, env, home);
|
|
4655
5127
|
}
|
|
4656
5128
|
function fieldError2(path4, requirement) {
|
|
@@ -4659,7 +5131,7 @@ function fieldError2(path4, requirement) {
|
|
|
4659
5131
|
|
|
4660
5132
|
// src/lib/host-workspace.ts
|
|
4661
5133
|
import { existsSync as existsSync19, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync4, readdirSync as readdirSync3, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync5 } from "node:fs";
|
|
4662
|
-
import { dirname as dirname9, join as
|
|
5134
|
+
import { dirname as dirname9, join as join16, resolve as resolve11 } from "node:path";
|
|
4663
5135
|
var AGRO_REPO_URL = "https://github.com/mifunedev/agro.git";
|
|
4664
5136
|
var IGNORABLE_ENTRIES = [
|
|
4665
5137
|
REGISTRY_SUBDIR,
|
|
@@ -4671,15 +5143,15 @@ function isDirectory3(path4) {
|
|
|
4671
5143
|
return statSync5(path4, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
4672
5144
|
}
|
|
4673
5145
|
function stateHomes(home) {
|
|
4674
|
-
return STATE_HOME_DIRS.map((name) =>
|
|
5146
|
+
return STATE_HOME_DIRS.map((name) => join16(home, name));
|
|
4675
5147
|
}
|
|
4676
5148
|
function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
4677
|
-
const target =
|
|
5149
|
+
const target = resolve11(root);
|
|
4678
5150
|
const enclosing = stateHomes(home).find((state) => target === state);
|
|
4679
5151
|
if (enclosing === void 0) return void 0;
|
|
4680
5152
|
return `${bin} ${verb}: the ${verb} root ${target} is the state home ${enclosing} itself.
|
|
4681
5153
|
A workspace there collides with AGRO state and blocks every command run from ${home}.
|
|
4682
|
-
Move it out \u2014 for example \`mv ${enclosing} ${
|
|
5154
|
+
Move it out \u2014 for example \`mv ${enclosing} ${join16(home, "agro")}\` \u2014 then re-run this command.
|
|
4683
5155
|
`;
|
|
4684
5156
|
}
|
|
4685
5157
|
function isIgnorableEntry(name) {
|
|
@@ -4696,24 +5168,24 @@ function cloneInto(target, run, root, ref) {
|
|
|
4696
5168
|
assertCloned(run("git", ["clone", ...branch, AGRO_REPO_URL, target], { stdio: "inherit" }), root);
|
|
4697
5169
|
}
|
|
4698
5170
|
function cloneThroughStaging(root, run, ref) {
|
|
4699
|
-
const staging = mkdtempSync4(
|
|
5171
|
+
const staging = mkdtempSync4(join16(dirname9(root), "agro-clone-"));
|
|
4700
5172
|
try {
|
|
4701
|
-
const checkout =
|
|
5173
|
+
const checkout = join16(staging, "agro");
|
|
4702
5174
|
cloneInto(checkout, run, root, ref);
|
|
4703
5175
|
for (const name of readdirSync3(checkout)) {
|
|
4704
|
-
const destination =
|
|
5176
|
+
const destination = join16(root, name);
|
|
4705
5177
|
if (existsSync19(destination)) {
|
|
4706
5178
|
throw new Error(`could not move ${name} into ${root} \u2014 the path already exists`);
|
|
4707
5179
|
}
|
|
4708
|
-
renameSync5(
|
|
5180
|
+
renameSync5(join16(checkout, name), destination);
|
|
4709
5181
|
}
|
|
4710
5182
|
} finally {
|
|
4711
5183
|
rmSync4(staging, { recursive: true, force: true });
|
|
4712
5184
|
}
|
|
4713
5185
|
}
|
|
4714
5186
|
function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
4715
|
-
const root =
|
|
4716
|
-
if (existsSync19(
|
|
5187
|
+
const root = resolve11(path4);
|
|
5188
|
+
if (existsSync19(join16(root, ".git"))) return { root, action: "reused" };
|
|
4717
5189
|
const stats = statSync5(root, { throwIfNoEntry: false });
|
|
4718
5190
|
if (stats === void 0) {
|
|
4719
5191
|
mkdirSync10(dirname9(root), { recursive: true });
|
|
@@ -4735,11 +5207,11 @@ function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
|
4735
5207
|
function listHostWorkspaces(env, home) {
|
|
4736
5208
|
const root = workspacesRoot(env, home);
|
|
4737
5209
|
if (!isDirectory3(root)) return [];
|
|
4738
|
-
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => SANDBOX_NAME_PATTERN.test(name)).filter((name) => existsSync19(
|
|
5210
|
+
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => SANDBOX_NAME_PATTERN.test(name)).filter((name) => existsSync19(join16(root, name, ".git"))).sort();
|
|
4739
5211
|
}
|
|
4740
5212
|
function resolveExistingWorkspace(bin, verb, path4, env, home) {
|
|
4741
|
-
const root =
|
|
4742
|
-
if (existsSync19(
|
|
5213
|
+
const root = resolve11(path4);
|
|
5214
|
+
if (existsSync19(join16(root, ".git"))) return { ok: true, root };
|
|
4743
5215
|
const existing = listHostWorkspaces(env, home);
|
|
4744
5216
|
const inventory = existing.length === 0 ? "No host workspace exists yet.\n" : `Workspaces that exist: ${existing.join(", ")}
|
|
4745
5217
|
`;
|
|
@@ -4776,7 +5248,7 @@ function targetFor(root, cwd, run, env) {
|
|
|
4776
5248
|
});
|
|
4777
5249
|
}
|
|
4778
5250
|
function hostPrefix(home) {
|
|
4779
|
-
return
|
|
5251
|
+
return join17(home, ".local");
|
|
4780
5252
|
}
|
|
4781
5253
|
function pathEntries(env) {
|
|
4782
5254
|
return (env.PATH ?? "").split(delimiter2).filter((part) => part !== "");
|
|
@@ -4823,7 +5295,7 @@ async function sandboxMarkerExists(target, entry) {
|
|
|
4823
5295
|
return r.exitCode === 0;
|
|
4824
5296
|
}
|
|
4825
5297
|
function hostBinaryUnderPrefix(entry, prefix) {
|
|
4826
|
-
return existsSync20(
|
|
5298
|
+
return existsSync20(join17(harnessBinPath(prefix), entry.binary));
|
|
4827
5299
|
}
|
|
4828
5300
|
function stateOf(entry, installed, location) {
|
|
4829
5301
|
return {
|
|
@@ -4844,7 +5316,7 @@ function probeableHost(env, home) {
|
|
|
4844
5316
|
return void 0;
|
|
4845
5317
|
}
|
|
4846
5318
|
const prefix = hostPrefix(home);
|
|
4847
|
-
if (!existsSync20(
|
|
5319
|
+
if (!existsSync20(join17(root, ".git")) && !existsSync20(prefix)) return void 0;
|
|
4848
5320
|
return { root, prefix };
|
|
4849
5321
|
}
|
|
4850
5322
|
async function collectStates(root, cwd, run, home, env, only) {
|
|
@@ -5445,7 +5917,7 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
5445
5917
|
// src/commands/workspace.ts
|
|
5446
5918
|
import { existsSync as existsSync21, rmSync as rmSync5 } from "node:fs";
|
|
5447
5919
|
import { homedir as homedir4 } from "node:os";
|
|
5448
|
-
import { join as
|
|
5920
|
+
import { join as join18, resolve as resolve12 } from "node:path";
|
|
5449
5921
|
function homeOf2(opts) {
|
|
5450
5922
|
return (opts.homedir ?? homedir4)();
|
|
5451
5923
|
}
|
|
@@ -5454,7 +5926,7 @@ function messageOf2(error) {
|
|
|
5454
5926
|
}
|
|
5455
5927
|
function recordedRoot(env, home) {
|
|
5456
5928
|
const configured = readHostConfig(env, home).harnessRoot;
|
|
5457
|
-
return typeof configured === "string" && configured !== "" ?
|
|
5929
|
+
return typeof configured === "string" && configured !== "" ? resolve12(configured) : void 0;
|
|
5458
5930
|
}
|
|
5459
5931
|
function renderTable2(rows, io) {
|
|
5460
5932
|
const header2 = ["WORKSPACE", "DEFAULT", "PATH"];
|
|
@@ -5479,7 +5951,7 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
5479
5951
|
let root;
|
|
5480
5952
|
const chosen = name ?? DEFAULT_WORKSPACE_NAME;
|
|
5481
5953
|
try {
|
|
5482
|
-
root = opts.path !== void 0 ?
|
|
5954
|
+
root = opts.path !== void 0 ? resolve12(opts.path) : workspaceRoot(chosen, env, home);
|
|
5483
5955
|
} catch (err) {
|
|
5484
5956
|
io.stderr(`${bin} workspace: ${messageOf2(err)}
|
|
5485
5957
|
`);
|
|
@@ -5493,7 +5965,7 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
5493
5965
|
let action;
|
|
5494
5966
|
const preexisting = existsSync21(root);
|
|
5495
5967
|
try {
|
|
5496
|
-
if (!opts.json && !existsSync21(
|
|
5968
|
+
if (!opts.json && !existsSync21(join18(root, ".git"))) {
|
|
5497
5969
|
const at = opts.ref !== void 0 ? ` at ${opts.ref}` : "";
|
|
5498
5970
|
io.stdout(`cloning ${AGRO_REPO_URL}${at} into ${root}\u2026
|
|
5499
5971
|
`);
|
|
@@ -5538,7 +6010,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
5538
6010
|
const recorded = recordedRoot(env, home);
|
|
5539
6011
|
const registry = workspacesRoot(env, home);
|
|
5540
6012
|
rows = listHostWorkspaces(env, home).map((name) => {
|
|
5541
|
-
const root =
|
|
6013
|
+
const root = join18(registry, name);
|
|
5542
6014
|
return { name, root, default: recorded === root };
|
|
5543
6015
|
});
|
|
5544
6016
|
} catch (err) {
|
|
@@ -5565,7 +6037,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
5565
6037
|
// src/commands/tool.ts
|
|
5566
6038
|
import { existsSync as existsSync22 } from "node:fs";
|
|
5567
6039
|
import { homedir as homedir5, userInfo as userInfo2 } from "node:os";
|
|
5568
|
-
import { delimiter as delimiter3, join as
|
|
6040
|
+
import { delimiter as delimiter3, join as join19 } from "node:path";
|
|
5569
6041
|
|
|
5570
6042
|
// src/lib/tools/catalog.ts
|
|
5571
6043
|
var TOOLS_DOC = "docs/installation.md";
|
|
@@ -5996,7 +6468,7 @@ function targetFor2(root, run, env) {
|
|
|
5996
6468
|
});
|
|
5997
6469
|
}
|
|
5998
6470
|
function hostPrefix2(home) {
|
|
5999
|
-
return
|
|
6471
|
+
return join19(home, ".local");
|
|
6000
6472
|
}
|
|
6001
6473
|
function sandboxMarkerPath2(entry) {
|
|
6002
6474
|
return `${SANDBOX_HARNESS_PREFIX}/share/agro/tools/${entry.id}.installed`;
|
|
@@ -6010,7 +6482,7 @@ async function sandboxMarkerExists2(target, entry) {
|
|
|
6010
6482
|
return r.exitCode === 0;
|
|
6011
6483
|
}
|
|
6012
6484
|
function hostBinaryUnderPrefix2(entry, prefix) {
|
|
6013
|
-
return existsSync22(
|
|
6485
|
+
return existsSync22(join19(harnessBinPath(prefix), entry.binary));
|
|
6014
6486
|
}
|
|
6015
6487
|
function pathEntries2(env) {
|
|
6016
6488
|
return (env.PATH ?? "").split(delimiter3).filter((part) => part !== "");
|
|
@@ -6090,7 +6562,7 @@ function probeableHost2(env, home) {
|
|
|
6090
6562
|
return void 0;
|
|
6091
6563
|
}
|
|
6092
6564
|
const prefix = hostPrefix2(home);
|
|
6093
|
-
if (!existsSync22(
|
|
6565
|
+
if (!existsSync22(join19(root, ".git")) && !existsSync22(prefix)) return void 0;
|
|
6094
6566
|
return { root, prefix };
|
|
6095
6567
|
}
|
|
6096
6568
|
async function collectRows(root, run, home, env, only) {
|
|
@@ -6656,7 +7128,7 @@ var AGRO_COMMANDS = Object.freeze({
|
|
|
6656
7128
|
});
|
|
6657
7129
|
|
|
6658
7130
|
// src/cli.ts
|
|
6659
|
-
var DEFAULT_SOURCE_CONTROL_DIR =
|
|
7131
|
+
var DEFAULT_SOURCE_CONTROL_DIR = resolve13(
|
|
6660
7132
|
dirname10(fileURLToPath(import.meta.url)),
|
|
6661
7133
|
"../.."
|
|
6662
7134
|
);
|
|
@@ -7501,18 +7973,18 @@ function parseGatewayArgs(rest) {
|
|
|
7501
7973
|
return { ok: true, args: { help: false, passthrough: [...rest] } };
|
|
7502
7974
|
}
|
|
7503
7975
|
function bundledPayloadExists(bundled, exists = existsSync23) {
|
|
7504
|
-
return exists(
|
|
7976
|
+
return exists(join20(bundled.sourceControlDir, "manifest.json"));
|
|
7505
7977
|
}
|
|
7506
7978
|
function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
7507
7979
|
const exists = bundled.exists ?? existsSync23;
|
|
7508
7980
|
if (args.fromDir !== void 0) {
|
|
7509
|
-
return { kind: "local", fromDir:
|
|
7981
|
+
return { kind: "local", fromDir: resolve13(args.fromDir) };
|
|
7510
7982
|
}
|
|
7511
7983
|
if (args.fromRemote) {
|
|
7512
7984
|
return { kind: "remote", ref: args.ref };
|
|
7513
7985
|
}
|
|
7514
7986
|
if (bundledPayloadExists(bundled, exists)) {
|
|
7515
|
-
return { kind: "local", fromDir:
|
|
7987
|
+
return { kind: "local", fromDir: resolve13(bundled.sourceControlDir, "..") };
|
|
7516
7988
|
}
|
|
7517
7989
|
return {
|
|
7518
7990
|
kind: "remote",
|
|
@@ -7524,7 +7996,7 @@ function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
|
7524
7996
|
function readPayloadVersion(checkoutDir) {
|
|
7525
7997
|
try {
|
|
7526
7998
|
const parsed = JSON.parse(
|
|
7527
|
-
readFileSync13(
|
|
7999
|
+
readFileSync13(join20(resolveControlDir(checkoutDir), "cli", "package.json"), "utf8")
|
|
7528
8000
|
);
|
|
7529
8001
|
if (parsed && typeof parsed.version === "string") return parsed.version;
|
|
7530
8002
|
} catch {
|
|
@@ -7533,7 +8005,7 @@ function readPayloadVersion(checkoutDir) {
|
|
|
7533
8005
|
}
|
|
7534
8006
|
async function runWithRemoteSource(hooks, fn) {
|
|
7535
8007
|
const fetch2 = hooks.fetch ?? fetchRemoteSource;
|
|
7536
|
-
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(
|
|
8008
|
+
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(join20(tmpdir4(), "oh-remote-")));
|
|
7537
8009
|
const rm = hooks.rm ?? ((dir) => rmSync6(dir, { recursive: true, force: true }));
|
|
7538
8010
|
const out = hooks.stdout ?? ((s) => process.stdout.write(s));
|
|
7539
8011
|
const checkoutDir = mkdtemp();
|
|
@@ -7899,7 +8371,19 @@ function lifecycleIo() {
|
|
|
7899
8371
|
stderr: (s) => process.stderr.write(s)
|
|
7900
8372
|
};
|
|
7901
8373
|
}
|
|
7902
|
-
|
|
8374
|
+
async function runCli(argv) {
|
|
8375
|
+
try {
|
|
8376
|
+
return await main(argv);
|
|
8377
|
+
} catch (err) {
|
|
8378
|
+
if (err instanceof RuntimeUnsupportedError) {
|
|
8379
|
+
process.stderr.write(`${err.message}
|
|
8380
|
+
`);
|
|
8381
|
+
return 1;
|
|
8382
|
+
}
|
|
8383
|
+
throw err;
|
|
8384
|
+
}
|
|
8385
|
+
}
|
|
8386
|
+
runCli(process.argv.slice(2)).then(
|
|
7903
8387
|
(code) => process.exit(code),
|
|
7904
8388
|
(err) => {
|
|
7905
8389
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -7941,6 +8425,7 @@ export {
|
|
|
7941
8425
|
printVendorHelp,
|
|
7942
8426
|
printWorkspaceHelp,
|
|
7943
8427
|
resolveVendorSource,
|
|
8428
|
+
runCli,
|
|
7944
8429
|
runWithRemoteSource,
|
|
7945
8430
|
runtimeLines
|
|
7946
8431
|
};
|
package/package.json
CHANGED