@great-arrow/cli 0.9.3 → 0.9.6
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 +511 -371
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3339,11 +3339,11 @@ var require_util = __commonJS({
|
|
|
3339
3339
|
if (files.includes("node_modules") || files.includes("package.json") || files.includes("package.json5") || files.includes("package.yaml") || files.includes("pnpm-workspace.yaml")) {
|
|
3340
3340
|
return name24;
|
|
3341
3341
|
}
|
|
3342
|
-
const
|
|
3343
|
-
if (
|
|
3342
|
+
const dirname11 = path10.dirname(name24);
|
|
3343
|
+
if (dirname11 === name24) {
|
|
3344
3344
|
return original;
|
|
3345
3345
|
}
|
|
3346
|
-
return find(
|
|
3346
|
+
return find(dirname11, original);
|
|
3347
3347
|
} catch (error62) {
|
|
3348
3348
|
if (name24 === original) {
|
|
3349
3349
|
if (error62.code === "ENOENT") {
|
|
@@ -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.6";
|
|
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
|
{
|
|
@@ -15479,7 +15755,7 @@ function serverMapsFromJson(parsed, serversKey) {
|
|
|
15479
15755
|
return maps;
|
|
15480
15756
|
}
|
|
15481
15757
|
function findCodexEntries(location, content) {
|
|
15482
|
-
const
|
|
15758
|
+
const entries2 = [];
|
|
15483
15759
|
const sections = content.split(/^\s*\[/m).slice(1);
|
|
15484
15760
|
const seen = /* @__PURE__ */ new Set();
|
|
15485
15761
|
for (const raw of sections) {
|
|
@@ -15493,7 +15769,7 @@ function findCodexEntries(location, content) {
|
|
|
15493
15769
|
const transport = codexTransportFields(body);
|
|
15494
15770
|
if (!isGreatArrowServer(name23, transport)) continue;
|
|
15495
15771
|
seen.add(name23);
|
|
15496
|
-
|
|
15772
|
+
entries2.push({
|
|
15497
15773
|
clientId: location.clientId,
|
|
15498
15774
|
client: location.client,
|
|
15499
15775
|
scope: location.scope,
|
|
@@ -15502,7 +15778,7 @@ function findCodexEntries(location, content) {
|
|
|
15502
15778
|
surface: typeof transport.command === "string" ? "stdio" : "http"
|
|
15503
15779
|
});
|
|
15504
15780
|
}
|
|
15505
|
-
return
|
|
15781
|
+
return entries2;
|
|
15506
15782
|
}
|
|
15507
15783
|
function findGreatArrowEntries(location, content, options = {}) {
|
|
15508
15784
|
if (location.format === "toml") return findCodexEntries(location, content);
|
|
@@ -15514,7 +15790,7 @@ function findGreatArrowEntries(location, content, options = {}) {
|
|
|
15514
15790
|
}
|
|
15515
15791
|
if (!parsed) return [];
|
|
15516
15792
|
const serversKey = location.serversKey ?? "mcpServers";
|
|
15517
|
-
const
|
|
15793
|
+
const entries2 = [];
|
|
15518
15794
|
for (const { servers, projectDir } of serverMapsFromJson(
|
|
15519
15795
|
parsed,
|
|
15520
15796
|
serversKey
|
|
@@ -15523,7 +15799,7 @@ function findGreatArrowEntries(location, content, options = {}) {
|
|
|
15523
15799
|
const entry = asObject3(value);
|
|
15524
15800
|
if (!entry) continue;
|
|
15525
15801
|
if (!isGreatArrowServer(name23, entry)) continue;
|
|
15526
|
-
|
|
15802
|
+
entries2.push({
|
|
15527
15803
|
clientId: location.clientId,
|
|
15528
15804
|
client: location.client,
|
|
15529
15805
|
scope: projectDir ? "project" : location.scope,
|
|
@@ -15538,7 +15814,7 @@ function findGreatArrowEntries(location, content, options = {}) {
|
|
|
15538
15814
|
});
|
|
15539
15815
|
}
|
|
15540
15816
|
}
|
|
15541
|
-
return
|
|
15817
|
+
return entries2;
|
|
15542
15818
|
}
|
|
15543
15819
|
function hasGreatArrowHooks(settingsContent) {
|
|
15544
15820
|
let parsed = null;
|
|
@@ -15573,9 +15849,9 @@ function compareEntries(a, b) {
|
|
|
15573
15849
|
if (a.scope !== b.scope) return a.scope === "user" ? -1 : 1;
|
|
15574
15850
|
return a.path.localeCompare(b.path);
|
|
15575
15851
|
}
|
|
15576
|
-
function detectDuplicates(
|
|
15852
|
+
function detectDuplicates(entries2) {
|
|
15577
15853
|
const byClient = /* @__PURE__ */ new Map();
|
|
15578
|
-
for (const entry of
|
|
15854
|
+
for (const entry of entries2) {
|
|
15579
15855
|
const list = byClient.get(entry.clientId) ?? [];
|
|
15580
15856
|
list.push(entry);
|
|
15581
15857
|
byClient.set(entry.clientId, list);
|
|
@@ -15602,8 +15878,8 @@ function detectDuplicates(entries) {
|
|
|
15602
15878
|
}
|
|
15603
15879
|
return findings.sort((a, b) => a.clientId.localeCompare(b.clientId));
|
|
15604
15880
|
}
|
|
15605
|
-
function connectorGuidance(
|
|
15606
|
-
const hasLocalClaude =
|
|
15881
|
+
function connectorGuidance(entries2) {
|
|
15882
|
+
const hasLocalClaude = entries2.some(
|
|
15607
15883
|
(entry) => entry.clientId === "claude-code" || entry.clientId === "claude-desktop"
|
|
15608
15884
|
);
|
|
15609
15885
|
if (!hasLocalClaude) return null;
|
|
@@ -17294,12 +17570,12 @@ var logsCommand = new Command("logs").description("Tail MCP connection log with
|
|
|
17294
17570
|
]);
|
|
17295
17571
|
return;
|
|
17296
17572
|
}
|
|
17297
|
-
const
|
|
17298
|
-
if (
|
|
17573
|
+
const entries2 = Array.isArray(data) ? data : [];
|
|
17574
|
+
if (entries2.length === 0) {
|
|
17299
17575
|
fmt.info("No recent MCP connection events.");
|
|
17300
17576
|
return;
|
|
17301
17577
|
}
|
|
17302
|
-
for (const entry of
|
|
17578
|
+
for (const entry of entries2) {
|
|
17303
17579
|
renderLogEntry(entry, c);
|
|
17304
17580
|
}
|
|
17305
17581
|
} catch {
|
|
@@ -17646,15 +17922,15 @@ async function scanConnections() {
|
|
|
17646
17922
|
join19(home, ".claude", "settings.json")
|
|
17647
17923
|
);
|
|
17648
17924
|
const hooksInstalled = hookSettings ? hasGreatArrowHooks(hookSettings) : false;
|
|
17649
|
-
const
|
|
17925
|
+
const entries2 = [];
|
|
17650
17926
|
for (const location of knownConfigLocations(home, process.cwd())) {
|
|
17651
17927
|
const content = await readIfExists(location.path);
|
|
17652
17928
|
if (content === null) continue;
|
|
17653
|
-
|
|
17929
|
+
entries2.push(
|
|
17654
17930
|
...findGreatArrowEntries(location, content, { hooksInstalled })
|
|
17655
17931
|
);
|
|
17656
17932
|
}
|
|
17657
|
-
return
|
|
17933
|
+
return entries2;
|
|
17658
17934
|
}
|
|
17659
17935
|
async function probeEndpoint() {
|
|
17660
17936
|
try {
|
|
@@ -17739,8 +18015,8 @@ var doctorSubcommand = new Command("doctor").description(
|
|
|
17739
18015
|
const home = homedir12();
|
|
17740
18016
|
const proxyPath = await resolveProxyScript(options.proxy);
|
|
17741
18017
|
const diagnostics = proxyPath ? await runProxyDiagnostics(proxyPath) : null;
|
|
17742
|
-
const
|
|
17743
|
-
const duplicates = detectDuplicates(
|
|
18018
|
+
const entries2 = await scanConnections();
|
|
18019
|
+
const duplicates = detectDuplicates(entries2);
|
|
17744
18020
|
const credentialFile = await readIfExists(
|
|
17745
18021
|
join19(home, ".great-arrow", "credential")
|
|
17746
18022
|
);
|
|
@@ -17763,7 +18039,7 @@ var doctorSubcommand = new Command("doctor").description(
|
|
|
17763
18039
|
}),
|
|
17764
18040
|
await probeEndpoint()
|
|
17765
18041
|
];
|
|
17766
|
-
if (
|
|
18042
|
+
if (entries2.length === 0) {
|
|
17767
18043
|
findings.push({
|
|
17768
18044
|
code: "no_connections",
|
|
17769
18045
|
level: "warn",
|
|
@@ -17788,14 +18064,14 @@ var doctorSubcommand = new Command("doctor").description(
|
|
|
17788
18064
|
now: Date.now()
|
|
17789
18065
|
});
|
|
17790
18066
|
const applied = options.migrate ? await applyMigration(plan) : [];
|
|
17791
|
-
const guidance = connectorGuidance(
|
|
18067
|
+
const guidance = connectorGuidance(entries2);
|
|
17792
18068
|
if (jsonFlag) {
|
|
17793
18069
|
process.stdout.write(
|
|
17794
18070
|
JSON.stringify(
|
|
17795
18071
|
{
|
|
17796
18072
|
proxyPath,
|
|
17797
18073
|
diagnostics,
|
|
17798
|
-
entries,
|
|
18074
|
+
entries: entries2,
|
|
17799
18075
|
duplicates,
|
|
17800
18076
|
findings,
|
|
17801
18077
|
migration: { plan, applied, migrated: Boolean(options.migrate) },
|
|
@@ -17808,10 +18084,10 @@ var doctorSubcommand = new Command("doctor").description(
|
|
|
17808
18084
|
} else {
|
|
17809
18085
|
process.stdout.write(`${c.dim("Connections")}
|
|
17810
18086
|
`);
|
|
17811
|
-
if (
|
|
18087
|
+
if (entries2.length === 0) {
|
|
17812
18088
|
process.stdout.write(" (none found)\n");
|
|
17813
18089
|
}
|
|
17814
|
-
for (const entry of
|
|
18090
|
+
for (const entry of entries2) {
|
|
17815
18091
|
process.stdout.write(` ${entry.client}: ${describeEntry(entry)}
|
|
17816
18092
|
`);
|
|
17817
18093
|
}
|
|
@@ -20733,9 +21009,9 @@ function assertNever(_x) {
|
|
|
20733
21009
|
}
|
|
20734
21010
|
function assert(_) {
|
|
20735
21011
|
}
|
|
20736
|
-
function getEnumValues(
|
|
20737
|
-
const numericValues = Object.values(
|
|
20738
|
-
const values = Object.entries(
|
|
21012
|
+
function getEnumValues(entries2) {
|
|
21013
|
+
const numericValues = Object.values(entries2).filter((v) => typeof v === "number");
|
|
21014
|
+
const values = Object.entries(entries2).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v);
|
|
20739
21015
|
return values;
|
|
20740
21016
|
}
|
|
20741
21017
|
function joinValues(array3, separator = "|") {
|
|
@@ -22570,16 +22846,16 @@ var $ZodCheckProperties = /* @__PURE__ */ $constructor("$ZodCheckProperties", (i
|
|
|
22570
22846
|
hide(inst, Symbol.iterator, function* () {
|
|
22571
22847
|
yield inst;
|
|
22572
22848
|
});
|
|
22573
|
-
let
|
|
22849
|
+
let entries2;
|
|
22574
22850
|
inst._zod.check = (payload) => {
|
|
22575
22851
|
if (payload.value == null) {
|
|
22576
22852
|
payload.issues.push({ expected: "object", code: "invalid_type", input: payload.value, inst });
|
|
22577
22853
|
return void 0;
|
|
22578
22854
|
}
|
|
22579
|
-
|
|
22855
|
+
entries2 ?? (entries2 = Reflect.ownKeys(def.shape).map((key) => [key, def.shape[key]]));
|
|
22580
22856
|
const input2 = payload.value;
|
|
22581
22857
|
let proms;
|
|
22582
|
-
for (const [key, schema] of
|
|
22858
|
+
for (const [key, schema] of entries2) {
|
|
22583
22859
|
const result = schema._zod.run({ value: input2[key], issues: [] }, {});
|
|
22584
22860
|
if (result instanceof Promise) {
|
|
22585
22861
|
proms ?? (proms = []);
|
|
@@ -35397,18 +35673,18 @@ function _set(Class2, valueType, params) {
|
|
|
35397
35673
|
}
|
|
35398
35674
|
// @__NO_SIDE_EFFECTS__
|
|
35399
35675
|
function _enum(Class2, values, params) {
|
|
35400
|
-
const
|
|
35676
|
+
const entries2 = Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values;
|
|
35401
35677
|
return new Class2({
|
|
35402
35678
|
type: "enum",
|
|
35403
|
-
entries,
|
|
35679
|
+
entries: entries2,
|
|
35404
35680
|
...normalizeParams(params)
|
|
35405
35681
|
});
|
|
35406
35682
|
}
|
|
35407
35683
|
// @__NO_SIDE_EFFECTS__
|
|
35408
|
-
function _nativeEnum(Class2,
|
|
35684
|
+
function _nativeEnum(Class2, entries2, params) {
|
|
35409
35685
|
return new Class2({
|
|
35410
35686
|
type: "enum",
|
|
35411
|
-
entries,
|
|
35687
|
+
entries: entries2,
|
|
35412
35688
|
...normalizeParams(params)
|
|
35413
35689
|
});
|
|
35414
35690
|
}
|
|
@@ -38391,17 +38667,17 @@ var ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
|
|
|
38391
38667
|
};
|
|
38392
38668
|
});
|
|
38393
38669
|
function _enum2(values, params) {
|
|
38394
|
-
const
|
|
38670
|
+
const entries2 = Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values;
|
|
38395
38671
|
return new ZodEnum({
|
|
38396
38672
|
type: "enum",
|
|
38397
|
-
entries,
|
|
38673
|
+
entries: entries2,
|
|
38398
38674
|
...util_exports.normalizeParams(params)
|
|
38399
38675
|
});
|
|
38400
38676
|
}
|
|
38401
|
-
function nativeEnum(
|
|
38677
|
+
function nativeEnum(entries2, params) {
|
|
38402
38678
|
return new ZodEnum({
|
|
38403
38679
|
type: "enum",
|
|
38404
|
-
entries,
|
|
38680
|
+
entries: entries2,
|
|
38405
38681
|
...util_exports.normalizeParams(params)
|
|
38406
38682
|
});
|
|
38407
38683
|
}
|
|
@@ -49178,11 +49454,11 @@ function firstString(...candidates) {
|
|
|
49178
49454
|
}
|
|
49179
49455
|
return void 0;
|
|
49180
49456
|
}
|
|
49181
|
-
function collect(
|
|
49182
|
-
if (!Array.isArray(
|
|
49457
|
+
function collect(entries2, read) {
|
|
49458
|
+
if (!Array.isArray(entries2)) return [];
|
|
49183
49459
|
const out2 = [];
|
|
49184
49460
|
const seen = /* @__PURE__ */ new Set();
|
|
49185
|
-
for (const entry of
|
|
49461
|
+
for (const entry of entries2) {
|
|
49186
49462
|
if (out2.length >= MAX_LOCAL_MODELS) break;
|
|
49187
49463
|
if (!entry || typeof entry !== "object") continue;
|
|
49188
49464
|
const { id, label, contextLength } = read(entry);
|
|
@@ -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) {
|
|
@@ -50334,7 +50518,7 @@ function callCommand() {
|
|
|
50334
50518
|
// src/lib/runner/approval.ts
|
|
50335
50519
|
import { createHash as createHash6 } from "node:crypto";
|
|
50336
50520
|
import { chmod as chmod4, mkdir as mkdir9, readFile as readFile8, rename as rename3, writeFile as writeFile7 } from "node:fs/promises";
|
|
50337
|
-
import { join as join14 } from "node:path";
|
|
50521
|
+
import { dirname as dirname9, join as join14 } from "node:path";
|
|
50338
50522
|
|
|
50339
50523
|
// src/lib/runner/repo.ts
|
|
50340
50524
|
import { chmod as chmod3, mkdir as mkdir8, realpath as realpath2, writeFile as writeFile6 } from "node:fs/promises";
|
|
@@ -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"],
|
|
@@ -50464,11 +50648,12 @@ function worktreeGitBytes(layout, args, opts = {}) {
|
|
|
50464
50648
|
}
|
|
50465
50649
|
|
|
50466
50650
|
// src/lib/runner/text.ts
|
|
50651
|
+
var FORMAT = new RegExp("(?!\\u200d)\\p{Cf}", "gu");
|
|
50467
50652
|
function cleanLine(text3) {
|
|
50468
|
-
return text3.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ").trim();
|
|
50653
|
+
return text3.replace(FORMAT, "").replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ").trim();
|
|
50469
50654
|
}
|
|
50470
50655
|
function cleanText(text3) {
|
|
50471
|
-
return text3.replace(/\r\n?/g, "\n").replace(/\t/g, " ").replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f]+/g, " ");
|
|
50656
|
+
return text3.replace(FORMAT, "").replace(/\r\n?/g, "\n").replace(/\t/g, " ").replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f]+/g, " ");
|
|
50472
50657
|
}
|
|
50473
50658
|
var PREVIEW_LINES = 3;
|
|
50474
50659
|
var PREVIEW_CHARS = 240;
|
|
@@ -50493,8 +50678,9 @@ function preview(text3) {
|
|
|
50493
50678
|
}
|
|
50494
50679
|
|
|
50495
50680
|
// src/lib/runner/sandbox.ts
|
|
50681
|
+
import { readdirSync } from "node:fs";
|
|
50496
50682
|
import { lstat, readFile as readFile7, realpath } from "node:fs/promises";
|
|
50497
|
-
import { dirname as dirname8, join as join12, resolve as resolve4 } from "node:path";
|
|
50683
|
+
import { basename as basename2, dirname as dirname8, join as join12, resolve as resolve4 } from "node:path";
|
|
50498
50684
|
var TASK_FILE = ".gad/task.md";
|
|
50499
50685
|
var CLAUDE_ALLOWED_DOMAINS = [
|
|
50500
50686
|
"api.anthropic.com",
|
|
@@ -50595,13 +50781,44 @@ var CLI_READ_HARDENING = [
|
|
|
50595
50781
|
".cargo/credentials.toml",
|
|
50596
50782
|
".terraform.d/credentials.tfrc.json"
|
|
50597
50783
|
];
|
|
50598
|
-
|
|
50599
|
-
|
|
50784
|
+
var CLI_ONLY_READ_HARDENING = ["Library/Keychains"];
|
|
50785
|
+
function runsDirOf(layout) {
|
|
50786
|
+
return dirname8(dirname8(dirname8(layout.worktree)));
|
|
50787
|
+
}
|
|
50788
|
+
function approvalsDir(layout) {
|
|
50789
|
+
return join12(dirname8(runsDirOf(layout)), "approvals");
|
|
50790
|
+
}
|
|
50791
|
+
function entries(dir) {
|
|
50792
|
+
try {
|
|
50793
|
+
return readdirSync(dir);
|
|
50794
|
+
} catch {
|
|
50795
|
+
return [];
|
|
50796
|
+
}
|
|
50797
|
+
}
|
|
50798
|
+
var UUID5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
50799
|
+
function siblingPaths(layout) {
|
|
50800
|
+
const wtDir = dirname8(layout.worktree);
|
|
50801
|
+
const runDir = dirname8(wtDir);
|
|
50802
|
+
const runsDir = dirname8(runDir);
|
|
50803
|
+
if (basename2(wtDir) !== "wt" || !UUID5.test(basename2(runDir))) return [];
|
|
50804
|
+
const others = (dir, keep) => entries(dir).map((name23) => join12(dir, name23)).filter((path10) => path10 !== keep);
|
|
50805
|
+
return [
|
|
50806
|
+
...others(runsDir, runDir),
|
|
50807
|
+
...others(runDir, wtDir),
|
|
50808
|
+
...others(wtDir, layout.worktree)
|
|
50809
|
+
];
|
|
50810
|
+
}
|
|
50811
|
+
function cliOnlyDenyRead(layout) {
|
|
50812
|
+
return [
|
|
50813
|
+
...CLI_ONLY_READ_HARDENING.map((rel) => join12(layout.home, rel)),
|
|
50814
|
+
approvalsDir(layout),
|
|
50815
|
+
...siblingPaths(layout)
|
|
50816
|
+
];
|
|
50600
50817
|
}
|
|
50601
50818
|
function denyRead(layout) {
|
|
50602
50819
|
return [
|
|
50603
50820
|
...CLI_READ_HARDENING.map((rel) => join12(layout.home, rel)),
|
|
50604
|
-
|
|
50821
|
+
...cliOnlyDenyRead(layout)
|
|
50605
50822
|
];
|
|
50606
50823
|
}
|
|
50607
50824
|
var CREDENTIAL_SET = new Set(
|
|
@@ -50741,14 +50958,14 @@ function kickoff(runId, todoId) {
|
|
|
50741
50958
|
}
|
|
50742
50959
|
|
|
50743
50960
|
// src/lib/runner/repo.ts
|
|
50744
|
-
var
|
|
50961
|
+
var UUID6 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
50745
50962
|
var BRANCH_NAME = /^(?!-)(?!.*\.\.)[A-Za-z0-9._/-]+$/;
|
|
50746
50963
|
var GITHUB_REMOTE = /^(?:https?:\/\/(?:[^@/]+@)?github\.com\/|ssh:\/\/git@github\.com(?::\d+)?\/|git@github\.com:)([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?\/?$/;
|
|
50747
50964
|
function runsRoot() {
|
|
50748
50965
|
return join13(homedir10(), ".gad", "runs");
|
|
50749
50966
|
}
|
|
50750
50967
|
function isRunId(id) {
|
|
50751
|
-
return
|
|
50968
|
+
return UUID6.test(id);
|
|
50752
50969
|
}
|
|
50753
50970
|
var short = (id) => id.slice(0, 8);
|
|
50754
50971
|
function runnerBranch(runId, todoId) {
|
|
@@ -50859,9 +51076,12 @@ function sha256(text3) {
|
|
|
50859
51076
|
return createHash6("sha256").update(text3, "utf8").digest("hex");
|
|
50860
51077
|
}
|
|
50861
51078
|
var plainTitle = cleanLine;
|
|
51079
|
+
function approvalsDir2(runsDir) {
|
|
51080
|
+
return join14(dirname9(runsDir), "approvals");
|
|
51081
|
+
}
|
|
50862
51082
|
function approvedFile(runsDir, runId) {
|
|
50863
51083
|
if (!isRunId(runId)) throw new Error("run id must be a UUID");
|
|
50864
|
-
return join14(runsDir, runId
|
|
51084
|
+
return join14(approvalsDir2(runsDir), `${runId}.json`);
|
|
50865
51085
|
}
|
|
50866
51086
|
function approveShown(cards) {
|
|
50867
51087
|
return new Map(
|
|
@@ -50873,9 +51093,8 @@ function approveShown(cards) {
|
|
|
50873
51093
|
}
|
|
50874
51094
|
async function saveApproved(runsDir, runId, approved) {
|
|
50875
51095
|
const file2 = approvedFile(runsDir, runId);
|
|
50876
|
-
const dir =
|
|
51096
|
+
const dir = approvalsDir2(runsDir);
|
|
50877
51097
|
await mkdir9(dir, { recursive: true, mode: 448 });
|
|
50878
|
-
await chmod4(runsDir, 448);
|
|
50879
51098
|
await chmod4(dir, 448);
|
|
50880
51099
|
const body = [...approved].map(([todoId, c]) => ({ todoId, ...c }));
|
|
50881
51100
|
const tmp = `${file2}.tmp`;
|
|
@@ -50977,13 +51196,19 @@ async function workCard(deps, todoId, card) {
|
|
|
50977
51196
|
if (ran.exit !== 0)
|
|
50978
51197
|
return { outcome: "failed", detail: `agent_exit_${ran.exit ?? "signal"}` };
|
|
50979
51198
|
const result = await deps.publish(prepared, todoId, card.title, deps.signal).catch(() => ({ failed: "publish_failed" }));
|
|
50980
|
-
|
|
51199
|
+
const takeBack = (sha) => () => deps.unpublish(prepared, sha).catch(() => false);
|
|
51200
|
+
if (deps.signal.aborted) {
|
|
51201
|
+
if (typeof result === "object" && "pushed" in result)
|
|
51202
|
+
await sayTakenBack(deps, prepared.branch, takeBack(result.pushed));
|
|
51203
|
+
return { outcome: "stopped" };
|
|
51204
|
+
}
|
|
50981
51205
|
if (result === "no_changes") return { outcome: "no_changes" };
|
|
50982
51206
|
if ("pushed" in result)
|
|
50983
51207
|
return {
|
|
50984
51208
|
outcome: "pushed",
|
|
50985
51209
|
branch: prepared.branch,
|
|
50986
|
-
headSha: result.pushed
|
|
51210
|
+
headSha: result.pushed,
|
|
51211
|
+
takeBack: takeBack(result.pushed)
|
|
50987
51212
|
};
|
|
50988
51213
|
return {
|
|
50989
51214
|
outcome: "failed",
|
|
@@ -50993,6 +51218,12 @@ async function workCard(deps, todoId, card) {
|
|
|
50993
51218
|
} : {}
|
|
50994
51219
|
};
|
|
50995
51220
|
}
|
|
51221
|
+
async function sayTakenBack(deps, branch, takeBack) {
|
|
51222
|
+
if (!await takeBack())
|
|
51223
|
+
deps.print(
|
|
51224
|
+
` Could not delete the pushed branch ${branch}; delete it on GitHub if you do not want it.`
|
|
51225
|
+
);
|
|
51226
|
+
}
|
|
50996
51227
|
function reportFields(o) {
|
|
50997
51228
|
if (o.outcome === "pushed")
|
|
50998
51229
|
return { outcome: o.outcome, branch: o.branch, head_sha: o.headSha };
|
|
@@ -51105,6 +51336,8 @@ async function runLoop(deps, input2) {
|
|
|
51105
51336
|
if (!(error62 instanceof RunnerCallError && error62.code === "card_released"))
|
|
51106
51337
|
throw error62;
|
|
51107
51338
|
deps.print(`${label} \u2026 taken back on the board`);
|
|
51339
|
+
if (outcome.outcome === "pushed")
|
|
51340
|
+
await sayTakenBack(deps, outcome.branch, outcome.takeBack);
|
|
51108
51341
|
continue;
|
|
51109
51342
|
} finally {
|
|
51110
51343
|
if (claimed) await release(deps, next.todoId);
|
|
@@ -51327,7 +51560,7 @@ import { join as join17 } from "node:path";
|
|
|
51327
51560
|
import { createInterface as createInterface3 } from "node:readline";
|
|
51328
51561
|
|
|
51329
51562
|
// src/lib/runner/agent.ts
|
|
51330
|
-
import { execFile as execFile4, spawn as
|
|
51563
|
+
import { execFile as execFile4, spawn as spawn3 } from "node:child_process";
|
|
51331
51564
|
import { accessSync, chmodSync as chmodSync3, closeSync, constants as constants2, openSync } from "node:fs";
|
|
51332
51565
|
import { delimiter, join as join15 } from "node:path";
|
|
51333
51566
|
var KILL_GRACE_MS = 5e3;
|
|
@@ -51395,7 +51628,7 @@ async function runAgent(input2) {
|
|
|
51395
51628
|
const env4 = { ...stripCredentialEnv(process.env), GIT_TERMINAL_PROMPT: "0" };
|
|
51396
51629
|
let child;
|
|
51397
51630
|
try {
|
|
51398
|
-
child =
|
|
51631
|
+
child = spawn3(input2.bin, agentArgv(input2), {
|
|
51399
51632
|
cwd: input2.layout.worktree,
|
|
51400
51633
|
detached: true,
|
|
51401
51634
|
stdio: ["ignore", fd, fd],
|
|
@@ -51466,7 +51699,7 @@ async function reapGroup(pgid, waitMs = REAP_WAIT_MS) {
|
|
|
51466
51699
|
|
|
51467
51700
|
// src/lib/runner/holders.ts
|
|
51468
51701
|
import { execFile as execFile5 } from "node:child_process";
|
|
51469
|
-
import { readdirSync, readlinkSync, statSync as statSync2 } from "node:fs";
|
|
51702
|
+
import { readdirSync as readdirSync2, readlinkSync, statSync as statSync2 } from "node:fs";
|
|
51470
51703
|
var LSOF_DEADLINE_MS = 2e4;
|
|
51471
51704
|
var CHECKS = 5;
|
|
51472
51705
|
var PAUSE_MS = 250;
|
|
@@ -51579,15 +51812,15 @@ async function lsofUsers(root, uid, exec2 = run3) {
|
|
|
51579
51812
|
return users;
|
|
51580
51813
|
}
|
|
51581
51814
|
var errno = (error62) => error62.code;
|
|
51582
|
-
function procUsers(root, uid, fs6 = { readdirSync, statSync: statSync2, readlinkSync }) {
|
|
51583
|
-
let
|
|
51815
|
+
function procUsers(root, uid, fs6 = { readdirSync: readdirSync2, statSync: statSync2, readlinkSync }) {
|
|
51816
|
+
let entries2;
|
|
51584
51817
|
try {
|
|
51585
|
-
|
|
51818
|
+
entries2 = fs6.readdirSync("/proc").filter((e) => /^\d+$/.test(e));
|
|
51586
51819
|
} catch {
|
|
51587
51820
|
return null;
|
|
51588
51821
|
}
|
|
51589
51822
|
const users = [];
|
|
51590
|
-
for (const pid of
|
|
51823
|
+
for (const pid of entries2) {
|
|
51591
51824
|
try {
|
|
51592
51825
|
if (fs6.statSync(`/proc/${pid}`).uid !== uid) continue;
|
|
51593
51826
|
} catch (error62) {
|
|
@@ -52093,6 +52326,17 @@ async function publishBranch(layout, branch, base, message, signal, options = {}
|
|
|
52093
52326
|
throw error62;
|
|
52094
52327
|
}
|
|
52095
52328
|
}
|
|
52329
|
+
async function unpublishBranch(layout, branch, sha) {
|
|
52330
|
+
if (!RUNNER_BRANCH.test(branch) || !SHA.test(sha)) return false;
|
|
52331
|
+
const push = await worktreeGit(layout, [
|
|
52332
|
+
"push",
|
|
52333
|
+
"--quiet",
|
|
52334
|
+
`--force-with-lease=refs/heads/${branch}:${sha}`,
|
|
52335
|
+
"origin",
|
|
52336
|
+
`:refs/heads/${branch}`
|
|
52337
|
+
]);
|
|
52338
|
+
return push.code === 0;
|
|
52339
|
+
}
|
|
52096
52340
|
|
|
52097
52341
|
// src/commands/board-run.ts
|
|
52098
52342
|
function parseData(result) {
|
|
@@ -52199,6 +52443,7 @@ var work = ({
|
|
|
52199
52443
|
commitMessage(title, view.runId, todoId),
|
|
52200
52444
|
signal
|
|
52201
52445
|
),
|
|
52446
|
+
unpublish: (prepared, headSha2) => unpublishBranch(prepared, prepared.branch, headSha2),
|
|
52202
52447
|
sleep: sleep2,
|
|
52203
52448
|
note: (todoId, line) => {
|
|
52204
52449
|
try {
|
|
@@ -52610,7 +52855,7 @@ import { createInterface as createInterface4 } from "node:readline";
|
|
|
52610
52855
|
import { Writable } from "node:stream";
|
|
52611
52856
|
|
|
52612
52857
|
// src/lib/keys-apply.ts
|
|
52613
|
-
import { spawn as
|
|
52858
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
52614
52859
|
import {
|
|
52615
52860
|
chmod as chmod6,
|
|
52616
52861
|
mkdir as mkdir10,
|
|
@@ -52621,7 +52866,7 @@ import {
|
|
|
52621
52866
|
writeFile as writeFile9
|
|
52622
52867
|
} from "node:fs/promises";
|
|
52623
52868
|
import { homedir as homedir11 } from "node:os";
|
|
52624
|
-
import { basename as
|
|
52869
|
+
import { basename as basename3, dirname as dirname10, isAbsolute as isAbsolute4, join as join18, relative as relative2, sep } from "node:path";
|
|
52625
52870
|
var GH_APPLY_TIMEOUT_MS = 3e4;
|
|
52626
52871
|
var GIT_APPLY_TIMEOUT_MS = 3e4;
|
|
52627
52872
|
function runWithStdin(file2, args, input2, timeoutMs) {
|
|
@@ -52635,7 +52880,7 @@ function runWithStdin(file2, args, input2, timeoutMs) {
|
|
|
52635
52880
|
};
|
|
52636
52881
|
let child;
|
|
52637
52882
|
try {
|
|
52638
|
-
child =
|
|
52883
|
+
child = spawn4(file2, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
52639
52884
|
} catch {
|
|
52640
52885
|
resolve7("not_found");
|
|
52641
52886
|
return;
|
|
@@ -52778,7 +53023,7 @@ async function applySshKey(path10, privatePem) {
|
|
|
52778
53023
|
const content = privatePem.endsWith("\n") ? privatePem : `${privatePem}
|
|
52779
53024
|
`;
|
|
52780
53025
|
try {
|
|
52781
|
-
await mkdir10(
|
|
53026
|
+
await mkdir10(dirname10(file2), { recursive: true, mode: 448 });
|
|
52782
53027
|
await writeAtomic(file2, content, 384);
|
|
52783
53028
|
} catch {
|
|
52784
53029
|
return { ok: false, code: "ssh_write_failed" };
|
|
@@ -52799,14 +53044,14 @@ var SSH_NOT_A_KEY = /* @__PURE__ */ new Set([
|
|
|
52799
53044
|
async function sshTargetRefusal(path10, sshDir = join18(homedir11(), ".ssh")) {
|
|
52800
53045
|
const file2 = expandHome(path10);
|
|
52801
53046
|
if (!isAbsolute4(file2)) return { ok: false, code: "target_invalid" };
|
|
52802
|
-
if (SSH_NOT_A_KEY.has(
|
|
53047
|
+
if (SSH_NOT_A_KEY.has(basename3(file2))) {
|
|
52803
53048
|
return { ok: false, code: "target_not_a_key" };
|
|
52804
53049
|
}
|
|
52805
53050
|
let root;
|
|
52806
53051
|
let parent;
|
|
52807
53052
|
try {
|
|
52808
53053
|
root = await realpath3(sshDir);
|
|
52809
|
-
parent = await realpath3(
|
|
53054
|
+
parent = await realpath3(dirname10(file2));
|
|
52810
53055
|
} catch {
|
|
52811
53056
|
return { ok: false, code: "target_outside_ssh_dir" };
|
|
52812
53057
|
}
|
|
@@ -52953,111 +53198,6 @@ function openFromServer(sealed, devicePrivateKeyB64) {
|
|
|
52953
53198
|
}
|
|
52954
53199
|
}
|
|
52955
53200
|
|
|
52956
|
-
// src/lib/keys-handoff.ts
|
|
52957
|
-
import { spawn as spawn4 } from "node:child_process";
|
|
52958
|
-
|
|
52959
|
-
// src/lib/keys-paths.ts
|
|
52960
|
-
function keyRotationPath() {
|
|
52961
|
-
return "/settings/key-rotation";
|
|
52962
|
-
}
|
|
52963
|
-
function keyRotationRunPath(runId) {
|
|
52964
|
-
return `/settings/key-rotation/runs/${encodeURIComponent(runId)}`;
|
|
52965
|
-
}
|
|
52966
|
-
function keyRotationHandoffPath(jti) {
|
|
52967
|
-
return `/settings/key-rotation/handoff/${encodeURIComponent(jti)}`;
|
|
52968
|
-
}
|
|
52969
|
-
|
|
52970
|
-
// src/lib/keys-handoff.ts
|
|
52971
|
-
var HANDOFF_POLL_INTERVAL_MS = 2e3;
|
|
52972
|
-
var HANDOFF_DEADLINE_MS = 5 * 6e4;
|
|
52973
|
-
var BROWSER_LAUNCH_TIMEOUT_MS = 5e3;
|
|
52974
|
-
function openInBrowser(url2) {
|
|
52975
|
-
const command = browserLaunchCommand(url2);
|
|
52976
|
-
if (!command) return Promise.resolve(false);
|
|
52977
|
-
return new Promise((resolve7) => {
|
|
52978
|
-
let settled = false;
|
|
52979
|
-
const done = (ok2) => {
|
|
52980
|
-
if (settled) return;
|
|
52981
|
-
settled = true;
|
|
52982
|
-
clearTimeout(timer);
|
|
52983
|
-
resolve7(ok2);
|
|
52984
|
-
};
|
|
52985
|
-
const timer = setTimeout(() => done(false), BROWSER_LAUNCH_TIMEOUT_MS);
|
|
52986
|
-
timer.unref?.();
|
|
52987
|
-
try {
|
|
52988
|
-
const child = spawn4(command.file, command.args, {
|
|
52989
|
-
detached: true,
|
|
52990
|
-
stdio: "ignore"
|
|
52991
|
-
});
|
|
52992
|
-
child.once("error", () => done(false));
|
|
52993
|
-
child.once("spawn", () => {
|
|
52994
|
-
child.unref();
|
|
52995
|
-
done(true);
|
|
52996
|
-
});
|
|
52997
|
-
} catch {
|
|
52998
|
-
done(false);
|
|
52999
|
-
}
|
|
53000
|
-
});
|
|
53001
|
-
}
|
|
53002
|
-
var defaultSleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
53003
|
-
function stepUpError(message, hint) {
|
|
53004
|
-
return new CliError(message, 1 /* AUTH_ERROR */, hint);
|
|
53005
|
-
}
|
|
53006
|
-
async function requestBrowserStepUp(client2, kind, subjectId, opts = {}) {
|
|
53007
|
-
const openUrl = opts.openUrl ?? openInBrowser;
|
|
53008
|
-
const notify = opts.notify ?? ((line) => process.stderr.write(`${line}
|
|
53009
|
-
`));
|
|
53010
|
-
const sleep3 = opts.sleep ?? defaultSleep;
|
|
53011
|
-
const now = opts.now ?? Date.now;
|
|
53012
|
-
const interval = opts.pollIntervalMs ?? HANDOFF_POLL_INTERVAL_MS;
|
|
53013
|
-
const deadline = now() + (opts.deadlineMs ?? HANDOFF_DEADLINE_MS);
|
|
53014
|
-
const created = await client2.post("/api/key-rotation/handoffs", {
|
|
53015
|
-
kind,
|
|
53016
|
-
subjectId
|
|
53017
|
-
});
|
|
53018
|
-
const jti = typeof created?.jti === "string" ? created.jti : null;
|
|
53019
|
-
if (!jti || !/^[A-Za-z0-9-]{1,64}$/.test(jti)) {
|
|
53020
|
-
throw stepUpError("The server did not return a browser approval.");
|
|
53021
|
-
}
|
|
53022
|
-
const url2 = `${client2.apiOrigin}${keyRotationHandoffPath(jti)}`;
|
|
53023
|
-
notify(`Approve this in your browser (passkey or two-step code): ${url2}`);
|
|
53024
|
-
if (!await openUrl(url2)) {
|
|
53025
|
-
notify("Couldn't launch a browser. Open the URL above to approve.");
|
|
53026
|
-
}
|
|
53027
|
-
while (now() < deadline) {
|
|
53028
|
-
const polled = await client2.get(
|
|
53029
|
-
`/api/key-rotation/handoffs/${encodeURIComponent(jti)}`
|
|
53030
|
-
);
|
|
53031
|
-
const status = polled?.status;
|
|
53032
|
-
if (status === "approved") {
|
|
53033
|
-
if (typeof polled.token === "string" && polled.token.length > 0) {
|
|
53034
|
-
return polled.token;
|
|
53035
|
-
}
|
|
53036
|
-
throw stepUpError(
|
|
53037
|
-
"This approval was already collected by another request.",
|
|
53038
|
-
"Run the command again to request a new approval."
|
|
53039
|
-
);
|
|
53040
|
-
}
|
|
53041
|
-
if (status === "consumed") {
|
|
53042
|
-
throw stepUpError(
|
|
53043
|
-
"This approval was already used.",
|
|
53044
|
-
"Run the command again to request a new approval."
|
|
53045
|
-
);
|
|
53046
|
-
}
|
|
53047
|
-
if (status === "expired") {
|
|
53048
|
-
throw stepUpError(
|
|
53049
|
-
"The browser approval expired.",
|
|
53050
|
-
"Run the command again and approve within 5 minutes."
|
|
53051
|
-
);
|
|
53052
|
-
}
|
|
53053
|
-
await sleep3(interval);
|
|
53054
|
-
}
|
|
53055
|
-
throw stepUpError(
|
|
53056
|
-
"Timed out waiting for the browser approval.",
|
|
53057
|
-
"Run the command again and approve within 5 minutes."
|
|
53058
|
-
);
|
|
53059
|
-
}
|
|
53060
|
-
|
|
53061
53201
|
// src/lib/keys-device.ts
|
|
53062
53202
|
var DEFAULT_ENV_FILE = ".env.local";
|
|
53063
53203
|
var DETECT_TIMEOUT_MS = 5e3;
|
|
@@ -53066,7 +53206,7 @@ var REPORT_KEY = /^[A-Z_][A-Z0-9_]{0,127}$/;
|
|
|
53066
53206
|
var REPORT_PATH = /^[ -~]{1,200}$/;
|
|
53067
53207
|
var TOKEN_SHAPED = /[A-Za-z0-9]{33,}/;
|
|
53068
53208
|
var SECRET_PREFIX = /(gh[pousr]_|github_pat_|-----BEGIN)/;
|
|
53069
|
-
var
|
|
53209
|
+
var UUID7 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
53070
53210
|
function deviceSlot(workspaceId) {
|
|
53071
53211
|
return `key-rotation-device:${workspaceId}`;
|
|
53072
53212
|
}
|
|
@@ -53244,7 +53384,7 @@ async function enroll(client2, workspaceId, opts = {}) {
|
|
|
53244
53384
|
publicKey: pair.publicKey,
|
|
53245
53385
|
report
|
|
53246
53386
|
});
|
|
53247
|
-
const enrollmentId = typeof begun?.enrollmentId === "string" &&
|
|
53387
|
+
const enrollmentId = typeof begun?.enrollmentId === "string" && UUID7.test(begun.enrollmentId) ? begun.enrollmentId : null;
|
|
53248
53388
|
if (!enrollmentId) {
|
|
53249
53389
|
throw new CliError(
|
|
53250
53390
|
"The server did not start the enrollment.",
|
|
@@ -53262,7 +53402,7 @@ async function enroll(client2, workspaceId, opts = {}) {
|
|
|
53262
53402
|
action: "enroll",
|
|
53263
53403
|
approvalToken
|
|
53264
53404
|
});
|
|
53265
|
-
const deviceId = typeof done?.deviceId === "string" &&
|
|
53405
|
+
const deviceId = typeof done?.deviceId === "string" && UUID7.test(done.deviceId) ? done.deviceId : null;
|
|
53266
53406
|
if (!deviceId) {
|
|
53267
53407
|
throw new CliError(
|
|
53268
53408
|
"The server did not complete the enrollment.",
|
|
@@ -53291,7 +53431,7 @@ async function enroll(client2, workspaceId, opts = {}) {
|
|
|
53291
53431
|
}
|
|
53292
53432
|
|
|
53293
53433
|
// src/commands/keys.ts
|
|
53294
|
-
var
|
|
53434
|
+
var UUID8 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
53295
53435
|
function patRequired(apiUrl) {
|
|
53296
53436
|
return authError(
|
|
53297
53437
|
"gad keys needs a personal access token; this sign-in is an OAuth session.",
|
|
@@ -53344,7 +53484,7 @@ async function keysContext() {
|
|
|
53344
53484
|
});
|
|
53345
53485
|
const value = await probe.mcpCall("workspace_introspect", {}, 5e3);
|
|
53346
53486
|
const workspaceId = value?.workspace?.id;
|
|
53347
|
-
if (value?.error || !workspaceId || !
|
|
53487
|
+
if (value?.error || !workspaceId || !UUID8.test(workspaceId)) {
|
|
53348
53488
|
throw validationError(
|
|
53349
53489
|
"GreatArrow.ai could not confirm your workspace.",
|
|
53350
53490
|
"Run 'gad whoami', or choose one with 'gad --workspace <id>'."
|
|
@@ -53365,7 +53505,7 @@ async function keysContext() {
|
|
|
53365
53505
|
};
|
|
53366
53506
|
}
|
|
53367
53507
|
function requireRunId(runId) {
|
|
53368
|
-
if (!
|
|
53508
|
+
if (!UUID8.test(runId)) {
|
|
53369
53509
|
throw validationError(
|
|
53370
53510
|
`'${stripControlChars(runId)}' is not a run id.`,
|
|
53371
53511
|
"Copy the run id from 'gad keys list' or the run page."
|
|
@@ -53494,7 +53634,7 @@ function enrollCommand() {
|
|
|
53494
53634
|
function toUpdateRow(v) {
|
|
53495
53635
|
if (typeof v !== "object" || v === null) return null;
|
|
53496
53636
|
const r = v;
|
|
53497
|
-
if (typeof r.id !== "string" || !
|
|
53637
|
+
if (typeof r.id !== "string" || !UUID8.test(r.id)) return null;
|
|
53498
53638
|
const ref = {};
|
|
53499
53639
|
if (typeof r.targetRef === "object" && r.targetRef !== null) {
|
|
53500
53640
|
for (const [k, val] of Object.entries(r.targetRef)) {
|
|
@@ -54381,14 +54521,14 @@ var Temp = {
|
|
|
54381
54521
|
}
|
|
54382
54522
|
},
|
|
54383
54523
|
truncate: (filePath) => {
|
|
54384
|
-
const
|
|
54385
|
-
if (
|
|
54524
|
+
const basename4 = path3.basename(filePath);
|
|
54525
|
+
if (basename4.length <= LIMIT_BASENAME_LENGTH)
|
|
54386
54526
|
return filePath;
|
|
54387
|
-
const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(
|
|
54527
|
+
const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename4);
|
|
54388
54528
|
if (!truncable)
|
|
54389
54529
|
return filePath;
|
|
54390
|
-
const truncationLength =
|
|
54391
|
-
return `${filePath.slice(0, -
|
|
54530
|
+
const truncationLength = basename4.length - LIMIT_BASENAME_LENGTH;
|
|
54531
|
+
return `${filePath.slice(0, -basename4.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
|
|
54392
54532
|
}
|
|
54393
54533
|
};
|
|
54394
54534
|
node_default(Temp.purgeSyncAll);
|