@mifune/agro 0.15.1 → 0.17.0-rc.1
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/README.md +14 -2
- package/dist/agro.js +1217 -452
- package/package.json +1 -1
package/dist/agro.js
CHANGED
|
@@ -7,8 +7,8 @@ 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
|
|
11
|
-
import { existsSync as
|
|
10
|
+
import { dirname as dirname10, join as join20, resolve as resolve13 } from "node:path";
|
|
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
|
|
|
14
14
|
// src/commands/update.ts
|
|
@@ -835,6 +835,49 @@ import { resolve as resolve3, sep } from "node:path";
|
|
|
835
835
|
// src/lib/agro-config.ts
|
|
836
836
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
837
837
|
import { basename as basename3, dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
838
|
+
|
|
839
|
+
// src/lib/runtimes/catalog.ts
|
|
840
|
+
var CATALOG = [
|
|
841
|
+
{
|
|
842
|
+
id: "docker",
|
|
843
|
+
title: "Docker container",
|
|
844
|
+
tier: "container",
|
|
845
|
+
state: "active",
|
|
846
|
+
provisionable: true,
|
|
847
|
+
docsPath: "docs/runtimes/docker.md"
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
id: "microsandbox",
|
|
851
|
+
title: "MicroSandbox",
|
|
852
|
+
tier: "microvm",
|
|
853
|
+
state: "planned",
|
|
854
|
+
provisionable: false,
|
|
855
|
+
notProvisionableReason: (bin) => `microsandbox is not a provisionable runtime yet; see docs/rfcs/rfc-runtime-support.md. Inside a sandbox run \`${bin} tool install microsandbox\`.`,
|
|
856
|
+
docsPath: "docs/runtimes/microsandbox.md"
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
id: "openshell",
|
|
860
|
+
title: "NVIDIA OpenShell",
|
|
861
|
+
tier: "container",
|
|
862
|
+
state: "active",
|
|
863
|
+
provisionable: true,
|
|
864
|
+
docsPath: "docs/runtimes/openshell.md"
|
|
865
|
+
}
|
|
866
|
+
];
|
|
867
|
+
var RUNTIME_CATALOG = Object.freeze(
|
|
868
|
+
CATALOG.map((entry) => Object.freeze({ ...entry }))
|
|
869
|
+
);
|
|
870
|
+
var PROVISIONABLE_RUNTIMES = Object.freeze(
|
|
871
|
+
CATALOG.filter((entry) => entry.provisionable).map((entry) => entry.id)
|
|
872
|
+
);
|
|
873
|
+
function findRuntime(id) {
|
|
874
|
+
return RUNTIME_CATALOG.find((r) => r.id === id);
|
|
875
|
+
}
|
|
876
|
+
function runtimeIds() {
|
|
877
|
+
return RUNTIME_CATALOG.map((r) => r.id);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// src/lib/agro-config.ts
|
|
838
881
|
var AGRO_CONFIG_FILE = NAMES.configFile;
|
|
839
882
|
var AGRO_CONFIG_MODE = 420;
|
|
840
883
|
var RESERVED_HOME_PATHS = [
|
|
@@ -856,7 +899,7 @@ var RESERVED_HOME_PATHS = [
|
|
|
856
899
|
"/usr",
|
|
857
900
|
"/var"
|
|
858
901
|
];
|
|
859
|
-
var SANDBOX_RUNTIMES =
|
|
902
|
+
var SANDBOX_RUNTIMES = PROVISIONABLE_RUNTIMES;
|
|
860
903
|
function configCheckout(config) {
|
|
861
904
|
const value = config.checkout ?? config.repo;
|
|
862
905
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
@@ -885,6 +928,13 @@ function defaultAgroConfig(name) {
|
|
|
885
928
|
composeOverrides: []
|
|
886
929
|
};
|
|
887
930
|
}
|
|
931
|
+
function entryRuntime(root) {
|
|
932
|
+
try {
|
|
933
|
+
return readAgroConfig(agroConfigPath(root)).runtime ?? "docker";
|
|
934
|
+
} catch {
|
|
935
|
+
return "docker";
|
|
936
|
+
}
|
|
937
|
+
}
|
|
888
938
|
function readAgroConfig(path4) {
|
|
889
939
|
if (!existsSync4(path4)) return defaultAgroConfig(basename3(dirname2(resolve2(path4))));
|
|
890
940
|
let raw;
|
|
@@ -1274,8 +1324,8 @@ function redact(token) {
|
|
|
1274
1324
|
async function ask(question) {
|
|
1275
1325
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1276
1326
|
try {
|
|
1277
|
-
return await new Promise((
|
|
1278
|
-
rl.question(` ${question} `, (answer) =>
|
|
1327
|
+
return await new Promise((resolve14) => {
|
|
1328
|
+
rl.question(` ${question} `, (answer) => resolve14(answer.trim()));
|
|
1279
1329
|
});
|
|
1280
1330
|
} finally {
|
|
1281
1331
|
rl.close();
|
|
@@ -1287,7 +1337,7 @@ async function askSecret(question) {
|
|
|
1287
1337
|
process.stdin.setRawMode(true);
|
|
1288
1338
|
process.stdin.resume();
|
|
1289
1339
|
const bytes = [];
|
|
1290
|
-
return await new Promise((
|
|
1340
|
+
return await new Promise((resolve14) => {
|
|
1291
1341
|
const cleanup = () => {
|
|
1292
1342
|
try {
|
|
1293
1343
|
process.stdin.setRawMode(false);
|
|
@@ -1305,7 +1355,7 @@ async function askSecret(question) {
|
|
|
1305
1355
|
} else if (byte === 13 || byte === 10) {
|
|
1306
1356
|
cleanup();
|
|
1307
1357
|
process.stdout.write("\n");
|
|
1308
|
-
|
|
1358
|
+
resolve14(Buffer.from(bytes).toString("utf8").trim());
|
|
1309
1359
|
return;
|
|
1310
1360
|
} else if (byte === 127 || byte === 8) {
|
|
1311
1361
|
if (bytes.length > 0) {
|
|
@@ -1355,10 +1405,10 @@ async function askSecretPiped(question) {
|
|
|
1355
1405
|
};
|
|
1356
1406
|
process.on("SIGINT", onSigint);
|
|
1357
1407
|
try {
|
|
1358
|
-
return await new Promise((
|
|
1408
|
+
return await new Promise((resolve14) => {
|
|
1359
1409
|
rl.question(` ${question} `, (answer) => {
|
|
1360
1410
|
process.stdout.write("\n");
|
|
1361
|
-
|
|
1411
|
+
resolve14(answer.trim());
|
|
1362
1412
|
});
|
|
1363
1413
|
});
|
|
1364
1414
|
} finally {
|
|
@@ -1736,6 +1786,9 @@ printf 'free\\n'
|
|
|
1736
1786
|
exit 0
|
|
1737
1787
|
`;
|
|
1738
1788
|
|
|
1789
|
+
// agro-asset:/home/runner/work/agro/agro/.devcontainer/openshell-policy.yaml
|
|
1790
|
+
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";
|
|
1791
|
+
|
|
1739
1792
|
// src/lib/registry.ts
|
|
1740
1793
|
var SANDBOX_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
1741
1794
|
var DEFAULT_NAME_PREFIX = "agro-sbx-";
|
|
@@ -1792,10 +1845,12 @@ function nextDefaultName(run = spawnRunner) {
|
|
|
1792
1845
|
if (!taken.has(candidate)) return candidate;
|
|
1793
1846
|
}
|
|
1794
1847
|
}
|
|
1795
|
-
function
|
|
1796
|
-
|
|
1848
|
+
function openshellFiles(entry) {
|
|
1849
|
+
return [{ dest: join4(entry, "openshell-policy.yaml"), body: openshell_policy_default, mode: 420 }];
|
|
1850
|
+
}
|
|
1851
|
+
function dockerFiles(entry, opts) {
|
|
1797
1852
|
const scripts = join4(resolveProjectLayout(entry).controlDir, "scripts");
|
|
1798
|
-
|
|
1853
|
+
return [
|
|
1799
1854
|
{
|
|
1800
1855
|
dest: join4(entry, ".devcontainer", "docker-compose.yml"),
|
|
1801
1856
|
body: (opts.checkout ?? opts.repo) === void 0 ? docker_compose_image_only_default : docker_compose_default,
|
|
@@ -1807,6 +1862,10 @@ function materialize(root, opts = {}) {
|
|
|
1807
1862
|
{ dest: join4(scripts, "paths.sh"), body: paths_default, mode: 420 },
|
|
1808
1863
|
{ dest: join4(scripts, "check-host-port.sh"), body: check_host_port_default, mode: 493 }
|
|
1809
1864
|
];
|
|
1865
|
+
}
|
|
1866
|
+
function materialize(root, opts = {}) {
|
|
1867
|
+
const entry = resolve5(root);
|
|
1868
|
+
const files = entryRuntime(entry) === "openshell" ? openshellFiles(entry) : dockerFiles(entry, opts);
|
|
1810
1869
|
for (const file of files) {
|
|
1811
1870
|
const dest = resolve5(file.dest);
|
|
1812
1871
|
if (!dest.startsWith(entry + sep2)) {
|
|
@@ -1965,11 +2024,27 @@ function stripQuotes2(s) {
|
|
|
1965
2024
|
// src/commands/lifecycle.ts
|
|
1966
2025
|
import { existsSync as existsSync10, mkdtempSync, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1967
2026
|
import { tmpdir } from "node:os";
|
|
1968
|
-
import { basename as
|
|
2027
|
+
import { basename as basename6, join as join6, sep as sep3 } from "node:path";
|
|
2028
|
+
|
|
2029
|
+
// src/lib/execution/index.ts
|
|
2030
|
+
import { basename as basename4, resolve as resolve7 } from "node:path";
|
|
2031
|
+
|
|
2032
|
+
// src/lib/version.ts
|
|
2033
|
+
var AGRO_VERSION = true ? "0.17.0-rc.1" : "0.0.0-dev";
|
|
2034
|
+
var OFFICIAL_IMAGE = "ghcr.io/mifunedev/agro";
|
|
2035
|
+
var RELEASE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z][a-z0-9]*\.[0-9]+)?$/;
|
|
2036
|
+
function parseReleaseVersion(value) {
|
|
2037
|
+
const bare = value.startsWith("v") ? value.slice(1) : value;
|
|
2038
|
+
return RELEASE_VERSION.test(bare) ? bare : void 0;
|
|
2039
|
+
}
|
|
2040
|
+
function officialImageRef(version) {
|
|
2041
|
+
return `${OFFICIAL_IMAGE}:${RELEASE_VERSION.test(version) ? version : "latest"}`;
|
|
2042
|
+
}
|
|
1969
2043
|
|
|
1970
2044
|
// src/lib/execution/detect.ts
|
|
1971
2045
|
import { existsSync as existsSync9 } from "node:fs";
|
|
1972
2046
|
var SANDBOX_MARKER_FILE = "/.dockerenv";
|
|
2047
|
+
var EXECUTION_TARGET_ENV = "AGRO_EXECUTION_TARGET";
|
|
1973
2048
|
function runningInsideSandbox(env = process.env, fileExists = existsSync9) {
|
|
1974
2049
|
const override = agroEnvValue(env, "EXECUTION_TARGET");
|
|
1975
2050
|
if (override === "local") return true;
|
|
@@ -2195,6 +2270,243 @@ function defaultIdentity() {
|
|
|
2195
2270
|
return { name: info2.username, uid: info2.uid };
|
|
2196
2271
|
}
|
|
2197
2272
|
|
|
2273
|
+
// src/lib/execution/openshell-target.ts
|
|
2274
|
+
import { join as join5 } from "node:path";
|
|
2275
|
+
var OPENSHELL_BIN = "openshell";
|
|
2276
|
+
var OPENSHELL_POLICY_FILE = "openshell-policy.yaml";
|
|
2277
|
+
var OPENSHELL_MAIN = "/usr/local/bin/openshell-main.sh";
|
|
2278
|
+
var OPENSHELL_WORKDIR = "/home/sandbox/harness";
|
|
2279
|
+
var OPENSHELL_USER = "sandbox";
|
|
2280
|
+
var OPENSHELL_PINNED_VERSION = "v0.1.2";
|
|
2281
|
+
var OPENSHELL_LOCAL_GATEWAY = "https://127.0.0.1:17670";
|
|
2282
|
+
var OPENSHELL_INSTALL_URL = "https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh";
|
|
2283
|
+
var LOGIN_SHELLS = /* @__PURE__ */ new Set(["zsh", "bash"]);
|
|
2284
|
+
var NOT_FOUND = /sandbox (?:'[^']*' )?not found/;
|
|
2285
|
+
var OPENSHELL_PHASES = [
|
|
2286
|
+
"Unspecified",
|
|
2287
|
+
"Provisioning",
|
|
2288
|
+
"Ready",
|
|
2289
|
+
"Error",
|
|
2290
|
+
"Deleting",
|
|
2291
|
+
"Unknown",
|
|
2292
|
+
"Stopping",
|
|
2293
|
+
"Stopped",
|
|
2294
|
+
"Starting",
|
|
2295
|
+
"Completed"
|
|
2296
|
+
];
|
|
2297
|
+
var OPENSHELL_PHASE_STATUS = Object.freeze({
|
|
2298
|
+
Ready: "ready",
|
|
2299
|
+
Provisioning: "starting",
|
|
2300
|
+
Starting: "starting",
|
|
2301
|
+
Stopping: "stopped",
|
|
2302
|
+
Stopped: "stopped",
|
|
2303
|
+
Deleting: "stopped",
|
|
2304
|
+
Completed: "stopped",
|
|
2305
|
+
Error: "failed",
|
|
2306
|
+
Unknown: "failed",
|
|
2307
|
+
Unspecified: "failed"
|
|
2308
|
+
});
|
|
2309
|
+
function createArgv(opts) {
|
|
2310
|
+
return [
|
|
2311
|
+
"sandbox",
|
|
2312
|
+
"create",
|
|
2313
|
+
"--name",
|
|
2314
|
+
opts.name,
|
|
2315
|
+
"--from",
|
|
2316
|
+
opts.image,
|
|
2317
|
+
"--policy",
|
|
2318
|
+
join5(opts.entryRoot, OPENSHELL_POLICY_FILE),
|
|
2319
|
+
"--env",
|
|
2320
|
+
`${EXECUTION_TARGET_ENV}=local`,
|
|
2321
|
+
"--env",
|
|
2322
|
+
`SANDBOX_NAME=${opts.name}`,
|
|
2323
|
+
"--detach",
|
|
2324
|
+
"--",
|
|
2325
|
+
OPENSHELL_MAIN
|
|
2326
|
+
];
|
|
2327
|
+
}
|
|
2328
|
+
function attachArgv(name, request = { argv: ["zsh"] }) {
|
|
2329
|
+
return [
|
|
2330
|
+
"sandbox",
|
|
2331
|
+
"exec",
|
|
2332
|
+
"-n",
|
|
2333
|
+
name,
|
|
2334
|
+
"--tty",
|
|
2335
|
+
"--workdir",
|
|
2336
|
+
request.cwd ?? OPENSHELL_WORKDIR,
|
|
2337
|
+
...envFlags(request.env),
|
|
2338
|
+
"--",
|
|
2339
|
+
...asLoginShell(request.argv)
|
|
2340
|
+
];
|
|
2341
|
+
}
|
|
2342
|
+
function execArgv(name, request) {
|
|
2343
|
+
return [
|
|
2344
|
+
"sandbox",
|
|
2345
|
+
"exec",
|
|
2346
|
+
"-n",
|
|
2347
|
+
name,
|
|
2348
|
+
...request.cwd !== void 0 ? ["--workdir", request.cwd] : [],
|
|
2349
|
+
...envFlags(request.env),
|
|
2350
|
+
...request.timeoutMs !== void 0 ? ["--timeout", String(Math.ceil(request.timeoutMs / 1e3))] : [],
|
|
2351
|
+
"--",
|
|
2352
|
+
...request.argv
|
|
2353
|
+
];
|
|
2354
|
+
}
|
|
2355
|
+
function getArgv(name) {
|
|
2356
|
+
return ["sandbox", "get", name, "-o", "json"];
|
|
2357
|
+
}
|
|
2358
|
+
function deleteArgv(name) {
|
|
2359
|
+
return ["sandbox", "delete", name];
|
|
2360
|
+
}
|
|
2361
|
+
function parseOpenShellPhase(json) {
|
|
2362
|
+
let parsed;
|
|
2363
|
+
try {
|
|
2364
|
+
parsed = JSON.parse(json);
|
|
2365
|
+
} catch {
|
|
2366
|
+
return "Unknown";
|
|
2367
|
+
}
|
|
2368
|
+
const phase = typeof parsed === "object" && parsed !== null ? parsed.phase : void 0;
|
|
2369
|
+
return OPENSHELL_PHASES.find((known) => known === phase) ?? "Unknown";
|
|
2370
|
+
}
|
|
2371
|
+
function openshellSandboxMissing(output) {
|
|
2372
|
+
return NOT_FOUND.test(output);
|
|
2373
|
+
}
|
|
2374
|
+
var OPENSHELL_VERB_HINTS = Object.freeze({
|
|
2375
|
+
stop: (name) => `openshell sandbox stop ${name}`,
|
|
2376
|
+
restart: (name) => `openshell sandbox stop ${name} && openshell sandbox start ${name}`,
|
|
2377
|
+
logs: (name) => `openshell logs ${name}`,
|
|
2378
|
+
ps: (name) => `openshell sandbox get ${name}`,
|
|
2379
|
+
config: (name) => `openshell policy get ${name}`
|
|
2380
|
+
});
|
|
2381
|
+
function openshellInstallHint() {
|
|
2382
|
+
return [
|
|
2383
|
+
"install the OpenShell CLI on the host, then run the command again:",
|
|
2384
|
+
` curl -LsSf ${OPENSHELL_INSTALL_URL} | OPENSHELL_VERSION=${OPENSHELL_PINNED_VERSION} sh`,
|
|
2385
|
+
"review-first alternative \u2014 download the script, inspect it, then run it:",
|
|
2386
|
+
` curl -LsSf -o openshell-install.sh ${OPENSHELL_INSTALL_URL}`,
|
|
2387
|
+
" less openshell-install.sh",
|
|
2388
|
+
` OPENSHELL_VERSION=${OPENSHELL_PINNED_VERSION} sh openshell-install.sh`
|
|
2389
|
+
].join("\n");
|
|
2390
|
+
}
|
|
2391
|
+
function openshellGatewayHint() {
|
|
2392
|
+
return [
|
|
2393
|
+
"the OpenShell gateway is not connected; start the local gateway and register it:",
|
|
2394
|
+
" systemctl --user restart openshell-gateway",
|
|
2395
|
+
` openshell gateway add ${OPENSHELL_LOCAL_GATEWAY} --local --name openshell`,
|
|
2396
|
+
"then confirm with: openshell status"
|
|
2397
|
+
].join("\n");
|
|
2398
|
+
}
|
|
2399
|
+
function openshellCleanupHint(name) {
|
|
2400
|
+
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}`;
|
|
2401
|
+
}
|
|
2402
|
+
function openshellPreflight(run) {
|
|
2403
|
+
const version = run(OPENSHELL_BIN, ["--version"], { stdio: "capture" });
|
|
2404
|
+
if (version.error?.code === "ENOENT") {
|
|
2405
|
+
return { ok: false, reason: "missing-binary", hint: openshellInstallHint() };
|
|
2406
|
+
}
|
|
2407
|
+
assertSpawned(version, `${OPENSHELL_BIN} --version`);
|
|
2408
|
+
const status = run(OPENSHELL_BIN, ["status", "-o", "json"], { stdio: "capture" });
|
|
2409
|
+
assertSpawned(status, `${OPENSHELL_BIN} status`);
|
|
2410
|
+
if (status.status !== 0 || !gatewayConnected(status.stdout ?? "")) {
|
|
2411
|
+
return { ok: false, reason: "gateway-unreachable", hint: openshellGatewayHint() };
|
|
2412
|
+
}
|
|
2413
|
+
return { ok: true };
|
|
2414
|
+
}
|
|
2415
|
+
var OpenShellExecutionTarget = class {
|
|
2416
|
+
kind = "openshell";
|
|
2417
|
+
contractVersion = 1;
|
|
2418
|
+
workspace;
|
|
2419
|
+
name;
|
|
2420
|
+
entryRoot;
|
|
2421
|
+
image;
|
|
2422
|
+
run;
|
|
2423
|
+
env;
|
|
2424
|
+
constructor(opts) {
|
|
2425
|
+
this.name = opts.name;
|
|
2426
|
+
this.entryRoot = opts.entryRoot;
|
|
2427
|
+
this.image = opts.image;
|
|
2428
|
+
this.run = opts.run ?? spawnRunner;
|
|
2429
|
+
this.env = opts.env;
|
|
2430
|
+
this.workspace = { hostRoot: opts.entryRoot, targetRoot: OPENSHELL_WORKDIR };
|
|
2431
|
+
}
|
|
2432
|
+
async provision() {
|
|
2433
|
+
const argv = createArgv({ name: this.name, image: this.image, entryRoot: this.entryRoot });
|
|
2434
|
+
this.runInherited(argv, "sandbox create");
|
|
2435
|
+
}
|
|
2436
|
+
async destroy() {
|
|
2437
|
+
this.runInherited(deleteArgv(this.name), "sandbox delete");
|
|
2438
|
+
}
|
|
2439
|
+
async status() {
|
|
2440
|
+
const r = this.run(OPENSHELL_BIN, getArgv(this.name), { stdio: "capture", ...this.childEnv() });
|
|
2441
|
+
assertSpawned(r, this.what("sandbox get"));
|
|
2442
|
+
if (r.status !== 0) {
|
|
2443
|
+
if (openshellSandboxMissing(`${r.stdout ?? ""}
|
|
2444
|
+
${r.stderr ?? ""}`)) return "absent";
|
|
2445
|
+
throw new ExecutionExitError(this.what("sandbox get"), r.status ?? 1);
|
|
2446
|
+
}
|
|
2447
|
+
return OPENSHELL_PHASE_STATUS[parseOpenShellPhase(r.stdout ?? "")];
|
|
2448
|
+
}
|
|
2449
|
+
async capabilities() {
|
|
2450
|
+
return /* @__PURE__ */ new Set(["exec", "pty"]);
|
|
2451
|
+
}
|
|
2452
|
+
async exec(request) {
|
|
2453
|
+
requireSandboxUser(request);
|
|
2454
|
+
const inherit = request.stdio === "inherit";
|
|
2455
|
+
const r = this.run(OPENSHELL_BIN, execArgv(this.name, request), {
|
|
2456
|
+
stdio: inherit ? "inherit" : "capture",
|
|
2457
|
+
...this.childEnv()
|
|
2458
|
+
});
|
|
2459
|
+
assertSpawned(r, this.what("sandbox exec"));
|
|
2460
|
+
return {
|
|
2461
|
+
exitCode: r.status ?? 1,
|
|
2462
|
+
stdout: r.stdout ?? "",
|
|
2463
|
+
stderr: r.stderr ?? ""
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
attach(request) {
|
|
2467
|
+
requireSandboxUser(request);
|
|
2468
|
+
const r = this.run(OPENSHELL_BIN, attachArgv(this.name, request), { stdio: "inherit", ...this.childEnv() });
|
|
2469
|
+
assertSpawned(r, this.what("sandbox exec"));
|
|
2470
|
+
return r.status ?? 1;
|
|
2471
|
+
}
|
|
2472
|
+
describe() {
|
|
2473
|
+
return `openshell sandbox ${this.name} (entry ${this.entryRoot})`;
|
|
2474
|
+
}
|
|
2475
|
+
runInherited(argv, verb) {
|
|
2476
|
+
const r = this.run(OPENSHELL_BIN, argv, { stdio: "inherit", ...this.childEnv() });
|
|
2477
|
+
assertSpawned(r, this.what(verb));
|
|
2478
|
+
const code = r.status ?? 1;
|
|
2479
|
+
if (code !== 0) throw new ExecutionExitError(this.what(verb), code);
|
|
2480
|
+
}
|
|
2481
|
+
childEnv() {
|
|
2482
|
+
return this.env ? { env: this.env } : {};
|
|
2483
|
+
}
|
|
2484
|
+
what(verb) {
|
|
2485
|
+
return `${OPENSHELL_BIN} ${verb} ${this.name}`;
|
|
2486
|
+
}
|
|
2487
|
+
};
|
|
2488
|
+
function asLoginShell(argv) {
|
|
2489
|
+
return argv.length === 1 && LOGIN_SHELLS.has(argv[0]) ? [argv[0], "-l"] : [...argv];
|
|
2490
|
+
}
|
|
2491
|
+
function envFlags(env) {
|
|
2492
|
+
return Object.entries(env ?? {}).flatMap(([k, v]) => ["--env", `${k}=${v}`]);
|
|
2493
|
+
}
|
|
2494
|
+
function gatewayConnected(json) {
|
|
2495
|
+
try {
|
|
2496
|
+
const parsed = JSON.parse(json);
|
|
2497
|
+
return typeof parsed === "object" && parsed !== null && parsed.status === "connected";
|
|
2498
|
+
} catch {
|
|
2499
|
+
return false;
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
function requireSandboxUser(request) {
|
|
2503
|
+
if (request.user !== void 0 && request.user !== OPENSHELL_USER) {
|
|
2504
|
+
throw new Error(
|
|
2505
|
+
`the openshell runtime runs every command as "${OPENSHELL_USER}" and cannot run as "${request.user}"`
|
|
2506
|
+
);
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2198
2510
|
// src/lib/execution/status.ts
|
|
2199
2511
|
var RUNTIME_ABSENT_STATUS = "no container runtime";
|
|
2200
2512
|
async function resolveTargetStatus(target) {
|
|
@@ -2218,19 +2530,23 @@ function resolveExecutionTarget(opts) {
|
|
|
2218
2530
|
...opts.env ? { env: opts.env } : {}
|
|
2219
2531
|
});
|
|
2220
2532
|
}
|
|
2533
|
+
if (entryRuntime(opts.projectRoot) === "openshell") {
|
|
2534
|
+
const config = readAgroConfig(agroConfigPath(opts.projectRoot));
|
|
2535
|
+
return new OpenShellExecutionTarget({
|
|
2536
|
+
name: opts.container ?? nonEmpty(config.name) ?? basename4(resolve7(opts.projectRoot)),
|
|
2537
|
+
entryRoot: opts.projectRoot,
|
|
2538
|
+
image: openshellImage(config),
|
|
2539
|
+
...opts.run ? { run: opts.run } : {},
|
|
2540
|
+
...opts.env ? { env: opts.env } : {}
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2221
2543
|
return new DockerComposeExecutionTarget(opts);
|
|
2222
2544
|
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
var AGRO_VERSION = true ? "0.15.1" : "0.0.0-dev";
|
|
2226
|
-
var OFFICIAL_IMAGE = "ghcr.io/mifunedev/agro";
|
|
2227
|
-
var RELEASE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z][a-z0-9]*\.[0-9]+)?$/;
|
|
2228
|
-
function parseReleaseVersion(value) {
|
|
2229
|
-
const bare = value.startsWith("v") ? value.slice(1) : value;
|
|
2230
|
-
return RELEASE_VERSION.test(bare) ? bare : void 0;
|
|
2545
|
+
function openshellImage(config) {
|
|
2546
|
+
return nonEmpty(config.image?.ref) ?? officialImageRef(AGRO_VERSION);
|
|
2231
2547
|
}
|
|
2232
|
-
function
|
|
2233
|
-
return
|
|
2548
|
+
function nonEmpty(value) {
|
|
2549
|
+
return value === void 0 || value === "" ? void 0 : value;
|
|
2234
2550
|
}
|
|
2235
2551
|
|
|
2236
2552
|
// src/lib/config-render.ts
|
|
@@ -2296,13 +2612,103 @@ function renderComposeEnv(config) {
|
|
|
2296
2612
|
`;
|
|
2297
2613
|
}
|
|
2298
2614
|
|
|
2615
|
+
// src/lib/runtimes/verbs.ts
|
|
2616
|
+
import { basename as basename5, resolve as resolve8 } from "node:path";
|
|
2617
|
+
var viaCompose = Object.freeze({ action: "route", via: "compose" });
|
|
2618
|
+
var viaTarget = Object.freeze({ action: "route", via: "target" });
|
|
2619
|
+
function refuse(hint) {
|
|
2620
|
+
return Object.freeze({ action: "refuse", hint });
|
|
2621
|
+
}
|
|
2622
|
+
var LIFECYCLE_COMMANDS = Object.freeze({
|
|
2623
|
+
shell: "shell",
|
|
2624
|
+
stop: "stop",
|
|
2625
|
+
restart: "restart",
|
|
2626
|
+
logs: "logs",
|
|
2627
|
+
ps: "ps",
|
|
2628
|
+
destroy: "destroy",
|
|
2629
|
+
config: "compose config",
|
|
2630
|
+
upgrade: "sandbox upgrade"
|
|
2631
|
+
});
|
|
2632
|
+
var DOCKER_VERBS = Object.freeze({
|
|
2633
|
+
shell: viaTarget,
|
|
2634
|
+
stop: viaCompose,
|
|
2635
|
+
restart: viaCompose,
|
|
2636
|
+
logs: viaCompose,
|
|
2637
|
+
ps: viaCompose,
|
|
2638
|
+
destroy: viaCompose,
|
|
2639
|
+
config: viaCompose,
|
|
2640
|
+
upgrade: viaTarget
|
|
2641
|
+
});
|
|
2642
|
+
var OPENSHELL_VERBS = Object.freeze({
|
|
2643
|
+
shell: viaTarget,
|
|
2644
|
+
stop: refuse(OPENSHELL_VERB_HINTS.stop),
|
|
2645
|
+
restart: refuse(OPENSHELL_VERB_HINTS.restart),
|
|
2646
|
+
logs: refuse(OPENSHELL_VERB_HINTS.logs),
|
|
2647
|
+
ps: refuse(OPENSHELL_VERB_HINTS.ps),
|
|
2648
|
+
destroy: viaTarget,
|
|
2649
|
+
config: refuse(OPENSHELL_VERB_HINTS.config),
|
|
2650
|
+
upgrade: refuse(
|
|
2651
|
+
(name) => `${activeBin()} destroy ${name}, then ${activeBin()} sandbox install openshell --name ${name} --image=<ref>`
|
|
2652
|
+
)
|
|
2653
|
+
});
|
|
2654
|
+
var RUNTIME_VERBS = Object.freeze({
|
|
2655
|
+
docker: DOCKER_VERBS,
|
|
2656
|
+
openshell: OPENSHELL_VERBS
|
|
2657
|
+
});
|
|
2658
|
+
function configuredEntryName(root) {
|
|
2659
|
+
try {
|
|
2660
|
+
const name = readAgroConfig(agroConfigPath(root)).name;
|
|
2661
|
+
return name === "" ? void 0 : name;
|
|
2662
|
+
} catch {
|
|
2663
|
+
return void 0;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
function entryDeclaredName(root) {
|
|
2667
|
+
return configuredEntryName(root) ?? basename5(resolve8(root));
|
|
2668
|
+
}
|
|
2669
|
+
var RUNTIME_NAMING = Object.freeze({
|
|
2670
|
+
docker: Object.freeze({
|
|
2671
|
+
entryName: () => void 0,
|
|
2672
|
+
shellFailure: (bin, name) => `container \`${name}\` not running? start it with \`${bin} sandbox install docker\``
|
|
2673
|
+
}),
|
|
2674
|
+
openshell: Object.freeze({
|
|
2675
|
+
entryName: entryDeclaredName,
|
|
2676
|
+
shellFailure: (bin, name) => `sandbox \`${name}\` did not accept the shell; check its status with \`${bin} sandbox list\``
|
|
2677
|
+
})
|
|
2678
|
+
});
|
|
2679
|
+
function entrySandboxName(root) {
|
|
2680
|
+
return RUNTIME_NAMING[entryRuntime(root)].entryName(root);
|
|
2681
|
+
}
|
|
2682
|
+
function shellFailureHint(runtime, bin, name) {
|
|
2683
|
+
return RUNTIME_NAMING[runtime].shellFailure(bin, name);
|
|
2684
|
+
}
|
|
2685
|
+
var RuntimeUnsupportedError = class extends Error {
|
|
2686
|
+
runtime;
|
|
2687
|
+
verb;
|
|
2688
|
+
hint;
|
|
2689
|
+
constructor(runtime, verb, hint) {
|
|
2690
|
+
const command = LIFECYCLE_COMMANDS[verb];
|
|
2691
|
+
super(`${activeBin()} ${command}: the ${runtime} runtime does not support ${command}; run: ${hint}`);
|
|
2692
|
+
this.name = "RuntimeUnsupportedError";
|
|
2693
|
+
this.runtime = runtime;
|
|
2694
|
+
this.verb = verb;
|
|
2695
|
+
this.hint = hint;
|
|
2696
|
+
}
|
|
2697
|
+
};
|
|
2698
|
+
function routeVerb(root, verb, name) {
|
|
2699
|
+
const runtime = entryRuntime(root);
|
|
2700
|
+
const policy = RUNTIME_VERBS[runtime][verb];
|
|
2701
|
+
if (policy.action === "refuse") throw new RuntimeUnsupportedError(runtime, verb, policy.hint(name));
|
|
2702
|
+
return { runtime, via: policy.via };
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2299
2705
|
// src/commands/lifecycle.ts
|
|
2300
2706
|
var COMPOSE_ENV_DIR_PREFIX = "oh-compose-env-";
|
|
2301
2707
|
function composeEnvDir() {
|
|
2302
|
-
return mkdtempSync(
|
|
2708
|
+
return mkdtempSync(join6(tmpdir(), COMPOSE_ENV_DIR_PREFIX));
|
|
2303
2709
|
}
|
|
2304
2710
|
function writeComposeEnv(root, dir) {
|
|
2305
|
-
const file =
|
|
2711
|
+
const file = join6(dir, "compose.env");
|
|
2306
2712
|
writeFileSync5(file, renderComposeEnv(readAgroConfig(agroConfigPath(root))), {
|
|
2307
2713
|
mode: 384,
|
|
2308
2714
|
encoding: "utf8"
|
|
@@ -2360,7 +2766,7 @@ async function maybePromptDockerSocket(root, io, bin) {
|
|
|
2360
2766
|
if (dockerSocketConfigured(root)) return;
|
|
2361
2767
|
const interactive = process.stdin.isTTY === true || io.ask !== void 0;
|
|
2362
2768
|
if (!interactive) return;
|
|
2363
|
-
const envDir =
|
|
2769
|
+
const envDir = join6(root, ".devcontainer");
|
|
2364
2770
|
if (!existsSync10(envDir)) return;
|
|
2365
2771
|
const askFn = io.ask ?? ask;
|
|
2366
2772
|
const answer = (await askFn(
|
|
@@ -2441,25 +2847,27 @@ async function runSandbox(opts, io) {
|
|
|
2441
2847
|
function configuredContainerName(root) {
|
|
2442
2848
|
return fromProcessEnv("SANDBOX_NAME") ?? configuredString(root, "name");
|
|
2443
2849
|
}
|
|
2850
|
+
function sandboxName(root) {
|
|
2851
|
+
return entrySandboxName(root) ?? configuredContainerName(root) ?? DEFAULT_CONTAINER_NAME;
|
|
2852
|
+
}
|
|
2444
2853
|
function runShell(opts, io) {
|
|
2445
2854
|
const run = opts.run ?? spawnRunner;
|
|
2446
2855
|
const root = sandboxRoot(opts);
|
|
2447
|
-
const name =
|
|
2856
|
+
const name = sandboxName(root);
|
|
2857
|
+
const { runtime } = routeVerb(root, "shell", name);
|
|
2448
2858
|
const target = resolveExecutionTarget({ projectRoot: root, container: name, run });
|
|
2449
2859
|
let code;
|
|
2450
2860
|
try {
|
|
2451
2861
|
code = target.attach({ argv: ["zsh"], user: "sandbox" });
|
|
2452
2862
|
} catch (err) {
|
|
2453
2863
|
if (err instanceof ExecutionSpawnError && err.code === "ENOENT") {
|
|
2454
|
-
throw new Error(
|
|
2864
|
+
throw new Error(`${runtime} is required for \`${opts.bin} shell\` but was not found on PATH`);
|
|
2455
2865
|
}
|
|
2456
2866
|
throw err;
|
|
2457
2867
|
}
|
|
2458
2868
|
if (code !== 0) {
|
|
2459
|
-
io.stderr(
|
|
2460
|
-
|
|
2461
|
-
`
|
|
2462
|
-
);
|
|
2869
|
+
io.stderr(`${shellFailureHint(runtime, opts.bin, name)}
|
|
2870
|
+
`);
|
|
2463
2871
|
}
|
|
2464
2872
|
return code;
|
|
2465
2873
|
}
|
|
@@ -2476,6 +2884,7 @@ function composeVerbs() {
|
|
|
2476
2884
|
function runComposeVerb(verb, opts, extra = []) {
|
|
2477
2885
|
const run = opts.run ?? spawnRunner;
|
|
2478
2886
|
const root = sandboxRoot(opts);
|
|
2887
|
+
routeVerb(root, verb, sandboxName(root));
|
|
2479
2888
|
const script = requireLifecycleScript(root, "docker-compose.sh");
|
|
2480
2889
|
return withComposeEnvFile(root, (extraArgs) => {
|
|
2481
2890
|
const r = run("bash", [script, ...extraArgs, ...COMPOSE_VERBS[verb], ...extra], {
|
|
@@ -2488,6 +2897,7 @@ function runComposeVerb(verb, opts, extra = []) {
|
|
|
2488
2897
|
function runComposeConfig(opts, extra = []) {
|
|
2489
2898
|
const run = opts.run ?? spawnRunner;
|
|
2490
2899
|
const root = sandboxRoot(opts);
|
|
2900
|
+
routeVerb(root, "config", sandboxName(root));
|
|
2491
2901
|
const script = requireLifecycleScript(root, "docker-compose.sh");
|
|
2492
2902
|
return withComposeEnvFile(root, (extraArgs) => {
|
|
2493
2903
|
const r = run("bash", [script, ...extraArgs, "config", ...extra], { stdio: "inherit" });
|
|
@@ -2498,7 +2908,7 @@ function runComposeConfig(opts, extra = []) {
|
|
|
2498
2908
|
function namedVolumes(root) {
|
|
2499
2909
|
let text;
|
|
2500
2910
|
try {
|
|
2501
|
-
text = readFileSync7(
|
|
2911
|
+
text = readFileSync7(join6(root, ".devcontainer", "docker-compose.yml"), "utf8");
|
|
2502
2912
|
} catch {
|
|
2503
2913
|
return [];
|
|
2504
2914
|
}
|
|
@@ -2514,11 +2924,23 @@ function namedVolumes(root) {
|
|
|
2514
2924
|
return names;
|
|
2515
2925
|
}
|
|
2516
2926
|
function destroyConfirmationPhrase(root) {
|
|
2517
|
-
return
|
|
2927
|
+
return sandboxName(root);
|
|
2928
|
+
}
|
|
2929
|
+
async function destroyThroughTarget(root, name, run) {
|
|
2930
|
+
const target = resolveExecutionTarget({ projectRoot: root, container: name, run });
|
|
2931
|
+
if (target.destroy === void 0) throw new Error(`${target.describe()} cannot destroy its sandbox`);
|
|
2932
|
+
try {
|
|
2933
|
+
await target.destroy();
|
|
2934
|
+
return 0;
|
|
2935
|
+
} catch (err) {
|
|
2936
|
+
if (err instanceof ExecutionExitError) return err.exitCode;
|
|
2937
|
+
throw err;
|
|
2938
|
+
}
|
|
2518
2939
|
}
|
|
2519
2940
|
async function runDestroy(opts, io) {
|
|
2520
2941
|
const root = sandboxRoot(opts);
|
|
2521
2942
|
const name = destroyConfirmationPhrase(root);
|
|
2943
|
+
const { via } = routeVerb(root, "destroy", name);
|
|
2522
2944
|
if (opts.yes !== true) {
|
|
2523
2945
|
const interactive = process.stdin.isTTY === true || io.ask !== void 0;
|
|
2524
2946
|
if (!interactive) {
|
|
@@ -2533,7 +2955,14 @@ async function runDestroy(opts, io) {
|
|
|
2533
2955
|
${bold(`${opts.bin} destroy \u2014 ${name}`)}
|
|
2534
2956
|
|
|
2535
2957
|
`);
|
|
2536
|
-
if (
|
|
2958
|
+
if (via === "target") {
|
|
2959
|
+
io.stdout(
|
|
2960
|
+
`This deletes the sandbox \`${name}\` and everything inside it \u2014 every agent CLI
|
|
2961
|
+
login, the gh CLI token, and the SSH keys. Sign-in starts over.
|
|
2962
|
+
|
|
2963
|
+
`
|
|
2964
|
+
);
|
|
2965
|
+
} else if (homePath !== void 0 && homePath !== "") {
|
|
2537
2966
|
io.stdout("`docker compose down -v` removes the containers.\n");
|
|
2538
2967
|
io.stdout(
|
|
2539
2968
|
`This sandbox keeps its home on a host bind at ${homePath}, which \`down -v\`
|
|
@@ -2564,7 +2993,7 @@ stay there. Delete that directory yourself if you also want them gone.
|
|
|
2564
2993
|
return 1;
|
|
2565
2994
|
}
|
|
2566
2995
|
}
|
|
2567
|
-
const code = runComposeVerb("destroy", { ...opts, name:
|
|
2996
|
+
const code = via === "target" ? await destroyThroughTarget(root, name, opts.run ?? spawnRunner) : runComposeVerb("destroy", { ...opts, name: basename6(root) });
|
|
2568
2997
|
if (code === 0 && root.startsWith(registryRoot() + sep3)) {
|
|
2569
2998
|
rmSync(root, { recursive: true, force: true });
|
|
2570
2999
|
io.stdout(`removed the sandbox entry ${root}
|
|
@@ -2853,13 +3282,13 @@ function sameRemote(url, owner, name) {
|
|
|
2853
3282
|
|
|
2854
3283
|
// src/commands/secret.ts
|
|
2855
3284
|
import { existsSync as existsSync12 } from "node:fs";
|
|
2856
|
-
import { join as
|
|
3285
|
+
import { join as join8 } from "node:path";
|
|
2857
3286
|
|
|
2858
3287
|
// src/lib/gitignore.ts
|
|
2859
3288
|
import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2860
|
-
import { dirname as dirname5, join as
|
|
3289
|
+
import { dirname as dirname5, join as join7 } from "node:path";
|
|
2861
3290
|
function appendGitignoreLines(root, lines) {
|
|
2862
|
-
const target =
|
|
3291
|
+
const target = join7(root, ".gitignore");
|
|
2863
3292
|
const existing = existsSync11(target) ? readFileSync8(target, "utf8") : "";
|
|
2864
3293
|
const present = new Set(existing.split("\n").map((line) => line.trimEnd()));
|
|
2865
3294
|
const added = [];
|
|
@@ -2884,7 +3313,7 @@ function secretsRoot(opts) {
|
|
|
2884
3313
|
return opts.sandbox === void 0 ? resolveProjectRoot(opts.cwd) : resolveSandboxRoot({ name: opts.sandbox });
|
|
2885
3314
|
}
|
|
2886
3315
|
function ignoreSecretsFile(root) {
|
|
2887
|
-
if (!existsSync12(
|
|
3316
|
+
if (!existsSync12(join8(root, ".git"))) return;
|
|
2888
3317
|
appendGitignoreLines(root, [".env"]);
|
|
2889
3318
|
}
|
|
2890
3319
|
function secretKeyList() {
|
|
@@ -2942,10 +3371,10 @@ async function runSecretList(opts, io) {
|
|
|
2942
3371
|
// src/commands/langfuse.ts
|
|
2943
3372
|
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";
|
|
2944
3373
|
import { homedir as homedir2, tmpdir as tmpdir2 } from "node:os";
|
|
2945
|
-
import { dirname as dirname7, join as
|
|
3374
|
+
import { dirname as dirname7, join as join11, relative } from "node:path";
|
|
2946
3375
|
|
|
2947
3376
|
// src/lib/tracing/harness-writers.ts
|
|
2948
|
-
import { join as
|
|
3377
|
+
import { join as join9 } from "node:path";
|
|
2949
3378
|
|
|
2950
3379
|
// src/lib/tracing/writer.ts
|
|
2951
3380
|
import { chmodSync, existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
|
|
@@ -2990,13 +3419,13 @@ function isPlainObject(value) {
|
|
|
2990
3419
|
// src/lib/tracing/harness-writers.ts
|
|
2991
3420
|
var CODEX_TRACING_TAGS = ["codex"];
|
|
2992
3421
|
function claudeCodeSettingsPath(home) {
|
|
2993
|
-
return
|
|
3422
|
+
return join9(home, ".claude", "settings.json");
|
|
2994
3423
|
}
|
|
2995
3424
|
function piTracingConfigPath(home) {
|
|
2996
|
-
return
|
|
3425
|
+
return join9(home, ".pi", "agent", "langfuse.json");
|
|
2997
3426
|
}
|
|
2998
3427
|
function codexTracingConfigPath(home) {
|
|
2999
|
-
return
|
|
3428
|
+
return join9(home, ".codex", "langfuse.json");
|
|
3000
3429
|
}
|
|
3001
3430
|
var claudeCodeTracingWriter = (home, settings) => {
|
|
3002
3431
|
const path4 = claudeCodeSettingsPath(home);
|
|
@@ -3129,7 +3558,7 @@ var HARNESS_CATALOG = [
|
|
|
3129
3558
|
installArgv: [
|
|
3130
3559
|
"bash",
|
|
3131
3560
|
"-lc",
|
|
3132
|
-
`curl -fsSL https://hermes-agent.nousresearch.com/install.sh | HERMES_INSTALL_DIR="${HARNESS_PREFIX_TOKEN}/lib/hermes-agent" bash -s -- --skip-setup --skip-browser &&
|
|
3561
|
+
`curl -fsSL https://hermes-agent.nousresearch.com/install.sh | HERMES_INSTALL_DIR="${HARNESS_PREFIX_TOKEN}/lib/hermes-agent" bash -s -- --skip-setup --skip-browser && "${HARNESS_PREFIX_TOKEN}/bin/hermes" pm install --extra slack --extra teams`
|
|
3133
3562
|
],
|
|
3134
3563
|
installUser: "sandbox",
|
|
3135
3564
|
verifyArgv: ["hermes", "--version"],
|
|
@@ -3206,7 +3635,7 @@ function harnessBinPath(prefix) {
|
|
|
3206
3635
|
}
|
|
3207
3636
|
|
|
3208
3637
|
// src/lib/tracing/providers/langfuse.ts
|
|
3209
|
-
import { join as
|
|
3638
|
+
import { join as join10 } from "node:path";
|
|
3210
3639
|
var LANGFUSE_FRAGMENT_KEYS = [
|
|
3211
3640
|
"LANGFUSE_PUBLIC_KEY",
|
|
3212
3641
|
"LANGFUSE_SECRET_KEY",
|
|
@@ -3214,7 +3643,7 @@ var LANGFUSE_FRAGMENT_KEYS = [
|
|
|
3214
3643
|
"LANGFUSE_TRACING_ENVIRONMENT"
|
|
3215
3644
|
];
|
|
3216
3645
|
function langfuseFragmentPath(home) {
|
|
3217
|
-
return
|
|
3646
|
+
return join10(home, ".config", "agro", "langfuse.env");
|
|
3218
3647
|
}
|
|
3219
3648
|
function loadLangfuseCredentials(root) {
|
|
3220
3649
|
const publicKey = readSecret(root, "LANGFUSE_PUBLIC_KEY");
|
|
@@ -3378,7 +3807,7 @@ function displayPath(home, path4) {
|
|
|
3378
3807
|
return rel.startsWith("..") ? path4 : `~/${rel}`;
|
|
3379
3808
|
}
|
|
3380
3809
|
function withTempHome(fn) {
|
|
3381
|
-
const dir = mkdtempSync2(
|
|
3810
|
+
const dir = mkdtempSync2(join11(tmpdir2(), "agro-langfuse-"));
|
|
3382
3811
|
try {
|
|
3383
3812
|
return fn(dir);
|
|
3384
3813
|
} finally {
|
|
@@ -3387,10 +3816,10 @@ function withTempHome(fn) {
|
|
|
3387
3816
|
}
|
|
3388
3817
|
function probeGeneratedFile(home, render) {
|
|
3389
3818
|
const rel = withTempHome((scratch) => relative(scratch, render(scratch).path));
|
|
3390
|
-
const path4 =
|
|
3819
|
+
const path4 = join11(home, rel);
|
|
3391
3820
|
if (!existsSync14(path4)) return { path: path4, state: "missing" };
|
|
3392
3821
|
const outcome = withTempHome((scratch) => {
|
|
3393
|
-
const copy =
|
|
3822
|
+
const copy = join11(scratch, rel);
|
|
3394
3823
|
mkdirSync6(dirname7(copy), { recursive: true });
|
|
3395
3824
|
copyFileSync2(path4, copy);
|
|
3396
3825
|
return render(scratch).outcome;
|
|
@@ -3453,7 +3882,7 @@ async function runLangfuseApply(opts, io) {
|
|
|
3453
3882
|
return renderAll(ctx, resolved, io, opts.bin, "apply");
|
|
3454
3883
|
}
|
|
3455
3884
|
function zshenvWarning(home) {
|
|
3456
|
-
const zshenv =
|
|
3885
|
+
const zshenv = join11(home, ".zshenv");
|
|
3457
3886
|
if (!existsSync14(zshenv)) return void 0;
|
|
3458
3887
|
if (readFileSync10(zshenv, "utf8").includes("langfuse.env")) return void 0;
|
|
3459
3888
|
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
|
|
@@ -3734,45 +4163,18 @@ async function runLangfuseSetup(opts, io) {
|
|
|
3734
4163
|
// src/commands/sandbox.ts
|
|
3735
4164
|
import { existsSync as existsSync16, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync3, statSync as statSync4 } from "node:fs";
|
|
3736
4165
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
3737
|
-
import { join as
|
|
3738
|
-
|
|
3739
|
-
// src/lib/runtimes/catalog.ts
|
|
3740
|
-
var RUNTIME_CATALOG = Object.freeze([
|
|
3741
|
-
Object.freeze({
|
|
3742
|
-
id: "docker",
|
|
3743
|
-
title: "Docker container",
|
|
3744
|
-
tier: "container",
|
|
3745
|
-
state: "active",
|
|
3746
|
-
provisionable: true,
|
|
3747
|
-
docsPath: "docs/runtimes/docker.md"
|
|
3748
|
-
}),
|
|
3749
|
-
Object.freeze({
|
|
3750
|
-
id: "microsandbox",
|
|
3751
|
-
title: "MicroSandbox",
|
|
3752
|
-
tier: "microvm",
|
|
3753
|
-
state: "planned",
|
|
3754
|
-
provisionable: false,
|
|
3755
|
-
notProvisionableReason: (bin) => `microsandbox is not a provisionable runtime yet; see docs/rfcs/rfc-runtime-support.md. Inside a sandbox run \`${bin} tool install microsandbox\`.`,
|
|
3756
|
-
docsPath: "docs/runtimes/microsandbox.md"
|
|
3757
|
-
})
|
|
3758
|
-
]);
|
|
3759
|
-
function findRuntime(id) {
|
|
3760
|
-
return RUNTIME_CATALOG.find((r) => r.id === id);
|
|
3761
|
-
}
|
|
3762
|
-
function runtimeIds() {
|
|
3763
|
-
return RUNTIME_CATALOG.map((r) => r.id);
|
|
3764
|
-
}
|
|
4166
|
+
import { join as join13, resolve as resolve9 } from "node:path";
|
|
3765
4167
|
|
|
3766
4168
|
// src/lib/star-prompt.ts
|
|
3767
4169
|
import { existsSync as existsSync15, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
3768
|
-
import { join as
|
|
4170
|
+
import { join as join12 } from "node:path";
|
|
3769
4171
|
var STAR_PROMPT_LINE = "\u2B50 If AGRO helps, star https://github.com/mifunedev/agro";
|
|
3770
4172
|
var MARKER = "star-prompt-shown";
|
|
3771
4173
|
function maybePrintStarPrompt(io, env = process.env, isTTY = process.stdout.isTTY === true, stateHome = resolveUserStateHome(env)) {
|
|
3772
4174
|
if (env.AGRO_NO_STAR_PROMPT === "1") return;
|
|
3773
4175
|
if (env.CI !== void 0 && env.CI !== "") return;
|
|
3774
4176
|
if (!isTTY) return;
|
|
3775
|
-
const marker =
|
|
4177
|
+
const marker = join12(stateHome, MARKER);
|
|
3776
4178
|
if (existsSync15(marker)) return;
|
|
3777
4179
|
io.stdout(`${STAR_PROMPT_LINE}
|
|
3778
4180
|
`);
|
|
@@ -3784,39 +4186,88 @@ function maybePrintStarPrompt(io, env = process.env, isTTY = process.stdout.isTT
|
|
|
3784
4186
|
}
|
|
3785
4187
|
}
|
|
3786
4188
|
|
|
3787
|
-
// src/commands/sandbox.ts
|
|
3788
|
-
function
|
|
3789
|
-
const tz = process.env.TZ;
|
|
3790
|
-
return tz !== void 0 && tz !== "" ? tz : "UTC";
|
|
3791
|
-
}
|
|
3792
|
-
function gitIdentity(run, key) {
|
|
3793
|
-
let result;
|
|
4189
|
+
// src/commands/sandbox-list.ts
|
|
4190
|
+
async function entryStatus(root, name, run) {
|
|
3794
4191
|
try {
|
|
3795
|
-
|
|
4192
|
+
const target = resolveExecutionTarget({ projectRoot: root, container: entrySandboxName(root) ?? name, run });
|
|
4193
|
+
return await target.status();
|
|
3796
4194
|
} catch {
|
|
3797
|
-
return "";
|
|
4195
|
+
return "unknown";
|
|
3798
4196
|
}
|
|
3799
|
-
if (result.error || result.status !== 0) return "";
|
|
3800
|
-
return (result.stdout ?? "").trim();
|
|
3801
|
-
}
|
|
3802
|
-
function isBuildCapable(repo) {
|
|
3803
|
-
return repo !== void 0 && existsSync16(join12(repo, ".devcontainer", "Dockerfile"));
|
|
3804
4197
|
}
|
|
3805
|
-
function
|
|
3806
|
-
const
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
4198
|
+
async function runSandboxList(opts, io) {
|
|
4199
|
+
const run = opts.run ?? spawnRunner;
|
|
4200
|
+
const rows = [];
|
|
4201
|
+
for (const name of listEntries()) {
|
|
4202
|
+
const root = entryRoot(name);
|
|
4203
|
+
const config = readAgroConfig(agroConfigPath(root));
|
|
4204
|
+
const checkout = configCheckout(config) ?? "-";
|
|
4205
|
+
rows.push({
|
|
4206
|
+
name,
|
|
4207
|
+
runtime: config.runtime ?? "docker",
|
|
4208
|
+
checkout,
|
|
4209
|
+
repo: checkout,
|
|
4210
|
+
status: await entryStatus(root, name, run)
|
|
4211
|
+
});
|
|
3812
4212
|
}
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
4213
|
+
if (opts.json === true) {
|
|
4214
|
+
io.stdout(`${JSON.stringify(rows, null, 2)}
|
|
4215
|
+
`);
|
|
4216
|
+
return 0;
|
|
4217
|
+
}
|
|
4218
|
+
if (rows.length === 0) {
|
|
4219
|
+
io.stdout(
|
|
4220
|
+
`no sandbox is registered in ${registryRoot()} \u2014 create one with \`${opts.bin} sandbox install docker\`
|
|
4221
|
+
`
|
|
4222
|
+
);
|
|
4223
|
+
return 0;
|
|
4224
|
+
}
|
|
4225
|
+
const width = (pick) => Math.max(...rows.map((row) => pick(row).length));
|
|
4226
|
+
const nameWidth = width((row) => row.name);
|
|
4227
|
+
const runtimeWidth = width((row) => row.runtime);
|
|
4228
|
+
const statusWidth = width((row) => row.status);
|
|
4229
|
+
for (const row of rows) {
|
|
4230
|
+
io.stdout(
|
|
4231
|
+
`${row.name.padEnd(nameWidth)} ${row.runtime.padEnd(runtimeWidth)} ${row.status.padEnd(statusWidth)} ${row.checkout}
|
|
4232
|
+
`
|
|
4233
|
+
);
|
|
4234
|
+
}
|
|
4235
|
+
return 0;
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4238
|
+
// src/commands/sandbox.ts
|
|
4239
|
+
function hostTimezone() {
|
|
4240
|
+
const tz = process.env.TZ;
|
|
4241
|
+
return tz !== void 0 && tz !== "" ? tz : "UTC";
|
|
4242
|
+
}
|
|
4243
|
+
function gitIdentity(run, key) {
|
|
4244
|
+
let result;
|
|
4245
|
+
try {
|
|
4246
|
+
result = run("git", ["config", "--global", key], { stdio: "capture" });
|
|
4247
|
+
} catch {
|
|
4248
|
+
return "";
|
|
4249
|
+
}
|
|
4250
|
+
if (result.error || result.status !== 0) return "";
|
|
4251
|
+
return (result.stdout ?? "").trim();
|
|
4252
|
+
}
|
|
4253
|
+
function isBuildCapable(repo) {
|
|
4254
|
+
return repo !== void 0 && existsSync16(join13(repo, ".devcontainer", "Dockerfile"));
|
|
4255
|
+
}
|
|
4256
|
+
function prepareHomeMount(value) {
|
|
4257
|
+
const path4 = resolve9(value);
|
|
4258
|
+
if (existsSync16(path4)) {
|
|
4259
|
+
if (!statSync4(path4).isDirectory()) {
|
|
4260
|
+
throw new Error(`--home-mount path exists and is not a directory: ${path4}`);
|
|
4261
|
+
}
|
|
4262
|
+
return path4;
|
|
4263
|
+
}
|
|
4264
|
+
mkdirSync8(path4, { recursive: true });
|
|
4265
|
+
return path4;
|
|
4266
|
+
}
|
|
4267
|
+
function readSeedConfig(repo) {
|
|
4268
|
+
if (repo === void 0) return void 0;
|
|
4269
|
+
const file = agroConfigPath(repo);
|
|
4270
|
+
return existsSync16(file) ? readAgroConfig(file) : void 0;
|
|
3820
4271
|
}
|
|
3821
4272
|
function readEntryConfig(name) {
|
|
3822
4273
|
const file = agroConfigPath(entryRoot(name));
|
|
@@ -3847,18 +4298,18 @@ function mergeSettings(...sources) {
|
|
|
3847
4298
|
for (const source of present) overlaySettings(merged, source);
|
|
3848
4299
|
return merged;
|
|
3849
4300
|
}
|
|
3850
|
-
function
|
|
4301
|
+
function nonEmpty2(value) {
|
|
3851
4302
|
return value === void 0 || value === "" ? void 0 : value;
|
|
3852
4303
|
}
|
|
3853
|
-
function seedConfig(name, checkout, seed, run) {
|
|
4304
|
+
function seedConfig(name, checkout, seed, run, runtime = "docker") {
|
|
3854
4305
|
const config = overlaySettings(defaultAgroConfig(name), seed);
|
|
3855
4306
|
config.name = name;
|
|
3856
|
-
config.runtime =
|
|
4307
|
+
config.runtime = runtime;
|
|
3857
4308
|
if (checkout !== void 0) config.checkout = checkout;
|
|
3858
|
-
config.timezone =
|
|
4309
|
+
config.timezone = nonEmpty2(seed?.timezone) ?? hostTimezone();
|
|
3859
4310
|
config.git = {
|
|
3860
|
-
userName:
|
|
3861
|
-
userEmail:
|
|
4311
|
+
userName: nonEmpty2(seed?.git?.userName) ?? gitIdentity(run, "user.name"),
|
|
4312
|
+
userEmail: nonEmpty2(seed?.git?.userEmail) ?? gitIdentity(run, "user.email")
|
|
3862
4313
|
};
|
|
3863
4314
|
config.image = {
|
|
3864
4315
|
...config.image,
|
|
@@ -3899,7 +4350,7 @@ async function runWizard(config, io) {
|
|
|
3899
4350
|
"Host path for /home/sandbox (blank keeps the Docker-managed volume)",
|
|
3900
4351
|
config.storage?.homePath ?? ""
|
|
3901
4352
|
);
|
|
3902
|
-
if (homePath !== "") config.storage = { ...config.storage, homePath:
|
|
4353
|
+
if (homePath !== "") config.storage = { ...config.storage, homePath: resolve9(homePath) };
|
|
3903
4354
|
}
|
|
3904
4355
|
async function runSandboxInstall(opts, io) {
|
|
3905
4356
|
const run = opts.run ?? spawnRunner;
|
|
@@ -3916,7 +4367,8 @@ async function runSandboxInstall(opts, io) {
|
|
|
3916
4367
|
`);
|
|
3917
4368
|
return 1;
|
|
3918
4369
|
}
|
|
3919
|
-
|
|
4370
|
+
if (runtime.id === "openshell") return await installOpenShell(opts, io, run);
|
|
4371
|
+
const checkout = opts.checkout === void 0 ? void 0 : resolve9(opts.checkout);
|
|
3920
4372
|
if (checkout !== void 0 && !existsSync16(checkout)) {
|
|
3921
4373
|
io.stderr(`${opts.bin} sandbox install: --checkout directory does not exist: ${checkout}
|
|
3922
4374
|
`);
|
|
@@ -3934,9 +4386,9 @@ async function runSandboxInstall(opts, io) {
|
|
|
3934
4386
|
const config = seedConfig(name, checkout, mergeSettings(readEntryConfig(name), repoSeed), run);
|
|
3935
4387
|
const buildRequested = config.image?.mode === "build" && opts.noBuild !== true && opts.image !== true && opts.imageRef === void 0;
|
|
3936
4388
|
if (buildRequested && !isBuildCapable(configCheckout(config))) {
|
|
3937
|
-
const target = configCheckout(config) ??
|
|
4389
|
+
const target = configCheckout(config) ?? resolve9(opts.checkout ?? ".");
|
|
3938
4390
|
io.stderr(
|
|
3939
|
-
`${opts.bin} sandbox install: image.mode is "build" but ${
|
|
4391
|
+
`${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
|
|
3940
4392
|
`
|
|
3941
4393
|
);
|
|
3942
4394
|
return 1;
|
|
@@ -3980,7 +4432,7 @@ async function runSandboxInstall(opts, io) {
|
|
|
3980
4432
|
...useNoBuild ? { noBuild: true } : {}
|
|
3981
4433
|
};
|
|
3982
4434
|
if (opts.printArgv === true) {
|
|
3983
|
-
const preview = mkdtempSync3(
|
|
4435
|
+
const preview = mkdtempSync3(join13(tmpdir3(), "oh-sandbox-preview-"));
|
|
3984
4436
|
try {
|
|
3985
4437
|
writeAgroConfig(preview, config);
|
|
3986
4438
|
materialize(preview, { ...configCheckout(config) !== void 0 ? { checkout: configCheckout(config) } : {} });
|
|
@@ -4001,58 +4453,450 @@ async function runSandboxInstall(opts, io) {
|
|
|
4001
4453
|
}
|
|
4002
4454
|
return code;
|
|
4003
4455
|
}
|
|
4004
|
-
async function
|
|
4456
|
+
async function installOpenShell(opts, io, run) {
|
|
4457
|
+
const fail2 = (message) => {
|
|
4458
|
+
io.stderr(`${opts.bin} sandbox install: ${message}
|
|
4459
|
+
`);
|
|
4460
|
+
return 1;
|
|
4461
|
+
};
|
|
4462
|
+
const unsupported = (what) => fail2(`${what} is not supported on the openshell runtime`);
|
|
4463
|
+
if (opts.checkout !== void 0) return unsupported("--checkout");
|
|
4464
|
+
if (opts.homeMount !== void 0) return unsupported("--home-mount");
|
|
4465
|
+
const name = opts.name ?? nextDefaultName(run);
|
|
4005
4466
|
try {
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
return "unknown";
|
|
4467
|
+
assertSandboxName(name);
|
|
4468
|
+
} catch (error) {
|
|
4469
|
+
return fail2(error instanceof Error ? error.message : String(error));
|
|
4010
4470
|
}
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
const
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
const config = readAgroConfig(agroConfigPath(root));
|
|
4018
|
-
const checkout = configCheckout(config) ?? "-";
|
|
4019
|
-
rows.push({
|
|
4020
|
-
name,
|
|
4021
|
-
runtime: config.runtime ?? "docker",
|
|
4022
|
-
checkout,
|
|
4023
|
-
repo: checkout,
|
|
4024
|
-
status: await entryStatus(root, name, run)
|
|
4025
|
-
});
|
|
4471
|
+
const config = seedConfig(name, void 0, readEntryConfig(name), run, "openshell");
|
|
4472
|
+
if (configCheckout(config) !== void 0) return unsupported("checkout");
|
|
4473
|
+
if (nonEmpty2(config.storage?.homePath) !== void 0) return unsupported("storage.homePath");
|
|
4474
|
+
const imageChosen = opts.image === true || opts.imageRef !== void 0;
|
|
4475
|
+
if (config.image?.mode === "build" && !imageChosen) {
|
|
4476
|
+
return unsupported('image.mode "build"');
|
|
4026
4477
|
}
|
|
4027
|
-
|
|
4028
|
-
|
|
4478
|
+
config.image = {
|
|
4479
|
+
...config.image,
|
|
4480
|
+
mode: "image",
|
|
4481
|
+
...opts.imageRef !== void 0 ? { ref: opts.imageRef } : {}
|
|
4482
|
+
};
|
|
4483
|
+
const root = entryRoot(name);
|
|
4484
|
+
const image = openshellImage(config);
|
|
4485
|
+
if (opts.printArgv === true) {
|
|
4486
|
+
io.stdout(`${[OPENSHELL_BIN, ...createArgv({ name, image, entryRoot: root })].join(" ")}
|
|
4029
4487
|
`);
|
|
4030
4488
|
return 0;
|
|
4031
4489
|
}
|
|
4032
|
-
if (
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
return
|
|
4490
|
+
if (runningInsideSandbox()) return fail2(new HostOnlyError(`\`${opts.bin} sandbox\``).message);
|
|
4491
|
+
try {
|
|
4492
|
+
const preflight = openshellPreflight(run);
|
|
4493
|
+
if (!preflight.ok) return fail2(preflight.hint);
|
|
4494
|
+
} catch (error) {
|
|
4495
|
+
return fail2(error instanceof Error ? error.message : String(error));
|
|
4038
4496
|
}
|
|
4039
|
-
const
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
)
|
|
4497
|
+
const created = !existsSync16(root);
|
|
4498
|
+
mkdirSync8(root, { recursive: true });
|
|
4499
|
+
writeAgroConfig(root, config);
|
|
4500
|
+
materialize(root);
|
|
4501
|
+
try {
|
|
4502
|
+
await new OpenShellExecutionTarget({ name, entryRoot: root, image, run }).provision();
|
|
4503
|
+
} catch (error) {
|
|
4504
|
+
if (created) rmSync3(root, { recursive: true, force: true });
|
|
4505
|
+
io.stderr(`${opts.bin} sandbox install: ${error instanceof Error ? error.message : String(error)}
|
|
4506
|
+
`);
|
|
4507
|
+
if (!(error instanceof ExecutionExitError)) return 1;
|
|
4508
|
+
io.stderr(`${opts.bin} sandbox install: ${openshellCleanupHint(name)}
|
|
4509
|
+
`);
|
|
4510
|
+
return error.exitCode;
|
|
4048
4511
|
}
|
|
4512
|
+
io.stdout(`next: ${opts.bin} shell ${name}
|
|
4513
|
+
`);
|
|
4514
|
+
maybePrintStarPrompt(io);
|
|
4049
4515
|
return 0;
|
|
4050
4516
|
}
|
|
4051
4517
|
|
|
4518
|
+
// src/services/sandbox-upgrade.ts
|
|
4519
|
+
import { closeSync as closeSync2, existsSync as existsSync17, openSync as openSync2, readFileSync as readFileSync11, unlinkSync as unlinkSync5, writeSync } from "node:fs";
|
|
4520
|
+
import { join as join14 } from "node:path";
|
|
4521
|
+
function errorMessage(error) {
|
|
4522
|
+
return error instanceof Error ? error.message : String(error);
|
|
4523
|
+
}
|
|
4524
|
+
function conflictingDotenvImage(root, imageRef) {
|
|
4525
|
+
const rootEnv = join14(root, ".env");
|
|
4526
|
+
const fallbackEnv = join14(root, ".devcontainer", ".env");
|
|
4527
|
+
const file = existsSync17(rootEnv) ? rootEnv : fallbackEnv;
|
|
4528
|
+
if (!existsSync17(file)) return void 0;
|
|
4529
|
+
for (const line of readFileSync11(file, "utf8").split(/\r?\n/)) {
|
|
4530
|
+
const match = /^AGRO_SANDBOX_IMAGE=(.*)$/.exec(line);
|
|
4531
|
+
if (!match) continue;
|
|
4532
|
+
const value = match[1].trim().replace(/^(?:"(.*)"|'(.*)')$/, (_whole, double, single) => double ?? single ?? "");
|
|
4533
|
+
if (value !== imageRef) return file;
|
|
4534
|
+
}
|
|
4535
|
+
return void 0;
|
|
4536
|
+
}
|
|
4537
|
+
async function runSandboxUpgrade(opts, io) {
|
|
4538
|
+
const prefix = `${opts.bin} sandbox upgrade:`;
|
|
4539
|
+
const version = parseReleaseVersion(opts.version);
|
|
4540
|
+
if (version === void 0) {
|
|
4541
|
+
io.stderr(`${prefix} invalid release version "${opts.version}"
|
|
4542
|
+
`);
|
|
4543
|
+
return 1;
|
|
4544
|
+
}
|
|
4545
|
+
if (runningInsideSandbox()) {
|
|
4546
|
+
io.stderr(`${prefix} host-only \u2014 run this command on the host
|
|
4547
|
+
`);
|
|
4548
|
+
return 1;
|
|
4549
|
+
}
|
|
4550
|
+
let root;
|
|
4551
|
+
try {
|
|
4552
|
+
root = entryRoot(opts.name);
|
|
4553
|
+
} catch (error) {
|
|
4554
|
+
io.stderr(`${prefix} ${errorMessage(error)}
|
|
4555
|
+
`);
|
|
4556
|
+
return 1;
|
|
4557
|
+
}
|
|
4558
|
+
const configFile = agroConfigPath(root);
|
|
4559
|
+
if (!existsSync17(configFile)) {
|
|
4560
|
+
io.stderr(`${prefix} no sandbox entry named "${opts.name}"
|
|
4561
|
+
`);
|
|
4562
|
+
return 1;
|
|
4563
|
+
}
|
|
4564
|
+
routeVerb(root, "upgrade", entrySandboxName(root) ?? opts.name);
|
|
4565
|
+
const lock = join14(root, ".sandbox-upgrade.lock");
|
|
4566
|
+
let fd;
|
|
4567
|
+
try {
|
|
4568
|
+
fd = openSync2(lock, "wx", 384);
|
|
4569
|
+
} catch (error) {
|
|
4570
|
+
const message = error instanceof Error && "code" in error && error.code === "EEXIST" ? `upgrade already in progress for ${opts.name}; lock: ${lock}. Confirm no upgrade process owns this entry before removing that lock and retrying` : errorMessage(error);
|
|
4571
|
+
io.stderr(`${prefix} ${message}
|
|
4572
|
+
`);
|
|
4573
|
+
return 1;
|
|
4574
|
+
}
|
|
4575
|
+
try {
|
|
4576
|
+
writeSync(fd, `${process.pid}
|
|
4577
|
+
`);
|
|
4578
|
+
const config = readAgroConfig(configFile);
|
|
4579
|
+
if (config.image?.mode !== "image") {
|
|
4580
|
+
io.stderr(`${prefix} ${opts.name} requires image.mode "image"
|
|
4581
|
+
`);
|
|
4582
|
+
return 1;
|
|
4583
|
+
}
|
|
4584
|
+
const imageRef = officialImageRef(version);
|
|
4585
|
+
if (process.env.AGRO_SANDBOX_IMAGE !== void 0 && process.env.AGRO_SANDBOX_IMAGE !== "" && process.env.AGRO_SANDBOX_IMAGE !== imageRef) {
|
|
4586
|
+
io.stderr(`${prefix} conflicting AGRO_SANDBOX_IMAGE in the host environment
|
|
4587
|
+
`);
|
|
4588
|
+
return 1;
|
|
4589
|
+
}
|
|
4590
|
+
const dotenv = conflictingDotenvImage(root, imageRef);
|
|
4591
|
+
if (dotenv !== void 0) {
|
|
4592
|
+
io.stderr(`${prefix} conflicting AGRO_SANDBOX_IMAGE in ${dotenv}
|
|
4593
|
+
`);
|
|
4594
|
+
return 1;
|
|
4595
|
+
}
|
|
4596
|
+
const run = opts.run ?? spawnRunner;
|
|
4597
|
+
let priorRef = config.image.ref;
|
|
4598
|
+
if (!priorRef) {
|
|
4599
|
+
const name = configuredContainerName(root) ?? DEFAULT_CONTAINER_NAME;
|
|
4600
|
+
const inspected = run("docker", ["inspect", "-f", "{{.Config.Image}}", name], { stdio: "capture" });
|
|
4601
|
+
priorRef = inspected.status === 0 && !inspected.error ? inspected.stdout?.trim() : void 0;
|
|
4602
|
+
if (!priorRef) {
|
|
4603
|
+
io.stderr(`${prefix} cannot determine previous image for ${name}; no changes made
|
|
4604
|
+
`);
|
|
4605
|
+
return 1;
|
|
4606
|
+
}
|
|
4607
|
+
}
|
|
4608
|
+
const apply = (ref) => runSandbox({ bin: opts.bin, cwd: root, imageRef: ref, run }, io);
|
|
4609
|
+
let failure;
|
|
4610
|
+
try {
|
|
4611
|
+
const code = await apply(imageRef);
|
|
4612
|
+
if (code !== 0) failure = `exit ${code}`;
|
|
4613
|
+
} catch (error) {
|
|
4614
|
+
failure = errorMessage(error);
|
|
4615
|
+
}
|
|
4616
|
+
if (failure === void 0) {
|
|
4617
|
+
try {
|
|
4618
|
+
(opts.writeConfig ?? writeAgroConfig)(root, { ...config, image: { ...config.image, ref: imageRef } });
|
|
4619
|
+
return 0;
|
|
4620
|
+
} catch (error) {
|
|
4621
|
+
failure = `config persistence failed (${errorMessage(error)})`;
|
|
4622
|
+
}
|
|
4623
|
+
} else {
|
|
4624
|
+
failure = `provisioning failed (${failure})`;
|
|
4625
|
+
}
|
|
4626
|
+
io.stderr(`${prefix} ${failure}; restoring previous image
|
|
4627
|
+
`);
|
|
4628
|
+
try {
|
|
4629
|
+
const code = await apply(priorRef);
|
|
4630
|
+
if (code !== 0) io.stderr(`${prefix} restoration failed (exit ${code})
|
|
4631
|
+
`);
|
|
4632
|
+
} catch (error) {
|
|
4633
|
+
io.stderr(`${prefix} restoration failed (${errorMessage(error)})
|
|
4634
|
+
`);
|
|
4635
|
+
}
|
|
4636
|
+
return 1;
|
|
4637
|
+
} catch (error) {
|
|
4638
|
+
io.stderr(`${prefix} ${errorMessage(error)}
|
|
4639
|
+
`);
|
|
4640
|
+
return 1;
|
|
4641
|
+
} finally {
|
|
4642
|
+
closeSync2(fd);
|
|
4643
|
+
unlinkSync5(lock);
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4647
|
+
// src/controllers/sandbox.ts
|
|
4648
|
+
function isHelpFlag(arg) {
|
|
4649
|
+
return arg === "--help" || arg === "-h" || arg === "help";
|
|
4650
|
+
}
|
|
4651
|
+
function runtimeLines() {
|
|
4652
|
+
const width = Math.max(...RUNTIME_CATALOG.map((r) => r.id.length));
|
|
4653
|
+
return RUNTIME_CATALOG.map(
|
|
4654
|
+
(r) => ` ${r.id.padEnd(width)} ${r.provisionable ? "provisionable" : "planned"}`
|
|
4655
|
+
).join("\n");
|
|
4656
|
+
}
|
|
4657
|
+
function printSandboxHelp(bin = AGRO_PRODUCT.bin) {
|
|
4658
|
+
process.stdout.write(`${bin} sandbox \u2014 Create and list sandboxes
|
|
4659
|
+
|
|
4660
|
+
Usage:
|
|
4661
|
+
${bin} sandbox install <runtime> [--name <name>] [--checkout <dir>]
|
|
4662
|
+
[--home-mount <dir>] [--yes]
|
|
4663
|
+
[--version <X.Y.Z> | --image[=<ref>]]
|
|
4664
|
+
[--no-build] [--print-argv]
|
|
4665
|
+
${bin} sandbox list [--json]
|
|
4666
|
+
${bin} sandbox upgrade <name> --version <X.Y.Z>
|
|
4667
|
+
|
|
4668
|
+
\`install\` writes a sandbox entry under \${${stateNames(bin).envPrefix}HOME:-~/${stateNames(bin).userStateDir}}/sandboxes/<name>/,
|
|
4669
|
+
materialises the compose files and the compose wrapper into it, then starts the
|
|
4670
|
+
container. It needs no project checkout and runs from any directory. Without
|
|
4671
|
+
--checkout the sandbox runs the prebuilt image; with --checkout <dir> that
|
|
4672
|
+
checkout is bound at /home/sandbox/harness; the sandbox builds locally only
|
|
4673
|
+
when that directory holds .devcontainer/Dockerfile.
|
|
4674
|
+
|
|
4675
|
+
On a terminal without --yes it asks for the sandbox name, the timezone, the git
|
|
4676
|
+
identity, SSH (with its host port), the host Docker socket, and the host path
|
|
4677
|
+
for /home/sandbox. With --checkout it seeds those answers from that
|
|
4678
|
+
checkout's ${stateNames(bin).configFile}.
|
|
4679
|
+
|
|
4680
|
+
\`install openshell\` never prompts. The command needs the host \`openshell\` CLI
|
|
4681
|
+
and a connected gateway, and runs the prebuilt image. The command refuses
|
|
4682
|
+
--checkout, --home-mount, and image.mode "build".
|
|
4683
|
+
|
|
4684
|
+
Flags:
|
|
4685
|
+
--name <name> Registry entry name (default: the lowest free ${DEFAULT_NAME_PREFIX}<n>)
|
|
4686
|
+
--checkout <dir> Bind this checkout into the sandbox and seed the defaults
|
|
4687
|
+
from its ${stateNames(bin).configFile}. The sandbox builds
|
|
4688
|
+
locally only when that directory holds
|
|
4689
|
+
.devcontainer/Dockerfile
|
|
4690
|
+
--repo <dir> Deprecated alias for --checkout
|
|
4691
|
+
--home-mount <dir>
|
|
4692
|
+
Bind this host directory at /home/sandbox instead of the
|
|
4693
|
+
Docker-managed volume. Relative paths resolve against the
|
|
4694
|
+
working directory. The directory is created when absent
|
|
4695
|
+
--yes Non-interactive: keep every default and ask nothing
|
|
4696
|
+
--version <X.Y.Z>
|
|
4697
|
+
Run the official release image ghcr.io/mifunedev/agro:<X.Y.Z>
|
|
4698
|
+
(implies --image). A leading v and a -<channel>.<n>
|
|
4699
|
+
pre-release suffix are accepted
|
|
4700
|
+
--image Run the prebuilt image instead of building (implies
|
|
4701
|
+
--no-build). The ref resolves first-match: --version or
|
|
4702
|
+
--image=<ref> > ${stateNames(bin).envPrefix}SANDBOX_IMAGE >
|
|
4703
|
+
${stateNames(bin).configFile} image.ref > ${officialImageRef(AGRO_VERSION)}
|
|
4704
|
+
--image=<ref> Run a custom image (implies --image); conflicts with --version
|
|
4705
|
+
--no-build Suppress the local build and reuse an existing image
|
|
4706
|
+
--print-argv Print the docker compose or openshell argv that would run, then exit
|
|
4707
|
+
without writing an entry
|
|
4708
|
+
--json Machine-readable output (list)
|
|
4709
|
+
|
|
4710
|
+
Runtimes:
|
|
4711
|
+
${runtimeLines()}
|
|
4712
|
+
|
|
4713
|
+
Next: ${bin} shell <name>
|
|
4714
|
+
`);
|
|
4715
|
+
}
|
|
4716
|
+
var SANDBOX_VALUE_FLAGS = {
|
|
4717
|
+
"--name": "name",
|
|
4718
|
+
"--checkout": "checkout",
|
|
4719
|
+
"--repo": "checkout",
|
|
4720
|
+
"--home-mount": "homeMount"
|
|
4721
|
+
};
|
|
4722
|
+
function parseSandboxArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
4723
|
+
const args = {
|
|
4724
|
+
help: false,
|
|
4725
|
+
yes: false,
|
|
4726
|
+
image: false,
|
|
4727
|
+
noBuild: false,
|
|
4728
|
+
printArgv: false,
|
|
4729
|
+
json: false
|
|
4730
|
+
};
|
|
4731
|
+
if (rest.length === 0 || isHelpFlag(rest[0])) {
|
|
4732
|
+
return { ok: true, args: { ...args, help: true } };
|
|
4733
|
+
}
|
|
4734
|
+
const [head, ...tail] = rest;
|
|
4735
|
+
if (head === "upgrade") {
|
|
4736
|
+
if (tail.length === 1 && isHelpFlag(tail[0])) {
|
|
4737
|
+
return { ok: true, args: { ...args, subcommand: "upgrade", help: true } };
|
|
4738
|
+
}
|
|
4739
|
+
const [name, flag, value, ...extra] = tail;
|
|
4740
|
+
if (name === void 0 || name === "" || name.startsWith("-")) {
|
|
4741
|
+
return { ok: false, error: `${bin} sandbox upgrade: a name is required` };
|
|
4742
|
+
}
|
|
4743
|
+
if (flag === void 0) {
|
|
4744
|
+
return { ok: false, error: `${bin} sandbox upgrade: --version is required` };
|
|
4745
|
+
}
|
|
4746
|
+
if (flag !== "--version") {
|
|
4747
|
+
return { ok: false, error: `${bin} sandbox upgrade: ${flag.startsWith("-") ? "unknown flag" : "unexpected argument"} "${flag}"` };
|
|
4748
|
+
}
|
|
4749
|
+
if (value === void 0 || value === "") {
|
|
4750
|
+
return { ok: false, error: `${bin} sandbox upgrade: --version requires a value` };
|
|
4751
|
+
}
|
|
4752
|
+
if (value.startsWith("-")) {
|
|
4753
|
+
return { ok: false, error: `${bin} sandbox upgrade: unknown flag "${value}"` };
|
|
4754
|
+
}
|
|
4755
|
+
const version2 = parseReleaseVersion(value);
|
|
4756
|
+
if (version2 === void 0) {
|
|
4757
|
+
return { ok: false, error: `${bin} sandbox upgrade: --version "${value}" is not a release version \u2014 expected X.Y.Z` };
|
|
4758
|
+
}
|
|
4759
|
+
if (extra.length > 0) {
|
|
4760
|
+
const token = extra[0];
|
|
4761
|
+
return { ok: false, error: `${bin} sandbox upgrade: ${token.startsWith("-") && token !== "--version" ? "unknown flag" : "unexpected argument"} "${token}"` };
|
|
4762
|
+
}
|
|
4763
|
+
return { ok: true, args: { ...args, subcommand: "upgrade", name, version: version2 } };
|
|
4764
|
+
}
|
|
4765
|
+
if (head !== "install" && head !== "list") {
|
|
4766
|
+
return {
|
|
4767
|
+
ok: false,
|
|
4768
|
+
error: `${bin} sandbox: unknown subcommand "${head}" \u2014 expected install or list`,
|
|
4769
|
+
showHelp: true
|
|
4770
|
+
};
|
|
4771
|
+
}
|
|
4772
|
+
args.subcommand = head;
|
|
4773
|
+
if (isHelpFlag(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
4774
|
+
const positionals = [];
|
|
4775
|
+
const checkoutSpellings = /* @__PURE__ */ new Set();
|
|
4776
|
+
let version;
|
|
4777
|
+
for (let i = 0; i < tail.length; i++) {
|
|
4778
|
+
const token = tail[i];
|
|
4779
|
+
const valueFlag = SANDBOX_VALUE_FLAGS[token];
|
|
4780
|
+
if (valueFlag !== void 0) {
|
|
4781
|
+
const value = tail[i + 1];
|
|
4782
|
+
if (value === void 0) {
|
|
4783
|
+
return { ok: false, error: `${bin} sandbox ${head}: ${token} requires a value` };
|
|
4784
|
+
}
|
|
4785
|
+
if (valueFlag === "checkout") checkoutSpellings.add(token);
|
|
4786
|
+
args[valueFlag] = value;
|
|
4787
|
+
i++;
|
|
4788
|
+
} else if (token === "--yes") {
|
|
4789
|
+
args.yes = true;
|
|
4790
|
+
} else if (token === "--no-build") {
|
|
4791
|
+
args.noBuild = true;
|
|
4792
|
+
} else if (token === "--print-argv") {
|
|
4793
|
+
args.printArgv = true;
|
|
4794
|
+
} else if (token === "--image") {
|
|
4795
|
+
args.image = true;
|
|
4796
|
+
} else if (token.startsWith("--image=")) {
|
|
4797
|
+
const ref = token.slice("--image=".length);
|
|
4798
|
+
if (ref === "") {
|
|
4799
|
+
return { ok: false, error: `${bin} sandbox: --image=<ref> requires a non-empty image ref` };
|
|
4800
|
+
}
|
|
4801
|
+
args.image = true;
|
|
4802
|
+
args.imageRef = ref;
|
|
4803
|
+
} else if (token === "--version" || token.startsWith("--version=")) {
|
|
4804
|
+
const value = token === "--version" ? tail[++i] : token.slice("--version=".length);
|
|
4805
|
+
if (value === void 0 || value === "") {
|
|
4806
|
+
return { ok: false, error: `${bin} sandbox ${head}: --version requires a value` };
|
|
4807
|
+
}
|
|
4808
|
+
version = parseReleaseVersion(value);
|
|
4809
|
+
if (version === void 0) {
|
|
4810
|
+
return {
|
|
4811
|
+
ok: false,
|
|
4812
|
+
error: `${bin} sandbox ${head}: --version "${value}" is not a release version \u2014 expected X.Y.Z`
|
|
4813
|
+
};
|
|
4814
|
+
}
|
|
4815
|
+
} else if (token === "--json") {
|
|
4816
|
+
args.json = true;
|
|
4817
|
+
} else if (token.startsWith("-")) {
|
|
4818
|
+
return { ok: false, error: `${bin} sandbox ${head}: unknown flag "${token}"` };
|
|
4819
|
+
} else {
|
|
4820
|
+
positionals.push(token);
|
|
4821
|
+
}
|
|
4822
|
+
}
|
|
4823
|
+
if (checkoutSpellings.has("--checkout") && checkoutSpellings.has("--repo")) {
|
|
4824
|
+
return {
|
|
4825
|
+
ok: false,
|
|
4826
|
+
error: `${bin} sandbox ${head}: --checkout conflicts with --repo \u2014 pass exactly one, and prefer --checkout`
|
|
4827
|
+
};
|
|
4828
|
+
}
|
|
4829
|
+
if (version !== void 0) {
|
|
4830
|
+
if (args.imageRef !== void 0) {
|
|
4831
|
+
return {
|
|
4832
|
+
ok: false,
|
|
4833
|
+
error: `${bin} sandbox ${head}: --version conflicts with --image=<ref> \u2014 pass --version for an official release or --image=<ref> for a custom image`
|
|
4834
|
+
};
|
|
4835
|
+
}
|
|
4836
|
+
args.image = true;
|
|
4837
|
+
args.imageRef = officialImageRef(version);
|
|
4838
|
+
}
|
|
4839
|
+
if (head === "list") {
|
|
4840
|
+
if (positionals.length > 0) {
|
|
4841
|
+
return { ok: false, error: `${bin} sandbox list: unexpected argument "${positionals[0]}"` };
|
|
4842
|
+
}
|
|
4843
|
+
return { ok: true, args };
|
|
4844
|
+
}
|
|
4845
|
+
if (positionals.length === 0) {
|
|
4846
|
+
return {
|
|
4847
|
+
ok: false,
|
|
4848
|
+
error: `${bin} sandbox install: a runtime is required, e.g. \`${bin} sandbox install docker\``,
|
|
4849
|
+
showHelp: true
|
|
4850
|
+
};
|
|
4851
|
+
}
|
|
4852
|
+
if (positionals.length > 1) {
|
|
4853
|
+
return { ok: false, error: `${bin} sandbox install: unexpected argument "${positionals[1]}"` };
|
|
4854
|
+
}
|
|
4855
|
+
args.runtime = positionals[0];
|
|
4856
|
+
return { ok: true, args };
|
|
4857
|
+
}
|
|
4858
|
+
async function runSandboxCommand(rest, bin) {
|
|
4859
|
+
const parsed = parseSandboxArgs(rest, bin);
|
|
4860
|
+
if (!parsed.ok) {
|
|
4861
|
+
process.stderr.write(`${parsed.error}
|
|
4862
|
+
`);
|
|
4863
|
+
if (parsed.showHelp) printSandboxHelp(bin);
|
|
4864
|
+
return 1;
|
|
4865
|
+
}
|
|
4866
|
+
const a = parsed.args;
|
|
4867
|
+
if (a.help) {
|
|
4868
|
+
printSandboxHelp(bin);
|
|
4869
|
+
return a.subcommand === void 0 ? 1 : 0;
|
|
4870
|
+
}
|
|
4871
|
+
const io = {
|
|
4872
|
+
stdout: (s) => process.stdout.write(s),
|
|
4873
|
+
stderr: (s) => process.stderr.write(s)
|
|
4874
|
+
};
|
|
4875
|
+
if (a.subcommand === "list") return await runSandboxList({ bin, json: a.json }, io);
|
|
4876
|
+
if (a.subcommand === "upgrade") {
|
|
4877
|
+
return await runSandboxUpgrade({ bin, name: a.name, version: a.version }, io);
|
|
4878
|
+
}
|
|
4879
|
+
return await runSandboxInstall(
|
|
4880
|
+
{
|
|
4881
|
+
bin,
|
|
4882
|
+
runtime: a.runtime,
|
|
4883
|
+
...a.name !== void 0 ? { name: a.name } : {},
|
|
4884
|
+
...a.checkout !== void 0 ? { checkout: a.checkout } : {},
|
|
4885
|
+
...a.homeMount !== void 0 ? { homeMount: a.homeMount } : {},
|
|
4886
|
+
yes: a.yes,
|
|
4887
|
+
image: a.image,
|
|
4888
|
+
...a.imageRef !== void 0 ? { imageRef: a.imageRef } : {},
|
|
4889
|
+
noBuild: a.noBuild,
|
|
4890
|
+
printArgv: a.printArgv
|
|
4891
|
+
},
|
|
4892
|
+
io
|
|
4893
|
+
);
|
|
4894
|
+
}
|
|
4895
|
+
|
|
4052
4896
|
// src/commands/harness.ts
|
|
4053
|
-
import { existsSync as
|
|
4897
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
4054
4898
|
import { homedir as homedir3 } from "node:os";
|
|
4055
|
-
import { delimiter as delimiter2, join as
|
|
4899
|
+
import { delimiter as delimiter2, join as join17 } from "node:path";
|
|
4056
4900
|
|
|
4057
4901
|
// src/lib/docs.ts
|
|
4058
4902
|
var SOURCE_DOCS_BASE = "https://github.com/mifunedev/agro/blob/main/";
|
|
@@ -4065,17 +4909,20 @@ function sourceDocsUrl(docsPath) {
|
|
|
4065
4909
|
}
|
|
4066
4910
|
|
|
4067
4911
|
// src/lib/host-config.ts
|
|
4068
|
-
import { existsSync as
|
|
4069
|
-
import { basename as
|
|
4912
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync12, renameSync as renameSync4, unlinkSync as unlinkSync6, writeFileSync as writeFileSync9 } from "node:fs";
|
|
4913
|
+
import { basename as basename7, dirname as dirname8, isAbsolute, join as join15, resolve as resolve10 } from "node:path";
|
|
4070
4914
|
var HOST_CONFIG_MODE = 420;
|
|
4071
4915
|
var HOST_CONFIG_FILE = "config.json";
|
|
4072
4916
|
var WORKSPACES_SUBDIR = "workspaces";
|
|
4073
4917
|
var DEFAULT_WORKSPACE_NAME = "harness";
|
|
4918
|
+
function isRootToolReceipt(receipt) {
|
|
4919
|
+
return receipt.scope === "root";
|
|
4920
|
+
}
|
|
4074
4921
|
function hostStateHome(env, home) {
|
|
4075
4922
|
return resolveUserStateHome(env, home);
|
|
4076
4923
|
}
|
|
4077
4924
|
function hostConfigPath(env, home) {
|
|
4078
|
-
return
|
|
4925
|
+
return join15(hostStateHome(env, home), HOST_CONFIG_FILE);
|
|
4079
4926
|
}
|
|
4080
4927
|
function assertWorkspaceName(name) {
|
|
4081
4928
|
if (!SANDBOX_NAME_PATTERN.test(name)) {
|
|
@@ -4085,14 +4932,14 @@ function assertWorkspaceName(name) {
|
|
|
4085
4932
|
}
|
|
4086
4933
|
}
|
|
4087
4934
|
function workspacesRoot(env, home) {
|
|
4088
|
-
return
|
|
4935
|
+
return join15(hostStateHome(env, home), WORKSPACES_SUBDIR);
|
|
4089
4936
|
}
|
|
4090
4937
|
function workspaceRoot(name, env, home) {
|
|
4091
4938
|
assertWorkspaceName(name);
|
|
4092
|
-
return
|
|
4939
|
+
return join15(workspacesRoot(env, home), name);
|
|
4093
4940
|
}
|
|
4094
4941
|
function defaultHarnessRoot(env, home) {
|
|
4095
|
-
return
|
|
4942
|
+
return join15(workspacesRoot(env, home), DEFAULT_WORKSPACE_NAME);
|
|
4096
4943
|
}
|
|
4097
4944
|
function validateHostConfig(value) {
|
|
4098
4945
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -4114,7 +4961,7 @@ function validateHostConfig(value) {
|
|
|
4114
4961
|
const hostHarnesses = record.hostHarnesses;
|
|
4115
4962
|
if (hostHarnesses !== void 0) validateReceipts(hostHarnesses, "hostHarnesses", "harness");
|
|
4116
4963
|
const hostTools = record.hostTools;
|
|
4117
|
-
if (hostTools !== void 0) validateReceipts(hostTools, "hostTools", "tool");
|
|
4964
|
+
if (hostTools !== void 0) validateReceipts(hostTools, "hostTools", "tool", true);
|
|
4118
4965
|
return { ...record, version: 1 };
|
|
4119
4966
|
}
|
|
4120
4967
|
function absoluteField(value, path4) {
|
|
@@ -4124,7 +4971,7 @@ function absoluteField(value, path4) {
|
|
|
4124
4971
|
if (!isAbsolute(value)) throw fieldError2(path4, "must be an absolute path");
|
|
4125
4972
|
return value;
|
|
4126
4973
|
}
|
|
4127
|
-
function validateReceipts(value, field, noun) {
|
|
4974
|
+
function validateReceipts(value, field, noun, allowRootScope = false) {
|
|
4128
4975
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4129
4976
|
throw fieldError2(field, `must be a JSON object keyed by ${noun} id`);
|
|
4130
4977
|
}
|
|
@@ -4135,8 +4982,15 @@ function validateReceipts(value, field, noun) {
|
|
|
4135
4982
|
throw fieldError2(at, "must be a JSON object");
|
|
4136
4983
|
}
|
|
4137
4984
|
const entry = receipt;
|
|
4138
|
-
|
|
4139
|
-
|
|
4985
|
+
if (allowRootScope && entry.scope !== void 0) {
|
|
4986
|
+
if (entry.scope !== "root") throw fieldError2(`${at}.scope`, 'must be "root" when present');
|
|
4987
|
+
for (const key of ["prefix", "binPath"]) {
|
|
4988
|
+
if (entry[key] !== void 0) throw fieldError2(`${at}.${key}`, 'must be absent when scope is "root"');
|
|
4989
|
+
}
|
|
4990
|
+
} else {
|
|
4991
|
+
absoluteField(entry.prefix, `${at}.prefix`);
|
|
4992
|
+
absoluteField(entry.binPath, `${at}.binPath`);
|
|
4993
|
+
}
|
|
4140
4994
|
if (typeof entry.binary !== "string" || entry.binary === "") {
|
|
4141
4995
|
throw fieldError2(`${at}.binary`, "must be a non-empty string");
|
|
4142
4996
|
}
|
|
@@ -4150,10 +5004,10 @@ function validateReceipts(value, field, noun) {
|
|
|
4150
5004
|
}
|
|
4151
5005
|
function readHostConfig(env, home) {
|
|
4152
5006
|
const path4 = hostConfigPath(env, home);
|
|
4153
|
-
if (!
|
|
5007
|
+
if (!existsSync18(path4)) return { version: 1 };
|
|
4154
5008
|
let raw;
|
|
4155
5009
|
try {
|
|
4156
|
-
raw =
|
|
5010
|
+
raw = readFileSync12(path4, "utf8");
|
|
4157
5011
|
} catch (error) {
|
|
4158
5012
|
const detail = error instanceof Error ? error.message : String(error);
|
|
4159
5013
|
throw new Error(`could not read ${path4}: ${detail}`);
|
|
@@ -4162,7 +5016,7 @@ function readHostConfig(env, home) {
|
|
|
4162
5016
|
try {
|
|
4163
5017
|
parsed = JSON.parse(raw);
|
|
4164
5018
|
} catch {
|
|
4165
|
-
throw new Error(`${
|
|
5019
|
+
throw new Error(`${basename7(path4)} is not valid JSON: ${path4}`);
|
|
4166
5020
|
}
|
|
4167
5021
|
return validateHostConfig(parsed);
|
|
4168
5022
|
}
|
|
@@ -4178,22 +5032,22 @@ function writeHostConfig(config, env, home) {
|
|
|
4178
5032
|
renameSync4(tmp, path4);
|
|
4179
5033
|
} catch (error) {
|
|
4180
5034
|
try {
|
|
4181
|
-
|
|
5035
|
+
unlinkSync6(tmp);
|
|
4182
5036
|
} catch {
|
|
4183
5037
|
}
|
|
4184
5038
|
throw error;
|
|
4185
5039
|
}
|
|
4186
5040
|
}
|
|
4187
5041
|
function resolveHarnessRoot(explicit, env, home) {
|
|
4188
|
-
if (typeof explicit === "string" && explicit !== "") return
|
|
5042
|
+
if (typeof explicit === "string" && explicit !== "") return resolve10(explicit);
|
|
4189
5043
|
const configured = readHostConfig(env, home).harnessRoot;
|
|
4190
|
-
if (typeof configured === "string" && configured !== "") return
|
|
5044
|
+
if (typeof configured === "string" && configured !== "") return resolve10(configured);
|
|
4191
5045
|
return defaultHarnessRoot(env, home);
|
|
4192
5046
|
}
|
|
4193
5047
|
function recordHarnessRoot(root, env, home) {
|
|
4194
5048
|
const config = readHostConfig(env, home);
|
|
4195
5049
|
const recorded = config.harnessRoot;
|
|
4196
|
-
if (typeof recorded === "string" && recorded !== "" &&
|
|
5050
|
+
if (typeof recorded === "string" && recorded !== "" && resolve10(recorded) === root) return;
|
|
4197
5051
|
writeHostConfig({ ...config, harnessRoot: root }, env, home);
|
|
4198
5052
|
}
|
|
4199
5053
|
function fieldError2(path4, requirement) {
|
|
@@ -4201,8 +5055,8 @@ function fieldError2(path4, requirement) {
|
|
|
4201
5055
|
}
|
|
4202
5056
|
|
|
4203
5057
|
// src/lib/host-workspace.ts
|
|
4204
|
-
import { existsSync as
|
|
4205
|
-
import { dirname as dirname9, join as
|
|
5058
|
+
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";
|
|
5059
|
+
import { dirname as dirname9, join as join16, resolve as resolve11 } from "node:path";
|
|
4206
5060
|
var AGRO_REPO_URL = "https://github.com/mifunedev/agro.git";
|
|
4207
5061
|
var IGNORABLE_ENTRIES = [
|
|
4208
5062
|
REGISTRY_SUBDIR,
|
|
@@ -4214,15 +5068,15 @@ function isDirectory3(path4) {
|
|
|
4214
5068
|
return statSync5(path4, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
4215
5069
|
}
|
|
4216
5070
|
function stateHomes(home) {
|
|
4217
|
-
return STATE_HOME_DIRS.map((name) =>
|
|
5071
|
+
return STATE_HOME_DIRS.map((name) => join16(home, name));
|
|
4218
5072
|
}
|
|
4219
5073
|
function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
4220
|
-
const target =
|
|
5074
|
+
const target = resolve11(root);
|
|
4221
5075
|
const enclosing = stateHomes(home).find((state) => target === state);
|
|
4222
5076
|
if (enclosing === void 0) return void 0;
|
|
4223
5077
|
return `${bin} ${verb}: the ${verb} root ${target} is the state home ${enclosing} itself.
|
|
4224
5078
|
A workspace there collides with AGRO state and blocks every command run from ${home}.
|
|
4225
|
-
Move it out \u2014 for example \`mv ${enclosing} ${
|
|
5079
|
+
Move it out \u2014 for example \`mv ${enclosing} ${join16(home, "agro")}\` \u2014 then re-run this command.
|
|
4226
5080
|
`;
|
|
4227
5081
|
}
|
|
4228
5082
|
function isIgnorableEntry(name) {
|
|
@@ -4239,24 +5093,24 @@ function cloneInto(target, run, root, ref) {
|
|
|
4239
5093
|
assertCloned(run("git", ["clone", ...branch, AGRO_REPO_URL, target], { stdio: "inherit" }), root);
|
|
4240
5094
|
}
|
|
4241
5095
|
function cloneThroughStaging(root, run, ref) {
|
|
4242
|
-
const staging = mkdtempSync4(
|
|
5096
|
+
const staging = mkdtempSync4(join16(dirname9(root), "agro-clone-"));
|
|
4243
5097
|
try {
|
|
4244
|
-
const checkout =
|
|
5098
|
+
const checkout = join16(staging, "agro");
|
|
4245
5099
|
cloneInto(checkout, run, root, ref);
|
|
4246
5100
|
for (const name of readdirSync3(checkout)) {
|
|
4247
|
-
const destination =
|
|
4248
|
-
if (
|
|
5101
|
+
const destination = join16(root, name);
|
|
5102
|
+
if (existsSync19(destination)) {
|
|
4249
5103
|
throw new Error(`could not move ${name} into ${root} \u2014 the path already exists`);
|
|
4250
5104
|
}
|
|
4251
|
-
renameSync5(
|
|
5105
|
+
renameSync5(join16(checkout, name), destination);
|
|
4252
5106
|
}
|
|
4253
5107
|
} finally {
|
|
4254
5108
|
rmSync4(staging, { recursive: true, force: true });
|
|
4255
5109
|
}
|
|
4256
5110
|
}
|
|
4257
5111
|
function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
4258
|
-
const root =
|
|
4259
|
-
if (
|
|
5112
|
+
const root = resolve11(path4);
|
|
5113
|
+
if (existsSync19(join16(root, ".git"))) return { root, action: "reused" };
|
|
4260
5114
|
const stats = statSync5(root, { throwIfNoEntry: false });
|
|
4261
5115
|
if (stats === void 0) {
|
|
4262
5116
|
mkdirSync10(dirname9(root), { recursive: true });
|
|
@@ -4278,11 +5132,11 @@ function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
|
4278
5132
|
function listHostWorkspaces(env, home) {
|
|
4279
5133
|
const root = workspacesRoot(env, home);
|
|
4280
5134
|
if (!isDirectory3(root)) return [];
|
|
4281
|
-
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => SANDBOX_NAME_PATTERN.test(name)).filter((name) =>
|
|
5135
|
+
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();
|
|
4282
5136
|
}
|
|
4283
5137
|
function resolveExistingWorkspace(bin, verb, path4, env, home) {
|
|
4284
|
-
const root =
|
|
4285
|
-
if (
|
|
5138
|
+
const root = resolve11(path4);
|
|
5139
|
+
if (existsSync19(join16(root, ".git"))) return { ok: true, root };
|
|
4286
5140
|
const existing = listHostWorkspaces(env, home);
|
|
4287
5141
|
const inventory = existing.length === 0 ? "No host workspace exists yet.\n" : `Workspaces that exist: ${existing.join(", ")}
|
|
4288
5142
|
`;
|
|
@@ -4312,7 +5166,7 @@ function targetFor(root, run, env) {
|
|
|
4312
5166
|
});
|
|
4313
5167
|
}
|
|
4314
5168
|
function hostPrefix(home) {
|
|
4315
|
-
return
|
|
5169
|
+
return join17(home, ".local");
|
|
4316
5170
|
}
|
|
4317
5171
|
function pathEntries(env) {
|
|
4318
5172
|
return (env.PATH ?? "").split(delimiter2).filter((part) => part !== "");
|
|
@@ -4346,6 +5200,21 @@ async function probeInstalled(target, entry, prefix, user, env) {
|
|
|
4346
5200
|
throw err;
|
|
4347
5201
|
}
|
|
4348
5202
|
}
|
|
5203
|
+
function sandboxMarkerPath(entry) {
|
|
5204
|
+
return `${SANDBOX_HARNESS_PREFIX}/share/agro/harnesses/${entry.id}.installed`;
|
|
5205
|
+
}
|
|
5206
|
+
async function sandboxMarkerExists(target, entry) {
|
|
5207
|
+
const r = await target.exec({
|
|
5208
|
+
argv: ["test", "-f", sandboxMarkerPath(entry)],
|
|
5209
|
+
user: "sandbox",
|
|
5210
|
+
stdio: "capture",
|
|
5211
|
+
timeoutMs: PROBE_TIMEOUT_MS
|
|
5212
|
+
});
|
|
5213
|
+
return r.exitCode === 0;
|
|
5214
|
+
}
|
|
5215
|
+
function hostBinaryUnderPrefix(entry, prefix) {
|
|
5216
|
+
return existsSync20(join17(harnessBinPath(prefix), entry.binary));
|
|
5217
|
+
}
|
|
4349
5218
|
function stateOf(entry, installed, location) {
|
|
4350
5219
|
return {
|
|
4351
5220
|
id: entry.id,
|
|
@@ -4365,7 +5234,7 @@ function probeableHost(env, home) {
|
|
|
4365
5234
|
return void 0;
|
|
4366
5235
|
}
|
|
4367
5236
|
const prefix = hostPrefix(home);
|
|
4368
|
-
if (!
|
|
5237
|
+
if (!existsSync20(join17(root, ".git")) && !existsSync20(prefix)) return void 0;
|
|
4369
5238
|
return { root, prefix };
|
|
4370
5239
|
}
|
|
4371
5240
|
async function collectStates(root, run, home, env, only) {
|
|
@@ -4571,7 +5440,15 @@ Run: bash ${root}/.agro/scripts/link-providers.sh --init
|
|
|
4571
5440
|
const code = await reconcileHermes(target, io, bin, void 0, root);
|
|
4572
5441
|
if (code !== 0) return code;
|
|
4573
5442
|
}
|
|
4574
|
-
|
|
5443
|
+
let hasReceipt;
|
|
5444
|
+
try {
|
|
5445
|
+
hasReceipt = readHostConfig(env, home).hostHarnesses?.[entry.id] !== void 0;
|
|
5446
|
+
} catch (err) {
|
|
5447
|
+
io.stderr(`${bin} harness: ${messageOf(err)}
|
|
5448
|
+
`);
|
|
5449
|
+
return 1;
|
|
5450
|
+
}
|
|
5451
|
+
if (await probeInstalled(target, entry, prefix, void 0, installEnv) === true && (hasReceipt || !hostBinaryUnderPrefix(entry, prefix))) {
|
|
4575
5452
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
4576
5453
|
`);
|
|
4577
5454
|
try {
|
|
@@ -4735,7 +5612,20 @@ async function runHarnessUninstall(name, opts, io) {
|
|
|
4735
5612
|
if (!isReachable(await resolveTargetStatus(target))) {
|
|
4736
5613
|
return await uninstallOnHost(entry, opts, io, run);
|
|
4737
5614
|
}
|
|
4738
|
-
|
|
5615
|
+
const outcome = await removeHarness(entry, target, SANDBOX_HARNESS_PREFIX, "sandbox", opts, io);
|
|
5616
|
+
if (outcome.dropReceipt) {
|
|
5617
|
+
const cleared = await target.exec({
|
|
5618
|
+
argv: ["rm", "-f", sandboxMarkerPath(entry)],
|
|
5619
|
+
user: "sandbox",
|
|
5620
|
+
stdio: "capture"
|
|
5621
|
+
});
|
|
5622
|
+
if (cleared.exitCode !== 0) {
|
|
5623
|
+
io.stderr(`${opts.bin} harness: could not clear the install record (exit ${cleared.exitCode}).
|
|
5624
|
+
`);
|
|
5625
|
+
return 1;
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
return outcome.code;
|
|
4739
5629
|
}
|
|
4740
5630
|
async function runHarnessInstall(name, opts, io) {
|
|
4741
5631
|
const run = opts.run ?? spawnRunner;
|
|
@@ -4761,7 +5651,7 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4761
5651
|
if (code !== 0) return code;
|
|
4762
5652
|
}
|
|
4763
5653
|
const already = await probeInstalled(target, entry, SANDBOX_HARNESS_PREFIX, "sandbox", installEnv);
|
|
4764
|
-
if (already === true) {
|
|
5654
|
+
if (already === true && await sandboxMarkerExists(target, entry)) {
|
|
4765
5655
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
4766
5656
|
`);
|
|
4767
5657
|
return 0;
|
|
@@ -4792,15 +5682,26 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4792
5682
|
const code = await reconcileHermes(target, io, opts.bin, "sandbox");
|
|
4793
5683
|
if (code !== 0) return code;
|
|
4794
5684
|
}
|
|
5685
|
+
const marker = sandboxMarkerPath(entry);
|
|
5686
|
+
const marked = await target.exec({
|
|
5687
|
+
argv: ["sh", "-c", `mkdir -p "$(dirname "$1")" && : > "$1"`, "sh", marker],
|
|
5688
|
+
user: "sandbox",
|
|
5689
|
+
stdio: "capture"
|
|
5690
|
+
});
|
|
5691
|
+
if (marked.exitCode !== 0) {
|
|
5692
|
+
io.stderr(`${opts.bin} harness: could not record the install at ${marker} (exit ${marked.exitCode}).
|
|
5693
|
+
`);
|
|
5694
|
+
return 1;
|
|
5695
|
+
}
|
|
4795
5696
|
io.stdout(`${entry.id}: installed \u2014 see ${sourceDocsUrl(entry.docsPath)} for authentication
|
|
4796
5697
|
`);
|
|
4797
5698
|
return 0;
|
|
4798
5699
|
}
|
|
4799
5700
|
|
|
4800
5701
|
// src/commands/workspace.ts
|
|
4801
|
-
import { existsSync as
|
|
5702
|
+
import { existsSync as existsSync21, rmSync as rmSync5 } from "node:fs";
|
|
4802
5703
|
import { homedir as homedir4 } from "node:os";
|
|
4803
|
-
import { join as
|
|
5704
|
+
import { join as join18, resolve as resolve12 } from "node:path";
|
|
4804
5705
|
function homeOf2(opts) {
|
|
4805
5706
|
return (opts.homedir ?? homedir4)();
|
|
4806
5707
|
}
|
|
@@ -4809,7 +5710,7 @@ function messageOf2(error) {
|
|
|
4809
5710
|
}
|
|
4810
5711
|
function recordedRoot(env, home) {
|
|
4811
5712
|
const configured = readHostConfig(env, home).harnessRoot;
|
|
4812
|
-
return typeof configured === "string" && configured !== "" ?
|
|
5713
|
+
return typeof configured === "string" && configured !== "" ? resolve12(configured) : void 0;
|
|
4813
5714
|
}
|
|
4814
5715
|
function renderTable2(rows, io) {
|
|
4815
5716
|
const header2 = ["WORKSPACE", "DEFAULT", "PATH"];
|
|
@@ -4834,7 +5735,7 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
4834
5735
|
let root;
|
|
4835
5736
|
const chosen = name ?? DEFAULT_WORKSPACE_NAME;
|
|
4836
5737
|
try {
|
|
4837
|
-
root = opts.path !== void 0 ?
|
|
5738
|
+
root = opts.path !== void 0 ? resolve12(opts.path) : workspaceRoot(chosen, env, home);
|
|
4838
5739
|
} catch (err) {
|
|
4839
5740
|
io.stderr(`${bin} workspace: ${messageOf2(err)}
|
|
4840
5741
|
`);
|
|
@@ -4846,9 +5747,9 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
4846
5747
|
return 1;
|
|
4847
5748
|
}
|
|
4848
5749
|
let action;
|
|
4849
|
-
const preexisting =
|
|
5750
|
+
const preexisting = existsSync21(root);
|
|
4850
5751
|
try {
|
|
4851
|
-
if (!opts.json && !
|
|
5752
|
+
if (!opts.json && !existsSync21(join18(root, ".git"))) {
|
|
4852
5753
|
const at = opts.ref !== void 0 ? ` at ${opts.ref}` : "";
|
|
4853
5754
|
io.stdout(`cloning ${AGRO_REPO_URL}${at} into ${root}\u2026
|
|
4854
5755
|
`);
|
|
@@ -4893,7 +5794,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
4893
5794
|
const recorded = recordedRoot(env, home);
|
|
4894
5795
|
const registry = workspacesRoot(env, home);
|
|
4895
5796
|
rows = listHostWorkspaces(env, home).map((name) => {
|
|
4896
|
-
const root =
|
|
5797
|
+
const root = join18(registry, name);
|
|
4897
5798
|
return { name, root, default: recorded === root };
|
|
4898
5799
|
});
|
|
4899
5800
|
} catch (err) {
|
|
@@ -4918,9 +5819,9 @@ async function runWorkspaceList(opts, io) {
|
|
|
4918
5819
|
}
|
|
4919
5820
|
|
|
4920
5821
|
// src/commands/tool.ts
|
|
4921
|
-
import { existsSync as
|
|
5822
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
4922
5823
|
import { homedir as homedir5, userInfo as userInfo2 } from "node:os";
|
|
4923
|
-
import { delimiter as delimiter3, join as
|
|
5824
|
+
import { delimiter as delimiter3, join as join19 } from "node:path";
|
|
4924
5825
|
|
|
4925
5826
|
// src/lib/tools/catalog.ts
|
|
4926
5827
|
var TOOLS_DOC = "docs/installation.md";
|
|
@@ -5337,6 +6238,7 @@ function resolveToolInstallArgv(entry, host) {
|
|
|
5337
6238
|
}
|
|
5338
6239
|
|
|
5339
6240
|
// src/commands/tool.ts
|
|
6241
|
+
var ROOT_TOOL_REMOVAL_DOCS = `${sourceDocsUrl("docs/installation.md")}#remove-a-root-level-tool`;
|
|
5340
6242
|
function isReachable2(status) {
|
|
5341
6243
|
return status === "ready" || status === "starting";
|
|
5342
6244
|
}
|
|
@@ -5350,7 +6252,21 @@ function targetFor2(root, run, env) {
|
|
|
5350
6252
|
});
|
|
5351
6253
|
}
|
|
5352
6254
|
function hostPrefix2(home) {
|
|
5353
|
-
return
|
|
6255
|
+
return join19(home, ".local");
|
|
6256
|
+
}
|
|
6257
|
+
function sandboxMarkerPath2(entry) {
|
|
6258
|
+
return `${SANDBOX_HARNESS_PREFIX}/share/agro/tools/${entry.id}.installed`;
|
|
6259
|
+
}
|
|
6260
|
+
async function sandboxMarkerExists2(target, entry) {
|
|
6261
|
+
const r = await target.exec({
|
|
6262
|
+
argv: ["test", "-f", sandboxMarkerPath2(entry)],
|
|
6263
|
+
user: "sandbox",
|
|
6264
|
+
stdio: "capture"
|
|
6265
|
+
});
|
|
6266
|
+
return r.exitCode === 0;
|
|
6267
|
+
}
|
|
6268
|
+
function hostBinaryUnderPrefix2(entry, prefix) {
|
|
6269
|
+
return existsSync22(join19(harnessBinPath(prefix), entry.binary));
|
|
5354
6270
|
}
|
|
5355
6271
|
function pathEntries2(env) {
|
|
5356
6272
|
return (env.PATH ?? "").split(delimiter3).filter((part) => part !== "");
|
|
@@ -5430,7 +6346,7 @@ function probeableHost2(env, home) {
|
|
|
5430
6346
|
return void 0;
|
|
5431
6347
|
}
|
|
5432
6348
|
const prefix = hostPrefix2(home);
|
|
5433
|
-
if (!
|
|
6349
|
+
if (!existsSync22(join19(root, ".git")) && !existsSync22(prefix)) return void 0;
|
|
5434
6350
|
return { root, prefix };
|
|
5435
6351
|
}
|
|
5436
6352
|
async function collectRows(root, run, home, env, only) {
|
|
@@ -5646,7 +6562,15 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5646
6562
|
const prefix = hostPrefix2(home);
|
|
5647
6563
|
const installEnv = { NPM_USER_PREFIX: prefix };
|
|
5648
6564
|
const target = hostTargetFor2(root, prefix, run, env);
|
|
5649
|
-
|
|
6565
|
+
let hasReceipt;
|
|
6566
|
+
try {
|
|
6567
|
+
hasReceipt = readHostConfig(env, home).hostTools?.[entry.id] !== void 0;
|
|
6568
|
+
} catch (err) {
|
|
6569
|
+
io.stderr(`${bin} tool: ${messageOf3(err)}
|
|
6570
|
+
`);
|
|
6571
|
+
return 1;
|
|
6572
|
+
}
|
|
6573
|
+
if (await probeInstalled2(target, entry, void 0, installEnv) === true && (hasReceipt || entry.hostInstallUser === "root" || !hostBinaryUnderPrefix2(entry, prefix))) {
|
|
5650
6574
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
5651
6575
|
`);
|
|
5652
6576
|
try {
|
|
@@ -5681,13 +6605,8 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5681
6605
|
`);
|
|
5682
6606
|
return r.exitCode;
|
|
5683
6607
|
}
|
|
5684
|
-
const
|
|
5685
|
-
|
|
5686
|
-
binary: entry.binary,
|
|
5687
|
-
binPath: harnessBinPath(prefix),
|
|
5688
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5689
|
-
workspaceRoot: root
|
|
5690
|
-
};
|
|
6608
|
+
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6609
|
+
const receipt = entry.hostInstallUser === "root" ? { scope: "root", binary: entry.binary, installedAt, workspaceRoot: root } : { prefix, binary: entry.binary, binPath: harnessBinPath(prefix), installedAt, workspaceRoot: root };
|
|
5691
6610
|
try {
|
|
5692
6611
|
const config = readHostConfig(env, home);
|
|
5693
6612
|
writeHostConfig(
|
|
@@ -5747,7 +6666,7 @@ ${installableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
|
5747
6666
|
}
|
|
5748
6667
|
if (entry.installArgv === void 0) return refuseContainer();
|
|
5749
6668
|
const already = await probeInstalled2(target, entry, "sandbox");
|
|
5750
|
-
if (already === true) {
|
|
6669
|
+
if (already === true && await sandboxMarkerExists2(target, entry)) {
|
|
5751
6670
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
5752
6671
|
`);
|
|
5753
6672
|
return 0;
|
|
@@ -5769,6 +6688,17 @@ ${installableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
|
5769
6688
|
`);
|
|
5770
6689
|
return r.exitCode;
|
|
5771
6690
|
}
|
|
6691
|
+
const marker = sandboxMarkerPath2(entry);
|
|
6692
|
+
const marked = await target.exec({
|
|
6693
|
+
argv: ["sh", "-c", `mkdir -p "$(dirname "$1")" && : > "$1"`, "sh", marker],
|
|
6694
|
+
user: "sandbox",
|
|
6695
|
+
stdio: "capture"
|
|
6696
|
+
});
|
|
6697
|
+
if (marked.exitCode !== 0) {
|
|
6698
|
+
io.stderr(`${opts.bin} tool: could not record the install at ${marker} (exit ${marked.exitCode}).
|
|
6699
|
+
`);
|
|
6700
|
+
return 1;
|
|
6701
|
+
}
|
|
5772
6702
|
io.stdout(`${entry.id}: installed \u2014 see ${sourceDocsUrl(entry.docsPath)}
|
|
5773
6703
|
`);
|
|
5774
6704
|
return 0;
|
|
@@ -5829,7 +6759,7 @@ Removing it could delete a tool you installed yourself. Re-run with \`--force\`
|
|
|
5829
6759
|
);
|
|
5830
6760
|
return 1;
|
|
5831
6761
|
}
|
|
5832
|
-
const prefix = receipt
|
|
6762
|
+
const prefix = receipt === void 0 || isRootToolReceipt(receipt) ? computed : receipt.prefix;
|
|
5833
6763
|
const target = hostTargetFor2(receipt?.workspaceRoot ?? workspace, prefix, run, env);
|
|
5834
6764
|
const outcome = await removeTool(entry, target, prefix, void 0, opts, io, true);
|
|
5835
6765
|
if (outcome.dropReceipt && receipt !== void 0) {
|
|
@@ -5852,6 +6782,15 @@ async function runToolUninstall(name, opts, io) {
|
|
|
5852
6782
|
const root = resolveProjectRoot(opts.cwd);
|
|
5853
6783
|
const entry = findTool(name);
|
|
5854
6784
|
if (!entry) return unknownTool(name, io, opts.bin);
|
|
6785
|
+
if (entry.hostInstallUser === "root") {
|
|
6786
|
+
io.stderr(
|
|
6787
|
+
`${opts.bin} tool: ${entry.id} cannot be removed by this command.
|
|
6788
|
+
${opts.bin} does not remove system packages that it installed as root.
|
|
6789
|
+
Remove ${entry.id} by hand: ${ROOT_TOOL_REMOVAL_DOCS}
|
|
6790
|
+
`
|
|
6791
|
+
);
|
|
6792
|
+
return 1;
|
|
6793
|
+
}
|
|
5855
6794
|
if (entry.uninstallArgv === null) {
|
|
5856
6795
|
io.stderr(`${opts.bin} tool: ${entry.id} cannot be removed by this command.
|
|
5857
6796
|
|
|
@@ -5864,7 +6803,20 @@ async function runToolUninstall(name, opts, io) {
|
|
|
5864
6803
|
if (!isReachable2(await resolveTargetStatus(target))) {
|
|
5865
6804
|
return await uninstallOnHost2(entry, opts, io, run);
|
|
5866
6805
|
}
|
|
5867
|
-
|
|
6806
|
+
const outcome = await removeTool(entry, target, SANDBOX_HARNESS_PREFIX, "sandbox", opts, io);
|
|
6807
|
+
if (outcome.dropReceipt) {
|
|
6808
|
+
const cleared = await target.exec({
|
|
6809
|
+
argv: ["rm", "-f", sandboxMarkerPath2(entry)],
|
|
6810
|
+
user: "sandbox",
|
|
6811
|
+
stdio: "capture"
|
|
6812
|
+
});
|
|
6813
|
+
if (cleared.exitCode !== 0) {
|
|
6814
|
+
io.stderr(`${opts.bin} tool: could not clear the install record (exit ${cleared.exitCode}).
|
|
6815
|
+
`);
|
|
6816
|
+
return 1;
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6819
|
+
return outcome.code;
|
|
5868
6820
|
}
|
|
5869
6821
|
|
|
5870
6822
|
// src/lib/remote.ts
|
|
@@ -5931,7 +6883,7 @@ function fetchRemoteSource(opts) {
|
|
|
5931
6883
|
}
|
|
5932
6884
|
|
|
5933
6885
|
// src/cli.ts
|
|
5934
|
-
var DEFAULT_SOURCE_CONTROL_DIR =
|
|
6886
|
+
var DEFAULT_SOURCE_CONTROL_DIR = resolve13(
|
|
5935
6887
|
dirname10(fileURLToPath(import.meta.url)),
|
|
5936
6888
|
"../.."
|
|
5937
6889
|
);
|
|
@@ -5944,7 +6896,7 @@ var INTEGRATIONS = {
|
|
|
5944
6896
|
)
|
|
5945
6897
|
}
|
|
5946
6898
|
};
|
|
5947
|
-
function
|
|
6899
|
+
function isHelpFlag2(arg) {
|
|
5948
6900
|
return arg === "--help" || arg === "-h" || arg === "help";
|
|
5949
6901
|
}
|
|
5950
6902
|
function isVersionFlag(arg) {
|
|
@@ -5961,7 +6913,7 @@ function printAgroHelp(product = AGRO_PRODUCT) {
|
|
|
5961
6913
|
process.stdout.write(`${bin} \u2014 ${title} (v${AGRO_VERSION})
|
|
5962
6914
|
|
|
5963
6915
|
Usage:
|
|
5964
|
-
${bin} sandbox <args...> Create and
|
|
6916
|
+
${bin} sandbox <args...> Create, list, and upgrade sandboxes (install|list|upgrade)
|
|
5965
6917
|
${bin} shell [name] Open a zsh shell in the running sandbox container
|
|
5966
6918
|
${bin} config <args...> Read and write ${stateNames(bin).configFile} (show|set), or run a wizard
|
|
5967
6919
|
${bin} secret <args...> Read and write the gitignored root .env (set|list)
|
|
@@ -6111,66 +7063,6 @@ Flags:
|
|
|
6111
7063
|
--force Override the up-to-date / downgrade gate.
|
|
6112
7064
|
`);
|
|
6113
7065
|
}
|
|
6114
|
-
function runtimeLines() {
|
|
6115
|
-
const width = Math.max(...RUNTIME_CATALOG.map((r) => r.id.length));
|
|
6116
|
-
return RUNTIME_CATALOG.map(
|
|
6117
|
-
(r) => ` ${r.id.padEnd(width)} ${r.provisionable ? "provisionable" : "planned"}`
|
|
6118
|
-
).join("\n");
|
|
6119
|
-
}
|
|
6120
|
-
function printSandboxHelp(bin = AGRO_PRODUCT.bin) {
|
|
6121
|
-
process.stdout.write(`${bin} sandbox \u2014 Create and list sandboxes
|
|
6122
|
-
|
|
6123
|
-
Usage:
|
|
6124
|
-
${bin} sandbox install <runtime> [--name <name>] [--checkout <dir>]
|
|
6125
|
-
[--home-mount <dir>] [--yes]
|
|
6126
|
-
[--version <X.Y.Z> | --image[=<ref>]]
|
|
6127
|
-
[--no-build] [--print-argv]
|
|
6128
|
-
${bin} sandbox list [--json]
|
|
6129
|
-
|
|
6130
|
-
\`install\` writes a sandbox entry under \${${stateNames(bin).envPrefix}HOME:-~/${stateNames(bin).userStateDir}}/sandboxes/<name>/,
|
|
6131
|
-
materialises the compose files and the compose wrapper into it, then starts the
|
|
6132
|
-
container. It needs no project checkout and runs from any directory. Without
|
|
6133
|
-
--checkout the sandbox runs the prebuilt image; with --checkout <dir> that
|
|
6134
|
-
checkout is bound at /home/sandbox/harness; the sandbox builds locally only
|
|
6135
|
-
when that directory holds .devcontainer/Dockerfile.
|
|
6136
|
-
|
|
6137
|
-
On a terminal without --yes it asks for the sandbox name, the timezone, the git
|
|
6138
|
-
identity, SSH (with its host port), the host Docker socket, and the host path
|
|
6139
|
-
for /home/sandbox. With --checkout it seeds those answers from that
|
|
6140
|
-
checkout's ${stateNames(bin).configFile}.
|
|
6141
|
-
|
|
6142
|
-
Flags:
|
|
6143
|
-
--name <name> Registry entry name (default: the lowest free ${DEFAULT_NAME_PREFIX}<n>)
|
|
6144
|
-
--checkout <dir> Bind this checkout into the sandbox and seed the defaults
|
|
6145
|
-
from its ${stateNames(bin).configFile}. The sandbox builds
|
|
6146
|
-
locally only when that directory holds
|
|
6147
|
-
.devcontainer/Dockerfile
|
|
6148
|
-
--repo <dir> Deprecated alias for --checkout
|
|
6149
|
-
--home-mount <dir>
|
|
6150
|
-
Bind this host directory at /home/sandbox instead of the
|
|
6151
|
-
Docker-managed volume. Relative paths resolve against the
|
|
6152
|
-
working directory. The directory is created when absent
|
|
6153
|
-
--yes Non-interactive: keep every default and ask nothing
|
|
6154
|
-
--version <X.Y.Z>
|
|
6155
|
-
Run the official release image ghcr.io/mifunedev/agro:<X.Y.Z>
|
|
6156
|
-
(implies --image). A leading v and a -<channel>.<n>
|
|
6157
|
-
pre-release suffix are accepted
|
|
6158
|
-
--image Run the prebuilt image instead of building (implies
|
|
6159
|
-
--no-build). The ref resolves first-match: --version or
|
|
6160
|
-
--image=<ref> > ${stateNames(bin).envPrefix}SANDBOX_IMAGE >
|
|
6161
|
-
${stateNames(bin).configFile} image.ref > ${officialImageRef(AGRO_VERSION)}
|
|
6162
|
-
--image=<ref> Run a custom image (implies --image); conflicts with --version
|
|
6163
|
-
--no-build Suppress the local build and reuse an existing image
|
|
6164
|
-
--print-argv Print the docker compose argv that would run, then exit
|
|
6165
|
-
without writing an entry
|
|
6166
|
-
--json Machine-readable output (list)
|
|
6167
|
-
|
|
6168
|
-
Runtimes:
|
|
6169
|
-
${runtimeLines()}
|
|
6170
|
-
|
|
6171
|
-
Next: ${bin} shell <name>
|
|
6172
|
-
`);
|
|
6173
|
-
}
|
|
6174
7066
|
function printShellHelp(bin = AGRO_PRODUCT.bin) {
|
|
6175
7067
|
process.stdout.write(`${bin} shell \u2014 Open a shell in the running sandbox container
|
|
6176
7068
|
|
|
@@ -6295,6 +7187,8 @@ no workspace: run \`${bin} workspace create\` first. It installs into
|
|
|
6295
7187
|
\`~/.local\` and records the install as \`hostTools\` in that same file. On the host,
|
|
6296
7188
|
\`uninstall\` removes only what \`install\` recorded, from the prefix in that record.
|
|
6297
7189
|
Without a record it refuses, and \`--force\` overrides.
|
|
7190
|
+
\`uninstall\` refuses each root-level tool, marked \`(root)\` in \`${bin} tool list\`,
|
|
7191
|
+
with and without \`--force\`. The refusal links the manual removal steps.
|
|
6298
7192
|
|
|
6299
7193
|
Flags:
|
|
6300
7194
|
--yes Accept a large download without prompting
|
|
@@ -6384,7 +7278,7 @@ function extractSandboxFlag(command, rest) {
|
|
|
6384
7278
|
}
|
|
6385
7279
|
var LANGFUSE_VERBS = ["apply", "status", "disable"];
|
|
6386
7280
|
function parseLangfuseArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6387
|
-
if (rest.length === 0 ||
|
|
7281
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) return { ok: true, args: { help: true } };
|
|
6388
7282
|
const [head, ...tail] = rest;
|
|
6389
7283
|
if (!LANGFUSE_VERBS.includes(head)) {
|
|
6390
7284
|
return {
|
|
@@ -6393,7 +7287,7 @@ function parseLangfuseArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6393
7287
|
showHelp: true
|
|
6394
7288
|
};
|
|
6395
7289
|
}
|
|
6396
|
-
if (
|
|
7290
|
+
if (isHelpFlag2(tail[0])) return { ok: true, args: { help: true } };
|
|
6397
7291
|
if (tail.length > 0) {
|
|
6398
7292
|
return { ok: false, error: `${bin} langfuse ${head}: unexpected argument "${tail[0]}"` };
|
|
6399
7293
|
}
|
|
@@ -6411,12 +7305,12 @@ function parseConfigArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6411
7305
|
...scoped.args.sandbox === void 0 ? {} : { sandbox: scoped.args.sandbox },
|
|
6412
7306
|
...forced ? { force: true } : {}
|
|
6413
7307
|
};
|
|
6414
|
-
if (rest.length === 0 ||
|
|
7308
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6415
7309
|
return { ok: true, args: { ...args, help: true } };
|
|
6416
7310
|
}
|
|
6417
7311
|
const [head, ...tail] = rest;
|
|
6418
7312
|
if (!CONFIG_VERBS.includes(head)) {
|
|
6419
|
-
if (tail.length > 0 &&
|
|
7313
|
+
if (tail.length > 0 && isHelpFlag2(tail[0])) {
|
|
6420
7314
|
return { ok: true, args: { ...args, integration: head, integrationHelp: true } };
|
|
6421
7315
|
}
|
|
6422
7316
|
if (tail.length > 0) {
|
|
@@ -6428,7 +7322,7 @@ function parseConfigArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6428
7322
|
return { ok: true, args: { ...args, integration: head } };
|
|
6429
7323
|
}
|
|
6430
7324
|
const verb = head;
|
|
6431
|
-
if (
|
|
7325
|
+
if (isHelpFlag2(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
6432
7326
|
if (verb === "show" || verb === "repo") {
|
|
6433
7327
|
if (tail.length > 0) {
|
|
6434
7328
|
return { ok: false, error: `${bin} config ${verb}: unexpected argument "${tail[0]}"` };
|
|
@@ -6460,7 +7354,7 @@ function parseSecretArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6460
7354
|
help: false,
|
|
6461
7355
|
...scoped.args.sandbox === void 0 ? {} : { sandbox: scoped.args.sandbox }
|
|
6462
7356
|
};
|
|
6463
|
-
if (rest.length === 0 ||
|
|
7357
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6464
7358
|
return { ok: true, args: { ...args, help: true } };
|
|
6465
7359
|
}
|
|
6466
7360
|
const [head, ...tail] = rest;
|
|
@@ -6472,7 +7366,7 @@ function parseSecretArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6472
7366
|
};
|
|
6473
7367
|
}
|
|
6474
7368
|
const verb = head;
|
|
6475
|
-
if (
|
|
7369
|
+
if (isHelpFlag2(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
6476
7370
|
if (verb === "list") {
|
|
6477
7371
|
if (tail.length > 0) {
|
|
6478
7372
|
return { ok: false, error: `${bin} secret list: unexpected argument "${tail[0]}"` };
|
|
@@ -6493,7 +7387,7 @@ function parseSecretArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6493
7387
|
}
|
|
6494
7388
|
function parseDestroyArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6495
7389
|
const args = { help: false, yes: false };
|
|
6496
|
-
if (
|
|
7390
|
+
if (isHelpFlag2(rest[0])) return { ok: true, args: { ...args, help: true } };
|
|
6497
7391
|
for (const token of rest) {
|
|
6498
7392
|
if (token === "--yes") {
|
|
6499
7393
|
args.yes = true;
|
|
@@ -6512,7 +7406,7 @@ function parseDestroyArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6512
7406
|
}
|
|
6513
7407
|
function parseComposeArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6514
7408
|
const args = { help: false, passthrough: [] };
|
|
6515
|
-
if (rest.length === 0 ||
|
|
7409
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6516
7410
|
return { ok: true, args: { ...args, help: true } };
|
|
6517
7411
|
}
|
|
6518
7412
|
if (rest[0] !== "config") {
|
|
@@ -6524,7 +7418,7 @@ function parseComposeArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6524
7418
|
}
|
|
6525
7419
|
args.subcommand = "config";
|
|
6526
7420
|
const tail = rest.slice(1);
|
|
6527
|
-
if (
|
|
7421
|
+
if (isHelpFlag2(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
6528
7422
|
const sep4 = tail.indexOf("--");
|
|
6529
7423
|
if (sep4 === -1 && tail.length > 0) {
|
|
6530
7424
|
return {
|
|
@@ -6550,7 +7444,7 @@ function parseSelfUpgradeArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6550
7444
|
args.dryRun = true;
|
|
6551
7445
|
continue;
|
|
6552
7446
|
}
|
|
6553
|
-
if (
|
|
7447
|
+
if (isHelpFlag2(arg)) {
|
|
6554
7448
|
args.help = true;
|
|
6555
7449
|
return { ok: true, args };
|
|
6556
7450
|
}
|
|
@@ -6592,7 +7486,7 @@ function parseVendorArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6592
7486
|
args.force = true;
|
|
6593
7487
|
continue;
|
|
6594
7488
|
}
|
|
6595
|
-
if (
|
|
7489
|
+
if (isHelpFlag2(arg)) {
|
|
6596
7490
|
args.help = true;
|
|
6597
7491
|
return { ok: true, args };
|
|
6598
7492
|
}
|
|
@@ -6609,121 +7503,9 @@ function parseVendorArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6609
7503
|
}
|
|
6610
7504
|
return { ok: true, args };
|
|
6611
7505
|
}
|
|
6612
|
-
var SANDBOX_VALUE_FLAGS = {
|
|
6613
|
-
"--name": "name",
|
|
6614
|
-
"--checkout": "checkout",
|
|
6615
|
-
"--repo": "checkout",
|
|
6616
|
-
"--home-mount": "homeMount"
|
|
6617
|
-
};
|
|
6618
|
-
function parseSandboxArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6619
|
-
const args = {
|
|
6620
|
-
help: false,
|
|
6621
|
-
yes: false,
|
|
6622
|
-
image: false,
|
|
6623
|
-
noBuild: false,
|
|
6624
|
-
printArgv: false,
|
|
6625
|
-
json: false
|
|
6626
|
-
};
|
|
6627
|
-
if (rest.length === 0 || isHelpFlag(rest[0])) {
|
|
6628
|
-
return { ok: true, args: { ...args, help: true } };
|
|
6629
|
-
}
|
|
6630
|
-
const [head, ...tail] = rest;
|
|
6631
|
-
if (head !== "install" && head !== "list") {
|
|
6632
|
-
return {
|
|
6633
|
-
ok: false,
|
|
6634
|
-
error: `${bin} sandbox: unknown subcommand "${head}" \u2014 expected install or list`,
|
|
6635
|
-
showHelp: true
|
|
6636
|
-
};
|
|
6637
|
-
}
|
|
6638
|
-
args.subcommand = head;
|
|
6639
|
-
if (isHelpFlag(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
6640
|
-
const positionals = [];
|
|
6641
|
-
const checkoutSpellings = /* @__PURE__ */ new Set();
|
|
6642
|
-
let version;
|
|
6643
|
-
for (let i = 0; i < tail.length; i++) {
|
|
6644
|
-
const token = tail[i];
|
|
6645
|
-
const valueFlag = SANDBOX_VALUE_FLAGS[token];
|
|
6646
|
-
if (valueFlag !== void 0) {
|
|
6647
|
-
const value = tail[i + 1];
|
|
6648
|
-
if (value === void 0) {
|
|
6649
|
-
return { ok: false, error: `${bin} sandbox ${head}: ${token} requires a value` };
|
|
6650
|
-
}
|
|
6651
|
-
if (valueFlag === "checkout") checkoutSpellings.add(token);
|
|
6652
|
-
args[valueFlag] = value;
|
|
6653
|
-
i++;
|
|
6654
|
-
} else if (token === "--yes") {
|
|
6655
|
-
args.yes = true;
|
|
6656
|
-
} else if (token === "--no-build") {
|
|
6657
|
-
args.noBuild = true;
|
|
6658
|
-
} else if (token === "--print-argv") {
|
|
6659
|
-
args.printArgv = true;
|
|
6660
|
-
} else if (token === "--image") {
|
|
6661
|
-
args.image = true;
|
|
6662
|
-
} else if (token.startsWith("--image=")) {
|
|
6663
|
-
const ref = token.slice("--image=".length);
|
|
6664
|
-
if (ref === "") {
|
|
6665
|
-
return { ok: false, error: `${bin} sandbox: --image=<ref> requires a non-empty image ref` };
|
|
6666
|
-
}
|
|
6667
|
-
args.image = true;
|
|
6668
|
-
args.imageRef = ref;
|
|
6669
|
-
} else if (token === "--version" || token.startsWith("--version=")) {
|
|
6670
|
-
const value = token === "--version" ? tail[++i] : token.slice("--version=".length);
|
|
6671
|
-
if (value === void 0 || value === "") {
|
|
6672
|
-
return { ok: false, error: `${bin} sandbox ${head}: --version requires a value` };
|
|
6673
|
-
}
|
|
6674
|
-
version = parseReleaseVersion(value);
|
|
6675
|
-
if (version === void 0) {
|
|
6676
|
-
return {
|
|
6677
|
-
ok: false,
|
|
6678
|
-
error: `${bin} sandbox ${head}: --version "${value}" is not a release version \u2014 expected X.Y.Z`
|
|
6679
|
-
};
|
|
6680
|
-
}
|
|
6681
|
-
} else if (token === "--json") {
|
|
6682
|
-
args.json = true;
|
|
6683
|
-
} else if (token.startsWith("-")) {
|
|
6684
|
-
return { ok: false, error: `${bin} sandbox ${head}: unknown flag "${token}"` };
|
|
6685
|
-
} else {
|
|
6686
|
-
positionals.push(token);
|
|
6687
|
-
}
|
|
6688
|
-
}
|
|
6689
|
-
if (checkoutSpellings.has("--checkout") && checkoutSpellings.has("--repo")) {
|
|
6690
|
-
return {
|
|
6691
|
-
ok: false,
|
|
6692
|
-
error: `${bin} sandbox ${head}: --checkout conflicts with --repo \u2014 pass exactly one, and prefer --checkout`
|
|
6693
|
-
};
|
|
6694
|
-
}
|
|
6695
|
-
if (version !== void 0) {
|
|
6696
|
-
if (args.imageRef !== void 0) {
|
|
6697
|
-
return {
|
|
6698
|
-
ok: false,
|
|
6699
|
-
error: `${bin} sandbox ${head}: --version conflicts with --image=<ref> \u2014 pass --version for an official release or --image=<ref> for a custom image`
|
|
6700
|
-
};
|
|
6701
|
-
}
|
|
6702
|
-
args.image = true;
|
|
6703
|
-
args.imageRef = officialImageRef(version);
|
|
6704
|
-
}
|
|
6705
|
-
if (head === "list") {
|
|
6706
|
-
if (positionals.length > 0) {
|
|
6707
|
-
return { ok: false, error: `${bin} sandbox list: unexpected argument "${positionals[0]}"` };
|
|
6708
|
-
}
|
|
6709
|
-
return { ok: true, args };
|
|
6710
|
-
}
|
|
6711
|
-
if (positionals.length === 0) {
|
|
6712
|
-
return {
|
|
6713
|
-
ok: false,
|
|
6714
|
-
error: `${bin} sandbox install: a runtime is required, e.g. \`${bin} sandbox install docker\``,
|
|
6715
|
-
showHelp: true
|
|
6716
|
-
};
|
|
6717
|
-
}
|
|
6718
|
-
if (positionals.length > 1) {
|
|
6719
|
-
return { ok: false, error: `${bin} sandbox install: unexpected argument "${positionals[1]}"` };
|
|
6720
|
-
}
|
|
6721
|
-
args.runtime = positionals[0];
|
|
6722
|
-
return { ok: true, args };
|
|
6723
|
-
}
|
|
6724
7506
|
function parseShellArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6725
7507
|
const args = { help: false };
|
|
6726
|
-
if (
|
|
7508
|
+
if (isHelpFlag2(rest[0])) return { ok: true, args: { help: true } };
|
|
6727
7509
|
for (const token of rest) {
|
|
6728
7510
|
if (token.startsWith("-")) {
|
|
6729
7511
|
return { ok: false, error: `${bin} shell: unknown flag "${token}"` };
|
|
@@ -6737,7 +7519,7 @@ function parseShellArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6737
7519
|
}
|
|
6738
7520
|
function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6739
7521
|
const args = { help: false, json: false };
|
|
6740
|
-
if (rest.length === 0 ||
|
|
7522
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6741
7523
|
return { ok: true, args: { ...args, help: true } };
|
|
6742
7524
|
}
|
|
6743
7525
|
const positionals = [];
|
|
@@ -6809,7 +7591,7 @@ function parseHarnessArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6809
7591
|
host: false,
|
|
6810
7592
|
force: false
|
|
6811
7593
|
};
|
|
6812
|
-
if (rest.length === 0 ||
|
|
7594
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6813
7595
|
return { ok: true, args: { ...args, help: true } };
|
|
6814
7596
|
}
|
|
6815
7597
|
const positionals = [];
|
|
@@ -6890,7 +7672,7 @@ function parseHarnessArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6890
7672
|
}
|
|
6891
7673
|
function parseToolArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6892
7674
|
const args = { help: false, yes: false, json: false, host: false, force: false };
|
|
6893
|
-
if (rest.length === 0 ||
|
|
7675
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6894
7676
|
return { ok: true, args: { ...args, help: true } };
|
|
6895
7677
|
}
|
|
6896
7678
|
const positionals = [];
|
|
@@ -6948,19 +7730,19 @@ function parseGatewayArgs(rest) {
|
|
|
6948
7730
|
}
|
|
6949
7731
|
return { ok: true, args: { help: false, passthrough: [...rest] } };
|
|
6950
7732
|
}
|
|
6951
|
-
function bundledPayloadExists(bundled, exists =
|
|
6952
|
-
return exists(
|
|
7733
|
+
function bundledPayloadExists(bundled, exists = existsSync23) {
|
|
7734
|
+
return exists(join20(bundled.sourceControlDir, "manifest.json"));
|
|
6953
7735
|
}
|
|
6954
7736
|
function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
6955
|
-
const exists = bundled.exists ??
|
|
7737
|
+
const exists = bundled.exists ?? existsSync23;
|
|
6956
7738
|
if (args.fromDir !== void 0) {
|
|
6957
|
-
return { kind: "local", fromDir:
|
|
7739
|
+
return { kind: "local", fromDir: resolve13(args.fromDir) };
|
|
6958
7740
|
}
|
|
6959
7741
|
if (args.fromRemote) {
|
|
6960
7742
|
return { kind: "remote", ref: args.ref };
|
|
6961
7743
|
}
|
|
6962
7744
|
if (bundledPayloadExists(bundled, exists)) {
|
|
6963
|
-
return { kind: "local", fromDir:
|
|
7745
|
+
return { kind: "local", fromDir: resolve13(bundled.sourceControlDir, "..") };
|
|
6964
7746
|
}
|
|
6965
7747
|
return {
|
|
6966
7748
|
kind: "remote",
|
|
@@ -6972,7 +7754,7 @@ function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
|
6972
7754
|
function readPayloadVersion(checkoutDir) {
|
|
6973
7755
|
try {
|
|
6974
7756
|
const parsed = JSON.parse(
|
|
6975
|
-
|
|
7757
|
+
readFileSync13(join20(resolveControlDir(checkoutDir), "cli", "package.json"), "utf8")
|
|
6976
7758
|
);
|
|
6977
7759
|
if (parsed && typeof parsed.version === "string") return parsed.version;
|
|
6978
7760
|
} catch {
|
|
@@ -6981,7 +7763,7 @@ function readPayloadVersion(checkoutDir) {
|
|
|
6981
7763
|
}
|
|
6982
7764
|
async function runWithRemoteSource(hooks, fn) {
|
|
6983
7765
|
const fetch2 = hooks.fetch ?? fetchRemoteSource;
|
|
6984
|
-
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(
|
|
7766
|
+
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(join20(tmpdir4(), "oh-remote-")));
|
|
6985
7767
|
const rm = hooks.rm ?? ((dir) => rmSync6(dir, { recursive: true, force: true }));
|
|
6986
7768
|
const out = hooks.stdout ?? ((s) => process.stdout.write(s));
|
|
6987
7769
|
const checkoutDir = mkdtemp();
|
|
@@ -6998,7 +7780,7 @@ async function main(argv) {
|
|
|
6998
7780
|
const product = resolveProduct(process.argv[1]);
|
|
6999
7781
|
const bin = product.bin;
|
|
7000
7782
|
const [first, second] = argv;
|
|
7001
|
-
if (!first ||
|
|
7783
|
+
if (!first || isHelpFlag2(first)) {
|
|
7002
7784
|
printAgroHelp(product);
|
|
7003
7785
|
return 0;
|
|
7004
7786
|
}
|
|
@@ -7141,37 +7923,7 @@ async function main(argv) {
|
|
|
7141
7923
|
(checkoutDir) => runUpdate({ bin, targetDir, fromDir: checkoutDir, force, dryRun }, io)
|
|
7142
7924
|
);
|
|
7143
7925
|
}
|
|
7144
|
-
if (first === "sandbox")
|
|
7145
|
-
const parsed = parseSandboxArgs(argv.slice(1), bin);
|
|
7146
|
-
if (!parsed.ok) {
|
|
7147
|
-
process.stderr.write(`${parsed.error}
|
|
7148
|
-
`);
|
|
7149
|
-
if (parsed.showHelp) printSandboxHelp(bin);
|
|
7150
|
-
return 1;
|
|
7151
|
-
}
|
|
7152
|
-
const a = parsed.args;
|
|
7153
|
-
if (a.help) {
|
|
7154
|
-
printSandboxHelp(bin);
|
|
7155
|
-
return a.subcommand === void 0 ? 1 : 0;
|
|
7156
|
-
}
|
|
7157
|
-
const io = lifecycleIo();
|
|
7158
|
-
if (a.subcommand === "list") return await runSandboxList({ bin, json: a.json }, io);
|
|
7159
|
-
return await runSandboxInstall(
|
|
7160
|
-
{
|
|
7161
|
-
bin,
|
|
7162
|
-
runtime: a.runtime,
|
|
7163
|
-
...a.name !== void 0 ? { name: a.name } : {},
|
|
7164
|
-
...a.checkout !== void 0 ? { checkout: a.checkout } : {},
|
|
7165
|
-
...a.homeMount !== void 0 ? { homeMount: a.homeMount } : {},
|
|
7166
|
-
yes: a.yes,
|
|
7167
|
-
image: a.image,
|
|
7168
|
-
...a.imageRef !== void 0 ? { imageRef: a.imageRef } : {},
|
|
7169
|
-
noBuild: a.noBuild,
|
|
7170
|
-
printArgv: a.printArgv
|
|
7171
|
-
},
|
|
7172
|
-
io
|
|
7173
|
-
);
|
|
7174
|
-
}
|
|
7926
|
+
if (first === "sandbox") return runSandboxCommand(argv.slice(1), bin);
|
|
7175
7927
|
if (first === "shell") {
|
|
7176
7928
|
const parsed = parseShellArgs(argv.slice(1), bin);
|
|
7177
7929
|
if (!parsed.ok) {
|
|
@@ -7256,7 +8008,7 @@ async function main(argv) {
|
|
|
7256
8008
|
if (composeVerbs().includes(first)) {
|
|
7257
8009
|
const verb = first;
|
|
7258
8010
|
const rest = argv.slice(1);
|
|
7259
|
-
if (
|
|
8011
|
+
if (isHelpFlag2(rest[0])) {
|
|
7260
8012
|
printComposeVerbHelp(verb, bin);
|
|
7261
8013
|
return 0;
|
|
7262
8014
|
}
|
|
@@ -7371,7 +8123,19 @@ function lifecycleIo() {
|
|
|
7371
8123
|
stderr: (s) => process.stderr.write(s)
|
|
7372
8124
|
};
|
|
7373
8125
|
}
|
|
7374
|
-
|
|
8126
|
+
async function runCli(argv) {
|
|
8127
|
+
try {
|
|
8128
|
+
return await main(argv);
|
|
8129
|
+
} catch (err) {
|
|
8130
|
+
if (err instanceof RuntimeUnsupportedError) {
|
|
8131
|
+
process.stderr.write(`${err.message}
|
|
8132
|
+
`);
|
|
8133
|
+
return 1;
|
|
8134
|
+
}
|
|
8135
|
+
throw err;
|
|
8136
|
+
}
|
|
8137
|
+
}
|
|
8138
|
+
runCli(process.argv.slice(2)).then(
|
|
7375
8139
|
(code) => process.exit(code),
|
|
7376
8140
|
(err) => {
|
|
7377
8141
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -7386,7 +8150,7 @@ export {
|
|
|
7386
8150
|
SECRET_VERBS,
|
|
7387
8151
|
bundledPayloadExists,
|
|
7388
8152
|
extractSandboxFlag,
|
|
7389
|
-
isHelpFlag,
|
|
8153
|
+
isHelpFlag2 as isHelpFlag,
|
|
7390
8154
|
isVersionFlag,
|
|
7391
8155
|
parseComposeArgs,
|
|
7392
8156
|
parseConfigArgs,
|
|
@@ -7416,6 +8180,7 @@ export {
|
|
|
7416
8180
|
printVendorHelp,
|
|
7417
8181
|
printWorkspaceHelp,
|
|
7418
8182
|
resolveVendorSource,
|
|
8183
|
+
runCli,
|
|
7419
8184
|
runWithRemoteSource,
|
|
7420
8185
|
runtimeLines
|
|
7421
8186
|
};
|