@mifune/agro 0.23.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 +481 -87
- 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,36 +5155,103 @@ 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;
|
|
5163
5237
|
var NODE_FAST_POLL_INTERVAL_MS = 1e3;
|
|
5164
5238
|
var NODE_FAST_POLL_WINDOW_MS = 30 * 1e3;
|
|
5165
5239
|
var NODE_POLL_INTERVAL_MS = 5e3;
|
|
5166
5240
|
var FREE_TIER_SPEC = "n4";
|
|
5241
|
+
var LOCAL_SSH_KEY_MISSING = "no --ssh-key given and ~/.ssh/id_ed25519.pub not found";
|
|
5167
5242
|
var FAILURE_STATUSES = /* @__PURE__ */ new Set(["failed", "destroy_requested", "destroying", "destroyed", "retired"]);
|
|
5168
|
-
function resolveConsoleConfig(env,
|
|
5169
|
-
const
|
|
5170
|
-
const
|
|
5171
|
-
|
|
5172
|
-
const fromSecret = readSecret2(key)?.trim();
|
|
5173
|
-
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;
|
|
5174
5247
|
};
|
|
5175
|
-
const
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
return readSecret(resolveProjectRoot(), key);
|
|
5183
|
-
} catch {
|
|
5184
|
-
return void 0;
|
|
5185
|
-
}
|
|
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" };
|
|
5186
5255
|
}
|
|
5187
5256
|
function nodePollDelayMs(elapsedMs) {
|
|
5188
5257
|
return elapsedMs < NODE_FAST_POLL_WINDOW_MS ? NODE_FAST_POLL_INTERVAL_MS : NODE_POLL_INTERVAL_MS;
|
|
@@ -5191,7 +5260,16 @@ function defaultDeps2() {
|
|
|
5191
5260
|
return {
|
|
5192
5261
|
fetch: (input, init) => globalThis.fetch(input, init),
|
|
5193
5262
|
env: process.env,
|
|
5194
|
-
|
|
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
|
+
},
|
|
5195
5273
|
sleep: (ms) => new Promise((done) => setTimeout(done, ms)),
|
|
5196
5274
|
now: () => Date.now(),
|
|
5197
5275
|
pollDelayMs: nodePollDelayMs,
|
|
@@ -5203,15 +5281,16 @@ function nodeHelp(bin) {
|
|
|
5203
5281
|
return `${bin} node \u2014 Create, list, and delete agro-console sandbox nodes (remote VMs)
|
|
5204
5282
|
|
|
5205
5283
|
Usage:
|
|
5206
|
-
${bin} node create --spec <spec>
|
|
5284
|
+
${bin} node create --spec <spec> [--ssh-key <uuid>]... [--name <name>] [--free-tier] [--json]
|
|
5207
5285
|
${bin} node list [--json]
|
|
5208
5286
|
${bin} node delete <id> [--yes]
|
|
5209
5287
|
|
|
5210
5288
|
\`create\` waits up to 15 minutes for the node to reach \`running\`, then prints
|
|
5211
5289
|
its SSH command. \`--free-tier\` requests the console free tier. The spec
|
|
5212
|
-
defaults to n4.
|
|
5213
|
-
|
|
5214
|
-
|
|
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}.
|
|
5215
5294
|
`;
|
|
5216
5295
|
}
|
|
5217
5296
|
function asRecord(value) {
|
|
@@ -5298,9 +5377,40 @@ function parseCreate(tokens) {
|
|
|
5298
5377
|
}
|
|
5299
5378
|
if (args.spec === void 0 && args.freeTier) args.spec = FREE_TIER_SPEC;
|
|
5300
5379
|
if (args.spec === void 0) return { ok: false, error: "--spec is required" };
|
|
5301
|
-
if (args.sshKeyIds.length === 0) return { ok: false, error: "at least one --ssh-key is required" };
|
|
5302
5380
|
return { ok: true, args };
|
|
5303
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
|
+
}
|
|
5304
5414
|
function printCreateResult(io, node, json) {
|
|
5305
5415
|
const ssh = node.status === "running" ? sshCommand(node) : null;
|
|
5306
5416
|
if (json) {
|
|
@@ -5321,7 +5431,17 @@ async function runCreate(tokens, io, deps, config, bin) {
|
|
|
5321
5431
|
`);
|
|
5322
5432
|
return 1;
|
|
5323
5433
|
}
|
|
5324
|
-
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
|
+
}
|
|
5325
5445
|
const requestedAt = deps.now();
|
|
5326
5446
|
const created = await callApi(deps, config, "POST", "/api/nodes", {
|
|
5327
5447
|
profile: "sandbox",
|
|
@@ -5463,9 +5583,9 @@ async function runNode(args, io, overrides = {}, bin = "agro") {
|
|
|
5463
5583
|
return 0;
|
|
5464
5584
|
}
|
|
5465
5585
|
const deps = { ...defaultDeps2(), ...overrides };
|
|
5466
|
-
const config = resolveConsoleConfig(deps.env, deps.
|
|
5586
|
+
const config = resolveConsoleConfig(deps.env, deps.readCredentials);
|
|
5467
5587
|
if (config === void 0) {
|
|
5468
|
-
io.stderr(`${
|
|
5588
|
+
io.stderr(`${CONSOLE_CONFIG_MISSING}
|
|
5469
5589
|
`);
|
|
5470
5590
|
return 1;
|
|
5471
5591
|
}
|
|
@@ -5474,10 +5594,250 @@ async function runNode(args, io, overrides = {}, bin = "agro") {
|
|
|
5474
5594
|
return runCreate(rest, io, deps, config, bin);
|
|
5475
5595
|
}
|
|
5476
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
|
+
|
|
5477
5837
|
// src/commands/harness.ts
|
|
5478
5838
|
import { existsSync as existsSync20 } from "node:fs";
|
|
5479
|
-
import { homedir as
|
|
5480
|
-
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";
|
|
5481
5841
|
|
|
5482
5842
|
// src/lib/docs.ts
|
|
5483
5843
|
var SOURCE_DOCS_BASE = "https://github.com/mifunedev/agro/blob/main/";
|
|
@@ -5490,8 +5850,8 @@ function sourceDocsUrl(docsPath) {
|
|
|
5490
5850
|
}
|
|
5491
5851
|
|
|
5492
5852
|
// src/lib/host-config.ts
|
|
5493
|
-
import { existsSync as existsSync18, mkdirSync as
|
|
5494
|
-
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";
|
|
5495
5855
|
var HOST_CONFIG_MODE = 420;
|
|
5496
5856
|
var HOST_CONFIG_FILE = "config.json";
|
|
5497
5857
|
var WORKSPACES_SUBDIR = "workspaces";
|
|
@@ -5503,7 +5863,7 @@ function hostStateHome(env, home) {
|
|
|
5503
5863
|
return resolveUserStateHome(env, home);
|
|
5504
5864
|
}
|
|
5505
5865
|
function hostConfigPath(env, home) {
|
|
5506
|
-
return
|
|
5866
|
+
return join17(hostStateHome(env, home), HOST_CONFIG_FILE);
|
|
5507
5867
|
}
|
|
5508
5868
|
function assertWorkspaceName(name) {
|
|
5509
5869
|
if (!SANDBOX_NAME_PATTERN.test(name)) {
|
|
@@ -5513,14 +5873,14 @@ function assertWorkspaceName(name) {
|
|
|
5513
5873
|
}
|
|
5514
5874
|
}
|
|
5515
5875
|
function workspacesRoot(env, home) {
|
|
5516
|
-
return
|
|
5876
|
+
return join17(hostStateHome(env, home), WORKSPACES_SUBDIR);
|
|
5517
5877
|
}
|
|
5518
5878
|
function workspaceRoot(name, env, home) {
|
|
5519
5879
|
assertWorkspaceName(name);
|
|
5520
|
-
return
|
|
5880
|
+
return join17(workspacesRoot(env, home), name);
|
|
5521
5881
|
}
|
|
5522
5882
|
function defaultHarnessRoot(env, home) {
|
|
5523
|
-
return
|
|
5883
|
+
return join17(workspacesRoot(env, home), DEFAULT_WORKSPACE_NAME);
|
|
5524
5884
|
}
|
|
5525
5885
|
function validateHostConfig(value) {
|
|
5526
5886
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -5588,7 +5948,7 @@ function readHostConfig(env, home) {
|
|
|
5588
5948
|
if (!existsSync18(path4)) return { version: 1 };
|
|
5589
5949
|
let raw;
|
|
5590
5950
|
try {
|
|
5591
|
-
raw =
|
|
5951
|
+
raw = readFileSync14(path4, "utf8");
|
|
5592
5952
|
} catch (error) {
|
|
5593
5953
|
const detail = error instanceof Error ? error.message : String(error);
|
|
5594
5954
|
throw new Error(`could not read ${path4}: ${detail}`);
|
|
@@ -5607,9 +5967,9 @@ function writeHostConfig(config, env, home) {
|
|
|
5607
5967
|
const body = `${JSON.stringify(validated, null, 2)}
|
|
5608
5968
|
`;
|
|
5609
5969
|
const tmp = `${path4}.tmp.${process.pid}`;
|
|
5610
|
-
|
|
5970
|
+
mkdirSync10(dirname9(path4), { recursive: true });
|
|
5611
5971
|
try {
|
|
5612
|
-
|
|
5972
|
+
writeFileSync10(tmp, body, { mode: HOST_CONFIG_MODE, encoding: "utf8" });
|
|
5613
5973
|
renameSync4(tmp, path4);
|
|
5614
5974
|
} catch (error) {
|
|
5615
5975
|
try {
|
|
@@ -5636,8 +5996,8 @@ function fieldError2(path4, requirement) {
|
|
|
5636
5996
|
}
|
|
5637
5997
|
|
|
5638
5998
|
// src/lib/host-workspace.ts
|
|
5639
|
-
import { existsSync as existsSync19, mkdirSync as
|
|
5640
|
-
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";
|
|
5641
6001
|
var AGRO_REPO_URL = "https://github.com/mifunedev/agro.git";
|
|
5642
6002
|
var IGNORABLE_ENTRIES = [
|
|
5643
6003
|
REGISTRY_SUBDIR,
|
|
@@ -5649,7 +6009,7 @@ function isDirectory3(path4) {
|
|
|
5649
6009
|
return statSync5(path4, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
5650
6010
|
}
|
|
5651
6011
|
function stateHomes(home) {
|
|
5652
|
-
return STATE_HOME_DIRS.map((name) =>
|
|
6012
|
+
return STATE_HOME_DIRS.map((name) => join18(home, name));
|
|
5653
6013
|
}
|
|
5654
6014
|
function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
5655
6015
|
const target = resolve11(root);
|
|
@@ -5657,7 +6017,7 @@ function stateHomeRootRefusal(bin, root, home, verb = "harness") {
|
|
|
5657
6017
|
if (enclosing === void 0) return void 0;
|
|
5658
6018
|
return `${bin} ${verb}: the ${verb} root ${target} is the state home ${enclosing} itself.
|
|
5659
6019
|
A workspace there collides with AGRO state and blocks every command run from ${home}.
|
|
5660
|
-
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.
|
|
5661
6021
|
`;
|
|
5662
6022
|
}
|
|
5663
6023
|
function isIgnorableEntry(name) {
|
|
@@ -5674,27 +6034,27 @@ function cloneInto(target, run, root, ref) {
|
|
|
5674
6034
|
assertCloned(run("git", ["clone", ...branch, AGRO_REPO_URL, target], { stdio: "inherit" }), root);
|
|
5675
6035
|
}
|
|
5676
6036
|
function cloneThroughStaging(root, run, ref) {
|
|
5677
|
-
const staging = mkdtempSync4(
|
|
6037
|
+
const staging = mkdtempSync4(join18(dirname10(root), "agro-clone-"));
|
|
5678
6038
|
try {
|
|
5679
|
-
const checkout =
|
|
6039
|
+
const checkout = join18(staging, "agro");
|
|
5680
6040
|
cloneInto(checkout, run, root, ref);
|
|
5681
6041
|
for (const name of readdirSync3(checkout)) {
|
|
5682
|
-
const destination =
|
|
6042
|
+
const destination = join18(root, name);
|
|
5683
6043
|
if (existsSync19(destination)) {
|
|
5684
6044
|
throw new Error(`could not move ${name} into ${root} \u2014 the path already exists`);
|
|
5685
6045
|
}
|
|
5686
|
-
renameSync5(
|
|
6046
|
+
renameSync5(join18(checkout, name), destination);
|
|
5687
6047
|
}
|
|
5688
6048
|
} finally {
|
|
5689
|
-
|
|
6049
|
+
rmSync5(staging, { recursive: true, force: true });
|
|
5690
6050
|
}
|
|
5691
6051
|
}
|
|
5692
6052
|
function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
5693
6053
|
const root = resolve11(path4);
|
|
5694
|
-
if (existsSync19(
|
|
6054
|
+
if (existsSync19(join18(root, ".git"))) return { root, action: "reused" };
|
|
5695
6055
|
const stats = statSync5(root, { throwIfNoEntry: false });
|
|
5696
6056
|
if (stats === void 0) {
|
|
5697
|
-
|
|
6057
|
+
mkdirSync11(dirname10(root), { recursive: true });
|
|
5698
6058
|
cloneInto(root, run, root, ref);
|
|
5699
6059
|
return { root, action: "cloned" };
|
|
5700
6060
|
}
|
|
@@ -5713,11 +6073,11 @@ function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
|
5713
6073
|
function listHostWorkspaces(env, home) {
|
|
5714
6074
|
const root = workspacesRoot(env, home);
|
|
5715
6075
|
if (!isDirectory3(root)) return [];
|
|
5716
|
-
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();
|
|
5717
6077
|
}
|
|
5718
6078
|
function resolveExistingWorkspace(bin, verb, path4, env, home) {
|
|
5719
6079
|
const root = resolve11(path4);
|
|
5720
|
-
if (existsSync19(
|
|
6080
|
+
if (existsSync19(join18(root, ".git"))) return { ok: true, root };
|
|
5721
6081
|
const existing = listHostWorkspaces(env, home);
|
|
5722
6082
|
const inventory = existing.length === 0 ? "No host workspace exists yet.\n" : `Workspaces that exist: ${existing.join(", ")}
|
|
5723
6083
|
`;
|
|
@@ -5754,7 +6114,7 @@ function targetFor(root, cwd, run, env) {
|
|
|
5754
6114
|
});
|
|
5755
6115
|
}
|
|
5756
6116
|
function hostPrefix(home) {
|
|
5757
|
-
return
|
|
6117
|
+
return join19(home, ".local");
|
|
5758
6118
|
}
|
|
5759
6119
|
function pathEntries(env) {
|
|
5760
6120
|
return (env.PATH ?? "").split(delimiter2).filter((part) => part !== "");
|
|
@@ -5801,7 +6161,7 @@ async function sandboxMarkerExists(target, entry) {
|
|
|
5801
6161
|
return r.exitCode === 0;
|
|
5802
6162
|
}
|
|
5803
6163
|
function hostBinaryUnderPrefix(entry, prefix) {
|
|
5804
|
-
return existsSync20(
|
|
6164
|
+
return existsSync20(join19(harnessBinPath(prefix), entry.binary));
|
|
5805
6165
|
}
|
|
5806
6166
|
function stateOf(entry, installed, location) {
|
|
5807
6167
|
return {
|
|
@@ -5822,7 +6182,7 @@ function probeableHost(env, home) {
|
|
|
5822
6182
|
return void 0;
|
|
5823
6183
|
}
|
|
5824
6184
|
const prefix = hostPrefix(home);
|
|
5825
|
-
if (!existsSync20(
|
|
6185
|
+
if (!existsSync20(join19(root, ".git")) && !existsSync20(prefix)) return void 0;
|
|
5826
6186
|
return { root, prefix };
|
|
5827
6187
|
}
|
|
5828
6188
|
async function collectStates(root, cwd, run, home, env, only) {
|
|
@@ -6053,7 +6413,7 @@ Start it with \`${bin} sandbox\`, then re-run this command.
|
|
|
6053
6413
|
`;
|
|
6054
6414
|
}
|
|
6055
6415
|
function homeOf(opts) {
|
|
6056
|
-
return (opts.homedir ??
|
|
6416
|
+
return (opts.homedir ?? homedir5)();
|
|
6057
6417
|
}
|
|
6058
6418
|
function isInteractive(opts) {
|
|
6059
6419
|
return opts.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
@@ -6442,11 +6802,11 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
6442
6802
|
}
|
|
6443
6803
|
|
|
6444
6804
|
// src/commands/workspace.ts
|
|
6445
|
-
import { existsSync as existsSync21, rmSync as
|
|
6446
|
-
import { homedir as
|
|
6447
|
-
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";
|
|
6448
6808
|
function homeOf2(opts) {
|
|
6449
|
-
return (opts.homedir ??
|
|
6809
|
+
return (opts.homedir ?? homedir6)();
|
|
6450
6810
|
}
|
|
6451
6811
|
function messageOf2(error) {
|
|
6452
6812
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -6492,7 +6852,7 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
6492
6852
|
let action;
|
|
6493
6853
|
const preexisting = existsSync21(root);
|
|
6494
6854
|
try {
|
|
6495
|
-
if (!opts.json && !existsSync21(
|
|
6855
|
+
if (!opts.json && !existsSync21(join20(root, ".git"))) {
|
|
6496
6856
|
const at = opts.ref !== void 0 ? ` at ${opts.ref}` : "";
|
|
6497
6857
|
io.stdout(`cloning ${AGRO_REPO_URL}${at} into ${root}\u2026
|
|
6498
6858
|
`);
|
|
@@ -6502,7 +6862,7 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
6502
6862
|
action = created.action;
|
|
6503
6863
|
} catch (err) {
|
|
6504
6864
|
if (opts.ref !== void 0) {
|
|
6505
|
-
if (!preexisting)
|
|
6865
|
+
if (!preexisting) rmSync6(root, { recursive: true, force: true });
|
|
6506
6866
|
io.stderr(
|
|
6507
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)})
|
|
6508
6868
|
`
|
|
@@ -6537,7 +6897,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
6537
6897
|
const recorded = recordedRoot(env, home);
|
|
6538
6898
|
const registry = workspacesRoot(env, home);
|
|
6539
6899
|
rows = listHostWorkspaces(env, home).map((name) => {
|
|
6540
|
-
const root =
|
|
6900
|
+
const root = join20(registry, name);
|
|
6541
6901
|
return { name, root, default: recorded === root };
|
|
6542
6902
|
});
|
|
6543
6903
|
} catch (err) {
|
|
@@ -6563,8 +6923,8 @@ async function runWorkspaceList(opts, io) {
|
|
|
6563
6923
|
|
|
6564
6924
|
// src/commands/tool.ts
|
|
6565
6925
|
import { existsSync as existsSync22 } from "node:fs";
|
|
6566
|
-
import { homedir as
|
|
6567
|
-
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";
|
|
6568
6928
|
|
|
6569
6929
|
// src/lib/tools/catalog.ts
|
|
6570
6930
|
var TOOLS_DOC = "docs/installation.md";
|
|
@@ -6607,22 +6967,47 @@ var TOOL_CATALOG = Object.freeze([
|
|
|
6607
6967
|
' browser="$resolved"',
|
|
6608
6968
|
" break",
|
|
6609
6969
|
"done",
|
|
6610
|
-
'
|
|
6611
|
-
'
|
|
6612
|
-
' echo "
|
|
6613
|
-
'
|
|
6614
|
-
|
|
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",
|
|
6615
6996
|
" exit 1",
|
|
6616
6997
|
"fi",
|
|
6617
|
-
'echo "agent-browser
|
|
6618
|
-
'"$prefix/bin/agent-browser" --version >/dev/null'
|
|
6998
|
+
'echo "agent-browser launched headless Chrome"'
|
|
6619
6999
|
].join("\n")
|
|
6620
7000
|
]),
|
|
6621
7001
|
installUser: "sandbox",
|
|
6622
7002
|
downloadSize: "~1 GB",
|
|
7003
|
+
hostDownloadSize: "~400 MB",
|
|
6623
7004
|
hostCapable: true,
|
|
6624
7005
|
uninstallArgv: Object.freeze(["bash", "-lc", "pnpm remove -g agent-browser"]),
|
|
6625
|
-
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
|
+
]),
|
|
6626
7011
|
docsPath: TOOLS_DOC
|
|
6627
7012
|
}),
|
|
6628
7013
|
Object.freeze({
|
|
@@ -7043,7 +7428,7 @@ function targetFor2(root, run, env) {
|
|
|
7043
7428
|
});
|
|
7044
7429
|
}
|
|
7045
7430
|
function hostPrefix2(home) {
|
|
7046
|
-
return
|
|
7431
|
+
return join21(home, ".local");
|
|
7047
7432
|
}
|
|
7048
7433
|
function sandboxMarkerPath2(entry) {
|
|
7049
7434
|
return `${SANDBOX_HARNESS_PREFIX}/share/agro/tools/${entry.id}.installed`;
|
|
@@ -7057,7 +7442,7 @@ async function sandboxMarkerExists2(target, entry) {
|
|
|
7057
7442
|
return r.exitCode === 0;
|
|
7058
7443
|
}
|
|
7059
7444
|
function hostBinaryUnderPrefix2(entry, prefix) {
|
|
7060
|
-
return existsSync22(
|
|
7445
|
+
return existsSync22(join21(harnessBinPath(prefix), entry.binary));
|
|
7061
7446
|
}
|
|
7062
7447
|
function pathEntries2(env) {
|
|
7063
7448
|
return (env.PATH ?? "").split(delimiter3).filter((part) => part !== "");
|
|
@@ -7077,7 +7462,7 @@ function messageOf3(error) {
|
|
|
7077
7462
|
return error instanceof Error ? error.message : String(error);
|
|
7078
7463
|
}
|
|
7079
7464
|
function homeOf3(opts) {
|
|
7080
|
-
return (opts.homedir ??
|
|
7465
|
+
return (opts.homedir ?? homedir7)();
|
|
7081
7466
|
}
|
|
7082
7467
|
function platformOf(opts) {
|
|
7083
7468
|
return opts.platform ?? process.platform;
|
|
@@ -7137,7 +7522,7 @@ function probeableHost2(env, home) {
|
|
|
7137
7522
|
return void 0;
|
|
7138
7523
|
}
|
|
7139
7524
|
const prefix = hostPrefix2(home);
|
|
7140
|
-
if (!existsSync22(
|
|
7525
|
+
if (!existsSync22(join21(root, ".git")) && !existsSync22(prefix)) return void 0;
|
|
7141
7526
|
return { root, prefix };
|
|
7142
7527
|
}
|
|
7143
7528
|
async function collectRows(root, run, home, env, only) {
|
|
@@ -7675,7 +8060,7 @@ function fetchRemoteSource(opts) {
|
|
|
7675
8060
|
|
|
7676
8061
|
// src/cli.ts
|
|
7677
8062
|
var DEFAULT_SOURCE_CONTROL_DIR = resolve13(
|
|
7678
|
-
|
|
8063
|
+
dirname11(fileURLToPath(import.meta.url)),
|
|
7679
8064
|
"../.."
|
|
7680
8065
|
);
|
|
7681
8066
|
var INTEGRATIONS = {
|
|
@@ -7720,6 +8105,7 @@ Usage:
|
|
|
7720
8105
|
${bin} egress <args...> Read the egress proxy log or reload its allowlist (log|reload)
|
|
7721
8106
|
${bin} workspace <args...> Create and list host AGRO workspaces (create|list)
|
|
7722
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)
|
|
7723
8109
|
${bin} harness <args...> Install and inspect agent CLI harnesses
|
|
7724
8110
|
${bin} tool <args...> Install and inspect sandbox tooling
|
|
7725
8111
|
${bin} gateway <args...> Manage a messaging client session (pi|hermes|openclaw)
|
|
@@ -8555,7 +8941,7 @@ function parseGatewayArgs(rest) {
|
|
|
8555
8941
|
return { ok: true, args: { help: false, passthrough: [...rest] } };
|
|
8556
8942
|
}
|
|
8557
8943
|
function bundledPayloadExists(bundled, exists = existsSync23) {
|
|
8558
|
-
return exists(
|
|
8944
|
+
return exists(join22(bundled.sourceControlDir, "manifest.json"));
|
|
8559
8945
|
}
|
|
8560
8946
|
function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
8561
8947
|
const exists = bundled.exists ?? existsSync23;
|
|
@@ -8578,7 +8964,7 @@ function resolveVendorSource(args, bundled, bin = AGRO_PRODUCT.bin) {
|
|
|
8578
8964
|
function readPayloadVersion(checkoutDir) {
|
|
8579
8965
|
try {
|
|
8580
8966
|
const parsed = JSON.parse(
|
|
8581
|
-
|
|
8967
|
+
readFileSync15(join22(resolveControlDir(checkoutDir), "cli", "package.json"), "utf8")
|
|
8582
8968
|
);
|
|
8583
8969
|
if (parsed && typeof parsed.version === "string") return parsed.version;
|
|
8584
8970
|
} catch {
|
|
@@ -8587,8 +8973,8 @@ function readPayloadVersion(checkoutDir) {
|
|
|
8587
8973
|
}
|
|
8588
8974
|
async function runWithRemoteSource(hooks, fn) {
|
|
8589
8975
|
const fetch2 = hooks.fetch ?? fetchRemoteSource;
|
|
8590
|
-
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(
|
|
8591
|
-
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 }));
|
|
8592
8978
|
const out = hooks.stdout ?? ((s) => process.stdout.write(s));
|
|
8593
8979
|
const checkoutDir = mkdtemp();
|
|
8594
8980
|
try {
|
|
@@ -8817,6 +9203,14 @@ async function main(argv) {
|
|
|
8817
9203
|
}
|
|
8818
9204
|
return runEgress(a.verb, a.name === void 0 ? { bin } : { bin, name: a.name }, lifecycleIo());
|
|
8819
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
|
+
}
|
|
8820
9214
|
if (first === "node") {
|
|
8821
9215
|
return await runNode(
|
|
8822
9216
|
argv.slice(1),
|