@mifune/agro 0.20.0-rc.2 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/agro.js +236 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The **AGRO CLI** (`agro`) — create an [AGRO](https://github.com/mifunedev/agro
|
|
|
4
4
|
Docker sandbox for coding agents and drive its lifecycle from the command line.
|
|
5
5
|
|
|
6
6
|
AGRO is a portable harness for running coding agents (Claude Code, Codex, Pi,
|
|
7
|
-
and others) in an isolated Docker sandbox, with the agent's identity
|
|
7
|
+
and others) in an isolated Docker sandbox, with the agent's identity and skills
|
|
8
8
|
versioned in git. This package is the standalone `agro` CLI that creates sandboxes and
|
|
9
9
|
manages their lifecycle.
|
|
10
10
|
|
|
@@ -54,7 +54,7 @@ To bind one of your own checkouts into the sandbox, equip it and pass `--checkou
|
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
56
|
cd your-project
|
|
57
|
-
agro vendor # vendor .agro/
|
|
57
|
+
agro vendor # vendor .agro/ — and nothing else (compatibility window)
|
|
58
58
|
agro sandbox install docker --checkout "$PWD" --name your-project
|
|
59
59
|
```
|
|
60
60
|
|
|
@@ -133,7 +133,7 @@ conflicting `AGRO_SANDBOX_IMAGE` values in the host shell or entry `.env` before
|
|
|
133
133
|
|
|
134
134
|
`agro update` upgrades only the installed CLI. It does not recreate the sandbox
|
|
135
135
|
or change its image. During the compatibility window `agro vendor` vendors
|
|
136
|
-
`.agro/`
|
|
136
|
+
`.agro/` into the current directory, equipping an empty checkout and upgrading an
|
|
137
137
|
equipped one (`--from <dir>` / `--from-remote [--ref <ref>]`, `--dry-run`, `--force`); it
|
|
138
138
|
writes nothing else and never prompts. `agro update` rejects those flags and points at
|
|
139
139
|
`agro vendor`. `agro vendor` prefers the payload bundled into the CLI itself; with `--from-remote`
|
package/dist/agro.js
CHANGED
|
@@ -908,6 +908,7 @@ var RESERVED_HOME_PATHS = [
|
|
|
908
908
|
"/usr",
|
|
909
909
|
"/var"
|
|
910
910
|
];
|
|
911
|
+
var EGRESS_MODES = ["open", "allowlist"];
|
|
911
912
|
var SANDBOX_RUNTIMES = PROVISIONABLE_RUNTIMES;
|
|
912
913
|
function configCheckout(config) {
|
|
913
914
|
const value = config.checkout ?? config.repo;
|
|
@@ -927,11 +928,11 @@ function defaultAgroConfig(name) {
|
|
|
927
928
|
ssh: false,
|
|
928
929
|
sshPort: 2222,
|
|
929
930
|
sshPasswordAuth: false,
|
|
930
|
-
dockerSocket: false
|
|
931
|
+
dockerSocket: false,
|
|
932
|
+
egress: "open",
|
|
933
|
+
egressAllow: []
|
|
931
934
|
},
|
|
932
935
|
hermesDashboard: { enabled: false, port: 9119 },
|
|
933
|
-
cron: { agentBin: "claude" },
|
|
934
|
-
build: { skipPnpmInstall: false },
|
|
935
936
|
image: { mode: "build", pullPolicy: "missing" },
|
|
936
937
|
langfuse: {},
|
|
937
938
|
composeOverrides: []
|
|
@@ -1019,16 +1020,14 @@ function validateAgroConfig(value) {
|
|
|
1019
1020
|
expectBoolean(access, "sshPasswordAuth", "access.");
|
|
1020
1021
|
expectString(access, "sshAuthorizedKeys", "access.");
|
|
1021
1022
|
expectBoolean(access, "dockerSocket", "access.");
|
|
1023
|
+
expectEnum(access, "egress", "access.", EGRESS_MODES);
|
|
1024
|
+
expectStringArray(access, "egressAllow", "access.");
|
|
1022
1025
|
}
|
|
1023
1026
|
const dashboard = expectSection(record, "hermesDashboard");
|
|
1024
1027
|
if (dashboard) {
|
|
1025
1028
|
expectBoolean(dashboard, "enabled", "hermesDashboard.");
|
|
1026
1029
|
expectPort(dashboard, "port", "hermesDashboard.");
|
|
1027
1030
|
}
|
|
1028
|
-
const cron = expectSection(record, "cron");
|
|
1029
|
-
if (cron) expectString(cron, "agentBin", "cron.");
|
|
1030
|
-
const build = expectSection(record, "build");
|
|
1031
|
-
if (build) expectBoolean(build, "skipPnpmInstall", "build.");
|
|
1032
1031
|
const image = expectSection(record, "image");
|
|
1033
1032
|
if (image) {
|
|
1034
1033
|
expectString(image, "ref", "image.");
|
|
@@ -1086,6 +1085,16 @@ function expectPort(record, key, prefix = "") {
|
|
|
1086
1085
|
throw fieldError(`${prefix}${key}`, "must be an integer between 1 and 65535");
|
|
1087
1086
|
}
|
|
1088
1087
|
}
|
|
1088
|
+
function expectStringArray(record, key, prefix = "") {
|
|
1089
|
+
const value = record[key];
|
|
1090
|
+
if (value === void 0) return;
|
|
1091
|
+
if (!Array.isArray(value)) throw fieldError(`${prefix}${key}`, "must be an array of strings");
|
|
1092
|
+
for (const entry of value) {
|
|
1093
|
+
if (typeof entry !== "string") {
|
|
1094
|
+
throw fieldError(`${prefix}${key}`, "must be an array of strings");
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1089
1098
|
function expectEnum(record, key, prefix, allowed) {
|
|
1090
1099
|
const value = record[key];
|
|
1091
1100
|
if (value === void 0) return;
|
|
@@ -1106,10 +1115,10 @@ var AGRO_CONFIG_FIELDS = [
|
|
|
1106
1115
|
{ path: "access.sshPasswordAuth", type: "boolean" },
|
|
1107
1116
|
{ path: "access.sshAuthorizedKeys", type: "string" },
|
|
1108
1117
|
{ path: "access.dockerSocket", type: "boolean" },
|
|
1118
|
+
{ path: "access.egress", type: "enum", values: EGRESS_MODES },
|
|
1119
|
+
{ path: "access.egressAllow", type: "list" },
|
|
1109
1120
|
{ path: "hermesDashboard.enabled", type: "boolean" },
|
|
1110
1121
|
{ path: "hermesDashboard.port", type: "port" },
|
|
1111
|
-
{ path: "cron.agentBin", type: "string" },
|
|
1112
|
-
{ path: "build.skipPnpmInstall", type: "boolean" },
|
|
1113
1122
|
{ path: "image.ref", type: "string" },
|
|
1114
1123
|
{ path: "image.mode", type: "enum", values: ["build", "image"] },
|
|
1115
1124
|
{ path: "image.pullPolicy", type: "enum", values: ["missing", "always", "never"] },
|
|
@@ -1462,10 +1471,10 @@ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync as read
|
|
|
1462
1471
|
import { dirname as dirname4, join as join4, resolve as resolve5, sep as sep2 } from "node:path";
|
|
1463
1472
|
|
|
1464
1473
|
// agro-asset:/home/runner/work/agro/agro/.devcontainer/docker-compose.yml
|
|
1465
|
-
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:-
|
|
1474
|
+
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:-}\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 init: true\n entrypoint: ["/usr/local/bin/sandbox-main.sh"]\n stop_signal: SIGTERM\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';
|
|
1466
1475
|
|
|
1467
1476
|
// agro-asset:/home/runner/work/agro/agro/.devcontainer/docker-compose.image-only.yml
|
|
1468
|
-
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:-
|
|
1477
|
+
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:-}\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 init: true\n entrypoint: ["/usr/local/bin/sandbox-main.sh"]\n stop_signal: SIGTERM\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';
|
|
1469
1478
|
|
|
1470
1479
|
// agro-asset:/home/runner/work/agro/agro/.devcontainer/docker-compose.ssh.yml
|
|
1471
1480
|
var docker_compose_ssh_default = 'services:\n sandbox:\n ports:\n - "127.0.0.1:${SANDBOX_SSH_PORT:-2222}:22"\n';
|
|
@@ -1473,6 +1482,9 @@ var docker_compose_ssh_default = 'services:\n sandbox:\n ports:\n - "12
|
|
|
1473
1482
|
// agro-asset:/home/runner/work/agro/agro/.devcontainer/docker-compose.docker-sock.yml
|
|
1474
1483
|
var docker_compose_docker_sock_default = "services:\n sandbox:\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n";
|
|
1475
1484
|
|
|
1485
|
+
// agro-asset:/home/runner/work/agro/agro/.devcontainer/docker-compose.egress.yml
|
|
1486
|
+
var docker_compose_egress_default = 'services:\n sandbox:\n networks:\n - egress-internal\n depends_on:\n - proxy\n environment:\n - HTTPS_PROXY=http://proxy:3128\n - HTTP_PROXY=http://proxy:3128\n - https_proxy=http://proxy:3128\n - http_proxy=http://proxy:3128\n - NO_PROXY=localhost,127.0.0.1\n - no_proxy=localhost,127.0.0.1\n - NODE_USE_ENV_PROXY=1\n\n proxy:\n image: ubuntu/squid@sha256:6a097f68bae708cedbabd6188d68c7e2e7a38cedd05a176e1cc0ba29e3bbe029\n networks:\n - egress-internal\n - egress-out\n configs:\n - source: egress-squid\n target: /etc/squid/squid.conf\n volumes:\n - type: bind\n source: ../egress-allow.txt\n target: /etc/squid/egress-allow.txt\n read_only: true\n restart: unless-stopped\n\nconfigs:\n egress-squid:\n content: |\n http_port 3128\n acl SSL_ports port 443\n acl Safe_ports port 80\n acl Safe_ports port 443\n acl CONNECT method CONNECT\n acl egress_allow dstdomain "/etc/squid/egress-allow.txt"\n http_access deny !Safe_ports\n http_access deny CONNECT !SSL_ports\n http_access allow egress_allow\n http_access deny all\n\nnetworks:\n egress-internal:\n internal: true\n egress-out: {}\n';
|
|
1487
|
+
|
|
1476
1488
|
// agro-asset:/home/runner/work/agro/agro/.agro/scripts/docker-compose.sh
|
|
1477
1489
|
var docker_compose_default2 = `#!/usr/bin/env bash
|
|
1478
1490
|
|
|
@@ -1612,6 +1624,11 @@ fi
|
|
|
1612
1624
|
|
|
1613
1625
|
args+=(-f "$(compose_path ".devcontainer/docker-compose.yml")")
|
|
1614
1626
|
|
|
1627
|
+
egress_value=\${AGRO_EGRESS:-$(read_env_value AGRO_EGRESS)}
|
|
1628
|
+
if [ "$(printf '%s' "$egress_value" | tr '[:upper:]' '[:lower:]')" = "allowlist" ]; then
|
|
1629
|
+
args+=(-f "$(compose_path ".devcontainer/docker-compose.egress.yml")")
|
|
1630
|
+
fi
|
|
1631
|
+
|
|
1615
1632
|
docker_socket_value=\${DOCKER_SOCKET:-$(read_env_value DOCKER_SOCKET)}
|
|
1616
1633
|
if truthy "$docker_socket_value"; then
|
|
1617
1634
|
args+=(-f "$(compose_path ".devcontainer/docker-compose.docker-sock.yml")")
|
|
@@ -1803,7 +1820,7 @@ exit 0
|
|
|
1803
1820
|
`;
|
|
1804
1821
|
|
|
1805
1822
|
// agro-asset:/home/runner/work/agro/agro/.devcontainer/openshell-policy.yaml
|
|
1806
|
-
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";
|
|
1823
|
+
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 - host: platform.claude.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";
|
|
1807
1824
|
|
|
1808
1825
|
// src/lib/registry.ts
|
|
1809
1826
|
var SANDBOX_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
@@ -1864,9 +1881,29 @@ function nextDefaultName(run = spawnRunner) {
|
|
|
1864
1881
|
function openshellFiles(entry) {
|
|
1865
1882
|
return [{ dest: join4(entry, "openshell-policy.yaml"), body: openshell_policy_default, mode: 420 }];
|
|
1866
1883
|
}
|
|
1867
|
-
function
|
|
1884
|
+
function policyHosts(policyText) {
|
|
1885
|
+
const marker = "network_policies:";
|
|
1886
|
+
const start = policyText.indexOf(marker);
|
|
1887
|
+
const section = start === -1 ? "" : policyText.slice(start);
|
|
1888
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
1889
|
+
for (const match of section.matchAll(/^\s*-\s*host:\s*(\S+)/gm)) hosts.add(match[1]);
|
|
1890
|
+
return [...hosts];
|
|
1891
|
+
}
|
|
1892
|
+
function egressAllowHosts(config) {
|
|
1893
|
+
const hosts = new Set(policyHosts(openshell_policy_default));
|
|
1894
|
+
for (const host of config.access?.egressAllow ?? []) hosts.add(host);
|
|
1895
|
+
return [...hosts].sort();
|
|
1896
|
+
}
|
|
1897
|
+
function safeReadAgroConfig(entry) {
|
|
1898
|
+
try {
|
|
1899
|
+
return readAgroConfig(agroConfigPath(entry));
|
|
1900
|
+
} catch {
|
|
1901
|
+
return { version: 1 };
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
function dockerFiles(entry, opts, config) {
|
|
1868
1905
|
const scripts = join4(resolveProjectLayout(entry).controlDir, "scripts");
|
|
1869
|
-
|
|
1906
|
+
const files = [
|
|
1870
1907
|
{
|
|
1871
1908
|
dest: join4(entry, ".devcontainer", "docker-compose.yml"),
|
|
1872
1909
|
body: (opts.checkout ?? opts.repo) === void 0 ? docker_compose_image_only_default : docker_compose_default,
|
|
@@ -1874,14 +1911,25 @@ function dockerFiles(entry, opts) {
|
|
|
1874
1911
|
},
|
|
1875
1912
|
{ dest: join4(entry, ".devcontainer", "docker-compose.ssh.yml"), body: docker_compose_ssh_default, mode: 420 },
|
|
1876
1913
|
{ dest: join4(entry, ".devcontainer", "docker-compose.docker-sock.yml"), body: docker_compose_docker_sock_default, mode: 420 },
|
|
1914
|
+
{ dest: join4(entry, ".devcontainer", "docker-compose.egress.yml"), body: docker_compose_egress_default, mode: 420 },
|
|
1877
1915
|
{ dest: join4(scripts, "docker-compose.sh"), body: docker_compose_default2, mode: 493 },
|
|
1878
1916
|
{ dest: join4(scripts, "paths.sh"), body: paths_default, mode: 420 },
|
|
1879
1917
|
{ dest: join4(scripts, "check-host-port.sh"), body: check_host_port_default, mode: 493 }
|
|
1880
1918
|
];
|
|
1919
|
+
if ((config.access?.egress ?? "open") === "allowlist") {
|
|
1920
|
+
files.push({
|
|
1921
|
+
dest: join4(entry, "egress-allow.txt"),
|
|
1922
|
+
body: `${egressAllowHosts(config).join("\n")}
|
|
1923
|
+
`,
|
|
1924
|
+
mode: 420
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
return files;
|
|
1881
1928
|
}
|
|
1882
1929
|
function materialize(root, opts = {}) {
|
|
1883
1930
|
const entry = resolve5(root);
|
|
1884
|
-
const
|
|
1931
|
+
const config = safeReadAgroConfig(entry);
|
|
1932
|
+
const files = entryRuntime(entry) === "openshell" ? openshellFiles(entry) : dockerFiles(entry, opts, config);
|
|
1885
1933
|
for (const file of files) {
|
|
1886
1934
|
const dest = resolve5(file.dest);
|
|
1887
1935
|
if (!dest.startsWith(entry + sep2)) {
|
|
@@ -1921,6 +1969,7 @@ function resolveSandboxRoot(opts = {}) {
|
|
|
1921
1969
|
}
|
|
1922
1970
|
|
|
1923
1971
|
// src/lib/secrets.ts
|
|
1972
|
+
import { randomBytes } from "node:crypto";
|
|
1924
1973
|
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
|
|
1925
1974
|
import { resolve as resolve6 } from "node:path";
|
|
1926
1975
|
|
|
@@ -2020,6 +2069,10 @@ function setSecret(root, key, value) {
|
|
|
2020
2069
|
const { content } = setKeyInEnv(original, key, value);
|
|
2021
2070
|
writeEnvFile(file, content);
|
|
2022
2071
|
}
|
|
2072
|
+
function ensureSandboxPassword(root) {
|
|
2073
|
+
if (readSecret(root, "SANDBOX_PASSWORD") !== void 0) return;
|
|
2074
|
+
setSecret(root, "SANDBOX_PASSWORD", randomBytes(24).toString("base64url"));
|
|
2075
|
+
}
|
|
2023
2076
|
function listSecretKeys(root) {
|
|
2024
2077
|
const file = secretsFilePath(root);
|
|
2025
2078
|
if (!existsSync8(file)) return [];
|
|
@@ -2046,7 +2099,7 @@ import { basename as basename6, join as join6, sep as sep3 } from "node:path";
|
|
|
2046
2099
|
import { basename as basename4, resolve as resolve7 } from "node:path";
|
|
2047
2100
|
|
|
2048
2101
|
// src/lib/version.ts
|
|
2049
|
-
var AGRO_VERSION = true ? "0.
|
|
2102
|
+
var AGRO_VERSION = true ? "0.21.0" : "0.0.0-dev";
|
|
2050
2103
|
var OFFICIAL_IMAGE = "ghcr.io/mifunedev/agro";
|
|
2051
2104
|
var RELEASE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z][a-z0-9]*\.[0-9]+)?$/;
|
|
2052
2105
|
function parseReleaseVersion(value) {
|
|
@@ -2621,6 +2674,7 @@ function renderComposeVars(config) {
|
|
|
2621
2674
|
put("GIT_USER_NAME", config.git?.userName);
|
|
2622
2675
|
put("GIT_USER_EMAIL", config.git?.userEmail);
|
|
2623
2676
|
put("DOCKER_SOCKET", config.access?.dockerSocket);
|
|
2677
|
+
put("AGRO_EGRESS", config.access?.egress);
|
|
2624
2678
|
put("SANDBOX_SSH", config.access?.ssh);
|
|
2625
2679
|
put("SANDBOX_SSH_PORT", config.access?.sshPort);
|
|
2626
2680
|
put("AGRO_SANDBOX_IMAGE", sandboxImageRef(config));
|
|
@@ -2848,6 +2902,7 @@ async function runSandbox(opts, io) {
|
|
|
2848
2902
|
}
|
|
2849
2903
|
await maybePromptDockerSocket(root, io, opts.bin);
|
|
2850
2904
|
requireLifecycleScript(root, "docker-compose.sh");
|
|
2905
|
+
ensureSandboxPassword(root);
|
|
2851
2906
|
if (imageRef !== void 0) {
|
|
2852
2907
|
io.stdout(`image mode: ${imageRef} (skipping local build)
|
|
2853
2908
|
`);
|
|
@@ -3388,7 +3443,26 @@ ${secretKeyList()}
|
|
|
3388
3443
|
`);
|
|
3389
3444
|
return 1;
|
|
3390
3445
|
}
|
|
3391
|
-
io.stdout(`.env: set ${key}
|
|
3446
|
+
io.stdout(`.env: set ${key}
|
|
3447
|
+
`);
|
|
3448
|
+
return 0;
|
|
3449
|
+
}
|
|
3450
|
+
async function runSecretGet(key, opts, io) {
|
|
3451
|
+
const root = secretsRoot(opts);
|
|
3452
|
+
let value;
|
|
3453
|
+
try {
|
|
3454
|
+
value = readSecret(root, key);
|
|
3455
|
+
} catch (error) {
|
|
3456
|
+
io.stderr(`${error instanceof Error ? error.message : String(error)}
|
|
3457
|
+
`);
|
|
3458
|
+
return 1;
|
|
3459
|
+
}
|
|
3460
|
+
if (value === void 0) {
|
|
3461
|
+
io.stderr(`${opts.bin} secret get: ${key} has no value
|
|
3462
|
+
`);
|
|
3463
|
+
return 1;
|
|
3464
|
+
}
|
|
3465
|
+
io.stdout(`${value}
|
|
3392
3466
|
`);
|
|
3393
3467
|
return 0;
|
|
3394
3468
|
}
|
|
@@ -3403,7 +3477,7 @@ async function runSecretList(opts, io) {
|
|
|
3403
3477
|
const width = Math.max(...keys.map((key) => key.length));
|
|
3404
3478
|
for (const key of keys) {
|
|
3405
3479
|
const value = readSecret(root, key);
|
|
3406
|
-
io.stdout(`${key.padEnd(width)} ${value === void 0 ? "\u2014" :
|
|
3480
|
+
io.stdout(`${key.padEnd(width)} ${value === void 0 ? "\u2014" : "set"}
|
|
3407
3481
|
`);
|
|
3408
3482
|
}
|
|
3409
3483
|
return 0;
|
|
@@ -3751,7 +3825,7 @@ function quoteFragmentValue(key, value) {
|
|
|
3751
3825
|
if (value === "") throw new Error(`${key} must not be empty`);
|
|
3752
3826
|
if (UNCARRIABLE_IN_VALUE.test(value)) {
|
|
3753
3827
|
throw new Error(
|
|
3754
|
-
`${key} must not contain a newline, a control character, or a single quote \u2014 zsh
|
|
3828
|
+
`${key} must not contain a newline, a control character, or a single quote \u2014 zsh reads the fragment and does not carry that value safely`
|
|
3755
3829
|
);
|
|
3756
3830
|
}
|
|
3757
3831
|
return BARE_SAFE_VALUE.test(value) ? value : `'${value}'`;
|
|
@@ -4968,6 +5042,59 @@ async function runSandboxCommand(rest, bin) {
|
|
|
4968
5042
|
);
|
|
4969
5043
|
}
|
|
4970
5044
|
|
|
5045
|
+
// src/commands/egress.ts
|
|
5046
|
+
import { basename as basename7 } from "node:path";
|
|
5047
|
+
var EGRESS_LOG_PATH = "/var/log/squid/access.log";
|
|
5048
|
+
var EGRESS_LOG_LINES = 1e3;
|
|
5049
|
+
var PROXY_EXEC = ["exec", "-T", "proxy"];
|
|
5050
|
+
var EGRESS_COMMANDS = Object.freeze({
|
|
5051
|
+
log: Object.freeze([...PROXY_EXEC, "tail", "-n", String(EGRESS_LOG_LINES), EGRESS_LOG_PATH]),
|
|
5052
|
+
reload: Object.freeze([...PROXY_EXEC, "squid", "-k", "reconfigure"])
|
|
5053
|
+
});
|
|
5054
|
+
var RUNTIME_REFUSAL = Object.freeze({
|
|
5055
|
+
docker: void 0,
|
|
5056
|
+
openshell: (name) => `the sandbox ${name} uses runtime openshell; access.egress allowlist applies to the docker runtime`
|
|
5057
|
+
});
|
|
5058
|
+
function markDenied(text) {
|
|
5059
|
+
return text.split("\n").map((line) => line.includes("TCP_DENIED") ? `DENIED ${line}` : line).join("\n");
|
|
5060
|
+
}
|
|
5061
|
+
function refusal(root, name) {
|
|
5062
|
+
const runtimeRefusal = RUNTIME_REFUSAL[entryRuntime(root)];
|
|
5063
|
+
if (runtimeRefusal !== void 0) return runtimeRefusal(name);
|
|
5064
|
+
const egress = readAgroConfig(agroConfigPath(root)).access?.egress ?? "open";
|
|
5065
|
+
if (egress !== "allowlist") return `the sandbox ${name} uses access.egress ${egress}; set access.egress allowlist`;
|
|
5066
|
+
return void 0;
|
|
5067
|
+
}
|
|
5068
|
+
function rematerialize(root) {
|
|
5069
|
+
const repo = readAgroConfig(agroConfigPath(root)).repo;
|
|
5070
|
+
materialize(root, repo === void 0 || repo === "" ? {} : { repo });
|
|
5071
|
+
}
|
|
5072
|
+
function runEgress(verb, opts, io) {
|
|
5073
|
+
const run = opts.run ?? spawnRunner;
|
|
5074
|
+
const root = resolveSandboxRoot({ name: opts.name, cwd: opts.cwd });
|
|
5075
|
+
const name = basename7(root);
|
|
5076
|
+
const refused = refusal(root, name);
|
|
5077
|
+
if (refused !== void 0) {
|
|
5078
|
+
io.stderr(`${opts.bin} egress ${verb}: ${refused}
|
|
5079
|
+
`);
|
|
5080
|
+
return 1;
|
|
5081
|
+
}
|
|
5082
|
+
rematerialize(root);
|
|
5083
|
+
const script = requireLifecycleScript(root, "docker-compose.sh");
|
|
5084
|
+
const capture2 = verb === "log";
|
|
5085
|
+
return withComposeEnvFile(root, (extraArgs) => {
|
|
5086
|
+
const r = run("bash", [script, ...extraArgs, ...EGRESS_COMMANDS[verb]], {
|
|
5087
|
+
stdio: capture2 ? "capture" : "inherit"
|
|
5088
|
+
});
|
|
5089
|
+
assertSpawned(r, `bash ${script} egress ${verb}`);
|
|
5090
|
+
if (capture2) {
|
|
5091
|
+
if (r.stdout) io.stdout(markDenied(r.stdout));
|
|
5092
|
+
if (r.stderr) io.stderr(r.stderr);
|
|
5093
|
+
}
|
|
5094
|
+
return r.status ?? 1;
|
|
5095
|
+
});
|
|
5096
|
+
}
|
|
5097
|
+
|
|
4971
5098
|
// src/commands/harness.ts
|
|
4972
5099
|
import { existsSync as existsSync20 } from "node:fs";
|
|
4973
5100
|
import { homedir as homedir3 } from "node:os";
|
|
@@ -4985,7 +5112,7 @@ function sourceDocsUrl(docsPath) {
|
|
|
4985
5112
|
|
|
4986
5113
|
// src/lib/host-config.ts
|
|
4987
5114
|
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync12, renameSync as renameSync4, unlinkSync as unlinkSync6, writeFileSync as writeFileSync9 } from "node:fs";
|
|
4988
|
-
import { basename as
|
|
5115
|
+
import { basename as basename8, dirname as dirname8, isAbsolute, join as join15, resolve as resolve10 } from "node:path";
|
|
4989
5116
|
var HOST_CONFIG_MODE = 420;
|
|
4990
5117
|
var HOST_CONFIG_FILE = "config.json";
|
|
4991
5118
|
var WORKSPACES_SUBDIR = "workspaces";
|
|
@@ -5091,7 +5218,7 @@ function readHostConfig(env, home) {
|
|
|
5091
5218
|
try {
|
|
5092
5219
|
parsed = JSON.parse(raw);
|
|
5093
5220
|
} catch {
|
|
5094
|
-
throw new Error(`${
|
|
5221
|
+
throw new Error(`${basename8(path4)} is not valid JSON: ${path4}`);
|
|
5095
5222
|
}
|
|
5096
5223
|
return validateHostConfig(parsed);
|
|
5097
5224
|
}
|
|
@@ -5453,6 +5580,19 @@ async function validateHermesHome(target, io, bin, user, inheritedHome, root = s
|
|
|
5453
5580
|
}
|
|
5454
5581
|
return result.exitCode;
|
|
5455
5582
|
}
|
|
5583
|
+
async function seedHermes(target, io, bin, user, root = sandboxTargetRoot(target)) {
|
|
5584
|
+
const result = await target.exec({
|
|
5585
|
+
argv: remoteControlDirScript(root, "scripts/hermes-workspace.sh", ["seed", root, `${root}/.hermes`]),
|
|
5586
|
+
env: hermesEnv(root),
|
|
5587
|
+
...user ? { user } : {},
|
|
5588
|
+
stdio: "inherit"
|
|
5589
|
+
});
|
|
5590
|
+
if (result.exitCode !== 0) {
|
|
5591
|
+
io.stderr(`${bin} harness: could not write the Hermes defaults to ${root}/.hermes/config.yaml (exit ${result.exitCode}); nothing was installed.
|
|
5592
|
+
`);
|
|
5593
|
+
}
|
|
5594
|
+
return result.exitCode;
|
|
5595
|
+
}
|
|
5456
5596
|
async function configureHermes(target, io, bin, user, root = sandboxTargetRoot(target)) {
|
|
5457
5597
|
const result = await target.exec({
|
|
5458
5598
|
argv: remoteControlDirScript(root, "scripts/hermes-workspace.sh", [
|
|
@@ -5655,6 +5795,10 @@ Run: bash ${root}/.agro/scripts/link-providers.sh --init
|
|
|
5655
5795
|
if (openclaw) openclawLaunch(io, root);
|
|
5656
5796
|
return 0;
|
|
5657
5797
|
}
|
|
5798
|
+
if (hermes) {
|
|
5799
|
+
const code = await seedHermes(target, io, bin, void 0, root);
|
|
5800
|
+
if (code !== 0) return code;
|
|
5801
|
+
}
|
|
5658
5802
|
io.stdout(`${present && hasReceipt ? "updating" : "installing"} ${entry.title} on the host\u2026
|
|
5659
5803
|
`);
|
|
5660
5804
|
const r = await target.exec({
|
|
@@ -5863,6 +6007,10 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
5863
6007
|
io.stderr("docker is required to install into the running sandbox but was not found on PATH\n");
|
|
5864
6008
|
return 1;
|
|
5865
6009
|
}
|
|
6010
|
+
if (hermes) {
|
|
6011
|
+
const code = await seedHermes(target, io, opts.bin, "sandbox");
|
|
6012
|
+
if (code !== 0) return code;
|
|
6013
|
+
}
|
|
5866
6014
|
if (already && await sandboxMarkerExists(target, entry)) {
|
|
5867
6015
|
io.stdout(`updating ${entry.title} in the sandbox\u2026
|
|
5868
6016
|
`);
|
|
@@ -7101,12 +7249,13 @@ function fetchRemoteSource(opts) {
|
|
|
7101
7249
|
// src/command-table.ts
|
|
7102
7250
|
var CONFIG_VERBS = ["show", "set", "repo"];
|
|
7103
7251
|
var CONFIG_INTEGRATIONS = ["langfuse"];
|
|
7104
|
-
var SECRET_VERBS = ["set", "list"];
|
|
7252
|
+
var SECRET_VERBS = ["set", "get", "list"];
|
|
7105
7253
|
var LANGFUSE_VERBS = ["apply", "status", "disable"];
|
|
7106
7254
|
var SANDBOX_SUBCOMMANDS = ["install", "list", "upgrade"];
|
|
7107
7255
|
var WORKSPACE_SUBCOMMANDS = ["create", "list"];
|
|
7108
7256
|
var HARNESS_SUBCOMMANDS = ["list", "install", "status", "uninstall"];
|
|
7109
7257
|
var TOOL_SUBCOMMANDS = ["list", "install", "status", "uninstall"];
|
|
7258
|
+
var EGRESS_VERBS = ["log", "reload"];
|
|
7110
7259
|
function isOneOf(values, value) {
|
|
7111
7260
|
return value !== void 0 && values.includes(value);
|
|
7112
7261
|
}
|
|
@@ -7124,6 +7273,7 @@ var AGRO_COMMANDS = Object.freeze({
|
|
|
7124
7273
|
harness: HARNESS_SUBCOMMANDS,
|
|
7125
7274
|
tool: TOOL_SUBCOMMANDS,
|
|
7126
7275
|
gateway: null,
|
|
7276
|
+
egress: EGRESS_VERBS,
|
|
7127
7277
|
...Object.fromEntries(composeVerbs().map((verb) => [verb, null]))
|
|
7128
7278
|
});
|
|
7129
7279
|
|
|
@@ -7161,7 +7311,7 @@ Usage:
|
|
|
7161
7311
|
${bin} sandbox <args...> Create, list, and upgrade sandboxes (install|list|upgrade)
|
|
7162
7312
|
${bin} shell [name] Open a zsh shell in the running sandbox container
|
|
7163
7313
|
${bin} config <args...> Read and write ${stateNames(bin).configFile} (show|set), or run a wizard
|
|
7164
|
-
${bin} secret <args...> Read and write the gitignored root .env (set|list)
|
|
7314
|
+
${bin} secret <args...> Read and write the gitignored root .env (set|get|list)
|
|
7165
7315
|
${bin} langfuse <args...> Render, check, or disable Langfuse tracing files (apply|status|disable)
|
|
7166
7316
|
${bin} self-upgrade Upgrade the installed ${bin} CLI
|
|
7167
7317
|
${bin} vendor Vendor or upgrade the ${stateNames(bin).controlDir}/ control plane
|
|
@@ -7171,6 +7321,7 @@ Usage:
|
|
|
7171
7321
|
${bin} ps [name] Show sandbox service status
|
|
7172
7322
|
${bin} destroy [name] Remove the sandbox and wipe its named volumes
|
|
7173
7323
|
${bin} compose config Print the resolved docker compose configuration
|
|
7324
|
+
${bin} egress <args...> Read the egress proxy log or reload its allowlist (log|reload)
|
|
7174
7325
|
${bin} workspace <args...> Create and list host AGRO workspaces (create|list)
|
|
7175
7326
|
${bin} harness <args...> Install and inspect agent CLI harnesses
|
|
7176
7327
|
${bin} tool <args...> Install and inspect sandbox tooling
|
|
@@ -7214,14 +7365,16 @@ function printSecretHelp(bin = AGRO_PRODUCT.bin) {
|
|
|
7214
7365
|
|
|
7215
7366
|
Usage:
|
|
7216
7367
|
${bin} secret set <KEY> [--sandbox <name>] Prompt for the value (input hidden)
|
|
7368
|
+
${bin} secret get <KEY> [--sandbox <name>] Print the raw value
|
|
7217
7369
|
${bin} secret list [--sandbox <name>] List the keys that hold a value
|
|
7218
7370
|
|
|
7219
|
-
Without --sandbox
|
|
7220
|
-
the registry entry \`${bin} sandbox list\` names.
|
|
7371
|
+
Without --sandbox all three verbs act on the equipped project root; with it they
|
|
7372
|
+
act on the registry entry \`${bin} sandbox list\` names.
|
|
7221
7373
|
|
|
7222
7374
|
The value is never read from the command line \u2014 an argument would land in your
|
|
7223
|
-
shell history. \`${bin} secret
|
|
7224
|
-
|
|
7375
|
+
shell history. \`${bin} secret get\` is the only command that prints a raw value;
|
|
7376
|
+
\`${bin} secret list\` never does. .env is mode 0600 and gitignored; every non-secret
|
|
7377
|
+
setting belongs in ${stateNames(bin).configFile} (\`${bin} config set\`).
|
|
7225
7378
|
|
|
7226
7379
|
Keys:
|
|
7227
7380
|
${secretKeyList()}
|
|
@@ -7236,7 +7389,7 @@ Usage:
|
|
|
7236
7389
|
${bin} langfuse disable Set langfuse.enabled=false, delete the credential fragment, rewrite the files
|
|
7237
7390
|
|
|
7238
7391
|
\`${bin} config langfuse\` is the interactive wizard that writes these settings.
|
|
7239
|
-
\`apply\` never prompts and never installs anything; bootstrap
|
|
7392
|
+
\`apply\` never prompts and never installs anything; the bootstrap runs it.
|
|
7240
7393
|
It reads langfuse.* from ${stateNames(bin).configFile} and the keys from the gitignored .env,
|
|
7241
7394
|
and writes nothing when langfuse.enabled is not true. The generated files live in the
|
|
7242
7395
|
sandbox home, so \`apply\` and \`disable\` refuse to touch them from the host.
|
|
@@ -7279,7 +7432,7 @@ Flags:
|
|
|
7279
7432
|
--dry-run Report the installation kind, target, and versions without
|
|
7280
7433
|
changing anything.
|
|
7281
7434
|
|
|
7282
|
-
Project payload vendoring (${stateNames(bin).controlDir}/
|
|
7435
|
+
Project payload vendoring (${stateNames(bin).controlDir}/) is \`${bin} vendor\`; its --from,
|
|
7283
7436
|
--from-remote, --ref and --force flags are not accepted here.
|
|
7284
7437
|
`);
|
|
7285
7438
|
}
|
|
@@ -7289,7 +7442,7 @@ function printVendorHelp(bin = AGRO_PRODUCT.bin) {
|
|
|
7289
7442
|
Usage:
|
|
7290
7443
|
${bin} vendor [--from <dir> | --from-remote [--ref <ref>]] [--dry-run] [--force]
|
|
7291
7444
|
|
|
7292
|
-
Writes ONLY the ${stateNames(bin).controlDir}/ control plane
|
|
7445
|
+
Writes ONLY the ${stateNames(bin).controlDir}/ control plane (skills, scripts, CLI) into the
|
|
7293
7446
|
current directory. An empty directory is equipped from scratch; everything else
|
|
7294
7447
|
in your project is left untouched \u2014 it writes no ${stateNames(bin).configFile}, .env, AGENTS.md,
|
|
7295
7448
|
.gitignore or .devcontainer/.
|
|
@@ -7488,6 +7641,38 @@ Flags:
|
|
|
7488
7641
|
See ${sourceDocsUrl("docs/lifecycle-commands.md")} for the full mapping.
|
|
7489
7642
|
`);
|
|
7490
7643
|
}
|
|
7644
|
+
function printEgressHelp(bin = AGRO_PRODUCT.bin) {
|
|
7645
|
+
process.stdout.write(`${bin} egress \u2014 Read the egress proxy log or reload its allowlist
|
|
7646
|
+
|
|
7647
|
+
Usage:
|
|
7648
|
+
${bin} egress log [name] Print the last ${EGRESS_LOG_LINES} lines of the proxy access log; prefix denied lines with DENIED
|
|
7649
|
+
${bin} egress reload [name] Rewrite egress-allow.txt and send the proxy \`squid -k reconfigure\`
|
|
7650
|
+
|
|
7651
|
+
Both verbs run ${stateNames(bin).controlDir}/scripts/docker-compose.sh \`exec proxy\` in the sandbox entry.
|
|
7652
|
+
The log is ${EGRESS_LOG_PATH} in the proxy container. \`reload\` keeps the sandbox running.
|
|
7653
|
+
Both verbs need a docker sandbox with access.egress allowlist; any other sandbox exits 1.
|
|
7654
|
+
[name] is a sandbox entry from \`${bin} sandbox list\`; with exactly one registered sandbox it can be omitted.
|
|
7655
|
+
|
|
7656
|
+
See ${sourceDocsUrl("docs/lifecycle-commands.md")} for every verb.
|
|
7657
|
+
`);
|
|
7658
|
+
}
|
|
7659
|
+
function parseEgressArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
7660
|
+
if (rest.length === 0 || isHelpFlag2(rest[0])) return { ok: true, args: { help: true } };
|
|
7661
|
+
const [head, ...tail] = rest;
|
|
7662
|
+
if (!isOneOf(EGRESS_VERBS, head)) {
|
|
7663
|
+
return {
|
|
7664
|
+
ok: false,
|
|
7665
|
+
error: `${bin} egress: unknown subcommand "${head}" \u2014 expected ${EGRESS_VERBS.join(" or ")}`,
|
|
7666
|
+
showHelp: true
|
|
7667
|
+
};
|
|
7668
|
+
}
|
|
7669
|
+
if (isHelpFlag2(tail[0])) return { ok: true, args: { help: true } };
|
|
7670
|
+
if (tail.length > 0 && tail[0].startsWith("-")) {
|
|
7671
|
+
return { ok: false, error: `${bin} egress ${head}: unknown flag "${tail[0]}"` };
|
|
7672
|
+
}
|
|
7673
|
+
if (tail.length > 1) return { ok: false, error: `${bin} egress ${head}: unexpected argument "${tail[1]}"` };
|
|
7674
|
+
return { ok: true, args: tail.length === 0 ? { help: false, verb: head } : { help: false, verb: head, name: tail[0] } };
|
|
7675
|
+
}
|
|
7491
7676
|
function printComposeHelp(bin = AGRO_PRODUCT.bin) {
|
|
7492
7677
|
process.stdout.write(`${bin} compose \u2014 Inspect the resolved docker compose setup
|
|
7493
7678
|
|
|
@@ -7603,7 +7788,7 @@ function parseSecretArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
7603
7788
|
if (!SECRET_VERBS.includes(head)) {
|
|
7604
7789
|
return {
|
|
7605
7790
|
ok: false,
|
|
7606
|
-
error: `${bin} secret: unknown subcommand "${head}" \u2014 expected set or
|
|
7791
|
+
error: `${bin} secret: unknown subcommand "${head}" \u2014 expected set, list, or get`,
|
|
7607
7792
|
showHelp: true
|
|
7608
7793
|
};
|
|
7609
7794
|
}
|
|
@@ -7617,12 +7802,12 @@ function parseSecretArgs(input, bin = AGRO_PRODUCT.bin) {
|
|
|
7617
7802
|
}
|
|
7618
7803
|
const [key, ...extra] = tail;
|
|
7619
7804
|
if (key === void 0) {
|
|
7620
|
-
return { ok: false, error: `${bin} secret
|
|
7805
|
+
return { ok: false, error: `${bin} secret ${verb}: a key is required`, showHelp: true };
|
|
7621
7806
|
}
|
|
7622
7807
|
if (extra.length > 0) {
|
|
7623
7808
|
return {
|
|
7624
7809
|
ok: false,
|
|
7625
|
-
error: `${bin} secret set: takes only a key \u2014 the value is prompted for, never passed on the command line where your shell history would keep it`
|
|
7810
|
+
error: verb === "get" ? `${bin} secret get: takes only a key` : `${bin} secret set: takes only a key \u2014 the value is prompted for, never passed on the command line where your shell history would keep it`
|
|
7626
7811
|
};
|
|
7627
7812
|
}
|
|
7628
7813
|
return { ok: true, args: { ...args, verb, key } };
|
|
@@ -8095,6 +8280,7 @@ async function main(argv) {
|
|
|
8095
8280
|
};
|
|
8096
8281
|
const scope = a.sandbox === void 0 ? { bin } : { bin, sandbox: a.sandbox };
|
|
8097
8282
|
if (a.verb === "list") return await runSecretList(scope, io);
|
|
8283
|
+
if (a.verb === "get") return await runSecretGet(a.key, scope, io);
|
|
8098
8284
|
return await runSecretSet(a.key, scope, io);
|
|
8099
8285
|
}
|
|
8100
8286
|
if (first === "langfuse") {
|
|
@@ -8219,6 +8405,21 @@ async function main(argv) {
|
|
|
8219
8405
|
}
|
|
8220
8406
|
return runComposeConfig({ bin }, parsed.args.passthrough);
|
|
8221
8407
|
}
|
|
8408
|
+
if (first === "egress") {
|
|
8409
|
+
const parsed = parseEgressArgs(argv.slice(1), bin);
|
|
8410
|
+
if (!parsed.ok) {
|
|
8411
|
+
process.stderr.write(`${parsed.error}
|
|
8412
|
+
`);
|
|
8413
|
+
if (parsed.showHelp) printEgressHelp(bin);
|
|
8414
|
+
return 1;
|
|
8415
|
+
}
|
|
8416
|
+
const a = parsed.args;
|
|
8417
|
+
if (a.help || a.verb === void 0) {
|
|
8418
|
+
printEgressHelp(bin);
|
|
8419
|
+
return second === void 0 ? 1 : 0;
|
|
8420
|
+
}
|
|
8421
|
+
return runEgress(a.verb, a.name === void 0 ? { bin } : { bin, name: a.name }, lifecycleIo());
|
|
8422
|
+
}
|
|
8222
8423
|
if (first === "workspace") {
|
|
8223
8424
|
const parsed = parseWorkspaceArgs(argv.slice(1), bin);
|
|
8224
8425
|
if (!parsed.ok) {
|
|
@@ -8400,6 +8601,7 @@ export {
|
|
|
8400
8601
|
parseComposeArgs,
|
|
8401
8602
|
parseConfigArgs,
|
|
8402
8603
|
parseDestroyArgs,
|
|
8604
|
+
parseEgressArgs,
|
|
8403
8605
|
parseGatewayArgs,
|
|
8404
8606
|
parseHarnessArgs,
|
|
8405
8607
|
parseLangfuseArgs,
|
|
@@ -8414,6 +8616,7 @@ export {
|
|
|
8414
8616
|
printComposeHelp,
|
|
8415
8617
|
printComposeVerbHelp,
|
|
8416
8618
|
printDestroyHelp,
|
|
8619
|
+
printEgressHelp,
|
|
8417
8620
|
printGatewayHelp,
|
|
8418
8621
|
printHarnessHelp,
|
|
8419
8622
|
printLangfuseHelp,
|
package/package.json
CHANGED