@mifune/agro 0.16.0 → 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/dist/agro.js +623 -141
- package/package.json +1 -1
package/dist/agro.js
CHANGED
|
@@ -7,7 +7,7 @@ var __export = (target, all) => {
|
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { dirname as dirname10, join as
|
|
10
|
+
import { dirname as dirname10, join as join20, resolve as resolve13 } from "node:path";
|
|
11
11
|
import { existsSync as existsSync23, mkdtempSync as mkdtempSync5, readFileSync as readFileSync13, rmSync as rmSync6 } from "node:fs";
|
|
12
12
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
13
13
|
|
|
@@ -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.16.0" : "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);
|
|
@@ -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
|
`);
|
|
@@ -3787,7 +4189,7 @@ function maybePrintStarPrompt(io, env = process.env, isTTY = process.stdout.isTT
|
|
|
3787
4189
|
// src/commands/sandbox-list.ts
|
|
3788
4190
|
async function entryStatus(root, name, run) {
|
|
3789
4191
|
try {
|
|
3790
|
-
const target = resolveExecutionTarget({ projectRoot: root, container: name, run });
|
|
4192
|
+
const target = resolveExecutionTarget({ projectRoot: root, container: entrySandboxName(root) ?? name, run });
|
|
3791
4193
|
return await target.status();
|
|
3792
4194
|
} catch {
|
|
3793
4195
|
return "unknown";
|
|
@@ -3849,10 +4251,10 @@ function gitIdentity(run, key) {
|
|
|
3849
4251
|
return (result.stdout ?? "").trim();
|
|
3850
4252
|
}
|
|
3851
4253
|
function isBuildCapable(repo) {
|
|
3852
|
-
return repo !== void 0 && existsSync16(
|
|
4254
|
+
return repo !== void 0 && existsSync16(join13(repo, ".devcontainer", "Dockerfile"));
|
|
3853
4255
|
}
|
|
3854
4256
|
function prepareHomeMount(value) {
|
|
3855
|
-
const path4 =
|
|
4257
|
+
const path4 = resolve9(value);
|
|
3856
4258
|
if (existsSync16(path4)) {
|
|
3857
4259
|
if (!statSync4(path4).isDirectory()) {
|
|
3858
4260
|
throw new Error(`--home-mount path exists and is not a directory: ${path4}`);
|
|
@@ -3896,18 +4298,18 @@ function mergeSettings(...sources) {
|
|
|
3896
4298
|
for (const source of present) overlaySettings(merged, source);
|
|
3897
4299
|
return merged;
|
|
3898
4300
|
}
|
|
3899
|
-
function
|
|
4301
|
+
function nonEmpty2(value) {
|
|
3900
4302
|
return value === void 0 || value === "" ? void 0 : value;
|
|
3901
4303
|
}
|
|
3902
|
-
function seedConfig(name, checkout, seed, run) {
|
|
4304
|
+
function seedConfig(name, checkout, seed, run, runtime = "docker") {
|
|
3903
4305
|
const config = overlaySettings(defaultAgroConfig(name), seed);
|
|
3904
4306
|
config.name = name;
|
|
3905
|
-
config.runtime =
|
|
4307
|
+
config.runtime = runtime;
|
|
3906
4308
|
if (checkout !== void 0) config.checkout = checkout;
|
|
3907
|
-
config.timezone =
|
|
4309
|
+
config.timezone = nonEmpty2(seed?.timezone) ?? hostTimezone();
|
|
3908
4310
|
config.git = {
|
|
3909
|
-
userName:
|
|
3910
|
-
userEmail:
|
|
4311
|
+
userName: nonEmpty2(seed?.git?.userName) ?? gitIdentity(run, "user.name"),
|
|
4312
|
+
userEmail: nonEmpty2(seed?.git?.userEmail) ?? gitIdentity(run, "user.email")
|
|
3911
4313
|
};
|
|
3912
4314
|
config.image = {
|
|
3913
4315
|
...config.image,
|
|
@@ -3948,7 +4350,7 @@ async function runWizard(config, io) {
|
|
|
3948
4350
|
"Host path for /home/sandbox (blank keeps the Docker-managed volume)",
|
|
3949
4351
|
config.storage?.homePath ?? ""
|
|
3950
4352
|
);
|
|
3951
|
-
if (homePath !== "") config.storage = { ...config.storage, homePath:
|
|
4353
|
+
if (homePath !== "") config.storage = { ...config.storage, homePath: resolve9(homePath) };
|
|
3952
4354
|
}
|
|
3953
4355
|
async function runSandboxInstall(opts, io) {
|
|
3954
4356
|
const run = opts.run ?? spawnRunner;
|
|
@@ -3965,7 +4367,8 @@ async function runSandboxInstall(opts, io) {
|
|
|
3965
4367
|
`);
|
|
3966
4368
|
return 1;
|
|
3967
4369
|
}
|
|
3968
|
-
|
|
4370
|
+
if (runtime.id === "openshell") return await installOpenShell(opts, io, run);
|
|
4371
|
+
const checkout = opts.checkout === void 0 ? void 0 : resolve9(opts.checkout);
|
|
3969
4372
|
if (checkout !== void 0 && !existsSync16(checkout)) {
|
|
3970
4373
|
io.stderr(`${opts.bin} sandbox install: --checkout directory does not exist: ${checkout}
|
|
3971
4374
|
`);
|
|
@@ -3983,9 +4386,9 @@ async function runSandboxInstall(opts, io) {
|
|
|
3983
4386
|
const config = seedConfig(name, checkout, mergeSettings(readEntryConfig(name), repoSeed), run);
|
|
3984
4387
|
const buildRequested = config.image?.mode === "build" && opts.noBuild !== true && opts.image !== true && opts.imageRef === void 0;
|
|
3985
4388
|
if (buildRequested && !isBuildCapable(configCheckout(config))) {
|
|
3986
|
-
const target = configCheckout(config) ??
|
|
4389
|
+
const target = configCheckout(config) ?? resolve9(opts.checkout ?? ".");
|
|
3987
4390
|
io.stderr(
|
|
3988
|
-
`${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
|
|
3989
4392
|
`
|
|
3990
4393
|
);
|
|
3991
4394
|
return 1;
|
|
@@ -4029,7 +4432,7 @@ async function runSandboxInstall(opts, io) {
|
|
|
4029
4432
|
...useNoBuild ? { noBuild: true } : {}
|
|
4030
4433
|
};
|
|
4031
4434
|
if (opts.printArgv === true) {
|
|
4032
|
-
const preview = mkdtempSync3(
|
|
4435
|
+
const preview = mkdtempSync3(join13(tmpdir3(), "oh-sandbox-preview-"));
|
|
4033
4436
|
try {
|
|
4034
4437
|
writeAgroConfig(preview, config);
|
|
4035
4438
|
materialize(preview, { ...configCheckout(config) !== void 0 ? { checkout: configCheckout(config) } : {} });
|
|
@@ -4050,16 +4453,77 @@ async function runSandboxInstall(opts, io) {
|
|
|
4050
4453
|
}
|
|
4051
4454
|
return code;
|
|
4052
4455
|
}
|
|
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);
|
|
4466
|
+
try {
|
|
4467
|
+
assertSandboxName(name);
|
|
4468
|
+
} catch (error) {
|
|
4469
|
+
return fail2(error instanceof Error ? error.message : String(error));
|
|
4470
|
+
}
|
|
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"');
|
|
4477
|
+
}
|
|
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(" ")}
|
|
4487
|
+
`);
|
|
4488
|
+
return 0;
|
|
4489
|
+
}
|
|
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));
|
|
4496
|
+
}
|
|
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;
|
|
4511
|
+
}
|
|
4512
|
+
io.stdout(`next: ${opts.bin} shell ${name}
|
|
4513
|
+
`);
|
|
4514
|
+
maybePrintStarPrompt(io);
|
|
4515
|
+
return 0;
|
|
4516
|
+
}
|
|
4053
4517
|
|
|
4054
4518
|
// src/services/sandbox-upgrade.ts
|
|
4055
4519
|
import { closeSync as closeSync2, existsSync as existsSync17, openSync as openSync2, readFileSync as readFileSync11, unlinkSync as unlinkSync5, writeSync } from "node:fs";
|
|
4056
|
-
import { join as
|
|
4520
|
+
import { join as join14 } from "node:path";
|
|
4057
4521
|
function errorMessage(error) {
|
|
4058
4522
|
return error instanceof Error ? error.message : String(error);
|
|
4059
4523
|
}
|
|
4060
4524
|
function conflictingDotenvImage(root, imageRef) {
|
|
4061
|
-
const rootEnv =
|
|
4062
|
-
const fallbackEnv =
|
|
4525
|
+
const rootEnv = join14(root, ".env");
|
|
4526
|
+
const fallbackEnv = join14(root, ".devcontainer", ".env");
|
|
4063
4527
|
const file = existsSync17(rootEnv) ? rootEnv : fallbackEnv;
|
|
4064
4528
|
if (!existsSync17(file)) return void 0;
|
|
4065
4529
|
for (const line of readFileSync11(file, "utf8").split(/\r?\n/)) {
|
|
@@ -4097,7 +4561,8 @@ async function runSandboxUpgrade(opts, io) {
|
|
|
4097
4561
|
`);
|
|
4098
4562
|
return 1;
|
|
4099
4563
|
}
|
|
4100
|
-
|
|
4564
|
+
routeVerb(root, "upgrade", entrySandboxName(root) ?? opts.name);
|
|
4565
|
+
const lock = join14(root, ".sandbox-upgrade.lock");
|
|
4101
4566
|
let fd;
|
|
4102
4567
|
try {
|
|
4103
4568
|
fd = openSync2(lock, "wx", 384);
|
|
@@ -4212,6 +4677,10 @@ identity, SSH (with its host port), the host Docker socket, and the host path
|
|
|
4212
4677
|
for /home/sandbox. With --checkout it seeds those answers from that
|
|
4213
4678
|
checkout's ${stateNames(bin).configFile}.
|
|
4214
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
|
+
|
|
4215
4684
|
Flags:
|
|
4216
4685
|
--name <name> Registry entry name (default: the lowest free ${DEFAULT_NAME_PREFIX}<n>)
|
|
4217
4686
|
--checkout <dir> Bind this checkout into the sandbox and seed the defaults
|
|
@@ -4234,7 +4703,7 @@ Flags:
|
|
|
4234
4703
|
${stateNames(bin).configFile} image.ref > ${officialImageRef(AGRO_VERSION)}
|
|
4235
4704
|
--image=<ref> Run a custom image (implies --image); conflicts with --version
|
|
4236
4705
|
--no-build Suppress the local build and reuse an existing image
|
|
4237
|
-
--print-argv Print the docker compose argv that would run, then exit
|
|
4706
|
+
--print-argv Print the docker compose or openshell argv that would run, then exit
|
|
4238
4707
|
without writing an entry
|
|
4239
4708
|
--json Machine-readable output (list)
|
|
4240
4709
|
|
|
@@ -4427,7 +4896,7 @@ async function runSandboxCommand(rest, bin) {
|
|
|
4427
4896
|
// src/commands/harness.ts
|
|
4428
4897
|
import { existsSync as existsSync20 } from "node:fs";
|
|
4429
4898
|
import { homedir as homedir3 } from "node:os";
|
|
4430
|
-
import { delimiter as delimiter2, join as
|
|
4899
|
+
import { delimiter as delimiter2, join as join17 } from "node:path";
|
|
4431
4900
|
|
|
4432
4901
|
// src/lib/docs.ts
|
|
4433
4902
|
var SOURCE_DOCS_BASE = "https://github.com/mifunedev/agro/blob/main/";
|
|
@@ -4441,7 +4910,7 @@ function sourceDocsUrl(docsPath) {
|
|
|
4441
4910
|
|
|
4442
4911
|
// src/lib/host-config.ts
|
|
4443
4912
|
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync12, renameSync as renameSync4, unlinkSync as unlinkSync6, writeFileSync as writeFileSync9 } from "node:fs";
|
|
4444
|
-
import { basename as
|
|
4913
|
+
import { basename as basename7, dirname as dirname8, isAbsolute, join as join15, resolve as resolve10 } from "node:path";
|
|
4445
4914
|
var HOST_CONFIG_MODE = 420;
|
|
4446
4915
|
var HOST_CONFIG_FILE = "config.json";
|
|
4447
4916
|
var WORKSPACES_SUBDIR = "workspaces";
|
|
@@ -4453,7 +4922,7 @@ function hostStateHome(env, home) {
|
|
|
4453
4922
|
return resolveUserStateHome(env, home);
|
|
4454
4923
|
}
|
|
4455
4924
|
function hostConfigPath(env, home) {
|
|
4456
|
-
return
|
|
4925
|
+
return join15(hostStateHome(env, home), HOST_CONFIG_FILE);
|
|
4457
4926
|
}
|
|
4458
4927
|
function assertWorkspaceName(name) {
|
|
4459
4928
|
if (!SANDBOX_NAME_PATTERN.test(name)) {
|
|
@@ -4463,14 +4932,14 @@ function assertWorkspaceName(name) {
|
|
|
4463
4932
|
}
|
|
4464
4933
|
}
|
|
4465
4934
|
function workspacesRoot(env, home) {
|
|
4466
|
-
return
|
|
4935
|
+
return join15(hostStateHome(env, home), WORKSPACES_SUBDIR);
|
|
4467
4936
|
}
|
|
4468
4937
|
function workspaceRoot(name, env, home) {
|
|
4469
4938
|
assertWorkspaceName(name);
|
|
4470
|
-
return
|
|
4939
|
+
return join15(workspacesRoot(env, home), name);
|
|
4471
4940
|
}
|
|
4472
4941
|
function defaultHarnessRoot(env, home) {
|
|
4473
|
-
return
|
|
4942
|
+
return join15(workspacesRoot(env, home), DEFAULT_WORKSPACE_NAME);
|
|
4474
4943
|
}
|
|
4475
4944
|
function validateHostConfig(value) {
|
|
4476
4945
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -4547,7 +5016,7 @@ function readHostConfig(env, home) {
|
|
|
4547
5016
|
try {
|
|
4548
5017
|
parsed = JSON.parse(raw);
|
|
4549
5018
|
} catch {
|
|
4550
|
-
throw new Error(`${
|
|
5019
|
+
throw new Error(`${basename7(path4)} is not valid JSON: ${path4}`);
|
|
4551
5020
|
}
|
|
4552
5021
|
return validateHostConfig(parsed);
|
|
4553
5022
|
}
|
|
@@ -4570,15 +5039,15 @@ function writeHostConfig(config, env, home) {
|
|
|
4570
5039
|
}
|
|
4571
5040
|
}
|
|
4572
5041
|
function resolveHarnessRoot(explicit, env, home) {
|
|
4573
|
-
if (typeof explicit === "string" && explicit !== "") return
|
|
5042
|
+
if (typeof explicit === "string" && explicit !== "") return resolve10(explicit);
|
|
4574
5043
|
const configured = readHostConfig(env, home).harnessRoot;
|
|
4575
|
-
if (typeof configured === "string" && configured !== "") return
|
|
5044
|
+
if (typeof configured === "string" && configured !== "") return resolve10(configured);
|
|
4576
5045
|
return defaultHarnessRoot(env, home);
|
|
4577
5046
|
}
|
|
4578
5047
|
function recordHarnessRoot(root, env, home) {
|
|
4579
5048
|
const config = readHostConfig(env, home);
|
|
4580
5049
|
const recorded = config.harnessRoot;
|
|
4581
|
-
if (typeof recorded === "string" && recorded !== "" &&
|
|
5050
|
+
if (typeof recorded === "string" && recorded !== "" && resolve10(recorded) === root) return;
|
|
4582
5051
|
writeHostConfig({ ...config, harnessRoot: root }, env, home);
|
|
4583
5052
|
}
|
|
4584
5053
|
function fieldError2(path4, requirement) {
|
|
@@ -4587,7 +5056,7 @@ function fieldError2(path4, requirement) {
|
|
|
4587
5056
|
|
|
4588
5057
|
// src/lib/host-workspace.ts
|
|
4589
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";
|
|
4590
|
-
import { dirname as dirname9, join as
|
|
5059
|
+
import { dirname as dirname9, join as join16, resolve as resolve11 } from "node:path";
|
|
4591
5060
|
var AGRO_REPO_URL = "https://github.com/mifunedev/agro.git";
|
|
4592
5061
|
var IGNORABLE_ENTRIES = [
|
|
4593
5062
|
REGISTRY_SUBDIR,
|
|
@@ -4599,15 +5068,15 @@ function isDirectory3(path4) {
|
|
|
4599
5068
|
return statSync5(path4, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
4600
5069
|
}
|
|
4601
5070
|
function stateHomes(home) {
|
|
4602
|
-
return STATE_HOME_DIRS.map((name) =>
|
|
5071
|
+
return STATE_HOME_DIRS.map((name) => join16(home, name));
|
|
4603
5072
|
}
|
|
4604
5073
|
function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
4605
|
-
const target =
|
|
5074
|
+
const target = resolve11(root);
|
|
4606
5075
|
const enclosing = stateHomes(home).find((state) => target === state);
|
|
4607
5076
|
if (enclosing === void 0) return void 0;
|
|
4608
5077
|
return `${bin} ${verb}: the ${verb} root ${target} is the state home ${enclosing} itself.
|
|
4609
5078
|
A workspace there collides with AGRO state and blocks every command run from ${home}.
|
|
4610
|
-
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.
|
|
4611
5080
|
`;
|
|
4612
5081
|
}
|
|
4613
5082
|
function isIgnorableEntry(name) {
|
|
@@ -4624,24 +5093,24 @@ function cloneInto(target, run, root, ref) {
|
|
|
4624
5093
|
assertCloned(run("git", ["clone", ...branch, AGRO_REPO_URL, target], { stdio: "inherit" }), root);
|
|
4625
5094
|
}
|
|
4626
5095
|
function cloneThroughStaging(root, run, ref) {
|
|
4627
|
-
const staging = mkdtempSync4(
|
|
5096
|
+
const staging = mkdtempSync4(join16(dirname9(root), "agro-clone-"));
|
|
4628
5097
|
try {
|
|
4629
|
-
const checkout =
|
|
5098
|
+
const checkout = join16(staging, "agro");
|
|
4630
5099
|
cloneInto(checkout, run, root, ref);
|
|
4631
5100
|
for (const name of readdirSync3(checkout)) {
|
|
4632
|
-
const destination =
|
|
5101
|
+
const destination = join16(root, name);
|
|
4633
5102
|
if (existsSync19(destination)) {
|
|
4634
5103
|
throw new Error(`could not move ${name} into ${root} \u2014 the path already exists`);
|
|
4635
5104
|
}
|
|
4636
|
-
renameSync5(
|
|
5105
|
+
renameSync5(join16(checkout, name), destination);
|
|
4637
5106
|
}
|
|
4638
5107
|
} finally {
|
|
4639
5108
|
rmSync4(staging, { recursive: true, force: true });
|
|
4640
5109
|
}
|
|
4641
5110
|
}
|
|
4642
5111
|
function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
4643
|
-
const root =
|
|
4644
|
-
if (existsSync19(
|
|
5112
|
+
const root = resolve11(path4);
|
|
5113
|
+
if (existsSync19(join16(root, ".git"))) return { root, action: "reused" };
|
|
4645
5114
|
const stats = statSync5(root, { throwIfNoEntry: false });
|
|
4646
5115
|
if (stats === void 0) {
|
|
4647
5116
|
mkdirSync10(dirname9(root), { recursive: true });
|
|
@@ -4663,11 +5132,11 @@ function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
|
4663
5132
|
function listHostWorkspaces(env, home) {
|
|
4664
5133
|
const root = workspacesRoot(env, home);
|
|
4665
5134
|
if (!isDirectory3(root)) return [];
|
|
4666
|
-
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => SANDBOX_NAME_PATTERN.test(name)).filter((name) => existsSync19(
|
|
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();
|
|
4667
5136
|
}
|
|
4668
5137
|
function resolveExistingWorkspace(bin, verb, path4, env, home) {
|
|
4669
|
-
const root =
|
|
4670
|
-
if (existsSync19(
|
|
5138
|
+
const root = resolve11(path4);
|
|
5139
|
+
if (existsSync19(join16(root, ".git"))) return { ok: true, root };
|
|
4671
5140
|
const existing = listHostWorkspaces(env, home);
|
|
4672
5141
|
const inventory = existing.length === 0 ? "No host workspace exists yet.\n" : `Workspaces that exist: ${existing.join(", ")}
|
|
4673
5142
|
`;
|
|
@@ -4697,7 +5166,7 @@ function targetFor(root, run, env) {
|
|
|
4697
5166
|
});
|
|
4698
5167
|
}
|
|
4699
5168
|
function hostPrefix(home) {
|
|
4700
|
-
return
|
|
5169
|
+
return join17(home, ".local");
|
|
4701
5170
|
}
|
|
4702
5171
|
function pathEntries(env) {
|
|
4703
5172
|
return (env.PATH ?? "").split(delimiter2).filter((part) => part !== "");
|
|
@@ -4744,7 +5213,7 @@ async function sandboxMarkerExists(target, entry) {
|
|
|
4744
5213
|
return r.exitCode === 0;
|
|
4745
5214
|
}
|
|
4746
5215
|
function hostBinaryUnderPrefix(entry, prefix) {
|
|
4747
|
-
return existsSync20(
|
|
5216
|
+
return existsSync20(join17(harnessBinPath(prefix), entry.binary));
|
|
4748
5217
|
}
|
|
4749
5218
|
function stateOf(entry, installed, location) {
|
|
4750
5219
|
return {
|
|
@@ -4765,7 +5234,7 @@ function probeableHost(env, home) {
|
|
|
4765
5234
|
return void 0;
|
|
4766
5235
|
}
|
|
4767
5236
|
const prefix = hostPrefix(home);
|
|
4768
|
-
if (!existsSync20(
|
|
5237
|
+
if (!existsSync20(join17(root, ".git")) && !existsSync20(prefix)) return void 0;
|
|
4769
5238
|
return { root, prefix };
|
|
4770
5239
|
}
|
|
4771
5240
|
async function collectStates(root, run, home, env, only) {
|
|
@@ -5232,7 +5701,7 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
5232
5701
|
// src/commands/workspace.ts
|
|
5233
5702
|
import { existsSync as existsSync21, rmSync as rmSync5 } from "node:fs";
|
|
5234
5703
|
import { homedir as homedir4 } from "node:os";
|
|
5235
|
-
import { join as
|
|
5704
|
+
import { join as join18, resolve as resolve12 } from "node:path";
|
|
5236
5705
|
function homeOf2(opts) {
|
|
5237
5706
|
return (opts.homedir ?? homedir4)();
|
|
5238
5707
|
}
|
|
@@ -5241,7 +5710,7 @@ function messageOf2(error) {
|
|
|
5241
5710
|
}
|
|
5242
5711
|
function recordedRoot(env, home) {
|
|
5243
5712
|
const configured = readHostConfig(env, home).harnessRoot;
|
|
5244
|
-
return typeof configured === "string" && configured !== "" ?
|
|
5713
|
+
return typeof configured === "string" && configured !== "" ? resolve12(configured) : void 0;
|
|
5245
5714
|
}
|
|
5246
5715
|
function renderTable2(rows, io) {
|
|
5247
5716
|
const header2 = ["WORKSPACE", "DEFAULT", "PATH"];
|
|
@@ -5266,7 +5735,7 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
5266
5735
|
let root;
|
|
5267
5736
|
const chosen = name ?? DEFAULT_WORKSPACE_NAME;
|
|
5268
5737
|
try {
|
|
5269
|
-
root = opts.path !== void 0 ?
|
|
5738
|
+
root = opts.path !== void 0 ? resolve12(opts.path) : workspaceRoot(chosen, env, home);
|
|
5270
5739
|
} catch (err) {
|
|
5271
5740
|
io.stderr(`${bin} workspace: ${messageOf2(err)}
|
|
5272
5741
|
`);
|
|
@@ -5280,7 +5749,7 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
5280
5749
|
let action;
|
|
5281
5750
|
const preexisting = existsSync21(root);
|
|
5282
5751
|
try {
|
|
5283
|
-
if (!opts.json && !existsSync21(
|
|
5752
|
+
if (!opts.json && !existsSync21(join18(root, ".git"))) {
|
|
5284
5753
|
const at = opts.ref !== void 0 ? ` at ${opts.ref}` : "";
|
|
5285
5754
|
io.stdout(`cloning ${AGRO_REPO_URL}${at} into ${root}\u2026
|
|
5286
5755
|
`);
|
|
@@ -5325,7 +5794,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
5325
5794
|
const recorded = recordedRoot(env, home);
|
|
5326
5795
|
const registry = workspacesRoot(env, home);
|
|
5327
5796
|
rows = listHostWorkspaces(env, home).map((name) => {
|
|
5328
|
-
const root =
|
|
5797
|
+
const root = join18(registry, name);
|
|
5329
5798
|
return { name, root, default: recorded === root };
|
|
5330
5799
|
});
|
|
5331
5800
|
} catch (err) {
|
|
@@ -5352,7 +5821,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
5352
5821
|
// src/commands/tool.ts
|
|
5353
5822
|
import { existsSync as existsSync22 } from "node:fs";
|
|
5354
5823
|
import { homedir as homedir5, userInfo as userInfo2 } from "node:os";
|
|
5355
|
-
import { delimiter as delimiter3, join as
|
|
5824
|
+
import { delimiter as delimiter3, join as join19 } from "node:path";
|
|
5356
5825
|
|
|
5357
5826
|
// src/lib/tools/catalog.ts
|
|
5358
5827
|
var TOOLS_DOC = "docs/installation.md";
|
|
@@ -5783,7 +6252,7 @@ function targetFor2(root, run, env) {
|
|
|
5783
6252
|
});
|
|
5784
6253
|
}
|
|
5785
6254
|
function hostPrefix2(home) {
|
|
5786
|
-
return
|
|
6255
|
+
return join19(home, ".local");
|
|
5787
6256
|
}
|
|
5788
6257
|
function sandboxMarkerPath2(entry) {
|
|
5789
6258
|
return `${SANDBOX_HARNESS_PREFIX}/share/agro/tools/${entry.id}.installed`;
|
|
@@ -5797,7 +6266,7 @@ async function sandboxMarkerExists2(target, entry) {
|
|
|
5797
6266
|
return r.exitCode === 0;
|
|
5798
6267
|
}
|
|
5799
6268
|
function hostBinaryUnderPrefix2(entry, prefix) {
|
|
5800
|
-
return existsSync22(
|
|
6269
|
+
return existsSync22(join19(harnessBinPath(prefix), entry.binary));
|
|
5801
6270
|
}
|
|
5802
6271
|
function pathEntries2(env) {
|
|
5803
6272
|
return (env.PATH ?? "").split(delimiter3).filter((part) => part !== "");
|
|
@@ -5877,7 +6346,7 @@ function probeableHost2(env, home) {
|
|
|
5877
6346
|
return void 0;
|
|
5878
6347
|
}
|
|
5879
6348
|
const prefix = hostPrefix2(home);
|
|
5880
|
-
if (!existsSync22(
|
|
6349
|
+
if (!existsSync22(join19(root, ".git")) && !existsSync22(prefix)) return void 0;
|
|
5881
6350
|
return { root, prefix };
|
|
5882
6351
|
}
|
|
5883
6352
|
async function collectRows(root, run, home, env, only) {
|
|
@@ -6414,7 +6883,7 @@ function fetchRemoteSource(opts) {
|
|
|
6414
6883
|
}
|
|
6415
6884
|
|
|
6416
6885
|
// src/cli.ts
|
|
6417
|
-
var DEFAULT_SOURCE_CONTROL_DIR =
|
|
6886
|
+
var DEFAULT_SOURCE_CONTROL_DIR = resolve13(
|
|
6418
6887
|
dirname10(fileURLToPath(import.meta.url)),
|
|
6419
6888
|
"../.."
|
|
6420
6889
|
);
|
|
@@ -7262,18 +7731,18 @@ function parseGatewayArgs(rest) {
|
|
|
7262
7731
|
return { ok: true, args: { help: false, passthrough: [...rest] } };
|
|
7263
7732
|
}
|
|
7264
7733
|
function bundledPayloadExists(bundled, exists = existsSync23) {
|
|
7265
|
-
return exists(
|
|
7734
|
+
return exists(join20(bundled.sourceControlDir, "manifest.json"));
|
|
7266
7735
|
}
|
|
7267
7736
|
function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
7268
7737
|
const exists = bundled.exists ?? existsSync23;
|
|
7269
7738
|
if (args.fromDir !== void 0) {
|
|
7270
|
-
return { kind: "local", fromDir:
|
|
7739
|
+
return { kind: "local", fromDir: resolve13(args.fromDir) };
|
|
7271
7740
|
}
|
|
7272
7741
|
if (args.fromRemote) {
|
|
7273
7742
|
return { kind: "remote", ref: args.ref };
|
|
7274
7743
|
}
|
|
7275
7744
|
if (bundledPayloadExists(bundled, exists)) {
|
|
7276
|
-
return { kind: "local", fromDir:
|
|
7745
|
+
return { kind: "local", fromDir: resolve13(bundled.sourceControlDir, "..") };
|
|
7277
7746
|
}
|
|
7278
7747
|
return {
|
|
7279
7748
|
kind: "remote",
|
|
@@ -7285,7 +7754,7 @@ function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
|
7285
7754
|
function readPayloadVersion(checkoutDir) {
|
|
7286
7755
|
try {
|
|
7287
7756
|
const parsed = JSON.parse(
|
|
7288
|
-
readFileSync13(
|
|
7757
|
+
readFileSync13(join20(resolveControlDir(checkoutDir), "cli", "package.json"), "utf8")
|
|
7289
7758
|
);
|
|
7290
7759
|
if (parsed && typeof parsed.version === "string") return parsed.version;
|
|
7291
7760
|
} catch {
|
|
@@ -7294,7 +7763,7 @@ function readPayloadVersion(checkoutDir) {
|
|
|
7294
7763
|
}
|
|
7295
7764
|
async function runWithRemoteSource(hooks, fn) {
|
|
7296
7765
|
const fetch2 = hooks.fetch ?? fetchRemoteSource;
|
|
7297
|
-
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(
|
|
7766
|
+
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(join20(tmpdir4(), "oh-remote-")));
|
|
7298
7767
|
const rm = hooks.rm ?? ((dir) => rmSync6(dir, { recursive: true, force: true }));
|
|
7299
7768
|
const out = hooks.stdout ?? ((s) => process.stdout.write(s));
|
|
7300
7769
|
const checkoutDir = mkdtemp();
|
|
@@ -7654,7 +8123,19 @@ function lifecycleIo() {
|
|
|
7654
8123
|
stderr: (s) => process.stderr.write(s)
|
|
7655
8124
|
};
|
|
7656
8125
|
}
|
|
7657
|
-
|
|
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(
|
|
7658
8139
|
(code) => process.exit(code),
|
|
7659
8140
|
(err) => {
|
|
7660
8141
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -7699,6 +8180,7 @@ export {
|
|
|
7699
8180
|
printVendorHelp,
|
|
7700
8181
|
printWorkspaceHelp,
|
|
7701
8182
|
resolveVendorSource,
|
|
8183
|
+
runCli,
|
|
7702
8184
|
runWithRemoteSource,
|
|
7703
8185
|
runtimeLines
|
|
7704
8186
|
};
|
package/package.json
CHANGED