@mifune/agro 0.15.0-minimal.1 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agro.js +371 -79
- package/package.json +1 -1
package/dist/agro.js
CHANGED
|
@@ -7,8 +7,8 @@ var __export = (target, all) => {
|
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { dirname as dirname10, join as
|
|
11
|
-
import { existsSync as
|
|
10
|
+
import { dirname as dirname10, join as join18, resolve as resolve11 } from "node:path";
|
|
11
|
+
import { existsSync as existsSync22, mkdtempSync as mkdtempSync5, readFileSync as readFileSync12, rmSync as rmSync6 } from "node:fs";
|
|
12
12
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
13
13
|
|
|
14
14
|
// src/commands/update.ts
|
|
@@ -1479,6 +1479,11 @@ fi
|
|
|
1479
1479
|
# shellcheck source=paths.sh
|
|
1480
1480
|
. "$PATHS_SH"
|
|
1481
1481
|
|
|
1482
|
+
MIGRATOR="$SCRIPT_DIR/migrate-harness-yaml.sh"
|
|
1483
|
+
if [ -f "$REPO_DIR/harness.yaml" ] && [ -f "$MIGRATOR" ]; then
|
|
1484
|
+
sh "$MIGRATOR" "$REPO_DIR" >&2 || true
|
|
1485
|
+
fi
|
|
1486
|
+
|
|
1482
1487
|
ENV_FILE="$REPO_DIR/.env"
|
|
1483
1488
|
[ -f "$ENV_FILE" ] || ENV_FILE="$REPO_DIR/.devcontainer/.env"
|
|
1484
1489
|
|
|
@@ -2217,7 +2222,7 @@ function resolveExecutionTarget(opts) {
|
|
|
2217
2222
|
}
|
|
2218
2223
|
|
|
2219
2224
|
// src/lib/version.ts
|
|
2220
|
-
var AGRO_VERSION = true ? "0.15.
|
|
2225
|
+
var AGRO_VERSION = true ? "0.15.1" : "0.0.0-dev";
|
|
2221
2226
|
var OFFICIAL_IMAGE = "ghcr.io/mifunedev/agro";
|
|
2222
2227
|
var RELEASE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z][a-z0-9]*\.[0-9]+)?$/;
|
|
2223
2228
|
function parseReleaseVersion(value) {
|
|
@@ -3727,9 +3732,9 @@ async function runLangfuseSetup(opts, io) {
|
|
|
3727
3732
|
}
|
|
3728
3733
|
|
|
3729
3734
|
// src/commands/sandbox.ts
|
|
3730
|
-
import { existsSync as
|
|
3735
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync3, statSync as statSync4 } from "node:fs";
|
|
3731
3736
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
3732
|
-
import { join as
|
|
3737
|
+
import { join as join12, resolve as resolve7 } from "node:path";
|
|
3733
3738
|
|
|
3734
3739
|
// src/lib/runtimes/catalog.ts
|
|
3735
3740
|
var RUNTIME_CATALOG = Object.freeze([
|
|
@@ -3758,6 +3763,27 @@ function runtimeIds() {
|
|
|
3758
3763
|
return RUNTIME_CATALOG.map((r) => r.id);
|
|
3759
3764
|
}
|
|
3760
3765
|
|
|
3766
|
+
// src/lib/star-prompt.ts
|
|
3767
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
3768
|
+
import { join as join11 } from "node:path";
|
|
3769
|
+
var STAR_PROMPT_LINE = "\u2B50 If AGRO helps, star https://github.com/mifunedev/agro";
|
|
3770
|
+
var MARKER = "star-prompt-shown";
|
|
3771
|
+
function maybePrintStarPrompt(io, env = process.env, isTTY = process.stdout.isTTY === true, stateHome = resolveUserStateHome(env)) {
|
|
3772
|
+
if (env.AGRO_NO_STAR_PROMPT === "1") return;
|
|
3773
|
+
if (env.CI !== void 0 && env.CI !== "") return;
|
|
3774
|
+
if (!isTTY) return;
|
|
3775
|
+
const marker = join11(stateHome, MARKER);
|
|
3776
|
+
if (existsSync15(marker)) return;
|
|
3777
|
+
io.stdout(`${STAR_PROMPT_LINE}
|
|
3778
|
+
`);
|
|
3779
|
+
try {
|
|
3780
|
+
mkdirSync7(stateHome, { recursive: true });
|
|
3781
|
+
writeFileSync8(marker, "");
|
|
3782
|
+
} catch {
|
|
3783
|
+
return;
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3761
3787
|
// src/commands/sandbox.ts
|
|
3762
3788
|
function hostTimezone() {
|
|
3763
3789
|
const tz = process.env.TZ;
|
|
@@ -3774,27 +3800,27 @@ function gitIdentity(run, key) {
|
|
|
3774
3800
|
return (result.stdout ?? "").trim();
|
|
3775
3801
|
}
|
|
3776
3802
|
function isBuildCapable(repo) {
|
|
3777
|
-
return repo !== void 0 &&
|
|
3803
|
+
return repo !== void 0 && existsSync16(join12(repo, ".devcontainer", "Dockerfile"));
|
|
3778
3804
|
}
|
|
3779
3805
|
function prepareHomeMount(value) {
|
|
3780
3806
|
const path4 = resolve7(value);
|
|
3781
|
-
if (
|
|
3807
|
+
if (existsSync16(path4)) {
|
|
3782
3808
|
if (!statSync4(path4).isDirectory()) {
|
|
3783
3809
|
throw new Error(`--home-mount path exists and is not a directory: ${path4}`);
|
|
3784
3810
|
}
|
|
3785
3811
|
return path4;
|
|
3786
3812
|
}
|
|
3787
|
-
|
|
3813
|
+
mkdirSync8(path4, { recursive: true });
|
|
3788
3814
|
return path4;
|
|
3789
3815
|
}
|
|
3790
3816
|
function readSeedConfig(repo) {
|
|
3791
3817
|
if (repo === void 0) return void 0;
|
|
3792
3818
|
const file = agroConfigPath(repo);
|
|
3793
|
-
return
|
|
3819
|
+
return existsSync16(file) ? readAgroConfig(file) : void 0;
|
|
3794
3820
|
}
|
|
3795
3821
|
function readEntryConfig(name) {
|
|
3796
3822
|
const file = agroConfigPath(entryRoot(name));
|
|
3797
|
-
return
|
|
3823
|
+
return existsSync16(file) ? readAgroConfig(file) : void 0;
|
|
3798
3824
|
}
|
|
3799
3825
|
function assignSetting(target, path4, value) {
|
|
3800
3826
|
const segments = path4.split(".");
|
|
@@ -3891,7 +3917,7 @@ async function runSandboxInstall(opts, io) {
|
|
|
3891
3917
|
return 1;
|
|
3892
3918
|
}
|
|
3893
3919
|
const checkout = opts.checkout === void 0 ? void 0 : resolve7(opts.checkout);
|
|
3894
|
-
if (checkout !== void 0 && !
|
|
3920
|
+
if (checkout !== void 0 && !existsSync16(checkout)) {
|
|
3895
3921
|
io.stderr(`${opts.bin} sandbox install: --checkout directory does not exist: ${checkout}
|
|
3896
3922
|
`);
|
|
3897
3923
|
return 1;
|
|
@@ -3910,7 +3936,7 @@ async function runSandboxInstall(opts, io) {
|
|
|
3910
3936
|
if (buildRequested && !isBuildCapable(configCheckout(config))) {
|
|
3911
3937
|
const target = configCheckout(config) ?? resolve7(opts.checkout ?? ".");
|
|
3912
3938
|
io.stderr(
|
|
3913
|
-
`${opts.bin} sandbox install: image.mode is "build" but ${
|
|
3939
|
+
`${opts.bin} sandbox install: image.mode is "build" but ${join12(target, ".devcontainer", "Dockerfile")} does not exist \u2014 point --checkout <dir> at a harness checkout that has .devcontainer/Dockerfile, or set image.mode to "image" to run the prebuilt image
|
|
3914
3940
|
`
|
|
3915
3941
|
);
|
|
3916
3942
|
return 1;
|
|
@@ -3954,7 +3980,7 @@ async function runSandboxInstall(opts, io) {
|
|
|
3954
3980
|
...useNoBuild ? { noBuild: true } : {}
|
|
3955
3981
|
};
|
|
3956
3982
|
if (opts.printArgv === true) {
|
|
3957
|
-
const preview = mkdtempSync3(
|
|
3983
|
+
const preview = mkdtempSync3(join12(tmpdir3(), "oh-sandbox-preview-"));
|
|
3958
3984
|
try {
|
|
3959
3985
|
writeAgroConfig(preview, config);
|
|
3960
3986
|
materialize(preview, { ...configCheckout(config) !== void 0 ? { checkout: configCheckout(config) } : {} });
|
|
@@ -3964,12 +3990,15 @@ async function runSandboxInstall(opts, io) {
|
|
|
3964
3990
|
}
|
|
3965
3991
|
}
|
|
3966
3992
|
const root = entryRoot(config.name);
|
|
3967
|
-
|
|
3993
|
+
mkdirSync8(root, { recursive: true });
|
|
3968
3994
|
writeAgroConfig(root, config);
|
|
3969
3995
|
materialize(root, { ...configCheckout(config) !== void 0 ? { checkout: configCheckout(config) } : {} });
|
|
3970
3996
|
const code = await runSandbox({ ...sandboxOpts, cwd: root }, io);
|
|
3971
|
-
if (code === 0)
|
|
3997
|
+
if (code === 0) {
|
|
3998
|
+
io.stdout(`next: ${opts.bin} shell ${config.name}
|
|
3972
3999
|
`);
|
|
4000
|
+
maybePrintStarPrompt(io);
|
|
4001
|
+
}
|
|
3973
4002
|
return code;
|
|
3974
4003
|
}
|
|
3975
4004
|
async function entryStatus(root, name, run) {
|
|
@@ -4021,9 +4050,9 @@ async function runSandboxList(opts, io) {
|
|
|
4021
4050
|
}
|
|
4022
4051
|
|
|
4023
4052
|
// src/commands/harness.ts
|
|
4024
|
-
import { existsSync as
|
|
4053
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
4025
4054
|
import { homedir as homedir3 } from "node:os";
|
|
4026
|
-
import { delimiter as delimiter2, join as
|
|
4055
|
+
import { delimiter as delimiter2, join as join15 } from "node:path";
|
|
4027
4056
|
|
|
4028
4057
|
// src/lib/docs.ts
|
|
4029
4058
|
var SOURCE_DOCS_BASE = "https://github.com/mifunedev/agro/blob/main/";
|
|
@@ -4036,17 +4065,17 @@ function sourceDocsUrl(docsPath) {
|
|
|
4036
4065
|
}
|
|
4037
4066
|
|
|
4038
4067
|
// src/lib/host-config.ts
|
|
4039
|
-
import { existsSync as
|
|
4040
|
-
import { basename as basename5, dirname as dirname8, isAbsolute, join as
|
|
4068
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9, readFileSync as readFileSync11, renameSync as renameSync4, unlinkSync as unlinkSync5, writeFileSync as writeFileSync9 } from "node:fs";
|
|
4069
|
+
import { basename as basename5, dirname as dirname8, isAbsolute, join as join13, resolve as resolve8 } from "node:path";
|
|
4041
4070
|
var HOST_CONFIG_MODE = 420;
|
|
4042
4071
|
var HOST_CONFIG_FILE = "config.json";
|
|
4043
4072
|
var WORKSPACES_SUBDIR = "workspaces";
|
|
4044
|
-
var DEFAULT_WORKSPACE_NAME = "
|
|
4073
|
+
var DEFAULT_WORKSPACE_NAME = "harness";
|
|
4045
4074
|
function hostStateHome(env, home) {
|
|
4046
4075
|
return resolveUserStateHome(env, home);
|
|
4047
4076
|
}
|
|
4048
4077
|
function hostConfigPath(env, home) {
|
|
4049
|
-
return
|
|
4078
|
+
return join13(hostStateHome(env, home), HOST_CONFIG_FILE);
|
|
4050
4079
|
}
|
|
4051
4080
|
function assertWorkspaceName(name) {
|
|
4052
4081
|
if (!SANDBOX_NAME_PATTERN.test(name)) {
|
|
@@ -4056,14 +4085,14 @@ function assertWorkspaceName(name) {
|
|
|
4056
4085
|
}
|
|
4057
4086
|
}
|
|
4058
4087
|
function workspacesRoot(env, home) {
|
|
4059
|
-
return
|
|
4088
|
+
return join13(hostStateHome(env, home), WORKSPACES_SUBDIR);
|
|
4060
4089
|
}
|
|
4061
4090
|
function workspaceRoot(name, env, home) {
|
|
4062
4091
|
assertWorkspaceName(name);
|
|
4063
|
-
return
|
|
4092
|
+
return join13(workspacesRoot(env, home), name);
|
|
4064
4093
|
}
|
|
4065
4094
|
function defaultHarnessRoot(env, home) {
|
|
4066
|
-
return
|
|
4095
|
+
return join13(workspacesRoot(env, home), DEFAULT_WORKSPACE_NAME);
|
|
4067
4096
|
}
|
|
4068
4097
|
function validateHostConfig(value) {
|
|
4069
4098
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -4121,7 +4150,7 @@ function validateReceipts(value, field, noun) {
|
|
|
4121
4150
|
}
|
|
4122
4151
|
function readHostConfig(env, home) {
|
|
4123
4152
|
const path4 = hostConfigPath(env, home);
|
|
4124
|
-
if (!
|
|
4153
|
+
if (!existsSync17(path4)) return { version: 1 };
|
|
4125
4154
|
let raw;
|
|
4126
4155
|
try {
|
|
4127
4156
|
raw = readFileSync11(path4, "utf8");
|
|
@@ -4143,9 +4172,9 @@ function writeHostConfig(config, env, home) {
|
|
|
4143
4172
|
const body = `${JSON.stringify(validated, null, 2)}
|
|
4144
4173
|
`;
|
|
4145
4174
|
const tmp = `${path4}.tmp.${process.pid}`;
|
|
4146
|
-
|
|
4175
|
+
mkdirSync9(dirname8(path4), { recursive: true });
|
|
4147
4176
|
try {
|
|
4148
|
-
|
|
4177
|
+
writeFileSync9(tmp, body, { mode: HOST_CONFIG_MODE, encoding: "utf8" });
|
|
4149
4178
|
renameSync4(tmp, path4);
|
|
4150
4179
|
} catch (error) {
|
|
4151
4180
|
try {
|
|
@@ -4172,8 +4201,8 @@ function fieldError2(path4, requirement) {
|
|
|
4172
4201
|
}
|
|
4173
4202
|
|
|
4174
4203
|
// src/lib/host-workspace.ts
|
|
4175
|
-
import { existsSync as
|
|
4176
|
-
import { dirname as dirname9, join as
|
|
4204
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync4, readdirSync as readdirSync3, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync5 } from "node:fs";
|
|
4205
|
+
import { dirname as dirname9, join as join14, resolve as resolve9 } from "node:path";
|
|
4177
4206
|
var AGRO_REPO_URL = "https://github.com/mifunedev/agro.git";
|
|
4178
4207
|
var IGNORABLE_ENTRIES = [
|
|
4179
4208
|
REGISTRY_SUBDIR,
|
|
@@ -4185,7 +4214,7 @@ function isDirectory3(path4) {
|
|
|
4185
4214
|
return statSync5(path4, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
4186
4215
|
}
|
|
4187
4216
|
function stateHomes(home) {
|
|
4188
|
-
return STATE_HOME_DIRS.map((name) =>
|
|
4217
|
+
return STATE_HOME_DIRS.map((name) => join14(home, name));
|
|
4189
4218
|
}
|
|
4190
4219
|
function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
4191
4220
|
const target = resolve9(root);
|
|
@@ -4193,7 +4222,7 @@ function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
|
4193
4222
|
if (enclosing === void 0) return void 0;
|
|
4194
4223
|
return `${bin} ${verb}: the ${verb} root ${target} is the state home ${enclosing} itself.
|
|
4195
4224
|
A workspace there collides with AGRO state and blocks every command run from ${home}.
|
|
4196
|
-
Move it out \u2014 for example \`mv ${enclosing} ${
|
|
4225
|
+
Move it out \u2014 for example \`mv ${enclosing} ${join14(home, "agro")}\` \u2014 then re-run this command.
|
|
4197
4226
|
`;
|
|
4198
4227
|
}
|
|
4199
4228
|
function isIgnorableEntry(name) {
|
|
@@ -4205,32 +4234,33 @@ function assertCloned(result, root) {
|
|
|
4205
4234
|
throw new Error(`could not clone ${AGRO_REPO_URL} into ${root} (exit status ${String(status)})`);
|
|
4206
4235
|
}
|
|
4207
4236
|
}
|
|
4208
|
-
function cloneInto(target, run, root) {
|
|
4209
|
-
|
|
4237
|
+
function cloneInto(target, run, root, ref) {
|
|
4238
|
+
const branch = ref !== void 0 ? ["--branch", ref] : [];
|
|
4239
|
+
assertCloned(run("git", ["clone", ...branch, AGRO_REPO_URL, target], { stdio: "inherit" }), root);
|
|
4210
4240
|
}
|
|
4211
|
-
function cloneThroughStaging(root, run) {
|
|
4212
|
-
const staging = mkdtempSync4(
|
|
4241
|
+
function cloneThroughStaging(root, run, ref) {
|
|
4242
|
+
const staging = mkdtempSync4(join14(dirname9(root), "agro-clone-"));
|
|
4213
4243
|
try {
|
|
4214
|
-
const checkout =
|
|
4215
|
-
cloneInto(checkout, run, root);
|
|
4244
|
+
const checkout = join14(staging, "agro");
|
|
4245
|
+
cloneInto(checkout, run, root, ref);
|
|
4216
4246
|
for (const name of readdirSync3(checkout)) {
|
|
4217
|
-
const destination =
|
|
4218
|
-
if (
|
|
4247
|
+
const destination = join14(root, name);
|
|
4248
|
+
if (existsSync18(destination)) {
|
|
4219
4249
|
throw new Error(`could not move ${name} into ${root} \u2014 the path already exists`);
|
|
4220
4250
|
}
|
|
4221
|
-
renameSync5(
|
|
4251
|
+
renameSync5(join14(checkout, name), destination);
|
|
4222
4252
|
}
|
|
4223
4253
|
} finally {
|
|
4224
4254
|
rmSync4(staging, { recursive: true, force: true });
|
|
4225
4255
|
}
|
|
4226
4256
|
}
|
|
4227
|
-
function ensureHostWorkspace(path4, run = spawnRunner) {
|
|
4257
|
+
function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
4228
4258
|
const root = resolve9(path4);
|
|
4229
|
-
if (
|
|
4259
|
+
if (existsSync18(join14(root, ".git"))) return { root, action: "reused" };
|
|
4230
4260
|
const stats = statSync5(root, { throwIfNoEntry: false });
|
|
4231
4261
|
if (stats === void 0) {
|
|
4232
|
-
|
|
4233
|
-
cloneInto(root, run, root);
|
|
4262
|
+
mkdirSync10(dirname9(root), { recursive: true });
|
|
4263
|
+
cloneInto(root, run, root, ref);
|
|
4234
4264
|
return { root, action: "cloned" };
|
|
4235
4265
|
}
|
|
4236
4266
|
if (!stats.isDirectory()) {
|
|
@@ -4242,17 +4272,17 @@ function ensureHostWorkspace(path4, run = spawnRunner) {
|
|
|
4242
4272
|
`${root} holds files but no git checkout (${blocking.sort().join(", ")}) \u2014 choose an empty directory or an existing AGRO checkout`
|
|
4243
4273
|
);
|
|
4244
4274
|
}
|
|
4245
|
-
cloneThroughStaging(root, run);
|
|
4275
|
+
cloneThroughStaging(root, run, ref);
|
|
4246
4276
|
return { root, action: "cloned" };
|
|
4247
4277
|
}
|
|
4248
4278
|
function listHostWorkspaces(env, home) {
|
|
4249
4279
|
const root = workspacesRoot(env, home);
|
|
4250
4280
|
if (!isDirectory3(root)) return [];
|
|
4251
|
-
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => SANDBOX_NAME_PATTERN.test(name)).filter((name) =>
|
|
4281
|
+
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => SANDBOX_NAME_PATTERN.test(name)).filter((name) => existsSync18(join14(root, name, ".git"))).sort();
|
|
4252
4282
|
}
|
|
4253
4283
|
function resolveExistingWorkspace(bin, verb, path4, env, home) {
|
|
4254
4284
|
const root = resolve9(path4);
|
|
4255
|
-
if (
|
|
4285
|
+
if (existsSync18(join14(root, ".git"))) return { ok: true, root };
|
|
4256
4286
|
const existing = listHostWorkspaces(env, home);
|
|
4257
4287
|
const inventory = existing.length === 0 ? "No host workspace exists yet.\n" : `Workspaces that exist: ${existing.join(", ")}
|
|
4258
4288
|
`;
|
|
@@ -4282,7 +4312,7 @@ function targetFor(root, run, env) {
|
|
|
4282
4312
|
});
|
|
4283
4313
|
}
|
|
4284
4314
|
function hostPrefix(home) {
|
|
4285
|
-
return
|
|
4315
|
+
return join15(home, ".local");
|
|
4286
4316
|
}
|
|
4287
4317
|
function pathEntries(env) {
|
|
4288
4318
|
return (env.PATH ?? "").split(delimiter2).filter((part) => part !== "");
|
|
@@ -4335,7 +4365,7 @@ function probeableHost(env, home) {
|
|
|
4335
4365
|
return void 0;
|
|
4336
4366
|
}
|
|
4337
4367
|
const prefix = hostPrefix(home);
|
|
4338
|
-
if (!
|
|
4368
|
+
if (!existsSync19(join15(root, ".git")) && !existsSync19(prefix)) return void 0;
|
|
4339
4369
|
return { root, prefix };
|
|
4340
4370
|
}
|
|
4341
4371
|
async function collectStates(root, run, home, env, only) {
|
|
@@ -4431,6 +4461,19 @@ async function runHarnessStatus(name, opts, io) {
|
|
|
4431
4461
|
function hermesTargetRoot(target) {
|
|
4432
4462
|
return target.kind === "docker-compose" ? "/home/sandbox/harness" : target.workspace.targetRoot;
|
|
4433
4463
|
}
|
|
4464
|
+
async function reconcileHermes(target, io, bin, user, root = hermesTargetRoot(target)) {
|
|
4465
|
+
const result = await target.exec({
|
|
4466
|
+
argv: remoteControlDirScript(root, "scripts/link-providers.sh", ["--init", "--hermes-only"]),
|
|
4467
|
+
env: agroEnvPair("PROJECT_ROOT", root),
|
|
4468
|
+
...user ? { user } : {},
|
|
4469
|
+
stdio: "inherit"
|
|
4470
|
+
});
|
|
4471
|
+
if (result.exitCode !== 0) {
|
|
4472
|
+
io.stderr(`${bin} harness: Hermes integration failed (exit ${result.exitCode}); no installation success reported.
|
|
4473
|
+
`);
|
|
4474
|
+
}
|
|
4475
|
+
return result.exitCode;
|
|
4476
|
+
}
|
|
4434
4477
|
function unreachableReason(status) {
|
|
4435
4478
|
return runtimeIsAbsent(status) ? "No container runtime is on PATH." : `The sandbox is not running (${status}).`;
|
|
4436
4479
|
}
|
|
@@ -4513,7 +4556,7 @@ async function installOnHost(entry, opts, io, run, status) {
|
|
|
4513
4556
|
});
|
|
4514
4557
|
if (linked.exitCode !== 0) {
|
|
4515
4558
|
io.stderr(
|
|
4516
|
-
`${bin} harness: could not link provider
|
|
4559
|
+
`${bin} harness: could not link provider skills in ${root} (exit ${linked.exitCode}); nothing was installed.
|
|
4517
4560
|
Run: bash ${root}/.agro/scripts/link-providers.sh --init
|
|
4518
4561
|
`
|
|
4519
4562
|
);
|
|
@@ -4524,6 +4567,10 @@ Run: bash ${root}/.agro/scripts/link-providers.sh --init
|
|
|
4524
4567
|
...agroEnvPair("PROJECT_ROOT", root),
|
|
4525
4568
|
HERMES_HOME: `${root}/.hermes`
|
|
4526
4569
|
} : void 0;
|
|
4570
|
+
if (hermes) {
|
|
4571
|
+
const code = await reconcileHermes(target, io, bin, void 0, root);
|
|
4572
|
+
if (code !== 0) return code;
|
|
4573
|
+
}
|
|
4527
4574
|
if (await probeInstalled(target, entry, prefix, void 0, installEnv) === true) {
|
|
4528
4575
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
4529
4576
|
`);
|
|
@@ -4554,6 +4601,8 @@ Run: bash ${root}/.agro/scripts/link-providers.sh --init
|
|
|
4554
4601
|
`);
|
|
4555
4602
|
return 1;
|
|
4556
4603
|
}
|
|
4604
|
+
const code = await reconcileHermes(target, io, bin, void 0, root);
|
|
4605
|
+
if (code !== 0) return code;
|
|
4557
4606
|
}
|
|
4558
4607
|
const receipt = {
|
|
4559
4608
|
prefix,
|
|
@@ -4707,6 +4756,10 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4707
4756
|
...agroEnvPair("PROJECT_ROOT", hermesTargetRoot(target)),
|
|
4708
4757
|
HERMES_HOME: `${hermesTargetRoot(target)}/.hermes`
|
|
4709
4758
|
} : void 0;
|
|
4759
|
+
if (hermes) {
|
|
4760
|
+
const code = await reconcileHermes(target, io, opts.bin, "sandbox");
|
|
4761
|
+
if (code !== 0) return code;
|
|
4762
|
+
}
|
|
4710
4763
|
const already = await probeInstalled(target, entry, SANDBOX_HARNESS_PREFIX, "sandbox", installEnv);
|
|
4711
4764
|
if (already === true) {
|
|
4712
4765
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
@@ -4736,6 +4789,8 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4736
4789
|
`);
|
|
4737
4790
|
return 1;
|
|
4738
4791
|
}
|
|
4792
|
+
const code = await reconcileHermes(target, io, opts.bin, "sandbox");
|
|
4793
|
+
if (code !== 0) return code;
|
|
4739
4794
|
}
|
|
4740
4795
|
io.stdout(`${entry.id}: installed \u2014 see ${sourceDocsUrl(entry.docsPath)} for authentication
|
|
4741
4796
|
`);
|
|
@@ -4743,9 +4798,9 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4743
4798
|
}
|
|
4744
4799
|
|
|
4745
4800
|
// src/commands/workspace.ts
|
|
4746
|
-
import { existsSync as
|
|
4801
|
+
import { existsSync as existsSync20, rmSync as rmSync5 } from "node:fs";
|
|
4747
4802
|
import { homedir as homedir4 } from "node:os";
|
|
4748
|
-
import { join as
|
|
4803
|
+
import { join as join16, resolve as resolve10 } from "node:path";
|
|
4749
4804
|
function homeOf2(opts) {
|
|
4750
4805
|
return (opts.homedir ?? homedir4)();
|
|
4751
4806
|
}
|
|
@@ -4791,15 +4846,25 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
4791
4846
|
return 1;
|
|
4792
4847
|
}
|
|
4793
4848
|
let action;
|
|
4849
|
+
const preexisting = existsSync20(root);
|
|
4794
4850
|
try {
|
|
4795
|
-
if (!opts.json && !
|
|
4796
|
-
|
|
4851
|
+
if (!opts.json && !existsSync20(join16(root, ".git"))) {
|
|
4852
|
+
const at = opts.ref !== void 0 ? ` at ${opts.ref}` : "";
|
|
4853
|
+
io.stdout(`cloning ${AGRO_REPO_URL}${at} into ${root}\u2026
|
|
4797
4854
|
`);
|
|
4798
4855
|
}
|
|
4799
|
-
const created = ensureHostWorkspace(root, run);
|
|
4856
|
+
const created = ensureHostWorkspace(root, run, opts.ref);
|
|
4800
4857
|
root = created.root;
|
|
4801
4858
|
action = created.action;
|
|
4802
4859
|
} catch (err) {
|
|
4860
|
+
if (opts.ref !== void 0) {
|
|
4861
|
+
if (!preexisting) rmSync5(root, { recursive: true, force: true });
|
|
4862
|
+
io.stderr(
|
|
4863
|
+
`${bin} workspace: could not clone ref "${opts.ref}" \u2014 the ref does not exist in ${AGRO_REPO_URL} or the clone failed (${messageOf2(err)})
|
|
4864
|
+
`
|
|
4865
|
+
);
|
|
4866
|
+
return 1;
|
|
4867
|
+
}
|
|
4803
4868
|
io.stderr(`${bin} workspace: ${messageOf2(err)}
|
|
4804
4869
|
`);
|
|
4805
4870
|
return 1;
|
|
@@ -4828,7 +4893,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
4828
4893
|
const recorded = recordedRoot(env, home);
|
|
4829
4894
|
const registry = workspacesRoot(env, home);
|
|
4830
4895
|
rows = listHostWorkspaces(env, home).map((name) => {
|
|
4831
|
-
const root =
|
|
4896
|
+
const root = join16(registry, name);
|
|
4832
4897
|
return { name, root, default: recorded === root };
|
|
4833
4898
|
});
|
|
4834
4899
|
} catch (err) {
|
|
@@ -4853,9 +4918,9 @@ async function runWorkspaceList(opts, io) {
|
|
|
4853
4918
|
}
|
|
4854
4919
|
|
|
4855
4920
|
// src/commands/tool.ts
|
|
4856
|
-
import { existsSync as
|
|
4857
|
-
import { homedir as homedir5 } from "node:os";
|
|
4858
|
-
import { delimiter as delimiter3, join as
|
|
4921
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
4922
|
+
import { homedir as homedir5, userInfo as userInfo2 } from "node:os";
|
|
4923
|
+
import { delimiter as delimiter3, join as join17 } from "node:path";
|
|
4859
4924
|
|
|
4860
4925
|
// src/lib/tools/catalog.ts
|
|
4861
4926
|
var TOOLS_DOC = "docs/installation.md";
|
|
@@ -5076,6 +5141,178 @@ install -d -m 0700 "$HOME/.tailscale"`
|
|
|
5076
5141
|
`${HARNESS_PREFIX_TOKEN}/bin/tailscaled`
|
|
5077
5142
|
]),
|
|
5078
5143
|
docsPath: TOOLS_DOC
|
|
5144
|
+
}),
|
|
5145
|
+
Object.freeze({
|
|
5146
|
+
id: "code-server",
|
|
5147
|
+
title: "code-server",
|
|
5148
|
+
kind: "installable",
|
|
5149
|
+
binary: "code-server",
|
|
5150
|
+
verifyArgv: Object.freeze(["bash", "-lc", "command -v code-server >/dev/null"]),
|
|
5151
|
+
versionArgv: Object.freeze(["code-server", "--version"]),
|
|
5152
|
+
installArgv: Object.freeze([
|
|
5153
|
+
"bash",
|
|
5154
|
+
"-lc",
|
|
5155
|
+
[
|
|
5156
|
+
"set -e",
|
|
5157
|
+
"version=4.129.0",
|
|
5158
|
+
'arch="$(dpkg --print-architecture)"',
|
|
5159
|
+
'case "$arch" in',
|
|
5160
|
+
" amd64) sha=889b09ff3a167a293f53cb68a5a7f38dbab6bd2b50d7a5951c757e56ba51a2b0 ;;",
|
|
5161
|
+
" arm64) sha=62f7886018923a18cc16112ccfbcd51aee80f8e0c1bb7abc48773d1fd32a7617 ;;",
|
|
5162
|
+
' *) echo "no pinned code-server build for $arch" >&2; exit 1 ;;',
|
|
5163
|
+
"esac",
|
|
5164
|
+
'prefix="${NPM_USER_PREFIX:-$HOME/.local}"',
|
|
5165
|
+
'dest="$prefix/lib/code-server-$version"',
|
|
5166
|
+
'tmp="$(mktemp -d)"',
|
|
5167
|
+
`trap 'rm -rf "$tmp"' EXIT`,
|
|
5168
|
+
'curl -fsSL "https://github.com/coder/code-server/releases/download/v$version/code-server-$version-linux-$arch.tar.gz" -o "$tmp/code-server.tar.gz"',
|
|
5169
|
+
'echo "$sha $tmp/code-server.tar.gz" | sha256sum -c -',
|
|
5170
|
+
'install -d "$tmp/release" "$prefix/lib" "$prefix/bin"',
|
|
5171
|
+
'tar -xzf "$tmp/code-server.tar.gz" -C "$tmp/release" --strip-components=1',
|
|
5172
|
+
'rm -rf "$dest"',
|
|
5173
|
+
'mv "$tmp/release" "$dest"',
|
|
5174
|
+
'ln -sfn "$dest/bin/code-server" "$prefix/bin/code-server"',
|
|
5175
|
+
'if ! "$prefix/bin/code-server" --version | grep -q "^$version "; then',
|
|
5176
|
+
' echo "$prefix/bin/code-server does not report version $version" >&2',
|
|
5177
|
+
" exit 1",
|
|
5178
|
+
"fi"
|
|
5179
|
+
].join("\n")
|
|
5180
|
+
]),
|
|
5181
|
+
installUser: "sandbox",
|
|
5182
|
+
hostCapable: true,
|
|
5183
|
+
uninstallArgv: Object.freeze([
|
|
5184
|
+
"rm",
|
|
5185
|
+
"-rf",
|
|
5186
|
+
`${HARNESS_PREFIX_TOKEN}/bin/code-server`,
|
|
5187
|
+
`${HARNESS_PREFIX_TOKEN}/lib/code-server-4.129.0`
|
|
5188
|
+
]),
|
|
5189
|
+
docsPath: TOOLS_DOC
|
|
5190
|
+
}),
|
|
5191
|
+
Object.freeze({
|
|
5192
|
+
id: "docker-engine",
|
|
5193
|
+
title: "Docker Engine + Compose",
|
|
5194
|
+
kind: "installable",
|
|
5195
|
+
binary: "docker",
|
|
5196
|
+
verifyArgv: Object.freeze([
|
|
5197
|
+
"bash",
|
|
5198
|
+
"-lc",
|
|
5199
|
+
'command -v docker >/dev/null && docker compose version >/dev/null && systemctl is-enabled --quiet docker && id -nG "${SUDO_USER:-$(id -un)}" | tr " " "\\n" | grep -qx docker'
|
|
5200
|
+
]),
|
|
5201
|
+
versionArgv: Object.freeze(["docker", "--version"]),
|
|
5202
|
+
hostInstallArgv: Object.freeze([
|
|
5203
|
+
"bash",
|
|
5204
|
+
"-lc",
|
|
5205
|
+
[
|
|
5206
|
+
"set -e",
|
|
5207
|
+
". /etc/os-release",
|
|
5208
|
+
'if [ "${ID:-}" != ubuntu ]; then',
|
|
5209
|
+
' echo "docker-engine installs from Docker\'s Ubuntu repository; this host is ${ID:-unknown}" >&2',
|
|
5210
|
+
" exit 1",
|
|
5211
|
+
"fi",
|
|
5212
|
+
"fingerprint=9DC858229FC7DD38854AE2D88D81803C0EBFCD88",
|
|
5213
|
+
'user="${SUDO_USER:-$(id -un)}"',
|
|
5214
|
+
"export DEBIAN_FRONTEND=noninteractive",
|
|
5215
|
+
'tmp="$(mktemp -d)"',
|
|
5216
|
+
`trap 'rm -rf "$tmp"' EXIT`,
|
|
5217
|
+
"apt-get update",
|
|
5218
|
+
"apt-get install -y ca-certificates curl gnupg",
|
|
5219
|
+
"install -m 0755 -d /etc/apt/keyrings",
|
|
5220
|
+
'curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o "$tmp/docker.asc"',
|
|
5221
|
+
`actual="$(gpg --homedir "$tmp" --show-keys --with-colons "$tmp/docker.asc" | awk -F: '$1 == "fpr" { print $10; exit }')"`,
|
|
5222
|
+
'if [ "$actual" != "$fingerprint" ]; then',
|
|
5223
|
+
' echo "Docker\'s repository key has fingerprint ${actual:-none}, expected $fingerprint" >&2',
|
|
5224
|
+
" exit 1",
|
|
5225
|
+
"fi",
|
|
5226
|
+
'install -m 0644 "$tmp/docker.asc" /etc/apt/keyrings/docker.asc',
|
|
5227
|
+
'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu ${UBUNTU_CODENAME:-$VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list',
|
|
5228
|
+
"apt-get update",
|
|
5229
|
+
"apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin",
|
|
5230
|
+
'usermod -aG docker "$user"',
|
|
5231
|
+
"systemctl enable --now docker",
|
|
5232
|
+
'echo "$user is in the docker group. Log out and back in before you run docker without sudo."'
|
|
5233
|
+
].join("\n")
|
|
5234
|
+
]),
|
|
5235
|
+
hostInstallUser: "root",
|
|
5236
|
+
notInstallableReason: (bin) => `Docker Engine installs on the host only, with \`${bin} tool install docker-engine --host\`. To use Docker inside the sandbox, set \`access.dockerSocket\` to true in agro.json on the host and recreate the sandbox. That mounts the host Docker socket, which is effectively host root.`,
|
|
5237
|
+
hostCapable: true,
|
|
5238
|
+
uninstallArgv: null,
|
|
5239
|
+
docsPath: TOOLS_DOC
|
|
5240
|
+
}),
|
|
5241
|
+
Object.freeze({
|
|
5242
|
+
id: "desktop",
|
|
5243
|
+
title: "XFCE desktop over XRDP",
|
|
5244
|
+
kind: "installable",
|
|
5245
|
+
binary: "xrdp",
|
|
5246
|
+
verifyArgv: Object.freeze([
|
|
5247
|
+
"bash",
|
|
5248
|
+
"-lc",
|
|
5249
|
+
'command -v xrdp >/dev/null && systemctl is-enabled --quiet xrdp && systemctl is-enabled --quiet tailscaled && test -f /etc/systemd/system/xrdp.service.d/agro-tailscale-only.conf && grep -qx xfce4-session "$HOME/.xsession"'
|
|
5250
|
+
]),
|
|
5251
|
+
hostInstallArgv: Object.freeze([
|
|
5252
|
+
"bash",
|
|
5253
|
+
"-lc",
|
|
5254
|
+
[
|
|
5255
|
+
"set -e",
|
|
5256
|
+
". /etc/os-release",
|
|
5257
|
+
'if [ "${ID:-}" != ubuntu ]; then',
|
|
5258
|
+
' echo "desktop installs Ubuntu packages; this host is ${ID:-unknown}" >&2',
|
|
5259
|
+
" exit 1",
|
|
5260
|
+
"fi",
|
|
5261
|
+
'user="${SUDO_USER:-$(id -un)}"',
|
|
5262
|
+
'home="$(getent passwd "$user" | cut -d: -f6)"',
|
|
5263
|
+
'codename="${UBUNTU_CODENAME:-$VERSION_CODENAME}"',
|
|
5264
|
+
"export DEBIAN_FRONTEND=noninteractive",
|
|
5265
|
+
'tmp="$(mktemp -d)"',
|
|
5266
|
+
`trap 'rm -rf "$tmp"' EXIT`,
|
|
5267
|
+
"apt-get update",
|
|
5268
|
+
"apt-get install -y ca-certificates curl gnupg nftables",
|
|
5269
|
+
"if ! systemctl cat tailscaled.service >/dev/null 2>&1; then",
|
|
5270
|
+
" fingerprint=2596A99EAAB33821893C0A79458CA832957F5868",
|
|
5271
|
+
' curl -fsSL "https://pkgs.tailscale.com/stable/ubuntu/$codename.noarmor.gpg" -o "$tmp/tailscale.gpg"',
|
|
5272
|
+
` actual="$(gpg --homedir "$tmp" --show-keys --with-colons "$tmp/tailscale.gpg" | awk -F: '$1 == "fpr" { print $10; exit }')"`,
|
|
5273
|
+
' if [ "$actual" != "$fingerprint" ]; then',
|
|
5274
|
+
' echo "Tailscale\'s repository key has fingerprint ${actual:-none}, expected $fingerprint" >&2',
|
|
5275
|
+
" exit 1",
|
|
5276
|
+
" fi",
|
|
5277
|
+
' install -m 0644 "$tmp/tailscale.gpg" /usr/share/keyrings/tailscale-archive-keyring.gpg',
|
|
5278
|
+
' echo "deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/ubuntu $codename main" > /etc/apt/sources.list.d/tailscale.list',
|
|
5279
|
+
" apt-get update",
|
|
5280
|
+
" apt-get install -y tailscale",
|
|
5281
|
+
"fi",
|
|
5282
|
+
"systemctl enable --now tailscaled",
|
|
5283
|
+
"install -d -m 0755 /etc/xrdp /etc/systemd/system/xrdp.service.d",
|
|
5284
|
+
"cat > /etc/xrdp/agro-tailscale-only.nft <<'EOF'",
|
|
5285
|
+
"table inet agro_xrdp",
|
|
5286
|
+
"delete table inet agro_xrdp",
|
|
5287
|
+
"table inet agro_xrdp {",
|
|
5288
|
+
" chain input {",
|
|
5289
|
+
" type filter hook input priority filter; policy accept;",
|
|
5290
|
+
' iifname { "lo", "tailscale0" } accept',
|
|
5291
|
+
" tcp dport 3389 reject with tcp reset",
|
|
5292
|
+
" }",
|
|
5293
|
+
"}",
|
|
5294
|
+
"EOF",
|
|
5295
|
+
"nft -f /etc/xrdp/agro-tailscale-only.nft",
|
|
5296
|
+
`printf '[Service]\\nExecStartPre=%s -f /etc/xrdp/agro-tailscale-only.nft\\n' "$(command -v nft)" > /etc/systemd/system/xrdp.service.d/agro-tailscale-only.conf`,
|
|
5297
|
+
"systemctl daemon-reload",
|
|
5298
|
+
"apt-get install -y xfce4 xfce4-goodies xrdp xorgxrdp",
|
|
5299
|
+
`printf '%s\\n' xfce4-session > "$home/.xsession"`,
|
|
5300
|
+
'chown "$user:$(id -gn "$user")" "$home/.xsession"',
|
|
5301
|
+
"adduser xrdp ssl-cert",
|
|
5302
|
+
"systemctl daemon-reload",
|
|
5303
|
+
"systemctl enable xrdp",
|
|
5304
|
+
"systemctl restart xrdp",
|
|
5305
|
+
"systemctl is-active --quiet xrdp",
|
|
5306
|
+
'echo "The desktop is installed. Two steps remain:"',
|
|
5307
|
+
'echo " sudo tailscale up"',
|
|
5308
|
+
'echo " sudo passwd $user"'
|
|
5309
|
+
].join("\n")
|
|
5310
|
+
]),
|
|
5311
|
+
hostInstallUser: "root",
|
|
5312
|
+
notInstallableReason: (bin) => `The desktop installs on the host only, with \`${bin} tool install desktop --host\`. It installs Tailscale and serves XFCE over XRDP on TCP 3389 through Tailscale only.`,
|
|
5313
|
+
hostCapable: true,
|
|
5314
|
+
uninstallArgv: null,
|
|
5315
|
+
docsPath: TOOLS_DOC
|
|
5079
5316
|
})
|
|
5080
5317
|
]);
|
|
5081
5318
|
function findTool(id) {
|
|
@@ -5113,7 +5350,7 @@ function targetFor2(root, run, env) {
|
|
|
5113
5350
|
});
|
|
5114
5351
|
}
|
|
5115
5352
|
function hostPrefix2(home) {
|
|
5116
|
-
return
|
|
5353
|
+
return join17(home, ".local");
|
|
5117
5354
|
}
|
|
5118
5355
|
function pathEntries2(env) {
|
|
5119
5356
|
return (env.PATH ?? "").split(delimiter3).filter((part) => part !== "");
|
|
@@ -5138,6 +5375,10 @@ function homeOf3(opts) {
|
|
|
5138
5375
|
function platformOf(opts) {
|
|
5139
5376
|
return opts.platform ?? process.platform;
|
|
5140
5377
|
}
|
|
5378
|
+
function isRootUser(opts) {
|
|
5379
|
+
const identity = opts.identity ?? (() => ({ name: userInfo2().username, uid: userInfo2().uid }));
|
|
5380
|
+
return identity().uid === 0;
|
|
5381
|
+
}
|
|
5141
5382
|
function isInteractive2(opts) {
|
|
5142
5383
|
return opts.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
5143
5384
|
}
|
|
@@ -5176,6 +5417,7 @@ function rowOf(entry, installed, version, location) {
|
|
|
5176
5417
|
version,
|
|
5177
5418
|
installable: entry.installArgv !== void 0,
|
|
5178
5419
|
hostCapable: entry.hostCapable,
|
|
5420
|
+
hostRoot: entry.hostInstallUser === "root",
|
|
5179
5421
|
location,
|
|
5180
5422
|
docs: sourceDocsUrl(entry.docsPath)
|
|
5181
5423
|
};
|
|
@@ -5188,7 +5430,7 @@ function probeableHost2(env, home) {
|
|
|
5188
5430
|
return void 0;
|
|
5189
5431
|
}
|
|
5190
5432
|
const prefix = hostPrefix2(home);
|
|
5191
|
-
if (!
|
|
5433
|
+
if (!existsSync21(join17(root, ".git")) && !existsSync21(prefix)) return void 0;
|
|
5192
5434
|
return { root, prefix };
|
|
5193
5435
|
}
|
|
5194
5436
|
async function collectRows(root, run, home, env, only) {
|
|
@@ -5223,13 +5465,20 @@ function cell2(value, absent) {
|
|
|
5223
5465
|
function renderTable3(collected, io, bin) {
|
|
5224
5466
|
const rows = collected.rows;
|
|
5225
5467
|
const header2 = ["TOOL", "KIND", "INSTALLED"];
|
|
5226
|
-
const body = rows.map((r) => [
|
|
5468
|
+
const body = rows.map((r) => [
|
|
5469
|
+
r.id,
|
|
5470
|
+
r.hostRoot ? `${r.kind} (root)` : r.kind,
|
|
5471
|
+
cell2(r.installed, "?")
|
|
5472
|
+
]);
|
|
5227
5473
|
const widths = header2.map(
|
|
5228
5474
|
(h, i) => Math.max(h.length, ...body.map((b) => b[i].length))
|
|
5229
5475
|
);
|
|
5230
5476
|
const line = (cols) => cols.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd() + "\n";
|
|
5231
5477
|
io.stdout(line(header2));
|
|
5232
5478
|
for (const row of body) io.stdout(line(row));
|
|
5479
|
+
if (rows.some((r) => r.hostRoot)) {
|
|
5480
|
+
io.stdout("\n(root) installs on the host as root through `sudo -n`.\n");
|
|
5481
|
+
}
|
|
5233
5482
|
if (rows.some((r) => r.location === "host")) {
|
|
5234
5483
|
io.stdout(
|
|
5235
5484
|
`
|
|
@@ -5409,11 +5658,21 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5409
5658
|
}
|
|
5410
5659
|
return 0;
|
|
5411
5660
|
}
|
|
5661
|
+
const sudo = entry.hostInstallUser === "root" && !isRootUser(opts);
|
|
5662
|
+
if (sudo && (await tryExec(target, ["sudo", "-n", "true"], void 0))?.exitCode !== 0) {
|
|
5663
|
+
io.stderr(
|
|
5664
|
+
`${bin} tool: ${entry.id} installs as root on the host and needs passwordless \`sudo\`.
|
|
5665
|
+
\`sudo -n true\` failed. Configure passwordless sudo for this user, or run as root.
|
|
5666
|
+
`
|
|
5667
|
+
);
|
|
5668
|
+
return 1;
|
|
5669
|
+
}
|
|
5412
5670
|
if (!await confirmDownload(entry, opts, io, true)) return 1;
|
|
5413
5671
|
io.stdout(`installing ${entry.title} on the host\u2026
|
|
5414
5672
|
`);
|
|
5673
|
+
const argv = resolveToolInstallArgv(entry, true);
|
|
5415
5674
|
const r = await target.exec({
|
|
5416
|
-
argv:
|
|
5675
|
+
argv: sudo ? ["sudo", "-n", "--", ...argv] : argv,
|
|
5417
5676
|
stdio: "inherit",
|
|
5418
5677
|
env: installEnv
|
|
5419
5678
|
});
|
|
@@ -5445,6 +5704,11 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5445
5704
|
`);
|
|
5446
5705
|
return 1;
|
|
5447
5706
|
}
|
|
5707
|
+
if (entry.hostInstallUser === "root") {
|
|
5708
|
+
io.stdout(`${entry.id} installed on the host \u2014 see ${sourceDocsUrl(entry.docsPath)}
|
|
5709
|
+
`);
|
|
5710
|
+
return 0;
|
|
5711
|
+
}
|
|
5448
5712
|
io.stdout(`${entry.id}: installed at ${prefix} \u2014 see ${sourceDocsUrl(entry.docsPath)}
|
|
5449
5713
|
`);
|
|
5450
5714
|
if (!onPath2(prefix, env)) {
|
|
@@ -5460,7 +5724,8 @@ async function runToolInstall(name, opts, io) {
|
|
|
5460
5724
|
const root = resolveProjectRoot(opts.cwd);
|
|
5461
5725
|
const entry = findTool(name);
|
|
5462
5726
|
if (!entry) return unknownTool(name, io, opts.bin);
|
|
5463
|
-
|
|
5727
|
+
const hostOnly = entry.installArgv === void 0 && entry.hostInstallArgv !== void 0;
|
|
5728
|
+
const refuseContainer = () => {
|
|
5464
5729
|
io.stderr(`${opts.bin} tool: ${entry.id} cannot be installed by this command.
|
|
5465
5730
|
|
|
5466
5731
|
`);
|
|
@@ -5471,12 +5736,16 @@ async function runToolInstall(name, opts, io) {
|
|
|
5471
5736
|
${installableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
5472
5737
|
`);
|
|
5473
5738
|
return 1;
|
|
5739
|
+
};
|
|
5740
|
+
if (entry.installArgv === void 0 && (!hostOnly || runningInsideSandbox(opts.env ?? process.env))) {
|
|
5741
|
+
return refuseContainer();
|
|
5474
5742
|
}
|
|
5475
5743
|
const target = targetFor2(root, run, opts.env);
|
|
5476
5744
|
const status = await resolveTargetStatus(target);
|
|
5477
|
-
if (!isReachable2(status)) {
|
|
5745
|
+
if (!isReachable2(status) || hostOnly && opts.host === true) {
|
|
5478
5746
|
return await installOnHost2(entry, opts, io, run, status);
|
|
5479
5747
|
}
|
|
5748
|
+
if (entry.installArgv === void 0) return refuseContainer();
|
|
5480
5749
|
const already = await probeInstalled2(target, entry, "sandbox");
|
|
5481
5750
|
if (already === true) {
|
|
5482
5751
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
@@ -5926,8 +6195,8 @@ Usage:
|
|
|
5926
6195
|
|
|
5927
6196
|
\`install\` and \`uninstall\` act on the running sandbox. When no sandbox is
|
|
5928
6197
|
reachable they act on the host. A host install needs an existing AGRO workspace
|
|
5929
|
-
and creates none \u2014 run \`${bin} workspace create
|
|
5930
|
-
the workspace registry entry \`~/.agro/workspaces/
|
|
6198
|
+
and creates none \u2014 run \`${bin} workspace create\` first. The fallback root is
|
|
6199
|
+
the workspace registry entry \`~/.agro/workspaces/harness\`. \`--workspace <name>\`
|
|
5931
6200
|
chooses another entry and \`--path <dir>\` a directory outside the registry. A
|
|
5932
6201
|
successful install records the root as \`harnessRoot\` in \`~/.agro/config.json\`. The
|
|
5933
6202
|
install prefix is always \`~/.local\`.
|
|
@@ -5951,13 +6220,14 @@ function printWorkspaceHelp(bin = AGRO_PRODUCT.bin) {
|
|
|
5951
6220
|
process.stdout.write(`${bin} workspace \u2014 Create and list host AGRO workspaces
|
|
5952
6221
|
|
|
5953
6222
|
Usage:
|
|
5954
|
-
${bin} workspace create [<name>]
|
|
6223
|
+
${bin} workspace create [<name>] [--ref <ref>]
|
|
6224
|
+
Clone the AGRO repository into a registry entry
|
|
5955
6225
|
${bin} workspace list List every host workspace
|
|
5956
6226
|
|
|
5957
6227
|
A host workspace is an AGRO checkout under \`~/.agro/workspaces/<name>\`. \`create\`
|
|
5958
6228
|
clones ${AGRO_REPO_URL} into that entry, and reuses an existing checkout. The
|
|
5959
|
-
|
|
5960
|
-
registry instead.
|
|
6229
|
+
implicit name is \`harness\`. Pass \`default\` to create that named workspace.
|
|
6230
|
+
\`--path <dir>\` creates the workspace outside the registry instead.
|
|
5961
6231
|
|
|
5962
6232
|
\`create\` records no default. A host install selects its root with
|
|
5963
6233
|
\`${bin} harness install <harness> --workspace <name>\`, and that install records the
|
|
@@ -5966,6 +6236,9 @@ root as \`harnessRoot\`. \`list\` marks the recorded root.
|
|
|
5966
6236
|
Flags:
|
|
5967
6237
|
--json Machine-readable output
|
|
5968
6238
|
--path <dir> Create the workspace at a directory outside the registry
|
|
6239
|
+
--ref <ref> Clone this branch or tag instead of the default branch (create
|
|
6240
|
+
only). A missing ref exits 1 and leaves no target directory.
|
|
6241
|
+
An existing checkout is reused as is.
|
|
5969
6242
|
`);
|
|
5970
6243
|
}
|
|
5971
6244
|
function printGatewayHelp(bin = AGRO_PRODUCT.bin) {
|
|
@@ -6017,8 +6290,8 @@ only on these tools:
|
|
|
6017
6290
|
${hostCapableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
6018
6291
|
|
|
6019
6292
|
A host install needs an existing AGRO workspace \u2014 \`--path <dir>\`, then
|
|
6020
|
-
\`harnessRoot\` in \`~/.agro/config.json\`, then \`~/.agro/workspaces/
|
|
6021
|
-
no workspace: run \`${bin} workspace create
|
|
6293
|
+
\`harnessRoot\` in \`~/.agro/config.json\`, then \`~/.agro/workspaces/harness\`. It creates
|
|
6294
|
+
no workspace: run \`${bin} workspace create\` first. It installs into
|
|
6022
6295
|
\`~/.local\` and records the install as \`hostTools\` in that same file. On the host,
|
|
6023
6296
|
\`uninstall\` removes only what \`install\` recorded, from the prefix in that record.
|
|
6024
6297
|
Without a record it refuses, and \`--force\` overrides.
|
|
@@ -6469,16 +6742,24 @@ function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6469
6742
|
}
|
|
6470
6743
|
const positionals = [];
|
|
6471
6744
|
let expectPath = false;
|
|
6745
|
+
let expectRef = false;
|
|
6472
6746
|
for (const token of rest) {
|
|
6473
6747
|
if (expectPath) {
|
|
6474
6748
|
expectPath = false;
|
|
6475
6749
|
args.path = token;
|
|
6750
|
+
} else if (expectRef) {
|
|
6751
|
+
expectRef = false;
|
|
6752
|
+
args.ref = token;
|
|
6476
6753
|
} else if (token === "--json") {
|
|
6477
6754
|
args.json = true;
|
|
6478
6755
|
} else if (token === "--path") {
|
|
6479
6756
|
expectPath = true;
|
|
6480
6757
|
} else if (token.startsWith("--path=")) {
|
|
6481
6758
|
args.path = token.slice("--path=".length);
|
|
6759
|
+
} else if (token === "--ref") {
|
|
6760
|
+
expectRef = true;
|
|
6761
|
+
} else if (token.startsWith("--ref=")) {
|
|
6762
|
+
args.ref = token.slice("--ref=".length);
|
|
6482
6763
|
} else if (token.startsWith("-")) {
|
|
6483
6764
|
return { ok: false, error: `${bin} workspace: unknown flag "${token}"` };
|
|
6484
6765
|
} else {
|
|
@@ -6488,6 +6769,9 @@ function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6488
6769
|
if (expectPath || args.path === "") {
|
|
6489
6770
|
return { ok: false, error: `${bin} workspace: --path requires a directory` };
|
|
6490
6771
|
}
|
|
6772
|
+
if (expectRef || args.ref === "") {
|
|
6773
|
+
return { ok: false, error: `${bin} workspace: --ref requires a branch or tag` };
|
|
6774
|
+
}
|
|
6491
6775
|
const [sub, name, ...extra] = positionals;
|
|
6492
6776
|
if (sub !== "create" && sub !== "list") {
|
|
6493
6777
|
return {
|
|
@@ -6505,6 +6789,9 @@ function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6505
6789
|
if (sub === "list" && args.path !== void 0) {
|
|
6506
6790
|
return { ok: false, error: `${bin} workspace list: --path applies to create only` };
|
|
6507
6791
|
}
|
|
6792
|
+
if (sub === "list" && args.ref !== void 0) {
|
|
6793
|
+
return { ok: false, error: `${bin} workspace list: --ref applies to create only` };
|
|
6794
|
+
}
|
|
6508
6795
|
if (args.path !== void 0 && name !== void 0) {
|
|
6509
6796
|
return {
|
|
6510
6797
|
ok: false,
|
|
@@ -6661,11 +6948,11 @@ function parseGatewayArgs(rest) {
|
|
|
6661
6948
|
}
|
|
6662
6949
|
return { ok: true, args: { help: false, passthrough: [...rest] } };
|
|
6663
6950
|
}
|
|
6664
|
-
function bundledPayloadExists(bundled, exists =
|
|
6665
|
-
return exists(
|
|
6951
|
+
function bundledPayloadExists(bundled, exists = existsSync22) {
|
|
6952
|
+
return exists(join18(bundled.sourceControlDir, "manifest.json"));
|
|
6666
6953
|
}
|
|
6667
6954
|
function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
6668
|
-
const exists = bundled.exists ??
|
|
6955
|
+
const exists = bundled.exists ?? existsSync22;
|
|
6669
6956
|
if (args.fromDir !== void 0) {
|
|
6670
6957
|
return { kind: "local", fromDir: resolve11(args.fromDir) };
|
|
6671
6958
|
}
|
|
@@ -6685,7 +6972,7 @@ function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
|
6685
6972
|
function readPayloadVersion(checkoutDir) {
|
|
6686
6973
|
try {
|
|
6687
6974
|
const parsed = JSON.parse(
|
|
6688
|
-
readFileSync12(
|
|
6975
|
+
readFileSync12(join18(resolveControlDir(checkoutDir), "cli", "package.json"), "utf8")
|
|
6689
6976
|
);
|
|
6690
6977
|
if (parsed && typeof parsed.version === "string") return parsed.version;
|
|
6691
6978
|
} catch {
|
|
@@ -6694,8 +6981,8 @@ function readPayloadVersion(checkoutDir) {
|
|
|
6694
6981
|
}
|
|
6695
6982
|
async function runWithRemoteSource(hooks, fn) {
|
|
6696
6983
|
const fetch2 = hooks.fetch ?? fetchRemoteSource;
|
|
6697
|
-
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(
|
|
6698
|
-
const rm = hooks.rm ?? ((dir) =>
|
|
6984
|
+
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(join18(tmpdir4(), "oh-remote-")));
|
|
6985
|
+
const rm = hooks.rm ?? ((dir) => rmSync6(dir, { recursive: true, force: true }));
|
|
6699
6986
|
const out = hooks.stdout ?? ((s) => process.stdout.write(s));
|
|
6700
6987
|
const checkoutDir = mkdtemp();
|
|
6701
6988
|
try {
|
|
@@ -6957,7 +7244,12 @@ async function main(argv) {
|
|
|
6957
7244
|
}
|
|
6958
7245
|
return await runWorkspaceCreate(
|
|
6959
7246
|
a.name,
|
|
6960
|
-
{
|
|
7247
|
+
{
|
|
7248
|
+
bin,
|
|
7249
|
+
json: a.json,
|
|
7250
|
+
...a.path !== void 0 ? { path: a.path } : {},
|
|
7251
|
+
...a.ref !== void 0 ? { ref: a.ref } : {}
|
|
7252
|
+
},
|
|
6961
7253
|
io
|
|
6962
7254
|
);
|
|
6963
7255
|
}
|
package/package.json
CHANGED