@great-arrow/cli 0.9.3 → 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.
Files changed (2) hide show
  1. package/dist/index.js +372 -293
  2. 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.3";
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/lib/global-opts.ts
12105
- function globalOutputOpts(cmd) {
12106
- const merged = cmd.optsWithGlobals();
12107
- return {
12108
- json: Boolean(merged.json),
12109
- quiet: Boolean(merged.quiet),
12110
- // --no-color sets `color: false`; some commands declare `--no-color`
12111
- // locally which surfaces as `noColor`.
12112
- noColor: merged.color === false || Boolean(merged.noColor)
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 execFile2, execFileSync as execFileSync2 } from "node:child_process";
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
- execFile2(
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 = spawn("git", argv, {
50573
+ const child = spawn2("git", argv, {
50390
50574
  cwd,
50391
50575
  env: { ...gitEnv(), ...opts.env ?? {} },
50392
50576
  stdio: ["pipe", "pipe", "pipe"],
@@ -51327,7 +51511,7 @@ import { join as join17 } from "node:path";
51327
51511
  import { createInterface as createInterface3 } from "node:readline";
51328
51512
 
51329
51513
  // src/lib/runner/agent.ts
51330
- import { execFile as execFile4, spawn as spawn2 } from "node:child_process";
51514
+ import { execFile as execFile4, spawn as spawn3 } from "node:child_process";
51331
51515
  import { accessSync, chmodSync as chmodSync3, closeSync, constants as constants2, openSync } from "node:fs";
51332
51516
  import { delimiter, join as join15 } from "node:path";
51333
51517
  var KILL_GRACE_MS = 5e3;
@@ -51395,7 +51579,7 @@ async function runAgent(input2) {
51395
51579
  const env4 = { ...stripCredentialEnv(process.env), GIT_TERMINAL_PROMPT: "0" };
51396
51580
  let child;
51397
51581
  try {
51398
- child = spawn2(input2.bin, agentArgv(input2), {
51582
+ child = spawn3(input2.bin, agentArgv(input2), {
51399
51583
  cwd: input2.layout.worktree,
51400
51584
  detached: true,
51401
51585
  stdio: ["ignore", fd, fd],
@@ -52610,7 +52794,7 @@ import { createInterface as createInterface4 } from "node:readline";
52610
52794
  import { Writable } from "node:stream";
52611
52795
 
52612
52796
  // src/lib/keys-apply.ts
52613
- import { spawn as spawn3 } from "node:child_process";
52797
+ import { spawn as spawn4 } from "node:child_process";
52614
52798
  import {
52615
52799
  chmod as chmod6,
52616
52800
  mkdir as mkdir10,
@@ -52635,7 +52819,7 @@ function runWithStdin(file2, args, input2, timeoutMs) {
52635
52819
  };
52636
52820
  let child;
52637
52821
  try {
52638
- child = spawn3(file2, args, { stdio: ["pipe", "ignore", "ignore"] });
52822
+ child = spawn4(file2, args, { stdio: ["pipe", "ignore", "ignore"] });
52639
52823
  } catch {
52640
52824
  resolve7("not_found");
52641
52825
  return;
@@ -52953,111 +53137,6 @@ function openFromServer(sealed, devicePrivateKeyB64) {
52953
53137
  }
52954
53138
  }
52955
53139
 
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
53140
  // src/lib/keys-device.ts
53062
53141
  var DEFAULT_ENV_FILE = ".env.local";
53063
53142
  var DETECT_TIMEOUT_MS = 5e3;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@great-arrow/cli",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
4
4
  "description": "GreatArrow.ai CLI tool",
5
5
  "type": "module",
6
6
  "bin": {