@mifune/agro 0.12.0 → 0.12.2
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 +200 -65
- package/dist/oh.js +200 -65
- package/package.json +1 -1
package/dist/agro.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { dirname as dirname10, join as join14, resolve as
|
|
5
|
+
import { dirname as dirname10, join as join14, resolve as resolve13 } from "node:path";
|
|
6
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
|
|
|
@@ -230,6 +230,11 @@ function resolveRegistryHome(env = process.env, home = homedir(), warn) {
|
|
|
230
230
|
if (configured !== void 0) return { path: resolve(configured), configured: true };
|
|
231
231
|
return { path: home, configured: false };
|
|
232
232
|
}
|
|
233
|
+
function resolveAgroUserStateHome(env = process.env, home = homedir(), warn) {
|
|
234
|
+
const configured = aliasedEnvValue(env, "HOME", warn);
|
|
235
|
+
if (configured !== void 0) return resolve(configured);
|
|
236
|
+
return join(home, GENERATIONS.agro.userStateDir);
|
|
237
|
+
}
|
|
233
238
|
function resolveUserStateHome(env = process.env, home = homedir(), warn) {
|
|
234
239
|
const configured = aliasedEnvValue(env, "HOME", warn);
|
|
235
240
|
if (configured !== void 0) return resolve(configured);
|
|
@@ -1959,8 +1964,8 @@ function redact(token) {
|
|
|
1959
1964
|
async function ask(question) {
|
|
1960
1965
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1961
1966
|
try {
|
|
1962
|
-
return await new Promise((
|
|
1963
|
-
rl.question(` ${question} `, (answer) =>
|
|
1967
|
+
return await new Promise((resolve14) => {
|
|
1968
|
+
rl.question(` ${question} `, (answer) => resolve14(answer.trim()));
|
|
1964
1969
|
});
|
|
1965
1970
|
} finally {
|
|
1966
1971
|
rl.close();
|
|
@@ -1972,7 +1977,7 @@ async function askSecret(question) {
|
|
|
1972
1977
|
process.stdin.setRawMode(true);
|
|
1973
1978
|
process.stdin.resume();
|
|
1974
1979
|
const bytes = [];
|
|
1975
|
-
return await new Promise((
|
|
1980
|
+
return await new Promise((resolve14) => {
|
|
1976
1981
|
const cleanup = () => {
|
|
1977
1982
|
try {
|
|
1978
1983
|
process.stdin.setRawMode(false);
|
|
@@ -1990,7 +1995,7 @@ async function askSecret(question) {
|
|
|
1990
1995
|
} else if (byte === 13 || byte === 10) {
|
|
1991
1996
|
cleanup();
|
|
1992
1997
|
process.stdout.write("\n");
|
|
1993
|
-
|
|
1998
|
+
resolve14(Buffer.from(bytes).toString("utf8").trim());
|
|
1994
1999
|
return;
|
|
1995
2000
|
} else if (byte === 127 || byte === 8) {
|
|
1996
2001
|
if (bytes.length > 0) {
|
|
@@ -2040,10 +2045,10 @@ async function askSecretPiped(question) {
|
|
|
2040
2045
|
};
|
|
2041
2046
|
process.on("SIGINT", onSigint);
|
|
2042
2047
|
try {
|
|
2043
|
-
return await new Promise((
|
|
2048
|
+
return await new Promise((resolve14) => {
|
|
2044
2049
|
rl.question(` ${question} `, (answer) => {
|
|
2045
2050
|
process.stdout.write("\n");
|
|
2046
|
-
|
|
2051
|
+
resolve14(answer.trim());
|
|
2047
2052
|
});
|
|
2048
2053
|
});
|
|
2049
2054
|
} finally {
|
|
@@ -4001,7 +4006,7 @@ async function runSandboxList(opts, io) {
|
|
|
4001
4006
|
// src/commands/harness.ts
|
|
4002
4007
|
import { existsSync as existsSync18 } from "node:fs";
|
|
4003
4008
|
import { homedir as homedir2 } from "node:os";
|
|
4004
|
-
import { delimiter as delimiter2, join as join12
|
|
4009
|
+
import { delimiter as delimiter2, join as join12 } from "node:path";
|
|
4005
4010
|
|
|
4006
4011
|
// src/lib/docs.ts
|
|
4007
4012
|
var SOURCE_DOCS_BASE = "https://github.com/mifunedev/agro/blob/main/";
|
|
@@ -4017,19 +4022,35 @@ function sourceDocsUrl(docsPath) {
|
|
|
4017
4022
|
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
4018
4023
|
import { basename as basename6, dirname as dirname8, isAbsolute as isAbsolute2, join as join10, resolve as resolve10 } from "node:path";
|
|
4019
4024
|
var HOST_CONFIG_MODE = 420;
|
|
4020
|
-
|
|
4021
|
-
|
|
4025
|
+
var HOST_CONFIG_FILE = "config.json";
|
|
4026
|
+
var WORKSPACES_SUBDIR = "workspaces";
|
|
4027
|
+
var DEFAULT_WORKSPACE_NAME = "default";
|
|
4028
|
+
function hostStateHome(env, home) {
|
|
4029
|
+
return resolveAgroUserStateHome(env, home);
|
|
4030
|
+
}
|
|
4031
|
+
function hostConfigPath(env, home) {
|
|
4032
|
+
return join10(hostStateHome(env, home), HOST_CONFIG_FILE);
|
|
4033
|
+
}
|
|
4034
|
+
function assertWorkspaceName(name) {
|
|
4035
|
+
if (!SANDBOX_NAME_PATTERN.test(name)) {
|
|
4036
|
+
throw new Error(
|
|
4037
|
+
`invalid workspace name "${name}" \u2014 use lowercase letters, digits and dashes, starting with a letter or digit`
|
|
4038
|
+
);
|
|
4039
|
+
}
|
|
4022
4040
|
}
|
|
4023
|
-
function
|
|
4024
|
-
|
|
4025
|
-
return join10(stateHome, hostConfigFileName(stateHome));
|
|
4041
|
+
function workspacesRoot(env, home) {
|
|
4042
|
+
return join10(hostStateHome(env, home), WORKSPACES_SUBDIR);
|
|
4026
4043
|
}
|
|
4027
|
-
function
|
|
4028
|
-
|
|
4044
|
+
function workspaceRoot(name, env, home) {
|
|
4045
|
+
assertWorkspaceName(name);
|
|
4046
|
+
return join10(workspacesRoot(env, home), name);
|
|
4047
|
+
}
|
|
4048
|
+
function defaultHarnessRoot(env, home) {
|
|
4049
|
+
return join10(workspacesRoot(env, home), DEFAULT_WORKSPACE_NAME);
|
|
4029
4050
|
}
|
|
4030
4051
|
function validateHostConfig(value) {
|
|
4031
4052
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4032
|
-
throw new Error(`${
|
|
4053
|
+
throw new Error(`${HOST_CONFIG_FILE}: must contain a JSON object`);
|
|
4033
4054
|
}
|
|
4034
4055
|
const record = value;
|
|
4035
4056
|
if (record.version !== void 0 && record.version !== 1) {
|
|
@@ -4081,8 +4102,8 @@ function validateReceipts(value, field, noun) {
|
|
|
4081
4102
|
}
|
|
4082
4103
|
}
|
|
4083
4104
|
}
|
|
4084
|
-
function readHostConfig(env
|
|
4085
|
-
const path4 = hostConfigPath(env);
|
|
4105
|
+
function readHostConfig(env, home) {
|
|
4106
|
+
const path4 = hostConfigPath(env, home);
|
|
4086
4107
|
if (!existsSync16(path4)) return { version: 1 };
|
|
4087
4108
|
let raw;
|
|
4088
4109
|
try {
|
|
@@ -4099,8 +4120,8 @@ function readHostConfig(env = process.env) {
|
|
|
4099
4120
|
}
|
|
4100
4121
|
return validateHostConfig(parsed);
|
|
4101
4122
|
}
|
|
4102
|
-
function writeHostConfig(config, env
|
|
4103
|
-
const path4 = hostConfigPath(env);
|
|
4123
|
+
function writeHostConfig(config, env, home) {
|
|
4124
|
+
const path4 = hostConfigPath(env, home);
|
|
4104
4125
|
const validated = validateHostConfig({ ...config, version: 1 });
|
|
4105
4126
|
const body = `${JSON.stringify(validated, null, 2)}
|
|
4106
4127
|
`;
|
|
@@ -4117,14 +4138,14 @@ function writeHostConfig(config, env = process.env) {
|
|
|
4117
4138
|
throw error;
|
|
4118
4139
|
}
|
|
4119
4140
|
}
|
|
4120
|
-
function resolveHarnessRoot(explicit, env
|
|
4141
|
+
function resolveHarnessRoot(explicit, env, home) {
|
|
4121
4142
|
if (typeof explicit === "string" && explicit !== "") return resolve10(explicit);
|
|
4122
|
-
const configured = readHostConfig(env).harnessRoot;
|
|
4143
|
+
const configured = readHostConfig(env, home).harnessRoot;
|
|
4123
4144
|
if (typeof configured === "string" && configured !== "") return resolve10(configured);
|
|
4124
|
-
return defaultHarnessRoot(env);
|
|
4145
|
+
return defaultHarnessRoot(env, home);
|
|
4125
4146
|
}
|
|
4126
4147
|
function fieldError2(path4, requirement) {
|
|
4127
|
-
return new Error(`${
|
|
4148
|
+
return new Error(`${HOST_CONFIG_FILE}: ${path4} ${requirement}`);
|
|
4128
4149
|
}
|
|
4129
4150
|
|
|
4130
4151
|
// src/lib/host-workspace.ts
|
|
@@ -4137,6 +4158,40 @@ var IGNORABLE_ENTRIES = [
|
|
|
4137
4158
|
GENERATIONS.agro.configFile,
|
|
4138
4159
|
GENERATIONS.legacy.configFile
|
|
4139
4160
|
];
|
|
4161
|
+
var STATE_HOME_DIRS = [
|
|
4162
|
+
GENERATIONS.legacy.userStateDir,
|
|
4163
|
+
GENERATIONS.agro.userStateDir
|
|
4164
|
+
];
|
|
4165
|
+
function isDirectory2(path4) {
|
|
4166
|
+
return statSync5(path4, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
4167
|
+
}
|
|
4168
|
+
function stateHomes(home) {
|
|
4169
|
+
return STATE_HOME_DIRS.map((name) => join11(home, name));
|
|
4170
|
+
}
|
|
4171
|
+
function stateHomeRefusal(bin, home) {
|
|
4172
|
+
const [legacy, agro] = stateHomes(home);
|
|
4173
|
+
if (isDirectory2(legacy) && isDirectory2(agro)) {
|
|
4174
|
+
return `${bin} harness: the host state home is split \u2014 ${legacy} and ${agro} both exist.
|
|
4175
|
+
Merge them with \`${bin} migrate --home\`, then re-run this command.
|
|
4176
|
+
`;
|
|
4177
|
+
}
|
|
4178
|
+
if (isDirectory2(legacy)) {
|
|
4179
|
+
return `${bin} harness: the host state home is the legacy ${legacy}, and host state now lives in ${agro}.
|
|
4180
|
+
Creating ${agro} beside it would split the two.
|
|
4181
|
+
Migrate first with \`${bin} migrate --home\`, then re-run this command.
|
|
4182
|
+
`;
|
|
4183
|
+
}
|
|
4184
|
+
return void 0;
|
|
4185
|
+
}
|
|
4186
|
+
function stateHomeRootRefusal(bin, root, home) {
|
|
4187
|
+
const target = resolve11(root);
|
|
4188
|
+
const enclosing = stateHomes(home).find((state) => target === state);
|
|
4189
|
+
if (enclosing === void 0) return void 0;
|
|
4190
|
+
return `${bin} harness: the harness root ${target} is the state home ${enclosing} itself.
|
|
4191
|
+
A workspace there collides with AGRO state and blocks every command run from ${home}.
|
|
4192
|
+
Move it out \u2014 for example \`mv ${enclosing} ${join11(home, "agro")}\` \u2014 then re-run this command.
|
|
4193
|
+
`;
|
|
4194
|
+
}
|
|
4140
4195
|
function isIgnorableEntry(name) {
|
|
4141
4196
|
return name.startsWith(".") || IGNORABLE_ENTRIES.includes(name);
|
|
4142
4197
|
}
|
|
@@ -4207,7 +4262,8 @@ var HARNESS_CATALOG = [
|
|
|
4207
4262
|
verifyArgv: ["claude", "--version"],
|
|
4208
4263
|
uninstallArgv: ["npm", "--prefix", HARNESS_PREFIX_TOKEN, "uninstall", "-g", "@anthropic-ai/claude-code"],
|
|
4209
4264
|
docsPath: "docs/harnesses/claude-code.md",
|
|
4210
|
-
kind: "installable"
|
|
4265
|
+
kind: "installable",
|
|
4266
|
+
bypassPermissionsFlag: "--permission-mode bypassPermissions"
|
|
4211
4267
|
},
|
|
4212
4268
|
{
|
|
4213
4269
|
id: "codex",
|
|
@@ -4354,6 +4410,9 @@ function resolveVerifyArgv(entry, prefix) {
|
|
|
4354
4410
|
function resolveUninstallArgv(entry, prefix) {
|
|
4355
4411
|
return entry.uninstallArgv === null ? null : substitute(entry.uninstallArgv, prefix);
|
|
4356
4412
|
}
|
|
4413
|
+
function harnessLaunchCommand(entry) {
|
|
4414
|
+
return entry.bypassPermissionsFlag === void 0 ? entry.binary : `${entry.binary} ${entry.bypassPermissionsFlag}`;
|
|
4415
|
+
}
|
|
4357
4416
|
function harnessBinPath(prefix) {
|
|
4358
4417
|
return `${prefix}/bin`;
|
|
4359
4418
|
}
|
|
@@ -4421,7 +4480,7 @@ function stateOf(entry, installed, location) {
|
|
|
4421
4480
|
function probeableHost(env, home) {
|
|
4422
4481
|
let root;
|
|
4423
4482
|
try {
|
|
4424
|
-
root = resolveHarnessRoot(void 0, env);
|
|
4483
|
+
root = resolveHarnessRoot(void 0, env, home);
|
|
4425
4484
|
} catch {
|
|
4426
4485
|
return void 0;
|
|
4427
4486
|
}
|
|
@@ -4556,7 +4615,13 @@ function isInteractive(opts) {
|
|
|
4556
4615
|
async function installOnHost(entry, opts, io, run, status) {
|
|
4557
4616
|
const bin = opts.bin;
|
|
4558
4617
|
const env = opts.env ?? process.env;
|
|
4559
|
-
const
|
|
4618
|
+
const home = homeOf(opts);
|
|
4619
|
+
const flagged = opts.host === true || opts.path !== void 0 || opts.workspace !== void 0;
|
|
4620
|
+
const generation = stateHomeRefusal(bin, home);
|
|
4621
|
+
if (generation !== void 0) {
|
|
4622
|
+
io.stderr(generation);
|
|
4623
|
+
return 1;
|
|
4624
|
+
}
|
|
4560
4625
|
if (!flagged && !isInteractive(opts)) {
|
|
4561
4626
|
io.stderr(
|
|
4562
4627
|
sandboxRefusal(bin, status) + `Or install on the host with \`${bin} harness install ${entry.id} --host\`.
|
|
@@ -4571,15 +4636,17 @@ async function installOnHost(entry, opts, io, run, status) {
|
|
|
4571
4636
|
}
|
|
4572
4637
|
let root;
|
|
4573
4638
|
let recorded;
|
|
4639
|
+
let explicit;
|
|
4574
4640
|
try {
|
|
4575
|
-
|
|
4576
|
-
|
|
4641
|
+
explicit = opts.workspace !== void 0 ? workspaceRoot(opts.workspace, env, home) : opts.path;
|
|
4642
|
+
recorded = readHostConfig(env, home).harnessRoot;
|
|
4643
|
+
root = resolveHarnessRoot(explicit, env, home);
|
|
4577
4644
|
} catch (err) {
|
|
4578
4645
|
io.stderr(`${bin} harness: ${messageOf(err)}
|
|
4579
4646
|
`);
|
|
4580
4647
|
return 1;
|
|
4581
4648
|
}
|
|
4582
|
-
const sticky =
|
|
4649
|
+
const sticky = explicit === void 0 && recorded !== void 0 && recorded !== "";
|
|
4583
4650
|
if (!flagged) {
|
|
4584
4651
|
const ask2 = io.ask ?? ask;
|
|
4585
4652
|
const answer = (await ask2(
|
|
@@ -4590,12 +4657,23 @@ async function installOnHost(entry, opts, io, run, status) {
|
|
|
4590
4657
|
return 1;
|
|
4591
4658
|
}
|
|
4592
4659
|
if (!sticky) {
|
|
4593
|
-
const chosen = (await ask2(`
|
|
4594
|
-
|
|
4660
|
+
const chosen = (await ask2(`Workspace name [${DEFAULT_WORKSPACE_NAME}]:`)).trim();
|
|
4661
|
+
try {
|
|
4662
|
+
root = workspaceRoot(chosen === "" ? DEFAULT_WORKSPACE_NAME : chosen, env, home);
|
|
4663
|
+
} catch (err) {
|
|
4664
|
+
io.stderr(`${bin} harness: ${messageOf(err)}
|
|
4665
|
+
`);
|
|
4666
|
+
return 1;
|
|
4667
|
+
}
|
|
4595
4668
|
}
|
|
4596
4669
|
}
|
|
4597
4670
|
if (sticky) io.stdout(`using the recorded harness root ${root}
|
|
4598
4671
|
`);
|
|
4672
|
+
const nested = stateHomeRootRefusal(bin, root, home);
|
|
4673
|
+
if (nested !== void 0) {
|
|
4674
|
+
io.stderr(nested);
|
|
4675
|
+
return 1;
|
|
4676
|
+
}
|
|
4599
4677
|
try {
|
|
4600
4678
|
if (!existsSync18(join12(root, ".git"))) io.stdout(`cloning ${AGRO_REPO_URL} into ${root}\u2026
|
|
4601
4679
|
`);
|
|
@@ -4611,8 +4689,21 @@ async function installOnHost(entry, opts, io, run, status) {
|
|
|
4611
4689
|
`);
|
|
4612
4690
|
return 1;
|
|
4613
4691
|
}
|
|
4614
|
-
const prefix = hostPrefix(
|
|
4692
|
+
const prefix = hostPrefix(home);
|
|
4615
4693
|
const target = hostTargetFor(root, prefix, run, env);
|
|
4694
|
+
const linked = await target.exec({
|
|
4695
|
+
argv: remoteControlDirScript(root, "scripts/link-providers.sh", ["--init"]),
|
|
4696
|
+
env: aliasedEnvPair("PROJECT_ROOT", root),
|
|
4697
|
+
stdio: "inherit"
|
|
4698
|
+
});
|
|
4699
|
+
if (linked.exitCode !== 0) {
|
|
4700
|
+
io.stderr(
|
|
4701
|
+
`${bin} harness: could not link provider skills in ${root} (exit ${linked.exitCode}); nothing was installed.
|
|
4702
|
+
Run: bash ${root}/.agro/scripts/link-providers.sh --init
|
|
4703
|
+
`
|
|
4704
|
+
);
|
|
4705
|
+
return 1;
|
|
4706
|
+
}
|
|
4616
4707
|
const hermes = entry.id === "hermes";
|
|
4617
4708
|
const installEnv = hermes ? {
|
|
4618
4709
|
...aliasedEnvPair("PROJECT_ROOT", root),
|
|
@@ -4656,14 +4747,15 @@ async function installOnHost(entry, opts, io, run, status) {
|
|
|
4656
4747
|
workspaceRoot: root
|
|
4657
4748
|
};
|
|
4658
4749
|
try {
|
|
4659
|
-
const config = readHostConfig(env);
|
|
4750
|
+
const config = readHostConfig(env, home);
|
|
4660
4751
|
writeHostConfig(
|
|
4661
4752
|
{
|
|
4662
4753
|
...config,
|
|
4663
4754
|
harnessRoot: root,
|
|
4664
4755
|
hostHarnesses: { ...config.hostHarnesses ?? {}, [entry.id]: receipt }
|
|
4665
4756
|
},
|
|
4666
|
-
env
|
|
4757
|
+
env,
|
|
4758
|
+
home
|
|
4667
4759
|
);
|
|
4668
4760
|
} catch (err) {
|
|
4669
4761
|
io.stderr(`${bin} harness: could not record the install: ${messageOf(err)}
|
|
@@ -4676,7 +4768,7 @@ async function installOnHost(entry, opts, io, run, status) {
|
|
|
4676
4768
|
io.stdout(`Add this line to your shell profile: export PATH="${harnessBinPath(prefix)}:$PATH"
|
|
4677
4769
|
`);
|
|
4678
4770
|
}
|
|
4679
|
-
io.stdout(`Run ${entry.
|
|
4771
|
+
io.stdout(`Run ${entry.title} in the AGRO workspace: cd ${root} && ${harnessLaunchCommand(entry)}
|
|
4680
4772
|
`);
|
|
4681
4773
|
return 0;
|
|
4682
4774
|
}
|
|
@@ -4720,7 +4812,13 @@ async function removeHarness(entry, target, prefix, user, opts, io) {
|
|
|
4720
4812
|
async function uninstallOnHost(entry, opts, io, run) {
|
|
4721
4813
|
const bin = opts.bin;
|
|
4722
4814
|
const env = opts.env ?? process.env;
|
|
4723
|
-
const
|
|
4815
|
+
const home = homeOf(opts);
|
|
4816
|
+
const computed = hostPrefix(home);
|
|
4817
|
+
const generation = stateHomeRefusal(bin, home);
|
|
4818
|
+
if (generation !== void 0) {
|
|
4819
|
+
io.stderr(generation);
|
|
4820
|
+
return 1;
|
|
4821
|
+
}
|
|
4724
4822
|
if (resolveUninstallArgv(entry, computed) === null) {
|
|
4725
4823
|
io.stdout(`${entry.id}: nothing to remove \u2014 npx fetches it at each run
|
|
4726
4824
|
`);
|
|
@@ -4729,13 +4827,18 @@ async function uninstallOnHost(entry, opts, io, run) {
|
|
|
4729
4827
|
let config;
|
|
4730
4828
|
let workspace;
|
|
4731
4829
|
try {
|
|
4732
|
-
config = readHostConfig(env);
|
|
4733
|
-
workspace = resolveHarnessRoot(void 0, env);
|
|
4830
|
+
config = readHostConfig(env, home);
|
|
4831
|
+
workspace = resolveHarnessRoot(void 0, env, home);
|
|
4734
4832
|
} catch (err) {
|
|
4735
4833
|
io.stderr(`${bin} harness: ${messageOf(err)}
|
|
4736
4834
|
`);
|
|
4737
4835
|
return 1;
|
|
4738
4836
|
}
|
|
4837
|
+
const nested = stateHomeRootRefusal(bin, workspace, home);
|
|
4838
|
+
if (nested !== void 0) {
|
|
4839
|
+
io.stderr(nested);
|
|
4840
|
+
return 1;
|
|
4841
|
+
}
|
|
4739
4842
|
const receipt = config.hostHarnesses?.[entry.id];
|
|
4740
4843
|
if (receipt === void 0 && opts.force !== true) {
|
|
4741
4844
|
io.stderr(
|
|
@@ -4752,7 +4855,7 @@ Removing it could delete a harness you installed yourself. Re-run with \`--force
|
|
|
4752
4855
|
const remaining = { ...config.hostHarnesses ?? {} };
|
|
4753
4856
|
delete remaining[entry.id];
|
|
4754
4857
|
try {
|
|
4755
|
-
writeHostConfig({ ...config, hostHarnesses: remaining }, env);
|
|
4858
|
+
writeHostConfig({ ...config, hostHarnesses: remaining }, env, home);
|
|
4756
4859
|
} catch (err) {
|
|
4757
4860
|
io.stderr(`${bin} harness: could not clear the install record: ${messageOf(err)}
|
|
4758
4861
|
`);
|
|
@@ -4837,7 +4940,7 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4837
4940
|
// src/commands/tool.ts
|
|
4838
4941
|
import { existsSync as existsSync19 } from "node:fs";
|
|
4839
4942
|
import { homedir as homedir3 } from "node:os";
|
|
4840
|
-
import { delimiter as delimiter3, join as join13, resolve as
|
|
4943
|
+
import { delimiter as delimiter3, join as join13, resolve as resolve12 } from "node:path";
|
|
4841
4944
|
|
|
4842
4945
|
// src/lib/tools/catalog.ts
|
|
4843
4946
|
var TOOLS_DOC = "docs/installation.md";
|
|
@@ -5123,7 +5226,7 @@ function rowOf(entry, installed, version, location) {
|
|
|
5123
5226
|
function probeableHost2(env, home) {
|
|
5124
5227
|
let root;
|
|
5125
5228
|
try {
|
|
5126
|
-
root = resolveHarnessRoot(void 0, env);
|
|
5229
|
+
root = resolveHarnessRoot(void 0, env, home);
|
|
5127
5230
|
} catch {
|
|
5128
5231
|
return void 0;
|
|
5129
5232
|
}
|
|
@@ -5271,6 +5374,7 @@ ${hostCapableToolIds().map((t) => ` ${bin} tool install ${t} --host`).join("\n"
|
|
|
5271
5374
|
async function installOnHost2(entry, opts, io, run, status) {
|
|
5272
5375
|
const bin = opts.bin;
|
|
5273
5376
|
const env = opts.env ?? process.env;
|
|
5377
|
+
const home = homeOf2(opts);
|
|
5274
5378
|
const flagged = opts.host === true || opts.path !== void 0;
|
|
5275
5379
|
if (!entry.hostCapable) {
|
|
5276
5380
|
io.stderr(sandboxRefusal2(bin, status));
|
|
@@ -5304,8 +5408,8 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5304
5408
|
let root;
|
|
5305
5409
|
let recorded;
|
|
5306
5410
|
try {
|
|
5307
|
-
recorded = readHostConfig(env).harnessRoot;
|
|
5308
|
-
root = resolveHarnessRoot(opts.path, env);
|
|
5411
|
+
recorded = readHostConfig(env, home).harnessRoot;
|
|
5412
|
+
root = resolveHarnessRoot(opts.path, env, home);
|
|
5309
5413
|
} catch (err) {
|
|
5310
5414
|
io.stderr(`${bin} tool: ${messageOf2(err)}
|
|
5311
5415
|
`);
|
|
@@ -5323,7 +5427,7 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5323
5427
|
}
|
|
5324
5428
|
if (!sticky) {
|
|
5325
5429
|
const chosen = (await ask2(`Harness root [${root}]:`)).trim();
|
|
5326
|
-
if (chosen !== "") root =
|
|
5430
|
+
if (chosen !== "") root = resolve12(chosen);
|
|
5327
5431
|
}
|
|
5328
5432
|
}
|
|
5329
5433
|
if (sticky) io.stdout(`using the recorded harness root ${root}
|
|
@@ -5343,7 +5447,7 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5343
5447
|
`);
|
|
5344
5448
|
return 1;
|
|
5345
5449
|
}
|
|
5346
|
-
const prefix = hostPrefix2(
|
|
5450
|
+
const prefix = hostPrefix2(home);
|
|
5347
5451
|
const installEnv = { NPM_USER_PREFIX: prefix };
|
|
5348
5452
|
const target = hostTargetFor2(root, prefix, run, env);
|
|
5349
5453
|
if (await probeInstalled2(target, entry, void 0, installEnv) === true) {
|
|
@@ -5372,14 +5476,15 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5372
5476
|
workspaceRoot: root
|
|
5373
5477
|
};
|
|
5374
5478
|
try {
|
|
5375
|
-
const config = readHostConfig(env);
|
|
5479
|
+
const config = readHostConfig(env, home);
|
|
5376
5480
|
writeHostConfig(
|
|
5377
5481
|
{
|
|
5378
5482
|
...config,
|
|
5379
5483
|
harnessRoot: root,
|
|
5380
5484
|
hostTools: { ...config.hostTools ?? {}, [entry.id]: receipt }
|
|
5381
5485
|
},
|
|
5382
|
-
env
|
|
5486
|
+
env,
|
|
5487
|
+
home
|
|
5383
5488
|
);
|
|
5384
5489
|
} catch (err) {
|
|
5385
5490
|
io.stderr(`${bin} tool: could not record the install: ${messageOf2(err)}
|
|
@@ -5480,12 +5585,13 @@ async function removeTool(entry, target, prefix, user, opts, io) {
|
|
|
5480
5585
|
async function uninstallOnHost2(entry, opts, io, run) {
|
|
5481
5586
|
const bin = opts.bin;
|
|
5482
5587
|
const env = opts.env ?? process.env;
|
|
5483
|
-
const
|
|
5588
|
+
const home = homeOf2(opts);
|
|
5589
|
+
const computed = hostPrefix2(home);
|
|
5484
5590
|
let config;
|
|
5485
5591
|
let workspace;
|
|
5486
5592
|
try {
|
|
5487
|
-
config = readHostConfig(env);
|
|
5488
|
-
workspace = resolveHarnessRoot(void 0, env);
|
|
5593
|
+
config = readHostConfig(env, home);
|
|
5594
|
+
workspace = resolveHarnessRoot(void 0, env, home);
|
|
5489
5595
|
} catch (err) {
|
|
5490
5596
|
io.stderr(`${bin} tool: ${messageOf2(err)}
|
|
5491
5597
|
`);
|
|
@@ -5507,7 +5613,7 @@ Removing it could delete a tool you installed yourself. Re-run with \`--force\`
|
|
|
5507
5613
|
const remaining = { ...config.hostTools ?? {} };
|
|
5508
5614
|
delete remaining[entry.id];
|
|
5509
5615
|
try {
|
|
5510
|
-
writeHostConfig({ ...config, hostTools: remaining }, env);
|
|
5616
|
+
writeHostConfig({ ...config, hostTools: remaining }, env, home);
|
|
5511
5617
|
} catch (err) {
|
|
5512
5618
|
io.stderr(`${bin} tool: could not clear the install record: ${messageOf2(err)}
|
|
5513
5619
|
`);
|
|
@@ -5602,8 +5708,8 @@ function fetchRemoteSource(opts) {
|
|
|
5602
5708
|
}
|
|
5603
5709
|
|
|
5604
5710
|
// src/cli.ts
|
|
5605
|
-
var VERSION = true ? "0.12.
|
|
5606
|
-
var DEFAULT_SOURCE_OH_DIR =
|
|
5711
|
+
var VERSION = true ? "0.12.2" : "0.0.0-dev";
|
|
5712
|
+
var DEFAULT_SOURCE_OH_DIR = resolve13(
|
|
5607
5713
|
dirname10(fileURLToPath(import.meta.url)),
|
|
5608
5714
|
"../.."
|
|
5609
5715
|
);
|
|
@@ -5840,10 +5946,11 @@ Usage:
|
|
|
5840
5946
|
|
|
5841
5947
|
\`install\` and \`uninstall\` act on the running sandbox. When no sandbox is
|
|
5842
5948
|
reachable they act on the host. A host install clones the AGRO workspace into the
|
|
5843
|
-
harness root, then installs the harness into \`~/.local\`.
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5949
|
+
harness root, then installs the harness into \`~/.local\`. The default root is the
|
|
5950
|
+
workspace registry entry \`~/.agro/workspaces/default\`; an interactive run asks for
|
|
5951
|
+
the workspace name. \`--workspace <name>\` chooses that entry and \`--path <dir>\` a
|
|
5952
|
+
directory outside the registry. A successful install records the root as
|
|
5953
|
+
\`harnessRoot\` in \`~/.agro/config.json\`. The install prefix is always \`~/.local\`.
|
|
5847
5954
|
|
|
5848
5955
|
On the host, \`uninstall\` removes only the harness \`install\` recorded, from the
|
|
5849
5956
|
prefix in that record. Without a record it refuses, and \`--force\` overrides. In
|
|
@@ -5852,7 +5959,8 @@ the sandbox it needs no record. See \`docs/harnesses/overview.md\`.
|
|
|
5852
5959
|
Flags:
|
|
5853
5960
|
--json Machine-readable output (list/status)
|
|
5854
5961
|
--host Install on the host when the sandbox is not running (install)
|
|
5855
|
-
--
|
|
5962
|
+
--workspace <name> Workspace registry entry to clone into; implies --host (install)
|
|
5963
|
+
--path <dir> Harness root outside the registry; implies --host (install)
|
|
5856
5964
|
--force Remove without a recorded host install (uninstall)
|
|
5857
5965
|
|
|
5858
5966
|
Harnesses:
|
|
@@ -5908,8 +6016,8 @@ only on these tools:
|
|
|
5908
6016
|
${hostCapableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
5909
6017
|
|
|
5910
6018
|
A host install clones the AGRO workspace into the harness root \u2014 \`--path <dir>\`,
|
|
5911
|
-
then \`harnessRoot\` in
|
|
5912
|
-
\`~/.local\`. It records the install as \`hostTools\` in that same file. On the host,
|
|
6019
|
+
then \`harnessRoot\` in \`~/.agro/config.json\`, then \`~/.agro/workspaces/default\` \u2014 and
|
|
6020
|
+
installs into \`~/.local\`. It records the install as \`hostTools\` in that same file. On the host,
|
|
5913
6021
|
\`uninstall\` removes only what \`install\` recorded, from the prefix in that record.
|
|
5914
6022
|
Without a record it refuses, and \`--force\` overrides.
|
|
5915
6023
|
|
|
@@ -6309,11 +6417,16 @@ function parseHarnessArgs(rest, bin = LEGACY_PRODUCT.bin) {
|
|
|
6309
6417
|
}
|
|
6310
6418
|
const positionals = [];
|
|
6311
6419
|
let expectPath = false;
|
|
6420
|
+
let expectWorkspace = false;
|
|
6312
6421
|
for (const token of rest) {
|
|
6313
6422
|
if (expectPath) {
|
|
6314
6423
|
expectPath = false;
|
|
6315
6424
|
args.path = token;
|
|
6316
6425
|
args.host = true;
|
|
6426
|
+
} else if (expectWorkspace) {
|
|
6427
|
+
expectWorkspace = false;
|
|
6428
|
+
args.workspace = token;
|
|
6429
|
+
args.host = true;
|
|
6317
6430
|
} else if (token === "--json") {
|
|
6318
6431
|
args.json = true;
|
|
6319
6432
|
} else if (token === "--host") {
|
|
@@ -6325,6 +6438,11 @@ function parseHarnessArgs(rest, bin = LEGACY_PRODUCT.bin) {
|
|
|
6325
6438
|
} else if (token.startsWith("--path=")) {
|
|
6326
6439
|
args.path = token.slice("--path=".length);
|
|
6327
6440
|
args.host = true;
|
|
6441
|
+
} else if (token === "--workspace") {
|
|
6442
|
+
expectWorkspace = true;
|
|
6443
|
+
} else if (token.startsWith("--workspace=")) {
|
|
6444
|
+
args.workspace = token.slice("--workspace=".length);
|
|
6445
|
+
args.host = true;
|
|
6328
6446
|
} else if (token.startsWith("-")) {
|
|
6329
6447
|
return { ok: false, error: `${bin} harness: unknown flag "${token}"` };
|
|
6330
6448
|
} else {
|
|
@@ -6334,6 +6452,15 @@ function parseHarnessArgs(rest, bin = LEGACY_PRODUCT.bin) {
|
|
|
6334
6452
|
if (expectPath || args.path === "") {
|
|
6335
6453
|
return { ok: false, error: `${bin} harness: --path requires a directory` };
|
|
6336
6454
|
}
|
|
6455
|
+
if (expectWorkspace || args.workspace === "") {
|
|
6456
|
+
return { ok: false, error: `${bin} harness: --workspace requires a name` };
|
|
6457
|
+
}
|
|
6458
|
+
if (args.path !== void 0 && args.workspace !== void 0) {
|
|
6459
|
+
return {
|
|
6460
|
+
ok: false,
|
|
6461
|
+
error: `${bin} harness: --workspace names a registry entry and --path names a directory \u2014 pass one, not both`
|
|
6462
|
+
};
|
|
6463
|
+
}
|
|
6337
6464
|
const [sub, name, ...extra] = positionals;
|
|
6338
6465
|
if (sub !== "list" && sub !== "install" && sub !== "status" && sub !== "uninstall") {
|
|
6339
6466
|
return {
|
|
@@ -6352,7 +6479,10 @@ function parseHarnessArgs(rest, bin = LEGACY_PRODUCT.bin) {
|
|
|
6352
6479
|
return { ok: false, error: `${bin} harness list: unexpected argument "${name}"` };
|
|
6353
6480
|
}
|
|
6354
6481
|
if (sub !== "install" && args.host) {
|
|
6355
|
-
return {
|
|
6482
|
+
return {
|
|
6483
|
+
ok: false,
|
|
6484
|
+
error: `${bin} harness ${sub}: --host, --workspace and --path apply to install only`
|
|
6485
|
+
};
|
|
6356
6486
|
}
|
|
6357
6487
|
if (sub !== "uninstall" && args.force) {
|
|
6358
6488
|
return { ok: false, error: `${bin} harness ${sub}: --force applies to uninstall only` };
|
|
@@ -6427,13 +6557,13 @@ function bundledPayloadExists(bundled, exists = existsSync20) {
|
|
|
6427
6557
|
function resolveUpdateSource(args, bundled, bin = LEGACY_PRODUCT.bin) {
|
|
6428
6558
|
const exists = bundled.exists ?? existsSync20;
|
|
6429
6559
|
if (args.fromDir !== void 0) {
|
|
6430
|
-
return { kind: "local", fromDir:
|
|
6560
|
+
return { kind: "local", fromDir: resolve13(args.fromDir) };
|
|
6431
6561
|
}
|
|
6432
6562
|
if (args.fromRemote) {
|
|
6433
6563
|
return { kind: "remote", ref: args.ref };
|
|
6434
6564
|
}
|
|
6435
6565
|
if (bundledPayloadExists(bundled, exists)) {
|
|
6436
|
-
return { kind: "local", fromDir:
|
|
6566
|
+
return { kind: "local", fromDir: resolve13(bundled.sourceOhDir, "..") };
|
|
6437
6567
|
}
|
|
6438
6568
|
return {
|
|
6439
6569
|
kind: "remote",
|
|
@@ -6725,7 +6855,12 @@ async function main(argv) {
|
|
|
6725
6855
|
}
|
|
6726
6856
|
return await runHarnessInstall(
|
|
6727
6857
|
a.name,
|
|
6728
|
-
{
|
|
6858
|
+
{
|
|
6859
|
+
bin,
|
|
6860
|
+
host: a.host,
|
|
6861
|
+
...a.path !== void 0 ? { path: a.path } : {},
|
|
6862
|
+
...a.workspace !== void 0 ? { workspace: a.workspace } : {}
|
|
6863
|
+
},
|
|
6729
6864
|
io
|
|
6730
6865
|
);
|
|
6731
6866
|
}
|
package/dist/oh.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { dirname as dirname10, join as join14, resolve as
|
|
5
|
+
import { dirname as dirname10, join as join14, resolve as resolve13 } from "node:path";
|
|
6
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
|
|
|
@@ -230,6 +230,11 @@ function resolveRegistryHome(env = process.env, home = homedir(), warn) {
|
|
|
230
230
|
if (configured !== void 0) return { path: resolve(configured), configured: true };
|
|
231
231
|
return { path: home, configured: false };
|
|
232
232
|
}
|
|
233
|
+
function resolveAgroUserStateHome(env = process.env, home = homedir(), warn) {
|
|
234
|
+
const configured = aliasedEnvValue(env, "HOME", warn);
|
|
235
|
+
if (configured !== void 0) return resolve(configured);
|
|
236
|
+
return join(home, GENERATIONS.agro.userStateDir);
|
|
237
|
+
}
|
|
233
238
|
function resolveUserStateHome(env = process.env, home = homedir(), warn) {
|
|
234
239
|
const configured = aliasedEnvValue(env, "HOME", warn);
|
|
235
240
|
if (configured !== void 0) return resolve(configured);
|
|
@@ -1959,8 +1964,8 @@ function redact(token) {
|
|
|
1959
1964
|
async function ask(question) {
|
|
1960
1965
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1961
1966
|
try {
|
|
1962
|
-
return await new Promise((
|
|
1963
|
-
rl.question(` ${question} `, (answer) =>
|
|
1967
|
+
return await new Promise((resolve14) => {
|
|
1968
|
+
rl.question(` ${question} `, (answer) => resolve14(answer.trim()));
|
|
1964
1969
|
});
|
|
1965
1970
|
} finally {
|
|
1966
1971
|
rl.close();
|
|
@@ -1972,7 +1977,7 @@ async function askSecret(question) {
|
|
|
1972
1977
|
process.stdin.setRawMode(true);
|
|
1973
1978
|
process.stdin.resume();
|
|
1974
1979
|
const bytes = [];
|
|
1975
|
-
return await new Promise((
|
|
1980
|
+
return await new Promise((resolve14) => {
|
|
1976
1981
|
const cleanup = () => {
|
|
1977
1982
|
try {
|
|
1978
1983
|
process.stdin.setRawMode(false);
|
|
@@ -1990,7 +1995,7 @@ async function askSecret(question) {
|
|
|
1990
1995
|
} else if (byte === 13 || byte === 10) {
|
|
1991
1996
|
cleanup();
|
|
1992
1997
|
process.stdout.write("\n");
|
|
1993
|
-
|
|
1998
|
+
resolve14(Buffer.from(bytes).toString("utf8").trim());
|
|
1994
1999
|
return;
|
|
1995
2000
|
} else if (byte === 127 || byte === 8) {
|
|
1996
2001
|
if (bytes.length > 0) {
|
|
@@ -2040,10 +2045,10 @@ async function askSecretPiped(question) {
|
|
|
2040
2045
|
};
|
|
2041
2046
|
process.on("SIGINT", onSigint);
|
|
2042
2047
|
try {
|
|
2043
|
-
return await new Promise((
|
|
2048
|
+
return await new Promise((resolve14) => {
|
|
2044
2049
|
rl.question(` ${question} `, (answer) => {
|
|
2045
2050
|
process.stdout.write("\n");
|
|
2046
|
-
|
|
2051
|
+
resolve14(answer.trim());
|
|
2047
2052
|
});
|
|
2048
2053
|
});
|
|
2049
2054
|
} finally {
|
|
@@ -4001,7 +4006,7 @@ async function runSandboxList(opts, io) {
|
|
|
4001
4006
|
// src/commands/harness.ts
|
|
4002
4007
|
import { existsSync as existsSync18 } from "node:fs";
|
|
4003
4008
|
import { homedir as homedir2 } from "node:os";
|
|
4004
|
-
import { delimiter as delimiter2, join as join12
|
|
4009
|
+
import { delimiter as delimiter2, join as join12 } from "node:path";
|
|
4005
4010
|
|
|
4006
4011
|
// src/lib/docs.ts
|
|
4007
4012
|
var SOURCE_DOCS_BASE = "https://github.com/mifunedev/agro/blob/main/";
|
|
@@ -4017,19 +4022,35 @@ function sourceDocsUrl(docsPath) {
|
|
|
4017
4022
|
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
4018
4023
|
import { basename as basename6, dirname as dirname8, isAbsolute as isAbsolute2, join as join10, resolve as resolve10 } from "node:path";
|
|
4019
4024
|
var HOST_CONFIG_MODE = 420;
|
|
4020
|
-
|
|
4021
|
-
|
|
4025
|
+
var HOST_CONFIG_FILE = "config.json";
|
|
4026
|
+
var WORKSPACES_SUBDIR = "workspaces";
|
|
4027
|
+
var DEFAULT_WORKSPACE_NAME = "default";
|
|
4028
|
+
function hostStateHome(env, home) {
|
|
4029
|
+
return resolveAgroUserStateHome(env, home);
|
|
4030
|
+
}
|
|
4031
|
+
function hostConfigPath(env, home) {
|
|
4032
|
+
return join10(hostStateHome(env, home), HOST_CONFIG_FILE);
|
|
4033
|
+
}
|
|
4034
|
+
function assertWorkspaceName(name) {
|
|
4035
|
+
if (!SANDBOX_NAME_PATTERN.test(name)) {
|
|
4036
|
+
throw new Error(
|
|
4037
|
+
`invalid workspace name "${name}" \u2014 use lowercase letters, digits and dashes, starting with a letter or digit`
|
|
4038
|
+
);
|
|
4039
|
+
}
|
|
4022
4040
|
}
|
|
4023
|
-
function
|
|
4024
|
-
|
|
4025
|
-
return join10(stateHome, hostConfigFileName(stateHome));
|
|
4041
|
+
function workspacesRoot(env, home) {
|
|
4042
|
+
return join10(hostStateHome(env, home), WORKSPACES_SUBDIR);
|
|
4026
4043
|
}
|
|
4027
|
-
function
|
|
4028
|
-
|
|
4044
|
+
function workspaceRoot(name, env, home) {
|
|
4045
|
+
assertWorkspaceName(name);
|
|
4046
|
+
return join10(workspacesRoot(env, home), name);
|
|
4047
|
+
}
|
|
4048
|
+
function defaultHarnessRoot(env, home) {
|
|
4049
|
+
return join10(workspacesRoot(env, home), DEFAULT_WORKSPACE_NAME);
|
|
4029
4050
|
}
|
|
4030
4051
|
function validateHostConfig(value) {
|
|
4031
4052
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4032
|
-
throw new Error(`${
|
|
4053
|
+
throw new Error(`${HOST_CONFIG_FILE}: must contain a JSON object`);
|
|
4033
4054
|
}
|
|
4034
4055
|
const record = value;
|
|
4035
4056
|
if (record.version !== void 0 && record.version !== 1) {
|
|
@@ -4081,8 +4102,8 @@ function validateReceipts(value, field, noun) {
|
|
|
4081
4102
|
}
|
|
4082
4103
|
}
|
|
4083
4104
|
}
|
|
4084
|
-
function readHostConfig(env
|
|
4085
|
-
const path4 = hostConfigPath(env);
|
|
4105
|
+
function readHostConfig(env, home) {
|
|
4106
|
+
const path4 = hostConfigPath(env, home);
|
|
4086
4107
|
if (!existsSync16(path4)) return { version: 1 };
|
|
4087
4108
|
let raw;
|
|
4088
4109
|
try {
|
|
@@ -4099,8 +4120,8 @@ function readHostConfig(env = process.env) {
|
|
|
4099
4120
|
}
|
|
4100
4121
|
return validateHostConfig(parsed);
|
|
4101
4122
|
}
|
|
4102
|
-
function writeHostConfig(config, env
|
|
4103
|
-
const path4 = hostConfigPath(env);
|
|
4123
|
+
function writeHostConfig(config, env, home) {
|
|
4124
|
+
const path4 = hostConfigPath(env, home);
|
|
4104
4125
|
const validated = validateHostConfig({ ...config, version: 1 });
|
|
4105
4126
|
const body = `${JSON.stringify(validated, null, 2)}
|
|
4106
4127
|
`;
|
|
@@ -4117,14 +4138,14 @@ function writeHostConfig(config, env = process.env) {
|
|
|
4117
4138
|
throw error;
|
|
4118
4139
|
}
|
|
4119
4140
|
}
|
|
4120
|
-
function resolveHarnessRoot(explicit, env
|
|
4141
|
+
function resolveHarnessRoot(explicit, env, home) {
|
|
4121
4142
|
if (typeof explicit === "string" && explicit !== "") return resolve10(explicit);
|
|
4122
|
-
const configured = readHostConfig(env).harnessRoot;
|
|
4143
|
+
const configured = readHostConfig(env, home).harnessRoot;
|
|
4123
4144
|
if (typeof configured === "string" && configured !== "") return resolve10(configured);
|
|
4124
|
-
return defaultHarnessRoot(env);
|
|
4145
|
+
return defaultHarnessRoot(env, home);
|
|
4125
4146
|
}
|
|
4126
4147
|
function fieldError2(path4, requirement) {
|
|
4127
|
-
return new Error(`${
|
|
4148
|
+
return new Error(`${HOST_CONFIG_FILE}: ${path4} ${requirement}`);
|
|
4128
4149
|
}
|
|
4129
4150
|
|
|
4130
4151
|
// src/lib/host-workspace.ts
|
|
@@ -4137,6 +4158,40 @@ var IGNORABLE_ENTRIES = [
|
|
|
4137
4158
|
GENERATIONS.agro.configFile,
|
|
4138
4159
|
GENERATIONS.legacy.configFile
|
|
4139
4160
|
];
|
|
4161
|
+
var STATE_HOME_DIRS = [
|
|
4162
|
+
GENERATIONS.legacy.userStateDir,
|
|
4163
|
+
GENERATIONS.agro.userStateDir
|
|
4164
|
+
];
|
|
4165
|
+
function isDirectory2(path4) {
|
|
4166
|
+
return statSync5(path4, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
4167
|
+
}
|
|
4168
|
+
function stateHomes(home) {
|
|
4169
|
+
return STATE_HOME_DIRS.map((name) => join11(home, name));
|
|
4170
|
+
}
|
|
4171
|
+
function stateHomeRefusal(bin, home) {
|
|
4172
|
+
const [legacy, agro] = stateHomes(home);
|
|
4173
|
+
if (isDirectory2(legacy) && isDirectory2(agro)) {
|
|
4174
|
+
return `${bin} harness: the host state home is split \u2014 ${legacy} and ${agro} both exist.
|
|
4175
|
+
Merge them with \`${bin} migrate --home\`, then re-run this command.
|
|
4176
|
+
`;
|
|
4177
|
+
}
|
|
4178
|
+
if (isDirectory2(legacy)) {
|
|
4179
|
+
return `${bin} harness: the host state home is the legacy ${legacy}, and host state now lives in ${agro}.
|
|
4180
|
+
Creating ${agro} beside it would split the two.
|
|
4181
|
+
Migrate first with \`${bin} migrate --home\`, then re-run this command.
|
|
4182
|
+
`;
|
|
4183
|
+
}
|
|
4184
|
+
return void 0;
|
|
4185
|
+
}
|
|
4186
|
+
function stateHomeRootRefusal(bin, root, home) {
|
|
4187
|
+
const target = resolve11(root);
|
|
4188
|
+
const enclosing = stateHomes(home).find((state) => target === state);
|
|
4189
|
+
if (enclosing === void 0) return void 0;
|
|
4190
|
+
return `${bin} harness: the harness root ${target} is the state home ${enclosing} itself.
|
|
4191
|
+
A workspace there collides with AGRO state and blocks every command run from ${home}.
|
|
4192
|
+
Move it out \u2014 for example \`mv ${enclosing} ${join11(home, "agro")}\` \u2014 then re-run this command.
|
|
4193
|
+
`;
|
|
4194
|
+
}
|
|
4140
4195
|
function isIgnorableEntry(name) {
|
|
4141
4196
|
return name.startsWith(".") || IGNORABLE_ENTRIES.includes(name);
|
|
4142
4197
|
}
|
|
@@ -4207,7 +4262,8 @@ var HARNESS_CATALOG = [
|
|
|
4207
4262
|
verifyArgv: ["claude", "--version"],
|
|
4208
4263
|
uninstallArgv: ["npm", "--prefix", HARNESS_PREFIX_TOKEN, "uninstall", "-g", "@anthropic-ai/claude-code"],
|
|
4209
4264
|
docsPath: "docs/harnesses/claude-code.md",
|
|
4210
|
-
kind: "installable"
|
|
4265
|
+
kind: "installable",
|
|
4266
|
+
bypassPermissionsFlag: "--permission-mode bypassPermissions"
|
|
4211
4267
|
},
|
|
4212
4268
|
{
|
|
4213
4269
|
id: "codex",
|
|
@@ -4354,6 +4410,9 @@ function resolveVerifyArgv(entry, prefix) {
|
|
|
4354
4410
|
function resolveUninstallArgv(entry, prefix) {
|
|
4355
4411
|
return entry.uninstallArgv === null ? null : substitute(entry.uninstallArgv, prefix);
|
|
4356
4412
|
}
|
|
4413
|
+
function harnessLaunchCommand(entry) {
|
|
4414
|
+
return entry.bypassPermissionsFlag === void 0 ? entry.binary : `${entry.binary} ${entry.bypassPermissionsFlag}`;
|
|
4415
|
+
}
|
|
4357
4416
|
function harnessBinPath(prefix) {
|
|
4358
4417
|
return `${prefix}/bin`;
|
|
4359
4418
|
}
|
|
@@ -4421,7 +4480,7 @@ function stateOf(entry, installed, location) {
|
|
|
4421
4480
|
function probeableHost(env, home) {
|
|
4422
4481
|
let root;
|
|
4423
4482
|
try {
|
|
4424
|
-
root = resolveHarnessRoot(void 0, env);
|
|
4483
|
+
root = resolveHarnessRoot(void 0, env, home);
|
|
4425
4484
|
} catch {
|
|
4426
4485
|
return void 0;
|
|
4427
4486
|
}
|
|
@@ -4556,7 +4615,13 @@ function isInteractive(opts) {
|
|
|
4556
4615
|
async function installOnHost(entry, opts, io, run, status) {
|
|
4557
4616
|
const bin = opts.bin;
|
|
4558
4617
|
const env = opts.env ?? process.env;
|
|
4559
|
-
const
|
|
4618
|
+
const home = homeOf(opts);
|
|
4619
|
+
const flagged = opts.host === true || opts.path !== void 0 || opts.workspace !== void 0;
|
|
4620
|
+
const generation = stateHomeRefusal(bin, home);
|
|
4621
|
+
if (generation !== void 0) {
|
|
4622
|
+
io.stderr(generation);
|
|
4623
|
+
return 1;
|
|
4624
|
+
}
|
|
4560
4625
|
if (!flagged && !isInteractive(opts)) {
|
|
4561
4626
|
io.stderr(
|
|
4562
4627
|
sandboxRefusal(bin, status) + `Or install on the host with \`${bin} harness install ${entry.id} --host\`.
|
|
@@ -4571,15 +4636,17 @@ async function installOnHost(entry, opts, io, run, status) {
|
|
|
4571
4636
|
}
|
|
4572
4637
|
let root;
|
|
4573
4638
|
let recorded;
|
|
4639
|
+
let explicit;
|
|
4574
4640
|
try {
|
|
4575
|
-
|
|
4576
|
-
|
|
4641
|
+
explicit = opts.workspace !== void 0 ? workspaceRoot(opts.workspace, env, home) : opts.path;
|
|
4642
|
+
recorded = readHostConfig(env, home).harnessRoot;
|
|
4643
|
+
root = resolveHarnessRoot(explicit, env, home);
|
|
4577
4644
|
} catch (err) {
|
|
4578
4645
|
io.stderr(`${bin} harness: ${messageOf(err)}
|
|
4579
4646
|
`);
|
|
4580
4647
|
return 1;
|
|
4581
4648
|
}
|
|
4582
|
-
const sticky =
|
|
4649
|
+
const sticky = explicit === void 0 && recorded !== void 0 && recorded !== "";
|
|
4583
4650
|
if (!flagged) {
|
|
4584
4651
|
const ask2 = io.ask ?? ask;
|
|
4585
4652
|
const answer = (await ask2(
|
|
@@ -4590,12 +4657,23 @@ async function installOnHost(entry, opts, io, run, status) {
|
|
|
4590
4657
|
return 1;
|
|
4591
4658
|
}
|
|
4592
4659
|
if (!sticky) {
|
|
4593
|
-
const chosen = (await ask2(`
|
|
4594
|
-
|
|
4660
|
+
const chosen = (await ask2(`Workspace name [${DEFAULT_WORKSPACE_NAME}]:`)).trim();
|
|
4661
|
+
try {
|
|
4662
|
+
root = workspaceRoot(chosen === "" ? DEFAULT_WORKSPACE_NAME : chosen, env, home);
|
|
4663
|
+
} catch (err) {
|
|
4664
|
+
io.stderr(`${bin} harness: ${messageOf(err)}
|
|
4665
|
+
`);
|
|
4666
|
+
return 1;
|
|
4667
|
+
}
|
|
4595
4668
|
}
|
|
4596
4669
|
}
|
|
4597
4670
|
if (sticky) io.stdout(`using the recorded harness root ${root}
|
|
4598
4671
|
`);
|
|
4672
|
+
const nested = stateHomeRootRefusal(bin, root, home);
|
|
4673
|
+
if (nested !== void 0) {
|
|
4674
|
+
io.stderr(nested);
|
|
4675
|
+
return 1;
|
|
4676
|
+
}
|
|
4599
4677
|
try {
|
|
4600
4678
|
if (!existsSync18(join12(root, ".git"))) io.stdout(`cloning ${AGRO_REPO_URL} into ${root}\u2026
|
|
4601
4679
|
`);
|
|
@@ -4611,8 +4689,21 @@ async function installOnHost(entry, opts, io, run, status) {
|
|
|
4611
4689
|
`);
|
|
4612
4690
|
return 1;
|
|
4613
4691
|
}
|
|
4614
|
-
const prefix = hostPrefix(
|
|
4692
|
+
const prefix = hostPrefix(home);
|
|
4615
4693
|
const target = hostTargetFor(root, prefix, run, env);
|
|
4694
|
+
const linked = await target.exec({
|
|
4695
|
+
argv: remoteControlDirScript(root, "scripts/link-providers.sh", ["--init"]),
|
|
4696
|
+
env: aliasedEnvPair("PROJECT_ROOT", root),
|
|
4697
|
+
stdio: "inherit"
|
|
4698
|
+
});
|
|
4699
|
+
if (linked.exitCode !== 0) {
|
|
4700
|
+
io.stderr(
|
|
4701
|
+
`${bin} harness: could not link provider skills in ${root} (exit ${linked.exitCode}); nothing was installed.
|
|
4702
|
+
Run: bash ${root}/.agro/scripts/link-providers.sh --init
|
|
4703
|
+
`
|
|
4704
|
+
);
|
|
4705
|
+
return 1;
|
|
4706
|
+
}
|
|
4616
4707
|
const hermes = entry.id === "hermes";
|
|
4617
4708
|
const installEnv = hermes ? {
|
|
4618
4709
|
...aliasedEnvPair("PROJECT_ROOT", root),
|
|
@@ -4656,14 +4747,15 @@ async function installOnHost(entry, opts, io, run, status) {
|
|
|
4656
4747
|
workspaceRoot: root
|
|
4657
4748
|
};
|
|
4658
4749
|
try {
|
|
4659
|
-
const config = readHostConfig(env);
|
|
4750
|
+
const config = readHostConfig(env, home);
|
|
4660
4751
|
writeHostConfig(
|
|
4661
4752
|
{
|
|
4662
4753
|
...config,
|
|
4663
4754
|
harnessRoot: root,
|
|
4664
4755
|
hostHarnesses: { ...config.hostHarnesses ?? {}, [entry.id]: receipt }
|
|
4665
4756
|
},
|
|
4666
|
-
env
|
|
4757
|
+
env,
|
|
4758
|
+
home
|
|
4667
4759
|
);
|
|
4668
4760
|
} catch (err) {
|
|
4669
4761
|
io.stderr(`${bin} harness: could not record the install: ${messageOf(err)}
|
|
@@ -4676,7 +4768,7 @@ async function installOnHost(entry, opts, io, run, status) {
|
|
|
4676
4768
|
io.stdout(`Add this line to your shell profile: export PATH="${harnessBinPath(prefix)}:$PATH"
|
|
4677
4769
|
`);
|
|
4678
4770
|
}
|
|
4679
|
-
io.stdout(`Run ${entry.
|
|
4771
|
+
io.stdout(`Run ${entry.title} in the AGRO workspace: cd ${root} && ${harnessLaunchCommand(entry)}
|
|
4680
4772
|
`);
|
|
4681
4773
|
return 0;
|
|
4682
4774
|
}
|
|
@@ -4720,7 +4812,13 @@ async function removeHarness(entry, target, prefix, user, opts, io) {
|
|
|
4720
4812
|
async function uninstallOnHost(entry, opts, io, run) {
|
|
4721
4813
|
const bin = opts.bin;
|
|
4722
4814
|
const env = opts.env ?? process.env;
|
|
4723
|
-
const
|
|
4815
|
+
const home = homeOf(opts);
|
|
4816
|
+
const computed = hostPrefix(home);
|
|
4817
|
+
const generation = stateHomeRefusal(bin, home);
|
|
4818
|
+
if (generation !== void 0) {
|
|
4819
|
+
io.stderr(generation);
|
|
4820
|
+
return 1;
|
|
4821
|
+
}
|
|
4724
4822
|
if (resolveUninstallArgv(entry, computed) === null) {
|
|
4725
4823
|
io.stdout(`${entry.id}: nothing to remove \u2014 npx fetches it at each run
|
|
4726
4824
|
`);
|
|
@@ -4729,13 +4827,18 @@ async function uninstallOnHost(entry, opts, io, run) {
|
|
|
4729
4827
|
let config;
|
|
4730
4828
|
let workspace;
|
|
4731
4829
|
try {
|
|
4732
|
-
config = readHostConfig(env);
|
|
4733
|
-
workspace = resolveHarnessRoot(void 0, env);
|
|
4830
|
+
config = readHostConfig(env, home);
|
|
4831
|
+
workspace = resolveHarnessRoot(void 0, env, home);
|
|
4734
4832
|
} catch (err) {
|
|
4735
4833
|
io.stderr(`${bin} harness: ${messageOf(err)}
|
|
4736
4834
|
`);
|
|
4737
4835
|
return 1;
|
|
4738
4836
|
}
|
|
4837
|
+
const nested = stateHomeRootRefusal(bin, workspace, home);
|
|
4838
|
+
if (nested !== void 0) {
|
|
4839
|
+
io.stderr(nested);
|
|
4840
|
+
return 1;
|
|
4841
|
+
}
|
|
4739
4842
|
const receipt = config.hostHarnesses?.[entry.id];
|
|
4740
4843
|
if (receipt === void 0 && opts.force !== true) {
|
|
4741
4844
|
io.stderr(
|
|
@@ -4752,7 +4855,7 @@ Removing it could delete a harness you installed yourself. Re-run with \`--force
|
|
|
4752
4855
|
const remaining = { ...config.hostHarnesses ?? {} };
|
|
4753
4856
|
delete remaining[entry.id];
|
|
4754
4857
|
try {
|
|
4755
|
-
writeHostConfig({ ...config, hostHarnesses: remaining }, env);
|
|
4858
|
+
writeHostConfig({ ...config, hostHarnesses: remaining }, env, home);
|
|
4756
4859
|
} catch (err) {
|
|
4757
4860
|
io.stderr(`${bin} harness: could not clear the install record: ${messageOf(err)}
|
|
4758
4861
|
`);
|
|
@@ -4837,7 +4940,7 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4837
4940
|
// src/commands/tool.ts
|
|
4838
4941
|
import { existsSync as existsSync19 } from "node:fs";
|
|
4839
4942
|
import { homedir as homedir3 } from "node:os";
|
|
4840
|
-
import { delimiter as delimiter3, join as join13, resolve as
|
|
4943
|
+
import { delimiter as delimiter3, join as join13, resolve as resolve12 } from "node:path";
|
|
4841
4944
|
|
|
4842
4945
|
// src/lib/tools/catalog.ts
|
|
4843
4946
|
var TOOLS_DOC = "docs/installation.md";
|
|
@@ -5123,7 +5226,7 @@ function rowOf(entry, installed, version, location) {
|
|
|
5123
5226
|
function probeableHost2(env, home) {
|
|
5124
5227
|
let root;
|
|
5125
5228
|
try {
|
|
5126
|
-
root = resolveHarnessRoot(void 0, env);
|
|
5229
|
+
root = resolveHarnessRoot(void 0, env, home);
|
|
5127
5230
|
} catch {
|
|
5128
5231
|
return void 0;
|
|
5129
5232
|
}
|
|
@@ -5271,6 +5374,7 @@ ${hostCapableToolIds().map((t) => ` ${bin} tool install ${t} --host`).join("\n"
|
|
|
5271
5374
|
async function installOnHost2(entry, opts, io, run, status) {
|
|
5272
5375
|
const bin = opts.bin;
|
|
5273
5376
|
const env = opts.env ?? process.env;
|
|
5377
|
+
const home = homeOf2(opts);
|
|
5274
5378
|
const flagged = opts.host === true || opts.path !== void 0;
|
|
5275
5379
|
if (!entry.hostCapable) {
|
|
5276
5380
|
io.stderr(sandboxRefusal2(bin, status));
|
|
@@ -5304,8 +5408,8 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5304
5408
|
let root;
|
|
5305
5409
|
let recorded;
|
|
5306
5410
|
try {
|
|
5307
|
-
recorded = readHostConfig(env).harnessRoot;
|
|
5308
|
-
root = resolveHarnessRoot(opts.path, env);
|
|
5411
|
+
recorded = readHostConfig(env, home).harnessRoot;
|
|
5412
|
+
root = resolveHarnessRoot(opts.path, env, home);
|
|
5309
5413
|
} catch (err) {
|
|
5310
5414
|
io.stderr(`${bin} tool: ${messageOf2(err)}
|
|
5311
5415
|
`);
|
|
@@ -5323,7 +5427,7 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5323
5427
|
}
|
|
5324
5428
|
if (!sticky) {
|
|
5325
5429
|
const chosen = (await ask2(`Harness root [${root}]:`)).trim();
|
|
5326
|
-
if (chosen !== "") root =
|
|
5430
|
+
if (chosen !== "") root = resolve12(chosen);
|
|
5327
5431
|
}
|
|
5328
5432
|
}
|
|
5329
5433
|
if (sticky) io.stdout(`using the recorded harness root ${root}
|
|
@@ -5343,7 +5447,7 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5343
5447
|
`);
|
|
5344
5448
|
return 1;
|
|
5345
5449
|
}
|
|
5346
|
-
const prefix = hostPrefix2(
|
|
5450
|
+
const prefix = hostPrefix2(home);
|
|
5347
5451
|
const installEnv = { NPM_USER_PREFIX: prefix };
|
|
5348
5452
|
const target = hostTargetFor2(root, prefix, run, env);
|
|
5349
5453
|
if (await probeInstalled2(target, entry, void 0, installEnv) === true) {
|
|
@@ -5372,14 +5476,15 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5372
5476
|
workspaceRoot: root
|
|
5373
5477
|
};
|
|
5374
5478
|
try {
|
|
5375
|
-
const config = readHostConfig(env);
|
|
5479
|
+
const config = readHostConfig(env, home);
|
|
5376
5480
|
writeHostConfig(
|
|
5377
5481
|
{
|
|
5378
5482
|
...config,
|
|
5379
5483
|
harnessRoot: root,
|
|
5380
5484
|
hostTools: { ...config.hostTools ?? {}, [entry.id]: receipt }
|
|
5381
5485
|
},
|
|
5382
|
-
env
|
|
5486
|
+
env,
|
|
5487
|
+
home
|
|
5383
5488
|
);
|
|
5384
5489
|
} catch (err) {
|
|
5385
5490
|
io.stderr(`${bin} tool: could not record the install: ${messageOf2(err)}
|
|
@@ -5480,12 +5585,13 @@ async function removeTool(entry, target, prefix, user, opts, io) {
|
|
|
5480
5585
|
async function uninstallOnHost2(entry, opts, io, run) {
|
|
5481
5586
|
const bin = opts.bin;
|
|
5482
5587
|
const env = opts.env ?? process.env;
|
|
5483
|
-
const
|
|
5588
|
+
const home = homeOf2(opts);
|
|
5589
|
+
const computed = hostPrefix2(home);
|
|
5484
5590
|
let config;
|
|
5485
5591
|
let workspace;
|
|
5486
5592
|
try {
|
|
5487
|
-
config = readHostConfig(env);
|
|
5488
|
-
workspace = resolveHarnessRoot(void 0, env);
|
|
5593
|
+
config = readHostConfig(env, home);
|
|
5594
|
+
workspace = resolveHarnessRoot(void 0, env, home);
|
|
5489
5595
|
} catch (err) {
|
|
5490
5596
|
io.stderr(`${bin} tool: ${messageOf2(err)}
|
|
5491
5597
|
`);
|
|
@@ -5507,7 +5613,7 @@ Removing it could delete a tool you installed yourself. Re-run with \`--force\`
|
|
|
5507
5613
|
const remaining = { ...config.hostTools ?? {} };
|
|
5508
5614
|
delete remaining[entry.id];
|
|
5509
5615
|
try {
|
|
5510
|
-
writeHostConfig({ ...config, hostTools: remaining }, env);
|
|
5616
|
+
writeHostConfig({ ...config, hostTools: remaining }, env, home);
|
|
5511
5617
|
} catch (err) {
|
|
5512
5618
|
io.stderr(`${bin} tool: could not clear the install record: ${messageOf2(err)}
|
|
5513
5619
|
`);
|
|
@@ -5602,8 +5708,8 @@ function fetchRemoteSource(opts) {
|
|
|
5602
5708
|
}
|
|
5603
5709
|
|
|
5604
5710
|
// src/cli.ts
|
|
5605
|
-
var VERSION = true ? "0.12.
|
|
5606
|
-
var DEFAULT_SOURCE_OH_DIR =
|
|
5711
|
+
var VERSION = true ? "0.12.2" : "0.0.0-dev";
|
|
5712
|
+
var DEFAULT_SOURCE_OH_DIR = resolve13(
|
|
5607
5713
|
dirname10(fileURLToPath(import.meta.url)),
|
|
5608
5714
|
"../.."
|
|
5609
5715
|
);
|
|
@@ -5840,10 +5946,11 @@ Usage:
|
|
|
5840
5946
|
|
|
5841
5947
|
\`install\` and \`uninstall\` act on the running sandbox. When no sandbox is
|
|
5842
5948
|
reachable they act on the host. A host install clones the AGRO workspace into the
|
|
5843
|
-
harness root, then installs the harness into \`~/.local\`.
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5949
|
+
harness root, then installs the harness into \`~/.local\`. The default root is the
|
|
5950
|
+
workspace registry entry \`~/.agro/workspaces/default\`; an interactive run asks for
|
|
5951
|
+
the workspace name. \`--workspace <name>\` chooses that entry and \`--path <dir>\` a
|
|
5952
|
+
directory outside the registry. A successful install records the root as
|
|
5953
|
+
\`harnessRoot\` in \`~/.agro/config.json\`. The install prefix is always \`~/.local\`.
|
|
5847
5954
|
|
|
5848
5955
|
On the host, \`uninstall\` removes only the harness \`install\` recorded, from the
|
|
5849
5956
|
prefix in that record. Without a record it refuses, and \`--force\` overrides. In
|
|
@@ -5852,7 +5959,8 @@ the sandbox it needs no record. See \`docs/harnesses/overview.md\`.
|
|
|
5852
5959
|
Flags:
|
|
5853
5960
|
--json Machine-readable output (list/status)
|
|
5854
5961
|
--host Install on the host when the sandbox is not running (install)
|
|
5855
|
-
--
|
|
5962
|
+
--workspace <name> Workspace registry entry to clone into; implies --host (install)
|
|
5963
|
+
--path <dir> Harness root outside the registry; implies --host (install)
|
|
5856
5964
|
--force Remove without a recorded host install (uninstall)
|
|
5857
5965
|
|
|
5858
5966
|
Harnesses:
|
|
@@ -5908,8 +6016,8 @@ only on these tools:
|
|
|
5908
6016
|
${hostCapableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
5909
6017
|
|
|
5910
6018
|
A host install clones the AGRO workspace into the harness root \u2014 \`--path <dir>\`,
|
|
5911
|
-
then \`harnessRoot\` in
|
|
5912
|
-
\`~/.local\`. It records the install as \`hostTools\` in that same file. On the host,
|
|
6019
|
+
then \`harnessRoot\` in \`~/.agro/config.json\`, then \`~/.agro/workspaces/default\` \u2014 and
|
|
6020
|
+
installs into \`~/.local\`. It records the install as \`hostTools\` in that same file. On the host,
|
|
5913
6021
|
\`uninstall\` removes only what \`install\` recorded, from the prefix in that record.
|
|
5914
6022
|
Without a record it refuses, and \`--force\` overrides.
|
|
5915
6023
|
|
|
@@ -6309,11 +6417,16 @@ function parseHarnessArgs(rest, bin = LEGACY_PRODUCT.bin) {
|
|
|
6309
6417
|
}
|
|
6310
6418
|
const positionals = [];
|
|
6311
6419
|
let expectPath = false;
|
|
6420
|
+
let expectWorkspace = false;
|
|
6312
6421
|
for (const token of rest) {
|
|
6313
6422
|
if (expectPath) {
|
|
6314
6423
|
expectPath = false;
|
|
6315
6424
|
args.path = token;
|
|
6316
6425
|
args.host = true;
|
|
6426
|
+
} else if (expectWorkspace) {
|
|
6427
|
+
expectWorkspace = false;
|
|
6428
|
+
args.workspace = token;
|
|
6429
|
+
args.host = true;
|
|
6317
6430
|
} else if (token === "--json") {
|
|
6318
6431
|
args.json = true;
|
|
6319
6432
|
} else if (token === "--host") {
|
|
@@ -6325,6 +6438,11 @@ function parseHarnessArgs(rest, bin = LEGACY_PRODUCT.bin) {
|
|
|
6325
6438
|
} else if (token.startsWith("--path=")) {
|
|
6326
6439
|
args.path = token.slice("--path=".length);
|
|
6327
6440
|
args.host = true;
|
|
6441
|
+
} else if (token === "--workspace") {
|
|
6442
|
+
expectWorkspace = true;
|
|
6443
|
+
} else if (token.startsWith("--workspace=")) {
|
|
6444
|
+
args.workspace = token.slice("--workspace=".length);
|
|
6445
|
+
args.host = true;
|
|
6328
6446
|
} else if (token.startsWith("-")) {
|
|
6329
6447
|
return { ok: false, error: `${bin} harness: unknown flag "${token}"` };
|
|
6330
6448
|
} else {
|
|
@@ -6334,6 +6452,15 @@ function parseHarnessArgs(rest, bin = LEGACY_PRODUCT.bin) {
|
|
|
6334
6452
|
if (expectPath || args.path === "") {
|
|
6335
6453
|
return { ok: false, error: `${bin} harness: --path requires a directory` };
|
|
6336
6454
|
}
|
|
6455
|
+
if (expectWorkspace || args.workspace === "") {
|
|
6456
|
+
return { ok: false, error: `${bin} harness: --workspace requires a name` };
|
|
6457
|
+
}
|
|
6458
|
+
if (args.path !== void 0 && args.workspace !== void 0) {
|
|
6459
|
+
return {
|
|
6460
|
+
ok: false,
|
|
6461
|
+
error: `${bin} harness: --workspace names a registry entry and --path names a directory \u2014 pass one, not both`
|
|
6462
|
+
};
|
|
6463
|
+
}
|
|
6337
6464
|
const [sub, name, ...extra] = positionals;
|
|
6338
6465
|
if (sub !== "list" && sub !== "install" && sub !== "status" && sub !== "uninstall") {
|
|
6339
6466
|
return {
|
|
@@ -6352,7 +6479,10 @@ function parseHarnessArgs(rest, bin = LEGACY_PRODUCT.bin) {
|
|
|
6352
6479
|
return { ok: false, error: `${bin} harness list: unexpected argument "${name}"` };
|
|
6353
6480
|
}
|
|
6354
6481
|
if (sub !== "install" && args.host) {
|
|
6355
|
-
return {
|
|
6482
|
+
return {
|
|
6483
|
+
ok: false,
|
|
6484
|
+
error: `${bin} harness ${sub}: --host, --workspace and --path apply to install only`
|
|
6485
|
+
};
|
|
6356
6486
|
}
|
|
6357
6487
|
if (sub !== "uninstall" && args.force) {
|
|
6358
6488
|
return { ok: false, error: `${bin} harness ${sub}: --force applies to uninstall only` };
|
|
@@ -6427,13 +6557,13 @@ function bundledPayloadExists(bundled, exists = existsSync20) {
|
|
|
6427
6557
|
function resolveUpdateSource(args, bundled, bin = LEGACY_PRODUCT.bin) {
|
|
6428
6558
|
const exists = bundled.exists ?? existsSync20;
|
|
6429
6559
|
if (args.fromDir !== void 0) {
|
|
6430
|
-
return { kind: "local", fromDir:
|
|
6560
|
+
return { kind: "local", fromDir: resolve13(args.fromDir) };
|
|
6431
6561
|
}
|
|
6432
6562
|
if (args.fromRemote) {
|
|
6433
6563
|
return { kind: "remote", ref: args.ref };
|
|
6434
6564
|
}
|
|
6435
6565
|
if (bundledPayloadExists(bundled, exists)) {
|
|
6436
|
-
return { kind: "local", fromDir:
|
|
6566
|
+
return { kind: "local", fromDir: resolve13(bundled.sourceOhDir, "..") };
|
|
6437
6567
|
}
|
|
6438
6568
|
return {
|
|
6439
6569
|
kind: "remote",
|
|
@@ -6725,7 +6855,12 @@ async function main(argv) {
|
|
|
6725
6855
|
}
|
|
6726
6856
|
return await runHarnessInstall(
|
|
6727
6857
|
a.name,
|
|
6728
|
-
{
|
|
6858
|
+
{
|
|
6859
|
+
bin,
|
|
6860
|
+
host: a.host,
|
|
6861
|
+
...a.path !== void 0 ? { path: a.path } : {},
|
|
6862
|
+
...a.workspace !== void 0 ? { workspace: a.workspace } : {}
|
|
6863
|
+
},
|
|
6729
6864
|
io
|
|
6730
6865
|
);
|
|
6731
6866
|
}
|