@great-arrow/cli 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +380 -332
  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.2";
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"],
@@ -50463,21 +50647,6 @@ function worktreeGitBytes(layout, args, opts = {}) {
50463
50647
  return safeGitBytes(layout.worktree, [...layoutArgs(layout), ...args], opts);
50464
50648
  }
50465
50649
 
50466
- // src/lib/runner/boot-ticks.ts
50467
- import { readFileSync as readFileSync4 } from "node:fs";
50468
- var USER_HZ = 100;
50469
- function bootTicks(read = readFileSync4) {
50470
- if (process.platform !== "linux") return void 0;
50471
- try {
50472
- const seconds = Number.parseFloat(
50473
- read("/proc/uptime", "utf8").split(" ")[0] ?? ""
50474
- );
50475
- return Number.isFinite(seconds) ? Math.floor(seconds * USER_HZ) : void 0;
50476
- } catch {
50477
- return void 0;
50478
- }
50479
- }
50480
-
50481
50650
  // src/lib/runner/text.ts
50482
50651
  function cleanLine(text3) {
50483
50652
  return text3.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ").trim();
@@ -50833,7 +51002,6 @@ async function prepareWorktree(input2) {
50833
51002
  await privateDir(join13(runDir, "wt"));
50834
51003
  const path10 = join13(runDir, "wt", short(todoId));
50835
51004
  const branch = runnerBranch(runId, todoId);
50836
- const preparedAt = bootTicks();
50837
51005
  const fetch2 = await safeGit(repoRoot, [
50838
51006
  "fetch",
50839
51007
  "--no-tags",
@@ -50859,8 +51027,7 @@ async function prepareWorktree(input2) {
50859
51027
  worktreeGitDir: await gitPath(worktree, "--absolute-git-dir"),
50860
51028
  home: homedir10(),
50861
51029
  hooksDir: await configuredHooksDir(worktree, commonDir),
50862
- branch,
50863
- ...preparedAt === void 0 ? {} : { preparedAt }
51030
+ branch
50864
51031
  };
50865
51032
  const gad = join13(worktree, ".gad");
50866
51033
  await privateDir(gad);
@@ -51344,7 +51511,7 @@ import { join as join17 } from "node:path";
51344
51511
  import { createInterface as createInterface3 } from "node:readline";
51345
51512
 
51346
51513
  // src/lib/runner/agent.ts
51347
- import { execFile as execFile4, spawn as spawn2 } from "node:child_process";
51514
+ import { execFile as execFile4, spawn as spawn3 } from "node:child_process";
51348
51515
  import { accessSync, chmodSync as chmodSync3, closeSync, constants as constants2, openSync } from "node:fs";
51349
51516
  import { delimiter, join as join15 } from "node:path";
51350
51517
  var KILL_GRACE_MS = 5e3;
@@ -51412,7 +51579,7 @@ async function runAgent(input2) {
51412
51579
  const env4 = { ...stripCredentialEnv(process.env), GIT_TERMINAL_PROMPT: "0" };
51413
51580
  let child;
51414
51581
  try {
51415
- child = spawn2(input2.bin, agentArgv(input2), {
51582
+ child = spawn3(input2.bin, agentArgv(input2), {
51416
51583
  cwd: input2.layout.worktree,
51417
51584
  detached: true,
51418
51585
  stdio: ["ignore", fd, fd],
@@ -51483,7 +51650,7 @@ async function reapGroup(pgid, waitMs = REAP_WAIT_MS) {
51483
51650
 
51484
51651
  // src/lib/runner/holders.ts
51485
51652
  import { execFile as execFile5 } from "node:child_process";
51486
- import { readFileSync as readFileSync5, readdirSync, readlinkSync, statSync as statSync2 } from "node:fs";
51653
+ import { readdirSync, readlinkSync, statSync as statSync2 } from "node:fs";
51487
51654
  var LSOF_DEADLINE_MS = 2e4;
51488
51655
  var CHECKS = 5;
51489
51656
  var PAUSE_MS = 250;
@@ -51595,18 +51762,8 @@ async function lsofUsers(root, uid, exec2 = run3) {
51595
51762
  for (const h of holders) if (!await isIndexer(h, exec2)) users.push(h.pid);
51596
51763
  return users;
51597
51764
  }
51598
- function startTicks(pid, fs6) {
51599
- try {
51600
- const stat3 = (fs6.readFileSync ?? readFileSync5)(`/proc/${pid}/stat`, "utf8");
51601
- const after = stat3.slice(stat3.lastIndexOf(")") + 1).trim().split(/\s+/);
51602
- const ticks = Number(after[19]);
51603
- return Number.isSafeInteger(ticks) ? ticks : null;
51604
- } catch {
51605
- return null;
51606
- }
51607
- }
51608
51765
  var errno = (error62) => error62.code;
51609
- function procUsers(root, uid, fs6 = { readdirSync, statSync: statSync2, readlinkSync, readFileSync: readFileSync5 }, notBefore) {
51766
+ function procUsers(root, uid, fs6 = { readdirSync, statSync: statSync2, readlinkSync }) {
51610
51767
  let entries;
51611
51768
  try {
51612
51769
  entries = fs6.readdirSync("/proc").filter((e) => /^\d+$/.test(e));
@@ -51636,24 +51793,20 @@ function procUsers(root, uid, fs6 = { readdirSync, statSync: statSync2, readlink
51636
51793
  if (holds) users.push(Number(pid));
51637
51794
  } catch (error62) {
51638
51795
  if (errno(error62) === "ENOENT" || errno(error62) === "ESRCH") continue;
51639
- if (errno(error62) === "EACCES" && notBefore !== void 0) {
51640
- const started = startTicks(pid, fs6);
51641
- if (started !== null && started < notBefore) continue;
51642
- }
51643
51796
  return null;
51644
51797
  }
51645
51798
  }
51646
51799
  return users;
51647
51800
  }
51648
- async function processesUsing(root, notBefore) {
51801
+ async function processesUsing(root) {
51649
51802
  const uid = process.getuid?.();
51650
51803
  if (uid === void 0) return null;
51651
- const found = process.platform === "darwin" ? await lsofUsers(root, uid) : process.platform === "linux" ? procUsers(root, uid, void 0, notBefore) : null;
51804
+ const found = process.platform === "darwin" ? await lsofUsers(root, uid) : process.platform === "linux" ? procUsers(root, uid) : null;
51652
51805
  return found?.filter((pid) => pid !== process.pid) ?? null;
51653
51806
  }
51654
- async function worktreeUnused(root, notBefore) {
51807
+ async function worktreeUnused(root) {
51655
51808
  for (let check2 = 0; check2 < CHECKS; check2++) {
51656
- const users = await processesUsing(root, notBefore);
51809
+ const users = await processesUsing(root);
51657
51810
  if (users === null) return false;
51658
51811
  if (users.length === 0) return true;
51659
51812
  if (check2 + 1 < CHECKS) await new Promise((r) => setTimeout(r, PAUSE_MS));
@@ -52059,7 +52212,7 @@ async function headSha(layout, opts) {
52059
52212
  }
52060
52213
  async function publishBranch(layout, branch, base, message, signal, options = {}) {
52061
52214
  if (!await verifyGitfile(layout)) return { failed: "sandbox_tampered" };
52062
- if (!await worktreeUnused(layout.worktree, layout.preparedAt))
52215
+ if (!await worktreeUnused(layout.worktree))
52063
52216
  return { failed: "agent_still_running" };
52064
52217
  if (!RUNNER_BRANCH.test(branch) || branch === base || !BASE_NAME.test(base))
52065
52218
  return { failed: "bad_branch" };
@@ -52641,7 +52794,7 @@ import { createInterface as createInterface4 } from "node:readline";
52641
52794
  import { Writable } from "node:stream";
52642
52795
 
52643
52796
  // src/lib/keys-apply.ts
52644
- import { spawn as spawn3 } from "node:child_process";
52797
+ import { spawn as spawn4 } from "node:child_process";
52645
52798
  import {
52646
52799
  chmod as chmod6,
52647
52800
  mkdir as mkdir10,
@@ -52666,7 +52819,7 @@ function runWithStdin(file2, args, input2, timeoutMs) {
52666
52819
  };
52667
52820
  let child;
52668
52821
  try {
52669
- child = spawn3(file2, args, { stdio: ["pipe", "ignore", "ignore"] });
52822
+ child = spawn4(file2, args, { stdio: ["pipe", "ignore", "ignore"] });
52670
52823
  } catch {
52671
52824
  resolve7("not_found");
52672
52825
  return;
@@ -52984,111 +53137,6 @@ function openFromServer(sealed, devicePrivateKeyB64) {
52984
53137
  }
52985
53138
  }
52986
53139
 
52987
- // src/lib/keys-handoff.ts
52988
- import { spawn as spawn4 } from "node:child_process";
52989
-
52990
- // src/lib/keys-paths.ts
52991
- function keyRotationPath() {
52992
- return "/settings/key-rotation";
52993
- }
52994
- function keyRotationRunPath(runId) {
52995
- return `/settings/key-rotation/runs/${encodeURIComponent(runId)}`;
52996
- }
52997
- function keyRotationHandoffPath(jti) {
52998
- return `/settings/key-rotation/handoff/${encodeURIComponent(jti)}`;
52999
- }
53000
-
53001
- // src/lib/keys-handoff.ts
53002
- var HANDOFF_POLL_INTERVAL_MS = 2e3;
53003
- var HANDOFF_DEADLINE_MS = 5 * 6e4;
53004
- var BROWSER_LAUNCH_TIMEOUT_MS = 5e3;
53005
- function openInBrowser(url2) {
53006
- const command = browserLaunchCommand(url2);
53007
- if (!command) return Promise.resolve(false);
53008
- return new Promise((resolve7) => {
53009
- let settled = false;
53010
- const done = (ok2) => {
53011
- if (settled) return;
53012
- settled = true;
53013
- clearTimeout(timer);
53014
- resolve7(ok2);
53015
- };
53016
- const timer = setTimeout(() => done(false), BROWSER_LAUNCH_TIMEOUT_MS);
53017
- timer.unref?.();
53018
- try {
53019
- const child = spawn4(command.file, command.args, {
53020
- detached: true,
53021
- stdio: "ignore"
53022
- });
53023
- child.once("error", () => done(false));
53024
- child.once("spawn", () => {
53025
- child.unref();
53026
- done(true);
53027
- });
53028
- } catch {
53029
- done(false);
53030
- }
53031
- });
53032
- }
53033
- var defaultSleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
53034
- function stepUpError(message, hint) {
53035
- return new CliError(message, 1 /* AUTH_ERROR */, hint);
53036
- }
53037
- async function requestBrowserStepUp(client2, kind, subjectId, opts = {}) {
53038
- const openUrl = opts.openUrl ?? openInBrowser;
53039
- const notify = opts.notify ?? ((line) => process.stderr.write(`${line}
53040
- `));
53041
- const sleep3 = opts.sleep ?? defaultSleep;
53042
- const now = opts.now ?? Date.now;
53043
- const interval = opts.pollIntervalMs ?? HANDOFF_POLL_INTERVAL_MS;
53044
- const deadline = now() + (opts.deadlineMs ?? HANDOFF_DEADLINE_MS);
53045
- const created = await client2.post("/api/key-rotation/handoffs", {
53046
- kind,
53047
- subjectId
53048
- });
53049
- const jti = typeof created?.jti === "string" ? created.jti : null;
53050
- if (!jti || !/^[A-Za-z0-9-]{1,64}$/.test(jti)) {
53051
- throw stepUpError("The server did not return a browser approval.");
53052
- }
53053
- const url2 = `${client2.apiOrigin}${keyRotationHandoffPath(jti)}`;
53054
- notify(`Approve this in your browser (passkey or two-step code): ${url2}`);
53055
- if (!await openUrl(url2)) {
53056
- notify("Couldn't launch a browser. Open the URL above to approve.");
53057
- }
53058
- while (now() < deadline) {
53059
- const polled = await client2.get(
53060
- `/api/key-rotation/handoffs/${encodeURIComponent(jti)}`
53061
- );
53062
- const status = polled?.status;
53063
- if (status === "approved") {
53064
- if (typeof polled.token === "string" && polled.token.length > 0) {
53065
- return polled.token;
53066
- }
53067
- throw stepUpError(
53068
- "This approval was already collected by another request.",
53069
- "Run the command again to request a new approval."
53070
- );
53071
- }
53072
- if (status === "consumed") {
53073
- throw stepUpError(
53074
- "This approval was already used.",
53075
- "Run the command again to request a new approval."
53076
- );
53077
- }
53078
- if (status === "expired") {
53079
- throw stepUpError(
53080
- "The browser approval expired.",
53081
- "Run the command again and approve within 5 minutes."
53082
- );
53083
- }
53084
- await sleep3(interval);
53085
- }
53086
- throw stepUpError(
53087
- "Timed out waiting for the browser approval.",
53088
- "Run the command again and approve within 5 minutes."
53089
- );
53090
- }
53091
-
53092
53140
  // src/lib/keys-device.ts
53093
53141
  var DEFAULT_ENV_FILE = ".env.local";
53094
53142
  var DETECT_TIMEOUT_MS = 5e3;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@great-arrow/cli",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "GreatArrow.ai CLI tool",
5
5
  "type": "module",
6
6
  "bin": {