@mifune/agro 0.17.0-rc.1 → 0.17.0
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 +6 -6
- package/dist/agro.js +240 -658
- 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 join19, resolve as resolve11 } 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
|
|
|
@@ -531,7 +531,7 @@ function refuseUnsupported(installation, bin) {
|
|
|
531
531
|
return new Error(`${at} \u2014 rebuild from the checkout: npm --prefix .agro/cli run build`);
|
|
532
532
|
default:
|
|
533
533
|
return new Error(
|
|
534
|
-
`unknown installation (${reason ?? target}) \u2014 install with npm install -g ${PACKAGE} or
|
|
534
|
+
`unknown installation (${reason ?? target}) \u2014 install with npm install -g ${PACKAGE} or install.sh`
|
|
535
535
|
);
|
|
536
536
|
}
|
|
537
537
|
}
|
|
@@ -611,6 +611,15 @@ ${detail}`);
|
|
|
611
611
|
function copyFile(deps, from, to) {
|
|
612
612
|
deps.writeFile(to, deps.readFile(from), deps.stat(from).mode & 511);
|
|
613
613
|
}
|
|
614
|
+
var ABSOLUTE_NODE_SHEBANG = /^#!\/\S*\/node$/;
|
|
615
|
+
function withPinnedShebang(current, artifact) {
|
|
616
|
+
const currentEnd = current.indexOf(10);
|
|
617
|
+
const artifactEnd = artifact.indexOf(10);
|
|
618
|
+
if (currentEnd < 0 || artifactEnd < 0) return artifact;
|
|
619
|
+
const firstLine = current.subarray(0, currentEnd);
|
|
620
|
+
if (!ABSOLUTE_NODE_SHEBANG.test(firstLine.toString("utf8"))) return artifact;
|
|
621
|
+
return Buffer.concat([firstLine, artifact.subarray(artifactEnd)]);
|
|
622
|
+
}
|
|
614
623
|
function removeQuietly(deps, path4, io) {
|
|
615
624
|
try {
|
|
616
625
|
deps.unlink(path4);
|
|
@@ -629,7 +638,7 @@ async function upgradeStandalone(installation, opts, deps, io) {
|
|
|
629
638
|
deps.access(dir, constants.W_OK);
|
|
630
639
|
} catch {
|
|
631
640
|
throw new Error(
|
|
632
|
-
`${dir} is not writable; re-run as the user who owns ${target}, or reinstall into a writable directory with
|
|
641
|
+
`${dir} is not writable; re-run as the user who owns ${target}, or reinstall into a writable directory with install.sh`
|
|
633
642
|
);
|
|
634
643
|
}
|
|
635
644
|
let artifact;
|
|
@@ -645,11 +654,11 @@ async function upgradeStandalone(installation, opts, deps, io) {
|
|
|
645
654
|
let prevPresent = false;
|
|
646
655
|
let replaced = false;
|
|
647
656
|
try {
|
|
648
|
-
deps.writeFile(tmp, artifact, 493);
|
|
649
|
-
tmpPresent = true;
|
|
650
657
|
if (artifact.subarray(0, 2).toString("utf8") !== "#!") {
|
|
651
658
|
throw new Error(`invalid artifact from ${url}: no #! shebang`);
|
|
652
659
|
}
|
|
660
|
+
deps.writeFile(tmp, withPinnedShebang(deps.readFile(target), artifact), 493);
|
|
661
|
+
tmpPresent = true;
|
|
653
662
|
const probe = deps.runNode(tmp, ["--version"]);
|
|
654
663
|
const available = versionOf(probe);
|
|
655
664
|
if (parseSemver(available) === void 0) {
|
|
@@ -835,49 +844,6 @@ import { resolve as resolve3, sep } from "node:path";
|
|
|
835
844
|
// src/lib/agro-config.ts
|
|
836
845
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
837
846
|
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
|
|
881
847
|
var AGRO_CONFIG_FILE = NAMES.configFile;
|
|
882
848
|
var AGRO_CONFIG_MODE = 420;
|
|
883
849
|
var RESERVED_HOME_PATHS = [
|
|
@@ -899,7 +865,7 @@ var RESERVED_HOME_PATHS = [
|
|
|
899
865
|
"/usr",
|
|
900
866
|
"/var"
|
|
901
867
|
];
|
|
902
|
-
var SANDBOX_RUNTIMES =
|
|
868
|
+
var SANDBOX_RUNTIMES = ["docker"];
|
|
903
869
|
function configCheckout(config) {
|
|
904
870
|
const value = config.checkout ?? config.repo;
|
|
905
871
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
@@ -928,13 +894,6 @@ function defaultAgroConfig(name) {
|
|
|
928
894
|
composeOverrides: []
|
|
929
895
|
};
|
|
930
896
|
}
|
|
931
|
-
function entryRuntime(root) {
|
|
932
|
-
try {
|
|
933
|
-
return readAgroConfig(agroConfigPath(root)).runtime ?? "docker";
|
|
934
|
-
} catch {
|
|
935
|
-
return "docker";
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
897
|
function readAgroConfig(path4) {
|
|
939
898
|
if (!existsSync4(path4)) return defaultAgroConfig(basename3(dirname2(resolve2(path4))));
|
|
940
899
|
let raw;
|
|
@@ -1324,8 +1283,8 @@ function redact(token) {
|
|
|
1324
1283
|
async function ask(question) {
|
|
1325
1284
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1326
1285
|
try {
|
|
1327
|
-
return await new Promise((
|
|
1328
|
-
rl.question(` ${question} `, (answer) =>
|
|
1286
|
+
return await new Promise((resolve12) => {
|
|
1287
|
+
rl.question(` ${question} `, (answer) => resolve12(answer.trim()));
|
|
1329
1288
|
});
|
|
1330
1289
|
} finally {
|
|
1331
1290
|
rl.close();
|
|
@@ -1337,7 +1296,7 @@ async function askSecret(question) {
|
|
|
1337
1296
|
process.stdin.setRawMode(true);
|
|
1338
1297
|
process.stdin.resume();
|
|
1339
1298
|
const bytes = [];
|
|
1340
|
-
return await new Promise((
|
|
1299
|
+
return await new Promise((resolve12) => {
|
|
1341
1300
|
const cleanup = () => {
|
|
1342
1301
|
try {
|
|
1343
1302
|
process.stdin.setRawMode(false);
|
|
@@ -1355,7 +1314,7 @@ async function askSecret(question) {
|
|
|
1355
1314
|
} else if (byte === 13 || byte === 10) {
|
|
1356
1315
|
cleanup();
|
|
1357
1316
|
process.stdout.write("\n");
|
|
1358
|
-
|
|
1317
|
+
resolve12(Buffer.from(bytes).toString("utf8").trim());
|
|
1359
1318
|
return;
|
|
1360
1319
|
} else if (byte === 127 || byte === 8) {
|
|
1361
1320
|
if (bytes.length > 0) {
|
|
@@ -1405,10 +1364,10 @@ async function askSecretPiped(question) {
|
|
|
1405
1364
|
};
|
|
1406
1365
|
process.on("SIGINT", onSigint);
|
|
1407
1366
|
try {
|
|
1408
|
-
return await new Promise((
|
|
1367
|
+
return await new Promise((resolve12) => {
|
|
1409
1368
|
rl.question(` ${question} `, (answer) => {
|
|
1410
1369
|
process.stdout.write("\n");
|
|
1411
|
-
|
|
1370
|
+
resolve12(answer.trim());
|
|
1412
1371
|
});
|
|
1413
1372
|
});
|
|
1414
1373
|
} finally {
|
|
@@ -1453,10 +1412,10 @@ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync as read
|
|
|
1453
1412
|
import { dirname as dirname4, join as join4, resolve as resolve5, sep as sep2 } from "node:path";
|
|
1454
1413
|
|
|
1455
1414
|
// agro-asset:/home/runner/work/agro/agro/.devcontainer/docker-compose.yml
|
|
1456
|
-
var docker_compose_default = 'name: ${SANDBOX_NAME:-agro}\n\nservices:\n sandbox:\n image: ${AGRO_SANDBOX_IMAGE:-sandbox-${SANDBOX_NAME:-agro}}\n pull_policy: ${AGRO_PULL_POLICY:-missing}\n container_name: ${SANDBOX_NAME:-agro}\n build:\n context: ${AGRO_REPO_DIR:-..}\n dockerfile: .devcontainer/Dockerfile\n volumes:\n - ${AGRO_HOME_MOUNT:-workspace}:/home/sandbox\n - ${AGRO_REPO_DIR:-..}:/home/sandbox/harness\n extra_hosts:\n - "host.docker.internal:host-gateway"\n environment:\n - SANDBOX_NAME=${SANDBOX_NAME:-agro}\n - SANDBOX_PASSWORD=${SANDBOX_PASSWORD:-test1234}\n - TZ=${TZ:-America/Los_Angeles}\n - CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS=true\n - CC_SAFETY_NET_STRICT=1\n - CC_SAFETY_NET_WORKTREE=1\n - GIT_USER_NAME=${GIT_USER_NAME:-}\n - GIT_USER_EMAIL=${GIT_USER_EMAIL:-}\n - GH_TOKEN=${GH_TOKEN:-}\n stdin_open: true\n tty: true\n cgroup: private\n cap_add:\n - SYS_ADMIN\n security_opt:\n - apparmor=unconfined\n tmpfs:\n - /run\n - /run/lock\n - /sys/fs\n healthcheck:\n test: ["CMD", "bash", "-c", "exec bash /home/sandbox/harness/.agro/scripts/sandbox-healthcheck.sh"]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 600s\n restart: unless-stopped\n\nvolumes:\n workspace:\n';
|
|
1415
|
+
var docker_compose_default = 'name: ${SANDBOX_NAME:-agro}\n\nservices:\n sandbox:\n image: ${AGRO_SANDBOX_IMAGE:-sandbox-${SANDBOX_NAME:-agro}}\n pull_policy: ${AGRO_PULL_POLICY:-missing}\n container_name: ${SANDBOX_NAME:-agro}\n build:\n context: ${AGRO_REPO_DIR:-..}\n dockerfile: .devcontainer/Dockerfile\n volumes:\n - ${AGRO_HOME_MOUNT:-workspace}:/home/sandbox\n - ${AGRO_REPO_DIR:-..}:/home/sandbox/harness\n extra_hosts:\n - "host.docker.internal:host-gateway"\n environment:\n - SANDBOX_NAME=${SANDBOX_NAME:-agro}\n - SANDBOX_PASSWORD=${SANDBOX_PASSWORD:-test1234}\n - TZ=${TZ:-America/Los_Angeles}\n - CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS=true\n - CC_SAFETY_NET_STRICT=1\n - CC_SAFETY_NET_WORKTREE=1\n - GIT_USER_NAME=${GIT_USER_NAME:-}\n - GIT_USER_EMAIL=${GIT_USER_EMAIL:-}\n - GH_TOKEN=${GH_TOKEN:-}\n - TYPESAFE_API_KEY=${TYPESAFE_API_KEY:-}\n stdin_open: true\n tty: true\n cgroup: private\n cap_add:\n - SYS_ADMIN\n security_opt:\n - apparmor=unconfined\n tmpfs:\n - /run\n - /run/lock\n - /sys/fs\n healthcheck:\n test: ["CMD", "bash", "-c", "exec bash /home/sandbox/harness/.agro/scripts/sandbox-healthcheck.sh"]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 600s\n restart: unless-stopped\n\nvolumes:\n workspace:\n';
|
|
1457
1416
|
|
|
1458
1417
|
// agro-asset:/home/runner/work/agro/agro/.devcontainer/docker-compose.image-only.yml
|
|
1459
|
-
var docker_compose_image_only_default = 'name: ${SANDBOX_NAME:-agro}\n\nservices:\n sandbox:\n image: ${AGRO_SANDBOX_IMAGE:-ghcr.io/mifunedev/agro:latest}\n pull_policy: ${AGRO_PULL_POLICY:-always}\n container_name: ${SANDBOX_NAME:-agro}\n volumes:\n - ${AGRO_HOME_MOUNT:-workspace}:/home/sandbox\n extra_hosts:\n - "host.docker.internal:host-gateway"\n environment:\n - SANDBOX_NAME=${SANDBOX_NAME:-agro}\n - SANDBOX_PASSWORD=${SANDBOX_PASSWORD:-test1234}\n - TZ=${TZ:-America/Los_Angeles}\n - CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS=true\n - CC_SAFETY_NET_STRICT=1\n - CC_SAFETY_NET_WORKTREE=1\n - GIT_USER_NAME=${GIT_USER_NAME:-}\n - GIT_USER_EMAIL=${GIT_USER_EMAIL:-}\n - GH_TOKEN=${GH_TOKEN:-}\n stdin_open: true\n tty: true\n cgroup: private\n cap_add:\n - SYS_ADMIN\n security_opt:\n - apparmor=unconfined\n tmpfs:\n - /run\n - /run/lock\n - /sys/fs\n healthcheck:\n test: ["CMD", "bash", "-c", "exec bash /home/sandbox/harness/.agro/scripts/sandbox-healthcheck.sh"]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 600s\n restart: unless-stopped\n\nvolumes:\n workspace:\n';
|
|
1418
|
+
var docker_compose_image_only_default = 'name: ${SANDBOX_NAME:-agro}\n\nservices:\n sandbox:\n image: ${AGRO_SANDBOX_IMAGE:-ghcr.io/mifunedev/agro:latest}\n pull_policy: ${AGRO_PULL_POLICY:-always}\n container_name: ${SANDBOX_NAME:-agro}\n volumes:\n - ${AGRO_HOME_MOUNT:-workspace}:/home/sandbox\n extra_hosts:\n - "host.docker.internal:host-gateway"\n environment:\n - SANDBOX_NAME=${SANDBOX_NAME:-agro}\n - SANDBOX_PASSWORD=${SANDBOX_PASSWORD:-test1234}\n - TZ=${TZ:-America/Los_Angeles}\n - CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS=true\n - CC_SAFETY_NET_STRICT=1\n - CC_SAFETY_NET_WORKTREE=1\n - GIT_USER_NAME=${GIT_USER_NAME:-}\n - GIT_USER_EMAIL=${GIT_USER_EMAIL:-}\n - GH_TOKEN=${GH_TOKEN:-}\n - TYPESAFE_API_KEY=${TYPESAFE_API_KEY:-}\n stdin_open: true\n tty: true\n cgroup: private\n cap_add:\n - SYS_ADMIN\n security_opt:\n - apparmor=unconfined\n tmpfs:\n - /run\n - /run/lock\n - /sys/fs\n healthcheck:\n test: ["CMD", "bash", "-c", "exec bash /home/sandbox/harness/.agro/scripts/sandbox-healthcheck.sh"]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 600s\n restart: unless-stopped\n\nvolumes:\n workspace:\n';
|
|
1460
1419
|
|
|
1461
1420
|
// agro-asset:/home/runner/work/agro/agro/.devcontainer/docker-compose.ssh.yml
|
|
1462
1421
|
var docker_compose_ssh_default = 'services:\n sandbox:\n ports:\n - "127.0.0.1:${SANDBOX_SSH_PORT:-2222}:22"\n';
|
|
@@ -1534,8 +1493,7 @@ if [ -f "$REPO_DIR/harness.yaml" ] && [ -f "$MIGRATOR" ]; then
|
|
|
1534
1493
|
sh "$MIGRATOR" "$REPO_DIR" >&2 || true
|
|
1535
1494
|
fi
|
|
1536
1495
|
|
|
1537
|
-
ENV_FILE="$REPO_DIR
|
|
1538
|
-
[ -f "$ENV_FILE" ] || ENV_FILE="$REPO_DIR/.devcontainer/.env"
|
|
1496
|
+
ENV_FILE="$(agro_env_file "$REPO_DIR")"
|
|
1539
1497
|
|
|
1540
1498
|
compose_path() {
|
|
1541
1499
|
case "$1" in
|
|
@@ -1682,6 +1640,14 @@ agro_config_file() {
|
|
|
1682
1640
|
printf '%s\\n' "$1/$AGRO_CONFIG_FILE"
|
|
1683
1641
|
}
|
|
1684
1642
|
|
|
1643
|
+
agro_env_file() {
|
|
1644
|
+
if [ -f "$1/.env" ]; then
|
|
1645
|
+
printf '%s\\n' "$1/.env"
|
|
1646
|
+
else
|
|
1647
|
+
printf '%s\\n' "$1/.devcontainer/.env"
|
|
1648
|
+
fi
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1685
1651
|
agro_env_value() {
|
|
1686
1652
|
local key="AGRO_$1"
|
|
1687
1653
|
printf '%s\\n' "\${!key:-}"
|
|
@@ -1786,9 +1752,6 @@ printf 'free\\n'
|
|
|
1786
1752
|
exit 0
|
|
1787
1753
|
`;
|
|
1788
1754
|
|
|
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
|
-
|
|
1792
1755
|
// src/lib/registry.ts
|
|
1793
1756
|
var SANDBOX_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
1794
1757
|
var DEFAULT_NAME_PREFIX = "agro-sbx-";
|
|
@@ -1845,12 +1808,10 @@ function nextDefaultName(run = spawnRunner) {
|
|
|
1845
1808
|
if (!taken.has(candidate)) return candidate;
|
|
1846
1809
|
}
|
|
1847
1810
|
}
|
|
1848
|
-
function
|
|
1849
|
-
|
|
1850
|
-
}
|
|
1851
|
-
function dockerFiles(entry, opts) {
|
|
1811
|
+
function materialize(root, opts = {}) {
|
|
1812
|
+
const entry = resolve5(root);
|
|
1852
1813
|
const scripts = join4(resolveProjectLayout(entry).controlDir, "scripts");
|
|
1853
|
-
|
|
1814
|
+
const files = [
|
|
1854
1815
|
{
|
|
1855
1816
|
dest: join4(entry, ".devcontainer", "docker-compose.yml"),
|
|
1856
1817
|
body: (opts.checkout ?? opts.repo) === void 0 ? docker_compose_image_only_default : docker_compose_default,
|
|
@@ -1862,10 +1823,6 @@ function dockerFiles(entry, opts) {
|
|
|
1862
1823
|
{ dest: join4(scripts, "paths.sh"), body: paths_default, mode: 420 },
|
|
1863
1824
|
{ dest: join4(scripts, "check-host-port.sh"), body: check_host_port_default, mode: 493 }
|
|
1864
1825
|
];
|
|
1865
|
-
}
|
|
1866
|
-
function materialize(root, opts = {}) {
|
|
1867
|
-
const entry = resolve5(root);
|
|
1868
|
-
const files = entryRuntime(entry) === "openshell" ? openshellFiles(entry) : dockerFiles(entry, opts);
|
|
1869
1826
|
for (const file of files) {
|
|
1870
1827
|
const dest = resolve5(file.dest);
|
|
1871
1828
|
if (!dest.startsWith(entry + sep2)) {
|
|
@@ -2024,32 +1981,29 @@ function stripQuotes2(s) {
|
|
|
2024
1981
|
// src/commands/lifecycle.ts
|
|
2025
1982
|
import { existsSync as existsSync10, mkdtempSync, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2026
1983
|
import { tmpdir } from "node:os";
|
|
2027
|
-
import { basename as
|
|
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
|
-
}
|
|
1984
|
+
import { basename as basename4, join as join5, sep as sep3 } from "node:path";
|
|
2043
1985
|
|
|
2044
1986
|
// src/lib/execution/detect.ts
|
|
2045
1987
|
import { existsSync as existsSync9 } from "node:fs";
|
|
2046
|
-
var SANDBOX_MARKER_FILE = "
|
|
2047
|
-
var
|
|
2048
|
-
function
|
|
1988
|
+
var SANDBOX_MARKER_FILE = "/etc/agro/sandbox";
|
|
1989
|
+
var DOCKERENV_FILE = "/.dockerenv";
|
|
1990
|
+
function detectSandbox(env = process.env, fileExists = existsSync9) {
|
|
2049
1991
|
const override = agroEnvValue(env, "EXECUTION_TARGET");
|
|
2050
|
-
if (override === "local") return true;
|
|
2051
|
-
if (override === "docker-compose") return false;
|
|
2052
|
-
|
|
1992
|
+
if (override === "local") return { inside: true, rule: "override" };
|
|
1993
|
+
if (override === "docker-compose") return { inside: false, rule: "override" };
|
|
1994
|
+
if (fileExists(SANDBOX_MARKER_FILE)) return { inside: true, rule: "marker" };
|
|
1995
|
+
if (fileExists(DOCKERENV_FILE) && (env.SANDBOX_NAME ?? "") !== "") {
|
|
1996
|
+
return { inside: true, rule: "fallback" };
|
|
1997
|
+
}
|
|
1998
|
+
return { inside: false, rule: "none" };
|
|
1999
|
+
}
|
|
2000
|
+
function runningInsideSandbox(env = process.env, fileExists = existsSync9) {
|
|
2001
|
+
return detectSandbox(env, fileExists).inside;
|
|
2002
|
+
}
|
|
2003
|
+
function sandboxFallbackWarning(env = process.env, fileExists = existsSync9) {
|
|
2004
|
+
if (detectSandbox(env, fileExists).rule !== "fallback") return void 0;
|
|
2005
|
+
return `agro: warning: ${SANDBOX_MARKER_FILE} is missing; detected the sandbox from ${DOCKERENV_FILE} and SANDBOX_NAME. Upgrade the sandbox image.
|
|
2006
|
+
`;
|
|
2053
2007
|
}
|
|
2054
2008
|
|
|
2055
2009
|
// src/lib/execution/docker-compose-target.ts
|
|
@@ -2270,243 +2224,6 @@ function defaultIdentity() {
|
|
|
2270
2224
|
return { name: info2.username, uid: info2.uid };
|
|
2271
2225
|
}
|
|
2272
2226
|
|
|
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
|
-
|
|
2510
2227
|
// src/lib/execution/status.ts
|
|
2511
2228
|
var RUNTIME_ABSENT_STATUS = "no container runtime";
|
|
2512
2229
|
async function resolveTargetStatus(target) {
|
|
@@ -2530,23 +2247,19 @@ function resolveExecutionTarget(opts) {
|
|
|
2530
2247
|
...opts.env ? { env: opts.env } : {}
|
|
2531
2248
|
});
|
|
2532
2249
|
}
|
|
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
|
-
}
|
|
2543
2250
|
return new DockerComposeExecutionTarget(opts);
|
|
2544
2251
|
}
|
|
2545
|
-
|
|
2546
|
-
|
|
2252
|
+
|
|
2253
|
+
// src/lib/version.ts
|
|
2254
|
+
var AGRO_VERSION = true ? "0.17.0" : "0.0.0-dev";
|
|
2255
|
+
var OFFICIAL_IMAGE = "ghcr.io/mifunedev/agro";
|
|
2256
|
+
var RELEASE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z][a-z0-9]*\.[0-9]+)?$/;
|
|
2257
|
+
function parseReleaseVersion(value) {
|
|
2258
|
+
const bare = value.startsWith("v") ? value.slice(1) : value;
|
|
2259
|
+
return RELEASE_VERSION.test(bare) ? bare : void 0;
|
|
2547
2260
|
}
|
|
2548
|
-
function
|
|
2549
|
-
return
|
|
2261
|
+
function officialImageRef(version) {
|
|
2262
|
+
return `${OFFICIAL_IMAGE}:${RELEASE_VERSION.test(version) ? version : "latest"}`;
|
|
2550
2263
|
}
|
|
2551
2264
|
|
|
2552
2265
|
// src/lib/config-render.ts
|
|
@@ -2612,103 +2325,13 @@ function renderComposeEnv(config) {
|
|
|
2612
2325
|
`;
|
|
2613
2326
|
}
|
|
2614
2327
|
|
|
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
|
-
|
|
2705
2328
|
// src/commands/lifecycle.ts
|
|
2706
2329
|
var COMPOSE_ENV_DIR_PREFIX = "oh-compose-env-";
|
|
2707
2330
|
function composeEnvDir() {
|
|
2708
|
-
return mkdtempSync(
|
|
2331
|
+
return mkdtempSync(join5(tmpdir(), COMPOSE_ENV_DIR_PREFIX));
|
|
2709
2332
|
}
|
|
2710
2333
|
function writeComposeEnv(root, dir) {
|
|
2711
|
-
const file =
|
|
2334
|
+
const file = join5(dir, "compose.env");
|
|
2712
2335
|
writeFileSync5(file, renderComposeEnv(readAgroConfig(agroConfigPath(root))), {
|
|
2713
2336
|
mode: 384,
|
|
2714
2337
|
encoding: "utf8"
|
|
@@ -2766,7 +2389,7 @@ async function maybePromptDockerSocket(root, io, bin) {
|
|
|
2766
2389
|
if (dockerSocketConfigured(root)) return;
|
|
2767
2390
|
const interactive = process.stdin.isTTY === true || io.ask !== void 0;
|
|
2768
2391
|
if (!interactive) return;
|
|
2769
|
-
const envDir =
|
|
2392
|
+
const envDir = join5(root, ".devcontainer");
|
|
2770
2393
|
if (!existsSync10(envDir)) return;
|
|
2771
2394
|
const askFn = io.ask ?? ask;
|
|
2772
2395
|
const answer = (await askFn(
|
|
@@ -2847,27 +2470,25 @@ async function runSandbox(opts, io) {
|
|
|
2847
2470
|
function configuredContainerName(root) {
|
|
2848
2471
|
return fromProcessEnv("SANDBOX_NAME") ?? configuredString(root, "name");
|
|
2849
2472
|
}
|
|
2850
|
-
function sandboxName(root) {
|
|
2851
|
-
return entrySandboxName(root) ?? configuredContainerName(root) ?? DEFAULT_CONTAINER_NAME;
|
|
2852
|
-
}
|
|
2853
2473
|
function runShell(opts, io) {
|
|
2854
2474
|
const run = opts.run ?? spawnRunner;
|
|
2855
2475
|
const root = sandboxRoot(opts);
|
|
2856
|
-
const name =
|
|
2857
|
-
const { runtime } = routeVerb(root, "shell", name);
|
|
2476
|
+
const name = configuredContainerName(root) ?? DEFAULT_CONTAINER_NAME;
|
|
2858
2477
|
const target = resolveExecutionTarget({ projectRoot: root, container: name, run });
|
|
2859
2478
|
let code;
|
|
2860
2479
|
try {
|
|
2861
2480
|
code = target.attach({ argv: ["zsh"], user: "sandbox" });
|
|
2862
2481
|
} catch (err) {
|
|
2863
2482
|
if (err instanceof ExecutionSpawnError && err.code === "ENOENT") {
|
|
2864
|
-
throw new Error(
|
|
2483
|
+
throw new Error(`docker is required for \`${opts.bin} shell\` but was not found on PATH`);
|
|
2865
2484
|
}
|
|
2866
2485
|
throw err;
|
|
2867
2486
|
}
|
|
2868
2487
|
if (code !== 0) {
|
|
2869
|
-
io.stderr(
|
|
2870
|
-
`
|
|
2488
|
+
io.stderr(
|
|
2489
|
+
`container \`${name}\` not running? start it with \`${opts.bin} sandbox install docker\`
|
|
2490
|
+
`
|
|
2491
|
+
);
|
|
2871
2492
|
}
|
|
2872
2493
|
return code;
|
|
2873
2494
|
}
|
|
@@ -2884,7 +2505,6 @@ function composeVerbs() {
|
|
|
2884
2505
|
function runComposeVerb(verb, opts, extra = []) {
|
|
2885
2506
|
const run = opts.run ?? spawnRunner;
|
|
2886
2507
|
const root = sandboxRoot(opts);
|
|
2887
|
-
routeVerb(root, verb, sandboxName(root));
|
|
2888
2508
|
const script = requireLifecycleScript(root, "docker-compose.sh");
|
|
2889
2509
|
return withComposeEnvFile(root, (extraArgs) => {
|
|
2890
2510
|
const r = run("bash", [script, ...extraArgs, ...COMPOSE_VERBS[verb], ...extra], {
|
|
@@ -2897,7 +2517,6 @@ function runComposeVerb(verb, opts, extra = []) {
|
|
|
2897
2517
|
function runComposeConfig(opts, extra = []) {
|
|
2898
2518
|
const run = opts.run ?? spawnRunner;
|
|
2899
2519
|
const root = sandboxRoot(opts);
|
|
2900
|
-
routeVerb(root, "config", sandboxName(root));
|
|
2901
2520
|
const script = requireLifecycleScript(root, "docker-compose.sh");
|
|
2902
2521
|
return withComposeEnvFile(root, (extraArgs) => {
|
|
2903
2522
|
const r = run("bash", [script, ...extraArgs, "config", ...extra], { stdio: "inherit" });
|
|
@@ -2908,7 +2527,7 @@ function runComposeConfig(opts, extra = []) {
|
|
|
2908
2527
|
function namedVolumes(root) {
|
|
2909
2528
|
let text;
|
|
2910
2529
|
try {
|
|
2911
|
-
text = readFileSync7(
|
|
2530
|
+
text = readFileSync7(join5(root, ".devcontainer", "docker-compose.yml"), "utf8");
|
|
2912
2531
|
} catch {
|
|
2913
2532
|
return [];
|
|
2914
2533
|
}
|
|
@@ -2924,23 +2543,11 @@ function namedVolumes(root) {
|
|
|
2924
2543
|
return names;
|
|
2925
2544
|
}
|
|
2926
2545
|
function destroyConfirmationPhrase(root) {
|
|
2927
|
-
return
|
|
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
|
-
}
|
|
2546
|
+
return configuredContainerName(root) ?? DEFAULT_CONTAINER_NAME;
|
|
2939
2547
|
}
|
|
2940
2548
|
async function runDestroy(opts, io) {
|
|
2941
2549
|
const root = sandboxRoot(opts);
|
|
2942
2550
|
const name = destroyConfirmationPhrase(root);
|
|
2943
|
-
const { via } = routeVerb(root, "destroy", name);
|
|
2944
2551
|
if (opts.yes !== true) {
|
|
2945
2552
|
const interactive = process.stdin.isTTY === true || io.ask !== void 0;
|
|
2946
2553
|
if (!interactive) {
|
|
@@ -2955,14 +2562,7 @@ async function runDestroy(opts, io) {
|
|
|
2955
2562
|
${bold(`${opts.bin} destroy \u2014 ${name}`)}
|
|
2956
2563
|
|
|
2957
2564
|
`);
|
|
2958
|
-
if (
|
|
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 !== "") {
|
|
2565
|
+
if (homePath !== void 0 && homePath !== "") {
|
|
2966
2566
|
io.stdout("`docker compose down -v` removes the containers.\n");
|
|
2967
2567
|
io.stdout(
|
|
2968
2568
|
`This sandbox keeps its home on a host bind at ${homePath}, which \`down -v\`
|
|
@@ -2993,7 +2593,7 @@ stay there. Delete that directory yourself if you also want them gone.
|
|
|
2993
2593
|
return 1;
|
|
2994
2594
|
}
|
|
2995
2595
|
}
|
|
2996
|
-
const code =
|
|
2596
|
+
const code = runComposeVerb("destroy", { ...opts, name: basename4(root) });
|
|
2997
2597
|
if (code === 0 && root.startsWith(registryRoot() + sep3)) {
|
|
2998
2598
|
rmSync(root, { recursive: true, force: true });
|
|
2999
2599
|
io.stdout(`removed the sandbox entry ${root}
|
|
@@ -3281,14 +2881,15 @@ function sameRemote(url, owner, name) {
|
|
|
3281
2881
|
}
|
|
3282
2882
|
|
|
3283
2883
|
// src/commands/secret.ts
|
|
2884
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
3284
2885
|
import { existsSync as existsSync12 } from "node:fs";
|
|
3285
|
-
import { join as
|
|
2886
|
+
import { join as join7 } from "node:path";
|
|
3286
2887
|
|
|
3287
2888
|
// src/lib/gitignore.ts
|
|
3288
2889
|
import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
3289
|
-
import { dirname as dirname5, join as
|
|
2890
|
+
import { dirname as dirname5, join as join6 } from "node:path";
|
|
3290
2891
|
function appendGitignoreLines(root, lines) {
|
|
3291
|
-
const target =
|
|
2892
|
+
const target = join6(root, ".gitignore");
|
|
3292
2893
|
const existing = existsSync11(target) ? readFileSync8(target, "utf8") : "";
|
|
3293
2894
|
const present = new Set(existing.split("\n").map((line) => line.trimEnd()));
|
|
3294
2895
|
const added = [];
|
|
@@ -3312,8 +2913,16 @@ function appendGitignoreLines(root, lines) {
|
|
|
3312
2913
|
function secretsRoot(opts) {
|
|
3313
2914
|
return opts.sandbox === void 0 ? resolveProjectRoot(opts.cwd) : resolveSandboxRoot({ name: opts.sandbox });
|
|
3314
2915
|
}
|
|
2916
|
+
function isGitIgnored(root, path4) {
|
|
2917
|
+
const result = spawnSync3("git", ["check-ignore", "-q", "--no-index", path4], {
|
|
2918
|
+
cwd: root,
|
|
2919
|
+
stdio: "ignore"
|
|
2920
|
+
});
|
|
2921
|
+
return result.status === 0;
|
|
2922
|
+
}
|
|
3315
2923
|
function ignoreSecretsFile(root) {
|
|
3316
|
-
if (!existsSync12(
|
|
2924
|
+
if (!existsSync12(join7(root, ".git"))) return;
|
|
2925
|
+
if (isGitIgnored(root, ".env")) return;
|
|
3317
2926
|
appendGitignoreLines(root, [".env"]);
|
|
3318
2927
|
}
|
|
3319
2928
|
function secretKeyList() {
|
|
@@ -3371,10 +2980,10 @@ async function runSecretList(opts, io) {
|
|
|
3371
2980
|
// src/commands/langfuse.ts
|
|
3372
2981
|
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";
|
|
3373
2982
|
import { homedir as homedir2, tmpdir as tmpdir2 } from "node:os";
|
|
3374
|
-
import { dirname as dirname7, join as
|
|
2983
|
+
import { dirname as dirname7, join as join10, relative } from "node:path";
|
|
3375
2984
|
|
|
3376
2985
|
// src/lib/tracing/harness-writers.ts
|
|
3377
|
-
import { join as
|
|
2986
|
+
import { join as join8 } from "node:path";
|
|
3378
2987
|
|
|
3379
2988
|
// src/lib/tracing/writer.ts
|
|
3380
2989
|
import { chmodSync, existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
|
|
@@ -3419,13 +3028,13 @@ function isPlainObject(value) {
|
|
|
3419
3028
|
// src/lib/tracing/harness-writers.ts
|
|
3420
3029
|
var CODEX_TRACING_TAGS = ["codex"];
|
|
3421
3030
|
function claudeCodeSettingsPath(home) {
|
|
3422
|
-
return
|
|
3031
|
+
return join8(home, ".claude", "settings.json");
|
|
3423
3032
|
}
|
|
3424
3033
|
function piTracingConfigPath(home) {
|
|
3425
|
-
return
|
|
3034
|
+
return join8(home, ".pi", "agent", "langfuse.json");
|
|
3426
3035
|
}
|
|
3427
3036
|
function codexTracingConfigPath(home) {
|
|
3428
|
-
return
|
|
3037
|
+
return join8(home, ".codex", "langfuse.json");
|
|
3429
3038
|
}
|
|
3430
3039
|
var claudeCodeTracingWriter = (home, settings) => {
|
|
3431
3040
|
const path4 = claudeCodeSettingsPath(home);
|
|
@@ -3635,7 +3244,7 @@ function harnessBinPath(prefix) {
|
|
|
3635
3244
|
}
|
|
3636
3245
|
|
|
3637
3246
|
// src/lib/tracing/providers/langfuse.ts
|
|
3638
|
-
import { join as
|
|
3247
|
+
import { join as join9 } from "node:path";
|
|
3639
3248
|
var LANGFUSE_FRAGMENT_KEYS = [
|
|
3640
3249
|
"LANGFUSE_PUBLIC_KEY",
|
|
3641
3250
|
"LANGFUSE_SECRET_KEY",
|
|
@@ -3643,7 +3252,7 @@ var LANGFUSE_FRAGMENT_KEYS = [
|
|
|
3643
3252
|
"LANGFUSE_TRACING_ENVIRONMENT"
|
|
3644
3253
|
];
|
|
3645
3254
|
function langfuseFragmentPath(home) {
|
|
3646
|
-
return
|
|
3255
|
+
return join9(home, ".config", "agro", "langfuse.env");
|
|
3647
3256
|
}
|
|
3648
3257
|
function loadLangfuseCredentials(root) {
|
|
3649
3258
|
const publicKey = readSecret(root, "LANGFUSE_PUBLIC_KEY");
|
|
@@ -3807,7 +3416,7 @@ function displayPath(home, path4) {
|
|
|
3807
3416
|
return rel.startsWith("..") ? path4 : `~/${rel}`;
|
|
3808
3417
|
}
|
|
3809
3418
|
function withTempHome(fn) {
|
|
3810
|
-
const dir = mkdtempSync2(
|
|
3419
|
+
const dir = mkdtempSync2(join10(tmpdir2(), "agro-langfuse-"));
|
|
3811
3420
|
try {
|
|
3812
3421
|
return fn(dir);
|
|
3813
3422
|
} finally {
|
|
@@ -3816,10 +3425,10 @@ function withTempHome(fn) {
|
|
|
3816
3425
|
}
|
|
3817
3426
|
function probeGeneratedFile(home, render) {
|
|
3818
3427
|
const rel = withTempHome((scratch) => relative(scratch, render(scratch).path));
|
|
3819
|
-
const path4 =
|
|
3428
|
+
const path4 = join10(home, rel);
|
|
3820
3429
|
if (!existsSync14(path4)) return { path: path4, state: "missing" };
|
|
3821
3430
|
const outcome = withTempHome((scratch) => {
|
|
3822
|
-
const copy =
|
|
3431
|
+
const copy = join10(scratch, rel);
|
|
3823
3432
|
mkdirSync6(dirname7(copy), { recursive: true });
|
|
3824
3433
|
copyFileSync2(path4, copy);
|
|
3825
3434
|
return render(scratch).outcome;
|
|
@@ -3882,7 +3491,7 @@ async function runLangfuseApply(opts, io) {
|
|
|
3882
3491
|
return renderAll(ctx, resolved, io, opts.bin, "apply");
|
|
3883
3492
|
}
|
|
3884
3493
|
function zshenvWarning(home) {
|
|
3885
|
-
const zshenv =
|
|
3494
|
+
const zshenv = join10(home, ".zshenv");
|
|
3886
3495
|
if (!existsSync14(zshenv)) return void 0;
|
|
3887
3496
|
if (readFileSync10(zshenv, "utf8").includes("langfuse.env")) return void 0;
|
|
3888
3497
|
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
|
|
@@ -4163,18 +3772,45 @@ async function runLangfuseSetup(opts, io) {
|
|
|
4163
3772
|
// src/commands/sandbox.ts
|
|
4164
3773
|
import { existsSync as existsSync16, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync3, statSync as statSync4 } from "node:fs";
|
|
4165
3774
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
4166
|
-
import { join as
|
|
3775
|
+
import { join as join12, resolve as resolve7 } from "node:path";
|
|
3776
|
+
|
|
3777
|
+
// src/lib/runtimes/catalog.ts
|
|
3778
|
+
var RUNTIME_CATALOG = Object.freeze([
|
|
3779
|
+
Object.freeze({
|
|
3780
|
+
id: "docker",
|
|
3781
|
+
title: "Docker container",
|
|
3782
|
+
tier: "container",
|
|
3783
|
+
state: "active",
|
|
3784
|
+
provisionable: true,
|
|
3785
|
+
docsPath: "docs/runtimes/overview.md"
|
|
3786
|
+
}),
|
|
3787
|
+
Object.freeze({
|
|
3788
|
+
id: "microsandbox",
|
|
3789
|
+
title: "MicroSandbox",
|
|
3790
|
+
tier: "microvm",
|
|
3791
|
+
state: "planned",
|
|
3792
|
+
provisionable: false,
|
|
3793
|
+
notProvisionableReason: (bin) => `microsandbox is not a provisionable runtime yet; see https://github.com/mifunedev/agro/issues/592. Inside a sandbox run \`${bin} tool install microsandbox\`.`,
|
|
3794
|
+
docsPath: "docs/runtimes/microsandbox.md"
|
|
3795
|
+
})
|
|
3796
|
+
]);
|
|
3797
|
+
function findRuntime(id) {
|
|
3798
|
+
return RUNTIME_CATALOG.find((r) => r.id === id);
|
|
3799
|
+
}
|
|
3800
|
+
function runtimeIds() {
|
|
3801
|
+
return RUNTIME_CATALOG.map((r) => r.id);
|
|
3802
|
+
}
|
|
4167
3803
|
|
|
4168
3804
|
// src/lib/star-prompt.ts
|
|
4169
3805
|
import { existsSync as existsSync15, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
4170
|
-
import { join as
|
|
3806
|
+
import { join as join11 } from "node:path";
|
|
4171
3807
|
var STAR_PROMPT_LINE = "\u2B50 If AGRO helps, star https://github.com/mifunedev/agro";
|
|
4172
3808
|
var MARKER = "star-prompt-shown";
|
|
4173
3809
|
function maybePrintStarPrompt(io, env = process.env, isTTY = process.stdout.isTTY === true, stateHome = resolveUserStateHome(env)) {
|
|
4174
3810
|
if (env.AGRO_NO_STAR_PROMPT === "1") return;
|
|
4175
3811
|
if (env.CI !== void 0 && env.CI !== "") return;
|
|
4176
3812
|
if (!isTTY) return;
|
|
4177
|
-
const marker =
|
|
3813
|
+
const marker = join11(stateHome, MARKER);
|
|
4178
3814
|
if (existsSync15(marker)) return;
|
|
4179
3815
|
io.stdout(`${STAR_PROMPT_LINE}
|
|
4180
3816
|
`);
|
|
@@ -4189,7 +3825,7 @@ function maybePrintStarPrompt(io, env = process.env, isTTY = process.stdout.isTT
|
|
|
4189
3825
|
// src/commands/sandbox-list.ts
|
|
4190
3826
|
async function entryStatus(root, name, run) {
|
|
4191
3827
|
try {
|
|
4192
|
-
const target = resolveExecutionTarget({ projectRoot: root, container:
|
|
3828
|
+
const target = resolveExecutionTarget({ projectRoot: root, container: name, run });
|
|
4193
3829
|
return await target.status();
|
|
4194
3830
|
} catch {
|
|
4195
3831
|
return "unknown";
|
|
@@ -4251,10 +3887,10 @@ function gitIdentity(run, key) {
|
|
|
4251
3887
|
return (result.stdout ?? "").trim();
|
|
4252
3888
|
}
|
|
4253
3889
|
function isBuildCapable(repo) {
|
|
4254
|
-
return repo !== void 0 && existsSync16(
|
|
3890
|
+
return repo !== void 0 && existsSync16(join12(repo, ".devcontainer", "Dockerfile"));
|
|
4255
3891
|
}
|
|
4256
3892
|
function prepareHomeMount(value) {
|
|
4257
|
-
const path4 =
|
|
3893
|
+
const path4 = resolve7(value);
|
|
4258
3894
|
if (existsSync16(path4)) {
|
|
4259
3895
|
if (!statSync4(path4).isDirectory()) {
|
|
4260
3896
|
throw new Error(`--home-mount path exists and is not a directory: ${path4}`);
|
|
@@ -4298,18 +3934,18 @@ function mergeSettings(...sources) {
|
|
|
4298
3934
|
for (const source of present) overlaySettings(merged, source);
|
|
4299
3935
|
return merged;
|
|
4300
3936
|
}
|
|
4301
|
-
function
|
|
3937
|
+
function nonEmpty(value) {
|
|
4302
3938
|
return value === void 0 || value === "" ? void 0 : value;
|
|
4303
3939
|
}
|
|
4304
|
-
function seedConfig(name, checkout, seed, run
|
|
3940
|
+
function seedConfig(name, checkout, seed, run) {
|
|
4305
3941
|
const config = overlaySettings(defaultAgroConfig(name), seed);
|
|
4306
3942
|
config.name = name;
|
|
4307
|
-
config.runtime =
|
|
3943
|
+
config.runtime = "docker";
|
|
4308
3944
|
if (checkout !== void 0) config.checkout = checkout;
|
|
4309
|
-
config.timezone =
|
|
3945
|
+
config.timezone = nonEmpty(seed?.timezone) ?? hostTimezone();
|
|
4310
3946
|
config.git = {
|
|
4311
|
-
userName:
|
|
4312
|
-
userEmail:
|
|
3947
|
+
userName: nonEmpty(seed?.git?.userName) ?? gitIdentity(run, "user.name"),
|
|
3948
|
+
userEmail: nonEmpty(seed?.git?.userEmail) ?? gitIdentity(run, "user.email")
|
|
4313
3949
|
};
|
|
4314
3950
|
config.image = {
|
|
4315
3951
|
...config.image,
|
|
@@ -4350,7 +3986,7 @@ async function runWizard(config, io) {
|
|
|
4350
3986
|
"Host path for /home/sandbox (blank keeps the Docker-managed volume)",
|
|
4351
3987
|
config.storage?.homePath ?? ""
|
|
4352
3988
|
);
|
|
4353
|
-
if (homePath !== "") config.storage = { ...config.storage, homePath:
|
|
3989
|
+
if (homePath !== "") config.storage = { ...config.storage, homePath: resolve7(homePath) };
|
|
4354
3990
|
}
|
|
4355
3991
|
async function runSandboxInstall(opts, io) {
|
|
4356
3992
|
const run = opts.run ?? spawnRunner;
|
|
@@ -4367,8 +4003,7 @@ async function runSandboxInstall(opts, io) {
|
|
|
4367
4003
|
`);
|
|
4368
4004
|
return 1;
|
|
4369
4005
|
}
|
|
4370
|
-
|
|
4371
|
-
const checkout = opts.checkout === void 0 ? void 0 : resolve9(opts.checkout);
|
|
4006
|
+
const checkout = opts.checkout === void 0 ? void 0 : resolve7(opts.checkout);
|
|
4372
4007
|
if (checkout !== void 0 && !existsSync16(checkout)) {
|
|
4373
4008
|
io.stderr(`${opts.bin} sandbox install: --checkout directory does not exist: ${checkout}
|
|
4374
4009
|
`);
|
|
@@ -4386,9 +4021,9 @@ async function runSandboxInstall(opts, io) {
|
|
|
4386
4021
|
const config = seedConfig(name, checkout, mergeSettings(readEntryConfig(name), repoSeed), run);
|
|
4387
4022
|
const buildRequested = config.image?.mode === "build" && opts.noBuild !== true && opts.image !== true && opts.imageRef === void 0;
|
|
4388
4023
|
if (buildRequested && !isBuildCapable(configCheckout(config))) {
|
|
4389
|
-
const target = configCheckout(config) ??
|
|
4024
|
+
const target = configCheckout(config) ?? resolve7(opts.checkout ?? ".");
|
|
4390
4025
|
io.stderr(
|
|
4391
|
-
`${opts.bin} sandbox install: image.mode is "build" but ${
|
|
4026
|
+
`${opts.bin} sandbox install: image.mode is "build" but ${join12(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
|
|
4392
4027
|
`
|
|
4393
4028
|
);
|
|
4394
4029
|
return 1;
|
|
@@ -4432,7 +4067,7 @@ async function runSandboxInstall(opts, io) {
|
|
|
4432
4067
|
...useNoBuild ? { noBuild: true } : {}
|
|
4433
4068
|
};
|
|
4434
4069
|
if (opts.printArgv === true) {
|
|
4435
|
-
const preview = mkdtempSync3(
|
|
4070
|
+
const preview = mkdtempSync3(join12(tmpdir3(), "oh-sandbox-preview-"));
|
|
4436
4071
|
try {
|
|
4437
4072
|
writeAgroConfig(preview, config);
|
|
4438
4073
|
materialize(preview, { ...configCheckout(config) !== void 0 ? { checkout: configCheckout(config) } : {} });
|
|
@@ -4453,77 +4088,16 @@ async function runSandboxInstall(opts, io) {
|
|
|
4453
4088
|
}
|
|
4454
4089
|
return code;
|
|
4455
4090
|
}
|
|
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
|
-
}
|
|
4517
4091
|
|
|
4518
4092
|
// src/services/sandbox-upgrade.ts
|
|
4519
4093
|
import { closeSync as closeSync2, existsSync as existsSync17, openSync as openSync2, readFileSync as readFileSync11, unlinkSync as unlinkSync5, writeSync } from "node:fs";
|
|
4520
|
-
import { join as
|
|
4094
|
+
import { join as join13 } from "node:path";
|
|
4521
4095
|
function errorMessage(error) {
|
|
4522
4096
|
return error instanceof Error ? error.message : String(error);
|
|
4523
4097
|
}
|
|
4524
4098
|
function conflictingDotenvImage(root, imageRef) {
|
|
4525
|
-
const rootEnv =
|
|
4526
|
-
const fallbackEnv =
|
|
4099
|
+
const rootEnv = join13(root, ".env");
|
|
4100
|
+
const fallbackEnv = join13(root, ".devcontainer", ".env");
|
|
4527
4101
|
const file = existsSync17(rootEnv) ? rootEnv : fallbackEnv;
|
|
4528
4102
|
if (!existsSync17(file)) return void 0;
|
|
4529
4103
|
for (const line of readFileSync11(file, "utf8").split(/\r?\n/)) {
|
|
@@ -4561,8 +4135,7 @@ async function runSandboxUpgrade(opts, io) {
|
|
|
4561
4135
|
`);
|
|
4562
4136
|
return 1;
|
|
4563
4137
|
}
|
|
4564
|
-
|
|
4565
|
-
const lock = join14(root, ".sandbox-upgrade.lock");
|
|
4138
|
+
const lock = join13(root, ".sandbox-upgrade.lock");
|
|
4566
4139
|
let fd;
|
|
4567
4140
|
try {
|
|
4568
4141
|
fd = openSync2(lock, "wx", 384);
|
|
@@ -4677,10 +4250,6 @@ identity, SSH (with its host port), the host Docker socket, and the host path
|
|
|
4677
4250
|
for /home/sandbox. With --checkout it seeds those answers from that
|
|
4678
4251
|
checkout's ${stateNames(bin).configFile}.
|
|
4679
4252
|
|
|
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
4253
|
Flags:
|
|
4685
4254
|
--name <name> Registry entry name (default: the lowest free ${DEFAULT_NAME_PREFIX}<n>)
|
|
4686
4255
|
--checkout <dir> Bind this checkout into the sandbox and seed the defaults
|
|
@@ -4703,7 +4272,7 @@ Flags:
|
|
|
4703
4272
|
${stateNames(bin).configFile} image.ref > ${officialImageRef(AGRO_VERSION)}
|
|
4704
4273
|
--image=<ref> Run a custom image (implies --image); conflicts with --version
|
|
4705
4274
|
--no-build Suppress the local build and reuse an existing image
|
|
4706
|
-
--print-argv Print the docker compose
|
|
4275
|
+
--print-argv Print the docker compose argv that would run, then exit
|
|
4707
4276
|
without writing an entry
|
|
4708
4277
|
--json Machine-readable output (list)
|
|
4709
4278
|
|
|
@@ -4896,7 +4465,7 @@ async function runSandboxCommand(rest, bin) {
|
|
|
4896
4465
|
// src/commands/harness.ts
|
|
4897
4466
|
import { existsSync as existsSync20 } from "node:fs";
|
|
4898
4467
|
import { homedir as homedir3 } from "node:os";
|
|
4899
|
-
import { delimiter as delimiter2, join as
|
|
4468
|
+
import { delimiter as delimiter2, join as join16 } from "node:path";
|
|
4900
4469
|
|
|
4901
4470
|
// src/lib/docs.ts
|
|
4902
4471
|
var SOURCE_DOCS_BASE = "https://github.com/mifunedev/agro/blob/main/";
|
|
@@ -4910,7 +4479,7 @@ function sourceDocsUrl(docsPath) {
|
|
|
4910
4479
|
|
|
4911
4480
|
// src/lib/host-config.ts
|
|
4912
4481
|
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
|
|
4482
|
+
import { basename as basename5, dirname as dirname8, isAbsolute, join as join14, resolve as resolve8 } from "node:path";
|
|
4914
4483
|
var HOST_CONFIG_MODE = 420;
|
|
4915
4484
|
var HOST_CONFIG_FILE = "config.json";
|
|
4916
4485
|
var WORKSPACES_SUBDIR = "workspaces";
|
|
@@ -4922,7 +4491,7 @@ function hostStateHome(env, home) {
|
|
|
4922
4491
|
return resolveUserStateHome(env, home);
|
|
4923
4492
|
}
|
|
4924
4493
|
function hostConfigPath(env, home) {
|
|
4925
|
-
return
|
|
4494
|
+
return join14(hostStateHome(env, home), HOST_CONFIG_FILE);
|
|
4926
4495
|
}
|
|
4927
4496
|
function assertWorkspaceName(name) {
|
|
4928
4497
|
if (!SANDBOX_NAME_PATTERN.test(name)) {
|
|
@@ -4932,14 +4501,14 @@ function assertWorkspaceName(name) {
|
|
|
4932
4501
|
}
|
|
4933
4502
|
}
|
|
4934
4503
|
function workspacesRoot(env, home) {
|
|
4935
|
-
return
|
|
4504
|
+
return join14(hostStateHome(env, home), WORKSPACES_SUBDIR);
|
|
4936
4505
|
}
|
|
4937
4506
|
function workspaceRoot(name, env, home) {
|
|
4938
4507
|
assertWorkspaceName(name);
|
|
4939
|
-
return
|
|
4508
|
+
return join14(workspacesRoot(env, home), name);
|
|
4940
4509
|
}
|
|
4941
4510
|
function defaultHarnessRoot(env, home) {
|
|
4942
|
-
return
|
|
4511
|
+
return join14(workspacesRoot(env, home), DEFAULT_WORKSPACE_NAME);
|
|
4943
4512
|
}
|
|
4944
4513
|
function validateHostConfig(value) {
|
|
4945
4514
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -5016,7 +4585,7 @@ function readHostConfig(env, home) {
|
|
|
5016
4585
|
try {
|
|
5017
4586
|
parsed = JSON.parse(raw);
|
|
5018
4587
|
} catch {
|
|
5019
|
-
throw new Error(`${
|
|
4588
|
+
throw new Error(`${basename5(path4)} is not valid JSON: ${path4}`);
|
|
5020
4589
|
}
|
|
5021
4590
|
return validateHostConfig(parsed);
|
|
5022
4591
|
}
|
|
@@ -5039,15 +4608,15 @@ function writeHostConfig(config, env, home) {
|
|
|
5039
4608
|
}
|
|
5040
4609
|
}
|
|
5041
4610
|
function resolveHarnessRoot(explicit, env, home) {
|
|
5042
|
-
if (typeof explicit === "string" && explicit !== "") return
|
|
4611
|
+
if (typeof explicit === "string" && explicit !== "") return resolve8(explicit);
|
|
5043
4612
|
const configured = readHostConfig(env, home).harnessRoot;
|
|
5044
|
-
if (typeof configured === "string" && configured !== "") return
|
|
4613
|
+
if (typeof configured === "string" && configured !== "") return resolve8(configured);
|
|
5045
4614
|
return defaultHarnessRoot(env, home);
|
|
5046
4615
|
}
|
|
5047
4616
|
function recordHarnessRoot(root, env, home) {
|
|
5048
4617
|
const config = readHostConfig(env, home);
|
|
5049
4618
|
const recorded = config.harnessRoot;
|
|
5050
|
-
if (typeof recorded === "string" && recorded !== "" &&
|
|
4619
|
+
if (typeof recorded === "string" && recorded !== "" && resolve8(recorded) === root) return;
|
|
5051
4620
|
writeHostConfig({ ...config, harnessRoot: root }, env, home);
|
|
5052
4621
|
}
|
|
5053
4622
|
function fieldError2(path4, requirement) {
|
|
@@ -5056,7 +4625,7 @@ function fieldError2(path4, requirement) {
|
|
|
5056
4625
|
|
|
5057
4626
|
// src/lib/host-workspace.ts
|
|
5058
4627
|
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
|
|
4628
|
+
import { dirname as dirname9, join as join15, resolve as resolve9 } from "node:path";
|
|
5060
4629
|
var AGRO_REPO_URL = "https://github.com/mifunedev/agro.git";
|
|
5061
4630
|
var IGNORABLE_ENTRIES = [
|
|
5062
4631
|
REGISTRY_SUBDIR,
|
|
@@ -5068,15 +4637,15 @@ function isDirectory3(path4) {
|
|
|
5068
4637
|
return statSync5(path4, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
5069
4638
|
}
|
|
5070
4639
|
function stateHomes(home) {
|
|
5071
|
-
return STATE_HOME_DIRS.map((name) =>
|
|
4640
|
+
return STATE_HOME_DIRS.map((name) => join15(home, name));
|
|
5072
4641
|
}
|
|
5073
4642
|
function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
5074
|
-
const target =
|
|
4643
|
+
const target = resolve9(root);
|
|
5075
4644
|
const enclosing = stateHomes(home).find((state) => target === state);
|
|
5076
4645
|
if (enclosing === void 0) return void 0;
|
|
5077
4646
|
return `${bin} ${verb}: the ${verb} root ${target} is the state home ${enclosing} itself.
|
|
5078
4647
|
A workspace there collides with AGRO state and blocks every command run from ${home}.
|
|
5079
|
-
Move it out \u2014 for example \`mv ${enclosing} ${
|
|
4648
|
+
Move it out \u2014 for example \`mv ${enclosing} ${join15(home, "agro")}\` \u2014 then re-run this command.
|
|
5080
4649
|
`;
|
|
5081
4650
|
}
|
|
5082
4651
|
function isIgnorableEntry(name) {
|
|
@@ -5093,24 +4662,24 @@ function cloneInto(target, run, root, ref) {
|
|
|
5093
4662
|
assertCloned(run("git", ["clone", ...branch, AGRO_REPO_URL, target], { stdio: "inherit" }), root);
|
|
5094
4663
|
}
|
|
5095
4664
|
function cloneThroughStaging(root, run, ref) {
|
|
5096
|
-
const staging = mkdtempSync4(
|
|
4665
|
+
const staging = mkdtempSync4(join15(dirname9(root), "agro-clone-"));
|
|
5097
4666
|
try {
|
|
5098
|
-
const checkout =
|
|
4667
|
+
const checkout = join15(staging, "agro");
|
|
5099
4668
|
cloneInto(checkout, run, root, ref);
|
|
5100
4669
|
for (const name of readdirSync3(checkout)) {
|
|
5101
|
-
const destination =
|
|
4670
|
+
const destination = join15(root, name);
|
|
5102
4671
|
if (existsSync19(destination)) {
|
|
5103
4672
|
throw new Error(`could not move ${name} into ${root} \u2014 the path already exists`);
|
|
5104
4673
|
}
|
|
5105
|
-
renameSync5(
|
|
4674
|
+
renameSync5(join15(checkout, name), destination);
|
|
5106
4675
|
}
|
|
5107
4676
|
} finally {
|
|
5108
4677
|
rmSync4(staging, { recursive: true, force: true });
|
|
5109
4678
|
}
|
|
5110
4679
|
}
|
|
5111
4680
|
function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
5112
|
-
const root =
|
|
5113
|
-
if (existsSync19(
|
|
4681
|
+
const root = resolve9(path4);
|
|
4682
|
+
if (existsSync19(join15(root, ".git"))) return { root, action: "reused" };
|
|
5114
4683
|
const stats = statSync5(root, { throwIfNoEntry: false });
|
|
5115
4684
|
if (stats === void 0) {
|
|
5116
4685
|
mkdirSync10(dirname9(root), { recursive: true });
|
|
@@ -5132,11 +4701,11 @@ function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
|
5132
4701
|
function listHostWorkspaces(env, home) {
|
|
5133
4702
|
const root = workspacesRoot(env, home);
|
|
5134
4703
|
if (!isDirectory3(root)) return [];
|
|
5135
|
-
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => SANDBOX_NAME_PATTERN.test(name)).filter((name) => existsSync19(
|
|
4704
|
+
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => SANDBOX_NAME_PATTERN.test(name)).filter((name) => existsSync19(join15(root, name, ".git"))).sort();
|
|
5136
4705
|
}
|
|
5137
4706
|
function resolveExistingWorkspace(bin, verb, path4, env, home) {
|
|
5138
|
-
const root =
|
|
5139
|
-
if (existsSync19(
|
|
4707
|
+
const root = resolve9(path4);
|
|
4708
|
+
if (existsSync19(join15(root, ".git"))) return { ok: true, root };
|
|
5140
4709
|
const existing = listHostWorkspaces(env, home);
|
|
5141
4710
|
const inventory = existing.length === 0 ? "No host workspace exists yet.\n" : `Workspaces that exist: ${existing.join(", ")}
|
|
5142
4711
|
`;
|
|
@@ -5166,7 +4735,7 @@ function targetFor(root, run, env) {
|
|
|
5166
4735
|
});
|
|
5167
4736
|
}
|
|
5168
4737
|
function hostPrefix(home) {
|
|
5169
|
-
return
|
|
4738
|
+
return join16(home, ".local");
|
|
5170
4739
|
}
|
|
5171
4740
|
function pathEntries(env) {
|
|
5172
4741
|
return (env.PATH ?? "").split(delimiter2).filter((part) => part !== "");
|
|
@@ -5213,7 +4782,7 @@ async function sandboxMarkerExists(target, entry) {
|
|
|
5213
4782
|
return r.exitCode === 0;
|
|
5214
4783
|
}
|
|
5215
4784
|
function hostBinaryUnderPrefix(entry, prefix) {
|
|
5216
|
-
return existsSync20(
|
|
4785
|
+
return existsSync20(join16(harnessBinPath(prefix), entry.binary));
|
|
5217
4786
|
}
|
|
5218
4787
|
function stateOf(entry, installed, location) {
|
|
5219
4788
|
return {
|
|
@@ -5234,7 +4803,7 @@ function probeableHost(env, home) {
|
|
|
5234
4803
|
return void 0;
|
|
5235
4804
|
}
|
|
5236
4805
|
const prefix = hostPrefix(home);
|
|
5237
|
-
if (!existsSync20(
|
|
4806
|
+
if (!existsSync20(join16(root, ".git")) && !existsSync20(prefix)) return void 0;
|
|
5238
4807
|
return { root, prefix };
|
|
5239
4808
|
}
|
|
5240
4809
|
async function collectStates(root, run, home, env, only) {
|
|
@@ -5330,10 +4899,13 @@ async function runHarnessStatus(name, opts, io) {
|
|
|
5330
4899
|
function hermesTargetRoot(target) {
|
|
5331
4900
|
return target.kind === "docker-compose" ? "/home/sandbox/harness" : target.workspace.targetRoot;
|
|
5332
4901
|
}
|
|
4902
|
+
function hermesEnv(root) {
|
|
4903
|
+
return { ...agroEnvPair("PROJECT_ROOT", root), HERMES_HOME: `${root}/.hermes` };
|
|
4904
|
+
}
|
|
5333
4905
|
async function reconcileHermes(target, io, bin, user, root = hermesTargetRoot(target)) {
|
|
5334
4906
|
const result = await target.exec({
|
|
5335
4907
|
argv: remoteControlDirScript(root, "scripts/link-providers.sh", ["--init", "--hermes-only"]),
|
|
5336
|
-
env:
|
|
4908
|
+
env: hermesEnv(root),
|
|
5337
4909
|
...user ? { user } : {},
|
|
5338
4910
|
stdio: "inherit"
|
|
5339
4911
|
});
|
|
@@ -5432,10 +5004,7 @@ Run: bash ${root}/.agro/scripts/link-providers.sh --init
|
|
|
5432
5004
|
return 1;
|
|
5433
5005
|
}
|
|
5434
5006
|
const hermes = entry.id === "hermes";
|
|
5435
|
-
const installEnv = hermes ?
|
|
5436
|
-
...agroEnvPair("PROJECT_ROOT", root),
|
|
5437
|
-
HERMES_HOME: `${root}/.hermes`
|
|
5438
|
-
} : void 0;
|
|
5007
|
+
const installEnv = hermes ? hermesEnv(root) : void 0;
|
|
5439
5008
|
if (hermes) {
|
|
5440
5009
|
const code = await reconcileHermes(target, io, bin, void 0, root);
|
|
5441
5010
|
if (code !== 0) return code;
|
|
@@ -5642,10 +5211,7 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
5642
5211
|
return await installOnHost(entry, opts, io, run, status);
|
|
5643
5212
|
}
|
|
5644
5213
|
const hermes = entry.id === "hermes";
|
|
5645
|
-
const installEnv = hermes ?
|
|
5646
|
-
...agroEnvPair("PROJECT_ROOT", hermesTargetRoot(target)),
|
|
5647
|
-
HERMES_HOME: `${hermesTargetRoot(target)}/.hermes`
|
|
5648
|
-
} : void 0;
|
|
5214
|
+
const installEnv = hermes ? hermesEnv(hermesTargetRoot(target)) : void 0;
|
|
5649
5215
|
if (hermes) {
|
|
5650
5216
|
const code = await reconcileHermes(target, io, opts.bin, "sandbox");
|
|
5651
5217
|
if (code !== 0) return code;
|
|
@@ -5701,7 +5267,7 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
5701
5267
|
// src/commands/workspace.ts
|
|
5702
5268
|
import { existsSync as existsSync21, rmSync as rmSync5 } from "node:fs";
|
|
5703
5269
|
import { homedir as homedir4 } from "node:os";
|
|
5704
|
-
import { join as
|
|
5270
|
+
import { join as join17, resolve as resolve10 } from "node:path";
|
|
5705
5271
|
function homeOf2(opts) {
|
|
5706
5272
|
return (opts.homedir ?? homedir4)();
|
|
5707
5273
|
}
|
|
@@ -5710,7 +5276,7 @@ function messageOf2(error) {
|
|
|
5710
5276
|
}
|
|
5711
5277
|
function recordedRoot(env, home) {
|
|
5712
5278
|
const configured = readHostConfig(env, home).harnessRoot;
|
|
5713
|
-
return typeof configured === "string" && configured !== "" ?
|
|
5279
|
+
return typeof configured === "string" && configured !== "" ? resolve10(configured) : void 0;
|
|
5714
5280
|
}
|
|
5715
5281
|
function renderTable2(rows, io) {
|
|
5716
5282
|
const header2 = ["WORKSPACE", "DEFAULT", "PATH"];
|
|
@@ -5735,7 +5301,7 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
5735
5301
|
let root;
|
|
5736
5302
|
const chosen = name ?? DEFAULT_WORKSPACE_NAME;
|
|
5737
5303
|
try {
|
|
5738
|
-
root = opts.path !== void 0 ?
|
|
5304
|
+
root = opts.path !== void 0 ? resolve10(opts.path) : workspaceRoot(chosen, env, home);
|
|
5739
5305
|
} catch (err) {
|
|
5740
5306
|
io.stderr(`${bin} workspace: ${messageOf2(err)}
|
|
5741
5307
|
`);
|
|
@@ -5749,7 +5315,7 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
5749
5315
|
let action;
|
|
5750
5316
|
const preexisting = existsSync21(root);
|
|
5751
5317
|
try {
|
|
5752
|
-
if (!opts.json && !existsSync21(
|
|
5318
|
+
if (!opts.json && !existsSync21(join17(root, ".git"))) {
|
|
5753
5319
|
const at = opts.ref !== void 0 ? ` at ${opts.ref}` : "";
|
|
5754
5320
|
io.stdout(`cloning ${AGRO_REPO_URL}${at} into ${root}\u2026
|
|
5755
5321
|
`);
|
|
@@ -5794,7 +5360,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
5794
5360
|
const recorded = recordedRoot(env, home);
|
|
5795
5361
|
const registry = workspacesRoot(env, home);
|
|
5796
5362
|
rows = listHostWorkspaces(env, home).map((name) => {
|
|
5797
|
-
const root =
|
|
5363
|
+
const root = join17(registry, name);
|
|
5798
5364
|
return { name, root, default: recorded === root };
|
|
5799
5365
|
});
|
|
5800
5366
|
} catch (err) {
|
|
@@ -5821,7 +5387,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
5821
5387
|
// src/commands/tool.ts
|
|
5822
5388
|
import { existsSync as existsSync22 } from "node:fs";
|
|
5823
5389
|
import { homedir as homedir5, userInfo as userInfo2 } from "node:os";
|
|
5824
|
-
import { delimiter as delimiter3, join as
|
|
5390
|
+
import { delimiter as delimiter3, join as join18 } from "node:path";
|
|
5825
5391
|
|
|
5826
5392
|
// src/lib/tools/catalog.ts
|
|
5827
5393
|
var TOOLS_DOC = "docs/installation.md";
|
|
@@ -6252,7 +5818,7 @@ function targetFor2(root, run, env) {
|
|
|
6252
5818
|
});
|
|
6253
5819
|
}
|
|
6254
5820
|
function hostPrefix2(home) {
|
|
6255
|
-
return
|
|
5821
|
+
return join18(home, ".local");
|
|
6256
5822
|
}
|
|
6257
5823
|
function sandboxMarkerPath2(entry) {
|
|
6258
5824
|
return `${SANDBOX_HARNESS_PREFIX}/share/agro/tools/${entry.id}.installed`;
|
|
@@ -6266,7 +5832,7 @@ async function sandboxMarkerExists2(target, entry) {
|
|
|
6266
5832
|
return r.exitCode === 0;
|
|
6267
5833
|
}
|
|
6268
5834
|
function hostBinaryUnderPrefix2(entry, prefix) {
|
|
6269
|
-
return existsSync22(
|
|
5835
|
+
return existsSync22(join18(harnessBinPath(prefix), entry.binary));
|
|
6270
5836
|
}
|
|
6271
5837
|
function pathEntries2(env) {
|
|
6272
5838
|
return (env.PATH ?? "").split(delimiter3).filter((part) => part !== "");
|
|
@@ -6346,7 +5912,7 @@ function probeableHost2(env, home) {
|
|
|
6346
5912
|
return void 0;
|
|
6347
5913
|
}
|
|
6348
5914
|
const prefix = hostPrefix2(home);
|
|
6349
|
-
if (!existsSync22(
|
|
5915
|
+
if (!existsSync22(join18(root, ".git")) && !existsSync22(prefix)) return void 0;
|
|
6350
5916
|
return { root, prefix };
|
|
6351
5917
|
}
|
|
6352
5918
|
async function collectRows(root, run, home, env, only) {
|
|
@@ -6820,11 +6386,11 @@ Remove ${entry.id} by hand: ${ROOT_TOOL_REMOVAL_DOCS}
|
|
|
6820
6386
|
}
|
|
6821
6387
|
|
|
6822
6388
|
// src/lib/remote.ts
|
|
6823
|
-
import { spawnSync as
|
|
6389
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
6824
6390
|
var DEFAULT_REPO_URL = "https://github.com/mifunedev/agro";
|
|
6825
6391
|
var DEFAULT_CLONE_TIMEOUT_MS = 12e4;
|
|
6826
6392
|
var spawnRunner2 = (cmd, args, opts) => {
|
|
6827
|
-
const r =
|
|
6393
|
+
const r = spawnSync4(cmd, args, {
|
|
6828
6394
|
env: opts.env,
|
|
6829
6395
|
timeout: opts.timeoutMs,
|
|
6830
6396
|
stdio: ["ignore", "ignore", "pipe"],
|
|
@@ -6882,8 +6448,37 @@ function fetchRemoteSource(opts) {
|
|
|
6882
6448
|
return opts.destDir;
|
|
6883
6449
|
}
|
|
6884
6450
|
|
|
6451
|
+
// src/command-table.ts
|
|
6452
|
+
var CONFIG_VERBS = ["show", "set", "repo"];
|
|
6453
|
+
var CONFIG_INTEGRATIONS = ["langfuse"];
|
|
6454
|
+
var SECRET_VERBS = ["set", "list"];
|
|
6455
|
+
var LANGFUSE_VERBS = ["apply", "status", "disable"];
|
|
6456
|
+
var SANDBOX_SUBCOMMANDS = ["install", "list", "upgrade"];
|
|
6457
|
+
var WORKSPACE_SUBCOMMANDS = ["create", "list"];
|
|
6458
|
+
var HARNESS_SUBCOMMANDS = ["list", "install", "status", "uninstall"];
|
|
6459
|
+
var TOOL_SUBCOMMANDS = ["list", "install", "status", "uninstall"];
|
|
6460
|
+
function isOneOf(values, value) {
|
|
6461
|
+
return value !== void 0 && values.includes(value);
|
|
6462
|
+
}
|
|
6463
|
+
var AGRO_COMMANDS = Object.freeze({
|
|
6464
|
+
config: [...CONFIG_VERBS, ...CONFIG_INTEGRATIONS],
|
|
6465
|
+
secret: SECRET_VERBS,
|
|
6466
|
+
langfuse: LANGFUSE_VERBS,
|
|
6467
|
+
update: null,
|
|
6468
|
+
"self-upgrade": null,
|
|
6469
|
+
vendor: null,
|
|
6470
|
+
sandbox: SANDBOX_SUBCOMMANDS,
|
|
6471
|
+
shell: null,
|
|
6472
|
+
compose: null,
|
|
6473
|
+
workspace: WORKSPACE_SUBCOMMANDS,
|
|
6474
|
+
harness: HARNESS_SUBCOMMANDS,
|
|
6475
|
+
tool: TOOL_SUBCOMMANDS,
|
|
6476
|
+
gateway: null,
|
|
6477
|
+
...Object.fromEntries(composeVerbs().map((verb) => [verb, null]))
|
|
6478
|
+
});
|
|
6479
|
+
|
|
6885
6480
|
// src/cli.ts
|
|
6886
|
-
var DEFAULT_SOURCE_CONTROL_DIR =
|
|
6481
|
+
var DEFAULT_SOURCE_CONTROL_DIR = resolve11(
|
|
6887
6482
|
dirname10(fileURLToPath(import.meta.url)),
|
|
6888
6483
|
"../.."
|
|
6889
6484
|
);
|
|
@@ -7018,7 +6613,7 @@ The upgrade follows whichever mechanism installed this executable:
|
|
|
7018
6613
|
npm-managed realpath under node_modules/${AGRO_PRODUCT.packageName}/: reads the registry
|
|
7019
6614
|
version with \`npm view\`, then runs
|
|
7020
6615
|
\`npm install -g --prefix <owning prefix> ${AGRO_PRODUCT.packageName}@<version>\`.
|
|
7021
|
-
standalone a plain file (
|
|
6616
|
+
standalone a plain file (install.sh): downloads AGRO_JS_URL (default
|
|
7022
6617
|
${DEFAULT_ARTIFACT_URL})
|
|
7023
6618
|
into the same directory, checks its shebang and \`--version\`,
|
|
7024
6619
|
renames it over the executable, and keeps <path>.prev until the
|
|
@@ -7276,7 +6871,6 @@ function extractSandboxFlag(command, rest) {
|
|
|
7276
6871
|
}
|
|
7277
6872
|
return { ok: true, args: sandbox === void 0 ? { rest: kept } : { rest: kept, sandbox } };
|
|
7278
6873
|
}
|
|
7279
|
-
var LANGFUSE_VERBS = ["apply", "status", "disable"];
|
|
7280
6874
|
function parseLangfuseArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
7281
6875
|
if (rest.length === 0 || isHelpFlag2(rest[0])) return { ok: true, args: { help: true } };
|
|
7282
6876
|
const [head, ...tail] = rest;
|
|
@@ -7293,7 +6887,6 @@ function parseLangfuseArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
7293
6887
|
}
|
|
7294
6888
|
return { ok: true, args: { help: false, verb: head } };
|
|
7295
6889
|
}
|
|
7296
|
-
var CONFIG_VERBS = ["show", "set", "repo"];
|
|
7297
6890
|
function parseConfigArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
7298
6891
|
const scoped = extractSandboxFlag(`${bin} config`, input);
|
|
7299
6892
|
if (!scoped.ok) return scoped;
|
|
@@ -7345,7 +6938,6 @@ function parseConfigArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
7345
6938
|
}
|
|
7346
6939
|
return { ok: true, args: { ...args, verb, key, value } };
|
|
7347
6940
|
}
|
|
7348
|
-
var SECRET_VERBS = ["set", "list"];
|
|
7349
6941
|
function parseSecretArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
7350
6942
|
const scoped = extractSandboxFlag(`${bin} secret`, input);
|
|
7351
6943
|
if (!scoped.ok) return scoped;
|
|
@@ -7555,7 +7147,7 @@ function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
7555
7147
|
return { ok: false, error: `${bin} workspace: --ref requires a branch or tag` };
|
|
7556
7148
|
}
|
|
7557
7149
|
const [sub, name, ...extra] = positionals;
|
|
7558
|
-
if (
|
|
7150
|
+
if (!isOneOf(WORKSPACE_SUBCOMMANDS, sub)) {
|
|
7559
7151
|
return {
|
|
7560
7152
|
ok: false,
|
|
7561
7153
|
error: `${bin} workspace: unknown subcommand "${sub}" \u2014 expected create or list`,
|
|
@@ -7641,7 +7233,7 @@ function parseHarnessArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
7641
7233
|
};
|
|
7642
7234
|
}
|
|
7643
7235
|
const [sub, name, ...extra] = positionals;
|
|
7644
|
-
if (
|
|
7236
|
+
if (!isOneOf(HARNESS_SUBCOMMANDS, sub)) {
|
|
7645
7237
|
return {
|
|
7646
7238
|
ok: false,
|
|
7647
7239
|
error: `${bin} harness: unknown subcommand "${sub}" \u2014 expected list, install, uninstall, or status`,
|
|
@@ -7698,7 +7290,7 @@ function parseToolArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
7698
7290
|
return { ok: false, error: `${bin} tool: --path requires a directory` };
|
|
7699
7291
|
}
|
|
7700
7292
|
const [sub, name, ...extra] = positionals;
|
|
7701
|
-
if (
|
|
7293
|
+
if (!isOneOf(TOOL_SUBCOMMANDS, sub)) {
|
|
7702
7294
|
return {
|
|
7703
7295
|
ok: false,
|
|
7704
7296
|
error: `${bin} tool: unknown subcommand "${sub}" \u2014 expected list, install, uninstall, or status`,
|
|
@@ -7731,18 +7323,18 @@ function parseGatewayArgs(rest) {
|
|
|
7731
7323
|
return { ok: true, args: { help: false, passthrough: [...rest] } };
|
|
7732
7324
|
}
|
|
7733
7325
|
function bundledPayloadExists(bundled, exists = existsSync23) {
|
|
7734
|
-
return exists(
|
|
7326
|
+
return exists(join19(bundled.sourceControlDir, "manifest.json"));
|
|
7735
7327
|
}
|
|
7736
7328
|
function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
7737
7329
|
const exists = bundled.exists ?? existsSync23;
|
|
7738
7330
|
if (args.fromDir !== void 0) {
|
|
7739
|
-
return { kind: "local", fromDir:
|
|
7331
|
+
return { kind: "local", fromDir: resolve11(args.fromDir) };
|
|
7740
7332
|
}
|
|
7741
7333
|
if (args.fromRemote) {
|
|
7742
7334
|
return { kind: "remote", ref: args.ref };
|
|
7743
7335
|
}
|
|
7744
7336
|
if (bundledPayloadExists(bundled, exists)) {
|
|
7745
|
-
return { kind: "local", fromDir:
|
|
7337
|
+
return { kind: "local", fromDir: resolve11(bundled.sourceControlDir, "..") };
|
|
7746
7338
|
}
|
|
7747
7339
|
return {
|
|
7748
7340
|
kind: "remote",
|
|
@@ -7754,7 +7346,7 @@ function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
|
7754
7346
|
function readPayloadVersion(checkoutDir) {
|
|
7755
7347
|
try {
|
|
7756
7348
|
const parsed = JSON.parse(
|
|
7757
|
-
readFileSync13(
|
|
7349
|
+
readFileSync13(join19(resolveControlDir(checkoutDir), "cli", "package.json"), "utf8")
|
|
7758
7350
|
);
|
|
7759
7351
|
if (parsed && typeof parsed.version === "string") return parsed.version;
|
|
7760
7352
|
} catch {
|
|
@@ -7763,7 +7355,7 @@ function readPayloadVersion(checkoutDir) {
|
|
|
7763
7355
|
}
|
|
7764
7356
|
async function runWithRemoteSource(hooks, fn) {
|
|
7765
7357
|
const fetch2 = hooks.fetch ?? fetchRemoteSource;
|
|
7766
|
-
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(
|
|
7358
|
+
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(join19(tmpdir4(), "oh-remote-")));
|
|
7767
7359
|
const rm = hooks.rm ?? ((dir) => rmSync6(dir, { recursive: true, force: true }));
|
|
7768
7360
|
const out = hooks.stdout ?? ((s) => process.stdout.write(s));
|
|
7769
7361
|
const checkoutDir = mkdtemp();
|
|
@@ -7789,6 +7381,9 @@ async function main(argv) {
|
|
|
7789
7381
|
`);
|
|
7790
7382
|
return 0;
|
|
7791
7383
|
}
|
|
7384
|
+
if (!Object.hasOwn(AGRO_COMMANDS, first)) return unknownCommand(first, product);
|
|
7385
|
+
const fallbackWarning = sandboxFallbackWarning();
|
|
7386
|
+
if (fallbackWarning !== void 0) process.stderr.write(fallbackWarning);
|
|
7792
7387
|
if (first === "config") {
|
|
7793
7388
|
const parsed = parseConfigArgs(argv.slice(1), bin);
|
|
7794
7389
|
if (!parsed.ok) {
|
|
@@ -8111,7 +7706,10 @@ async function main(argv) {
|
|
|
8111
7706
|
}
|
|
8112
7707
|
return runGateway(parsed.args.passthrough, { bin });
|
|
8113
7708
|
}
|
|
8114
|
-
|
|
7709
|
+
return unknownCommand(first, product);
|
|
7710
|
+
}
|
|
7711
|
+
function unknownCommand(first, product) {
|
|
7712
|
+
process.stderr.write(`${product.bin}: unknown command "${first}"
|
|
8115
7713
|
|
|
8116
7714
|
`);
|
|
8117
7715
|
printAgroHelp(product);
|
|
@@ -8123,19 +7721,7 @@ function lifecycleIo() {
|
|
|
8123
7721
|
stderr: (s) => process.stderr.write(s)
|
|
8124
7722
|
};
|
|
8125
7723
|
}
|
|
8126
|
-
|
|
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(
|
|
7724
|
+
main(process.argv.slice(2)).then(
|
|
8139
7725
|
(code) => process.exit(code),
|
|
8140
7726
|
(err) => {
|
|
8141
7727
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -8145,9 +7731,6 @@ runCli(process.argv.slice(2)).then(
|
|
|
8145
7731
|
}
|
|
8146
7732
|
);
|
|
8147
7733
|
export {
|
|
8148
|
-
CONFIG_VERBS,
|
|
8149
|
-
LANGFUSE_VERBS,
|
|
8150
|
-
SECRET_VERBS,
|
|
8151
7734
|
bundledPayloadExists,
|
|
8152
7735
|
extractSandboxFlag,
|
|
8153
7736
|
isHelpFlag2 as isHelpFlag,
|
|
@@ -8180,7 +7763,6 @@ export {
|
|
|
8180
7763
|
printVendorHelp,
|
|
8181
7764
|
printWorkspaceHelp,
|
|
8182
7765
|
resolveVendorSource,
|
|
8183
|
-
runCli,
|
|
8184
7766
|
runWithRemoteSource,
|
|
8185
7767
|
runtimeLines
|
|
8186
7768
|
};
|