@mifune/agro 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agro.js +488 -88
- 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
|
|
11
|
-
import { existsSync as existsSync23, mkdtempSync as mkdtempSync5, readFileSync as
|
|
10
|
+
import { dirname as dirname11, join as join22, resolve as resolve13 } from "node:path";
|
|
11
|
+
import { existsSync as existsSync23, mkdtempSync as mkdtempSync5, readFileSync as readFileSync15, rmSync as rmSync7 } from "node:fs";
|
|
12
12
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
13
13
|
|
|
14
14
|
// src/commands/update.ts
|
|
@@ -2040,9 +2040,7 @@ var SECRET_KEYS = [
|
|
|
2040
2040
|
"PI_SLACK_BOT_TOKEN",
|
|
2041
2041
|
"LANGFUSE_PUBLIC_KEY",
|
|
2042
2042
|
"LANGFUSE_SECRET_KEY",
|
|
2043
|
-
"TYPESAFE_API_KEY"
|
|
2044
|
-
"AGRO_CONSOLE_URL",
|
|
2045
|
-
"AGRO_CONSOLE_TOKEN"
|
|
2043
|
+
"TYPESAFE_API_KEY"
|
|
2046
2044
|
];
|
|
2047
2045
|
var COMPOSE_SECRET_KEYS = [
|
|
2048
2046
|
"GH_TOKEN",
|
|
@@ -2113,7 +2111,7 @@ import { basename as basename6, join as join6, sep as sep3 } from "node:path";
|
|
|
2113
2111
|
import { basename as basename4, resolve as resolve7 } from "node:path";
|
|
2114
2112
|
|
|
2115
2113
|
// src/lib/version.ts
|
|
2116
|
-
var AGRO_VERSION = true ? "0.
|
|
2114
|
+
var AGRO_VERSION = true ? "0.24.0" : "0.0.0-dev";
|
|
2117
2115
|
var OFFICIAL_IMAGE = "ghcr.io/mifunedev/agro";
|
|
2118
2116
|
var RELEASE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z][a-z0-9]*\.[0-9]+)?$/;
|
|
2119
2117
|
function parseReleaseVersion(value) {
|
|
@@ -5123,6 +5121,9 @@ function runEgress(verb, opts, io) {
|
|
|
5123
5121
|
});
|
|
5124
5122
|
}
|
|
5125
5123
|
|
|
5124
|
+
// src/commands/auth.ts
|
|
5125
|
+
import { hostname as hostname2 } from "node:os";
|
|
5126
|
+
|
|
5126
5127
|
// src/command-table.ts
|
|
5127
5128
|
var CONFIG_VERBS = ["show", "set", "repo"];
|
|
5128
5129
|
var CONFIG_INTEGRATIONS = ["langfuse"];
|
|
@@ -5134,6 +5135,7 @@ var HARNESS_SUBCOMMANDS = ["list", "install", "status", "uninstall"];
|
|
|
5134
5135
|
var TOOL_SUBCOMMANDS = ["list", "install", "status", "uninstall"];
|
|
5135
5136
|
var EGRESS_VERBS = ["log", "reload"];
|
|
5136
5137
|
var NODE_VERBS = ["create", "list", "delete"];
|
|
5138
|
+
var AUTH_VERBS = ["login", "status", "logout", "token"];
|
|
5137
5139
|
function isOneOf(values, value) {
|
|
5138
5140
|
return value !== void 0 && values.includes(value);
|
|
5139
5141
|
}
|
|
@@ -5153,43 +5155,124 @@ var AGRO_COMMANDS = Object.freeze({
|
|
|
5153
5155
|
gateway: null,
|
|
5154
5156
|
egress: EGRESS_VERBS,
|
|
5155
5157
|
node: NODE_VERBS,
|
|
5158
|
+
auth: AUTH_VERBS,
|
|
5156
5159
|
...Object.fromEntries(composeVerbs().map((verb) => [verb, null]))
|
|
5157
5160
|
});
|
|
5158
5161
|
|
|
5162
|
+
// src/lib/console-auth.ts
|
|
5163
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as readFileSync12, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
|
|
5164
|
+
import { homedir as homedir3 } from "node:os";
|
|
5165
|
+
import { dirname as dirname8, join as join15 } from "node:path";
|
|
5166
|
+
var CREDENTIAL_FIELDS = ["url", "token", "tokenId", "tokenPrefix", "login"];
|
|
5167
|
+
function consoleCredentialsPath(env = process.env, homeDir = homedir3()) {
|
|
5168
|
+
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
5169
|
+
const configHome = xdg === void 0 || xdg === "" ? join15(homeDir, ".config") : xdg;
|
|
5170
|
+
return join15(configHome, "agro", "console.json");
|
|
5171
|
+
}
|
|
5172
|
+
function isMissing(error) {
|
|
5173
|
+
return error.code === "ENOENT";
|
|
5174
|
+
}
|
|
5175
|
+
function toCredentials(value) {
|
|
5176
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
5177
|
+
const record = value;
|
|
5178
|
+
if (!CREDENTIAL_FIELDS.every((field) => typeof record[field] === "string")) return void 0;
|
|
5179
|
+
return {
|
|
5180
|
+
url: record.url,
|
|
5181
|
+
token: record.token,
|
|
5182
|
+
tokenId: record.tokenId,
|
|
5183
|
+
tokenPrefix: record.tokenPrefix,
|
|
5184
|
+
login: record.login
|
|
5185
|
+
};
|
|
5186
|
+
}
|
|
5187
|
+
function readConsoleCredentials(env = process.env, homeDir = homedir3()) {
|
|
5188
|
+
let text;
|
|
5189
|
+
try {
|
|
5190
|
+
text = readFileSync12(consoleCredentialsPath(env, homeDir), "utf8");
|
|
5191
|
+
} catch (error) {
|
|
5192
|
+
if (isMissing(error)) return void 0;
|
|
5193
|
+
throw error;
|
|
5194
|
+
}
|
|
5195
|
+
try {
|
|
5196
|
+
return toCredentials(JSON.parse(text));
|
|
5197
|
+
} catch {
|
|
5198
|
+
return void 0;
|
|
5199
|
+
}
|
|
5200
|
+
}
|
|
5201
|
+
function writeConsoleCredentials(credentials, env = process.env, homeDir = homedir3()) {
|
|
5202
|
+
const path4 = consoleCredentialsPath(env, homeDir);
|
|
5203
|
+
const dir = dirname8(path4);
|
|
5204
|
+
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
5205
|
+
chmodSync2(dir, 448);
|
|
5206
|
+
const body = {
|
|
5207
|
+
url: credentials.url,
|
|
5208
|
+
token: credentials.token,
|
|
5209
|
+
tokenId: credentials.tokenId,
|
|
5210
|
+
tokenPrefix: credentials.tokenPrefix,
|
|
5211
|
+
login: credentials.login
|
|
5212
|
+
};
|
|
5213
|
+
rmSync4(path4, { force: true });
|
|
5214
|
+
writeFileSync9(path4, `${JSON.stringify(body, null, 2)}
|
|
5215
|
+
`, { mode: 384, flag: "wx" });
|
|
5216
|
+
chmodSync2(path4, 384);
|
|
5217
|
+
return path4;
|
|
5218
|
+
}
|
|
5219
|
+
function deleteConsoleCredentials(env = process.env, homeDir = homedir3()) {
|
|
5220
|
+
const path4 = consoleCredentialsPath(env, homeDir);
|
|
5221
|
+
try {
|
|
5222
|
+
rmSync4(path4);
|
|
5223
|
+
return true;
|
|
5224
|
+
} catch (error) {
|
|
5225
|
+
if (isMissing(error)) return false;
|
|
5226
|
+
throw error;
|
|
5227
|
+
}
|
|
5228
|
+
}
|
|
5229
|
+
|
|
5159
5230
|
// src/commands/node.ts
|
|
5231
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
5232
|
+
import { homedir as homedir4, hostname } from "node:os";
|
|
5233
|
+
import { join as join16 } from "node:path";
|
|
5160
5234
|
var DEFAULT_CONSOLE_URL = "https://console.mifune.dev";
|
|
5161
|
-
var CONSOLE_CONFIG_MISSING = "
|
|
5235
|
+
var CONSOLE_CONFIG_MISSING = "not logged in to agro-console: run agro auth login";
|
|
5162
5236
|
var NODE_READY_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
5237
|
+
var NODE_FAST_POLL_INTERVAL_MS = 1e3;
|
|
5238
|
+
var NODE_FAST_POLL_WINDOW_MS = 30 * 1e3;
|
|
5163
5239
|
var NODE_POLL_INTERVAL_MS = 5e3;
|
|
5164
5240
|
var FREE_TIER_SPEC = "n4";
|
|
5241
|
+
var LOCAL_SSH_KEY_MISSING = "no --ssh-key given and ~/.ssh/id_ed25519.pub not found";
|
|
5165
5242
|
var FAILURE_STATUSES = /* @__PURE__ */ new Set(["failed", "destroy_requested", "destroying", "destroyed", "retired"]);
|
|
5166
|
-
function resolveConsoleConfig(env,
|
|
5167
|
-
const
|
|
5168
|
-
const
|
|
5169
|
-
|
|
5170
|
-
const fromSecret = readSecret2(key)?.trim();
|
|
5171
|
-
return fromSecret === void 0 || fromSecret === "" ? void 0 : fromSecret;
|
|
5243
|
+
function resolveConsoleConfig(env, readCredentials) {
|
|
5244
|
+
const present = (value) => {
|
|
5245
|
+
const trimmed = value?.trim();
|
|
5246
|
+
return trimmed === void 0 || trimmed === "" ? void 0 : trimmed;
|
|
5172
5247
|
};
|
|
5173
|
-
const
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5248
|
+
const trimUrl2 = (url) => (url ?? DEFAULT_CONSOLE_URL).replace(/\/+$/, "");
|
|
5249
|
+
const envToken = present(env.AGRO_CONSOLE_TOKEN);
|
|
5250
|
+
if (envToken !== void 0) return { url: trimUrl2(present(env.AGRO_CONSOLE_URL)), token: envToken, source: "env" };
|
|
5251
|
+
const file = readCredentials();
|
|
5252
|
+
const fileToken = present(file?.token);
|
|
5253
|
+
if (fileToken === void 0) return void 0;
|
|
5254
|
+
return { url: trimUrl2(present(file?.url)), token: fileToken, source: "file" };
|
|
5177
5255
|
}
|
|
5178
|
-
function
|
|
5179
|
-
|
|
5180
|
-
return readSecret(resolveProjectRoot(), key);
|
|
5181
|
-
} catch {
|
|
5182
|
-
return void 0;
|
|
5183
|
-
}
|
|
5256
|
+
function nodePollDelayMs(elapsedMs) {
|
|
5257
|
+
return elapsedMs < NODE_FAST_POLL_WINDOW_MS ? NODE_FAST_POLL_INTERVAL_MS : NODE_POLL_INTERVAL_MS;
|
|
5184
5258
|
}
|
|
5185
5259
|
function defaultDeps2() {
|
|
5186
5260
|
return {
|
|
5187
5261
|
fetch: (input, init) => globalThis.fetch(input, init),
|
|
5188
5262
|
env: process.env,
|
|
5189
|
-
|
|
5263
|
+
readCredentials: () => readConsoleCredentials(),
|
|
5264
|
+
homeDir: () => homedir4(),
|
|
5265
|
+
hostname: () => hostname(),
|
|
5266
|
+
readFile: (path4) => {
|
|
5267
|
+
try {
|
|
5268
|
+
return readFileSync13(path4, "utf8");
|
|
5269
|
+
} catch {
|
|
5270
|
+
return void 0;
|
|
5271
|
+
}
|
|
5272
|
+
},
|
|
5190
5273
|
sleep: (ms) => new Promise((done) => setTimeout(done, ms)),
|
|
5191
5274
|
now: () => Date.now(),
|
|
5192
|
-
|
|
5275
|
+
pollDelayMs: nodePollDelayMs,
|
|
5193
5276
|
timeoutMs: NODE_READY_TIMEOUT_MS,
|
|
5194
5277
|
confirm: (question) => confirm(question, false)
|
|
5195
5278
|
};
|
|
@@ -5198,15 +5281,16 @@ function nodeHelp(bin) {
|
|
|
5198
5281
|
return `${bin} node \u2014 Create, list, and delete agro-console sandbox nodes (remote VMs)
|
|
5199
5282
|
|
|
5200
5283
|
Usage:
|
|
5201
|
-
${bin} node create --spec <spec>
|
|
5284
|
+
${bin} node create --spec <spec> [--ssh-key <uuid>]... [--name <name>] [--free-tier] [--json]
|
|
5202
5285
|
${bin} node list [--json]
|
|
5203
5286
|
${bin} node delete <id> [--yes]
|
|
5204
5287
|
|
|
5205
5288
|
\`create\` waits up to 15 minutes for the node to reach \`running\`, then prints
|
|
5206
5289
|
its SSH command. \`--free-tier\` requests the console free tier. The spec
|
|
5207
|
-
defaults to n4.
|
|
5208
|
-
|
|
5209
|
-
|
|
5290
|
+
defaults to n4. When AGRO_CONSOLE_TOKEN is set, the verbs use it with
|
|
5291
|
+
AGRO_CONSOLE_URL. Otherwise the verbs use the token and the URL from the
|
|
5292
|
+
credentials file that \`${bin} auth login\` writes. The URL defaults to
|
|
5293
|
+
${DEFAULT_CONSOLE_URL}.
|
|
5210
5294
|
`;
|
|
5211
5295
|
}
|
|
5212
5296
|
function asRecord(value) {
|
|
@@ -5293,9 +5377,40 @@ function parseCreate(tokens) {
|
|
|
5293
5377
|
}
|
|
5294
5378
|
if (args.spec === void 0 && args.freeTier) args.spec = FREE_TIER_SPEC;
|
|
5295
5379
|
if (args.spec === void 0) return { ok: false, error: "--spec is required" };
|
|
5296
|
-
if (args.sshKeyIds.length === 0) return { ok: false, error: "at least one --ssh-key is required" };
|
|
5297
5380
|
return { ok: true, args };
|
|
5298
5381
|
}
|
|
5382
|
+
function keyIdentity(publicKey) {
|
|
5383
|
+
return publicKey.trim().split(/\s+/).slice(0, 2).join(" ");
|
|
5384
|
+
}
|
|
5385
|
+
function toKeyList(body) {
|
|
5386
|
+
const record = asRecord(body);
|
|
5387
|
+
const items = Array.isArray(body) ? body : record?.sshKeys;
|
|
5388
|
+
if (!Array.isArray(items)) return [];
|
|
5389
|
+
return items.flatMap((item) => {
|
|
5390
|
+
const key = asRecord(item);
|
|
5391
|
+
return key === void 0 ? [] : [key];
|
|
5392
|
+
});
|
|
5393
|
+
}
|
|
5394
|
+
async function resolveSshKeyId(deps, config) {
|
|
5395
|
+
const publicKey = deps.readFile(join16(deps.homeDir(), ".ssh", "id_ed25519.pub"))?.trim();
|
|
5396
|
+
if (publicKey === void 0 || publicKey === "") return { ok: false, error: LOCAL_SSH_KEY_MISSING };
|
|
5397
|
+
const listed = await callApi(deps, config, "GET", "/api/ssh-keys");
|
|
5398
|
+
if (!listed.ok) return { ok: false, error: listed.message };
|
|
5399
|
+
const identity = keyIdentity(publicKey);
|
|
5400
|
+
const match = toKeyList(listed.body).find(
|
|
5401
|
+
(key) => typeof key.publicKey === "string" && keyIdentity(key.publicKey) === identity
|
|
5402
|
+
);
|
|
5403
|
+
const matchId = asText(match?.id);
|
|
5404
|
+
if (matchId !== null) return { ok: true, id: matchId };
|
|
5405
|
+
const registered = await callApi(deps, config, "POST", "/api/ssh-keys", {
|
|
5406
|
+
name: `agro-cli@${deps.hostname()}`,
|
|
5407
|
+
publicKey
|
|
5408
|
+
});
|
|
5409
|
+
if (!registered.ok) return { ok: false, error: registered.message };
|
|
5410
|
+
const outer = asRecord(registered.body);
|
|
5411
|
+
const id = asText((asRecord(outer?.sshKey) ?? outer)?.id);
|
|
5412
|
+
return id === null ? { ok: false, error: "the console response has no SSH key id" } : { ok: true, id };
|
|
5413
|
+
}
|
|
5299
5414
|
function printCreateResult(io, node, json) {
|
|
5300
5415
|
const ssh = node.status === "running" ? sshCommand(node) : null;
|
|
5301
5416
|
if (json) {
|
|
@@ -5316,7 +5431,18 @@ async function runCreate(tokens, io, deps, config, bin) {
|
|
|
5316
5431
|
`);
|
|
5317
5432
|
return 1;
|
|
5318
5433
|
}
|
|
5319
|
-
const { spec,
|
|
5434
|
+
const { spec, name, freeTier, json } = parsed.args;
|
|
5435
|
+
let sshKeyIds = parsed.args.sshKeyIds;
|
|
5436
|
+
if (sshKeyIds.length === 0) {
|
|
5437
|
+
const resolved = await resolveSshKeyId(deps, config);
|
|
5438
|
+
if (!resolved.ok) {
|
|
5439
|
+
io.stderr(`${prefix}: ${resolved.error}
|
|
5440
|
+
`);
|
|
5441
|
+
return 1;
|
|
5442
|
+
}
|
|
5443
|
+
sshKeyIds = [resolved.id];
|
|
5444
|
+
}
|
|
5445
|
+
const requestedAt = deps.now();
|
|
5320
5446
|
const created = await callApi(deps, config, "POST", "/api/nodes", {
|
|
5321
5447
|
profile: "sandbox",
|
|
5322
5448
|
spec,
|
|
@@ -5346,7 +5472,7 @@ async function runCreate(tokens, io, deps, config, bin) {
|
|
|
5346
5472
|
`);
|
|
5347
5473
|
return 1;
|
|
5348
5474
|
}
|
|
5349
|
-
await deps.sleep(Math.min(deps.
|
|
5475
|
+
await deps.sleep(Math.min(deps.pollDelayMs(deps.now() - requestedAt), remaining));
|
|
5350
5476
|
const polled = await callApi(deps, config, "GET", `/api/nodes/${encodeURIComponent(node.id)}`);
|
|
5351
5477
|
if (!polled.ok) {
|
|
5352
5478
|
io.stderr(`${prefix}: node ${node.id}: ${polled.message}
|
|
@@ -5457,9 +5583,9 @@ async function runNode(args, io, overrides = {}, bin = "agro") {
|
|
|
5457
5583
|
return 0;
|
|
5458
5584
|
}
|
|
5459
5585
|
const deps = { ...defaultDeps2(), ...overrides };
|
|
5460
|
-
const config = resolveConsoleConfig(deps.env, deps.
|
|
5586
|
+
const config = resolveConsoleConfig(deps.env, deps.readCredentials);
|
|
5461
5587
|
if (config === void 0) {
|
|
5462
|
-
io.stderr(`${
|
|
5588
|
+
io.stderr(`${CONSOLE_CONFIG_MISSING}
|
|
5463
5589
|
`);
|
|
5464
5590
|
return 1;
|
|
5465
5591
|
}
|
|
@@ -5468,10 +5594,250 @@ async function runNode(args, io, overrides = {}, bin = "agro") {
|
|
|
5468
5594
|
return runCreate(rest, io, deps, config, bin);
|
|
5469
5595
|
}
|
|
5470
5596
|
|
|
5597
|
+
// src/commands/auth.ts
|
|
5598
|
+
var ENV_TOKEN_LOGOUT = "token comes from AGRO_CONSOLE_TOKEN; unset it to log out";
|
|
5599
|
+
var ALREADY_REVOKED = "token was already revoked; removed local credentials";
|
|
5600
|
+
var SLOW_DOWN_STEP_S = 5;
|
|
5601
|
+
function defaultDeps3() {
|
|
5602
|
+
return {
|
|
5603
|
+
fetch: (input, init) => globalThis.fetch(input, init),
|
|
5604
|
+
env: process.env,
|
|
5605
|
+
hostname: () => hostname2(),
|
|
5606
|
+
sleep: (ms) => new Promise((done) => setTimeout(done, ms)),
|
|
5607
|
+
now: () => Date.now(),
|
|
5608
|
+
readCredentials: () => readConsoleCredentials(),
|
|
5609
|
+
writeCredentials: (credentials) => writeConsoleCredentials(credentials),
|
|
5610
|
+
deleteCredentials: () => deleteConsoleCredentials()
|
|
5611
|
+
};
|
|
5612
|
+
}
|
|
5613
|
+
function authHelp(bin) {
|
|
5614
|
+
return `${bin} auth \u2014 Sign in to agro-console
|
|
5615
|
+
|
|
5616
|
+
Usage:
|
|
5617
|
+
${bin} auth login [--url <url>]
|
|
5618
|
+
${bin} auth status
|
|
5619
|
+
${bin} auth logout
|
|
5620
|
+
${bin} auth token
|
|
5621
|
+
|
|
5622
|
+
\`login\` starts a device authorization, prints a URL and a code, and waits
|
|
5623
|
+
for approval in the browser. It stores the token in the user credentials file.
|
|
5624
|
+
Without --url it uses AGRO_CONSOLE_URL, then ${DEFAULT_CONSOLE_URL}.
|
|
5625
|
+
\`status\` shows the signed-in identity. \`logout\` revokes the token and deletes
|
|
5626
|
+
the credentials file. \`token\` prints the token. AGRO_CONSOLE_TOKEN in the
|
|
5627
|
+
environment overrides the credentials file.
|
|
5628
|
+
`;
|
|
5629
|
+
}
|
|
5630
|
+
function asRecord2(value) {
|
|
5631
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
5632
|
+
}
|
|
5633
|
+
function asText2(value) {
|
|
5634
|
+
if (typeof value === "string" && value !== "") return value;
|
|
5635
|
+
if (typeof value === "number") return String(value);
|
|
5636
|
+
return void 0;
|
|
5637
|
+
}
|
|
5638
|
+
async function callApi2(deps, url, method, path4, options = {}) {
|
|
5639
|
+
let response;
|
|
5640
|
+
try {
|
|
5641
|
+
response = await deps.fetch(`${url}${path4}`, {
|
|
5642
|
+
method,
|
|
5643
|
+
headers: {
|
|
5644
|
+
Accept: "application/json",
|
|
5645
|
+
...options.token === void 0 ? {} : { Authorization: `Bearer ${options.token}` },
|
|
5646
|
+
...options.payload === void 0 ? {} : { "Content-Type": "application/json" }
|
|
5647
|
+
},
|
|
5648
|
+
...options.payload === void 0 ? {} : { body: JSON.stringify(options.payload) }
|
|
5649
|
+
});
|
|
5650
|
+
} catch (error) {
|
|
5651
|
+
return { ok: false, message: `request failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
5652
|
+
}
|
|
5653
|
+
const text = await response.text();
|
|
5654
|
+
let body = null;
|
|
5655
|
+
try {
|
|
5656
|
+
body = text === "" ? null : JSON.parse(text);
|
|
5657
|
+
} catch {
|
|
5658
|
+
body = null;
|
|
5659
|
+
}
|
|
5660
|
+
const record = asRecord2(body);
|
|
5661
|
+
if (response.status >= 400) {
|
|
5662
|
+
const nested = asRecord2(record.error);
|
|
5663
|
+
const code = asText2(record.code) ?? asText2(nested.code);
|
|
5664
|
+
return { ok: false, status: response.status, code, message: `HTTP ${response.status} ${code ?? "no code"}` };
|
|
5665
|
+
}
|
|
5666
|
+
return { ok: true, status: response.status, body: record };
|
|
5667
|
+
}
|
|
5668
|
+
function trimUrl(url) {
|
|
5669
|
+
return url.trim().replace(/\/+$/, "");
|
|
5670
|
+
}
|
|
5671
|
+
function parseLogin(tokens) {
|
|
5672
|
+
let url;
|
|
5673
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
5674
|
+
const token = tokens[i];
|
|
5675
|
+
const [flag, inline] = token.startsWith("--") && token.includes("=") ? [token.slice(0, token.indexOf("=")), token.slice(token.indexOf("=") + 1)] : [token, void 0];
|
|
5676
|
+
if (flag !== "--url") {
|
|
5677
|
+
return { ok: false, error: token.startsWith("-") ? `unknown flag "${token}"` : `unexpected argument "${token}"` };
|
|
5678
|
+
}
|
|
5679
|
+
const value = inline ?? tokens[++i];
|
|
5680
|
+
if (value === void 0 || value.trim() === "") return { ok: false, error: "--url requires a value" };
|
|
5681
|
+
url = value;
|
|
5682
|
+
}
|
|
5683
|
+
return { ok: true, url };
|
|
5684
|
+
}
|
|
5685
|
+
async function runLogin(tokens, io, deps, bin) {
|
|
5686
|
+
const prefix = `${bin} auth login`;
|
|
5687
|
+
const parsed = parseLogin(tokens);
|
|
5688
|
+
if (!parsed.ok) {
|
|
5689
|
+
io.stderr(`${prefix}: ${parsed.error}
|
|
5690
|
+
`);
|
|
5691
|
+
return 1;
|
|
5692
|
+
}
|
|
5693
|
+
const envUrl = deps.env.AGRO_CONSOLE_URL?.trim();
|
|
5694
|
+
const url = trimUrl(parsed.url ?? (envUrl === void 0 || envUrl === "" ? DEFAULT_CONSOLE_URL : envUrl));
|
|
5695
|
+
const started = await callApi2(deps, url, "POST", "/api/cli/device", { payload: { clientName: deps.hostname() } });
|
|
5696
|
+
if (!started.ok) {
|
|
5697
|
+
io.stderr(`${prefix}: ${started.message}
|
|
5698
|
+
`);
|
|
5699
|
+
return 1;
|
|
5700
|
+
}
|
|
5701
|
+
const deviceCode = asText2(started.body.device_code);
|
|
5702
|
+
const userCode = asText2(started.body.user_code);
|
|
5703
|
+
const verificationUri = asText2(started.body.verification_uri);
|
|
5704
|
+
if (deviceCode === void 0 || userCode === void 0 || verificationUri === void 0) {
|
|
5705
|
+
io.stderr(`${prefix}: unexpected device authorization response
|
|
5706
|
+
`);
|
|
5707
|
+
return 1;
|
|
5708
|
+
}
|
|
5709
|
+
let intervalS = Number(started.body.interval) > 0 ? Number(started.body.interval) : 5;
|
|
5710
|
+
const expiresInS = Number(started.body.expires_in) > 0 ? Number(started.body.expires_in) : 900;
|
|
5711
|
+
const deadline = deps.now() + expiresInS * 1e3;
|
|
5712
|
+
io.stdout(`Open ${verificationUri} and enter the code ${userCode}
|
|
5713
|
+
Waiting for approval...
|
|
5714
|
+
`);
|
|
5715
|
+
while (deps.now() + intervalS * 1e3 <= deadline) {
|
|
5716
|
+
await deps.sleep(intervalS * 1e3);
|
|
5717
|
+
const polled = await callApi2(deps, url, "POST", "/api/cli/device/token", { payload: { device_code: deviceCode } });
|
|
5718
|
+
if (polled.ok) {
|
|
5719
|
+
const credentials = {
|
|
5720
|
+
url,
|
|
5721
|
+
token: asText2(polled.body.token),
|
|
5722
|
+
tokenId: asText2(polled.body.tokenId),
|
|
5723
|
+
tokenPrefix: asText2(polled.body.tokenPrefix),
|
|
5724
|
+
login: asText2(polled.body.login)
|
|
5725
|
+
};
|
|
5726
|
+
if (Object.values(credentials).some((value) => value === void 0)) {
|
|
5727
|
+
io.stderr(`${prefix}: unexpected token response
|
|
5728
|
+
`);
|
|
5729
|
+
return 1;
|
|
5730
|
+
}
|
|
5731
|
+
deps.writeCredentials(credentials);
|
|
5732
|
+
io.stdout(`logged in to ${url} as ${credentials.login}
|
|
5733
|
+
`);
|
|
5734
|
+
return 0;
|
|
5735
|
+
}
|
|
5736
|
+
if (polled.code === "authorization_pending") continue;
|
|
5737
|
+
if (polled.code === "slow_down") {
|
|
5738
|
+
intervalS += SLOW_DOWN_STEP_S;
|
|
5739
|
+
continue;
|
|
5740
|
+
}
|
|
5741
|
+
if (polled.code === "access_denied") {
|
|
5742
|
+
io.stderr(`${prefix}: the authorization was denied
|
|
5743
|
+
`);
|
|
5744
|
+
return 1;
|
|
5745
|
+
}
|
|
5746
|
+
if (polled.code === "expired_token") {
|
|
5747
|
+
io.stderr(`${prefix}: the code expired; run ${bin} auth login again
|
|
5748
|
+
`);
|
|
5749
|
+
return 1;
|
|
5750
|
+
}
|
|
5751
|
+
io.stderr(`${prefix}: ${polled.message}
|
|
5752
|
+
`);
|
|
5753
|
+
return 1;
|
|
5754
|
+
}
|
|
5755
|
+
io.stderr(`${prefix}: the code expired; run ${bin} auth login again
|
|
5756
|
+
`);
|
|
5757
|
+
return 1;
|
|
5758
|
+
}
|
|
5759
|
+
async function runStatus(io, deps, config, bin) {
|
|
5760
|
+
const me = await callApi2(deps, config.url, "GET", "/api/me", { token: config.token });
|
|
5761
|
+
if (!me.ok) {
|
|
5762
|
+
const hint = me.status === 401 ? `: the token is invalid or revoked; run ${bin} auth login` : "";
|
|
5763
|
+
io.stderr(`${bin} auth status: ${me.message}${hint}
|
|
5764
|
+
`);
|
|
5765
|
+
return 1;
|
|
5766
|
+
}
|
|
5767
|
+
const field = (value) => asText2(value) ?? "-";
|
|
5768
|
+
io.stdout(
|
|
5769
|
+
`url: ${config.url}
|
|
5770
|
+
login: ${field(me.body.login)}
|
|
5771
|
+
role: ${field(me.body.role)}
|
|
5772
|
+
org: ${field(me.body.orgId)}
|
|
5773
|
+
source: ${config.source}
|
|
5774
|
+
token prefix: ${field(me.body.tokenPrefix)}
|
|
5775
|
+
`
|
|
5776
|
+
);
|
|
5777
|
+
return 0;
|
|
5778
|
+
}
|
|
5779
|
+
async function runLogout(io, deps, config, bin) {
|
|
5780
|
+
if (config.source === "env") {
|
|
5781
|
+
io.stderr(`${ENV_TOKEN_LOGOUT}
|
|
5782
|
+
`);
|
|
5783
|
+
return 1;
|
|
5784
|
+
}
|
|
5785
|
+
const revoked = await callApi2(deps, config.url, "DELETE", "/api/me/token", { token: config.token });
|
|
5786
|
+
if (!revoked.ok && revoked.status === 401) {
|
|
5787
|
+
deps.deleteCredentials();
|
|
5788
|
+
io.stdout(`${ALREADY_REVOKED}
|
|
5789
|
+
`);
|
|
5790
|
+
return 0;
|
|
5791
|
+
}
|
|
5792
|
+
if (!revoked.ok) {
|
|
5793
|
+
io.stderr(`${bin} auth logout: ${revoked.message}
|
|
5794
|
+
`);
|
|
5795
|
+
return 1;
|
|
5796
|
+
}
|
|
5797
|
+
deps.deleteCredentials();
|
|
5798
|
+
io.stdout(`logged out of ${config.url}
|
|
5799
|
+
`);
|
|
5800
|
+
return 0;
|
|
5801
|
+
}
|
|
5802
|
+
async function runAuth(args, io, overrides = {}, bin = "agro") {
|
|
5803
|
+
const [verb, ...rest] = args;
|
|
5804
|
+
if (verb === void 0 || verb === "--help" || verb === "-h" || verb === "help") {
|
|
5805
|
+
io.stdout(authHelp(bin));
|
|
5806
|
+
return verb === void 0 ? 1 : 0;
|
|
5807
|
+
}
|
|
5808
|
+
if (!isOneOf(AUTH_VERBS, verb)) {
|
|
5809
|
+
io.stderr(`${bin} auth: unknown subcommand "${verb}" \u2014 expected ${AUTH_VERBS.join(", ")}
|
|
5810
|
+
`);
|
|
5811
|
+
return 1;
|
|
5812
|
+
}
|
|
5813
|
+
if (rest.some((token) => token === "--help" || token === "-h")) {
|
|
5814
|
+
io.stdout(authHelp(bin));
|
|
5815
|
+
return 0;
|
|
5816
|
+
}
|
|
5817
|
+
const deps = { ...defaultDeps3(), ...overrides };
|
|
5818
|
+
if (verb === "login") return runLogin(rest, io, deps, bin);
|
|
5819
|
+
if (rest.length > 0) {
|
|
5820
|
+
io.stderr(`${bin} auth ${verb}: unexpected argument "${rest[0]}"
|
|
5821
|
+
`);
|
|
5822
|
+
return 1;
|
|
5823
|
+
}
|
|
5824
|
+
const config = resolveConsoleConfig(deps.env, deps.readCredentials);
|
|
5825
|
+
if (config === void 0) {
|
|
5826
|
+
io.stderr(`${CONSOLE_CONFIG_MISSING}
|
|
5827
|
+
`);
|
|
5828
|
+
return 1;
|
|
5829
|
+
}
|
|
5830
|
+
if (verb === "status") return runStatus(io, deps, config, bin);
|
|
5831
|
+
if (verb === "logout") return runLogout(io, deps, config, bin);
|
|
5832
|
+
io.stdout(`${config.token}
|
|
5833
|
+
`);
|
|
5834
|
+
return 0;
|
|
5835
|
+
}
|
|
5836
|
+
|
|
5471
5837
|
// src/commands/harness.ts
|
|
5472
5838
|
import { existsSync as existsSync20 } from "node:fs";
|
|
5473
|
-
import { homedir as
|
|
5474
|
-
import { delimiter as delimiter2, join as
|
|
5839
|
+
import { homedir as homedir5 } from "node:os";
|
|
5840
|
+
import { delimiter as delimiter2, join as join19 } from "node:path";
|
|
5475
5841
|
|
|
5476
5842
|
// src/lib/docs.ts
|
|
5477
5843
|
var SOURCE_DOCS_BASE = "https://github.com/mifunedev/agro/blob/main/";
|
|
@@ -5484,8 +5850,8 @@ function sourceDocsUrl(docsPath) {
|
|
|
5484
5850
|
}
|
|
5485
5851
|
|
|
5486
5852
|
// src/lib/host-config.ts
|
|
5487
|
-
import { existsSync as existsSync18, mkdirSync as
|
|
5488
|
-
import { basename as basename8, dirname as
|
|
5853
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync10, readFileSync as readFileSync14, renameSync as renameSync4, unlinkSync as unlinkSync6, writeFileSync as writeFileSync10 } from "node:fs";
|
|
5854
|
+
import { basename as basename8, dirname as dirname9, isAbsolute, join as join17, resolve as resolve10 } from "node:path";
|
|
5489
5855
|
var HOST_CONFIG_MODE = 420;
|
|
5490
5856
|
var HOST_CONFIG_FILE = "config.json";
|
|
5491
5857
|
var WORKSPACES_SUBDIR = "workspaces";
|
|
@@ -5497,7 +5863,7 @@ function hostStateHome(env, home) {
|
|
|
5497
5863
|
return resolveUserStateHome(env, home);
|
|
5498
5864
|
}
|
|
5499
5865
|
function hostConfigPath(env, home) {
|
|
5500
|
-
return
|
|
5866
|
+
return join17(hostStateHome(env, home), HOST_CONFIG_FILE);
|
|
5501
5867
|
}
|
|
5502
5868
|
function assertWorkspaceName(name) {
|
|
5503
5869
|
if (!SANDBOX_NAME_PATTERN.test(name)) {
|
|
@@ -5507,14 +5873,14 @@ function assertWorkspaceName(name) {
|
|
|
5507
5873
|
}
|
|
5508
5874
|
}
|
|
5509
5875
|
function workspacesRoot(env, home) {
|
|
5510
|
-
return
|
|
5876
|
+
return join17(hostStateHome(env, home), WORKSPACES_SUBDIR);
|
|
5511
5877
|
}
|
|
5512
5878
|
function workspaceRoot(name, env, home) {
|
|
5513
5879
|
assertWorkspaceName(name);
|
|
5514
|
-
return
|
|
5880
|
+
return join17(workspacesRoot(env, home), name);
|
|
5515
5881
|
}
|
|
5516
5882
|
function defaultHarnessRoot(env, home) {
|
|
5517
|
-
return
|
|
5883
|
+
return join17(workspacesRoot(env, home), DEFAULT_WORKSPACE_NAME);
|
|
5518
5884
|
}
|
|
5519
5885
|
function validateHostConfig(value) {
|
|
5520
5886
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -5582,7 +5948,7 @@ function readHostConfig(env, home) {
|
|
|
5582
5948
|
if (!existsSync18(path4)) return { version: 1 };
|
|
5583
5949
|
let raw;
|
|
5584
5950
|
try {
|
|
5585
|
-
raw =
|
|
5951
|
+
raw = readFileSync14(path4, "utf8");
|
|
5586
5952
|
} catch (error) {
|
|
5587
5953
|
const detail = error instanceof Error ? error.message : String(error);
|
|
5588
5954
|
throw new Error(`could not read ${path4}: ${detail}`);
|
|
@@ -5601,9 +5967,9 @@ function writeHostConfig(config, env, home) {
|
|
|
5601
5967
|
const body = `${JSON.stringify(validated, null, 2)}
|
|
5602
5968
|
`;
|
|
5603
5969
|
const tmp = `${path4}.tmp.${process.pid}`;
|
|
5604
|
-
|
|
5970
|
+
mkdirSync10(dirname9(path4), { recursive: true });
|
|
5605
5971
|
try {
|
|
5606
|
-
|
|
5972
|
+
writeFileSync10(tmp, body, { mode: HOST_CONFIG_MODE, encoding: "utf8" });
|
|
5607
5973
|
renameSync4(tmp, path4);
|
|
5608
5974
|
} catch (error) {
|
|
5609
5975
|
try {
|
|
@@ -5630,8 +5996,8 @@ function fieldError2(path4, requirement) {
|
|
|
5630
5996
|
}
|
|
5631
5997
|
|
|
5632
5998
|
// src/lib/host-workspace.ts
|
|
5633
|
-
import { existsSync as existsSync19, mkdirSync as
|
|
5634
|
-
import { dirname as
|
|
5999
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, readdirSync as readdirSync3, renameSync as renameSync5, rmSync as rmSync5, statSync as statSync5 } from "node:fs";
|
|
6000
|
+
import { dirname as dirname10, join as join18, resolve as resolve11 } from "node:path";
|
|
5635
6001
|
var AGRO_REPO_URL = "https://github.com/mifunedev/agro.git";
|
|
5636
6002
|
var IGNORABLE_ENTRIES = [
|
|
5637
6003
|
REGISTRY_SUBDIR,
|
|
@@ -5643,7 +6009,7 @@ function isDirectory3(path4) {
|
|
|
5643
6009
|
return statSync5(path4, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
5644
6010
|
}
|
|
5645
6011
|
function stateHomes(home) {
|
|
5646
|
-
return STATE_HOME_DIRS.map((name) =>
|
|
6012
|
+
return STATE_HOME_DIRS.map((name) => join18(home, name));
|
|
5647
6013
|
}
|
|
5648
6014
|
function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
5649
6015
|
const target = resolve11(root);
|
|
@@ -5651,7 +6017,7 @@ function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
|
5651
6017
|
if (enclosing === void 0) return void 0;
|
|
5652
6018
|
return `${bin} ${verb}: the ${verb} root ${target} is the state home ${enclosing} itself.
|
|
5653
6019
|
A workspace there collides with AGRO state and blocks every command run from ${home}.
|
|
5654
|
-
Move it out \u2014 for example \`mv ${enclosing} ${
|
|
6020
|
+
Move it out \u2014 for example \`mv ${enclosing} ${join18(home, "agro")}\` \u2014 then re-run this command.
|
|
5655
6021
|
`;
|
|
5656
6022
|
}
|
|
5657
6023
|
function isIgnorableEntry(name) {
|
|
@@ -5668,27 +6034,27 @@ function cloneInto(target, run, root, ref) {
|
|
|
5668
6034
|
assertCloned(run("git", ["clone", ...branch, AGRO_REPO_URL, target], { stdio: "inherit" }), root);
|
|
5669
6035
|
}
|
|
5670
6036
|
function cloneThroughStaging(root, run, ref) {
|
|
5671
|
-
const staging = mkdtempSync4(
|
|
6037
|
+
const staging = mkdtempSync4(join18(dirname10(root), "agro-clone-"));
|
|
5672
6038
|
try {
|
|
5673
|
-
const checkout =
|
|
6039
|
+
const checkout = join18(staging, "agro");
|
|
5674
6040
|
cloneInto(checkout, run, root, ref);
|
|
5675
6041
|
for (const name of readdirSync3(checkout)) {
|
|
5676
|
-
const destination =
|
|
6042
|
+
const destination = join18(root, name);
|
|
5677
6043
|
if (existsSync19(destination)) {
|
|
5678
6044
|
throw new Error(`could not move ${name} into ${root} \u2014 the path already exists`);
|
|
5679
6045
|
}
|
|
5680
|
-
renameSync5(
|
|
6046
|
+
renameSync5(join18(checkout, name), destination);
|
|
5681
6047
|
}
|
|
5682
6048
|
} finally {
|
|
5683
|
-
|
|
6049
|
+
rmSync5(staging, { recursive: true, force: true });
|
|
5684
6050
|
}
|
|
5685
6051
|
}
|
|
5686
6052
|
function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
5687
6053
|
const root = resolve11(path4);
|
|
5688
|
-
if (existsSync19(
|
|
6054
|
+
if (existsSync19(join18(root, ".git"))) return { root, action: "reused" };
|
|
5689
6055
|
const stats = statSync5(root, { throwIfNoEntry: false });
|
|
5690
6056
|
if (stats === void 0) {
|
|
5691
|
-
|
|
6057
|
+
mkdirSync11(dirname10(root), { recursive: true });
|
|
5692
6058
|
cloneInto(root, run, root, ref);
|
|
5693
6059
|
return { root, action: "cloned" };
|
|
5694
6060
|
}
|
|
@@ -5707,11 +6073,11 @@ function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
|
5707
6073
|
function listHostWorkspaces(env, home) {
|
|
5708
6074
|
const root = workspacesRoot(env, home);
|
|
5709
6075
|
if (!isDirectory3(root)) return [];
|
|
5710
|
-
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => SANDBOX_NAME_PATTERN.test(name)).filter((name) => existsSync19(
|
|
6076
|
+
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => SANDBOX_NAME_PATTERN.test(name)).filter((name) => existsSync19(join18(root, name, ".git"))).sort();
|
|
5711
6077
|
}
|
|
5712
6078
|
function resolveExistingWorkspace(bin, verb, path4, env, home) {
|
|
5713
6079
|
const root = resolve11(path4);
|
|
5714
|
-
if (existsSync19(
|
|
6080
|
+
if (existsSync19(join18(root, ".git"))) return { ok: true, root };
|
|
5715
6081
|
const existing = listHostWorkspaces(env, home);
|
|
5716
6082
|
const inventory = existing.length === 0 ? "No host workspace exists yet.\n" : `Workspaces that exist: ${existing.join(", ")}
|
|
5717
6083
|
`;
|
|
@@ -5748,7 +6114,7 @@ function targetFor(root, cwd, run, env) {
|
|
|
5748
6114
|
});
|
|
5749
6115
|
}
|
|
5750
6116
|
function hostPrefix(home) {
|
|
5751
|
-
return
|
|
6117
|
+
return join19(home, ".local");
|
|
5752
6118
|
}
|
|
5753
6119
|
function pathEntries(env) {
|
|
5754
6120
|
return (env.PATH ?? "").split(delimiter2).filter((part) => part !== "");
|
|
@@ -5795,7 +6161,7 @@ async function sandboxMarkerExists(target, entry) {
|
|
|
5795
6161
|
return r.exitCode === 0;
|
|
5796
6162
|
}
|
|
5797
6163
|
function hostBinaryUnderPrefix(entry, prefix) {
|
|
5798
|
-
return existsSync20(
|
|
6164
|
+
return existsSync20(join19(harnessBinPath(prefix), entry.binary));
|
|
5799
6165
|
}
|
|
5800
6166
|
function stateOf(entry, installed, location) {
|
|
5801
6167
|
return {
|
|
@@ -5816,7 +6182,7 @@ function probeableHost(env, home) {
|
|
|
5816
6182
|
return void 0;
|
|
5817
6183
|
}
|
|
5818
6184
|
const prefix = hostPrefix(home);
|
|
5819
|
-
if (!existsSync20(
|
|
6185
|
+
if (!existsSync20(join19(root, ".git")) && !existsSync20(prefix)) return void 0;
|
|
5820
6186
|
return { root, prefix };
|
|
5821
6187
|
}
|
|
5822
6188
|
async function collectStates(root, cwd, run, home, env, only) {
|
|
@@ -6047,7 +6413,7 @@ Start it with \`${bin} sandbox\`, then re-run this command.
|
|
|
6047
6413
|
`;
|
|
6048
6414
|
}
|
|
6049
6415
|
function homeOf(opts) {
|
|
6050
|
-
return (opts.homedir ??
|
|
6416
|
+
return (opts.homedir ?? homedir5)();
|
|
6051
6417
|
}
|
|
6052
6418
|
function isInteractive(opts) {
|
|
6053
6419
|
return opts.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
@@ -6436,11 +6802,11 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
6436
6802
|
}
|
|
6437
6803
|
|
|
6438
6804
|
// src/commands/workspace.ts
|
|
6439
|
-
import { existsSync as existsSync21, rmSync as
|
|
6440
|
-
import { homedir as
|
|
6441
|
-
import { join as
|
|
6805
|
+
import { existsSync as existsSync21, rmSync as rmSync6 } from "node:fs";
|
|
6806
|
+
import { homedir as homedir6 } from "node:os";
|
|
6807
|
+
import { join as join20, resolve as resolve12 } from "node:path";
|
|
6442
6808
|
function homeOf2(opts) {
|
|
6443
|
-
return (opts.homedir ??
|
|
6809
|
+
return (opts.homedir ?? homedir6)();
|
|
6444
6810
|
}
|
|
6445
6811
|
function messageOf2(error) {
|
|
6446
6812
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -6486,7 +6852,7 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
6486
6852
|
let action;
|
|
6487
6853
|
const preexisting = existsSync21(root);
|
|
6488
6854
|
try {
|
|
6489
|
-
if (!opts.json && !existsSync21(
|
|
6855
|
+
if (!opts.json && !existsSync21(join20(root, ".git"))) {
|
|
6490
6856
|
const at = opts.ref !== void 0 ? ` at ${opts.ref}` : "";
|
|
6491
6857
|
io.stdout(`cloning ${AGRO_REPO_URL}${at} into ${root}\u2026
|
|
6492
6858
|
`);
|
|
@@ -6496,7 +6862,7 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
6496
6862
|
action = created.action;
|
|
6497
6863
|
} catch (err) {
|
|
6498
6864
|
if (opts.ref !== void 0) {
|
|
6499
|
-
if (!preexisting)
|
|
6865
|
+
if (!preexisting) rmSync6(root, { recursive: true, force: true });
|
|
6500
6866
|
io.stderr(
|
|
6501
6867
|
`${bin} workspace: could not clone ref "${opts.ref}" \u2014 the ref does not exist in ${AGRO_REPO_URL} or the clone failed (${messageOf2(err)})
|
|
6502
6868
|
`
|
|
@@ -6531,7 +6897,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
6531
6897
|
const recorded = recordedRoot(env, home);
|
|
6532
6898
|
const registry = workspacesRoot(env, home);
|
|
6533
6899
|
rows = listHostWorkspaces(env, home).map((name) => {
|
|
6534
|
-
const root =
|
|
6900
|
+
const root = join20(registry, name);
|
|
6535
6901
|
return { name, root, default: recorded === root };
|
|
6536
6902
|
});
|
|
6537
6903
|
} catch (err) {
|
|
@@ -6557,8 +6923,8 @@ async function runWorkspaceList(opts, io) {
|
|
|
6557
6923
|
|
|
6558
6924
|
// src/commands/tool.ts
|
|
6559
6925
|
import { existsSync as existsSync22 } from "node:fs";
|
|
6560
|
-
import { homedir as
|
|
6561
|
-
import { delimiter as delimiter3, join as
|
|
6926
|
+
import { homedir as homedir7, userInfo as userInfo2 } from "node:os";
|
|
6927
|
+
import { delimiter as delimiter3, join as join21 } from "node:path";
|
|
6562
6928
|
|
|
6563
6929
|
// src/lib/tools/catalog.ts
|
|
6564
6930
|
var TOOLS_DOC = "docs/installation.md";
|
|
@@ -6601,22 +6967,47 @@ var TOOL_CATALOG = Object.freeze([
|
|
|
6601
6967
|
' browser="$resolved"',
|
|
6602
6968
|
" break",
|
|
6603
6969
|
"done",
|
|
6604
|
-
'
|
|
6605
|
-
'
|
|
6606
|
-
' echo "
|
|
6607
|
-
'
|
|
6608
|
-
|
|
6970
|
+
'deps=""',
|
|
6971
|
+
'if [ -n "$browser" ]; then',
|
|
6972
|
+
' echo "agent-browser will drive $browser"',
|
|
6973
|
+
'elif [ "$(id -u)" = 0 ] || sudo -n true 2>/dev/null; then',
|
|
6974
|
+
" if ! command -v sudo >/dev/null 2>&1; then",
|
|
6975
|
+
` printf '#!/bin/sh\\nexec "$@"\\n' > "$tmp/sudo"`,
|
|
6976
|
+
' chmod +x "$tmp/sudo"',
|
|
6977
|
+
' PATH="$tmp:$PATH"',
|
|
6978
|
+
" fi",
|
|
6979
|
+
' "$prefix/bin/agent-browser" install --with-deps',
|
|
6980
|
+
"else",
|
|
6981
|
+
' "$prefix/bin/agent-browser" install',
|
|
6982
|
+
" deps=missing",
|
|
6983
|
+
"fi",
|
|
6984
|
+
'export AGENT_BROWSER_SOCKET_DIR="$tmp"',
|
|
6985
|
+
"session=agro-install-check",
|
|
6986
|
+
"launched=yes",
|
|
6987
|
+
'"$prefix/bin/agent-browser" --session "$session" open about:blank >"$tmp/launch.log" 2>&1 || launched=no',
|
|
6988
|
+
'"$prefix/bin/agent-browser" --session "$session" close >/dev/null 2>&1 || true',
|
|
6989
|
+
'if [ "$launched" = no ]; then',
|
|
6990
|
+
' cat "$tmp/launch.log" >&2',
|
|
6991
|
+
' echo "agent-browser is installed at $prefix/bin/agent-browser, but headless Chrome did not launch." >&2',
|
|
6992
|
+
' if [ "$deps" = missing ]; then',
|
|
6993
|
+
' echo "Chrome needs system libraries, and this user has no passwordless sudo. Run this command, then rerun agro tool install agent-browser:" >&2',
|
|
6994
|
+
' echo " sudo $prefix/bin/agent-browser install --with-deps" >&2',
|
|
6995
|
+
" fi",
|
|
6609
6996
|
" exit 1",
|
|
6610
6997
|
"fi",
|
|
6611
|
-
'echo "agent-browser
|
|
6612
|
-
'"$prefix/bin/agent-browser" --version >/dev/null'
|
|
6998
|
+
'echo "agent-browser launched headless Chrome"'
|
|
6613
6999
|
].join("\n")
|
|
6614
7000
|
]),
|
|
6615
7001
|
installUser: "sandbox",
|
|
6616
7002
|
downloadSize: "~1 GB",
|
|
7003
|
+
hostDownloadSize: "~400 MB",
|
|
6617
7004
|
hostCapable: true,
|
|
6618
7005
|
uninstallArgv: Object.freeze(["bash", "-lc", "pnpm remove -g agent-browser"]),
|
|
6619
|
-
hostUninstallArgv: Object.freeze([
|
|
7006
|
+
hostUninstallArgv: Object.freeze([
|
|
7007
|
+
"bash",
|
|
7008
|
+
"-c",
|
|
7009
|
+
`rm -rf "${HARNESS_PREFIX_TOKEN}/bin/agent-browser" "$HOME/.agent-browser/browsers"`
|
|
7010
|
+
]),
|
|
6620
7011
|
docsPath: TOOLS_DOC
|
|
6621
7012
|
}),
|
|
6622
7013
|
Object.freeze({
|
|
@@ -7037,7 +7428,7 @@ function targetFor2(root, run, env) {
|
|
|
7037
7428
|
});
|
|
7038
7429
|
}
|
|
7039
7430
|
function hostPrefix2(home) {
|
|
7040
|
-
return
|
|
7431
|
+
return join21(home, ".local");
|
|
7041
7432
|
}
|
|
7042
7433
|
function sandboxMarkerPath2(entry) {
|
|
7043
7434
|
return `${SANDBOX_HARNESS_PREFIX}/share/agro/tools/${entry.id}.installed`;
|
|
@@ -7051,7 +7442,7 @@ async function sandboxMarkerExists2(target, entry) {
|
|
|
7051
7442
|
return r.exitCode === 0;
|
|
7052
7443
|
}
|
|
7053
7444
|
function hostBinaryUnderPrefix2(entry, prefix) {
|
|
7054
|
-
return existsSync22(
|
|
7445
|
+
return existsSync22(join21(harnessBinPath(prefix), entry.binary));
|
|
7055
7446
|
}
|
|
7056
7447
|
function pathEntries2(env) {
|
|
7057
7448
|
return (env.PATH ?? "").split(delimiter3).filter((part) => part !== "");
|
|
@@ -7071,7 +7462,7 @@ function messageOf3(error) {
|
|
|
7071
7462
|
return error instanceof Error ? error.message : String(error);
|
|
7072
7463
|
}
|
|
7073
7464
|
function homeOf3(opts) {
|
|
7074
|
-
return (opts.homedir ??
|
|
7465
|
+
return (opts.homedir ?? homedir7)();
|
|
7075
7466
|
}
|
|
7076
7467
|
function platformOf(opts) {
|
|
7077
7468
|
return opts.platform ?? process.platform;
|
|
@@ -7131,7 +7522,7 @@ function probeableHost2(env, home) {
|
|
|
7131
7522
|
return void 0;
|
|
7132
7523
|
}
|
|
7133
7524
|
const prefix = hostPrefix2(home);
|
|
7134
|
-
if (!existsSync22(
|
|
7525
|
+
if (!existsSync22(join21(root, ".git")) && !existsSync22(prefix)) return void 0;
|
|
7135
7526
|
return { root, prefix };
|
|
7136
7527
|
}
|
|
7137
7528
|
async function collectRows(root, run, home, env, only) {
|
|
@@ -7669,7 +8060,7 @@ function fetchRemoteSource(opts) {
|
|
|
7669
8060
|
|
|
7670
8061
|
// src/cli.ts
|
|
7671
8062
|
var DEFAULT_SOURCE_CONTROL_DIR = resolve13(
|
|
7672
|
-
|
|
8063
|
+
dirname11(fileURLToPath(import.meta.url)),
|
|
7673
8064
|
"../.."
|
|
7674
8065
|
);
|
|
7675
8066
|
var INTEGRATIONS = {
|
|
@@ -7714,6 +8105,7 @@ Usage:
|
|
|
7714
8105
|
${bin} egress <args...> Read the egress proxy log or reload its allowlist (log|reload)
|
|
7715
8106
|
${bin} workspace <args...> Create and list host AGRO workspaces (create|list)
|
|
7716
8107
|
${bin} node <args...> Manage remote agro-console sandbox nodes (create|list|delete)
|
|
8108
|
+
${bin} auth <args...> Sign in to agro-console (login|status|logout|token)
|
|
7717
8109
|
${bin} harness <args...> Install and inspect agent CLI harnesses
|
|
7718
8110
|
${bin} tool <args...> Install and inspect sandbox tooling
|
|
7719
8111
|
${bin} gateway <args...> Manage a messaging client session (pi|hermes|openclaw)
|
|
@@ -8549,7 +8941,7 @@ function parseGatewayArgs(rest) {
|
|
|
8549
8941
|
return { ok: true, args: { help: false, passthrough: [...rest] } };
|
|
8550
8942
|
}
|
|
8551
8943
|
function bundledPayloadExists(bundled, exists = existsSync23) {
|
|
8552
|
-
return exists(
|
|
8944
|
+
return exists(join22(bundled.sourceControlDir, "manifest.json"));
|
|
8553
8945
|
}
|
|
8554
8946
|
function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
8555
8947
|
const exists = bundled.exists ?? existsSync23;
|
|
@@ -8572,7 +8964,7 @@ function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
|
8572
8964
|
function readPayloadVersion(checkoutDir) {
|
|
8573
8965
|
try {
|
|
8574
8966
|
const parsed = JSON.parse(
|
|
8575
|
-
|
|
8967
|
+
readFileSync15(join22(resolveControlDir(checkoutDir), "cli", "package.json"), "utf8")
|
|
8576
8968
|
);
|
|
8577
8969
|
if (parsed && typeof parsed.version === "string") return parsed.version;
|
|
8578
8970
|
} catch {
|
|
@@ -8581,8 +8973,8 @@ function readPayloadVersion(checkoutDir) {
|
|
|
8581
8973
|
}
|
|
8582
8974
|
async function runWithRemoteSource(hooks, fn) {
|
|
8583
8975
|
const fetch2 = hooks.fetch ?? fetchRemoteSource;
|
|
8584
|
-
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(
|
|
8585
|
-
const rm = hooks.rm ?? ((dir) =>
|
|
8976
|
+
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(join22(tmpdir4(), "oh-remote-")));
|
|
8977
|
+
const rm = hooks.rm ?? ((dir) => rmSync7(dir, { recursive: true, force: true }));
|
|
8586
8978
|
const out = hooks.stdout ?? ((s) => process.stdout.write(s));
|
|
8587
8979
|
const checkoutDir = mkdtemp();
|
|
8588
8980
|
try {
|
|
@@ -8811,6 +9203,14 @@ async function main(argv) {
|
|
|
8811
9203
|
}
|
|
8812
9204
|
return runEgress(a.verb, a.name === void 0 ? { bin } : { bin, name: a.name }, lifecycleIo());
|
|
8813
9205
|
}
|
|
9206
|
+
if (first === "auth") {
|
|
9207
|
+
return await runAuth(
|
|
9208
|
+
argv.slice(1),
|
|
9209
|
+
{ stdout: (s) => process.stdout.write(s), stderr: (s) => process.stderr.write(s) },
|
|
9210
|
+
{},
|
|
9211
|
+
bin
|
|
9212
|
+
);
|
|
9213
|
+
}
|
|
8814
9214
|
if (first === "node") {
|
|
8815
9215
|
return await runNode(
|
|
8816
9216
|
argv.slice(1),
|