@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.
Files changed (3) hide show
  1. package/README.md +6 -6
  2. package/dist/agro.js +240 -658
  3. 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 join20, resolve as resolve13 } from "node:path";
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 get-agro.sh`
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 get-agro.sh`
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 = PROVISIONABLE_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((resolve14) => {
1328
- rl.question(` ${question} `, (answer) => resolve14(answer.trim()));
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((resolve14) => {
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
- resolve14(Buffer.from(bytes).toString("utf8").trim());
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((resolve14) => {
1367
+ return await new Promise((resolve12) => {
1409
1368
  rl.question(` ${question} `, (answer) => {
1410
1369
  process.stdout.write("\n");
1411
- resolve14(answer.trim());
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/.env"
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 openshellFiles(entry) {
1849
- return [{ dest: join4(entry, "openshell-policy.yaml"), body: openshell_policy_default, mode: 420 }];
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
- return [
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 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
- }
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 = "/.dockerenv";
2047
- var EXECUTION_TARGET_ENV = "AGRO_EXECUTION_TARGET";
2048
- function runningInsideSandbox(env = process.env, fileExists = existsSync9) {
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
- return fileExists(SANDBOX_MARKER_FILE) && (env.SANDBOX_NAME ?? "") !== "";
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
- function openshellImage(config) {
2546
- return nonEmpty(config.image?.ref) ?? officialImageRef(AGRO_VERSION);
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 nonEmpty(value) {
2549
- return value === void 0 || value === "" ? void 0 : value;
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(join6(tmpdir(), COMPOSE_ENV_DIR_PREFIX));
2331
+ return mkdtempSync(join5(tmpdir(), COMPOSE_ENV_DIR_PREFIX));
2709
2332
  }
2710
2333
  function writeComposeEnv(root, dir) {
2711
- const file = join6(dir, "compose.env");
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 = join6(root, ".devcontainer");
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 = sandboxName(root);
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(`${runtime} is required for \`${opts.bin} shell\` but was not found on PATH`);
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(`${shellFailureHint(runtime, opts.bin, name)}
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(join6(root, ".devcontainer", "docker-compose.yml"), "utf8");
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 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
- }
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 (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 !== "") {
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 = via === "target" ? await destroyThroughTarget(root, name, opts.run ?? spawnRunner) : runComposeVerb("destroy", { ...opts, name: basename6(root) });
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 join8 } from "node:path";
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 join7 } from "node:path";
2890
+ import { dirname as dirname5, join as join6 } from "node:path";
3290
2891
  function appendGitignoreLines(root, lines) {
3291
- const target = join7(root, ".gitignore");
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(join8(root, ".git"))) return;
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 join11, relative } from "node:path";
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 join9 } from "node:path";
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 join9(home, ".claude", "settings.json");
3031
+ return join8(home, ".claude", "settings.json");
3423
3032
  }
3424
3033
  function piTracingConfigPath(home) {
3425
- return join9(home, ".pi", "agent", "langfuse.json");
3034
+ return join8(home, ".pi", "agent", "langfuse.json");
3426
3035
  }
3427
3036
  function codexTracingConfigPath(home) {
3428
- return join9(home, ".codex", "langfuse.json");
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 join10 } from "node:path";
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 join10(home, ".config", "agro", "langfuse.env");
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(join11(tmpdir2(), "agro-langfuse-"));
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 = join11(home, rel);
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 = join11(scratch, rel);
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 = join11(home, ".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 join13, resolve as resolve9 } from "node:path";
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 join12 } from "node:path";
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 = join12(stateHome, 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: entrySandboxName(root) ?? name, run });
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(join13(repo, ".devcontainer", "Dockerfile"));
3890
+ return repo !== void 0 && existsSync16(join12(repo, ".devcontainer", "Dockerfile"));
4255
3891
  }
4256
3892
  function prepareHomeMount(value) {
4257
- const path4 = resolve9(value);
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 nonEmpty2(value) {
3937
+ function nonEmpty(value) {
4302
3938
  return value === void 0 || value === "" ? void 0 : value;
4303
3939
  }
4304
- function seedConfig(name, checkout, seed, run, runtime = "docker") {
3940
+ function seedConfig(name, checkout, seed, run) {
4305
3941
  const config = overlaySettings(defaultAgroConfig(name), seed);
4306
3942
  config.name = name;
4307
- config.runtime = runtime;
3943
+ config.runtime = "docker";
4308
3944
  if (checkout !== void 0) config.checkout = checkout;
4309
- config.timezone = nonEmpty2(seed?.timezone) ?? hostTimezone();
3945
+ config.timezone = nonEmpty(seed?.timezone) ?? hostTimezone();
4310
3946
  config.git = {
4311
- userName: nonEmpty2(seed?.git?.userName) ?? gitIdentity(run, "user.name"),
4312
- userEmail: nonEmpty2(seed?.git?.userEmail) ?? gitIdentity(run, "user.email")
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: resolve9(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
- if (runtime.id === "openshell") return await installOpenShell(opts, io, run);
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) ?? resolve9(opts.checkout ?? ".");
4024
+ const target = configCheckout(config) ?? resolve7(opts.checkout ?? ".");
4390
4025
  io.stderr(
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
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(join13(tmpdir3(), "oh-sandbox-preview-"));
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 join14 } from "node:path";
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 = join14(root, ".env");
4526
- const fallbackEnv = join14(root, ".devcontainer", ".env");
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
- routeVerb(root, "upgrade", entrySandboxName(root) ?? opts.name);
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 or openshell argv that would run, then exit
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 join17 } from "node:path";
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 basename7, dirname as dirname8, isAbsolute, join as join15, resolve as resolve10 } from "node:path";
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 join15(hostStateHome(env, home), HOST_CONFIG_FILE);
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 join15(hostStateHome(env, home), WORKSPACES_SUBDIR);
4504
+ return join14(hostStateHome(env, home), WORKSPACES_SUBDIR);
4936
4505
  }
4937
4506
  function workspaceRoot(name, env, home) {
4938
4507
  assertWorkspaceName(name);
4939
- return join15(workspacesRoot(env, home), name);
4508
+ return join14(workspacesRoot(env, home), name);
4940
4509
  }
4941
4510
  function defaultHarnessRoot(env, home) {
4942
- return join15(workspacesRoot(env, home), DEFAULT_WORKSPACE_NAME);
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(`${basename7(path4)} is not valid JSON: ${path4}`);
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 resolve10(explicit);
4611
+ if (typeof explicit === "string" && explicit !== "") return resolve8(explicit);
5043
4612
  const configured = readHostConfig(env, home).harnessRoot;
5044
- if (typeof configured === "string" && configured !== "") return resolve10(configured);
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 !== "" && resolve10(recorded) === root) return;
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 join16, resolve as resolve11 } from "node:path";
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) => join16(home, 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 = resolve11(root);
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} ${join16(home, "agro")}\` \u2014 then re-run this command.
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(join16(dirname9(root), "agro-clone-"));
4665
+ const staging = mkdtempSync4(join15(dirname9(root), "agro-clone-"));
5097
4666
  try {
5098
- const checkout = join16(staging, "agro");
4667
+ const checkout = join15(staging, "agro");
5099
4668
  cloneInto(checkout, run, root, ref);
5100
4669
  for (const name of readdirSync3(checkout)) {
5101
- const destination = join16(root, name);
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(join16(checkout, name), destination);
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 = resolve11(path4);
5113
- if (existsSync19(join16(root, ".git"))) return { root, action: "reused" };
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(join16(root, name, ".git"))).sort();
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 = resolve11(path4);
5139
- if (existsSync19(join16(root, ".git"))) return { ok: true, root };
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 join17(home, ".local");
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(join17(harnessBinPath(prefix), entry.binary));
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(join17(root, ".git")) && !existsSync20(prefix)) return void 0;
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: agroEnvPair("PROJECT_ROOT", root),
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 join18, resolve as resolve12 } from "node:path";
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 !== "" ? resolve12(configured) : void 0;
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 ? resolve12(opts.path) : workspaceRoot(chosen, env, home);
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(join18(root, ".git"))) {
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 = join18(registry, name);
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 join19 } from "node:path";
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 join19(home, ".local");
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(join19(harnessBinPath(prefix), entry.binary));
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(join19(root, ".git")) && !existsSync22(prefix)) return void 0;
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 spawnSync3 } from "node:child_process";
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 = spawnSync3(cmd, args, {
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 = resolve13(
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 (get-agro.sh): downloads AGRO_JS_URL (default
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 (sub !== "create" && sub !== "list") {
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 (sub !== "list" && sub !== "install" && sub !== "status" && sub !== "uninstall") {
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 (sub !== "list" && sub !== "install" && sub !== "status" && sub !== "uninstall") {
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(join20(bundled.sourceControlDir, "manifest.json"));
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: resolve13(args.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: resolve13(bundled.sourceControlDir, "..") };
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(join20(resolveControlDir(checkoutDir), "cli", "package.json"), "utf8")
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(join20(tmpdir4(), "oh-remote-")));
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
- process.stderr.write(`${bin}: unknown command "${first}"
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
- 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(
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
  };