@great-arrow/cli 0.9.2 → 0.9.4
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/index.js +380 -332
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9483,7 +9483,7 @@ function isNodeError(value) {
|
|
|
9483
9483
|
|
|
9484
9484
|
// src/lib/version.ts
|
|
9485
9485
|
var CLI_PACKAGE_NAME = "@great-arrow/cli";
|
|
9486
|
-
var CLI_VERSION = "0.9.
|
|
9486
|
+
var CLI_VERSION = "0.9.4";
|
|
9487
9487
|
var GAD_INSTALL_VERSION = "2026.08.08";
|
|
9488
9488
|
var GAD_INSTALL_MIN_VERSION = "2026.06.22";
|
|
9489
9489
|
var GAD_INSTALL_HEADER = "X-GAD-Install-Version";
|
|
@@ -10969,83 +10969,6 @@ function sessionExpired() {
|
|
|
10969
10969
|
return authError("Session expired.", "Run 'gad login' to re-authenticate.");
|
|
10970
10970
|
}
|
|
10971
10971
|
|
|
10972
|
-
// src/commands/auth.ts
|
|
10973
|
-
var EXIT_SESSION_EXPIRED = 2;
|
|
10974
|
-
var EXIT_NOT_OAUTH = 3;
|
|
10975
|
-
function authCommand() {
|
|
10976
|
-
const cmd = new Command("auth").description("Authentication helpers");
|
|
10977
|
-
cmd.command("token").description("Print a fresh OAuth access token, refreshing it if needed").option("--json", "Output {accessToken, accessExpiresAt, apiUrl} as JSON").action(async (options, actionCommand2) => {
|
|
10978
|
-
try {
|
|
10979
|
-
const t = await getFreshOAuthAccessToken();
|
|
10980
|
-
writeCliLocator();
|
|
10981
|
-
const jsonOutput = options.json || actionCommand2.optsWithGlobals().json;
|
|
10982
|
-
process.stdout.write(
|
|
10983
|
-
jsonOutput ? `${JSON.stringify(t)}
|
|
10984
|
-
` : `${t.accessToken}
|
|
10985
|
-
`
|
|
10986
|
-
);
|
|
10987
|
-
} catch (error62) {
|
|
10988
|
-
if (error62 instanceof NotOAuthCredential) {
|
|
10989
|
-
process.stderr.write(`${error62.message}
|
|
10990
|
-
`);
|
|
10991
|
-
process.exitCode = EXIT_NOT_OAUTH;
|
|
10992
|
-
return;
|
|
10993
|
-
}
|
|
10994
|
-
if (error62 instanceof CliError && error62.code === 1) {
|
|
10995
|
-
const hint = error62.hint ? ` ${error62.hint}` : "";
|
|
10996
|
-
process.stderr.write(`${error62.message}${hint}
|
|
10997
|
-
`);
|
|
10998
|
-
process.exitCode = EXIT_SESSION_EXPIRED;
|
|
10999
|
-
return;
|
|
11000
|
-
}
|
|
11001
|
-
throw error62;
|
|
11002
|
-
}
|
|
11003
|
-
});
|
|
11004
|
-
return cmd;
|
|
11005
|
-
}
|
|
11006
|
-
|
|
11007
|
-
// src/commands/logout.ts
|
|
11008
|
-
var logoutCommand = new Command("logout").description("Remove stored authentication credentials").action(async (_options, cmd) => {
|
|
11009
|
-
const parent = cmd.parent;
|
|
11010
|
-
const json3 = parent?.opts().json ?? false;
|
|
11011
|
-
const quiet = parent?.opts().quiet ?? false;
|
|
11012
|
-
const noColor = parent?.opts().noColor ?? false;
|
|
11013
|
-
const fmt = createFormatter({
|
|
11014
|
-
json: json3,
|
|
11015
|
-
quiet,
|
|
11016
|
-
isTTY: isTTY(),
|
|
11017
|
-
noColor
|
|
11018
|
-
});
|
|
11019
|
-
const stored = await loadStoredCredential();
|
|
11020
|
-
if (stored?.kind === "oauth") {
|
|
11021
|
-
await revokeToken({
|
|
11022
|
-
apiUrl: stored.credential.apiUrl,
|
|
11023
|
-
clientId: stored.credential.clientId,
|
|
11024
|
-
token: stored.credential.refreshToken,
|
|
11025
|
-
tokenTypeHint: "refresh_token"
|
|
11026
|
-
});
|
|
11027
|
-
}
|
|
11028
|
-
let clearError = null;
|
|
11029
|
-
try {
|
|
11030
|
-
await clearStoredCredential();
|
|
11031
|
-
} catch (err2) {
|
|
11032
|
-
clearError = err2;
|
|
11033
|
-
}
|
|
11034
|
-
const config2 = await loadConfig();
|
|
11035
|
-
if (config2.token) {
|
|
11036
|
-
config2.token = "";
|
|
11037
|
-
await saveConfig(config2);
|
|
11038
|
-
}
|
|
11039
|
-
if (clearError) {
|
|
11040
|
-
fmt.error(
|
|
11041
|
-
clearError instanceof Error ? clearError.message : "Could not remove stored authentication credentials."
|
|
11042
|
-
);
|
|
11043
|
-
process.exitCode = 1 /* AUTH_ERROR */;
|
|
11044
|
-
return;
|
|
11045
|
-
}
|
|
11046
|
-
fmt.success("Logged out successfully.");
|
|
11047
|
-
});
|
|
11048
|
-
|
|
11049
10972
|
// src/lib/client.ts
|
|
11050
10973
|
import { execSync } from "node:child_process";
|
|
11051
10974
|
|
|
@@ -11547,6 +11470,273 @@ async function withNativeResponse(context2, path10, init, consume, parentSignal)
|
|
|
11547
11470
|
);
|
|
11548
11471
|
}
|
|
11549
11472
|
|
|
11473
|
+
// src/lib/keys-handoff.ts
|
|
11474
|
+
import { spawn } from "node:child_process";
|
|
11475
|
+
|
|
11476
|
+
// src/commands/open.ts
|
|
11477
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
11478
|
+
|
|
11479
|
+
// src/lib/global-opts.ts
|
|
11480
|
+
function globalOutputOpts(cmd) {
|
|
11481
|
+
const merged = cmd.optsWithGlobals();
|
|
11482
|
+
return {
|
|
11483
|
+
json: Boolean(merged.json),
|
|
11484
|
+
quiet: Boolean(merged.quiet),
|
|
11485
|
+
// --no-color sets `color: false`; some commands declare `--no-color`
|
|
11486
|
+
// locally which surfaces as `noColor`.
|
|
11487
|
+
noColor: merged.color === false || Boolean(merged.noColor)
|
|
11488
|
+
};
|
|
11489
|
+
}
|
|
11490
|
+
|
|
11491
|
+
// src/commands/open.ts
|
|
11492
|
+
var PLANE_NOTE = {
|
|
11493
|
+
workspace: "follows your active workspace",
|
|
11494
|
+
cross: "spans every workspace you belong to",
|
|
11495
|
+
account: "your account, not a workspace"
|
|
11496
|
+
};
|
|
11497
|
+
function browserLaunchCommand(url2, platform2 = process.platform) {
|
|
11498
|
+
let parsed;
|
|
11499
|
+
try {
|
|
11500
|
+
parsed = new URL(url2);
|
|
11501
|
+
} catch {
|
|
11502
|
+
return null;
|
|
11503
|
+
}
|
|
11504
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
11505
|
+
if (platform2 === "darwin") return { file: "open", args: [url2] };
|
|
11506
|
+
if (platform2 === "win32") return { file: "explorer.exe", args: [url2] };
|
|
11507
|
+
return { file: "xdg-open", args: [url2] };
|
|
11508
|
+
}
|
|
11509
|
+
function openBrowser2(url2) {
|
|
11510
|
+
const command = browserLaunchCommand(url2);
|
|
11511
|
+
if (!command) return Promise.resolve(false);
|
|
11512
|
+
return new Promise((resolve7) => {
|
|
11513
|
+
execFile2(command.file, command.args, (error62) => resolve7(!error62));
|
|
11514
|
+
});
|
|
11515
|
+
}
|
|
11516
|
+
function toSurfaceRow(value) {
|
|
11517
|
+
if (typeof value !== "object" || value === null) return null;
|
|
11518
|
+
const r = value;
|
|
11519
|
+
if (typeof r.id !== "string" || typeof r.href !== "string") return null;
|
|
11520
|
+
if (!r.href.startsWith("/") || r.href.startsWith("//")) return null;
|
|
11521
|
+
const plane = r.plane === "workspace" || r.plane === "cross" || r.plane === "account" ? r.plane : "workspace";
|
|
11522
|
+
return {
|
|
11523
|
+
id: r.id,
|
|
11524
|
+
label: typeof r.label === "string" ? r.label : r.id,
|
|
11525
|
+
href: r.href,
|
|
11526
|
+
plane,
|
|
11527
|
+
group: typeof r.group === "string" ? r.group : ""
|
|
11528
|
+
};
|
|
11529
|
+
}
|
|
11530
|
+
function surfaceUrl(apiUrl, href) {
|
|
11531
|
+
return `${apiUrl.replace(/\/$/, "")}${href}`;
|
|
11532
|
+
}
|
|
11533
|
+
function openCommand() {
|
|
11534
|
+
return new Command("open").description("Open an app surface in the browser (e.g. gad open library)").argument(
|
|
11535
|
+
"<surface>",
|
|
11536
|
+
"Surface id, path, label or keyword \u2014 e.g. library, /leads, 'Pitch page'"
|
|
11537
|
+
).option("--print", "Print the URL instead of opening a browser").option("--json", "Output the resolved surface as JSON").action(async (surface, options, command) => {
|
|
11538
|
+
const g = globalOutputOpts(command);
|
|
11539
|
+
const auth = await resolveAuth();
|
|
11540
|
+
const client2 = new GadClient({
|
|
11541
|
+
token: auth.token,
|
|
11542
|
+
getToken: auth.getToken,
|
|
11543
|
+
apiUrl: auth.apiUrl
|
|
11544
|
+
});
|
|
11545
|
+
const result = await client2.mcpCall("surfaces_list", {
|
|
11546
|
+
query: surface
|
|
11547
|
+
});
|
|
11548
|
+
const match = toSurfaceRow(result?.match);
|
|
11549
|
+
const candidates = (Array.isArray(result?.candidates) ? result.candidates : []).map(toSurfaceRow).filter((r) => r !== null);
|
|
11550
|
+
if (!match) {
|
|
11551
|
+
throw new CliError(
|
|
11552
|
+
candidates.length === 0 ? `No app surface matches '${surface}'.` : `'${surface}' matches ${candidates.length} surfaces.`,
|
|
11553
|
+
3 /* VALIDATION_ERROR */,
|
|
11554
|
+
candidates.length === 0 ? "Run 'gad open --help', or ask an AI for surfaces_list to see every page." : `Did you mean: ${candidates.map((c2) => c2.id).join(", ")}?`
|
|
11555
|
+
);
|
|
11556
|
+
}
|
|
11557
|
+
const url2 = surfaceUrl(auth.apiUrl, match.href);
|
|
11558
|
+
if (g.json) {
|
|
11559
|
+
process.stdout.write(JSON.stringify({ ...match, url: url2 }) + "\n");
|
|
11560
|
+
return;
|
|
11561
|
+
}
|
|
11562
|
+
const c = createColor(g.noColor || g.quiet);
|
|
11563
|
+
if (options.print) {
|
|
11564
|
+
process.stdout.write(url2 + "\n");
|
|
11565
|
+
return;
|
|
11566
|
+
}
|
|
11567
|
+
const opened = await openBrowser2(url2);
|
|
11568
|
+
process.stdout.write(
|
|
11569
|
+
`${match.label} ${c.dim(`(${PLANE_NOTE[match.plane]})`)}
|
|
11570
|
+
${c.cyan(url2)}
|
|
11571
|
+
`
|
|
11572
|
+
);
|
|
11573
|
+
if (!opened) {
|
|
11574
|
+
process.stderr.write(
|
|
11575
|
+
`${c.dim("Couldn't launch a browser \u2014 open the URL above.")}
|
|
11576
|
+
`
|
|
11577
|
+
);
|
|
11578
|
+
}
|
|
11579
|
+
});
|
|
11580
|
+
}
|
|
11581
|
+
|
|
11582
|
+
// src/lib/keys-paths.ts
|
|
11583
|
+
function keyRotationPath() {
|
|
11584
|
+
return "/settings/key-rotation";
|
|
11585
|
+
}
|
|
11586
|
+
function keyRotationRunPath(runId) {
|
|
11587
|
+
return `/settings/key-rotation/runs/${encodeURIComponent(runId)}`;
|
|
11588
|
+
}
|
|
11589
|
+
function keyRotationHandoffPath(jti) {
|
|
11590
|
+
return `/settings/key-rotation/handoff/${encodeURIComponent(jti)}`;
|
|
11591
|
+
}
|
|
11592
|
+
|
|
11593
|
+
// src/lib/keys-handoff.ts
|
|
11594
|
+
var HANDOFF_POLL_INTERVAL_MS = 2e3;
|
|
11595
|
+
var HANDOFF_DEADLINE_MS = 5 * 6e4;
|
|
11596
|
+
var BROWSER_LAUNCH_TIMEOUT_MS = 5e3;
|
|
11597
|
+
function openInBrowser(url2) {
|
|
11598
|
+
const command = browserLaunchCommand(url2);
|
|
11599
|
+
if (!command) return Promise.resolve(false);
|
|
11600
|
+
return new Promise((resolve7) => {
|
|
11601
|
+
let settled = false;
|
|
11602
|
+
const done = (ok2) => {
|
|
11603
|
+
if (settled) return;
|
|
11604
|
+
settled = true;
|
|
11605
|
+
clearTimeout(timer);
|
|
11606
|
+
resolve7(ok2);
|
|
11607
|
+
};
|
|
11608
|
+
const timer = setTimeout(() => done(false), BROWSER_LAUNCH_TIMEOUT_MS);
|
|
11609
|
+
timer.unref?.();
|
|
11610
|
+
try {
|
|
11611
|
+
const child = spawn(command.file, command.args, {
|
|
11612
|
+
detached: true,
|
|
11613
|
+
stdio: "ignore"
|
|
11614
|
+
});
|
|
11615
|
+
child.once("error", () => done(false));
|
|
11616
|
+
child.once("spawn", () => {
|
|
11617
|
+
child.unref();
|
|
11618
|
+
done(true);
|
|
11619
|
+
});
|
|
11620
|
+
} catch {
|
|
11621
|
+
done(false);
|
|
11622
|
+
}
|
|
11623
|
+
});
|
|
11624
|
+
}
|
|
11625
|
+
var defaultSleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
11626
|
+
function stepUpError(message, hint) {
|
|
11627
|
+
return new CliError(message, 1 /* AUTH_ERROR */, hint);
|
|
11628
|
+
}
|
|
11629
|
+
async function requestBrowserStepUp(client2, kind, subjectId, opts = {}) {
|
|
11630
|
+
const openUrl = opts.openUrl ?? openInBrowser;
|
|
11631
|
+
const notify = opts.notify ?? ((line) => process.stderr.write(`${line}
|
|
11632
|
+
`));
|
|
11633
|
+
const sleep3 = opts.sleep ?? defaultSleep;
|
|
11634
|
+
const now = opts.now ?? Date.now;
|
|
11635
|
+
const interval = opts.pollIntervalMs ?? HANDOFF_POLL_INTERVAL_MS;
|
|
11636
|
+
const deadline = now() + (opts.deadlineMs ?? HANDOFF_DEADLINE_MS);
|
|
11637
|
+
const created = await client2.post("/api/key-rotation/handoffs", {
|
|
11638
|
+
kind,
|
|
11639
|
+
subjectId
|
|
11640
|
+
});
|
|
11641
|
+
const jti = typeof created?.jti === "string" ? created.jti : null;
|
|
11642
|
+
if (!jti || !/^[A-Za-z0-9-]{1,64}$/.test(jti)) {
|
|
11643
|
+
throw stepUpError("The server did not return a browser approval.");
|
|
11644
|
+
}
|
|
11645
|
+
const url2 = `${client2.apiOrigin}${keyRotationHandoffPath(jti)}`;
|
|
11646
|
+
notify(`Approve this in your browser (passkey or two-step code): ${url2}`);
|
|
11647
|
+
if (!await openUrl(url2)) {
|
|
11648
|
+
notify("Couldn't launch a browser. Open the URL above to approve.");
|
|
11649
|
+
}
|
|
11650
|
+
while (now() < deadline) {
|
|
11651
|
+
const polled = await client2.get(
|
|
11652
|
+
`/api/key-rotation/handoffs/${encodeURIComponent(jti)}`
|
|
11653
|
+
);
|
|
11654
|
+
const status = polled?.status;
|
|
11655
|
+
if (status === "approved") {
|
|
11656
|
+
if (typeof polled.token === "string" && polled.token.length > 0) {
|
|
11657
|
+
return polled.token;
|
|
11658
|
+
}
|
|
11659
|
+
throw stepUpError(
|
|
11660
|
+
"This approval was already collected by another request.",
|
|
11661
|
+
"Run the command again to request a new approval."
|
|
11662
|
+
);
|
|
11663
|
+
}
|
|
11664
|
+
if (status === "consumed") {
|
|
11665
|
+
throw stepUpError(
|
|
11666
|
+
"This approval was already used.",
|
|
11667
|
+
"Run the command again to request a new approval."
|
|
11668
|
+
);
|
|
11669
|
+
}
|
|
11670
|
+
if (status === "expired") {
|
|
11671
|
+
throw stepUpError(
|
|
11672
|
+
"The browser approval expired.",
|
|
11673
|
+
"Run the command again and approve within 5 minutes."
|
|
11674
|
+
);
|
|
11675
|
+
}
|
|
11676
|
+
await sleep3(interval);
|
|
11677
|
+
}
|
|
11678
|
+
throw stepUpError(
|
|
11679
|
+
"Timed out waiting for the browser approval.",
|
|
11680
|
+
"Run the command again and approve within 5 minutes."
|
|
11681
|
+
);
|
|
11682
|
+
}
|
|
11683
|
+
|
|
11684
|
+
// src/lib/admin-access.ts
|
|
11685
|
+
async function requestAdminAccess(client2, apiOrigin, opts = {}) {
|
|
11686
|
+
const now = opts.now ?? Date.now;
|
|
11687
|
+
const deadline = now() + (opts.deadlineMs ?? 5 * 6e4);
|
|
11688
|
+
const notify = opts.notify ?? ((line) => process.stderr.write(`${line}
|
|
11689
|
+
`));
|
|
11690
|
+
const created = await client2.requestOnce("POST", "/api/admin/access", {
|
|
11691
|
+
timeoutMs: 15e3
|
|
11692
|
+
});
|
|
11693
|
+
const request = created.body;
|
|
11694
|
+
if (created.status !== 200 || !request?.requestId || !/^[a-f0-9-]{36}$/.test(request.requestId))
|
|
11695
|
+
throw new CliError(
|
|
11696
|
+
"Could not request admin approval.",
|
|
11697
|
+
1 /* AUTH_ERROR */,
|
|
11698
|
+
"Check this connection has admin scope and belongs to a super admin."
|
|
11699
|
+
);
|
|
11700
|
+
if (request.status === "approved") return;
|
|
11701
|
+
if (request.status !== "pending")
|
|
11702
|
+
throw new CliError("Admin approval is unavailable.", 1 /* AUTH_ERROR */);
|
|
11703
|
+
const url2 = new URL(`/admin/compliance/audit/${request.requestId}`, apiOrigin).href;
|
|
11704
|
+
notify(`Approve 15-minute admin access with your passkey: ${url2}`);
|
|
11705
|
+
if (!await (opts.openUrl ?? openInBrowser)(url2))
|
|
11706
|
+
notify("Open the link above in your browser to approve.");
|
|
11707
|
+
const sleep3 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
11708
|
+
while (now() < deadline) {
|
|
11709
|
+
const result = await client2.requestOnce("GET", "/api/admin/access", {
|
|
11710
|
+
params: { requestId: request.requestId },
|
|
11711
|
+
timeoutMs: 15e3
|
|
11712
|
+
});
|
|
11713
|
+
const status = result.body;
|
|
11714
|
+
if (result.status !== 200)
|
|
11715
|
+
throw new CliError(
|
|
11716
|
+
"Admin approval could not be checked.",
|
|
11717
|
+
1 /* AUTH_ERROR */,
|
|
11718
|
+
"The connection may have changed. Run the command again."
|
|
11719
|
+
);
|
|
11720
|
+
if (status?.status === "approved") {
|
|
11721
|
+
notify(
|
|
11722
|
+
`Admin access approved until ${status.expiresAt}. No key was copied or saved.`
|
|
11723
|
+
);
|
|
11724
|
+
return;
|
|
11725
|
+
}
|
|
11726
|
+
if (status?.status !== "pending")
|
|
11727
|
+
throw new CliError(
|
|
11728
|
+
"Admin approval was denied or expired.",
|
|
11729
|
+
1 /* AUTH_ERROR */
|
|
11730
|
+
);
|
|
11731
|
+
await sleep3(opts.pollIntervalMs ?? 2e3);
|
|
11732
|
+
}
|
|
11733
|
+
throw new CliError(
|
|
11734
|
+
"Timed out waiting for admin approval.",
|
|
11735
|
+
1 /* AUTH_ERROR */,
|
|
11736
|
+
"Run the command again to request a new approval."
|
|
11737
|
+
);
|
|
11738
|
+
}
|
|
11739
|
+
|
|
11550
11740
|
// src/lib/client.ts
|
|
11551
11741
|
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
11552
11742
|
var PRESENCE_TIMEOUT_MS = 4e4;
|
|
@@ -11811,6 +12001,16 @@ var GadClient = class {
|
|
|
11811
12001
|
* @returns The decoded JSON payload from the tool result content
|
|
11812
12002
|
*/
|
|
11813
12003
|
async mcpCall(toolName, args, timeoutMs) {
|
|
12004
|
+
try {
|
|
12005
|
+
return await this.mcpCallOnce(toolName, args, timeoutMs);
|
|
12006
|
+
} catch (error62) {
|
|
12007
|
+
if (!(error62 instanceof McpToolError) || !["step_up_required", "needs_grant"].includes(error62.toolCode))
|
|
12008
|
+
throw error62;
|
|
12009
|
+
await requestAdminAccess(this, this.apiUrl);
|
|
12010
|
+
return this.mcpCallOnce(toolName, args, timeoutMs);
|
|
12011
|
+
}
|
|
12012
|
+
}
|
|
12013
|
+
async mcpCallOnce(toolName, args, timeoutMs) {
|
|
11814
12014
|
const url2 = `${this.apiUrl}/api/mcp`;
|
|
11815
12015
|
const body = JSON.stringify({
|
|
11816
12016
|
jsonrpc: "2.0",
|
|
@@ -12101,18 +12301,94 @@ var GadClient = class {
|
|
|
12101
12301
|
}
|
|
12102
12302
|
};
|
|
12103
12303
|
|
|
12104
|
-
// src/
|
|
12105
|
-
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12304
|
+
// src/commands/auth.ts
|
|
12305
|
+
var EXIT_SESSION_EXPIRED = 2;
|
|
12306
|
+
var EXIT_NOT_OAUTH = 3;
|
|
12307
|
+
function authCommand() {
|
|
12308
|
+
const cmd = new Command("auth").description("Authentication helpers");
|
|
12309
|
+
cmd.command("admin").description(
|
|
12310
|
+
"Approve 15-minute admin access in your browser; no key is printed"
|
|
12311
|
+
).action(async () => {
|
|
12312
|
+
const auth = await resolveAuth();
|
|
12313
|
+
const client2 = new GadClient({
|
|
12314
|
+
token: auth.token,
|
|
12315
|
+
getToken: auth.getToken,
|
|
12316
|
+
apiUrl: auth.apiUrl
|
|
12317
|
+
});
|
|
12318
|
+
await requestAdminAccess(client2, auth.apiUrl);
|
|
12319
|
+
});
|
|
12320
|
+
cmd.command("token").description("Print a fresh OAuth access token, refreshing it if needed").option("--json", "Output {accessToken, accessExpiresAt, apiUrl} as JSON").action(async (options, actionCommand2) => {
|
|
12321
|
+
try {
|
|
12322
|
+
const t = await getFreshOAuthAccessToken();
|
|
12323
|
+
writeCliLocator();
|
|
12324
|
+
const jsonOutput = options.json || actionCommand2.optsWithGlobals().json;
|
|
12325
|
+
process.stdout.write(
|
|
12326
|
+
jsonOutput ? `${JSON.stringify(t)}
|
|
12327
|
+
` : `${t.accessToken}
|
|
12328
|
+
`
|
|
12329
|
+
);
|
|
12330
|
+
} catch (error62) {
|
|
12331
|
+
if (error62 instanceof NotOAuthCredential) {
|
|
12332
|
+
process.stderr.write(`${error62.message}
|
|
12333
|
+
`);
|
|
12334
|
+
process.exitCode = EXIT_NOT_OAUTH;
|
|
12335
|
+
return;
|
|
12336
|
+
}
|
|
12337
|
+
if (error62 instanceof CliError && error62.code === 1) {
|
|
12338
|
+
const hint = error62.hint ? ` ${error62.hint}` : "";
|
|
12339
|
+
process.stderr.write(`${error62.message}${hint}
|
|
12340
|
+
`);
|
|
12341
|
+
process.exitCode = EXIT_SESSION_EXPIRED;
|
|
12342
|
+
return;
|
|
12343
|
+
}
|
|
12344
|
+
throw error62;
|
|
12345
|
+
}
|
|
12346
|
+
});
|
|
12347
|
+
return cmd;
|
|
12114
12348
|
}
|
|
12115
12349
|
|
|
12350
|
+
// src/commands/logout.ts
|
|
12351
|
+
var logoutCommand = new Command("logout").description("Remove stored authentication credentials").action(async (_options, cmd) => {
|
|
12352
|
+
const parent = cmd.parent;
|
|
12353
|
+
const json3 = parent?.opts().json ?? false;
|
|
12354
|
+
const quiet = parent?.opts().quiet ?? false;
|
|
12355
|
+
const noColor = parent?.opts().noColor ?? false;
|
|
12356
|
+
const fmt = createFormatter({
|
|
12357
|
+
json: json3,
|
|
12358
|
+
quiet,
|
|
12359
|
+
isTTY: isTTY(),
|
|
12360
|
+
noColor
|
|
12361
|
+
});
|
|
12362
|
+
const stored = await loadStoredCredential();
|
|
12363
|
+
if (stored?.kind === "oauth") {
|
|
12364
|
+
await revokeToken({
|
|
12365
|
+
apiUrl: stored.credential.apiUrl,
|
|
12366
|
+
clientId: stored.credential.clientId,
|
|
12367
|
+
token: stored.credential.refreshToken,
|
|
12368
|
+
tokenTypeHint: "refresh_token"
|
|
12369
|
+
});
|
|
12370
|
+
}
|
|
12371
|
+
let clearError = null;
|
|
12372
|
+
try {
|
|
12373
|
+
await clearStoredCredential();
|
|
12374
|
+
} catch (err2) {
|
|
12375
|
+
clearError = err2;
|
|
12376
|
+
}
|
|
12377
|
+
const config2 = await loadConfig();
|
|
12378
|
+
if (config2.token) {
|
|
12379
|
+
config2.token = "";
|
|
12380
|
+
await saveConfig(config2);
|
|
12381
|
+
}
|
|
12382
|
+
if (clearError) {
|
|
12383
|
+
fmt.error(
|
|
12384
|
+
clearError instanceof Error ? clearError.message : "Could not remove stored authentication credentials."
|
|
12385
|
+
);
|
|
12386
|
+
process.exitCode = 1 /* AUTH_ERROR */;
|
|
12387
|
+
return;
|
|
12388
|
+
}
|
|
12389
|
+
fmt.success("Logged out successfully.");
|
|
12390
|
+
});
|
|
12391
|
+
|
|
12116
12392
|
// src/commands/whoami.ts
|
|
12117
12393
|
function firstThingsToTry(data) {
|
|
12118
12394
|
const o = data.onboarding;
|
|
@@ -13079,7 +13355,7 @@ var claim_default = claimCommand;
|
|
|
13079
13355
|
import { readFileSync } from "node:fs";
|
|
13080
13356
|
|
|
13081
13357
|
// src/lib/work-status.ts
|
|
13082
|
-
import { execFile as
|
|
13358
|
+
import { execFile as execFile3, execFileSync as execFileSync2 } from "node:child_process";
|
|
13083
13359
|
|
|
13084
13360
|
// ../../src/lib/work-risk/push-state.ts
|
|
13085
13361
|
var REMOTE_BRANCH_REF_PREFIX = "refs/remotes/origin/";
|
|
@@ -13131,7 +13407,7 @@ var WORKTREE_STATUS_CONCURRENCY = 8;
|
|
|
13131
13407
|
var WORKTREE_STATUS_TIMEOUT_MS = 2e3;
|
|
13132
13408
|
function gitAsync(args, cwd) {
|
|
13133
13409
|
return new Promise((resolve7) => {
|
|
13134
|
-
|
|
13410
|
+
execFile3(
|
|
13135
13411
|
"git",
|
|
13136
13412
|
args,
|
|
13137
13413
|
{
|
|
@@ -50050,98 +50326,6 @@ function renderStatus2(payload, plain, apiUrl) {
|
|
|
50050
50326
|
process.stdout.write(brandedBlock("status", lines) + "\n");
|
|
50051
50327
|
}
|
|
50052
50328
|
|
|
50053
|
-
// src/commands/open.ts
|
|
50054
|
-
import { execFile as execFile3 } from "node:child_process";
|
|
50055
|
-
var PLANE_NOTE = {
|
|
50056
|
-
workspace: "follows your active workspace",
|
|
50057
|
-
cross: "spans every workspace you belong to",
|
|
50058
|
-
account: "your account, not a workspace"
|
|
50059
|
-
};
|
|
50060
|
-
function browserLaunchCommand(url2, platform2 = process.platform) {
|
|
50061
|
-
let parsed;
|
|
50062
|
-
try {
|
|
50063
|
-
parsed = new URL(url2);
|
|
50064
|
-
} catch {
|
|
50065
|
-
return null;
|
|
50066
|
-
}
|
|
50067
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
50068
|
-
if (platform2 === "darwin") return { file: "open", args: [url2] };
|
|
50069
|
-
if (platform2 === "win32") return { file: "explorer.exe", args: [url2] };
|
|
50070
|
-
return { file: "xdg-open", args: [url2] };
|
|
50071
|
-
}
|
|
50072
|
-
function openBrowser2(url2) {
|
|
50073
|
-
const command = browserLaunchCommand(url2);
|
|
50074
|
-
if (!command) return Promise.resolve(false);
|
|
50075
|
-
return new Promise((resolve7) => {
|
|
50076
|
-
execFile3(command.file, command.args, (error62) => resolve7(!error62));
|
|
50077
|
-
});
|
|
50078
|
-
}
|
|
50079
|
-
function toSurfaceRow(value) {
|
|
50080
|
-
if (typeof value !== "object" || value === null) return null;
|
|
50081
|
-
const r = value;
|
|
50082
|
-
if (typeof r.id !== "string" || typeof r.href !== "string") return null;
|
|
50083
|
-
if (!r.href.startsWith("/") || r.href.startsWith("//")) return null;
|
|
50084
|
-
const plane = r.plane === "workspace" || r.plane === "cross" || r.plane === "account" ? r.plane : "workspace";
|
|
50085
|
-
return {
|
|
50086
|
-
id: r.id,
|
|
50087
|
-
label: typeof r.label === "string" ? r.label : r.id,
|
|
50088
|
-
href: r.href,
|
|
50089
|
-
plane,
|
|
50090
|
-
group: typeof r.group === "string" ? r.group : ""
|
|
50091
|
-
};
|
|
50092
|
-
}
|
|
50093
|
-
function surfaceUrl(apiUrl, href) {
|
|
50094
|
-
return `${apiUrl.replace(/\/$/, "")}${href}`;
|
|
50095
|
-
}
|
|
50096
|
-
function openCommand() {
|
|
50097
|
-
return new Command("open").description("Open an app surface in the browser (e.g. gad open library)").argument(
|
|
50098
|
-
"<surface>",
|
|
50099
|
-
"Surface id, path, label or keyword \u2014 e.g. library, /leads, 'Pitch page'"
|
|
50100
|
-
).option("--print", "Print the URL instead of opening a browser").option("--json", "Output the resolved surface as JSON").action(async (surface, options, command) => {
|
|
50101
|
-
const g = globalOutputOpts(command);
|
|
50102
|
-
const auth = await resolveAuth();
|
|
50103
|
-
const client2 = new GadClient({
|
|
50104
|
-
token: auth.token,
|
|
50105
|
-
getToken: auth.getToken,
|
|
50106
|
-
apiUrl: auth.apiUrl
|
|
50107
|
-
});
|
|
50108
|
-
const result = await client2.mcpCall("surfaces_list", {
|
|
50109
|
-
query: surface
|
|
50110
|
-
});
|
|
50111
|
-
const match = toSurfaceRow(result?.match);
|
|
50112
|
-
const candidates = (Array.isArray(result?.candidates) ? result.candidates : []).map(toSurfaceRow).filter((r) => r !== null);
|
|
50113
|
-
if (!match) {
|
|
50114
|
-
throw new CliError(
|
|
50115
|
-
candidates.length === 0 ? `No app surface matches '${surface}'.` : `'${surface}' matches ${candidates.length} surfaces.`,
|
|
50116
|
-
3 /* VALIDATION_ERROR */,
|
|
50117
|
-
candidates.length === 0 ? "Run 'gad open --help', or ask an AI for surfaces_list to see every page." : `Did you mean: ${candidates.map((c2) => c2.id).join(", ")}?`
|
|
50118
|
-
);
|
|
50119
|
-
}
|
|
50120
|
-
const url2 = surfaceUrl(auth.apiUrl, match.href);
|
|
50121
|
-
if (g.json) {
|
|
50122
|
-
process.stdout.write(JSON.stringify({ ...match, url: url2 }) + "\n");
|
|
50123
|
-
return;
|
|
50124
|
-
}
|
|
50125
|
-
const c = createColor(g.noColor || g.quiet);
|
|
50126
|
-
if (options.print) {
|
|
50127
|
-
process.stdout.write(url2 + "\n");
|
|
50128
|
-
return;
|
|
50129
|
-
}
|
|
50130
|
-
const opened = await openBrowser2(url2);
|
|
50131
|
-
process.stdout.write(
|
|
50132
|
-
`${match.label} ${c.dim(`(${PLANE_NOTE[match.plane]})`)}
|
|
50133
|
-
${c.cyan(url2)}
|
|
50134
|
-
`
|
|
50135
|
-
);
|
|
50136
|
-
if (!opened) {
|
|
50137
|
-
process.stderr.write(
|
|
50138
|
-
`${c.dim("Couldn't launch a browser \u2014 open the URL above.")}
|
|
50139
|
-
`
|
|
50140
|
-
);
|
|
50141
|
-
}
|
|
50142
|
-
});
|
|
50143
|
-
}
|
|
50144
|
-
|
|
50145
50329
|
// src/commands/next.ts
|
|
50146
50330
|
var PICKUP_LIMIT = 10;
|
|
50147
50331
|
function pickupArgs(identityStatus) {
|
|
@@ -50342,7 +50526,7 @@ import { homedir as homedir10 } from "node:os";
|
|
|
50342
50526
|
import { isAbsolute as isAbsolute3, join as join13, relative, resolve as resolve5 } from "node:path";
|
|
50343
50527
|
|
|
50344
50528
|
// src/lib/runner/git.ts
|
|
50345
|
-
import { spawn } from "node:child_process";
|
|
50529
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
50346
50530
|
var GIT_DEADLINE_MS = 12e4;
|
|
50347
50531
|
var HOST_PATH = "/usr/bin:/bin:/usr/sbin:/sbin";
|
|
50348
50532
|
var MAX_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
@@ -50386,7 +50570,7 @@ function safeGitBytes(cwd, args, opts = {}) {
|
|
|
50386
50570
|
return new Promise((resolve7) => {
|
|
50387
50571
|
const failed = (stderr) => resolve7({ code: 1, stdout: Buffer.alloc(0), stderr });
|
|
50388
50572
|
if (opts.signal?.aborted) return failed("aborted");
|
|
50389
|
-
const child =
|
|
50573
|
+
const child = spawn2("git", argv, {
|
|
50390
50574
|
cwd,
|
|
50391
50575
|
env: { ...gitEnv(), ...opts.env ?? {} },
|
|
50392
50576
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -50463,21 +50647,6 @@ function worktreeGitBytes(layout, args, opts = {}) {
|
|
|
50463
50647
|
return safeGitBytes(layout.worktree, [...layoutArgs(layout), ...args], opts);
|
|
50464
50648
|
}
|
|
50465
50649
|
|
|
50466
|
-
// src/lib/runner/boot-ticks.ts
|
|
50467
|
-
import { readFileSync as readFileSync4 } from "node:fs";
|
|
50468
|
-
var USER_HZ = 100;
|
|
50469
|
-
function bootTicks(read = readFileSync4) {
|
|
50470
|
-
if (process.platform !== "linux") return void 0;
|
|
50471
|
-
try {
|
|
50472
|
-
const seconds = Number.parseFloat(
|
|
50473
|
-
read("/proc/uptime", "utf8").split(" ")[0] ?? ""
|
|
50474
|
-
);
|
|
50475
|
-
return Number.isFinite(seconds) ? Math.floor(seconds * USER_HZ) : void 0;
|
|
50476
|
-
} catch {
|
|
50477
|
-
return void 0;
|
|
50478
|
-
}
|
|
50479
|
-
}
|
|
50480
|
-
|
|
50481
50650
|
// src/lib/runner/text.ts
|
|
50482
50651
|
function cleanLine(text3) {
|
|
50483
50652
|
return text3.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ").trim();
|
|
@@ -50833,7 +51002,6 @@ async function prepareWorktree(input2) {
|
|
|
50833
51002
|
await privateDir(join13(runDir, "wt"));
|
|
50834
51003
|
const path10 = join13(runDir, "wt", short(todoId));
|
|
50835
51004
|
const branch = runnerBranch(runId, todoId);
|
|
50836
|
-
const preparedAt = bootTicks();
|
|
50837
51005
|
const fetch2 = await safeGit(repoRoot, [
|
|
50838
51006
|
"fetch",
|
|
50839
51007
|
"--no-tags",
|
|
@@ -50859,8 +51027,7 @@ async function prepareWorktree(input2) {
|
|
|
50859
51027
|
worktreeGitDir: await gitPath(worktree, "--absolute-git-dir"),
|
|
50860
51028
|
home: homedir10(),
|
|
50861
51029
|
hooksDir: await configuredHooksDir(worktree, commonDir),
|
|
50862
|
-
branch
|
|
50863
|
-
...preparedAt === void 0 ? {} : { preparedAt }
|
|
51030
|
+
branch
|
|
50864
51031
|
};
|
|
50865
51032
|
const gad = join13(worktree, ".gad");
|
|
50866
51033
|
await privateDir(gad);
|
|
@@ -51344,7 +51511,7 @@ import { join as join17 } from "node:path";
|
|
|
51344
51511
|
import { createInterface as createInterface3 } from "node:readline";
|
|
51345
51512
|
|
|
51346
51513
|
// src/lib/runner/agent.ts
|
|
51347
|
-
import { execFile as execFile4, spawn as
|
|
51514
|
+
import { execFile as execFile4, spawn as spawn3 } from "node:child_process";
|
|
51348
51515
|
import { accessSync, chmodSync as chmodSync3, closeSync, constants as constants2, openSync } from "node:fs";
|
|
51349
51516
|
import { delimiter, join as join15 } from "node:path";
|
|
51350
51517
|
var KILL_GRACE_MS = 5e3;
|
|
@@ -51412,7 +51579,7 @@ async function runAgent(input2) {
|
|
|
51412
51579
|
const env4 = { ...stripCredentialEnv(process.env), GIT_TERMINAL_PROMPT: "0" };
|
|
51413
51580
|
let child;
|
|
51414
51581
|
try {
|
|
51415
|
-
child =
|
|
51582
|
+
child = spawn3(input2.bin, agentArgv(input2), {
|
|
51416
51583
|
cwd: input2.layout.worktree,
|
|
51417
51584
|
detached: true,
|
|
51418
51585
|
stdio: ["ignore", fd, fd],
|
|
@@ -51483,7 +51650,7 @@ async function reapGroup(pgid, waitMs = REAP_WAIT_MS) {
|
|
|
51483
51650
|
|
|
51484
51651
|
// src/lib/runner/holders.ts
|
|
51485
51652
|
import { execFile as execFile5 } from "node:child_process";
|
|
51486
|
-
import {
|
|
51653
|
+
import { readdirSync, readlinkSync, statSync as statSync2 } from "node:fs";
|
|
51487
51654
|
var LSOF_DEADLINE_MS = 2e4;
|
|
51488
51655
|
var CHECKS = 5;
|
|
51489
51656
|
var PAUSE_MS = 250;
|
|
@@ -51595,18 +51762,8 @@ async function lsofUsers(root, uid, exec2 = run3) {
|
|
|
51595
51762
|
for (const h of holders) if (!await isIndexer(h, exec2)) users.push(h.pid);
|
|
51596
51763
|
return users;
|
|
51597
51764
|
}
|
|
51598
|
-
function startTicks(pid, fs6) {
|
|
51599
|
-
try {
|
|
51600
|
-
const stat3 = (fs6.readFileSync ?? readFileSync5)(`/proc/${pid}/stat`, "utf8");
|
|
51601
|
-
const after = stat3.slice(stat3.lastIndexOf(")") + 1).trim().split(/\s+/);
|
|
51602
|
-
const ticks = Number(after[19]);
|
|
51603
|
-
return Number.isSafeInteger(ticks) ? ticks : null;
|
|
51604
|
-
} catch {
|
|
51605
|
-
return null;
|
|
51606
|
-
}
|
|
51607
|
-
}
|
|
51608
51765
|
var errno = (error62) => error62.code;
|
|
51609
|
-
function procUsers(root, uid, fs6 = { readdirSync, statSync: statSync2, readlinkSync
|
|
51766
|
+
function procUsers(root, uid, fs6 = { readdirSync, statSync: statSync2, readlinkSync }) {
|
|
51610
51767
|
let entries;
|
|
51611
51768
|
try {
|
|
51612
51769
|
entries = fs6.readdirSync("/proc").filter((e) => /^\d+$/.test(e));
|
|
@@ -51636,24 +51793,20 @@ function procUsers(root, uid, fs6 = { readdirSync, statSync: statSync2, readlink
|
|
|
51636
51793
|
if (holds) users.push(Number(pid));
|
|
51637
51794
|
} catch (error62) {
|
|
51638
51795
|
if (errno(error62) === "ENOENT" || errno(error62) === "ESRCH") continue;
|
|
51639
|
-
if (errno(error62) === "EACCES" && notBefore !== void 0) {
|
|
51640
|
-
const started = startTicks(pid, fs6);
|
|
51641
|
-
if (started !== null && started < notBefore) continue;
|
|
51642
|
-
}
|
|
51643
51796
|
return null;
|
|
51644
51797
|
}
|
|
51645
51798
|
}
|
|
51646
51799
|
return users;
|
|
51647
51800
|
}
|
|
51648
|
-
async function processesUsing(root
|
|
51801
|
+
async function processesUsing(root) {
|
|
51649
51802
|
const uid = process.getuid?.();
|
|
51650
51803
|
if (uid === void 0) return null;
|
|
51651
|
-
const found = process.platform === "darwin" ? await lsofUsers(root, uid) : process.platform === "linux" ? procUsers(root, uid
|
|
51804
|
+
const found = process.platform === "darwin" ? await lsofUsers(root, uid) : process.platform === "linux" ? procUsers(root, uid) : null;
|
|
51652
51805
|
return found?.filter((pid) => pid !== process.pid) ?? null;
|
|
51653
51806
|
}
|
|
51654
|
-
async function worktreeUnused(root
|
|
51807
|
+
async function worktreeUnused(root) {
|
|
51655
51808
|
for (let check2 = 0; check2 < CHECKS; check2++) {
|
|
51656
|
-
const users = await processesUsing(root
|
|
51809
|
+
const users = await processesUsing(root);
|
|
51657
51810
|
if (users === null) return false;
|
|
51658
51811
|
if (users.length === 0) return true;
|
|
51659
51812
|
if (check2 + 1 < CHECKS) await new Promise((r) => setTimeout(r, PAUSE_MS));
|
|
@@ -52059,7 +52212,7 @@ async function headSha(layout, opts) {
|
|
|
52059
52212
|
}
|
|
52060
52213
|
async function publishBranch(layout, branch, base, message, signal, options = {}) {
|
|
52061
52214
|
if (!await verifyGitfile(layout)) return { failed: "sandbox_tampered" };
|
|
52062
|
-
if (!await worktreeUnused(layout.worktree
|
|
52215
|
+
if (!await worktreeUnused(layout.worktree))
|
|
52063
52216
|
return { failed: "agent_still_running" };
|
|
52064
52217
|
if (!RUNNER_BRANCH.test(branch) || branch === base || !BASE_NAME.test(base))
|
|
52065
52218
|
return { failed: "bad_branch" };
|
|
@@ -52641,7 +52794,7 @@ import { createInterface as createInterface4 } from "node:readline";
|
|
|
52641
52794
|
import { Writable } from "node:stream";
|
|
52642
52795
|
|
|
52643
52796
|
// src/lib/keys-apply.ts
|
|
52644
|
-
import { spawn as
|
|
52797
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
52645
52798
|
import {
|
|
52646
52799
|
chmod as chmod6,
|
|
52647
52800
|
mkdir as mkdir10,
|
|
@@ -52666,7 +52819,7 @@ function runWithStdin(file2, args, input2, timeoutMs) {
|
|
|
52666
52819
|
};
|
|
52667
52820
|
let child;
|
|
52668
52821
|
try {
|
|
52669
|
-
child =
|
|
52822
|
+
child = spawn4(file2, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
52670
52823
|
} catch {
|
|
52671
52824
|
resolve7("not_found");
|
|
52672
52825
|
return;
|
|
@@ -52984,111 +53137,6 @@ function openFromServer(sealed, devicePrivateKeyB64) {
|
|
|
52984
53137
|
}
|
|
52985
53138
|
}
|
|
52986
53139
|
|
|
52987
|
-
// src/lib/keys-handoff.ts
|
|
52988
|
-
import { spawn as spawn4 } from "node:child_process";
|
|
52989
|
-
|
|
52990
|
-
// src/lib/keys-paths.ts
|
|
52991
|
-
function keyRotationPath() {
|
|
52992
|
-
return "/settings/key-rotation";
|
|
52993
|
-
}
|
|
52994
|
-
function keyRotationRunPath(runId) {
|
|
52995
|
-
return `/settings/key-rotation/runs/${encodeURIComponent(runId)}`;
|
|
52996
|
-
}
|
|
52997
|
-
function keyRotationHandoffPath(jti) {
|
|
52998
|
-
return `/settings/key-rotation/handoff/${encodeURIComponent(jti)}`;
|
|
52999
|
-
}
|
|
53000
|
-
|
|
53001
|
-
// src/lib/keys-handoff.ts
|
|
53002
|
-
var HANDOFF_POLL_INTERVAL_MS = 2e3;
|
|
53003
|
-
var HANDOFF_DEADLINE_MS = 5 * 6e4;
|
|
53004
|
-
var BROWSER_LAUNCH_TIMEOUT_MS = 5e3;
|
|
53005
|
-
function openInBrowser(url2) {
|
|
53006
|
-
const command = browserLaunchCommand(url2);
|
|
53007
|
-
if (!command) return Promise.resolve(false);
|
|
53008
|
-
return new Promise((resolve7) => {
|
|
53009
|
-
let settled = false;
|
|
53010
|
-
const done = (ok2) => {
|
|
53011
|
-
if (settled) return;
|
|
53012
|
-
settled = true;
|
|
53013
|
-
clearTimeout(timer);
|
|
53014
|
-
resolve7(ok2);
|
|
53015
|
-
};
|
|
53016
|
-
const timer = setTimeout(() => done(false), BROWSER_LAUNCH_TIMEOUT_MS);
|
|
53017
|
-
timer.unref?.();
|
|
53018
|
-
try {
|
|
53019
|
-
const child = spawn4(command.file, command.args, {
|
|
53020
|
-
detached: true,
|
|
53021
|
-
stdio: "ignore"
|
|
53022
|
-
});
|
|
53023
|
-
child.once("error", () => done(false));
|
|
53024
|
-
child.once("spawn", () => {
|
|
53025
|
-
child.unref();
|
|
53026
|
-
done(true);
|
|
53027
|
-
});
|
|
53028
|
-
} catch {
|
|
53029
|
-
done(false);
|
|
53030
|
-
}
|
|
53031
|
-
});
|
|
53032
|
-
}
|
|
53033
|
-
var defaultSleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
53034
|
-
function stepUpError(message, hint) {
|
|
53035
|
-
return new CliError(message, 1 /* AUTH_ERROR */, hint);
|
|
53036
|
-
}
|
|
53037
|
-
async function requestBrowserStepUp(client2, kind, subjectId, opts = {}) {
|
|
53038
|
-
const openUrl = opts.openUrl ?? openInBrowser;
|
|
53039
|
-
const notify = opts.notify ?? ((line) => process.stderr.write(`${line}
|
|
53040
|
-
`));
|
|
53041
|
-
const sleep3 = opts.sleep ?? defaultSleep;
|
|
53042
|
-
const now = opts.now ?? Date.now;
|
|
53043
|
-
const interval = opts.pollIntervalMs ?? HANDOFF_POLL_INTERVAL_MS;
|
|
53044
|
-
const deadline = now() + (opts.deadlineMs ?? HANDOFF_DEADLINE_MS);
|
|
53045
|
-
const created = await client2.post("/api/key-rotation/handoffs", {
|
|
53046
|
-
kind,
|
|
53047
|
-
subjectId
|
|
53048
|
-
});
|
|
53049
|
-
const jti = typeof created?.jti === "string" ? created.jti : null;
|
|
53050
|
-
if (!jti || !/^[A-Za-z0-9-]{1,64}$/.test(jti)) {
|
|
53051
|
-
throw stepUpError("The server did not return a browser approval.");
|
|
53052
|
-
}
|
|
53053
|
-
const url2 = `${client2.apiOrigin}${keyRotationHandoffPath(jti)}`;
|
|
53054
|
-
notify(`Approve this in your browser (passkey or two-step code): ${url2}`);
|
|
53055
|
-
if (!await openUrl(url2)) {
|
|
53056
|
-
notify("Couldn't launch a browser. Open the URL above to approve.");
|
|
53057
|
-
}
|
|
53058
|
-
while (now() < deadline) {
|
|
53059
|
-
const polled = await client2.get(
|
|
53060
|
-
`/api/key-rotation/handoffs/${encodeURIComponent(jti)}`
|
|
53061
|
-
);
|
|
53062
|
-
const status = polled?.status;
|
|
53063
|
-
if (status === "approved") {
|
|
53064
|
-
if (typeof polled.token === "string" && polled.token.length > 0) {
|
|
53065
|
-
return polled.token;
|
|
53066
|
-
}
|
|
53067
|
-
throw stepUpError(
|
|
53068
|
-
"This approval was already collected by another request.",
|
|
53069
|
-
"Run the command again to request a new approval."
|
|
53070
|
-
);
|
|
53071
|
-
}
|
|
53072
|
-
if (status === "consumed") {
|
|
53073
|
-
throw stepUpError(
|
|
53074
|
-
"This approval was already used.",
|
|
53075
|
-
"Run the command again to request a new approval."
|
|
53076
|
-
);
|
|
53077
|
-
}
|
|
53078
|
-
if (status === "expired") {
|
|
53079
|
-
throw stepUpError(
|
|
53080
|
-
"The browser approval expired.",
|
|
53081
|
-
"Run the command again and approve within 5 minutes."
|
|
53082
|
-
);
|
|
53083
|
-
}
|
|
53084
|
-
await sleep3(interval);
|
|
53085
|
-
}
|
|
53086
|
-
throw stepUpError(
|
|
53087
|
-
"Timed out waiting for the browser approval.",
|
|
53088
|
-
"Run the command again and approve within 5 minutes."
|
|
53089
|
-
);
|
|
53090
|
-
}
|
|
53091
|
-
|
|
53092
53140
|
// src/lib/keys-device.ts
|
|
53093
53141
|
var DEFAULT_ENV_FILE = ".env.local";
|
|
53094
53142
|
var DETECT_TIMEOUT_MS = 5e3;
|