@mifune/agro 0.15.1 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -6
- package/dist/agro.js +792 -457
- package/package.json +1 -1
package/dist/agro.js
CHANGED
|
@@ -7,8 +7,8 @@ var __export = (target, all) => {
|
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { dirname as dirname10, join as
|
|
11
|
-
import { existsSync as
|
|
10
|
+
import { dirname as dirname10, join as join19, resolve as resolve11 } from "node:path";
|
|
11
|
+
import { existsSync as existsSync23, mkdtempSync as mkdtempSync5, readFileSync as readFileSync13, rmSync as rmSync6 } from "node:fs";
|
|
12
12
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
13
13
|
|
|
14
14
|
// src/commands/update.ts
|
|
@@ -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);
|
|
@@ -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) {
|
|
@@ -1403,10 +1412,10 @@ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync as read
|
|
|
1403
1412
|
import { dirname as dirname4, join as join4, resolve as resolve5, sep as sep2 } from "node:path";
|
|
1404
1413
|
|
|
1405
1414
|
// agro-asset:/home/runner/work/agro/agro/.devcontainer/docker-compose.yml
|
|
1406
|
-
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';
|
|
1407
1416
|
|
|
1408
1417
|
// agro-asset:/home/runner/work/agro/agro/.devcontainer/docker-compose.image-only.yml
|
|
1409
|
-
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';
|
|
1410
1419
|
|
|
1411
1420
|
// agro-asset:/home/runner/work/agro/agro/.devcontainer/docker-compose.ssh.yml
|
|
1412
1421
|
var docker_compose_ssh_default = 'services:\n sandbox:\n ports:\n - "127.0.0.1:${SANDBOX_SSH_PORT:-2222}:22"\n';
|
|
@@ -1484,8 +1493,7 @@ if [ -f "$REPO_DIR/harness.yaml" ] && [ -f "$MIGRATOR" ]; then
|
|
|
1484
1493
|
sh "$MIGRATOR" "$REPO_DIR" >&2 || true
|
|
1485
1494
|
fi
|
|
1486
1495
|
|
|
1487
|
-
ENV_FILE="$REPO_DIR
|
|
1488
|
-
[ -f "$ENV_FILE" ] || ENV_FILE="$REPO_DIR/.devcontainer/.env"
|
|
1496
|
+
ENV_FILE="$(agro_env_file "$REPO_DIR")"
|
|
1489
1497
|
|
|
1490
1498
|
compose_path() {
|
|
1491
1499
|
case "$1" in
|
|
@@ -1632,6 +1640,14 @@ agro_config_file() {
|
|
|
1632
1640
|
printf '%s\\n' "$1/$AGRO_CONFIG_FILE"
|
|
1633
1641
|
}
|
|
1634
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
|
+
|
|
1635
1651
|
agro_env_value() {
|
|
1636
1652
|
local key="AGRO_$1"
|
|
1637
1653
|
printf '%s\\n' "\${!key:-}"
|
|
@@ -2222,7 +2238,7 @@ function resolveExecutionTarget(opts) {
|
|
|
2222
2238
|
}
|
|
2223
2239
|
|
|
2224
2240
|
// src/lib/version.ts
|
|
2225
|
-
var AGRO_VERSION = true ? "0.
|
|
2241
|
+
var AGRO_VERSION = true ? "0.16.1" : "0.0.0-dev";
|
|
2226
2242
|
var OFFICIAL_IMAGE = "ghcr.io/mifunedev/agro";
|
|
2227
2243
|
var RELEASE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z][a-z0-9]*\.[0-9]+)?$/;
|
|
2228
2244
|
function parseReleaseVersion(value) {
|
|
@@ -2852,6 +2868,7 @@ function sameRemote(url, owner, name) {
|
|
|
2852
2868
|
}
|
|
2853
2869
|
|
|
2854
2870
|
// src/commands/secret.ts
|
|
2871
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
2855
2872
|
import { existsSync as existsSync12 } from "node:fs";
|
|
2856
2873
|
import { join as join7 } from "node:path";
|
|
2857
2874
|
|
|
@@ -2883,8 +2900,16 @@ function appendGitignoreLines(root, lines) {
|
|
|
2883
2900
|
function secretsRoot(opts) {
|
|
2884
2901
|
return opts.sandbox === void 0 ? resolveProjectRoot(opts.cwd) : resolveSandboxRoot({ name: opts.sandbox });
|
|
2885
2902
|
}
|
|
2903
|
+
function isGitIgnored(root, path4) {
|
|
2904
|
+
const result = spawnSync3("git", ["check-ignore", "-q", "--no-index", path4], {
|
|
2905
|
+
cwd: root,
|
|
2906
|
+
stdio: "ignore"
|
|
2907
|
+
});
|
|
2908
|
+
return result.status === 0;
|
|
2909
|
+
}
|
|
2886
2910
|
function ignoreSecretsFile(root) {
|
|
2887
2911
|
if (!existsSync12(join7(root, ".git"))) return;
|
|
2912
|
+
if (isGitIgnored(root, ".env")) return;
|
|
2888
2913
|
appendGitignoreLines(root, [".env"]);
|
|
2889
2914
|
}
|
|
2890
2915
|
function secretKeyList() {
|
|
@@ -3129,7 +3154,7 @@ var HARNESS_CATALOG = [
|
|
|
3129
3154
|
installArgv: [
|
|
3130
3155
|
"bash",
|
|
3131
3156
|
"-lc",
|
|
3132
|
-
`curl -fsSL https://hermes-agent.nousresearch.com/install.sh | HERMES_INSTALL_DIR="${HARNESS_PREFIX_TOKEN}/lib/hermes-agent" bash -s -- --skip-setup --skip-browser &&
|
|
3157
|
+
`curl -fsSL https://hermes-agent.nousresearch.com/install.sh | HERMES_INSTALL_DIR="${HARNESS_PREFIX_TOKEN}/lib/hermes-agent" bash -s -- --skip-setup --skip-browser && "${HARNESS_PREFIX_TOKEN}/bin/hermes" pm install --extra slack --extra teams`
|
|
3133
3158
|
],
|
|
3134
3159
|
installUser: "sandbox",
|
|
3135
3160
|
verifyArgv: ["hermes", "--version"],
|
|
@@ -3744,7 +3769,7 @@ var RUNTIME_CATALOG = Object.freeze([
|
|
|
3744
3769
|
tier: "container",
|
|
3745
3770
|
state: "active",
|
|
3746
3771
|
provisionable: true,
|
|
3747
|
-
docsPath: "docs/runtimes/
|
|
3772
|
+
docsPath: "docs/runtimes/overview.md"
|
|
3748
3773
|
}),
|
|
3749
3774
|
Object.freeze({
|
|
3750
3775
|
id: "microsandbox",
|
|
@@ -3752,7 +3777,7 @@ var RUNTIME_CATALOG = Object.freeze([
|
|
|
3752
3777
|
tier: "microvm",
|
|
3753
3778
|
state: "planned",
|
|
3754
3779
|
provisionable: false,
|
|
3755
|
-
notProvisionableReason: (bin) => `microsandbox is not a provisionable runtime yet; see
|
|
3780
|
+
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\`.`,
|
|
3756
3781
|
docsPath: "docs/runtimes/microsandbox.md"
|
|
3757
3782
|
})
|
|
3758
3783
|
]);
|
|
@@ -3784,6 +3809,55 @@ function maybePrintStarPrompt(io, env = process.env, isTTY = process.stdout.isTT
|
|
|
3784
3809
|
}
|
|
3785
3810
|
}
|
|
3786
3811
|
|
|
3812
|
+
// src/commands/sandbox-list.ts
|
|
3813
|
+
async function entryStatus(root, name, run) {
|
|
3814
|
+
try {
|
|
3815
|
+
const target = resolveExecutionTarget({ projectRoot: root, container: name, run });
|
|
3816
|
+
return await target.status();
|
|
3817
|
+
} catch {
|
|
3818
|
+
return "unknown";
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
async function runSandboxList(opts, io) {
|
|
3822
|
+
const run = opts.run ?? spawnRunner;
|
|
3823
|
+
const rows = [];
|
|
3824
|
+
for (const name of listEntries()) {
|
|
3825
|
+
const root = entryRoot(name);
|
|
3826
|
+
const config = readAgroConfig(agroConfigPath(root));
|
|
3827
|
+
const checkout = configCheckout(config) ?? "-";
|
|
3828
|
+
rows.push({
|
|
3829
|
+
name,
|
|
3830
|
+
runtime: config.runtime ?? "docker",
|
|
3831
|
+
checkout,
|
|
3832
|
+
repo: checkout,
|
|
3833
|
+
status: await entryStatus(root, name, run)
|
|
3834
|
+
});
|
|
3835
|
+
}
|
|
3836
|
+
if (opts.json === true) {
|
|
3837
|
+
io.stdout(`${JSON.stringify(rows, null, 2)}
|
|
3838
|
+
`);
|
|
3839
|
+
return 0;
|
|
3840
|
+
}
|
|
3841
|
+
if (rows.length === 0) {
|
|
3842
|
+
io.stdout(
|
|
3843
|
+
`no sandbox is registered in ${registryRoot()} \u2014 create one with \`${opts.bin} sandbox install docker\`
|
|
3844
|
+
`
|
|
3845
|
+
);
|
|
3846
|
+
return 0;
|
|
3847
|
+
}
|
|
3848
|
+
const width = (pick) => Math.max(...rows.map((row) => pick(row).length));
|
|
3849
|
+
const nameWidth = width((row) => row.name);
|
|
3850
|
+
const runtimeWidth = width((row) => row.runtime);
|
|
3851
|
+
const statusWidth = width((row) => row.status);
|
|
3852
|
+
for (const row of rows) {
|
|
3853
|
+
io.stdout(
|
|
3854
|
+
`${row.name.padEnd(nameWidth)} ${row.runtime.padEnd(runtimeWidth)} ${row.status.padEnd(statusWidth)} ${row.checkout}
|
|
3855
|
+
`
|
|
3856
|
+
);
|
|
3857
|
+
}
|
|
3858
|
+
return 0;
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3787
3861
|
// src/commands/sandbox.ts
|
|
3788
3862
|
function hostTimezone() {
|
|
3789
3863
|
const tz = process.env.TZ;
|
|
@@ -4001,191 +4075,527 @@ async function runSandboxInstall(opts, io) {
|
|
|
4001
4075
|
}
|
|
4002
4076
|
return code;
|
|
4003
4077
|
}
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
}
|
|
4078
|
+
|
|
4079
|
+
// src/services/sandbox-upgrade.ts
|
|
4080
|
+
import { closeSync as closeSync2, existsSync as existsSync17, openSync as openSync2, readFileSync as readFileSync11, unlinkSync as unlinkSync5, writeSync } from "node:fs";
|
|
4081
|
+
import { join as join13 } from "node:path";
|
|
4082
|
+
function errorMessage(error) {
|
|
4083
|
+
return error instanceof Error ? error.message : String(error);
|
|
4011
4084
|
}
|
|
4012
|
-
|
|
4013
|
-
const
|
|
4014
|
-
const
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
const
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
checkout,
|
|
4023
|
-
repo: checkout,
|
|
4024
|
-
status: await entryStatus(root, name, run)
|
|
4025
|
-
});
|
|
4085
|
+
function conflictingDotenvImage(root, imageRef) {
|
|
4086
|
+
const rootEnv = join13(root, ".env");
|
|
4087
|
+
const fallbackEnv = join13(root, ".devcontainer", ".env");
|
|
4088
|
+
const file = existsSync17(rootEnv) ? rootEnv : fallbackEnv;
|
|
4089
|
+
if (!existsSync17(file)) return void 0;
|
|
4090
|
+
for (const line of readFileSync11(file, "utf8").split(/\r?\n/)) {
|
|
4091
|
+
const match = /^AGRO_SANDBOX_IMAGE=(.*)$/.exec(line);
|
|
4092
|
+
if (!match) continue;
|
|
4093
|
+
const value = match[1].trim().replace(/^(?:"(.*)"|'(.*)')$/, (_whole, double, single) => double ?? single ?? "");
|
|
4094
|
+
if (value !== imageRef) return file;
|
|
4026
4095
|
}
|
|
4027
|
-
|
|
4028
|
-
|
|
4096
|
+
return void 0;
|
|
4097
|
+
}
|
|
4098
|
+
async function runSandboxUpgrade(opts, io) {
|
|
4099
|
+
const prefix = `${opts.bin} sandbox upgrade:`;
|
|
4100
|
+
const version = parseReleaseVersion(opts.version);
|
|
4101
|
+
if (version === void 0) {
|
|
4102
|
+
io.stderr(`${prefix} invalid release version "${opts.version}"
|
|
4029
4103
|
`);
|
|
4030
|
-
return
|
|
4031
|
-
}
|
|
4032
|
-
if (rows.length === 0) {
|
|
4033
|
-
io.stdout(
|
|
4034
|
-
`no sandbox is registered in ${registryRoot()} \u2014 create one with \`${opts.bin} sandbox install docker\`
|
|
4035
|
-
`
|
|
4036
|
-
);
|
|
4037
|
-
return 0;
|
|
4038
|
-
}
|
|
4039
|
-
const width = (pick) => Math.max(...rows.map((row) => pick(row).length));
|
|
4040
|
-
const nameWidth = width((row) => row.name);
|
|
4041
|
-
const runtimeWidth = width((row) => row.runtime);
|
|
4042
|
-
const statusWidth = width((row) => row.status);
|
|
4043
|
-
for (const row of rows) {
|
|
4044
|
-
io.stdout(
|
|
4045
|
-
`${row.name.padEnd(nameWidth)} ${row.runtime.padEnd(runtimeWidth)} ${row.status.padEnd(statusWidth)} ${row.checkout}
|
|
4046
|
-
`
|
|
4047
|
-
);
|
|
4104
|
+
return 1;
|
|
4048
4105
|
}
|
|
4049
|
-
|
|
4050
|
-
}
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
import { existsSync as existsSync19 } from "node:fs";
|
|
4054
|
-
import { homedir as homedir3 } from "node:os";
|
|
4055
|
-
import { delimiter as delimiter2, join as join15 } from "node:path";
|
|
4056
|
-
|
|
4057
|
-
// src/lib/docs.ts
|
|
4058
|
-
var SOURCE_DOCS_BASE = "https://github.com/mifunedev/agro/blob/main/";
|
|
4059
|
-
function sourceDocsUrl(docsPath) {
|
|
4060
|
-
const segments = docsPath.split("/");
|
|
4061
|
-
if (segments[0] !== "docs" || segments.length < 2 || segments.some((segment) => segment === "" || segment === "." || segment === "..") || docsPath.includes("\\") || docsPath.includes("?") || docsPath.includes("#")) {
|
|
4062
|
-
throw new Error(`source docs path must be a normalized docs/ path: ${docsPath}`);
|
|
4106
|
+
if (runningInsideSandbox()) {
|
|
4107
|
+
io.stderr(`${prefix} host-only \u2014 run this command on the host
|
|
4108
|
+
`);
|
|
4109
|
+
return 1;
|
|
4063
4110
|
}
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
var HOST_CONFIG_FILE = "config.json";
|
|
4072
|
-
var WORKSPACES_SUBDIR = "workspaces";
|
|
4073
|
-
var DEFAULT_WORKSPACE_NAME = "harness";
|
|
4074
|
-
function hostStateHome(env, home) {
|
|
4075
|
-
return resolveUserStateHome(env, home);
|
|
4076
|
-
}
|
|
4077
|
-
function hostConfigPath(env, home) {
|
|
4078
|
-
return join13(hostStateHome(env, home), HOST_CONFIG_FILE);
|
|
4079
|
-
}
|
|
4080
|
-
function assertWorkspaceName(name) {
|
|
4081
|
-
if (!SANDBOX_NAME_PATTERN.test(name)) {
|
|
4082
|
-
throw new Error(
|
|
4083
|
-
`invalid workspace name "${name}" \u2014 use lowercase letters, digits and dashes, starting with a letter or digit`
|
|
4084
|
-
);
|
|
4111
|
+
let root;
|
|
4112
|
+
try {
|
|
4113
|
+
root = entryRoot(opts.name);
|
|
4114
|
+
} catch (error) {
|
|
4115
|
+
io.stderr(`${prefix} ${errorMessage(error)}
|
|
4116
|
+
`);
|
|
4117
|
+
return 1;
|
|
4085
4118
|
}
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
assertWorkspaceName(name);
|
|
4092
|
-
return join13(workspacesRoot(env, home), name);
|
|
4093
|
-
}
|
|
4094
|
-
function defaultHarnessRoot(env, home) {
|
|
4095
|
-
return join13(workspacesRoot(env, home), DEFAULT_WORKSPACE_NAME);
|
|
4096
|
-
}
|
|
4097
|
-
function validateHostConfig(value) {
|
|
4098
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4099
|
-
throw new Error(`${HOST_CONFIG_FILE}: must contain a JSON object`);
|
|
4119
|
+
const configFile = agroConfigPath(root);
|
|
4120
|
+
if (!existsSync17(configFile)) {
|
|
4121
|
+
io.stderr(`${prefix} no sandbox entry named "${opts.name}"
|
|
4122
|
+
`);
|
|
4123
|
+
return 1;
|
|
4100
4124
|
}
|
|
4101
|
-
const
|
|
4102
|
-
|
|
4103
|
-
|
|
4125
|
+
const lock = join13(root, ".sandbox-upgrade.lock");
|
|
4126
|
+
let fd;
|
|
4127
|
+
try {
|
|
4128
|
+
fd = openSync2(lock, "wx", 384);
|
|
4129
|
+
} catch (error) {
|
|
4130
|
+
const message = error instanceof Error && "code" in error && error.code === "EEXIST" ? `upgrade already in progress for ${opts.name}; lock: ${lock}. Confirm no upgrade process owns this entry before removing that lock and retrying` : errorMessage(error);
|
|
4131
|
+
io.stderr(`${prefix} ${message}
|
|
4132
|
+
`);
|
|
4133
|
+
return 1;
|
|
4104
4134
|
}
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4135
|
+
try {
|
|
4136
|
+
writeSync(fd, `${process.pid}
|
|
4137
|
+
`);
|
|
4138
|
+
const config = readAgroConfig(configFile);
|
|
4139
|
+
if (config.image?.mode !== "image") {
|
|
4140
|
+
io.stderr(`${prefix} ${opts.name} requires image.mode "image"
|
|
4141
|
+
`);
|
|
4142
|
+
return 1;
|
|
4109
4143
|
}
|
|
4110
|
-
|
|
4111
|
-
|
|
4144
|
+
const imageRef = officialImageRef(version);
|
|
4145
|
+
if (process.env.AGRO_SANDBOX_IMAGE !== void 0 && process.env.AGRO_SANDBOX_IMAGE !== "" && process.env.AGRO_SANDBOX_IMAGE !== imageRef) {
|
|
4146
|
+
io.stderr(`${prefix} conflicting AGRO_SANDBOX_IMAGE in the host environment
|
|
4147
|
+
`);
|
|
4148
|
+
return 1;
|
|
4112
4149
|
}
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
return { ...record, version: 1 };
|
|
4119
|
-
}
|
|
4120
|
-
function absoluteField(value, path4) {
|
|
4121
|
-
if (typeof value !== "string" || value === "") {
|
|
4122
|
-
throw fieldError2(path4, "must be a non-empty string");
|
|
4123
|
-
}
|
|
4124
|
-
if (!isAbsolute(value)) throw fieldError2(path4, "must be an absolute path");
|
|
4125
|
-
return value;
|
|
4126
|
-
}
|
|
4127
|
-
function validateReceipts(value, field, noun) {
|
|
4128
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4129
|
-
throw fieldError2(field, `must be a JSON object keyed by ${noun} id`);
|
|
4130
|
-
}
|
|
4131
|
-
for (const [id, receipt] of Object.entries(value)) {
|
|
4132
|
-
if (id === "") throw fieldError2(field, `must use a non-empty ${noun} id as each key`);
|
|
4133
|
-
const at = `${field}.${id}`;
|
|
4134
|
-
if (!receipt || typeof receipt !== "object" || Array.isArray(receipt)) {
|
|
4135
|
-
throw fieldError2(at, "must be a JSON object");
|
|
4150
|
+
const dotenv = conflictingDotenvImage(root, imageRef);
|
|
4151
|
+
if (dotenv !== void 0) {
|
|
4152
|
+
io.stderr(`${prefix} conflicting AGRO_SANDBOX_IMAGE in ${dotenv}
|
|
4153
|
+
`);
|
|
4154
|
+
return 1;
|
|
4136
4155
|
}
|
|
4137
|
-
const
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4156
|
+
const run = opts.run ?? spawnRunner;
|
|
4157
|
+
let priorRef = config.image.ref;
|
|
4158
|
+
if (!priorRef) {
|
|
4159
|
+
const name = configuredContainerName(root) ?? DEFAULT_CONTAINER_NAME;
|
|
4160
|
+
const inspected = run("docker", ["inspect", "-f", "{{.Config.Image}}", name], { stdio: "capture" });
|
|
4161
|
+
priorRef = inspected.status === 0 && !inspected.error ? inspected.stdout?.trim() : void 0;
|
|
4162
|
+
if (!priorRef) {
|
|
4163
|
+
io.stderr(`${prefix} cannot determine previous image for ${name}; no changes made
|
|
4164
|
+
`);
|
|
4165
|
+
return 1;
|
|
4166
|
+
}
|
|
4142
4167
|
}
|
|
4143
|
-
|
|
4144
|
-
|
|
4168
|
+
const apply = (ref) => runSandbox({ bin: opts.bin, cwd: root, imageRef: ref, run }, io);
|
|
4169
|
+
let failure;
|
|
4170
|
+
try {
|
|
4171
|
+
const code = await apply(imageRef);
|
|
4172
|
+
if (code !== 0) failure = `exit ${code}`;
|
|
4173
|
+
} catch (error) {
|
|
4174
|
+
failure = errorMessage(error);
|
|
4145
4175
|
}
|
|
4146
|
-
if (
|
|
4147
|
-
|
|
4176
|
+
if (failure === void 0) {
|
|
4177
|
+
try {
|
|
4178
|
+
(opts.writeConfig ?? writeAgroConfig)(root, { ...config, image: { ...config.image, ref: imageRef } });
|
|
4179
|
+
return 0;
|
|
4180
|
+
} catch (error) {
|
|
4181
|
+
failure = `config persistence failed (${errorMessage(error)})`;
|
|
4182
|
+
}
|
|
4183
|
+
} else {
|
|
4184
|
+
failure = `provisioning failed (${failure})`;
|
|
4148
4185
|
}
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
function readHostConfig(env, home) {
|
|
4152
|
-
const path4 = hostConfigPath(env, home);
|
|
4153
|
-
if (!existsSync17(path4)) return { version: 1 };
|
|
4154
|
-
let raw;
|
|
4155
|
-
try {
|
|
4156
|
-
raw = readFileSync11(path4, "utf8");
|
|
4157
|
-
} catch (error) {
|
|
4158
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
4159
|
-
throw new Error(`could not read ${path4}: ${detail}`);
|
|
4160
|
-
}
|
|
4161
|
-
let parsed;
|
|
4162
|
-
try {
|
|
4163
|
-
parsed = JSON.parse(raw);
|
|
4164
|
-
} catch {
|
|
4165
|
-
throw new Error(`${basename5(path4)} is not valid JSON: ${path4}`);
|
|
4166
|
-
}
|
|
4167
|
-
return validateHostConfig(parsed);
|
|
4168
|
-
}
|
|
4169
|
-
function writeHostConfig(config, env, home) {
|
|
4170
|
-
const path4 = hostConfigPath(env, home);
|
|
4171
|
-
const validated = validateHostConfig({ ...config, version: 1 });
|
|
4172
|
-
const body = `${JSON.stringify(validated, null, 2)}
|
|
4173
|
-
`;
|
|
4174
|
-
const tmp = `${path4}.tmp.${process.pid}`;
|
|
4175
|
-
mkdirSync9(dirname8(path4), { recursive: true });
|
|
4176
|
-
try {
|
|
4177
|
-
writeFileSync9(tmp, body, { mode: HOST_CONFIG_MODE, encoding: "utf8" });
|
|
4178
|
-
renameSync4(tmp, path4);
|
|
4179
|
-
} catch (error) {
|
|
4186
|
+
io.stderr(`${prefix} ${failure}; restoring previous image
|
|
4187
|
+
`);
|
|
4180
4188
|
try {
|
|
4181
|
-
|
|
4182
|
-
|
|
4189
|
+
const code = await apply(priorRef);
|
|
4190
|
+
if (code !== 0) io.stderr(`${prefix} restoration failed (exit ${code})
|
|
4191
|
+
`);
|
|
4192
|
+
} catch (error) {
|
|
4193
|
+
io.stderr(`${prefix} restoration failed (${errorMessage(error)})
|
|
4194
|
+
`);
|
|
4183
4195
|
}
|
|
4184
|
-
|
|
4196
|
+
return 1;
|
|
4197
|
+
} catch (error) {
|
|
4198
|
+
io.stderr(`${prefix} ${errorMessage(error)}
|
|
4199
|
+
`);
|
|
4200
|
+
return 1;
|
|
4201
|
+
} finally {
|
|
4202
|
+
closeSync2(fd);
|
|
4203
|
+
unlinkSync5(lock);
|
|
4185
4204
|
}
|
|
4186
4205
|
}
|
|
4187
|
-
|
|
4188
|
-
|
|
4206
|
+
|
|
4207
|
+
// src/controllers/sandbox.ts
|
|
4208
|
+
function isHelpFlag(arg) {
|
|
4209
|
+
return arg === "--help" || arg === "-h" || arg === "help";
|
|
4210
|
+
}
|
|
4211
|
+
function runtimeLines() {
|
|
4212
|
+
const width = Math.max(...RUNTIME_CATALOG.map((r) => r.id.length));
|
|
4213
|
+
return RUNTIME_CATALOG.map(
|
|
4214
|
+
(r) => ` ${r.id.padEnd(width)} ${r.provisionable ? "provisionable" : "planned"}`
|
|
4215
|
+
).join("\n");
|
|
4216
|
+
}
|
|
4217
|
+
function printSandboxHelp(bin = AGRO_PRODUCT.bin) {
|
|
4218
|
+
process.stdout.write(`${bin} sandbox \u2014 Create and list sandboxes
|
|
4219
|
+
|
|
4220
|
+
Usage:
|
|
4221
|
+
${bin} sandbox install <runtime> [--name <name>] [--checkout <dir>]
|
|
4222
|
+
[--home-mount <dir>] [--yes]
|
|
4223
|
+
[--version <X.Y.Z> | --image[=<ref>]]
|
|
4224
|
+
[--no-build] [--print-argv]
|
|
4225
|
+
${bin} sandbox list [--json]
|
|
4226
|
+
${bin} sandbox upgrade <name> --version <X.Y.Z>
|
|
4227
|
+
|
|
4228
|
+
\`install\` writes a sandbox entry under \${${stateNames(bin).envPrefix}HOME:-~/${stateNames(bin).userStateDir}}/sandboxes/<name>/,
|
|
4229
|
+
materialises the compose files and the compose wrapper into it, then starts the
|
|
4230
|
+
container. It needs no project checkout and runs from any directory. Without
|
|
4231
|
+
--checkout the sandbox runs the prebuilt image; with --checkout <dir> that
|
|
4232
|
+
checkout is bound at /home/sandbox/harness; the sandbox builds locally only
|
|
4233
|
+
when that directory holds .devcontainer/Dockerfile.
|
|
4234
|
+
|
|
4235
|
+
On a terminal without --yes it asks for the sandbox name, the timezone, the git
|
|
4236
|
+
identity, SSH (with its host port), the host Docker socket, and the host path
|
|
4237
|
+
for /home/sandbox. With --checkout it seeds those answers from that
|
|
4238
|
+
checkout's ${stateNames(bin).configFile}.
|
|
4239
|
+
|
|
4240
|
+
Flags:
|
|
4241
|
+
--name <name> Registry entry name (default: the lowest free ${DEFAULT_NAME_PREFIX}<n>)
|
|
4242
|
+
--checkout <dir> Bind this checkout into the sandbox and seed the defaults
|
|
4243
|
+
from its ${stateNames(bin).configFile}. The sandbox builds
|
|
4244
|
+
locally only when that directory holds
|
|
4245
|
+
.devcontainer/Dockerfile
|
|
4246
|
+
--repo <dir> Deprecated alias for --checkout
|
|
4247
|
+
--home-mount <dir>
|
|
4248
|
+
Bind this host directory at /home/sandbox instead of the
|
|
4249
|
+
Docker-managed volume. Relative paths resolve against the
|
|
4250
|
+
working directory. The directory is created when absent
|
|
4251
|
+
--yes Non-interactive: keep every default and ask nothing
|
|
4252
|
+
--version <X.Y.Z>
|
|
4253
|
+
Run the official release image ghcr.io/mifunedev/agro:<X.Y.Z>
|
|
4254
|
+
(implies --image). A leading v and a -<channel>.<n>
|
|
4255
|
+
pre-release suffix are accepted
|
|
4256
|
+
--image Run the prebuilt image instead of building (implies
|
|
4257
|
+
--no-build). The ref resolves first-match: --version or
|
|
4258
|
+
--image=<ref> > ${stateNames(bin).envPrefix}SANDBOX_IMAGE >
|
|
4259
|
+
${stateNames(bin).configFile} image.ref > ${officialImageRef(AGRO_VERSION)}
|
|
4260
|
+
--image=<ref> Run a custom image (implies --image); conflicts with --version
|
|
4261
|
+
--no-build Suppress the local build and reuse an existing image
|
|
4262
|
+
--print-argv Print the docker compose argv that would run, then exit
|
|
4263
|
+
without writing an entry
|
|
4264
|
+
--json Machine-readable output (list)
|
|
4265
|
+
|
|
4266
|
+
Runtimes:
|
|
4267
|
+
${runtimeLines()}
|
|
4268
|
+
|
|
4269
|
+
Next: ${bin} shell <name>
|
|
4270
|
+
`);
|
|
4271
|
+
}
|
|
4272
|
+
var SANDBOX_VALUE_FLAGS = {
|
|
4273
|
+
"--name": "name",
|
|
4274
|
+
"--checkout": "checkout",
|
|
4275
|
+
"--repo": "checkout",
|
|
4276
|
+
"--home-mount": "homeMount"
|
|
4277
|
+
};
|
|
4278
|
+
function parseSandboxArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
4279
|
+
const args = {
|
|
4280
|
+
help: false,
|
|
4281
|
+
yes: false,
|
|
4282
|
+
image: false,
|
|
4283
|
+
noBuild: false,
|
|
4284
|
+
printArgv: false,
|
|
4285
|
+
json: false
|
|
4286
|
+
};
|
|
4287
|
+
if (rest.length === 0 || isHelpFlag(rest[0])) {
|
|
4288
|
+
return { ok: true, args: { ...args, help: true } };
|
|
4289
|
+
}
|
|
4290
|
+
const [head, ...tail] = rest;
|
|
4291
|
+
if (head === "upgrade") {
|
|
4292
|
+
if (tail.length === 1 && isHelpFlag(tail[0])) {
|
|
4293
|
+
return { ok: true, args: { ...args, subcommand: "upgrade", help: true } };
|
|
4294
|
+
}
|
|
4295
|
+
const [name, flag, value, ...extra] = tail;
|
|
4296
|
+
if (name === void 0 || name === "" || name.startsWith("-")) {
|
|
4297
|
+
return { ok: false, error: `${bin} sandbox upgrade: a name is required` };
|
|
4298
|
+
}
|
|
4299
|
+
if (flag === void 0) {
|
|
4300
|
+
return { ok: false, error: `${bin} sandbox upgrade: --version is required` };
|
|
4301
|
+
}
|
|
4302
|
+
if (flag !== "--version") {
|
|
4303
|
+
return { ok: false, error: `${bin} sandbox upgrade: ${flag.startsWith("-") ? "unknown flag" : "unexpected argument"} "${flag}"` };
|
|
4304
|
+
}
|
|
4305
|
+
if (value === void 0 || value === "") {
|
|
4306
|
+
return { ok: false, error: `${bin} sandbox upgrade: --version requires a value` };
|
|
4307
|
+
}
|
|
4308
|
+
if (value.startsWith("-")) {
|
|
4309
|
+
return { ok: false, error: `${bin} sandbox upgrade: unknown flag "${value}"` };
|
|
4310
|
+
}
|
|
4311
|
+
const version2 = parseReleaseVersion(value);
|
|
4312
|
+
if (version2 === void 0) {
|
|
4313
|
+
return { ok: false, error: `${bin} sandbox upgrade: --version "${value}" is not a release version \u2014 expected X.Y.Z` };
|
|
4314
|
+
}
|
|
4315
|
+
if (extra.length > 0) {
|
|
4316
|
+
const token = extra[0];
|
|
4317
|
+
return { ok: false, error: `${bin} sandbox upgrade: ${token.startsWith("-") && token !== "--version" ? "unknown flag" : "unexpected argument"} "${token}"` };
|
|
4318
|
+
}
|
|
4319
|
+
return { ok: true, args: { ...args, subcommand: "upgrade", name, version: version2 } };
|
|
4320
|
+
}
|
|
4321
|
+
if (head !== "install" && head !== "list") {
|
|
4322
|
+
return {
|
|
4323
|
+
ok: false,
|
|
4324
|
+
error: `${bin} sandbox: unknown subcommand "${head}" \u2014 expected install or list`,
|
|
4325
|
+
showHelp: true
|
|
4326
|
+
};
|
|
4327
|
+
}
|
|
4328
|
+
args.subcommand = head;
|
|
4329
|
+
if (isHelpFlag(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
4330
|
+
const positionals = [];
|
|
4331
|
+
const checkoutSpellings = /* @__PURE__ */ new Set();
|
|
4332
|
+
let version;
|
|
4333
|
+
for (let i = 0; i < tail.length; i++) {
|
|
4334
|
+
const token = tail[i];
|
|
4335
|
+
const valueFlag = SANDBOX_VALUE_FLAGS[token];
|
|
4336
|
+
if (valueFlag !== void 0) {
|
|
4337
|
+
const value = tail[i + 1];
|
|
4338
|
+
if (value === void 0) {
|
|
4339
|
+
return { ok: false, error: `${bin} sandbox ${head}: ${token} requires a value` };
|
|
4340
|
+
}
|
|
4341
|
+
if (valueFlag === "checkout") checkoutSpellings.add(token);
|
|
4342
|
+
args[valueFlag] = value;
|
|
4343
|
+
i++;
|
|
4344
|
+
} else if (token === "--yes") {
|
|
4345
|
+
args.yes = true;
|
|
4346
|
+
} else if (token === "--no-build") {
|
|
4347
|
+
args.noBuild = true;
|
|
4348
|
+
} else if (token === "--print-argv") {
|
|
4349
|
+
args.printArgv = true;
|
|
4350
|
+
} else if (token === "--image") {
|
|
4351
|
+
args.image = true;
|
|
4352
|
+
} else if (token.startsWith("--image=")) {
|
|
4353
|
+
const ref = token.slice("--image=".length);
|
|
4354
|
+
if (ref === "") {
|
|
4355
|
+
return { ok: false, error: `${bin} sandbox: --image=<ref> requires a non-empty image ref` };
|
|
4356
|
+
}
|
|
4357
|
+
args.image = true;
|
|
4358
|
+
args.imageRef = ref;
|
|
4359
|
+
} else if (token === "--version" || token.startsWith("--version=")) {
|
|
4360
|
+
const value = token === "--version" ? tail[++i] : token.slice("--version=".length);
|
|
4361
|
+
if (value === void 0 || value === "") {
|
|
4362
|
+
return { ok: false, error: `${bin} sandbox ${head}: --version requires a value` };
|
|
4363
|
+
}
|
|
4364
|
+
version = parseReleaseVersion(value);
|
|
4365
|
+
if (version === void 0) {
|
|
4366
|
+
return {
|
|
4367
|
+
ok: false,
|
|
4368
|
+
error: `${bin} sandbox ${head}: --version "${value}" is not a release version \u2014 expected X.Y.Z`
|
|
4369
|
+
};
|
|
4370
|
+
}
|
|
4371
|
+
} else if (token === "--json") {
|
|
4372
|
+
args.json = true;
|
|
4373
|
+
} else if (token.startsWith("-")) {
|
|
4374
|
+
return { ok: false, error: `${bin} sandbox ${head}: unknown flag "${token}"` };
|
|
4375
|
+
} else {
|
|
4376
|
+
positionals.push(token);
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
if (checkoutSpellings.has("--checkout") && checkoutSpellings.has("--repo")) {
|
|
4380
|
+
return {
|
|
4381
|
+
ok: false,
|
|
4382
|
+
error: `${bin} sandbox ${head}: --checkout conflicts with --repo \u2014 pass exactly one, and prefer --checkout`
|
|
4383
|
+
};
|
|
4384
|
+
}
|
|
4385
|
+
if (version !== void 0) {
|
|
4386
|
+
if (args.imageRef !== void 0) {
|
|
4387
|
+
return {
|
|
4388
|
+
ok: false,
|
|
4389
|
+
error: `${bin} sandbox ${head}: --version conflicts with --image=<ref> \u2014 pass --version for an official release or --image=<ref> for a custom image`
|
|
4390
|
+
};
|
|
4391
|
+
}
|
|
4392
|
+
args.image = true;
|
|
4393
|
+
args.imageRef = officialImageRef(version);
|
|
4394
|
+
}
|
|
4395
|
+
if (head === "list") {
|
|
4396
|
+
if (positionals.length > 0) {
|
|
4397
|
+
return { ok: false, error: `${bin} sandbox list: unexpected argument "${positionals[0]}"` };
|
|
4398
|
+
}
|
|
4399
|
+
return { ok: true, args };
|
|
4400
|
+
}
|
|
4401
|
+
if (positionals.length === 0) {
|
|
4402
|
+
return {
|
|
4403
|
+
ok: false,
|
|
4404
|
+
error: `${bin} sandbox install: a runtime is required, e.g. \`${bin} sandbox install docker\``,
|
|
4405
|
+
showHelp: true
|
|
4406
|
+
};
|
|
4407
|
+
}
|
|
4408
|
+
if (positionals.length > 1) {
|
|
4409
|
+
return { ok: false, error: `${bin} sandbox install: unexpected argument "${positionals[1]}"` };
|
|
4410
|
+
}
|
|
4411
|
+
args.runtime = positionals[0];
|
|
4412
|
+
return { ok: true, args };
|
|
4413
|
+
}
|
|
4414
|
+
async function runSandboxCommand(rest, bin) {
|
|
4415
|
+
const parsed = parseSandboxArgs(rest, bin);
|
|
4416
|
+
if (!parsed.ok) {
|
|
4417
|
+
process.stderr.write(`${parsed.error}
|
|
4418
|
+
`);
|
|
4419
|
+
if (parsed.showHelp) printSandboxHelp(bin);
|
|
4420
|
+
return 1;
|
|
4421
|
+
}
|
|
4422
|
+
const a = parsed.args;
|
|
4423
|
+
if (a.help) {
|
|
4424
|
+
printSandboxHelp(bin);
|
|
4425
|
+
return a.subcommand === void 0 ? 1 : 0;
|
|
4426
|
+
}
|
|
4427
|
+
const io = {
|
|
4428
|
+
stdout: (s) => process.stdout.write(s),
|
|
4429
|
+
stderr: (s) => process.stderr.write(s)
|
|
4430
|
+
};
|
|
4431
|
+
if (a.subcommand === "list") return await runSandboxList({ bin, json: a.json }, io);
|
|
4432
|
+
if (a.subcommand === "upgrade") {
|
|
4433
|
+
return await runSandboxUpgrade({ bin, name: a.name, version: a.version }, io);
|
|
4434
|
+
}
|
|
4435
|
+
return await runSandboxInstall(
|
|
4436
|
+
{
|
|
4437
|
+
bin,
|
|
4438
|
+
runtime: a.runtime,
|
|
4439
|
+
...a.name !== void 0 ? { name: a.name } : {},
|
|
4440
|
+
...a.checkout !== void 0 ? { checkout: a.checkout } : {},
|
|
4441
|
+
...a.homeMount !== void 0 ? { homeMount: a.homeMount } : {},
|
|
4442
|
+
yes: a.yes,
|
|
4443
|
+
image: a.image,
|
|
4444
|
+
...a.imageRef !== void 0 ? { imageRef: a.imageRef } : {},
|
|
4445
|
+
noBuild: a.noBuild,
|
|
4446
|
+
printArgv: a.printArgv
|
|
4447
|
+
},
|
|
4448
|
+
io
|
|
4449
|
+
);
|
|
4450
|
+
}
|
|
4451
|
+
|
|
4452
|
+
// src/commands/harness.ts
|
|
4453
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
4454
|
+
import { homedir as homedir3 } from "node:os";
|
|
4455
|
+
import { delimiter as delimiter2, join as join16 } from "node:path";
|
|
4456
|
+
|
|
4457
|
+
// src/lib/docs.ts
|
|
4458
|
+
var SOURCE_DOCS_BASE = "https://github.com/mifunedev/agro/blob/main/";
|
|
4459
|
+
function sourceDocsUrl(docsPath) {
|
|
4460
|
+
const segments = docsPath.split("/");
|
|
4461
|
+
if (segments[0] !== "docs" || segments.length < 2 || segments.some((segment) => segment === "" || segment === "." || segment === "..") || docsPath.includes("\\") || docsPath.includes("?") || docsPath.includes("#")) {
|
|
4462
|
+
throw new Error(`source docs path must be a normalized docs/ path: ${docsPath}`);
|
|
4463
|
+
}
|
|
4464
|
+
return new URL(segments.map(encodeURIComponent).join("/"), SOURCE_DOCS_BASE).toString();
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4467
|
+
// src/lib/host-config.ts
|
|
4468
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync12, renameSync as renameSync4, unlinkSync as unlinkSync6, writeFileSync as writeFileSync9 } from "node:fs";
|
|
4469
|
+
import { basename as basename5, dirname as dirname8, isAbsolute, join as join14, resolve as resolve8 } from "node:path";
|
|
4470
|
+
var HOST_CONFIG_MODE = 420;
|
|
4471
|
+
var HOST_CONFIG_FILE = "config.json";
|
|
4472
|
+
var WORKSPACES_SUBDIR = "workspaces";
|
|
4473
|
+
var DEFAULT_WORKSPACE_NAME = "harness";
|
|
4474
|
+
function isRootToolReceipt(receipt) {
|
|
4475
|
+
return receipt.scope === "root";
|
|
4476
|
+
}
|
|
4477
|
+
function hostStateHome(env, home) {
|
|
4478
|
+
return resolveUserStateHome(env, home);
|
|
4479
|
+
}
|
|
4480
|
+
function hostConfigPath(env, home) {
|
|
4481
|
+
return join14(hostStateHome(env, home), HOST_CONFIG_FILE);
|
|
4482
|
+
}
|
|
4483
|
+
function assertWorkspaceName(name) {
|
|
4484
|
+
if (!SANDBOX_NAME_PATTERN.test(name)) {
|
|
4485
|
+
throw new Error(
|
|
4486
|
+
`invalid workspace name "${name}" \u2014 use lowercase letters, digits and dashes, starting with a letter or digit`
|
|
4487
|
+
);
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
function workspacesRoot(env, home) {
|
|
4491
|
+
return join14(hostStateHome(env, home), WORKSPACES_SUBDIR);
|
|
4492
|
+
}
|
|
4493
|
+
function workspaceRoot(name, env, home) {
|
|
4494
|
+
assertWorkspaceName(name);
|
|
4495
|
+
return join14(workspacesRoot(env, home), name);
|
|
4496
|
+
}
|
|
4497
|
+
function defaultHarnessRoot(env, home) {
|
|
4498
|
+
return join14(workspacesRoot(env, home), DEFAULT_WORKSPACE_NAME);
|
|
4499
|
+
}
|
|
4500
|
+
function validateHostConfig(value) {
|
|
4501
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4502
|
+
throw new Error(`${HOST_CONFIG_FILE}: must contain a JSON object`);
|
|
4503
|
+
}
|
|
4504
|
+
const record = value;
|
|
4505
|
+
if (record.version !== void 0 && record.version !== 1) {
|
|
4506
|
+
throw fieldError2("version", "must be 1");
|
|
4507
|
+
}
|
|
4508
|
+
const harnessRoot = record.harnessRoot;
|
|
4509
|
+
if (harnessRoot !== void 0) {
|
|
4510
|
+
if (typeof harnessRoot !== "string" || harnessRoot === "") {
|
|
4511
|
+
throw fieldError2("harnessRoot", "must be a non-empty string");
|
|
4512
|
+
}
|
|
4513
|
+
if (!isAbsolute(harnessRoot)) {
|
|
4514
|
+
throw fieldError2("harnessRoot", "must be an absolute path");
|
|
4515
|
+
}
|
|
4516
|
+
}
|
|
4517
|
+
const hostHarnesses = record.hostHarnesses;
|
|
4518
|
+
if (hostHarnesses !== void 0) validateReceipts(hostHarnesses, "hostHarnesses", "harness");
|
|
4519
|
+
const hostTools = record.hostTools;
|
|
4520
|
+
if (hostTools !== void 0) validateReceipts(hostTools, "hostTools", "tool", true);
|
|
4521
|
+
return { ...record, version: 1 };
|
|
4522
|
+
}
|
|
4523
|
+
function absoluteField(value, path4) {
|
|
4524
|
+
if (typeof value !== "string" || value === "") {
|
|
4525
|
+
throw fieldError2(path4, "must be a non-empty string");
|
|
4526
|
+
}
|
|
4527
|
+
if (!isAbsolute(value)) throw fieldError2(path4, "must be an absolute path");
|
|
4528
|
+
return value;
|
|
4529
|
+
}
|
|
4530
|
+
function validateReceipts(value, field, noun, allowRootScope = false) {
|
|
4531
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4532
|
+
throw fieldError2(field, `must be a JSON object keyed by ${noun} id`);
|
|
4533
|
+
}
|
|
4534
|
+
for (const [id, receipt] of Object.entries(value)) {
|
|
4535
|
+
if (id === "") throw fieldError2(field, `must use a non-empty ${noun} id as each key`);
|
|
4536
|
+
const at = `${field}.${id}`;
|
|
4537
|
+
if (!receipt || typeof receipt !== "object" || Array.isArray(receipt)) {
|
|
4538
|
+
throw fieldError2(at, "must be a JSON object");
|
|
4539
|
+
}
|
|
4540
|
+
const entry = receipt;
|
|
4541
|
+
if (allowRootScope && entry.scope !== void 0) {
|
|
4542
|
+
if (entry.scope !== "root") throw fieldError2(`${at}.scope`, 'must be "root" when present');
|
|
4543
|
+
for (const key of ["prefix", "binPath"]) {
|
|
4544
|
+
if (entry[key] !== void 0) throw fieldError2(`${at}.${key}`, 'must be absent when scope is "root"');
|
|
4545
|
+
}
|
|
4546
|
+
} else {
|
|
4547
|
+
absoluteField(entry.prefix, `${at}.prefix`);
|
|
4548
|
+
absoluteField(entry.binPath, `${at}.binPath`);
|
|
4549
|
+
}
|
|
4550
|
+
if (typeof entry.binary !== "string" || entry.binary === "") {
|
|
4551
|
+
throw fieldError2(`${at}.binary`, "must be a non-empty string");
|
|
4552
|
+
}
|
|
4553
|
+
if (typeof entry.installedAt !== "string" || entry.installedAt === "") {
|
|
4554
|
+
throw fieldError2(`${at}.installedAt`, "must be a non-empty string");
|
|
4555
|
+
}
|
|
4556
|
+
if (entry.workspaceRoot !== void 0) {
|
|
4557
|
+
absoluteField(entry.workspaceRoot, `${at}.workspaceRoot`);
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
function readHostConfig(env, home) {
|
|
4562
|
+
const path4 = hostConfigPath(env, home);
|
|
4563
|
+
if (!existsSync18(path4)) return { version: 1 };
|
|
4564
|
+
let raw;
|
|
4565
|
+
try {
|
|
4566
|
+
raw = readFileSync12(path4, "utf8");
|
|
4567
|
+
} catch (error) {
|
|
4568
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
4569
|
+
throw new Error(`could not read ${path4}: ${detail}`);
|
|
4570
|
+
}
|
|
4571
|
+
let parsed;
|
|
4572
|
+
try {
|
|
4573
|
+
parsed = JSON.parse(raw);
|
|
4574
|
+
} catch {
|
|
4575
|
+
throw new Error(`${basename5(path4)} is not valid JSON: ${path4}`);
|
|
4576
|
+
}
|
|
4577
|
+
return validateHostConfig(parsed);
|
|
4578
|
+
}
|
|
4579
|
+
function writeHostConfig(config, env, home) {
|
|
4580
|
+
const path4 = hostConfigPath(env, home);
|
|
4581
|
+
const validated = validateHostConfig({ ...config, version: 1 });
|
|
4582
|
+
const body = `${JSON.stringify(validated, null, 2)}
|
|
4583
|
+
`;
|
|
4584
|
+
const tmp = `${path4}.tmp.${process.pid}`;
|
|
4585
|
+
mkdirSync9(dirname8(path4), { recursive: true });
|
|
4586
|
+
try {
|
|
4587
|
+
writeFileSync9(tmp, body, { mode: HOST_CONFIG_MODE, encoding: "utf8" });
|
|
4588
|
+
renameSync4(tmp, path4);
|
|
4589
|
+
} catch (error) {
|
|
4590
|
+
try {
|
|
4591
|
+
unlinkSync6(tmp);
|
|
4592
|
+
} catch {
|
|
4593
|
+
}
|
|
4594
|
+
throw error;
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
function resolveHarnessRoot(explicit, env, home) {
|
|
4598
|
+
if (typeof explicit === "string" && explicit !== "") return resolve8(explicit);
|
|
4189
4599
|
const configured = readHostConfig(env, home).harnessRoot;
|
|
4190
4600
|
if (typeof configured === "string" && configured !== "") return resolve8(configured);
|
|
4191
4601
|
return defaultHarnessRoot(env, home);
|
|
@@ -4201,8 +4611,8 @@ function fieldError2(path4, requirement) {
|
|
|
4201
4611
|
}
|
|
4202
4612
|
|
|
4203
4613
|
// src/lib/host-workspace.ts
|
|
4204
|
-
import { existsSync as
|
|
4205
|
-
import { dirname as dirname9, join as
|
|
4614
|
+
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";
|
|
4615
|
+
import { dirname as dirname9, join as join15, resolve as resolve9 } from "node:path";
|
|
4206
4616
|
var AGRO_REPO_URL = "https://github.com/mifunedev/agro.git";
|
|
4207
4617
|
var IGNORABLE_ENTRIES = [
|
|
4208
4618
|
REGISTRY_SUBDIR,
|
|
@@ -4214,7 +4624,7 @@ function isDirectory3(path4) {
|
|
|
4214
4624
|
return statSync5(path4, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
4215
4625
|
}
|
|
4216
4626
|
function stateHomes(home) {
|
|
4217
|
-
return STATE_HOME_DIRS.map((name) =>
|
|
4627
|
+
return STATE_HOME_DIRS.map((name) => join15(home, name));
|
|
4218
4628
|
}
|
|
4219
4629
|
function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
4220
4630
|
const target = resolve9(root);
|
|
@@ -4222,7 +4632,7 @@ function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
|
4222
4632
|
if (enclosing === void 0) return void 0;
|
|
4223
4633
|
return `${bin} ${verb}: the ${verb} root ${target} is the state home ${enclosing} itself.
|
|
4224
4634
|
A workspace there collides with AGRO state and blocks every command run from ${home}.
|
|
4225
|
-
Move it out \u2014 for example \`mv ${enclosing} ${
|
|
4635
|
+
Move it out \u2014 for example \`mv ${enclosing} ${join15(home, "agro")}\` \u2014 then re-run this command.
|
|
4226
4636
|
`;
|
|
4227
4637
|
}
|
|
4228
4638
|
function isIgnorableEntry(name) {
|
|
@@ -4239,16 +4649,16 @@ function cloneInto(target, run, root, ref) {
|
|
|
4239
4649
|
assertCloned(run("git", ["clone", ...branch, AGRO_REPO_URL, target], { stdio: "inherit" }), root);
|
|
4240
4650
|
}
|
|
4241
4651
|
function cloneThroughStaging(root, run, ref) {
|
|
4242
|
-
const staging = mkdtempSync4(
|
|
4652
|
+
const staging = mkdtempSync4(join15(dirname9(root), "agro-clone-"));
|
|
4243
4653
|
try {
|
|
4244
|
-
const checkout =
|
|
4654
|
+
const checkout = join15(staging, "agro");
|
|
4245
4655
|
cloneInto(checkout, run, root, ref);
|
|
4246
4656
|
for (const name of readdirSync3(checkout)) {
|
|
4247
|
-
const destination =
|
|
4248
|
-
if (
|
|
4657
|
+
const destination = join15(root, name);
|
|
4658
|
+
if (existsSync19(destination)) {
|
|
4249
4659
|
throw new Error(`could not move ${name} into ${root} \u2014 the path already exists`);
|
|
4250
4660
|
}
|
|
4251
|
-
renameSync5(
|
|
4661
|
+
renameSync5(join15(checkout, name), destination);
|
|
4252
4662
|
}
|
|
4253
4663
|
} finally {
|
|
4254
4664
|
rmSync4(staging, { recursive: true, force: true });
|
|
@@ -4256,7 +4666,7 @@ function cloneThroughStaging(root, run, ref) {
|
|
|
4256
4666
|
}
|
|
4257
4667
|
function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
4258
4668
|
const root = resolve9(path4);
|
|
4259
|
-
if (
|
|
4669
|
+
if (existsSync19(join15(root, ".git"))) return { root, action: "reused" };
|
|
4260
4670
|
const stats = statSync5(root, { throwIfNoEntry: false });
|
|
4261
4671
|
if (stats === void 0) {
|
|
4262
4672
|
mkdirSync10(dirname9(root), { recursive: true });
|
|
@@ -4278,11 +4688,11 @@ function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
|
4278
4688
|
function listHostWorkspaces(env, home) {
|
|
4279
4689
|
const root = workspacesRoot(env, home);
|
|
4280
4690
|
if (!isDirectory3(root)) return [];
|
|
4281
|
-
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => SANDBOX_NAME_PATTERN.test(name)).filter((name) =>
|
|
4691
|
+
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();
|
|
4282
4692
|
}
|
|
4283
4693
|
function resolveExistingWorkspace(bin, verb, path4, env, home) {
|
|
4284
4694
|
const root = resolve9(path4);
|
|
4285
|
-
if (
|
|
4695
|
+
if (existsSync19(join15(root, ".git"))) return { ok: true, root };
|
|
4286
4696
|
const existing = listHostWorkspaces(env, home);
|
|
4287
4697
|
const inventory = existing.length === 0 ? "No host workspace exists yet.\n" : `Workspaces that exist: ${existing.join(", ")}
|
|
4288
4698
|
`;
|
|
@@ -4312,7 +4722,7 @@ function targetFor(root, run, env) {
|
|
|
4312
4722
|
});
|
|
4313
4723
|
}
|
|
4314
4724
|
function hostPrefix(home) {
|
|
4315
|
-
return
|
|
4725
|
+
return join16(home, ".local");
|
|
4316
4726
|
}
|
|
4317
4727
|
function pathEntries(env) {
|
|
4318
4728
|
return (env.PATH ?? "").split(delimiter2).filter((part) => part !== "");
|
|
@@ -4346,6 +4756,21 @@ async function probeInstalled(target, entry, prefix, user, env) {
|
|
|
4346
4756
|
throw err;
|
|
4347
4757
|
}
|
|
4348
4758
|
}
|
|
4759
|
+
function sandboxMarkerPath(entry) {
|
|
4760
|
+
return `${SANDBOX_HARNESS_PREFIX}/share/agro/harnesses/${entry.id}.installed`;
|
|
4761
|
+
}
|
|
4762
|
+
async function sandboxMarkerExists(target, entry) {
|
|
4763
|
+
const r = await target.exec({
|
|
4764
|
+
argv: ["test", "-f", sandboxMarkerPath(entry)],
|
|
4765
|
+
user: "sandbox",
|
|
4766
|
+
stdio: "capture",
|
|
4767
|
+
timeoutMs: PROBE_TIMEOUT_MS
|
|
4768
|
+
});
|
|
4769
|
+
return r.exitCode === 0;
|
|
4770
|
+
}
|
|
4771
|
+
function hostBinaryUnderPrefix(entry, prefix) {
|
|
4772
|
+
return existsSync20(join16(harnessBinPath(prefix), entry.binary));
|
|
4773
|
+
}
|
|
4349
4774
|
function stateOf(entry, installed, location) {
|
|
4350
4775
|
return {
|
|
4351
4776
|
id: entry.id,
|
|
@@ -4365,7 +4790,7 @@ function probeableHost(env, home) {
|
|
|
4365
4790
|
return void 0;
|
|
4366
4791
|
}
|
|
4367
4792
|
const prefix = hostPrefix(home);
|
|
4368
|
-
if (!
|
|
4793
|
+
if (!existsSync20(join16(root, ".git")) && !existsSync20(prefix)) return void 0;
|
|
4369
4794
|
return { root, prefix };
|
|
4370
4795
|
}
|
|
4371
4796
|
async function collectStates(root, run, home, env, only) {
|
|
@@ -4571,7 +4996,15 @@ Run: bash ${root}/.agro/scripts/link-providers.sh --init
|
|
|
4571
4996
|
const code = await reconcileHermes(target, io, bin, void 0, root);
|
|
4572
4997
|
if (code !== 0) return code;
|
|
4573
4998
|
}
|
|
4574
|
-
|
|
4999
|
+
let hasReceipt;
|
|
5000
|
+
try {
|
|
5001
|
+
hasReceipt = readHostConfig(env, home).hostHarnesses?.[entry.id] !== void 0;
|
|
5002
|
+
} catch (err) {
|
|
5003
|
+
io.stderr(`${bin} harness: ${messageOf(err)}
|
|
5004
|
+
`);
|
|
5005
|
+
return 1;
|
|
5006
|
+
}
|
|
5007
|
+
if (await probeInstalled(target, entry, prefix, void 0, installEnv) === true && (hasReceipt || !hostBinaryUnderPrefix(entry, prefix))) {
|
|
4575
5008
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
4576
5009
|
`);
|
|
4577
5010
|
try {
|
|
@@ -4735,7 +5168,20 @@ async function runHarnessUninstall(name, opts, io) {
|
|
|
4735
5168
|
if (!isReachable(await resolveTargetStatus(target))) {
|
|
4736
5169
|
return await uninstallOnHost(entry, opts, io, run);
|
|
4737
5170
|
}
|
|
4738
|
-
|
|
5171
|
+
const outcome = await removeHarness(entry, target, SANDBOX_HARNESS_PREFIX, "sandbox", opts, io);
|
|
5172
|
+
if (outcome.dropReceipt) {
|
|
5173
|
+
const cleared = await target.exec({
|
|
5174
|
+
argv: ["rm", "-f", sandboxMarkerPath(entry)],
|
|
5175
|
+
user: "sandbox",
|
|
5176
|
+
stdio: "capture"
|
|
5177
|
+
});
|
|
5178
|
+
if (cleared.exitCode !== 0) {
|
|
5179
|
+
io.stderr(`${opts.bin} harness: could not clear the install record (exit ${cleared.exitCode}).
|
|
5180
|
+
`);
|
|
5181
|
+
return 1;
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
return outcome.code;
|
|
4739
5185
|
}
|
|
4740
5186
|
async function runHarnessInstall(name, opts, io) {
|
|
4741
5187
|
const run = opts.run ?? spawnRunner;
|
|
@@ -4761,7 +5207,7 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4761
5207
|
if (code !== 0) return code;
|
|
4762
5208
|
}
|
|
4763
5209
|
const already = await probeInstalled(target, entry, SANDBOX_HARNESS_PREFIX, "sandbox", installEnv);
|
|
4764
|
-
if (already === true) {
|
|
5210
|
+
if (already === true && await sandboxMarkerExists(target, entry)) {
|
|
4765
5211
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
4766
5212
|
`);
|
|
4767
5213
|
return 0;
|
|
@@ -4792,15 +5238,26 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4792
5238
|
const code = await reconcileHermes(target, io, opts.bin, "sandbox");
|
|
4793
5239
|
if (code !== 0) return code;
|
|
4794
5240
|
}
|
|
5241
|
+
const marker = sandboxMarkerPath(entry);
|
|
5242
|
+
const marked = await target.exec({
|
|
5243
|
+
argv: ["sh", "-c", `mkdir -p "$(dirname "$1")" && : > "$1"`, "sh", marker],
|
|
5244
|
+
user: "sandbox",
|
|
5245
|
+
stdio: "capture"
|
|
5246
|
+
});
|
|
5247
|
+
if (marked.exitCode !== 0) {
|
|
5248
|
+
io.stderr(`${opts.bin} harness: could not record the install at ${marker} (exit ${marked.exitCode}).
|
|
5249
|
+
`);
|
|
5250
|
+
return 1;
|
|
5251
|
+
}
|
|
4795
5252
|
io.stdout(`${entry.id}: installed \u2014 see ${sourceDocsUrl(entry.docsPath)} for authentication
|
|
4796
5253
|
`);
|
|
4797
5254
|
return 0;
|
|
4798
5255
|
}
|
|
4799
5256
|
|
|
4800
5257
|
// src/commands/workspace.ts
|
|
4801
|
-
import { existsSync as
|
|
5258
|
+
import { existsSync as existsSync21, rmSync as rmSync5 } from "node:fs";
|
|
4802
5259
|
import { homedir as homedir4 } from "node:os";
|
|
4803
|
-
import { join as
|
|
5260
|
+
import { join as join17, resolve as resolve10 } from "node:path";
|
|
4804
5261
|
function homeOf2(opts) {
|
|
4805
5262
|
return (opts.homedir ?? homedir4)();
|
|
4806
5263
|
}
|
|
@@ -4846,9 +5303,9 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
4846
5303
|
return 1;
|
|
4847
5304
|
}
|
|
4848
5305
|
let action;
|
|
4849
|
-
const preexisting =
|
|
5306
|
+
const preexisting = existsSync21(root);
|
|
4850
5307
|
try {
|
|
4851
|
-
if (!opts.json && !
|
|
5308
|
+
if (!opts.json && !existsSync21(join17(root, ".git"))) {
|
|
4852
5309
|
const at = opts.ref !== void 0 ? ` at ${opts.ref}` : "";
|
|
4853
5310
|
io.stdout(`cloning ${AGRO_REPO_URL}${at} into ${root}\u2026
|
|
4854
5311
|
`);
|
|
@@ -4893,7 +5350,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
4893
5350
|
const recorded = recordedRoot(env, home);
|
|
4894
5351
|
const registry = workspacesRoot(env, home);
|
|
4895
5352
|
rows = listHostWorkspaces(env, home).map((name) => {
|
|
4896
|
-
const root =
|
|
5353
|
+
const root = join17(registry, name);
|
|
4897
5354
|
return { name, root, default: recorded === root };
|
|
4898
5355
|
});
|
|
4899
5356
|
} catch (err) {
|
|
@@ -4918,9 +5375,9 @@ async function runWorkspaceList(opts, io) {
|
|
|
4918
5375
|
}
|
|
4919
5376
|
|
|
4920
5377
|
// src/commands/tool.ts
|
|
4921
|
-
import { existsSync as
|
|
5378
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
4922
5379
|
import { homedir as homedir5, userInfo as userInfo2 } from "node:os";
|
|
4923
|
-
import { delimiter as delimiter3, join as
|
|
5380
|
+
import { delimiter as delimiter3, join as join18 } from "node:path";
|
|
4924
5381
|
|
|
4925
5382
|
// src/lib/tools/catalog.ts
|
|
4926
5383
|
var TOOLS_DOC = "docs/installation.md";
|
|
@@ -5337,6 +5794,7 @@ function resolveToolInstallArgv(entry, host) {
|
|
|
5337
5794
|
}
|
|
5338
5795
|
|
|
5339
5796
|
// src/commands/tool.ts
|
|
5797
|
+
var ROOT_TOOL_REMOVAL_DOCS = `${sourceDocsUrl("docs/installation.md")}#remove-a-root-level-tool`;
|
|
5340
5798
|
function isReachable2(status) {
|
|
5341
5799
|
return status === "ready" || status === "starting";
|
|
5342
5800
|
}
|
|
@@ -5350,7 +5808,21 @@ function targetFor2(root, run, env) {
|
|
|
5350
5808
|
});
|
|
5351
5809
|
}
|
|
5352
5810
|
function hostPrefix2(home) {
|
|
5353
|
-
return
|
|
5811
|
+
return join18(home, ".local");
|
|
5812
|
+
}
|
|
5813
|
+
function sandboxMarkerPath2(entry) {
|
|
5814
|
+
return `${SANDBOX_HARNESS_PREFIX}/share/agro/tools/${entry.id}.installed`;
|
|
5815
|
+
}
|
|
5816
|
+
async function sandboxMarkerExists2(target, entry) {
|
|
5817
|
+
const r = await target.exec({
|
|
5818
|
+
argv: ["test", "-f", sandboxMarkerPath2(entry)],
|
|
5819
|
+
user: "sandbox",
|
|
5820
|
+
stdio: "capture"
|
|
5821
|
+
});
|
|
5822
|
+
return r.exitCode === 0;
|
|
5823
|
+
}
|
|
5824
|
+
function hostBinaryUnderPrefix2(entry, prefix) {
|
|
5825
|
+
return existsSync22(join18(harnessBinPath(prefix), entry.binary));
|
|
5354
5826
|
}
|
|
5355
5827
|
function pathEntries2(env) {
|
|
5356
5828
|
return (env.PATH ?? "").split(delimiter3).filter((part) => part !== "");
|
|
@@ -5430,7 +5902,7 @@ function probeableHost2(env, home) {
|
|
|
5430
5902
|
return void 0;
|
|
5431
5903
|
}
|
|
5432
5904
|
const prefix = hostPrefix2(home);
|
|
5433
|
-
if (!
|
|
5905
|
+
if (!existsSync22(join18(root, ".git")) && !existsSync22(prefix)) return void 0;
|
|
5434
5906
|
return { root, prefix };
|
|
5435
5907
|
}
|
|
5436
5908
|
async function collectRows(root, run, home, env, only) {
|
|
@@ -5646,7 +6118,15 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5646
6118
|
const prefix = hostPrefix2(home);
|
|
5647
6119
|
const installEnv = { NPM_USER_PREFIX: prefix };
|
|
5648
6120
|
const target = hostTargetFor2(root, prefix, run, env);
|
|
5649
|
-
|
|
6121
|
+
let hasReceipt;
|
|
6122
|
+
try {
|
|
6123
|
+
hasReceipt = readHostConfig(env, home).hostTools?.[entry.id] !== void 0;
|
|
6124
|
+
} catch (err) {
|
|
6125
|
+
io.stderr(`${bin} tool: ${messageOf3(err)}
|
|
6126
|
+
`);
|
|
6127
|
+
return 1;
|
|
6128
|
+
}
|
|
6129
|
+
if (await probeInstalled2(target, entry, void 0, installEnv) === true && (hasReceipt || entry.hostInstallUser === "root" || !hostBinaryUnderPrefix2(entry, prefix))) {
|
|
5650
6130
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
5651
6131
|
`);
|
|
5652
6132
|
try {
|
|
@@ -5681,13 +6161,8 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5681
6161
|
`);
|
|
5682
6162
|
return r.exitCode;
|
|
5683
6163
|
}
|
|
5684
|
-
const
|
|
5685
|
-
|
|
5686
|
-
binary: entry.binary,
|
|
5687
|
-
binPath: harnessBinPath(prefix),
|
|
5688
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5689
|
-
workspaceRoot: root
|
|
5690
|
-
};
|
|
6164
|
+
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6165
|
+
const receipt = entry.hostInstallUser === "root" ? { scope: "root", binary: entry.binary, installedAt, workspaceRoot: root } : { prefix, binary: entry.binary, binPath: harnessBinPath(prefix), installedAt, workspaceRoot: root };
|
|
5691
6166
|
try {
|
|
5692
6167
|
const config = readHostConfig(env, home);
|
|
5693
6168
|
writeHostConfig(
|
|
@@ -5747,7 +6222,7 @@ ${installableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
|
5747
6222
|
}
|
|
5748
6223
|
if (entry.installArgv === void 0) return refuseContainer();
|
|
5749
6224
|
const already = await probeInstalled2(target, entry, "sandbox");
|
|
5750
|
-
if (already === true) {
|
|
6225
|
+
if (already === true && await sandboxMarkerExists2(target, entry)) {
|
|
5751
6226
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
5752
6227
|
`);
|
|
5753
6228
|
return 0;
|
|
@@ -5769,6 +6244,17 @@ ${installableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
|
5769
6244
|
`);
|
|
5770
6245
|
return r.exitCode;
|
|
5771
6246
|
}
|
|
6247
|
+
const marker = sandboxMarkerPath2(entry);
|
|
6248
|
+
const marked = await target.exec({
|
|
6249
|
+
argv: ["sh", "-c", `mkdir -p "$(dirname "$1")" && : > "$1"`, "sh", marker],
|
|
6250
|
+
user: "sandbox",
|
|
6251
|
+
stdio: "capture"
|
|
6252
|
+
});
|
|
6253
|
+
if (marked.exitCode !== 0) {
|
|
6254
|
+
io.stderr(`${opts.bin} tool: could not record the install at ${marker} (exit ${marked.exitCode}).
|
|
6255
|
+
`);
|
|
6256
|
+
return 1;
|
|
6257
|
+
}
|
|
5772
6258
|
io.stdout(`${entry.id}: installed \u2014 see ${sourceDocsUrl(entry.docsPath)}
|
|
5773
6259
|
`);
|
|
5774
6260
|
return 0;
|
|
@@ -5829,7 +6315,7 @@ Removing it could delete a tool you installed yourself. Re-run with \`--force\`
|
|
|
5829
6315
|
);
|
|
5830
6316
|
return 1;
|
|
5831
6317
|
}
|
|
5832
|
-
const prefix = receipt
|
|
6318
|
+
const prefix = receipt === void 0 || isRootToolReceipt(receipt) ? computed : receipt.prefix;
|
|
5833
6319
|
const target = hostTargetFor2(receipt?.workspaceRoot ?? workspace, prefix, run, env);
|
|
5834
6320
|
const outcome = await removeTool(entry, target, prefix, void 0, opts, io, true);
|
|
5835
6321
|
if (outcome.dropReceipt && receipt !== void 0) {
|
|
@@ -5852,6 +6338,15 @@ async function runToolUninstall(name, opts, io) {
|
|
|
5852
6338
|
const root = resolveProjectRoot(opts.cwd);
|
|
5853
6339
|
const entry = findTool(name);
|
|
5854
6340
|
if (!entry) return unknownTool(name, io, opts.bin);
|
|
6341
|
+
if (entry.hostInstallUser === "root") {
|
|
6342
|
+
io.stderr(
|
|
6343
|
+
`${opts.bin} tool: ${entry.id} cannot be removed by this command.
|
|
6344
|
+
${opts.bin} does not remove system packages that it installed as root.
|
|
6345
|
+
Remove ${entry.id} by hand: ${ROOT_TOOL_REMOVAL_DOCS}
|
|
6346
|
+
`
|
|
6347
|
+
);
|
|
6348
|
+
return 1;
|
|
6349
|
+
}
|
|
5855
6350
|
if (entry.uninstallArgv === null) {
|
|
5856
6351
|
io.stderr(`${opts.bin} tool: ${entry.id} cannot be removed by this command.
|
|
5857
6352
|
|
|
@@ -5864,15 +6359,28 @@ async function runToolUninstall(name, opts, io) {
|
|
|
5864
6359
|
if (!isReachable2(await resolveTargetStatus(target))) {
|
|
5865
6360
|
return await uninstallOnHost2(entry, opts, io, run);
|
|
5866
6361
|
}
|
|
5867
|
-
|
|
6362
|
+
const outcome = await removeTool(entry, target, SANDBOX_HARNESS_PREFIX, "sandbox", opts, io);
|
|
6363
|
+
if (outcome.dropReceipt) {
|
|
6364
|
+
const cleared = await target.exec({
|
|
6365
|
+
argv: ["rm", "-f", sandboxMarkerPath2(entry)],
|
|
6366
|
+
user: "sandbox",
|
|
6367
|
+
stdio: "capture"
|
|
6368
|
+
});
|
|
6369
|
+
if (cleared.exitCode !== 0) {
|
|
6370
|
+
io.stderr(`${opts.bin} tool: could not clear the install record (exit ${cleared.exitCode}).
|
|
6371
|
+
`);
|
|
6372
|
+
return 1;
|
|
6373
|
+
}
|
|
6374
|
+
}
|
|
6375
|
+
return outcome.code;
|
|
5868
6376
|
}
|
|
5869
6377
|
|
|
5870
6378
|
// src/lib/remote.ts
|
|
5871
|
-
import { spawnSync as
|
|
6379
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
5872
6380
|
var DEFAULT_REPO_URL = "https://github.com/mifunedev/agro";
|
|
5873
6381
|
var DEFAULT_CLONE_TIMEOUT_MS = 12e4;
|
|
5874
6382
|
var spawnRunner2 = (cmd, args, opts) => {
|
|
5875
|
-
const r =
|
|
6383
|
+
const r = spawnSync4(cmd, args, {
|
|
5876
6384
|
env: opts.env,
|
|
5877
6385
|
timeout: opts.timeoutMs,
|
|
5878
6386
|
stdio: ["ignore", "ignore", "pipe"],
|
|
@@ -5930,6 +6438,35 @@ function fetchRemoteSource(opts) {
|
|
|
5930
6438
|
return opts.destDir;
|
|
5931
6439
|
}
|
|
5932
6440
|
|
|
6441
|
+
// src/command-table.ts
|
|
6442
|
+
var CONFIG_VERBS = ["show", "set", "repo"];
|
|
6443
|
+
var CONFIG_INTEGRATIONS = ["langfuse"];
|
|
6444
|
+
var SECRET_VERBS = ["set", "list"];
|
|
6445
|
+
var LANGFUSE_VERBS = ["apply", "status", "disable"];
|
|
6446
|
+
var SANDBOX_SUBCOMMANDS = ["install", "list", "upgrade"];
|
|
6447
|
+
var WORKSPACE_SUBCOMMANDS = ["create", "list"];
|
|
6448
|
+
var HARNESS_SUBCOMMANDS = ["list", "install", "status", "uninstall"];
|
|
6449
|
+
var TOOL_SUBCOMMANDS = ["list", "install", "status", "uninstall"];
|
|
6450
|
+
function isOneOf(values, value) {
|
|
6451
|
+
return value !== void 0 && values.includes(value);
|
|
6452
|
+
}
|
|
6453
|
+
var AGRO_COMMANDS = Object.freeze({
|
|
6454
|
+
config: [...CONFIG_VERBS, ...CONFIG_INTEGRATIONS],
|
|
6455
|
+
secret: SECRET_VERBS,
|
|
6456
|
+
langfuse: LANGFUSE_VERBS,
|
|
6457
|
+
update: null,
|
|
6458
|
+
"self-upgrade": null,
|
|
6459
|
+
vendor: null,
|
|
6460
|
+
sandbox: SANDBOX_SUBCOMMANDS,
|
|
6461
|
+
shell: null,
|
|
6462
|
+
compose: null,
|
|
6463
|
+
workspace: WORKSPACE_SUBCOMMANDS,
|
|
6464
|
+
harness: HARNESS_SUBCOMMANDS,
|
|
6465
|
+
tool: TOOL_SUBCOMMANDS,
|
|
6466
|
+
gateway: null,
|
|
6467
|
+
...Object.fromEntries(composeVerbs().map((verb) => [verb, null]))
|
|
6468
|
+
});
|
|
6469
|
+
|
|
5933
6470
|
// src/cli.ts
|
|
5934
6471
|
var DEFAULT_SOURCE_CONTROL_DIR = resolve11(
|
|
5935
6472
|
dirname10(fileURLToPath(import.meta.url)),
|
|
@@ -5944,7 +6481,7 @@ var INTEGRATIONS = {
|
|
|
5944
6481
|
)
|
|
5945
6482
|
}
|
|
5946
6483
|
};
|
|
5947
|
-
function
|
|
6484
|
+
function isHelpFlag2(arg) {
|
|
5948
6485
|
return arg === "--help" || arg === "-h" || arg === "help";
|
|
5949
6486
|
}
|
|
5950
6487
|
function isVersionFlag(arg) {
|
|
@@ -5961,7 +6498,7 @@ function printAgroHelp(product = AGRO_PRODUCT) {
|
|
|
5961
6498
|
process.stdout.write(`${bin} \u2014 ${title} (v${AGRO_VERSION})
|
|
5962
6499
|
|
|
5963
6500
|
Usage:
|
|
5964
|
-
${bin} sandbox <args...> Create and
|
|
6501
|
+
${bin} sandbox <args...> Create, list, and upgrade sandboxes (install|list|upgrade)
|
|
5965
6502
|
${bin} shell [name] Open a zsh shell in the running sandbox container
|
|
5966
6503
|
${bin} config <args...> Read and write ${stateNames(bin).configFile} (show|set), or run a wizard
|
|
5967
6504
|
${bin} secret <args...> Read and write the gitignored root .env (set|list)
|
|
@@ -6111,66 +6648,6 @@ Flags:
|
|
|
6111
6648
|
--force Override the up-to-date / downgrade gate.
|
|
6112
6649
|
`);
|
|
6113
6650
|
}
|
|
6114
|
-
function runtimeLines() {
|
|
6115
|
-
const width = Math.max(...RUNTIME_CATALOG.map((r) => r.id.length));
|
|
6116
|
-
return RUNTIME_CATALOG.map(
|
|
6117
|
-
(r) => ` ${r.id.padEnd(width)} ${r.provisionable ? "provisionable" : "planned"}`
|
|
6118
|
-
).join("\n");
|
|
6119
|
-
}
|
|
6120
|
-
function printSandboxHelp(bin = AGRO_PRODUCT.bin) {
|
|
6121
|
-
process.stdout.write(`${bin} sandbox \u2014 Create and list sandboxes
|
|
6122
|
-
|
|
6123
|
-
Usage:
|
|
6124
|
-
${bin} sandbox install <runtime> [--name <name>] [--checkout <dir>]
|
|
6125
|
-
[--home-mount <dir>] [--yes]
|
|
6126
|
-
[--version <X.Y.Z> | --image[=<ref>]]
|
|
6127
|
-
[--no-build] [--print-argv]
|
|
6128
|
-
${bin} sandbox list [--json]
|
|
6129
|
-
|
|
6130
|
-
\`install\` writes a sandbox entry under \${${stateNames(bin).envPrefix}HOME:-~/${stateNames(bin).userStateDir}}/sandboxes/<name>/,
|
|
6131
|
-
materialises the compose files and the compose wrapper into it, then starts the
|
|
6132
|
-
container. It needs no project checkout and runs from any directory. Without
|
|
6133
|
-
--checkout the sandbox runs the prebuilt image; with --checkout <dir> that
|
|
6134
|
-
checkout is bound at /home/sandbox/harness; the sandbox builds locally only
|
|
6135
|
-
when that directory holds .devcontainer/Dockerfile.
|
|
6136
|
-
|
|
6137
|
-
On a terminal without --yes it asks for the sandbox name, the timezone, the git
|
|
6138
|
-
identity, SSH (with its host port), the host Docker socket, and the host path
|
|
6139
|
-
for /home/sandbox. With --checkout it seeds those answers from that
|
|
6140
|
-
checkout's ${stateNames(bin).configFile}.
|
|
6141
|
-
|
|
6142
|
-
Flags:
|
|
6143
|
-
--name <name> Registry entry name (default: the lowest free ${DEFAULT_NAME_PREFIX}<n>)
|
|
6144
|
-
--checkout <dir> Bind this checkout into the sandbox and seed the defaults
|
|
6145
|
-
from its ${stateNames(bin).configFile}. The sandbox builds
|
|
6146
|
-
locally only when that directory holds
|
|
6147
|
-
.devcontainer/Dockerfile
|
|
6148
|
-
--repo <dir> Deprecated alias for --checkout
|
|
6149
|
-
--home-mount <dir>
|
|
6150
|
-
Bind this host directory at /home/sandbox instead of the
|
|
6151
|
-
Docker-managed volume. Relative paths resolve against the
|
|
6152
|
-
working directory. The directory is created when absent
|
|
6153
|
-
--yes Non-interactive: keep every default and ask nothing
|
|
6154
|
-
--version <X.Y.Z>
|
|
6155
|
-
Run the official release image ghcr.io/mifunedev/agro:<X.Y.Z>
|
|
6156
|
-
(implies --image). A leading v and a -<channel>.<n>
|
|
6157
|
-
pre-release suffix are accepted
|
|
6158
|
-
--image Run the prebuilt image instead of building (implies
|
|
6159
|
-
--no-build). The ref resolves first-match: --version or
|
|
6160
|
-
--image=<ref> > ${stateNames(bin).envPrefix}SANDBOX_IMAGE >
|
|
6161
|
-
${stateNames(bin).configFile} image.ref > ${officialImageRef(AGRO_VERSION)}
|
|
6162
|
-
--image=<ref> Run a custom image (implies --image); conflicts with --version
|
|
6163
|
-
--no-build Suppress the local build and reuse an existing image
|
|
6164
|
-
--print-argv Print the docker compose argv that would run, then exit
|
|
6165
|
-
without writing an entry
|
|
6166
|
-
--json Machine-readable output (list)
|
|
6167
|
-
|
|
6168
|
-
Runtimes:
|
|
6169
|
-
${runtimeLines()}
|
|
6170
|
-
|
|
6171
|
-
Next: ${bin} shell <name>
|
|
6172
|
-
`);
|
|
6173
|
-
}
|
|
6174
6651
|
function printShellHelp(bin = AGRO_PRODUCT.bin) {
|
|
6175
6652
|
process.stdout.write(`${bin} shell \u2014 Open a shell in the running sandbox container
|
|
6176
6653
|
|
|
@@ -6295,6 +6772,8 @@ no workspace: run \`${bin} workspace create\` first. It installs into
|
|
|
6295
6772
|
\`~/.local\` and records the install as \`hostTools\` in that same file. On the host,
|
|
6296
6773
|
\`uninstall\` removes only what \`install\` recorded, from the prefix in that record.
|
|
6297
6774
|
Without a record it refuses, and \`--force\` overrides.
|
|
6775
|
+
\`uninstall\` refuses each root-level tool, marked \`(root)\` in \`${bin} tool list\`,
|
|
6776
|
+
with and without \`--force\`. The refusal links the manual removal steps.
|
|
6298
6777
|
|
|
6299
6778
|
Flags:
|
|
6300
6779
|
--yes Accept a large download without prompting
|
|
@@ -6382,9 +6861,8 @@ function extractSandboxFlag(command, rest) {
|
|
|
6382
6861
|
}
|
|
6383
6862
|
return { ok: true, args: sandbox === void 0 ? { rest: kept } : { rest: kept, sandbox } };
|
|
6384
6863
|
}
|
|
6385
|
-
var LANGFUSE_VERBS = ["apply", "status", "disable"];
|
|
6386
6864
|
function parseLangfuseArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6387
|
-
if (rest.length === 0 ||
|
|
6865
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) return { ok: true, args: { help: true } };
|
|
6388
6866
|
const [head, ...tail] = rest;
|
|
6389
6867
|
if (!LANGFUSE_VERBS.includes(head)) {
|
|
6390
6868
|
return {
|
|
@@ -6393,13 +6871,12 @@ function parseLangfuseArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6393
6871
|
showHelp: true
|
|
6394
6872
|
};
|
|
6395
6873
|
}
|
|
6396
|
-
if (
|
|
6874
|
+
if (isHelpFlag2(tail[0])) return { ok: true, args: { help: true } };
|
|
6397
6875
|
if (tail.length > 0) {
|
|
6398
6876
|
return { ok: false, error: `${bin} langfuse ${head}: unexpected argument "${tail[0]}"` };
|
|
6399
6877
|
}
|
|
6400
6878
|
return { ok: true, args: { help: false, verb: head } };
|
|
6401
6879
|
}
|
|
6402
|
-
var CONFIG_VERBS = ["show", "set", "repo"];
|
|
6403
6880
|
function parseConfigArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
6404
6881
|
const scoped = extractSandboxFlag(`${bin} config`, input);
|
|
6405
6882
|
if (!scoped.ok) return scoped;
|
|
@@ -6411,12 +6888,12 @@ function parseConfigArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6411
6888
|
...scoped.args.sandbox === void 0 ? {} : { sandbox: scoped.args.sandbox },
|
|
6412
6889
|
...forced ? { force: true } : {}
|
|
6413
6890
|
};
|
|
6414
|
-
if (rest.length === 0 ||
|
|
6891
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6415
6892
|
return { ok: true, args: { ...args, help: true } };
|
|
6416
6893
|
}
|
|
6417
6894
|
const [head, ...tail] = rest;
|
|
6418
6895
|
if (!CONFIG_VERBS.includes(head)) {
|
|
6419
|
-
if (tail.length > 0 &&
|
|
6896
|
+
if (tail.length > 0 && isHelpFlag2(tail[0])) {
|
|
6420
6897
|
return { ok: true, args: { ...args, integration: head, integrationHelp: true } };
|
|
6421
6898
|
}
|
|
6422
6899
|
if (tail.length > 0) {
|
|
@@ -6428,7 +6905,7 @@ function parseConfigArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6428
6905
|
return { ok: true, args: { ...args, integration: head } };
|
|
6429
6906
|
}
|
|
6430
6907
|
const verb = head;
|
|
6431
|
-
if (
|
|
6908
|
+
if (isHelpFlag2(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
6432
6909
|
if (verb === "show" || verb === "repo") {
|
|
6433
6910
|
if (tail.length > 0) {
|
|
6434
6911
|
return { ok: false, error: `${bin} config ${verb}: unexpected argument "${tail[0]}"` };
|
|
@@ -6451,7 +6928,6 @@ function parseConfigArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6451
6928
|
}
|
|
6452
6929
|
return { ok: true, args: { ...args, verb, key, value } };
|
|
6453
6930
|
}
|
|
6454
|
-
var SECRET_VERBS = ["set", "list"];
|
|
6455
6931
|
function parseSecretArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
6456
6932
|
const scoped = extractSandboxFlag(`${bin} secret`, input);
|
|
6457
6933
|
if (!scoped.ok) return scoped;
|
|
@@ -6460,7 +6936,7 @@ function parseSecretArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6460
6936
|
help: false,
|
|
6461
6937
|
...scoped.args.sandbox === void 0 ? {} : { sandbox: scoped.args.sandbox }
|
|
6462
6938
|
};
|
|
6463
|
-
if (rest.length === 0 ||
|
|
6939
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6464
6940
|
return { ok: true, args: { ...args, help: true } };
|
|
6465
6941
|
}
|
|
6466
6942
|
const [head, ...tail] = rest;
|
|
@@ -6472,7 +6948,7 @@ function parseSecretArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6472
6948
|
};
|
|
6473
6949
|
}
|
|
6474
6950
|
const verb = head;
|
|
6475
|
-
if (
|
|
6951
|
+
if (isHelpFlag2(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
6476
6952
|
if (verb === "list") {
|
|
6477
6953
|
if (tail.length > 0) {
|
|
6478
6954
|
return { ok: false, error: `${bin} secret list: unexpected argument "${tail[0]}"` };
|
|
@@ -6493,7 +6969,7 @@ function parseSecretArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
6493
6969
|
}
|
|
6494
6970
|
function parseDestroyArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6495
6971
|
const args = { help: false, yes: false };
|
|
6496
|
-
if (
|
|
6972
|
+
if (isHelpFlag2(rest[0])) return { ok: true, args: { ...args, help: true } };
|
|
6497
6973
|
for (const token of rest) {
|
|
6498
6974
|
if (token === "--yes") {
|
|
6499
6975
|
args.yes = true;
|
|
@@ -6512,7 +6988,7 @@ function parseDestroyArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6512
6988
|
}
|
|
6513
6989
|
function parseComposeArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6514
6990
|
const args = { help: false, passthrough: [] };
|
|
6515
|
-
if (rest.length === 0 ||
|
|
6991
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6516
6992
|
return { ok: true, args: { ...args, help: true } };
|
|
6517
6993
|
}
|
|
6518
6994
|
if (rest[0] !== "config") {
|
|
@@ -6524,7 +7000,7 @@ function parseComposeArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6524
7000
|
}
|
|
6525
7001
|
args.subcommand = "config";
|
|
6526
7002
|
const tail = rest.slice(1);
|
|
6527
|
-
if (
|
|
7003
|
+
if (isHelpFlag2(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
6528
7004
|
const sep4 = tail.indexOf("--");
|
|
6529
7005
|
if (sep4 === -1 && tail.length > 0) {
|
|
6530
7006
|
return {
|
|
@@ -6550,7 +7026,7 @@ function parseSelfUpgradeArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6550
7026
|
args.dryRun = true;
|
|
6551
7027
|
continue;
|
|
6552
7028
|
}
|
|
6553
|
-
if (
|
|
7029
|
+
if (isHelpFlag2(arg)) {
|
|
6554
7030
|
args.help = true;
|
|
6555
7031
|
return { ok: true, args };
|
|
6556
7032
|
}
|
|
@@ -6592,7 +7068,7 @@ function parseVendorArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6592
7068
|
args.force = true;
|
|
6593
7069
|
continue;
|
|
6594
7070
|
}
|
|
6595
|
-
if (
|
|
7071
|
+
if (isHelpFlag2(arg)) {
|
|
6596
7072
|
args.help = true;
|
|
6597
7073
|
return { ok: true, args };
|
|
6598
7074
|
}
|
|
@@ -6609,121 +7085,9 @@ function parseVendorArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6609
7085
|
}
|
|
6610
7086
|
return { ok: true, args };
|
|
6611
7087
|
}
|
|
6612
|
-
var SANDBOX_VALUE_FLAGS = {
|
|
6613
|
-
"--name": "name",
|
|
6614
|
-
"--checkout": "checkout",
|
|
6615
|
-
"--repo": "checkout",
|
|
6616
|
-
"--home-mount": "homeMount"
|
|
6617
|
-
};
|
|
6618
|
-
function parseSandboxArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6619
|
-
const args = {
|
|
6620
|
-
help: false,
|
|
6621
|
-
yes: false,
|
|
6622
|
-
image: false,
|
|
6623
|
-
noBuild: false,
|
|
6624
|
-
printArgv: false,
|
|
6625
|
-
json: false
|
|
6626
|
-
};
|
|
6627
|
-
if (rest.length === 0 || isHelpFlag(rest[0])) {
|
|
6628
|
-
return { ok: true, args: { ...args, help: true } };
|
|
6629
|
-
}
|
|
6630
|
-
const [head, ...tail] = rest;
|
|
6631
|
-
if (head !== "install" && head !== "list") {
|
|
6632
|
-
return {
|
|
6633
|
-
ok: false,
|
|
6634
|
-
error: `${bin} sandbox: unknown subcommand "${head}" \u2014 expected install or list`,
|
|
6635
|
-
showHelp: true
|
|
6636
|
-
};
|
|
6637
|
-
}
|
|
6638
|
-
args.subcommand = head;
|
|
6639
|
-
if (isHelpFlag(tail[0])) return { ok: true, args: { ...args, help: true } };
|
|
6640
|
-
const positionals = [];
|
|
6641
|
-
const checkoutSpellings = /* @__PURE__ */ new Set();
|
|
6642
|
-
let version;
|
|
6643
|
-
for (let i = 0; i < tail.length; i++) {
|
|
6644
|
-
const token = tail[i];
|
|
6645
|
-
const valueFlag = SANDBOX_VALUE_FLAGS[token];
|
|
6646
|
-
if (valueFlag !== void 0) {
|
|
6647
|
-
const value = tail[i + 1];
|
|
6648
|
-
if (value === void 0) {
|
|
6649
|
-
return { ok: false, error: `${bin} sandbox ${head}: ${token} requires a value` };
|
|
6650
|
-
}
|
|
6651
|
-
if (valueFlag === "checkout") checkoutSpellings.add(token);
|
|
6652
|
-
args[valueFlag] = value;
|
|
6653
|
-
i++;
|
|
6654
|
-
} else if (token === "--yes") {
|
|
6655
|
-
args.yes = true;
|
|
6656
|
-
} else if (token === "--no-build") {
|
|
6657
|
-
args.noBuild = true;
|
|
6658
|
-
} else if (token === "--print-argv") {
|
|
6659
|
-
args.printArgv = true;
|
|
6660
|
-
} else if (token === "--image") {
|
|
6661
|
-
args.image = true;
|
|
6662
|
-
} else if (token.startsWith("--image=")) {
|
|
6663
|
-
const ref = token.slice("--image=".length);
|
|
6664
|
-
if (ref === "") {
|
|
6665
|
-
return { ok: false, error: `${bin} sandbox: --image=<ref> requires a non-empty image ref` };
|
|
6666
|
-
}
|
|
6667
|
-
args.image = true;
|
|
6668
|
-
args.imageRef = ref;
|
|
6669
|
-
} else if (token === "--version" || token.startsWith("--version=")) {
|
|
6670
|
-
const value = token === "--version" ? tail[++i] : token.slice("--version=".length);
|
|
6671
|
-
if (value === void 0 || value === "") {
|
|
6672
|
-
return { ok: false, error: `${bin} sandbox ${head}: --version requires a value` };
|
|
6673
|
-
}
|
|
6674
|
-
version = parseReleaseVersion(value);
|
|
6675
|
-
if (version === void 0) {
|
|
6676
|
-
return {
|
|
6677
|
-
ok: false,
|
|
6678
|
-
error: `${bin} sandbox ${head}: --version "${value}" is not a release version \u2014 expected X.Y.Z`
|
|
6679
|
-
};
|
|
6680
|
-
}
|
|
6681
|
-
} else if (token === "--json") {
|
|
6682
|
-
args.json = true;
|
|
6683
|
-
} else if (token.startsWith("-")) {
|
|
6684
|
-
return { ok: false, error: `${bin} sandbox ${head}: unknown flag "${token}"` };
|
|
6685
|
-
} else {
|
|
6686
|
-
positionals.push(token);
|
|
6687
|
-
}
|
|
6688
|
-
}
|
|
6689
|
-
if (checkoutSpellings.has("--checkout") && checkoutSpellings.has("--repo")) {
|
|
6690
|
-
return {
|
|
6691
|
-
ok: false,
|
|
6692
|
-
error: `${bin} sandbox ${head}: --checkout conflicts with --repo \u2014 pass exactly one, and prefer --checkout`
|
|
6693
|
-
};
|
|
6694
|
-
}
|
|
6695
|
-
if (version !== void 0) {
|
|
6696
|
-
if (args.imageRef !== void 0) {
|
|
6697
|
-
return {
|
|
6698
|
-
ok: false,
|
|
6699
|
-
error: `${bin} sandbox ${head}: --version conflicts with --image=<ref> \u2014 pass --version for an official release or --image=<ref> for a custom image`
|
|
6700
|
-
};
|
|
6701
|
-
}
|
|
6702
|
-
args.image = true;
|
|
6703
|
-
args.imageRef = officialImageRef(version);
|
|
6704
|
-
}
|
|
6705
|
-
if (head === "list") {
|
|
6706
|
-
if (positionals.length > 0) {
|
|
6707
|
-
return { ok: false, error: `${bin} sandbox list: unexpected argument "${positionals[0]}"` };
|
|
6708
|
-
}
|
|
6709
|
-
return { ok: true, args };
|
|
6710
|
-
}
|
|
6711
|
-
if (positionals.length === 0) {
|
|
6712
|
-
return {
|
|
6713
|
-
ok: false,
|
|
6714
|
-
error: `${bin} sandbox install: a runtime is required, e.g. \`${bin} sandbox install docker\``,
|
|
6715
|
-
showHelp: true
|
|
6716
|
-
};
|
|
6717
|
-
}
|
|
6718
|
-
if (positionals.length > 1) {
|
|
6719
|
-
return { ok: false, error: `${bin} sandbox install: unexpected argument "${positionals[1]}"` };
|
|
6720
|
-
}
|
|
6721
|
-
args.runtime = positionals[0];
|
|
6722
|
-
return { ok: true, args };
|
|
6723
|
-
}
|
|
6724
7088
|
function parseShellArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6725
7089
|
const args = { help: false };
|
|
6726
|
-
if (
|
|
7090
|
+
if (isHelpFlag2(rest[0])) return { ok: true, args: { help: true } };
|
|
6727
7091
|
for (const token of rest) {
|
|
6728
7092
|
if (token.startsWith("-")) {
|
|
6729
7093
|
return { ok: false, error: `${bin} shell: unknown flag "${token}"` };
|
|
@@ -6737,7 +7101,7 @@ function parseShellArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6737
7101
|
}
|
|
6738
7102
|
function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6739
7103
|
const args = { help: false, json: false };
|
|
6740
|
-
if (rest.length === 0 ||
|
|
7104
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6741
7105
|
return { ok: true, args: { ...args, help: true } };
|
|
6742
7106
|
}
|
|
6743
7107
|
const positionals = [];
|
|
@@ -6773,7 +7137,7 @@ function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6773
7137
|
return { ok: false, error: `${bin} workspace: --ref requires a branch or tag` };
|
|
6774
7138
|
}
|
|
6775
7139
|
const [sub, name, ...extra] = positionals;
|
|
6776
|
-
if (
|
|
7140
|
+
if (!isOneOf(WORKSPACE_SUBCOMMANDS, sub)) {
|
|
6777
7141
|
return {
|
|
6778
7142
|
ok: false,
|
|
6779
7143
|
error: `${bin} workspace: unknown subcommand "${sub}" \u2014 expected create or list`,
|
|
@@ -6809,7 +7173,7 @@ function parseHarnessArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6809
7173
|
host: false,
|
|
6810
7174
|
force: false
|
|
6811
7175
|
};
|
|
6812
|
-
if (rest.length === 0 ||
|
|
7176
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6813
7177
|
return { ok: true, args: { ...args, help: true } };
|
|
6814
7178
|
}
|
|
6815
7179
|
const positionals = [];
|
|
@@ -6859,7 +7223,7 @@ function parseHarnessArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6859
7223
|
};
|
|
6860
7224
|
}
|
|
6861
7225
|
const [sub, name, ...extra] = positionals;
|
|
6862
|
-
if (
|
|
7226
|
+
if (!isOneOf(HARNESS_SUBCOMMANDS, sub)) {
|
|
6863
7227
|
return {
|
|
6864
7228
|
ok: false,
|
|
6865
7229
|
error: `${bin} harness: unknown subcommand "${sub}" \u2014 expected list, install, uninstall, or status`,
|
|
@@ -6890,7 +7254,7 @@ function parseHarnessArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6890
7254
|
}
|
|
6891
7255
|
function parseToolArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
6892
7256
|
const args = { help: false, yes: false, json: false, host: false, force: false };
|
|
6893
|
-
if (rest.length === 0 ||
|
|
7257
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) {
|
|
6894
7258
|
return { ok: true, args: { ...args, help: true } };
|
|
6895
7259
|
}
|
|
6896
7260
|
const positionals = [];
|
|
@@ -6916,7 +7280,7 @@ function parseToolArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6916
7280
|
return { ok: false, error: `${bin} tool: --path requires a directory` };
|
|
6917
7281
|
}
|
|
6918
7282
|
const [sub, name, ...extra] = positionals;
|
|
6919
|
-
if (
|
|
7283
|
+
if (!isOneOf(TOOL_SUBCOMMANDS, sub)) {
|
|
6920
7284
|
return {
|
|
6921
7285
|
ok: false,
|
|
6922
7286
|
error: `${bin} tool: unknown subcommand "${sub}" \u2014 expected list, install, uninstall, or status`,
|
|
@@ -6948,11 +7312,11 @@ function parseGatewayArgs(rest) {
|
|
|
6948
7312
|
}
|
|
6949
7313
|
return { ok: true, args: { help: false, passthrough: [...rest] } };
|
|
6950
7314
|
}
|
|
6951
|
-
function bundledPayloadExists(bundled, exists =
|
|
6952
|
-
return exists(
|
|
7315
|
+
function bundledPayloadExists(bundled, exists = existsSync23) {
|
|
7316
|
+
return exists(join19(bundled.sourceControlDir, "manifest.json"));
|
|
6953
7317
|
}
|
|
6954
7318
|
function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
6955
|
-
const exists = bundled.exists ??
|
|
7319
|
+
const exists = bundled.exists ?? existsSync23;
|
|
6956
7320
|
if (args.fromDir !== void 0) {
|
|
6957
7321
|
return { kind: "local", fromDir: resolve11(args.fromDir) };
|
|
6958
7322
|
}
|
|
@@ -6972,7 +7336,7 @@ function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
|
6972
7336
|
function readPayloadVersion(checkoutDir) {
|
|
6973
7337
|
try {
|
|
6974
7338
|
const parsed = JSON.parse(
|
|
6975
|
-
|
|
7339
|
+
readFileSync13(join19(resolveControlDir(checkoutDir), "cli", "package.json"), "utf8")
|
|
6976
7340
|
);
|
|
6977
7341
|
if (parsed && typeof parsed.version === "string") return parsed.version;
|
|
6978
7342
|
} catch {
|
|
@@ -6981,7 +7345,7 @@ function readPayloadVersion(checkoutDir) {
|
|
|
6981
7345
|
}
|
|
6982
7346
|
async function runWithRemoteSource(hooks, fn) {
|
|
6983
7347
|
const fetch2 = hooks.fetch ?? fetchRemoteSource;
|
|
6984
|
-
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(
|
|
7348
|
+
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(join19(tmpdir4(), "oh-remote-")));
|
|
6985
7349
|
const rm = hooks.rm ?? ((dir) => rmSync6(dir, { recursive: true, force: true }));
|
|
6986
7350
|
const out = hooks.stdout ?? ((s) => process.stdout.write(s));
|
|
6987
7351
|
const checkoutDir = mkdtemp();
|
|
@@ -6998,7 +7362,7 @@ async function main(argv) {
|
|
|
6998
7362
|
const product = resolveProduct(process.argv[1]);
|
|
6999
7363
|
const bin = product.bin;
|
|
7000
7364
|
const [first, second] = argv;
|
|
7001
|
-
if (!first ||
|
|
7365
|
+
if (!first || isHelpFlag2(first)) {
|
|
7002
7366
|
printAgroHelp(product);
|
|
7003
7367
|
return 0;
|
|
7004
7368
|
}
|
|
@@ -7007,6 +7371,7 @@ async function main(argv) {
|
|
|
7007
7371
|
`);
|
|
7008
7372
|
return 0;
|
|
7009
7373
|
}
|
|
7374
|
+
if (!Object.hasOwn(AGRO_COMMANDS, first)) return unknownCommand(first, product);
|
|
7010
7375
|
if (first === "config") {
|
|
7011
7376
|
const parsed = parseConfigArgs(argv.slice(1), bin);
|
|
7012
7377
|
if (!parsed.ok) {
|
|
@@ -7141,37 +7506,7 @@ async function main(argv) {
|
|
|
7141
7506
|
(checkoutDir) => runUpdate({ bin, targetDir, fromDir: checkoutDir, force, dryRun }, io)
|
|
7142
7507
|
);
|
|
7143
7508
|
}
|
|
7144
|
-
if (first === "sandbox")
|
|
7145
|
-
const parsed = parseSandboxArgs(argv.slice(1), bin);
|
|
7146
|
-
if (!parsed.ok) {
|
|
7147
|
-
process.stderr.write(`${parsed.error}
|
|
7148
|
-
`);
|
|
7149
|
-
if (parsed.showHelp) printSandboxHelp(bin);
|
|
7150
|
-
return 1;
|
|
7151
|
-
}
|
|
7152
|
-
const a = parsed.args;
|
|
7153
|
-
if (a.help) {
|
|
7154
|
-
printSandboxHelp(bin);
|
|
7155
|
-
return a.subcommand === void 0 ? 1 : 0;
|
|
7156
|
-
}
|
|
7157
|
-
const io = lifecycleIo();
|
|
7158
|
-
if (a.subcommand === "list") return await runSandboxList({ bin, json: a.json }, io);
|
|
7159
|
-
return await runSandboxInstall(
|
|
7160
|
-
{
|
|
7161
|
-
bin,
|
|
7162
|
-
runtime: a.runtime,
|
|
7163
|
-
...a.name !== void 0 ? { name: a.name } : {},
|
|
7164
|
-
...a.checkout !== void 0 ? { checkout: a.checkout } : {},
|
|
7165
|
-
...a.homeMount !== void 0 ? { homeMount: a.homeMount } : {},
|
|
7166
|
-
yes: a.yes,
|
|
7167
|
-
image: a.image,
|
|
7168
|
-
...a.imageRef !== void 0 ? { imageRef: a.imageRef } : {},
|
|
7169
|
-
noBuild: a.noBuild,
|
|
7170
|
-
printArgv: a.printArgv
|
|
7171
|
-
},
|
|
7172
|
-
io
|
|
7173
|
-
);
|
|
7174
|
-
}
|
|
7509
|
+
if (first === "sandbox") return runSandboxCommand(argv.slice(1), bin);
|
|
7175
7510
|
if (first === "shell") {
|
|
7176
7511
|
const parsed = parseShellArgs(argv.slice(1), bin);
|
|
7177
7512
|
if (!parsed.ok) {
|
|
@@ -7256,7 +7591,7 @@ async function main(argv) {
|
|
|
7256
7591
|
if (composeVerbs().includes(first)) {
|
|
7257
7592
|
const verb = first;
|
|
7258
7593
|
const rest = argv.slice(1);
|
|
7259
|
-
if (
|
|
7594
|
+
if (isHelpFlag2(rest[0])) {
|
|
7260
7595
|
printComposeVerbHelp(verb, bin);
|
|
7261
7596
|
return 0;
|
|
7262
7597
|
}
|
|
@@ -7359,7 +7694,10 @@ async function main(argv) {
|
|
|
7359
7694
|
}
|
|
7360
7695
|
return runGateway(parsed.args.passthrough, { bin });
|
|
7361
7696
|
}
|
|
7362
|
-
|
|
7697
|
+
return unknownCommand(first, product);
|
|
7698
|
+
}
|
|
7699
|
+
function unknownCommand(first, product) {
|
|
7700
|
+
process.stderr.write(`${product.bin}: unknown command "${first}"
|
|
7363
7701
|
|
|
7364
7702
|
`);
|
|
7365
7703
|
printAgroHelp(product);
|
|
@@ -7381,12 +7719,9 @@ main(process.argv.slice(2)).then(
|
|
|
7381
7719
|
}
|
|
7382
7720
|
);
|
|
7383
7721
|
export {
|
|
7384
|
-
CONFIG_VERBS,
|
|
7385
|
-
LANGFUSE_VERBS,
|
|
7386
|
-
SECRET_VERBS,
|
|
7387
7722
|
bundledPayloadExists,
|
|
7388
7723
|
extractSandboxFlag,
|
|
7389
|
-
isHelpFlag,
|
|
7724
|
+
isHelpFlag2 as isHelpFlag,
|
|
7390
7725
|
isVersionFlag,
|
|
7391
7726
|
parseComposeArgs,
|
|
7392
7727
|
parseConfigArgs,
|