@mifune/agro 0.11.0 → 0.12.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/dist/agro.js +1061 -151
- package/dist/oh.js +1061 -151
- package/package.json +1 -1
package/dist/agro.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { dirname as
|
|
6
|
-
import { existsSync as
|
|
5
|
+
import { dirname as dirname10, join as join14, resolve as resolve14 } from "node:path";
|
|
6
|
+
import { existsSync as existsSync20, mkdtempSync as mkdtempSync4, readFileSync as readFileSync12, rmSync as rmSync4 } from "node:fs";
|
|
7
7
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
8
8
|
|
|
9
9
|
// src/commands/update.ts
|
|
@@ -1959,8 +1959,8 @@ function redact(token) {
|
|
|
1959
1959
|
async function ask(question) {
|
|
1960
1960
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1961
1961
|
try {
|
|
1962
|
-
return await new Promise((
|
|
1963
|
-
rl.question(` ${question} `, (answer) =>
|
|
1962
|
+
return await new Promise((resolve15) => {
|
|
1963
|
+
rl.question(` ${question} `, (answer) => resolve15(answer.trim()));
|
|
1964
1964
|
});
|
|
1965
1965
|
} finally {
|
|
1966
1966
|
rl.close();
|
|
@@ -1972,7 +1972,7 @@ async function askSecret(question) {
|
|
|
1972
1972
|
process.stdin.setRawMode(true);
|
|
1973
1973
|
process.stdin.resume();
|
|
1974
1974
|
const bytes = [];
|
|
1975
|
-
return await new Promise((
|
|
1975
|
+
return await new Promise((resolve15) => {
|
|
1976
1976
|
const cleanup = () => {
|
|
1977
1977
|
try {
|
|
1978
1978
|
process.stdin.setRawMode(false);
|
|
@@ -1990,7 +1990,7 @@ async function askSecret(question) {
|
|
|
1990
1990
|
} else if (byte === 13 || byte === 10) {
|
|
1991
1991
|
cleanup();
|
|
1992
1992
|
process.stdout.write("\n");
|
|
1993
|
-
|
|
1993
|
+
resolve15(Buffer.from(bytes).toString("utf8").trim());
|
|
1994
1994
|
return;
|
|
1995
1995
|
} else if (byte === 127 || byte === 8) {
|
|
1996
1996
|
if (bytes.length > 0) {
|
|
@@ -2040,10 +2040,10 @@ async function askSecretPiped(question) {
|
|
|
2040
2040
|
};
|
|
2041
2041
|
process.on("SIGINT", onSigint);
|
|
2042
2042
|
try {
|
|
2043
|
-
return await new Promise((
|
|
2043
|
+
return await new Promise((resolve15) => {
|
|
2044
2044
|
rl.question(` ${question} `, (answer) => {
|
|
2045
2045
|
process.stdout.write("\n");
|
|
2046
|
-
|
|
2046
|
+
resolve15(answer.trim());
|
|
2047
2047
|
});
|
|
2048
2048
|
});
|
|
2049
2049
|
} finally {
|
|
@@ -2976,6 +2976,20 @@ function defaultIdentity() {
|
|
|
2976
2976
|
return { name: info.username, uid: info.uid };
|
|
2977
2977
|
}
|
|
2978
2978
|
|
|
2979
|
+
// src/lib/execution/status.ts
|
|
2980
|
+
var RUNTIME_ABSENT_STATUS = "no container runtime";
|
|
2981
|
+
async function resolveTargetStatus(target) {
|
|
2982
|
+
try {
|
|
2983
|
+
return await target.status();
|
|
2984
|
+
} catch (err) {
|
|
2985
|
+
if (err instanceof ExecutionSpawnError) return RUNTIME_ABSENT_STATUS;
|
|
2986
|
+
throw err;
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
function runtimeIsAbsent(status) {
|
|
2990
|
+
return status === RUNTIME_ABSENT_STATUS;
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2979
2993
|
// src/lib/execution/index.ts
|
|
2980
2994
|
function resolveExecutionTarget(opts) {
|
|
2981
2995
|
if (runningInsideSandbox(opts.env ?? process.env)) {
|
|
@@ -3984,6 +3998,11 @@ async function runSandboxList(opts, io) {
|
|
|
3984
3998
|
return 0;
|
|
3985
3999
|
}
|
|
3986
4000
|
|
|
4001
|
+
// src/commands/harness.ts
|
|
4002
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
4003
|
+
import { homedir as homedir2 } from "node:os";
|
|
4004
|
+
import { delimiter as delimiter2, join as join12, resolve as resolve12 } from "node:path";
|
|
4005
|
+
|
|
3987
4006
|
// src/lib/docs.ts
|
|
3988
4007
|
var SOURCE_DOCS_BASE = "https://github.com/mifunedev/agro/blob/main/";
|
|
3989
4008
|
function sourceDocsUrl(docsPath) {
|
|
@@ -3994,7 +4013,183 @@ function sourceDocsUrl(docsPath) {
|
|
|
3994
4013
|
return new URL(segments.map(encodeURIComponent).join("/"), SOURCE_DOCS_BASE).toString();
|
|
3995
4014
|
}
|
|
3996
4015
|
|
|
4016
|
+
// src/lib/host-config.ts
|
|
4017
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
4018
|
+
import { basename as basename6, dirname as dirname8, isAbsolute as isAbsolute2, join as join10, resolve as resolve10 } from "node:path";
|
|
4019
|
+
var HOST_CONFIG_MODE = 420;
|
|
4020
|
+
function hostConfigFileName(stateHome) {
|
|
4021
|
+
return basename6(stateHome) === GENERATIONS.legacy.userStateDir ? GENERATIONS.legacy.configFile : GENERATIONS.agro.configFile;
|
|
4022
|
+
}
|
|
4023
|
+
function hostConfigPath(env = process.env) {
|
|
4024
|
+
const stateHome = resolveUserStateHome(env);
|
|
4025
|
+
return join10(stateHome, hostConfigFileName(stateHome));
|
|
4026
|
+
}
|
|
4027
|
+
function defaultHarnessRoot(env = process.env) {
|
|
4028
|
+
return resolveUserStateHome(env);
|
|
4029
|
+
}
|
|
4030
|
+
function validateHostConfig(value) {
|
|
4031
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4032
|
+
throw new Error(`${GENERATIONS.agro.configFile}: must contain a JSON object`);
|
|
4033
|
+
}
|
|
4034
|
+
const record = value;
|
|
4035
|
+
if (record.version !== void 0 && record.version !== 1) {
|
|
4036
|
+
throw fieldError2("version", "must be 1");
|
|
4037
|
+
}
|
|
4038
|
+
const harnessRoot = record.harnessRoot;
|
|
4039
|
+
if (harnessRoot !== void 0) {
|
|
4040
|
+
if (typeof harnessRoot !== "string" || harnessRoot === "") {
|
|
4041
|
+
throw fieldError2("harnessRoot", "must be a non-empty string");
|
|
4042
|
+
}
|
|
4043
|
+
if (!isAbsolute2(harnessRoot)) {
|
|
4044
|
+
throw fieldError2("harnessRoot", "must be an absolute path");
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
const hostHarnesses = record.hostHarnesses;
|
|
4048
|
+
if (hostHarnesses !== void 0) validateReceipts(hostHarnesses, "hostHarnesses", "harness");
|
|
4049
|
+
const hostTools = record.hostTools;
|
|
4050
|
+
if (hostTools !== void 0) validateReceipts(hostTools, "hostTools", "tool");
|
|
4051
|
+
return { ...record, version: 1 };
|
|
4052
|
+
}
|
|
4053
|
+
function absoluteField(value, path4) {
|
|
4054
|
+
if (typeof value !== "string" || value === "") {
|
|
4055
|
+
throw fieldError2(path4, "must be a non-empty string");
|
|
4056
|
+
}
|
|
4057
|
+
if (!isAbsolute2(value)) throw fieldError2(path4, "must be an absolute path");
|
|
4058
|
+
return value;
|
|
4059
|
+
}
|
|
4060
|
+
function validateReceipts(value, field, noun) {
|
|
4061
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4062
|
+
throw fieldError2(field, `must be a JSON object keyed by ${noun} id`);
|
|
4063
|
+
}
|
|
4064
|
+
for (const [id, receipt] of Object.entries(value)) {
|
|
4065
|
+
if (id === "") throw fieldError2(field, `must use a non-empty ${noun} id as each key`);
|
|
4066
|
+
const at = `${field}.${id}`;
|
|
4067
|
+
if (!receipt || typeof receipt !== "object" || Array.isArray(receipt)) {
|
|
4068
|
+
throw fieldError2(at, "must be a JSON object");
|
|
4069
|
+
}
|
|
4070
|
+
const entry = receipt;
|
|
4071
|
+
absoluteField(entry.prefix, `${at}.prefix`);
|
|
4072
|
+
absoluteField(entry.binPath, `${at}.binPath`);
|
|
4073
|
+
if (typeof entry.binary !== "string" || entry.binary === "") {
|
|
4074
|
+
throw fieldError2(`${at}.binary`, "must be a non-empty string");
|
|
4075
|
+
}
|
|
4076
|
+
if (typeof entry.installedAt !== "string" || entry.installedAt === "") {
|
|
4077
|
+
throw fieldError2(`${at}.installedAt`, "must be a non-empty string");
|
|
4078
|
+
}
|
|
4079
|
+
if (entry.workspaceRoot !== void 0) {
|
|
4080
|
+
absoluteField(entry.workspaceRoot, `${at}.workspaceRoot`);
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
function readHostConfig(env = process.env) {
|
|
4085
|
+
const path4 = hostConfigPath(env);
|
|
4086
|
+
if (!existsSync16(path4)) return { version: 1 };
|
|
4087
|
+
let raw;
|
|
4088
|
+
try {
|
|
4089
|
+
raw = readFileSync11(path4, "utf8");
|
|
4090
|
+
} catch (error) {
|
|
4091
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
4092
|
+
throw new Error(`could not read ${path4}: ${detail}`);
|
|
4093
|
+
}
|
|
4094
|
+
let parsed;
|
|
4095
|
+
try {
|
|
4096
|
+
parsed = JSON.parse(raw);
|
|
4097
|
+
} catch {
|
|
4098
|
+
throw new Error(`${basename6(path4)} is not valid JSON: ${path4}`);
|
|
4099
|
+
}
|
|
4100
|
+
return validateHostConfig(parsed);
|
|
4101
|
+
}
|
|
4102
|
+
function writeHostConfig(config, env = process.env) {
|
|
4103
|
+
const path4 = hostConfigPath(env);
|
|
4104
|
+
const validated = validateHostConfig({ ...config, version: 1 });
|
|
4105
|
+
const body = `${JSON.stringify(validated, null, 2)}
|
|
4106
|
+
`;
|
|
4107
|
+
const tmp = `${path4}.tmp.${process.pid}`;
|
|
4108
|
+
mkdirSync7(dirname8(path4), { recursive: true });
|
|
4109
|
+
try {
|
|
4110
|
+
writeFileSync8(tmp, body, { mode: HOST_CONFIG_MODE, encoding: "utf8" });
|
|
4111
|
+
renameSync5(tmp, path4);
|
|
4112
|
+
} catch (error) {
|
|
4113
|
+
try {
|
|
4114
|
+
unlinkSync5(tmp);
|
|
4115
|
+
} catch {
|
|
4116
|
+
}
|
|
4117
|
+
throw error;
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
function resolveHarnessRoot(explicit, env = process.env) {
|
|
4121
|
+
if (typeof explicit === "string" && explicit !== "") return resolve10(explicit);
|
|
4122
|
+
const configured = readHostConfig(env).harnessRoot;
|
|
4123
|
+
if (typeof configured === "string" && configured !== "") return resolve10(configured);
|
|
4124
|
+
return defaultHarnessRoot(env);
|
|
4125
|
+
}
|
|
4126
|
+
function fieldError2(path4, requirement) {
|
|
4127
|
+
return new Error(`${GENERATIONS.agro.configFile}: ${path4} ${requirement}`);
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
// src/lib/host-workspace.ts
|
|
4131
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, readdirSync as readdirSync4, renameSync as renameSync6, rmSync as rmSync3, statSync as statSync5 } from "node:fs";
|
|
4132
|
+
import { dirname as dirname9, join as join11, resolve as resolve11 } from "node:path";
|
|
4133
|
+
var AGRO_REPO_URL = "https://github.com/mifunedev/agro.git";
|
|
4134
|
+
var IGNORABLE_ENTRIES = [
|
|
4135
|
+
REGISTRY_SUBDIR,
|
|
4136
|
+
"escalate",
|
|
4137
|
+
GENERATIONS.agro.configFile,
|
|
4138
|
+
GENERATIONS.legacy.configFile
|
|
4139
|
+
];
|
|
4140
|
+
function isIgnorableEntry(name) {
|
|
4141
|
+
return name.startsWith(".") || IGNORABLE_ENTRIES.includes(name);
|
|
4142
|
+
}
|
|
4143
|
+
function assertCloned(result, root) {
|
|
4144
|
+
const status = result.error ? result.error.code ?? "spawn failed" : result.status;
|
|
4145
|
+
if (result.error || result.status !== 0) {
|
|
4146
|
+
throw new Error(`could not clone ${AGRO_REPO_URL} into ${root} (exit status ${String(status)})`);
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
function cloneInto(target, run, root) {
|
|
4150
|
+
assertCloned(run("git", ["clone", AGRO_REPO_URL, target], { stdio: "inherit" }), root);
|
|
4151
|
+
}
|
|
4152
|
+
function cloneThroughStaging(root, run) {
|
|
4153
|
+
const staging = mkdtempSync3(join11(dirname9(root), "agro-clone-"));
|
|
4154
|
+
try {
|
|
4155
|
+
const checkout = join11(staging, "agro");
|
|
4156
|
+
cloneInto(checkout, run, root);
|
|
4157
|
+
for (const name of readdirSync4(checkout)) {
|
|
4158
|
+
const destination = join11(root, name);
|
|
4159
|
+
if (existsSync17(destination)) {
|
|
4160
|
+
throw new Error(`could not move ${name} into ${root} \u2014 the path already exists`);
|
|
4161
|
+
}
|
|
4162
|
+
renameSync6(join11(checkout, name), destination);
|
|
4163
|
+
}
|
|
4164
|
+
} finally {
|
|
4165
|
+
rmSync3(staging, { recursive: true, force: true });
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
function ensureHostWorkspace(path4, run = spawnRunner) {
|
|
4169
|
+
const root = resolve11(path4);
|
|
4170
|
+
if (existsSync17(join11(root, ".git"))) return { root, action: "reused" };
|
|
4171
|
+
const stats = statSync5(root, { throwIfNoEntry: false });
|
|
4172
|
+
if (stats === void 0) {
|
|
4173
|
+
mkdirSync8(dirname9(root), { recursive: true });
|
|
4174
|
+
cloneInto(root, run, root);
|
|
4175
|
+
return { root, action: "cloned" };
|
|
4176
|
+
}
|
|
4177
|
+
if (!stats.isDirectory()) {
|
|
4178
|
+
throw new Error(`${root} exists and is not a directory \u2014 choose another harness root`);
|
|
4179
|
+
}
|
|
4180
|
+
const blocking = readdirSync4(root).filter((name) => !isIgnorableEntry(name));
|
|
4181
|
+
if (blocking.length > 0) {
|
|
4182
|
+
throw new Error(
|
|
4183
|
+
`${root} holds files but no git checkout (${blocking.sort().join(", ")}) \u2014 choose an empty directory or an existing AGRO checkout`
|
|
4184
|
+
);
|
|
4185
|
+
}
|
|
4186
|
+
cloneThroughStaging(root, run);
|
|
4187
|
+
return { root, action: "cloned" };
|
|
4188
|
+
}
|
|
4189
|
+
|
|
3997
4190
|
// src/lib/harnesses/catalog.ts
|
|
4191
|
+
var SANDBOX_HARNESS_PREFIX = "/home/sandbox/.local";
|
|
4192
|
+
var HARNESS_PREFIX_TOKEN = "{{prefix}}";
|
|
3998
4193
|
var HARNESS_CATALOG = [
|
|
3999
4194
|
{
|
|
4000
4195
|
id: "claude-code",
|
|
@@ -4003,13 +4198,14 @@ var HARNESS_CATALOG = [
|
|
|
4003
4198
|
installArgv: [
|
|
4004
4199
|
"npm",
|
|
4005
4200
|
"--prefix",
|
|
4006
|
-
|
|
4201
|
+
HARNESS_PREFIX_TOKEN,
|
|
4007
4202
|
"install",
|
|
4008
4203
|
"-g",
|
|
4009
4204
|
"@anthropic-ai/claude-code"
|
|
4010
4205
|
],
|
|
4011
4206
|
installUser: "sandbox",
|
|
4012
4207
|
verifyArgv: ["claude", "--version"],
|
|
4208
|
+
uninstallArgv: ["npm", "--prefix", HARNESS_PREFIX_TOKEN, "uninstall", "-g", "@anthropic-ai/claude-code"],
|
|
4013
4209
|
docsPath: "docs/harnesses/claude-code.md",
|
|
4014
4210
|
kind: "installable"
|
|
4015
4211
|
},
|
|
@@ -4020,13 +4216,14 @@ var HARNESS_CATALOG = [
|
|
|
4020
4216
|
installArgv: [
|
|
4021
4217
|
"npm",
|
|
4022
4218
|
"--prefix",
|
|
4023
|
-
|
|
4219
|
+
HARNESS_PREFIX_TOKEN,
|
|
4024
4220
|
"install",
|
|
4025
4221
|
"-g",
|
|
4026
4222
|
"@openai/codex"
|
|
4027
4223
|
],
|
|
4028
4224
|
installUser: "sandbox",
|
|
4029
4225
|
verifyArgv: ["codex", "--version"],
|
|
4226
|
+
uninstallArgv: ["npm", "--prefix", HARNESS_PREFIX_TOKEN, "uninstall", "-g", "@openai/codex"],
|
|
4030
4227
|
docsPath: "docs/harnesses/codex.md",
|
|
4031
4228
|
kind: "installable"
|
|
4032
4229
|
},
|
|
@@ -4037,7 +4234,7 @@ var HARNESS_CATALOG = [
|
|
|
4037
4234
|
installArgv: [
|
|
4038
4235
|
"npm",
|
|
4039
4236
|
"--prefix",
|
|
4040
|
-
|
|
4237
|
+
HARNESS_PREFIX_TOKEN,
|
|
4041
4238
|
"install",
|
|
4042
4239
|
"-g",
|
|
4043
4240
|
"--ignore-scripts",
|
|
@@ -4045,6 +4242,7 @@ var HARNESS_CATALOG = [
|
|
|
4045
4242
|
],
|
|
4046
4243
|
installUser: "sandbox",
|
|
4047
4244
|
verifyArgv: ["pi", "--version"],
|
|
4245
|
+
uninstallArgv: ["npm", "--prefix", HARNESS_PREFIX_TOKEN, "uninstall", "-g", "@earendil-works/pi-coding-agent"],
|
|
4048
4246
|
docsPath: "docs/harnesses/pi.md",
|
|
4049
4247
|
kind: "installable"
|
|
4050
4248
|
},
|
|
@@ -4055,13 +4253,14 @@ var HARNESS_CATALOG = [
|
|
|
4055
4253
|
installArgv: [
|
|
4056
4254
|
"npm",
|
|
4057
4255
|
"--prefix",
|
|
4058
|
-
|
|
4256
|
+
HARNESS_PREFIX_TOKEN,
|
|
4059
4257
|
"install",
|
|
4060
4258
|
"-g",
|
|
4061
4259
|
"opencode-ai"
|
|
4062
4260
|
],
|
|
4063
4261
|
installUser: "sandbox",
|
|
4064
4262
|
verifyArgv: ["opencode", "--version"],
|
|
4263
|
+
uninstallArgv: ["npm", "--prefix", HARNESS_PREFIX_TOKEN, "uninstall", "-g", "opencode-ai"],
|
|
4065
4264
|
docsPath: "docs/harnesses/opencode.md",
|
|
4066
4265
|
kind: "installable"
|
|
4067
4266
|
},
|
|
@@ -4072,10 +4271,11 @@ var HARNESS_CATALOG = [
|
|
|
4072
4271
|
installArgv: [
|
|
4073
4272
|
"bash",
|
|
4074
4273
|
"-lc",
|
|
4075
|
-
|
|
4274
|
+
`curl -fsSL https://x.ai/cli/install.sh | GROK_BIN_DIR="${HARNESS_PREFIX_TOKEN}/bin" bash -s 0.2.39 && rm -f "${HARNESS_PREFIX_TOKEN}/bin/agent"`
|
|
4076
4275
|
],
|
|
4077
4276
|
installUser: "sandbox",
|
|
4078
4277
|
verifyArgv: ["grok", "--version"],
|
|
4278
|
+
uninstallArgv: ["rm", "-rf", `${HARNESS_PREFIX_TOKEN}/bin/grok`],
|
|
4079
4279
|
docsPath: "docs/harnesses/grok-build.md",
|
|
4080
4280
|
kind: "installable"
|
|
4081
4281
|
},
|
|
@@ -4086,10 +4286,11 @@ var HARNESS_CATALOG = [
|
|
|
4086
4286
|
installArgv: [
|
|
4087
4287
|
"bash",
|
|
4088
4288
|
"-lc",
|
|
4089
|
-
`curl -fsSL https://hermes-agent.nousresearch.com/install.sh | HERMES_INSTALL_DIR="$
|
|
4289
|
+
`curl -fsSL https://hermes-agent.nousresearch.com/install.sh | HERMES_INSTALL_DIR="${HARNESS_PREFIX_TOKEN}/lib/hermes-agent" bash -s -- --skip-setup --skip-browser && uv pip install --python "${HARNESS_PREFIX_TOKEN}/lib/hermes-agent/venv/bin/python" 'hermes-agent[slack,teams,web,pty]'`
|
|
4090
4290
|
],
|
|
4091
4291
|
installUser: "sandbox",
|
|
4092
4292
|
verifyArgv: ["hermes", "--version"],
|
|
4293
|
+
uninstallArgv: ["rm", "-rf", `${HARNESS_PREFIX_TOKEN}/bin/hermes`, `${HARNESS_PREFIX_TOKEN}/lib/hermes-agent`],
|
|
4093
4294
|
docsPath: "docs/harnesses/hermes.md",
|
|
4094
4295
|
kind: "installable"
|
|
4095
4296
|
},
|
|
@@ -4100,10 +4301,11 @@ var HARNESS_CATALOG = [
|
|
|
4100
4301
|
installArgv: [
|
|
4101
4302
|
"bash",
|
|
4102
4303
|
"-lc",
|
|
4103
|
-
|
|
4304
|
+
`set -o pipefail; curl -fsSL https://dev.meta.ai/install.sh | MUSE_INSTALL_DIR="${HARNESS_PREFIX_TOKEN}/bin" MUSE_NO_MODIFY_PATH=1 MUSE_LOGIN=0 bash`
|
|
4104
4305
|
],
|
|
4105
4306
|
installUser: "sandbox",
|
|
4106
4307
|
verifyArgv: ["muse", "--version"],
|
|
4308
|
+
uninstallArgv: ["rm", "-rf", `${HARNESS_PREFIX_TOKEN}/bin/muse`],
|
|
4107
4309
|
docsPath: "docs/harnesses/muse-code.md",
|
|
4108
4310
|
kind: "installable"
|
|
4109
4311
|
},
|
|
@@ -4114,10 +4316,11 @@ var HARNESS_CATALOG = [
|
|
|
4114
4316
|
installArgv: [
|
|
4115
4317
|
"bash",
|
|
4116
4318
|
"-lc",
|
|
4117
|
-
|
|
4319
|
+
`set -o pipefail; curl -fsSL https://antigravity.google/cli/install.sh | bash -s -- --dir "${HARNESS_PREFIX_TOKEN}/bin"`
|
|
4118
4320
|
],
|
|
4119
4321
|
installUser: "sandbox",
|
|
4120
4322
|
verifyArgv: ["agy", "--version"],
|
|
4323
|
+
uninstallArgv: ["rm", "-rf", `${HARNESS_PREFIX_TOKEN}/bin/agy`],
|
|
4121
4324
|
docsPath: "docs/harnesses/antigravity-cli.md",
|
|
4122
4325
|
kind: "installable"
|
|
4123
4326
|
},
|
|
@@ -4128,6 +4331,7 @@ var HARNESS_CATALOG = [
|
|
|
4128
4331
|
installArgv: ["npx", "--yes", "t3", "--version"],
|
|
4129
4332
|
installUser: "sandbox",
|
|
4130
4333
|
verifyArgv: ["npx", "--no-install", "t3", "--version"],
|
|
4334
|
+
uninstallArgv: null,
|
|
4131
4335
|
docsPath: "docs/harnesses/t3code.md",
|
|
4132
4336
|
kind: "on-demand"
|
|
4133
4337
|
}
|
|
@@ -4138,6 +4342,21 @@ function findHarness(id) {
|
|
|
4138
4342
|
function harnessIds() {
|
|
4139
4343
|
return HARNESS_CATALOG.map((h) => h.id);
|
|
4140
4344
|
}
|
|
4345
|
+
function substitute(argv, prefix) {
|
|
4346
|
+
return argv.map((part) => part.split(HARNESS_PREFIX_TOKEN).join(prefix));
|
|
4347
|
+
}
|
|
4348
|
+
function resolveInstallArgv(entry, prefix) {
|
|
4349
|
+
return substitute(entry.installArgv, prefix);
|
|
4350
|
+
}
|
|
4351
|
+
function resolveVerifyArgv(entry, prefix) {
|
|
4352
|
+
return substitute(entry.verifyArgv, prefix);
|
|
4353
|
+
}
|
|
4354
|
+
function resolveUninstallArgv(entry, prefix) {
|
|
4355
|
+
return entry.uninstallArgv === null ? null : substitute(entry.uninstallArgv, prefix);
|
|
4356
|
+
}
|
|
4357
|
+
function harnessBinPath(prefix) {
|
|
4358
|
+
return `${prefix}/bin`;
|
|
4359
|
+
}
|
|
4141
4360
|
|
|
4142
4361
|
// src/commands/harness.ts
|
|
4143
4362
|
var PROBE_TIMEOUT_MS = 15e3;
|
|
@@ -4153,11 +4372,31 @@ function targetFor(root, run, env) {
|
|
|
4153
4372
|
...env ? { env } : {}
|
|
4154
4373
|
});
|
|
4155
4374
|
}
|
|
4156
|
-
|
|
4375
|
+
function hostPrefix(home) {
|
|
4376
|
+
return join12(home, ".local");
|
|
4377
|
+
}
|
|
4378
|
+
function pathEntries(env) {
|
|
4379
|
+
return (env.PATH ?? "").split(delimiter2).filter((part) => part !== "");
|
|
4380
|
+
}
|
|
4381
|
+
function onPath(prefix, env) {
|
|
4382
|
+
return pathEntries(env).includes(harnessBinPath(prefix));
|
|
4383
|
+
}
|
|
4384
|
+
function hostTargetFor(root, prefix, run, env) {
|
|
4385
|
+
const path4 = [harnessBinPath(prefix), ...pathEntries(env)].join(delimiter2);
|
|
4386
|
+
return new LocalExecutionTarget({
|
|
4387
|
+
projectRoot: root,
|
|
4388
|
+
run,
|
|
4389
|
+
env: { ...env, PATH: path4 }
|
|
4390
|
+
});
|
|
4391
|
+
}
|
|
4392
|
+
function messageOf(error) {
|
|
4393
|
+
return error instanceof Error ? error.message : String(error);
|
|
4394
|
+
}
|
|
4395
|
+
async function probeInstalled(target, entry, prefix, user, env) {
|
|
4157
4396
|
try {
|
|
4158
4397
|
const r = await target.exec({
|
|
4159
|
-
argv:
|
|
4160
|
-
user:
|
|
4398
|
+
argv: resolveVerifyArgv(entry, prefix),
|
|
4399
|
+
...user ? { user } : {},
|
|
4161
4400
|
stdio: "capture",
|
|
4162
4401
|
timeoutMs: PROBE_TIMEOUT_MS,
|
|
4163
4402
|
...env ? { env } : {}
|
|
@@ -4168,33 +4407,58 @@ async function probeInstalled(target, entry, env) {
|
|
|
4168
4407
|
throw err;
|
|
4169
4408
|
}
|
|
4170
4409
|
}
|
|
4171
|
-
|
|
4410
|
+
function stateOf(entry, installed, location) {
|
|
4411
|
+
return {
|
|
4412
|
+
id: entry.id,
|
|
4413
|
+
title: entry.title,
|
|
4414
|
+
binary: entry.binary,
|
|
4415
|
+
kind: entry.kind,
|
|
4416
|
+
installed,
|
|
4417
|
+
location,
|
|
4418
|
+
docs: sourceDocsUrl(entry.docsPath)
|
|
4419
|
+
};
|
|
4420
|
+
}
|
|
4421
|
+
function probeableHost(env, home) {
|
|
4422
|
+
let root;
|
|
4423
|
+
try {
|
|
4424
|
+
root = resolveHarnessRoot(void 0, env);
|
|
4425
|
+
} catch {
|
|
4426
|
+
return void 0;
|
|
4427
|
+
}
|
|
4428
|
+
const prefix = hostPrefix(home);
|
|
4429
|
+
if (!existsSync18(join12(root, ".git")) && !existsSync18(prefix)) return void 0;
|
|
4430
|
+
return { root, prefix };
|
|
4431
|
+
}
|
|
4432
|
+
async function collectStates(root, run, home, env, only) {
|
|
4172
4433
|
const entries = only ? [...only] : [...HARNESS_CATALOG];
|
|
4173
4434
|
const target = targetFor(root, run, env);
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4435
|
+
const reachable = isReachable(await resolveTargetStatus(target));
|
|
4436
|
+
if (reachable) {
|
|
4437
|
+
const states2 = [];
|
|
4438
|
+
for (const entry of entries) {
|
|
4439
|
+
const installed = await probeInstalled(target, entry, SANDBOX_HARNESS_PREFIX, "sandbox");
|
|
4440
|
+
states2.push(stateOf(entry, installed, installed === null ? "unknown" : "sandbox"));
|
|
4441
|
+
}
|
|
4442
|
+
return { states: states2 };
|
|
4179
4443
|
}
|
|
4444
|
+
const host = probeableHost(env ?? process.env, home);
|
|
4445
|
+
if (host === void 0) {
|
|
4446
|
+
return { states: entries.map((entry) => stateOf(entry, null, "unknown")) };
|
|
4447
|
+
}
|
|
4448
|
+
const hostTarget = hostTargetFor(host.root, host.prefix, run, env ?? process.env);
|
|
4180
4449
|
const states = [];
|
|
4181
4450
|
for (const entry of entries) {
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
title: entry.title,
|
|
4185
|
-
binary: entry.binary,
|
|
4186
|
-
kind: entry.kind,
|
|
4187
|
-
installed: reachable ? await probeInstalled(target, entry) : null,
|
|
4188
|
-
docs: sourceDocsUrl(entry.docsPath)
|
|
4189
|
-
});
|
|
4451
|
+
const installed = await probeInstalled(hostTarget, entry, host.prefix, void 0);
|
|
4452
|
+
states.push(stateOf(entry, installed, installed === null ? "unknown" : "host"));
|
|
4190
4453
|
}
|
|
4191
|
-
return states;
|
|
4454
|
+
return { states, hostPrefix: host.prefix };
|
|
4192
4455
|
}
|
|
4193
4456
|
function cell(value, absent) {
|
|
4194
4457
|
if (value === null) return absent;
|
|
4195
4458
|
return value ? "yes" : "no";
|
|
4196
4459
|
}
|
|
4197
|
-
function renderTable(
|
|
4460
|
+
function renderTable(collected, io, bin) {
|
|
4461
|
+
const states = collected.states;
|
|
4198
4462
|
const header2 = ["HARNESS", "KIND", "INSTALLED"];
|
|
4199
4463
|
const rows = states.map((s) => [s.id, s.kind, cell(s.installed, "?")]);
|
|
4200
4464
|
const widths = header2.map(
|
|
@@ -4203,6 +4467,14 @@ function renderTable(states, io, bin) {
|
|
|
4203
4467
|
const line = (cols) => cols.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd() + "\n";
|
|
4204
4468
|
io.stdout(line(header2));
|
|
4205
4469
|
for (const row of rows) io.stdout(line(row));
|
|
4470
|
+
if (states.some((s) => s.location === "host")) {
|
|
4471
|
+
io.stdout(
|
|
4472
|
+
`
|
|
4473
|
+
INSTALLED reports the host prefix ${collected.hostPrefix} \u2014 the sandbox is not running.
|
|
4474
|
+
`
|
|
4475
|
+
);
|
|
4476
|
+
return;
|
|
4477
|
+
}
|
|
4206
4478
|
if (states.some((s) => s.installed === null)) {
|
|
4207
4479
|
io.stdout(`
|
|
4208
4480
|
INSTALLED is \`?\` \u2014 the sandbox is not running. Start it with \`${bin} sandbox\`.
|
|
@@ -4212,12 +4484,12 @@ INSTALLED is \`?\` \u2014 the sandbox is not running. Start it with \`${bin} san
|
|
|
4212
4484
|
async function runHarnessList(opts, io) {
|
|
4213
4485
|
const run = opts.run ?? spawnRunner;
|
|
4214
4486
|
const root = resolveProjectRoot(opts.cwd);
|
|
4215
|
-
const
|
|
4487
|
+
const collected = await collectStates(root, run, homeOf(opts), opts.env);
|
|
4216
4488
|
if (opts.json) {
|
|
4217
|
-
io.stdout(`${JSON.stringify(states, null, 2)}
|
|
4489
|
+
io.stdout(`${JSON.stringify(collected.states, null, 2)}
|
|
4218
4490
|
`);
|
|
4219
4491
|
} else {
|
|
4220
|
-
renderTable(
|
|
4492
|
+
renderTable(collected, io, opts.bin);
|
|
4221
4493
|
}
|
|
4222
4494
|
return 0;
|
|
4223
4495
|
}
|
|
@@ -4238,24 +4510,23 @@ async function runHarnessStatus(name, opts, io) {
|
|
|
4238
4510
|
only = findHarness(name);
|
|
4239
4511
|
if (!only) return unknownHarness(name, io, opts.bin);
|
|
4240
4512
|
}
|
|
4241
|
-
const
|
|
4513
|
+
const collected = await collectStates(root, run, homeOf(opts), opts.env, only ? [only] : void 0);
|
|
4242
4514
|
if (opts.json) {
|
|
4243
|
-
io.stdout(`${JSON.stringify(only ? states[0] : states, null, 2)}
|
|
4515
|
+
io.stdout(`${JSON.stringify(only ? collected.states[0] : collected.states, null, 2)}
|
|
4244
4516
|
`);
|
|
4245
4517
|
} else {
|
|
4246
|
-
renderTable(
|
|
4518
|
+
renderTable(collected, io, opts.bin);
|
|
4247
4519
|
}
|
|
4248
4520
|
return 0;
|
|
4249
4521
|
}
|
|
4250
4522
|
function hermesTargetRoot(target) {
|
|
4251
4523
|
return target.kind === "docker-compose" ? "/home/sandbox/harness" : target.workspace.targetRoot;
|
|
4252
4524
|
}
|
|
4253
|
-
async function reconcileHermes(target, io, bin) {
|
|
4254
|
-
const root = hermesTargetRoot(target);
|
|
4525
|
+
async function reconcileHermes(target, io, bin, user, root = hermesTargetRoot(target)) {
|
|
4255
4526
|
const result = await target.exec({
|
|
4256
4527
|
argv: remoteControlDirScript(root, "scripts/link-providers.sh", ["--init", "--hermes-only"]),
|
|
4257
4528
|
env: aliasedEnvPair("PROJECT_ROOT", root),
|
|
4258
|
-
user:
|
|
4529
|
+
...user ? { user } : {},
|
|
4259
4530
|
stdio: "inherit"
|
|
4260
4531
|
});
|
|
4261
4532
|
if (result.exitCode !== 0) {
|
|
@@ -4264,6 +4535,245 @@ async function reconcileHermes(target, io, bin) {
|
|
|
4264
4535
|
}
|
|
4265
4536
|
return result.exitCode;
|
|
4266
4537
|
}
|
|
4538
|
+
function unreachableReason(status) {
|
|
4539
|
+
return runtimeIsAbsent(status) ? "No container runtime is on PATH." : `The sandbox is not running (${status}).`;
|
|
4540
|
+
}
|
|
4541
|
+
function sandboxRefusal(bin, status) {
|
|
4542
|
+
if (runtimeIsAbsent(status)) {
|
|
4543
|
+
return `${bin} harness: no container runtime is on PATH.
|
|
4544
|
+
`;
|
|
4545
|
+
}
|
|
4546
|
+
return `${bin} harness: the sandbox is not running (${status}).
|
|
4547
|
+
Start it with \`${bin} sandbox\`, then re-run this command.
|
|
4548
|
+
`;
|
|
4549
|
+
}
|
|
4550
|
+
function homeOf(opts) {
|
|
4551
|
+
return (opts.homedir ?? homedir2)();
|
|
4552
|
+
}
|
|
4553
|
+
function isInteractive(opts) {
|
|
4554
|
+
return opts.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
4555
|
+
}
|
|
4556
|
+
async function installOnHost(entry, opts, io, run, status) {
|
|
4557
|
+
const bin = opts.bin;
|
|
4558
|
+
const env = opts.env ?? process.env;
|
|
4559
|
+
const flagged = opts.host === true || opts.path !== void 0;
|
|
4560
|
+
if (!flagged && !isInteractive(opts)) {
|
|
4561
|
+
io.stderr(
|
|
4562
|
+
sandboxRefusal(bin, status) + `Or install on the host with \`${bin} harness install ${entry.id} --host\`.
|
|
4563
|
+
`
|
|
4564
|
+
);
|
|
4565
|
+
return 1;
|
|
4566
|
+
}
|
|
4567
|
+
if (entry.kind === "on-demand") {
|
|
4568
|
+
io.stdout(`${entry.id}: no installation is needed \u2014 npx fetches it at each run
|
|
4569
|
+
`);
|
|
4570
|
+
return 0;
|
|
4571
|
+
}
|
|
4572
|
+
let root;
|
|
4573
|
+
let recorded;
|
|
4574
|
+
try {
|
|
4575
|
+
recorded = readHostConfig(env).harnessRoot;
|
|
4576
|
+
root = resolveHarnessRoot(opts.path, env);
|
|
4577
|
+
} catch (err) {
|
|
4578
|
+
io.stderr(`${bin} harness: ${messageOf(err)}
|
|
4579
|
+
`);
|
|
4580
|
+
return 1;
|
|
4581
|
+
}
|
|
4582
|
+
const sticky = opts.path === void 0 && recorded !== void 0 && recorded !== "";
|
|
4583
|
+
if (!flagged) {
|
|
4584
|
+
const ask2 = io.ask ?? ask;
|
|
4585
|
+
const answer = (await ask2(
|
|
4586
|
+
`${unreachableReason(status)} Install ${entry.title} on the host? [y/N]`
|
|
4587
|
+
)).trim().toLowerCase();
|
|
4588
|
+
if (!/^y/.test(answer)) {
|
|
4589
|
+
io.stderr(sandboxRefusal(bin, status));
|
|
4590
|
+
return 1;
|
|
4591
|
+
}
|
|
4592
|
+
if (!sticky) {
|
|
4593
|
+
const chosen = (await ask2(`Harness root [${root}]:`)).trim();
|
|
4594
|
+
if (chosen !== "") root = resolve12(chosen);
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
if (sticky) io.stdout(`using the recorded harness root ${root}
|
|
4598
|
+
`);
|
|
4599
|
+
try {
|
|
4600
|
+
if (!existsSync18(join12(root, ".git"))) io.stdout(`cloning ${AGRO_REPO_URL} into ${root}\u2026
|
|
4601
|
+
`);
|
|
4602
|
+
const workspace = ensureHostWorkspace(root, run);
|
|
4603
|
+
root = workspace.root;
|
|
4604
|
+
io.stdout(
|
|
4605
|
+
workspace.action === "cloned" ? `host workspace cloned into ${root}
|
|
4606
|
+
` : `host workspace reused at ${root}
|
|
4607
|
+
`
|
|
4608
|
+
);
|
|
4609
|
+
} catch (err) {
|
|
4610
|
+
io.stderr(`${bin} harness: ${messageOf(err)}
|
|
4611
|
+
`);
|
|
4612
|
+
return 1;
|
|
4613
|
+
}
|
|
4614
|
+
const prefix = hostPrefix(homeOf(opts));
|
|
4615
|
+
const target = hostTargetFor(root, prefix, run, env);
|
|
4616
|
+
const hermes = entry.id === "hermes";
|
|
4617
|
+
const installEnv = hermes ? {
|
|
4618
|
+
...aliasedEnvPair("PROJECT_ROOT", root),
|
|
4619
|
+
HERMES_HOME: `${root}/.hermes`
|
|
4620
|
+
} : void 0;
|
|
4621
|
+
if (hermes) {
|
|
4622
|
+
const code = await reconcileHermes(target, io, bin, void 0, root);
|
|
4623
|
+
if (code !== 0) return code;
|
|
4624
|
+
}
|
|
4625
|
+
if (await probeInstalled(target, entry, prefix, void 0, installEnv) === true) {
|
|
4626
|
+
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
4627
|
+
`);
|
|
4628
|
+
return 0;
|
|
4629
|
+
}
|
|
4630
|
+
io.stdout(`installing ${entry.title} on the host\u2026
|
|
4631
|
+
`);
|
|
4632
|
+
const r = await target.exec({
|
|
4633
|
+
argv: resolveInstallArgv(entry, prefix),
|
|
4634
|
+
stdio: "inherit",
|
|
4635
|
+
...installEnv ? { env: installEnv } : {}
|
|
4636
|
+
});
|
|
4637
|
+
if (r.exitCode !== 0) {
|
|
4638
|
+
io.stderr(`${bin} harness: installing ${entry.id} failed (exit ${r.exitCode}).
|
|
4639
|
+
`);
|
|
4640
|
+
return r.exitCode;
|
|
4641
|
+
}
|
|
4642
|
+
if (hermes) {
|
|
4643
|
+
if (await probeInstalled(target, entry, prefix, void 0, installEnv) !== true) {
|
|
4644
|
+
io.stderr(`${bin} harness: Hermes installation finished but executable verification failed.
|
|
4645
|
+
`);
|
|
4646
|
+
return 1;
|
|
4647
|
+
}
|
|
4648
|
+
const code = await reconcileHermes(target, io, bin, void 0, root);
|
|
4649
|
+
if (code !== 0) return code;
|
|
4650
|
+
}
|
|
4651
|
+
const receipt = {
|
|
4652
|
+
prefix,
|
|
4653
|
+
binary: entry.binary,
|
|
4654
|
+
binPath: harnessBinPath(prefix),
|
|
4655
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4656
|
+
workspaceRoot: root
|
|
4657
|
+
};
|
|
4658
|
+
try {
|
|
4659
|
+
const config = readHostConfig(env);
|
|
4660
|
+
writeHostConfig(
|
|
4661
|
+
{
|
|
4662
|
+
...config,
|
|
4663
|
+
harnessRoot: root,
|
|
4664
|
+
hostHarnesses: { ...config.hostHarnesses ?? {}, [entry.id]: receipt }
|
|
4665
|
+
},
|
|
4666
|
+
env
|
|
4667
|
+
);
|
|
4668
|
+
} catch (err) {
|
|
4669
|
+
io.stderr(`${bin} harness: could not record the install: ${messageOf(err)}
|
|
4670
|
+
`);
|
|
4671
|
+
return 1;
|
|
4672
|
+
}
|
|
4673
|
+
io.stdout(`${entry.id}: installed at ${prefix} \u2014 see ${sourceDocsUrl(entry.docsPath)} for authentication
|
|
4674
|
+
`);
|
|
4675
|
+
if (!onPath(prefix, env)) {
|
|
4676
|
+
io.stdout(`Add this line to your shell profile: export PATH="${harnessBinPath(prefix)}:$PATH"
|
|
4677
|
+
`);
|
|
4678
|
+
}
|
|
4679
|
+
io.stdout(`Run ${entry.binary} from the AGRO workspace: cd ${root}
|
|
4680
|
+
`);
|
|
4681
|
+
return 0;
|
|
4682
|
+
}
|
|
4683
|
+
async function removeHarness(entry, target, prefix, user, opts, io) {
|
|
4684
|
+
const argv = resolveUninstallArgv(entry, prefix);
|
|
4685
|
+
if (argv === null) {
|
|
4686
|
+
io.stdout(`${entry.id}: nothing to remove \u2014 npx fetches it at each run
|
|
4687
|
+
`);
|
|
4688
|
+
return { code: 0, dropReceipt: false };
|
|
4689
|
+
}
|
|
4690
|
+
if (await probeInstalled(target, entry, prefix, user) !== true) {
|
|
4691
|
+
io.stdout(`${entry.id}: not installed (${entry.binary})
|
|
4692
|
+
`);
|
|
4693
|
+
return { code: 0, dropReceipt: true };
|
|
4694
|
+
}
|
|
4695
|
+
if (isInteractive(opts)) {
|
|
4696
|
+
const ask2 = io.ask ?? ask;
|
|
4697
|
+
const answer = (await ask2(`Remove ${entry.title} from ${prefix}? [y/N]`)).trim().toLowerCase();
|
|
4698
|
+
if (!/^y/.test(answer)) {
|
|
4699
|
+
io.stderr(`${opts.bin} harness: removed nothing.
|
|
4700
|
+
`);
|
|
4701
|
+
return { code: 1, dropReceipt: false };
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
io.stdout(`removing ${entry.title} from ${prefix}\u2026
|
|
4705
|
+
`);
|
|
4706
|
+
const r = await target.exec({
|
|
4707
|
+
argv,
|
|
4708
|
+
...user ? { user } : {},
|
|
4709
|
+
stdio: "inherit"
|
|
4710
|
+
});
|
|
4711
|
+
if (r.exitCode !== 0) {
|
|
4712
|
+
io.stderr(`${opts.bin} harness: removing ${entry.id} failed (exit ${r.exitCode}).
|
|
4713
|
+
`);
|
|
4714
|
+
return { code: r.exitCode, dropReceipt: false };
|
|
4715
|
+
}
|
|
4716
|
+
io.stdout(`${entry.id}: removed from ${prefix}
|
|
4717
|
+
`);
|
|
4718
|
+
return { code: 0, dropReceipt: true };
|
|
4719
|
+
}
|
|
4720
|
+
async function uninstallOnHost(entry, opts, io, run) {
|
|
4721
|
+
const bin = opts.bin;
|
|
4722
|
+
const env = opts.env ?? process.env;
|
|
4723
|
+
const computed = hostPrefix(homeOf(opts));
|
|
4724
|
+
if (resolveUninstallArgv(entry, computed) === null) {
|
|
4725
|
+
io.stdout(`${entry.id}: nothing to remove \u2014 npx fetches it at each run
|
|
4726
|
+
`);
|
|
4727
|
+
return 0;
|
|
4728
|
+
}
|
|
4729
|
+
let config;
|
|
4730
|
+
let workspace;
|
|
4731
|
+
try {
|
|
4732
|
+
config = readHostConfig(env);
|
|
4733
|
+
workspace = resolveHarnessRoot(void 0, env);
|
|
4734
|
+
} catch (err) {
|
|
4735
|
+
io.stderr(`${bin} harness: ${messageOf(err)}
|
|
4736
|
+
`);
|
|
4737
|
+
return 1;
|
|
4738
|
+
}
|
|
4739
|
+
const receipt = config.hostHarnesses?.[entry.id];
|
|
4740
|
+
if (receipt === void 0 && opts.force !== true) {
|
|
4741
|
+
io.stderr(
|
|
4742
|
+
`${bin} harness: no record of installing ${entry.id} on this host.
|
|
4743
|
+
Removing it could delete a harness you installed yourself. Re-run with \`--force\` to remove it from ${computed}.
|
|
4744
|
+
`
|
|
4745
|
+
);
|
|
4746
|
+
return 1;
|
|
4747
|
+
}
|
|
4748
|
+
const prefix = receipt?.prefix ?? computed;
|
|
4749
|
+
const target = hostTargetFor(receipt?.workspaceRoot ?? workspace, prefix, run, env);
|
|
4750
|
+
const outcome = await removeHarness(entry, target, prefix, void 0, opts, io);
|
|
4751
|
+
if (outcome.dropReceipt && receipt !== void 0) {
|
|
4752
|
+
const remaining = { ...config.hostHarnesses ?? {} };
|
|
4753
|
+
delete remaining[entry.id];
|
|
4754
|
+
try {
|
|
4755
|
+
writeHostConfig({ ...config, hostHarnesses: remaining }, env);
|
|
4756
|
+
} catch (err) {
|
|
4757
|
+
io.stderr(`${bin} harness: could not clear the install record: ${messageOf(err)}
|
|
4758
|
+
`);
|
|
4759
|
+
return 1;
|
|
4760
|
+
}
|
|
4761
|
+
io.stdout(`${entry.id}: cleared the host install record
|
|
4762
|
+
`);
|
|
4763
|
+
}
|
|
4764
|
+
return outcome.code;
|
|
4765
|
+
}
|
|
4766
|
+
async function runHarnessUninstall(name, opts, io) {
|
|
4767
|
+
const run = opts.run ?? spawnRunner;
|
|
4768
|
+
const root = resolveProjectRoot(opts.cwd);
|
|
4769
|
+
const entry = findHarness(name);
|
|
4770
|
+
if (!entry) return unknownHarness(name, io, opts.bin);
|
|
4771
|
+
const target = targetFor(root, run, opts.env);
|
|
4772
|
+
if (!isReachable(await resolveTargetStatus(target))) {
|
|
4773
|
+
return await uninstallOnHost(entry, opts, io, run);
|
|
4774
|
+
}
|
|
4775
|
+
return (await removeHarness(entry, target, SANDBOX_HARNESS_PREFIX, "sandbox", opts, io)).code;
|
|
4776
|
+
}
|
|
4267
4777
|
async function runHarnessInstall(name, opts, io) {
|
|
4268
4778
|
const run = opts.run ?? spawnRunner;
|
|
4269
4779
|
const env = opts.env ?? process.env;
|
|
@@ -4274,23 +4784,9 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4274
4784
|
const entry = findHarness(name);
|
|
4275
4785
|
if (!entry) return unknownHarness(name, io, opts.bin);
|
|
4276
4786
|
const target = targetFor(root, run, opts.env);
|
|
4277
|
-
|
|
4278
|
-
try {
|
|
4279
|
-
status = await target.status();
|
|
4280
|
-
} catch (err) {
|
|
4281
|
-
if (err instanceof ExecutionSpawnError && err.code === "ENOENT") {
|
|
4282
|
-
io.stderr("docker is required to install into the running sandbox but was not found on PATH\n");
|
|
4283
|
-
return 1;
|
|
4284
|
-
}
|
|
4285
|
-
throw err;
|
|
4286
|
-
}
|
|
4787
|
+
const status = await resolveTargetStatus(target);
|
|
4287
4788
|
if (!isReachable(status)) {
|
|
4288
|
-
io
|
|
4289
|
-
`${opts.bin} harness: the sandbox is not running (${status}).
|
|
4290
|
-
Start it with \`${opts.bin} sandbox\`, then re-run this command.
|
|
4291
|
-
`
|
|
4292
|
-
);
|
|
4293
|
-
return 1;
|
|
4789
|
+
return await installOnHost(entry, opts, io, run, status);
|
|
4294
4790
|
}
|
|
4295
4791
|
const hermes = entry.id === "hermes";
|
|
4296
4792
|
const installEnv = hermes ? {
|
|
@@ -4298,10 +4794,10 @@ Start it with \`${opts.bin} sandbox\`, then re-run this command.
|
|
|
4298
4794
|
HERMES_HOME: `${hermesTargetRoot(target)}/.hermes`
|
|
4299
4795
|
} : void 0;
|
|
4300
4796
|
if (hermes) {
|
|
4301
|
-
const code = await reconcileHermes(target, io, opts.bin);
|
|
4797
|
+
const code = await reconcileHermes(target, io, opts.bin, "sandbox");
|
|
4302
4798
|
if (code !== 0) return code;
|
|
4303
4799
|
}
|
|
4304
|
-
const already = await probeInstalled(target, entry, installEnv);
|
|
4800
|
+
const already = await probeInstalled(target, entry, SANDBOX_HARNESS_PREFIX, "sandbox", installEnv);
|
|
4305
4801
|
if (already === true) {
|
|
4306
4802
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
4307
4803
|
`);
|
|
@@ -4314,7 +4810,7 @@ Start it with \`${opts.bin} sandbox\`, then re-run this command.
|
|
|
4314
4810
|
io.stdout(`installing ${entry.title} into the sandbox\u2026
|
|
4315
4811
|
`);
|
|
4316
4812
|
const r = await target.exec({
|
|
4317
|
-
argv:
|
|
4813
|
+
argv: resolveInstallArgv(entry, SANDBOX_HARNESS_PREFIX),
|
|
4318
4814
|
user: entry.installUser,
|
|
4319
4815
|
stdio: "inherit",
|
|
4320
4816
|
...installEnv ? { env: installEnv } : {}
|
|
@@ -4325,12 +4821,12 @@ Start it with \`${opts.bin} sandbox\`, then re-run this command.
|
|
|
4325
4821
|
return r.exitCode;
|
|
4326
4822
|
}
|
|
4327
4823
|
if (hermes) {
|
|
4328
|
-
if (await probeInstalled(target, entry, installEnv) !== true) {
|
|
4824
|
+
if (await probeInstalled(target, entry, SANDBOX_HARNESS_PREFIX, "sandbox", installEnv) !== true) {
|
|
4329
4825
|
io.stderr(`${opts.bin} harness: Hermes installation finished but executable verification failed.
|
|
4330
4826
|
`);
|
|
4331
4827
|
return 1;
|
|
4332
4828
|
}
|
|
4333
|
-
const code = await reconcileHermes(target, io, opts.bin);
|
|
4829
|
+
const code = await reconcileHermes(target, io, opts.bin, "sandbox");
|
|
4334
4830
|
if (code !== 0) return code;
|
|
4335
4831
|
}
|
|
4336
4832
|
io.stdout(`${entry.id}: installed \u2014 see ${sourceDocsUrl(entry.docsPath)} for authentication
|
|
@@ -4338,8 +4834,14 @@ Start it with \`${opts.bin} sandbox\`, then re-run this command.
|
|
|
4338
4834
|
return 0;
|
|
4339
4835
|
}
|
|
4340
4836
|
|
|
4837
|
+
// src/commands/tool.ts
|
|
4838
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
4839
|
+
import { homedir as homedir3 } from "node:os";
|
|
4840
|
+
import { delimiter as delimiter3, join as join13, resolve as resolve13 } from "node:path";
|
|
4841
|
+
|
|
4341
4842
|
// src/lib/tools/catalog.ts
|
|
4342
4843
|
var TOOLS_DOC = "docs/installation.md";
|
|
4844
|
+
var TOOL_HOST_PLATFORM = "linux";
|
|
4343
4845
|
var TOOL_CATALOG = Object.freeze([
|
|
4344
4846
|
Object.freeze({
|
|
4345
4847
|
id: "agent-browser",
|
|
@@ -4354,6 +4856,9 @@ var TOOL_CATALOG = Object.freeze([
|
|
|
4354
4856
|
]),
|
|
4355
4857
|
installUser: "sandbox",
|
|
4356
4858
|
downloadSize: "~1 GB",
|
|
4859
|
+
hostCapable: false,
|
|
4860
|
+
notHostCapableReason: () => "agent-browser runs `agent-browser install --with-deps`, which installs system packages with the operating system package manager. AGRO installs it into the sandbox only, where that stays contained.",
|
|
4861
|
+
uninstallArgv: Object.freeze(["bash", "-lc", "pnpm remove -g agent-browser"]),
|
|
4357
4862
|
docsPath: TOOLS_DOC
|
|
4358
4863
|
}),
|
|
4359
4864
|
Object.freeze({
|
|
@@ -4385,6 +4890,8 @@ var TOOL_CATALOG = Object.freeze([
|
|
|
4385
4890
|
].join("\n")
|
|
4386
4891
|
]),
|
|
4387
4892
|
installUser: "sandbox",
|
|
4893
|
+
hostCapable: true,
|
|
4894
|
+
uninstallArgv: Object.freeze(["rm", "-rf", `${HARNESS_PREFIX_TOKEN}/bin/herdr`]),
|
|
4388
4895
|
docsPath: TOOLS_DOC
|
|
4389
4896
|
}),
|
|
4390
4897
|
Object.freeze({
|
|
@@ -4417,6 +4924,8 @@ var TOOL_CATALOG = Object.freeze([
|
|
|
4417
4924
|
].join("\n")
|
|
4418
4925
|
]),
|
|
4419
4926
|
installUser: "sandbox",
|
|
4927
|
+
hostCapable: true,
|
|
4928
|
+
uninstallArgv: Object.freeze(["rm", "-rf", `${HARNESS_PREFIX_TOKEN}/bin/cloudflared`]),
|
|
4420
4929
|
docsPath: TOOLS_DOC
|
|
4421
4930
|
}),
|
|
4422
4931
|
Object.freeze({
|
|
@@ -4442,6 +4951,13 @@ var TOOL_CATALOG = Object.freeze([
|
|
|
4442
4951
|
].join("\n")
|
|
4443
4952
|
]),
|
|
4444
4953
|
installUser: "sandbox",
|
|
4954
|
+
hostCapable: true,
|
|
4955
|
+
uninstallArgv: Object.freeze([
|
|
4956
|
+
"rm",
|
|
4957
|
+
"-rf",
|
|
4958
|
+
`${HARNESS_PREFIX_TOKEN}/bin/msb`,
|
|
4959
|
+
`${HARNESS_PREFIX_TOKEN}/microsandbox`
|
|
4960
|
+
]),
|
|
4445
4961
|
docsPath: "docs/runtimes/microsandbox.md"
|
|
4446
4962
|
}),
|
|
4447
4963
|
Object.freeze({
|
|
@@ -4452,6 +4968,8 @@ var TOOL_CATALOG = Object.freeze([
|
|
|
4452
4968
|
verifyArgv: Object.freeze(["bash", "-lc", "command -v docker >/dev/null"]),
|
|
4453
4969
|
versionArgv: Object.freeze(["docker", "--version"]),
|
|
4454
4970
|
notInstallableReason: (bin) => `The Docker CLI is installed in the base image. Note that the CLI being present says nothing about whether a daemon is reachable \u2014 \`${bin} ps <name>\` answers that.`,
|
|
4971
|
+
hostCapable: false,
|
|
4972
|
+
uninstallArgv: null,
|
|
4455
4973
|
docsPath: TOOLS_DOC
|
|
4456
4974
|
}),
|
|
4457
4975
|
Object.freeze({
|
|
@@ -4462,6 +4980,8 @@ var TOOL_CATALOG = Object.freeze([
|
|
|
4462
4980
|
verifyArgv: Object.freeze(["bash", "-lc", "command -v gh >/dev/null"]),
|
|
4463
4981
|
versionArgv: Object.freeze(["gh", "--version"]),
|
|
4464
4982
|
notInstallableReason: () => "The GitHub CLI is installed in the base image. Run `gh auth login` inside the sandbox to authenticate it.",
|
|
4983
|
+
hostCapable: false,
|
|
4984
|
+
uninstallArgv: null,
|
|
4465
4985
|
docsPath: TOOLS_DOC
|
|
4466
4986
|
}),
|
|
4467
4987
|
Object.freeze({
|
|
@@ -4493,6 +5013,13 @@ install -m 0755 "$tmp/tailscale_1.102.3_$arch/tailscaled" "$prefix/bin/tailscale
|
|
|
4493
5013
|
install -d -m 0700 "$HOME/.tailscale"`
|
|
4494
5014
|
]),
|
|
4495
5015
|
installUser: "sandbox",
|
|
5016
|
+
hostCapable: true,
|
|
5017
|
+
uninstallArgv: Object.freeze([
|
|
5018
|
+
"rm",
|
|
5019
|
+
"-rf",
|
|
5020
|
+
`${HARNESS_PREFIX_TOKEN}/bin/tailscale`,
|
|
5021
|
+
`${HARNESS_PREFIX_TOKEN}/bin/tailscaled`
|
|
5022
|
+
]),
|
|
4496
5023
|
docsPath: TOOLS_DOC
|
|
4497
5024
|
})
|
|
4498
5025
|
]);
|
|
@@ -4505,6 +5032,12 @@ function toolIds() {
|
|
|
4505
5032
|
function installableToolIds() {
|
|
4506
5033
|
return TOOL_CATALOG.filter((t) => t.installArgv !== void 0).map((t) => t.id);
|
|
4507
5034
|
}
|
|
5035
|
+
function hostCapableToolIds() {
|
|
5036
|
+
return TOOL_CATALOG.filter((t) => t.hostCapable).map((t) => t.id);
|
|
5037
|
+
}
|
|
5038
|
+
function resolveToolUninstallArgv(entry, prefix) {
|
|
5039
|
+
return entry.uninstallArgv === null ? null : entry.uninstallArgv.map((part) => part.split(HARNESS_PREFIX_TOKEN).join(prefix));
|
|
5040
|
+
}
|
|
4508
5041
|
|
|
4509
5042
|
// src/commands/tool.ts
|
|
4510
5043
|
function isReachable2(status) {
|
|
@@ -4519,56 +5052,116 @@ function targetFor2(root, run, env) {
|
|
|
4519
5052
|
...env ? { env } : {}
|
|
4520
5053
|
});
|
|
4521
5054
|
}
|
|
4522
|
-
|
|
5055
|
+
function hostPrefix2(home) {
|
|
5056
|
+
return join13(home, ".local");
|
|
5057
|
+
}
|
|
5058
|
+
function pathEntries2(env) {
|
|
5059
|
+
return (env.PATH ?? "").split(delimiter3).filter((part) => part !== "");
|
|
5060
|
+
}
|
|
5061
|
+
function onPath2(prefix, env) {
|
|
5062
|
+
return pathEntries2(env).includes(harnessBinPath(prefix));
|
|
5063
|
+
}
|
|
5064
|
+
function hostTargetFor2(root, prefix, run, env) {
|
|
5065
|
+
const path4 = [harnessBinPath(prefix), ...pathEntries2(env)].join(delimiter3);
|
|
5066
|
+
return new LocalExecutionTarget({
|
|
5067
|
+
projectRoot: root,
|
|
5068
|
+
run,
|
|
5069
|
+
env: { ...env, PATH: path4 }
|
|
5070
|
+
});
|
|
5071
|
+
}
|
|
5072
|
+
function messageOf2(error) {
|
|
5073
|
+
return error instanceof Error ? error.message : String(error);
|
|
5074
|
+
}
|
|
5075
|
+
function homeOf2(opts) {
|
|
5076
|
+
return (opts.homedir ?? homedir3)();
|
|
5077
|
+
}
|
|
5078
|
+
function platformOf(opts) {
|
|
5079
|
+
return opts.platform ?? process.platform;
|
|
5080
|
+
}
|
|
5081
|
+
function isInteractive2(opts) {
|
|
5082
|
+
return opts.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
5083
|
+
}
|
|
5084
|
+
async function tryExec(target, argv, user, env) {
|
|
4523
5085
|
try {
|
|
4524
|
-
const r = await target.exec({
|
|
5086
|
+
const r = await target.exec({
|
|
5087
|
+
argv: [...argv],
|
|
5088
|
+
...user ? { user } : {},
|
|
5089
|
+
stdio: "capture",
|
|
5090
|
+
...env ? { env } : {}
|
|
5091
|
+
});
|
|
4525
5092
|
return { exitCode: r.exitCode, stdout: r.stdout };
|
|
4526
5093
|
} catch (err) {
|
|
4527
5094
|
if (err instanceof ExecutionSpawnError) return null;
|
|
4528
5095
|
throw err;
|
|
4529
5096
|
}
|
|
4530
5097
|
}
|
|
4531
|
-
async function probeInstalled2(target, entry) {
|
|
4532
|
-
const r = await tryExec(target, entry.verifyArgv,
|
|
5098
|
+
async function probeInstalled2(target, entry, user, env) {
|
|
5099
|
+
const r = await tryExec(target, entry.verifyArgv, user, env);
|
|
4533
5100
|
return r === null ? null : r.exitCode === 0;
|
|
4534
5101
|
}
|
|
4535
|
-
async function probeVersion(target, entry) {
|
|
5102
|
+
async function probeVersion(target, entry, user) {
|
|
4536
5103
|
if (entry.versionArgv === void 0) return null;
|
|
4537
|
-
const r = await tryExec(target, entry.versionArgv,
|
|
5104
|
+
const r = await tryExec(target, entry.versionArgv, user);
|
|
4538
5105
|
if (r === null || r.exitCode !== 0) return null;
|
|
4539
5106
|
const first = r.stdout.trim().split("\n")[0] ?? "";
|
|
4540
5107
|
return first === "" ? null : first;
|
|
4541
5108
|
}
|
|
4542
|
-
|
|
5109
|
+
function rowOf(entry, installed, version, location) {
|
|
5110
|
+
return {
|
|
5111
|
+
id: entry.id,
|
|
5112
|
+
title: entry.title,
|
|
5113
|
+
binary: entry.binary,
|
|
5114
|
+
kind: entry.kind,
|
|
5115
|
+
installed,
|
|
5116
|
+
version,
|
|
5117
|
+
installable: entry.installArgv !== void 0,
|
|
5118
|
+
hostCapable: entry.hostCapable,
|
|
5119
|
+
location,
|
|
5120
|
+
docs: sourceDocsUrl(entry.docsPath)
|
|
5121
|
+
};
|
|
5122
|
+
}
|
|
5123
|
+
function probeableHost2(env, home) {
|
|
5124
|
+
let root;
|
|
5125
|
+
try {
|
|
5126
|
+
root = resolveHarnessRoot(void 0, env);
|
|
5127
|
+
} catch {
|
|
5128
|
+
return void 0;
|
|
5129
|
+
}
|
|
5130
|
+
const prefix = hostPrefix2(home);
|
|
5131
|
+
if (!existsSync19(join13(root, ".git")) && !existsSync19(prefix)) return void 0;
|
|
5132
|
+
return { root, prefix };
|
|
5133
|
+
}
|
|
5134
|
+
async function collectRows(root, run, home, env, only) {
|
|
4543
5135
|
const entries = only ? [...only] : [...TOOL_CATALOG];
|
|
4544
5136
|
const target = targetFor2(root, run, env);
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
5137
|
+
if (isReachable2(await resolveTargetStatus(target))) {
|
|
5138
|
+
const rows2 = [];
|
|
5139
|
+
for (const entry of entries) {
|
|
5140
|
+
const installed = await probeInstalled2(target, entry, "sandbox");
|
|
5141
|
+
const version = installed === true ? await probeVersion(target, entry, "sandbox") : null;
|
|
5142
|
+
rows2.push(rowOf(entry, installed, version, installed === null ? "unknown" : "sandbox"));
|
|
5143
|
+
}
|
|
5144
|
+
return { rows: rows2 };
|
|
4550
5145
|
}
|
|
5146
|
+
const host = probeableHost2(env ?? process.env, home);
|
|
5147
|
+
if (host === void 0) {
|
|
5148
|
+
return { rows: entries.map((entry) => rowOf(entry, null, null, "unknown")) };
|
|
5149
|
+
}
|
|
5150
|
+
const hostTarget = hostTargetFor2(host.root, host.prefix, run, env ?? process.env);
|
|
4551
5151
|
const rows = [];
|
|
4552
5152
|
for (const entry of entries) {
|
|
4553
|
-
const installed =
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
title: entry.title,
|
|
4557
|
-
binary: entry.binary,
|
|
4558
|
-
kind: entry.kind,
|
|
4559
|
-
installed,
|
|
4560
|
-
version: reachable && installed === true ? await probeVersion(target, entry) : null,
|
|
4561
|
-
installable: entry.installArgv !== void 0,
|
|
4562
|
-
docs: sourceDocsUrl(entry.docsPath)
|
|
4563
|
-
});
|
|
5153
|
+
const installed = await probeInstalled2(hostTarget, entry, void 0);
|
|
5154
|
+
const version = installed === true ? await probeVersion(hostTarget, entry, void 0) : null;
|
|
5155
|
+
rows.push(rowOf(entry, installed, version, installed === null ? "unknown" : "host"));
|
|
4564
5156
|
}
|
|
4565
|
-
return rows;
|
|
5157
|
+
return { rows, hostPrefix: host.prefix };
|
|
4566
5158
|
}
|
|
4567
5159
|
function cell2(value, absent) {
|
|
4568
5160
|
if (value === null) return absent;
|
|
4569
5161
|
return value ? "yes" : "no";
|
|
4570
5162
|
}
|
|
4571
|
-
function renderTable2(
|
|
5163
|
+
function renderTable2(collected, io, bin) {
|
|
5164
|
+
const rows = collected.rows;
|
|
4572
5165
|
const header2 = ["TOOL", "KIND", "INSTALLED"];
|
|
4573
5166
|
const body = rows.map((r) => [r.id, r.kind, cell2(r.installed, "?")]);
|
|
4574
5167
|
const widths = header2.map(
|
|
@@ -4577,21 +5170,31 @@ function renderTable2(rows, io, bin) {
|
|
|
4577
5170
|
const line = (cols) => cols.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd() + "\n";
|
|
4578
5171
|
io.stdout(line(header2));
|
|
4579
5172
|
for (const row of body) io.stdout(line(row));
|
|
5173
|
+
if (rows.some((r) => r.location === "host")) {
|
|
5174
|
+
io.stdout(
|
|
5175
|
+
`
|
|
5176
|
+
INSTALLED reports the host prefix ${collected.hostPrefix} \u2014 the sandbox is not running.
|
|
5177
|
+
`
|
|
5178
|
+
);
|
|
5179
|
+
return;
|
|
5180
|
+
}
|
|
4580
5181
|
if (rows.some((r) => r.installed === null)) {
|
|
4581
5182
|
io.stdout(`
|
|
4582
5183
|
INSTALLED is \`?\` \u2014 the sandbox is not running. Start it with \`${bin} sandbox\`.
|
|
4583
5184
|
`);
|
|
4584
5185
|
}
|
|
4585
5186
|
}
|
|
4586
|
-
function renderDetail(
|
|
4587
|
-
renderTable2(
|
|
4588
|
-
for (const r of rows) {
|
|
5187
|
+
function renderDetail(collected, io, bin) {
|
|
5188
|
+
renderTable2(collected, io, bin);
|
|
5189
|
+
for (const r of collected.rows) {
|
|
4589
5190
|
io.stdout(`
|
|
4590
5191
|
${r.id} \u2014 ${r.title}
|
|
4591
5192
|
`);
|
|
4592
5193
|
io.stdout(` version: ${r.version ?? "\u2014"}
|
|
4593
5194
|
`);
|
|
4594
5195
|
io.stdout(` installable: ${r.installable ? "yes" : "no"}
|
|
5196
|
+
`);
|
|
5197
|
+
io.stdout(` on the host: ${r.hostCapable ? "yes" : "no"}
|
|
4595
5198
|
`);
|
|
4596
5199
|
io.stdout(` see ${r.docs}
|
|
4597
5200
|
`);
|
|
@@ -4600,12 +5203,12 @@ ${r.id} \u2014 ${r.title}
|
|
|
4600
5203
|
async function runToolList(opts, io) {
|
|
4601
5204
|
const run = opts.run ?? spawnRunner;
|
|
4602
5205
|
const root = resolveProjectRoot(opts.cwd);
|
|
4603
|
-
const
|
|
5206
|
+
const collected = await collectRows(root, run, homeOf2(opts), opts.env);
|
|
4604
5207
|
if (opts.json) {
|
|
4605
|
-
io.stdout(`${JSON.stringify(rows, null, 2)}
|
|
5208
|
+
io.stdout(`${JSON.stringify(collected.rows, null, 2)}
|
|
4606
5209
|
`);
|
|
4607
5210
|
} else {
|
|
4608
|
-
renderTable2(
|
|
5211
|
+
renderTable2(collected, io, opts.bin);
|
|
4609
5212
|
}
|
|
4610
5213
|
return 0;
|
|
4611
5214
|
}
|
|
@@ -4626,12 +5229,12 @@ async function runToolStatus(name, opts, io) {
|
|
|
4626
5229
|
only = findTool(name);
|
|
4627
5230
|
if (!only) return unknownTool(name, io, opts.bin);
|
|
4628
5231
|
}
|
|
4629
|
-
const
|
|
5232
|
+
const collected = await collectRows(root, run, homeOf2(opts), opts.env, only ? [only] : void 0);
|
|
4630
5233
|
if (opts.json) {
|
|
4631
|
-
io.stdout(`${JSON.stringify(only ? rows[0] : rows, null, 2)}
|
|
5234
|
+
io.stdout(`${JSON.stringify(only ? collected.rows[0] : collected.rows, null, 2)}
|
|
4632
5235
|
`);
|
|
4633
5236
|
} else {
|
|
4634
|
-
renderDetail(
|
|
5237
|
+
renderDetail(collected, io, opts.bin);
|
|
4635
5238
|
}
|
|
4636
5239
|
return 0;
|
|
4637
5240
|
}
|
|
@@ -4648,6 +5251,151 @@ Re-run with --yes to accept the download.
|
|
|
4648
5251
|
);
|
|
4649
5252
|
return false;
|
|
4650
5253
|
}
|
|
5254
|
+
function unreachableReason2(status) {
|
|
5255
|
+
return runtimeIsAbsent(status) ? "No container runtime is on PATH." : `The sandbox is not running (${status}).`;
|
|
5256
|
+
}
|
|
5257
|
+
function sandboxRefusal2(bin, status) {
|
|
5258
|
+
if (runtimeIsAbsent(status)) {
|
|
5259
|
+
return `${bin} tool: no container runtime is on PATH.
|
|
5260
|
+
`;
|
|
5261
|
+
}
|
|
5262
|
+
return `${bin} tool: the sandbox is not running (${status}).
|
|
5263
|
+
Start it with \`${bin} sandbox\`, then re-run this command.
|
|
5264
|
+
`;
|
|
5265
|
+
}
|
|
5266
|
+
function hostCapableList(bin) {
|
|
5267
|
+
return `Tools that install on the host:
|
|
5268
|
+
${hostCapableToolIds().map((t) => ` ${bin} tool install ${t} --host`).join("\n")}
|
|
5269
|
+
`;
|
|
5270
|
+
}
|
|
5271
|
+
async function installOnHost2(entry, opts, io, run, status) {
|
|
5272
|
+
const bin = opts.bin;
|
|
5273
|
+
const env = opts.env ?? process.env;
|
|
5274
|
+
const flagged = opts.host === true || opts.path !== void 0;
|
|
5275
|
+
if (!entry.hostCapable) {
|
|
5276
|
+
io.stderr(sandboxRefusal2(bin, status));
|
|
5277
|
+
io.stderr(`
|
|
5278
|
+
${bin} tool: ${entry.id} cannot be installed on the host.
|
|
5279
|
+
`);
|
|
5280
|
+
io.stderr(`${(entry.notHostCapableReason ?? entry.notInstallableReason)?.(bin) ?? ""}
|
|
5281
|
+
|
|
5282
|
+
`);
|
|
5283
|
+
io.stderr(hostCapableList(bin));
|
|
5284
|
+
return 1;
|
|
5285
|
+
}
|
|
5286
|
+
const platform = platformOf(opts);
|
|
5287
|
+
if (platform !== TOOL_HOST_PLATFORM) {
|
|
5288
|
+
io.stderr(sandboxRefusal2(bin, status));
|
|
5289
|
+
io.stderr(
|
|
5290
|
+
`
|
|
5291
|
+
${bin} tool: a host install needs ${TOOL_HOST_PLATFORM}; this host is ${platform}.
|
|
5292
|
+
Every tool installer in the catalog is Debian-specific. Install ${entry.id} with your own package manager, or start the sandbox.
|
|
5293
|
+
`
|
|
5294
|
+
);
|
|
5295
|
+
return 1;
|
|
5296
|
+
}
|
|
5297
|
+
if (!flagged && !isInteractive2(opts)) {
|
|
5298
|
+
io.stderr(
|
|
5299
|
+
sandboxRefusal2(bin, status) + `Or install on the host with \`${bin} tool install ${entry.id} --host\`.
|
|
5300
|
+
`
|
|
5301
|
+
);
|
|
5302
|
+
return 1;
|
|
5303
|
+
}
|
|
5304
|
+
let root;
|
|
5305
|
+
let recorded;
|
|
5306
|
+
try {
|
|
5307
|
+
recorded = readHostConfig(env).harnessRoot;
|
|
5308
|
+
root = resolveHarnessRoot(opts.path, env);
|
|
5309
|
+
} catch (err) {
|
|
5310
|
+
io.stderr(`${bin} tool: ${messageOf2(err)}
|
|
5311
|
+
`);
|
|
5312
|
+
return 1;
|
|
5313
|
+
}
|
|
5314
|
+
const sticky = opts.path === void 0 && recorded !== void 0 && recorded !== "";
|
|
5315
|
+
if (!flagged) {
|
|
5316
|
+
const ask2 = io.ask ?? ask;
|
|
5317
|
+
const answer = (await ask2(
|
|
5318
|
+
`${unreachableReason2(status)} Install ${entry.title} on the host? [y/N]`
|
|
5319
|
+
)).trim().toLowerCase();
|
|
5320
|
+
if (!/^y/.test(answer)) {
|
|
5321
|
+
io.stderr(sandboxRefusal2(bin, status));
|
|
5322
|
+
return 1;
|
|
5323
|
+
}
|
|
5324
|
+
if (!sticky) {
|
|
5325
|
+
const chosen = (await ask2(`Harness root [${root}]:`)).trim();
|
|
5326
|
+
if (chosen !== "") root = resolve13(chosen);
|
|
5327
|
+
}
|
|
5328
|
+
}
|
|
5329
|
+
if (sticky) io.stdout(`using the recorded harness root ${root}
|
|
5330
|
+
`);
|
|
5331
|
+
try {
|
|
5332
|
+
if (!existsSync19(join13(root, ".git"))) io.stdout(`cloning ${AGRO_REPO_URL} into ${root}\u2026
|
|
5333
|
+
`);
|
|
5334
|
+
const workspace = ensureHostWorkspace(root, run);
|
|
5335
|
+
root = workspace.root;
|
|
5336
|
+
io.stdout(
|
|
5337
|
+
workspace.action === "cloned" ? `host workspace cloned into ${root}
|
|
5338
|
+
` : `host workspace reused at ${root}
|
|
5339
|
+
`
|
|
5340
|
+
);
|
|
5341
|
+
} catch (err) {
|
|
5342
|
+
io.stderr(`${bin} tool: ${messageOf2(err)}
|
|
5343
|
+
`);
|
|
5344
|
+
return 1;
|
|
5345
|
+
}
|
|
5346
|
+
const prefix = hostPrefix2(homeOf2(opts));
|
|
5347
|
+
const installEnv = { NPM_USER_PREFIX: prefix };
|
|
5348
|
+
const target = hostTargetFor2(root, prefix, run, env);
|
|
5349
|
+
if (await probeInstalled2(target, entry, void 0, installEnv) === true) {
|
|
5350
|
+
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
5351
|
+
`);
|
|
5352
|
+
return 0;
|
|
5353
|
+
}
|
|
5354
|
+
if (!await confirmDownload(entry, opts, io)) return 1;
|
|
5355
|
+
io.stdout(`installing ${entry.title} on the host\u2026
|
|
5356
|
+
`);
|
|
5357
|
+
const r = await target.exec({
|
|
5358
|
+
argv: [...entry.installArgv],
|
|
5359
|
+
stdio: "inherit",
|
|
5360
|
+
env: installEnv
|
|
5361
|
+
});
|
|
5362
|
+
if (r.exitCode !== 0) {
|
|
5363
|
+
io.stderr(`${bin} tool: installing ${entry.id} failed (exit ${r.exitCode}).
|
|
5364
|
+
`);
|
|
5365
|
+
return r.exitCode;
|
|
5366
|
+
}
|
|
5367
|
+
const receipt = {
|
|
5368
|
+
prefix,
|
|
5369
|
+
binary: entry.binary,
|
|
5370
|
+
binPath: harnessBinPath(prefix),
|
|
5371
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5372
|
+
workspaceRoot: root
|
|
5373
|
+
};
|
|
5374
|
+
try {
|
|
5375
|
+
const config = readHostConfig(env);
|
|
5376
|
+
writeHostConfig(
|
|
5377
|
+
{
|
|
5378
|
+
...config,
|
|
5379
|
+
harnessRoot: root,
|
|
5380
|
+
hostTools: { ...config.hostTools ?? {}, [entry.id]: receipt }
|
|
5381
|
+
},
|
|
5382
|
+
env
|
|
5383
|
+
);
|
|
5384
|
+
} catch (err) {
|
|
5385
|
+
io.stderr(`${bin} tool: could not record the install: ${messageOf2(err)}
|
|
5386
|
+
`);
|
|
5387
|
+
return 1;
|
|
5388
|
+
}
|
|
5389
|
+
io.stdout(`${entry.id}: installed at ${prefix} \u2014 see ${sourceDocsUrl(entry.docsPath)}
|
|
5390
|
+
`);
|
|
5391
|
+
if (!onPath2(prefix, env)) {
|
|
5392
|
+
io.stdout(`Add this line to your shell profile: export PATH="${harnessBinPath(prefix)}:$PATH"
|
|
5393
|
+
`);
|
|
5394
|
+
}
|
|
5395
|
+
io.stdout(`Run ${entry.binary} from the AGRO workspace: cd ${root}
|
|
5396
|
+
`);
|
|
5397
|
+
return 0;
|
|
5398
|
+
}
|
|
4651
5399
|
async function runToolInstall(name, opts, io) {
|
|
4652
5400
|
const run = opts.run ?? spawnRunner;
|
|
4653
5401
|
const root = resolveProjectRoot(opts.cwd);
|
|
@@ -4666,25 +5414,11 @@ ${installableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
|
4666
5414
|
return 1;
|
|
4667
5415
|
}
|
|
4668
5416
|
const target = targetFor2(root, run, opts.env);
|
|
4669
|
-
|
|
4670
|
-
try {
|
|
4671
|
-
status = await target.status();
|
|
4672
|
-
} catch (err) {
|
|
4673
|
-
if (err instanceof ExecutionSpawnError && err.code === "ENOENT") {
|
|
4674
|
-
io.stderr("docker is required to install into the running sandbox but was not found on PATH\n");
|
|
4675
|
-
return 1;
|
|
4676
|
-
}
|
|
4677
|
-
throw err;
|
|
4678
|
-
}
|
|
5417
|
+
const status = await resolveTargetStatus(target);
|
|
4679
5418
|
if (!isReachable2(status)) {
|
|
4680
|
-
io
|
|
4681
|
-
`${opts.bin} tool: the sandbox is not running (${status}).
|
|
4682
|
-
Start it with \`${opts.bin} sandbox\`, then re-run this command.
|
|
4683
|
-
`
|
|
4684
|
-
);
|
|
4685
|
-
return 1;
|
|
5419
|
+
return await installOnHost2(entry, opts, io, run, status);
|
|
4686
5420
|
}
|
|
4687
|
-
const already = await probeInstalled2(target, entry);
|
|
5421
|
+
const already = await probeInstalled2(target, entry, "sandbox");
|
|
4688
5422
|
if (already === true) {
|
|
4689
5423
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
4690
5424
|
`);
|
|
@@ -4711,6 +5445,98 @@ Start it with \`${opts.bin} sandbox\`, then re-run this command.
|
|
|
4711
5445
|
`);
|
|
4712
5446
|
return 0;
|
|
4713
5447
|
}
|
|
5448
|
+
async function removeTool(entry, target, prefix, user, opts, io) {
|
|
5449
|
+
const argv = resolveToolUninstallArgv(entry, prefix);
|
|
5450
|
+
if (await probeInstalled2(target, entry, user) !== true) {
|
|
5451
|
+
io.stdout(`${entry.id}: not installed (${entry.binary})
|
|
5452
|
+
`);
|
|
5453
|
+
return { code: 0, dropReceipt: true };
|
|
5454
|
+
}
|
|
5455
|
+
if (isInteractive2(opts)) {
|
|
5456
|
+
const ask2 = io.ask ?? ask;
|
|
5457
|
+
const answer = (await ask2(`Remove ${entry.title} from ${prefix}? [y/N]`)).trim().toLowerCase();
|
|
5458
|
+
if (!/^y/.test(answer)) {
|
|
5459
|
+
io.stderr(`${opts.bin} tool: removed nothing.
|
|
5460
|
+
`);
|
|
5461
|
+
return { code: 1, dropReceipt: false };
|
|
5462
|
+
}
|
|
5463
|
+
}
|
|
5464
|
+
io.stdout(`removing ${entry.title} from ${prefix}\u2026
|
|
5465
|
+
`);
|
|
5466
|
+
const r = await target.exec({
|
|
5467
|
+
argv,
|
|
5468
|
+
...user ? { user } : {},
|
|
5469
|
+
stdio: "inherit"
|
|
5470
|
+
});
|
|
5471
|
+
if (r.exitCode !== 0) {
|
|
5472
|
+
io.stderr(`${opts.bin} tool: removing ${entry.id} failed (exit ${r.exitCode}).
|
|
5473
|
+
`);
|
|
5474
|
+
return { code: r.exitCode, dropReceipt: false };
|
|
5475
|
+
}
|
|
5476
|
+
io.stdout(`${entry.id}: removed from ${prefix}
|
|
5477
|
+
`);
|
|
5478
|
+
return { code: 0, dropReceipt: true };
|
|
5479
|
+
}
|
|
5480
|
+
async function uninstallOnHost2(entry, opts, io, run) {
|
|
5481
|
+
const bin = opts.bin;
|
|
5482
|
+
const env = opts.env ?? process.env;
|
|
5483
|
+
const computed = hostPrefix2(homeOf2(opts));
|
|
5484
|
+
let config;
|
|
5485
|
+
let workspace;
|
|
5486
|
+
try {
|
|
5487
|
+
config = readHostConfig(env);
|
|
5488
|
+
workspace = resolveHarnessRoot(void 0, env);
|
|
5489
|
+
} catch (err) {
|
|
5490
|
+
io.stderr(`${bin} tool: ${messageOf2(err)}
|
|
5491
|
+
`);
|
|
5492
|
+
return 1;
|
|
5493
|
+
}
|
|
5494
|
+
const receipt = config.hostTools?.[entry.id];
|
|
5495
|
+
if (receipt === void 0 && opts.force !== true) {
|
|
5496
|
+
io.stderr(
|
|
5497
|
+
`${bin} tool: no record of installing ${entry.id} on this host.
|
|
5498
|
+
Removing it could delete a tool you installed yourself. Re-run with \`--force\` to remove it from ${computed}.
|
|
5499
|
+
`
|
|
5500
|
+
);
|
|
5501
|
+
return 1;
|
|
5502
|
+
}
|
|
5503
|
+
const prefix = receipt?.prefix ?? computed;
|
|
5504
|
+
const target = hostTargetFor2(receipt?.workspaceRoot ?? workspace, prefix, run, env);
|
|
5505
|
+
const outcome = await removeTool(entry, target, prefix, void 0, opts, io);
|
|
5506
|
+
if (outcome.dropReceipt && receipt !== void 0) {
|
|
5507
|
+
const remaining = { ...config.hostTools ?? {} };
|
|
5508
|
+
delete remaining[entry.id];
|
|
5509
|
+
try {
|
|
5510
|
+
writeHostConfig({ ...config, hostTools: remaining }, env);
|
|
5511
|
+
} catch (err) {
|
|
5512
|
+
io.stderr(`${bin} tool: could not clear the install record: ${messageOf2(err)}
|
|
5513
|
+
`);
|
|
5514
|
+
return 1;
|
|
5515
|
+
}
|
|
5516
|
+
io.stdout(`${entry.id}: cleared the host install record
|
|
5517
|
+
`);
|
|
5518
|
+
}
|
|
5519
|
+
return outcome.code;
|
|
5520
|
+
}
|
|
5521
|
+
async function runToolUninstall(name, opts, io) {
|
|
5522
|
+
const run = opts.run ?? spawnRunner;
|
|
5523
|
+
const root = resolveProjectRoot(opts.cwd);
|
|
5524
|
+
const entry = findTool(name);
|
|
5525
|
+
if (!entry) return unknownTool(name, io, opts.bin);
|
|
5526
|
+
if (entry.uninstallArgv === null) {
|
|
5527
|
+
io.stderr(`${opts.bin} tool: ${entry.id} cannot be removed by this command.
|
|
5528
|
+
|
|
5529
|
+
`);
|
|
5530
|
+
io.stderr(`${entry.notInstallableReason?.(opts.bin) ?? ""}
|
|
5531
|
+
`);
|
|
5532
|
+
return 1;
|
|
5533
|
+
}
|
|
5534
|
+
const target = targetFor2(root, run, opts.env);
|
|
5535
|
+
if (!isReachable2(await resolveTargetStatus(target))) {
|
|
5536
|
+
return await uninstallOnHost2(entry, opts, io, run);
|
|
5537
|
+
}
|
|
5538
|
+
return (await removeTool(entry, target, SANDBOX_HARNESS_PREFIX, "sandbox", opts, io)).code;
|
|
5539
|
+
}
|
|
4714
5540
|
|
|
4715
5541
|
// src/lib/remote.ts
|
|
4716
5542
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
@@ -4776,9 +5602,9 @@ function fetchRemoteSource(opts) {
|
|
|
4776
5602
|
}
|
|
4777
5603
|
|
|
4778
5604
|
// src/cli.ts
|
|
4779
|
-
var VERSION = true ? "0.
|
|
4780
|
-
var DEFAULT_SOURCE_OH_DIR =
|
|
4781
|
-
|
|
5605
|
+
var VERSION = true ? "0.12.0" : "0.0.0-dev";
|
|
5606
|
+
var DEFAULT_SOURCE_OH_DIR = resolve14(
|
|
5607
|
+
dirname10(fileURLToPath(import.meta.url)),
|
|
4782
5608
|
"../.."
|
|
4783
5609
|
);
|
|
4784
5610
|
var INTEGRATIONS = {};
|
|
@@ -5008,16 +5834,26 @@ function printHarnessHelp(bin = LEGACY_PRODUCT.bin) {
|
|
|
5008
5834
|
|
|
5009
5835
|
Usage:
|
|
5010
5836
|
${bin} harness list List known harnesses and their state
|
|
5011
|
-
${bin} harness install <name> Install a harness into the sandbox
|
|
5837
|
+
${bin} harness install <name> Install a harness into the sandbox or the host
|
|
5838
|
+
${bin} harness uninstall <name> Remove a harness the same way it was installed
|
|
5012
5839
|
${bin} harness status [name] Show installed state
|
|
5013
5840
|
|
|
5014
|
-
\`install\`
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5841
|
+
\`install\` and \`uninstall\` act on the running sandbox. When no sandbox is
|
|
5842
|
+
reachable they act on the host. A host install clones the AGRO workspace into the
|
|
5843
|
+
harness root, then installs the harness into \`~/.local\`. \`--path <dir>\` chooses
|
|
5844
|
+
the harness root; a successful install records it as \`harnessRoot\` in the host
|
|
5845
|
+
\`${stateNames(bin).configFile}\`. The default is \`~/.agro\`, and the install prefix is
|
|
5846
|
+
always \`~/.local\`.
|
|
5847
|
+
|
|
5848
|
+
On the host, \`uninstall\` removes only the harness \`install\` recorded, from the
|
|
5849
|
+
prefix in that record. Without a record it refuses, and \`--force\` overrides. In
|
|
5850
|
+
the sandbox it needs no record. See \`docs/harnesses/overview.md\`.
|
|
5018
5851
|
|
|
5019
5852
|
Flags:
|
|
5020
5853
|
--json Machine-readable output (list/status)
|
|
5854
|
+
--host Install on the host when the sandbox is not running (install)
|
|
5855
|
+
--path <dir> Harness root for the workspace clone; implies --host (install)
|
|
5856
|
+
--force Remove without a recorded host install (uninstall)
|
|
5021
5857
|
|
|
5022
5858
|
Harnesses:
|
|
5023
5859
|
${harnessIds().map((h) => ` ${h}`).join("\n")}
|
|
@@ -5055,19 +5891,34 @@ tunnel client, the GitHub CLI, an isolation runtime's own binary.
|
|
|
5055
5891
|
Usage:
|
|
5056
5892
|
${bin} tool list List known tools and their state
|
|
5057
5893
|
${bin} tool status [name] Show installed state and version
|
|
5058
|
-
${bin} tool install <name> Install a tool into the sandbox
|
|
5894
|
+
${bin} tool install <name> Install a tool into the sandbox or the host
|
|
5895
|
+
${bin} tool uninstall <name> Remove a tool the same way it was installed
|
|
5059
5896
|
|
|
5060
5897
|
Most tools are baked into the image and are report-only; \`install\` works on:
|
|
5061
5898
|
${installableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
5062
5899
|
|
|
5063
5900
|
\`install\` is the only door: it probes the running sandbox, installs the tool
|
|
5064
|
-
into the persistent home volume, and reports. It
|
|
5065
|
-
|
|
5066
|
-
|
|
5901
|
+
into the persistent home volume, and reports. It never rebuilds or restarts the
|
|
5902
|
+
sandbox. A large download is confirmed first, and a non-interactive run without
|
|
5903
|
+
--yes installs nothing.
|
|
5904
|
+
|
|
5905
|
+
When no sandbox is reachable, \`install\` and \`uninstall\` act on the host. A host
|
|
5906
|
+
install needs Linux, because every installer is Debian-specific, and it works
|
|
5907
|
+
only on these tools:
|
|
5908
|
+
${hostCapableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
5909
|
+
|
|
5910
|
+
A host install clones the AGRO workspace into the harness root \u2014 \`--path <dir>\`,
|
|
5911
|
+
then \`harnessRoot\` in the host \`${stateNames(bin).configFile}\`, then \`~/.agro\` \u2014 and installs into
|
|
5912
|
+
\`~/.local\`. It records the install as \`hostTools\` in that same file. On the host,
|
|
5913
|
+
\`uninstall\` removes only what \`install\` recorded, from the prefix in that record.
|
|
5914
|
+
Without a record it refuses, and \`--force\` overrides.
|
|
5067
5915
|
|
|
5068
5916
|
Flags:
|
|
5069
5917
|
--yes Accept a large download without prompting
|
|
5070
5918
|
--json Machine-readable output (list/status)
|
|
5919
|
+
--host Install on the host when the sandbox is not running (install)
|
|
5920
|
+
--path <dir> Harness root for the workspace clone; implies --host (install)
|
|
5921
|
+
--force Remove without a recorded host install (uninstall)
|
|
5071
5922
|
|
|
5072
5923
|
Tools:
|
|
5073
5924
|
${toolIds().map((t) => ` ${t}`).join("\n")}
|
|
@@ -5449,72 +6300,117 @@ function parseShellArgs(rest, bin = LEGACY_PRODUCT.bin) {
|
|
|
5449
6300
|
function parseHarnessArgs(rest, bin = LEGACY_PRODUCT.bin) {
|
|
5450
6301
|
const args = {
|
|
5451
6302
|
help: false,
|
|
5452
|
-
json: false
|
|
6303
|
+
json: false,
|
|
6304
|
+
host: false,
|
|
6305
|
+
force: false
|
|
5453
6306
|
};
|
|
5454
6307
|
if (rest.length === 0 || isHelpFlag(rest[0])) {
|
|
5455
6308
|
return { ok: true, args: { ...args, help: true } };
|
|
5456
6309
|
}
|
|
5457
6310
|
const positionals = [];
|
|
6311
|
+
let expectPath = false;
|
|
5458
6312
|
for (const token of rest) {
|
|
5459
|
-
if (
|
|
6313
|
+
if (expectPath) {
|
|
6314
|
+
expectPath = false;
|
|
6315
|
+
args.path = token;
|
|
6316
|
+
args.host = true;
|
|
6317
|
+
} else if (token === "--json") {
|
|
5460
6318
|
args.json = true;
|
|
6319
|
+
} else if (token === "--host") {
|
|
6320
|
+
args.host = true;
|
|
6321
|
+
} else if (token === "--force") {
|
|
6322
|
+
args.force = true;
|
|
6323
|
+
} else if (token === "--path") {
|
|
6324
|
+
expectPath = true;
|
|
6325
|
+
} else if (token.startsWith("--path=")) {
|
|
6326
|
+
args.path = token.slice("--path=".length);
|
|
6327
|
+
args.host = true;
|
|
5461
6328
|
} else if (token.startsWith("-")) {
|
|
5462
6329
|
return { ok: false, error: `${bin} harness: unknown flag "${token}"` };
|
|
5463
6330
|
} else {
|
|
5464
6331
|
positionals.push(token);
|
|
5465
6332
|
}
|
|
5466
6333
|
}
|
|
6334
|
+
if (expectPath || args.path === "") {
|
|
6335
|
+
return { ok: false, error: `${bin} harness: --path requires a directory` };
|
|
6336
|
+
}
|
|
5467
6337
|
const [sub, name, ...extra] = positionals;
|
|
5468
|
-
if (sub !== "list" && sub !== "install" && sub !== "status") {
|
|
6338
|
+
if (sub !== "list" && sub !== "install" && sub !== "status" && sub !== "uninstall") {
|
|
5469
6339
|
return {
|
|
5470
6340
|
ok: false,
|
|
5471
|
-
error: `${bin} harness: unknown subcommand "${sub}" \u2014 expected list, install, or status`,
|
|
6341
|
+
error: `${bin} harness: unknown subcommand "${sub}" \u2014 expected list, install, uninstall, or status`,
|
|
5472
6342
|
showHelp: true
|
|
5473
6343
|
};
|
|
5474
6344
|
}
|
|
5475
6345
|
if (extra.length > 0) {
|
|
5476
6346
|
return { ok: false, error: `${bin} harness: unexpected argument "${extra[0]}"` };
|
|
5477
6347
|
}
|
|
5478
|
-
if (sub === "install" && name === void 0) {
|
|
5479
|
-
return { ok: false, error: `${bin} harness
|
|
6348
|
+
if ((sub === "install" || sub === "uninstall") && name === void 0) {
|
|
6349
|
+
return { ok: false, error: `${bin} harness ${sub}: a harness name is required`, showHelp: true };
|
|
5480
6350
|
}
|
|
5481
6351
|
if (sub === "list" && name !== void 0) {
|
|
5482
6352
|
return { ok: false, error: `${bin} harness list: unexpected argument "${name}"` };
|
|
5483
6353
|
}
|
|
6354
|
+
if (sub !== "install" && args.host) {
|
|
6355
|
+
return { ok: false, error: `${bin} harness ${sub}: --host and --path apply to install only` };
|
|
6356
|
+
}
|
|
6357
|
+
if (sub !== "uninstall" && args.force) {
|
|
6358
|
+
return { ok: false, error: `${bin} harness ${sub}: --force applies to uninstall only` };
|
|
6359
|
+
}
|
|
5484
6360
|
args.subcommand = sub;
|
|
5485
6361
|
if (name !== void 0) args.name = name;
|
|
5486
6362
|
return { ok: true, args };
|
|
5487
6363
|
}
|
|
5488
6364
|
function parseToolArgs(rest, bin = LEGACY_PRODUCT.bin) {
|
|
5489
|
-
const args = { help: false, yes: false, json: false };
|
|
6365
|
+
const args = { help: false, yes: false, json: false, host: false, force: false };
|
|
5490
6366
|
if (rest.length === 0 || isHelpFlag(rest[0])) {
|
|
5491
6367
|
return { ok: true, args: { ...args, help: true } };
|
|
5492
6368
|
}
|
|
5493
6369
|
const positionals = [];
|
|
6370
|
+
let expectPath = false;
|
|
5494
6371
|
for (const token of rest) {
|
|
5495
|
-
if (
|
|
6372
|
+
if (expectPath) {
|
|
6373
|
+
expectPath = false;
|
|
6374
|
+
args.path = token;
|
|
6375
|
+
args.host = true;
|
|
6376
|
+
} else if (token === "--yes" || token === "-y") args.yes = true;
|
|
5496
6377
|
else if (token === "--json") args.json = true;
|
|
5497
|
-
else if (token
|
|
6378
|
+
else if (token === "--host") args.host = true;
|
|
6379
|
+
else if (token === "--force") args.force = true;
|
|
6380
|
+
else if (token === "--path") expectPath = true;
|
|
6381
|
+
else if (token.startsWith("--path=")) {
|
|
6382
|
+
args.path = token.slice("--path=".length);
|
|
6383
|
+
args.host = true;
|
|
6384
|
+
} else if (token.startsWith("-")) {
|
|
5498
6385
|
return { ok: false, error: `${bin} tool: unknown flag "${token}"` };
|
|
5499
6386
|
} else positionals.push(token);
|
|
5500
6387
|
}
|
|
6388
|
+
if (expectPath || args.path === "") {
|
|
6389
|
+
return { ok: false, error: `${bin} tool: --path requires a directory` };
|
|
6390
|
+
}
|
|
5501
6391
|
const [sub, name, ...extra] = positionals;
|
|
5502
|
-
if (sub !== "list" && sub !== "install" && sub !== "status") {
|
|
6392
|
+
if (sub !== "list" && sub !== "install" && sub !== "status" && sub !== "uninstall") {
|
|
5503
6393
|
return {
|
|
5504
6394
|
ok: false,
|
|
5505
|
-
error: `${bin} tool: unknown subcommand "${sub}" \u2014 expected list, install, or status`,
|
|
6395
|
+
error: `${bin} tool: unknown subcommand "${sub}" \u2014 expected list, install, uninstall, or status`,
|
|
5506
6396
|
showHelp: true
|
|
5507
6397
|
};
|
|
5508
6398
|
}
|
|
5509
6399
|
if (extra.length > 0) {
|
|
5510
6400
|
return { ok: false, error: `${bin} tool: unexpected argument "${extra[0]}"` };
|
|
5511
6401
|
}
|
|
5512
|
-
if (sub === "install" && name === void 0) {
|
|
5513
|
-
return { ok: false, error: `${bin} tool
|
|
6402
|
+
if ((sub === "install" || sub === "uninstall") && name === void 0) {
|
|
6403
|
+
return { ok: false, error: `${bin} tool ${sub}: a tool name is required`, showHelp: true };
|
|
5514
6404
|
}
|
|
5515
6405
|
if (sub === "list" && name !== void 0) {
|
|
5516
6406
|
return { ok: false, error: `${bin} tool list: unexpected argument "${name}"` };
|
|
5517
6407
|
}
|
|
6408
|
+
if (sub !== "install" && args.host) {
|
|
6409
|
+
return { ok: false, error: `${bin} tool ${sub}: --host and --path apply to install only` };
|
|
6410
|
+
}
|
|
6411
|
+
if (sub !== "uninstall" && args.force) {
|
|
6412
|
+
return { ok: false, error: `${bin} tool ${sub}: --force applies to uninstall only` };
|
|
6413
|
+
}
|
|
5518
6414
|
args.subcommand = sub;
|
|
5519
6415
|
if (name !== void 0) args.name = name;
|
|
5520
6416
|
return { ok: true, args };
|
|
@@ -5525,19 +6421,19 @@ function parseGatewayArgs(rest) {
|
|
|
5525
6421
|
}
|
|
5526
6422
|
return { ok: true, args: { help: false, passthrough: [...rest] } };
|
|
5527
6423
|
}
|
|
5528
|
-
function bundledPayloadExists(bundled, exists =
|
|
5529
|
-
return exists(
|
|
6424
|
+
function bundledPayloadExists(bundled, exists = existsSync20) {
|
|
6425
|
+
return exists(join14(bundled.sourceOhDir, "manifest.json"));
|
|
5530
6426
|
}
|
|
5531
6427
|
function resolveUpdateSource(args, bundled, bin = LEGACY_PRODUCT.bin) {
|
|
5532
|
-
const exists = bundled.exists ??
|
|
6428
|
+
const exists = bundled.exists ?? existsSync20;
|
|
5533
6429
|
if (args.fromDir !== void 0) {
|
|
5534
|
-
return { kind: "local", fromDir:
|
|
6430
|
+
return { kind: "local", fromDir: resolve14(args.fromDir) };
|
|
5535
6431
|
}
|
|
5536
6432
|
if (args.fromRemote) {
|
|
5537
6433
|
return { kind: "remote", ref: args.ref };
|
|
5538
6434
|
}
|
|
5539
6435
|
if (bundledPayloadExists(bundled, exists)) {
|
|
5540
|
-
return { kind: "local", fromDir:
|
|
6436
|
+
return { kind: "local", fromDir: resolve14(bundled.sourceOhDir, "..") };
|
|
5541
6437
|
}
|
|
5542
6438
|
return {
|
|
5543
6439
|
kind: "remote",
|
|
@@ -5549,7 +6445,7 @@ function resolveUpdateSource(args, bundled, bin = LEGACY_PRODUCT.bin) {
|
|
|
5549
6445
|
function readPayloadVersion(checkoutDir) {
|
|
5550
6446
|
try {
|
|
5551
6447
|
const parsed = JSON.parse(
|
|
5552
|
-
|
|
6448
|
+
readFileSync12(join14(resolveControlDir(checkoutDir).path, "cli", "package.json"), "utf8")
|
|
5553
6449
|
);
|
|
5554
6450
|
if (parsed && typeof parsed.version === "string") return parsed.version;
|
|
5555
6451
|
} catch {
|
|
@@ -5558,8 +6454,8 @@ function readPayloadVersion(checkoutDir) {
|
|
|
5558
6454
|
}
|
|
5559
6455
|
async function runWithRemoteSource(hooks, fn) {
|
|
5560
6456
|
const fetch2 = hooks.fetch ?? fetchRemoteSource;
|
|
5561
|
-
const mkdtemp = hooks.mkdtemp ?? (() =>
|
|
5562
|
-
const rm = hooks.rm ?? ((dir) =>
|
|
6457
|
+
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync4(join14(tmpdir3(), "oh-remote-")));
|
|
6458
|
+
const rm = hooks.rm ?? ((dir) => rmSync4(dir, { recursive: true, force: true }));
|
|
5563
6459
|
const out = hooks.stdout ?? ((s) => process.stdout.write(s));
|
|
5564
6460
|
const checkoutDir = mkdtemp();
|
|
5565
6461
|
try {
|
|
@@ -5824,7 +6720,14 @@ async function main(argv) {
|
|
|
5824
6720
|
if (a.subcommand === "status") {
|
|
5825
6721
|
return await runHarnessStatus(a.name, { bin, json: a.json }, io);
|
|
5826
6722
|
}
|
|
5827
|
-
|
|
6723
|
+
if (a.subcommand === "uninstall") {
|
|
6724
|
+
return await runHarnessUninstall(a.name, { bin, force: a.force }, io);
|
|
6725
|
+
}
|
|
6726
|
+
return await runHarnessInstall(
|
|
6727
|
+
a.name,
|
|
6728
|
+
{ bin, host: a.host, ...a.path !== void 0 ? { path: a.path } : {} },
|
|
6729
|
+
io
|
|
6730
|
+
);
|
|
5828
6731
|
}
|
|
5829
6732
|
if (first === "tool") {
|
|
5830
6733
|
const parsed = parseToolArgs(argv.slice(1), bin);
|
|
@@ -5849,7 +6752,14 @@ async function main(argv) {
|
|
|
5849
6752
|
if (a.subcommand === "status") {
|
|
5850
6753
|
return await runToolStatus(a.name, { bin, json: a.json }, io);
|
|
5851
6754
|
}
|
|
5852
|
-
|
|
6755
|
+
if (a.subcommand === "uninstall") {
|
|
6756
|
+
return await runToolUninstall(a.name, { bin, force: a.force }, io);
|
|
6757
|
+
}
|
|
6758
|
+
return await runToolInstall(
|
|
6759
|
+
a.name,
|
|
6760
|
+
{ bin, yes: a.yes, host: a.host, ...a.path !== void 0 ? { path: a.path } : {} },
|
|
6761
|
+
io
|
|
6762
|
+
);
|
|
5853
6763
|
}
|
|
5854
6764
|
if (first === "gateway") {
|
|
5855
6765
|
const parsed = parseGatewayArgs(argv.slice(1));
|