@great-arrow/cli 0.1.53 → 0.1.54

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 +290 -63
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9402,7 +9402,7 @@ function isNodeError(value) {
9402
9402
 
9403
9403
  // src/lib/version.ts
9404
9404
  var CLI_PACKAGE_NAME = "@great-arrow/cli";
9405
- var CLI_VERSION = "0.1.53";
9405
+ var CLI_VERSION = "0.1.54";
9406
9406
  var GAD_INSTALL_VERSION = "2026.08.08";
9407
9407
  var GAD_INSTALL_MIN_VERSION = "2026.06.22";
9408
9408
  var GAD_INSTALL_HEADER = "X-GAD-Install-Version";
@@ -9411,6 +9411,9 @@ var GAD_CLI_VERSION_HEADER = "X-GAD-CLI-Version";
9411
9411
  // src/commands/login.ts
9412
9412
  import { execFile } from "node:child_process";
9413
9413
 
9414
+ // src/lib/credentials.ts
9415
+ import { randomUUID } from "node:crypto";
9416
+
9414
9417
  // src/lib/keychain.ts
9415
9418
  import { mkdir, writeFile, readFile, unlink } from "node:fs/promises";
9416
9419
  import { homedir } from "node:os";
@@ -9532,7 +9535,12 @@ async function loadStoredCredential() {
9532
9535
  return parseStoredValue(await getToken());
9533
9536
  }
9534
9537
  async function saveOAuthCredential(credential) {
9535
- return setToken(JSON.stringify(credential));
9538
+ return setToken(
9539
+ JSON.stringify({
9540
+ ...credential,
9541
+ localSessionId: credential.localSessionId || randomUUID()
9542
+ })
9543
+ );
9536
9544
  }
9537
9545
  async function savePatToken(token) {
9538
9546
  return setToken(token);
@@ -9903,14 +9911,33 @@ function toCredential(env4, apiUrl, clientId, now) {
9903
9911
  }
9904
9912
  var TOKEN_POST_TIMEOUT_MS = 8e3;
9905
9913
  async function postToken(apiUrl, form, fetchImpl, timeoutMs = TOKEN_POST_TIMEOUT_MS) {
9906
- const res = await fetchImpl(`${trimSlash2(apiUrl)}/api/oauth/token`, {
9907
- method: "POST",
9908
- headers: TOKEN_HEADERS,
9909
- body: new URLSearchParams(form).toString(),
9910
- signal: AbortSignal.timeout(timeoutMs)
9914
+ const controller = new AbortController();
9915
+ let timer;
9916
+ const deadline = new Promise((_resolve, reject) => {
9917
+ timer = setTimeout(() => {
9918
+ const error = new DOMException(
9919
+ "Token exchange timed out.",
9920
+ "TimeoutError"
9921
+ );
9922
+ controller.abort(error);
9923
+ reject(error);
9924
+ }, timeoutMs);
9911
9925
  });
9912
- const json = await res.json().catch(() => ({}));
9913
- return { ok: res.ok, status: res.status, json };
9926
+ const request = async () => {
9927
+ const res = await fetchImpl(`${trimSlash2(apiUrl)}/api/oauth/token`, {
9928
+ method: "POST",
9929
+ headers: TOKEN_HEADERS,
9930
+ body: new URLSearchParams(form).toString(),
9931
+ signal: controller.signal
9932
+ });
9933
+ const json = await res.json().catch(() => ({}));
9934
+ return { ok: res.ok, status: res.status, json };
9935
+ };
9936
+ try {
9937
+ return await Promise.race([request(), deadline]);
9938
+ } finally {
9939
+ if (timer) clearTimeout(timer);
9940
+ }
9914
9941
  }
9915
9942
  async function exchangeCode(opts) {
9916
9943
  const now = opts.now ?? Date.now();
@@ -10364,6 +10391,20 @@ var login_default = loginCommand;
10364
10391
  import { mkdir as mkdir3, open, rm, stat } from "node:fs/promises";
10365
10392
  import { homedir as homedir4 } from "node:os";
10366
10393
  import { join as join4 } from "node:path";
10394
+
10395
+ // src/lib/oauth/session-identity.ts
10396
+ import { createHash as createHash2 } from "node:crypto";
10397
+ function credentialSessionId(credential) {
10398
+ return credential.localSessionId || createHash2("sha256").update(
10399
+ JSON.stringify([
10400
+ credential.clientId,
10401
+ credential.apiUrl,
10402
+ credential.refreshToken
10403
+ ])
10404
+ ).digest("hex");
10405
+ }
10406
+
10407
+ // src/lib/auth.ts
10367
10408
  var REFRESH_SKEW_MS = 12e4;
10368
10409
  var inflightRefresh = null;
10369
10410
  var REFRESH_LOCK_STALE_MS = 15e3;
@@ -10387,7 +10428,12 @@ async function acquireRefreshLock() {
10387
10428
  await handle.close();
10388
10429
  return true;
10389
10430
  } catch (err) {
10390
- if (err.code !== "EEXIST") return true;
10431
+ if (err.code !== "EEXIST") {
10432
+ throw networkError(
10433
+ "Could not lock session renewal.",
10434
+ "Check local credential storage and retry; your session has been kept."
10435
+ );
10436
+ }
10391
10437
  }
10392
10438
  const age = await stat(path10).then((st) => Date.now() - st.mtimeMs).catch(() => Number.POSITIVE_INFINITY);
10393
10439
  if (age > REFRESH_LOCK_STALE_MS) {
@@ -10416,8 +10462,13 @@ async function storedPairOtherThan(ours) {
10416
10462
  } catch (err) {
10417
10463
  debugSwallow("refresh.reread", err);
10418
10464
  }
10419
- if (stored?.kind !== "oauth") return null;
10420
- if (stored.credential.clientId !== ours.clientId || stored.credential.apiUrl.replace(/\/$/, "") !== ours.apiUrl.replace(/\/$/, "")) {
10465
+ if (stored?.kind !== "oauth") {
10466
+ throw authError(
10467
+ "The original session changed or ended during renewal.",
10468
+ "Restart this command with your current connection."
10469
+ );
10470
+ }
10471
+ if (ours.localSessionId && credentialSessionId(stored.credential) !== credentialSessionId(ours) || stored.credential.clientId !== ours.clientId || stored.credential.apiUrl.replace(/\/$/, "") !== ours.apiUrl.replace(/\/$/, "")) {
10421
10472
  throw networkError(
10422
10473
  "The stored session changed while renewing it.",
10423
10474
  "Retry the command with the current session."
@@ -10443,11 +10494,32 @@ async function resolveAuth() {
10443
10494
  if (stored?.kind === "oauth") {
10444
10495
  const fresh = await ensureFreshAccessToken(stored.credential);
10445
10496
  warnLegacyApiUrl(fresh.apiUrl);
10497
+ const originalSessionId = credentialSessionId(fresh);
10498
+ const originalApiUrl = fresh.apiUrl;
10499
+ const originalClientId = fresh.clientId;
10446
10500
  return withWorkspace({
10447
10501
  token: fresh.accessToken,
10448
10502
  apiUrl: fresh.apiUrl,
10449
10503
  tokenType: "oauth",
10450
- expiresAt: fresh.accessExpiresAt
10504
+ expiresAt: fresh.accessExpiresAt,
10505
+ getToken: async () => {
10506
+ const latestConfig = await loadConfig();
10507
+ const latest = await loadStoredCredential();
10508
+ if ((latestConfig.api_url || DEFAULT_API_URL) !== configApiUrl || (latestConfig.default_workspace || void 0) !== activeWorkspace || process.env.GAD_TOKEN || latest?.kind !== "oauth" || credentialSessionId(latest.credential) !== originalSessionId || latest.credential.apiUrl !== originalApiUrl || latest.credential.clientId !== originalClientId) {
10509
+ throw authError(
10510
+ "The original session or workspace changed or ended.",
10511
+ "Restart this command with your current connection."
10512
+ );
10513
+ }
10514
+ const renewed = await ensureFreshAccessToken(latest.credential);
10515
+ if (credentialSessionId(renewed) !== originalSessionId) {
10516
+ throw authError(
10517
+ "The original session changed.",
10518
+ "Restart this command with your current connection."
10519
+ );
10520
+ }
10521
+ return renewed.accessToken;
10522
+ }
10451
10523
  });
10452
10524
  }
10453
10525
  if (stored?.kind === "pat") {
@@ -10502,10 +10574,6 @@ async function ensureFreshAccessToken(credential) {
10502
10574
  if (Date.now() < credential.accessExpiresAt - REFRESH_SKEW_MS) {
10503
10575
  return credential;
10504
10576
  }
10505
- if (Date.now() >= credential.refreshExpiresAt) {
10506
- await clearStoredCredential();
10507
- throw sessionExpired();
10508
- }
10509
10577
  inflightRefresh ??= doRefresh(credential);
10510
10578
  try {
10511
10579
  return await inflightRefresh;
@@ -10529,12 +10597,32 @@ async function doRefresh(credential) {
10529
10597
  "Retry shortly; your saved session has been kept."
10530
10598
  );
10531
10599
  }
10532
- const refreshed = await refreshTokens({
10600
+ if (Date.now() >= current.refreshExpiresAt) {
10601
+ await clearStoredCredential();
10602
+ throw sessionExpired();
10603
+ }
10604
+ const rotated = await refreshTokens({
10533
10605
  apiUrl: current.apiUrl,
10534
10606
  clientId: current.clientId,
10535
10607
  refreshToken: current.refreshToken
10536
10608
  });
10537
- await saveOAuthCredential(refreshed);
10609
+ const refreshed = {
10610
+ ...rotated,
10611
+ localSessionId: credentialSessionId(current)
10612
+ };
10613
+ const saved = await loadStoredCredential();
10614
+ if (saved?.kind !== "oauth" || credentialSessionId(saved.credential) !== credentialSessionId(current)) {
10615
+ throw authError(
10616
+ "The original session changed or ended during renewal.",
10617
+ "Restart this command with your current connection."
10618
+ );
10619
+ }
10620
+ if (!await saveOAuthCredential(refreshed)) {
10621
+ throw networkError(
10622
+ "Could not persist the renewed session.",
10623
+ "Check your credential storage before retrying."
10624
+ );
10625
+ }
10538
10626
  return refreshed;
10539
10627
  } catch (err) {
10540
10628
  if (err instanceof RefreshRejected) {
@@ -10890,12 +10978,14 @@ function sleep(ms) {
10890
10978
  }
10891
10979
  var GadClient = class {
10892
10980
  token;
10981
+ getToken;
10893
10982
  apiUrl;
10894
10983
  repoUrl;
10895
10984
  workspaceId;
10896
10985
  timeout;
10897
10986
  constructor(options) {
10898
10987
  this.token = options.token;
10988
+ this.getToken = options.getToken;
10899
10989
  this.apiUrl = options.apiUrl.replace(/\/$/, "");
10900
10990
  this.repoUrl = options.repoUrl ?? detectGitOrigin();
10901
10991
  this.workspaceId = options.workspaceId?.trim() || getCommandWorkspaceTarget({ hasRepo: Boolean(this.repoUrl) }) || null;
@@ -10931,8 +11021,28 @@ var GadClient = class {
10931
11021
  const controller = new AbortController();
10932
11022
  const timer = setTimeout(() => controller.abort(), effectiveTimeout);
10933
11023
  try {
11024
+ const headers = this.getToken ? new Headers(init.headers) : void 0;
11025
+ if (headers && this.getToken) {
11026
+ const token = await Promise.race([
11027
+ this.getToken(),
11028
+ new Promise((_resolve, reject) => {
11029
+ controller.signal.addEventListener(
11030
+ "abort",
11031
+ () => reject(
11032
+ networkError(
11033
+ "Session renewal timed out.",
11034
+ "Retry shortly; your saved session has been kept."
11035
+ )
11036
+ ),
11037
+ { once: true }
11038
+ );
11039
+ })
11040
+ ]);
11041
+ headers.set("Authorization", `Bearer ${token}`);
11042
+ }
10934
11043
  const response = await fetch(url, {
10935
11044
  ...init,
11045
+ ...headers ? { headers: Object.fromEntries(headers) } : {},
10936
11046
  signal: controller.signal
10937
11047
  });
10938
11048
  clearTimeout(timer);
@@ -11181,6 +11291,7 @@ function whoamiCommand() {
11181
11291
  const auth = await resolveAuth();
11182
11292
  const client = new GadClient({
11183
11293
  token: auth.token,
11294
+ getToken: auth.getToken,
11184
11295
  apiUrl: auth.apiUrl
11185
11296
  });
11186
11297
  let result;
@@ -11291,7 +11402,11 @@ var detailFields = [
11291
11402
  ];
11292
11403
  async function buildClient() {
11293
11404
  const auth = await resolveAuth();
11294
- return new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
11405
+ return new GadClient({
11406
+ token: auth.token,
11407
+ getToken: auth.getToken,
11408
+ apiUrl: auth.apiUrl
11409
+ });
11295
11410
  }
11296
11411
  function extractPreview(item) {
11297
11412
  if (typeof item.content === "string") {
@@ -11469,7 +11584,11 @@ var memberColumns = [
11469
11584
  ];
11470
11585
  async function createClient() {
11471
11586
  const auth = await resolveAuth();
11472
- return new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
11587
+ return new GadClient({
11588
+ token: auth.token,
11589
+ getToken: auth.getToken,
11590
+ apiUrl: auth.apiUrl
11591
+ });
11473
11592
  }
11474
11593
  var listCommand = new Command("list").description("List all accessible workspaces").option("--json", "Output as JSON").action(async (options) => {
11475
11594
  const jsonFlag = Boolean(
@@ -11663,7 +11782,11 @@ var listCommand2 = new Command("list").description("List open todos sorted by pr
11663
11782
  noColor: parentOpts.noColor || false
11664
11783
  });
11665
11784
  const auth = await resolveAuth();
11666
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
11785
+ const client = new GadClient({
11786
+ token: auth.token,
11787
+ getToken: auth.getToken,
11788
+ apiUrl: auth.apiUrl
11789
+ });
11667
11790
  const args = {
11668
11791
  status: options.status ?? "open"
11669
11792
  };
@@ -11689,7 +11812,11 @@ var createCommand = new Command("create").description("Create a new todo card").
11689
11812
  noColor: false
11690
11813
  });
11691
11814
  const auth = await resolveAuth();
11692
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
11815
+ const client = new GadClient({
11816
+ token: auth.token,
11817
+ getToken: auth.getToken,
11818
+ apiUrl: auth.apiUrl
11819
+ });
11693
11820
  const args = { title };
11694
11821
  if (options.priority) {
11695
11822
  args.priority = options.priority;
@@ -11717,7 +11844,11 @@ var doneCommand = new Command("done").description("Mark a todo as done").argumen
11717
11844
  noColor: false
11718
11845
  });
11719
11846
  const auth = await resolveAuth();
11720
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
11847
+ const client = new GadClient({
11848
+ token: auth.token,
11849
+ getToken: auth.getToken,
11850
+ apiUrl: auth.apiUrl
11851
+ });
11721
11852
  await client.mcpCall("todo_update", { id, status: "done" });
11722
11853
  fmt.success(`Todo ${id} marked as done.`);
11723
11854
  });
@@ -11736,7 +11867,11 @@ var updateCommand = new Command("update").description("Update a todo's status").
11736
11867
  process.exit(3);
11737
11868
  }
11738
11869
  const auth = await resolveAuth();
11739
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
11870
+ const client = new GadClient({
11871
+ token: auth.token,
11872
+ getToken: auth.getToken,
11873
+ apiUrl: auth.apiUrl
11874
+ });
11740
11875
  await client.mcpCall("todo_update", { id, status: options.status });
11741
11876
  fmt.success(`Todo ${id} updated to status: ${options.status}`);
11742
11877
  });
@@ -11778,7 +11913,11 @@ var getCommand = new Command("get").description("Show one card's full state (ass
11778
11913
  noColor: parentOpts.noColor || false
11779
11914
  });
11780
11915
  const auth = await resolveAuth();
11781
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
11916
+ const client = new GadClient({
11917
+ token: auth.token,
11918
+ getToken: auth.getToken,
11919
+ apiUrl: auth.apiUrl
11920
+ });
11782
11921
  const result = await client.mcpCall("todo_get", { id });
11783
11922
  if (!result || typeof result !== "object" || result.error) {
11784
11923
  fmt.error(`Todo ${id} not found in this workspace.`);
@@ -11838,7 +11977,11 @@ var assignCommand = new Command("assign").description("Assign a card to a specia
11838
11977
  process.exit(3);
11839
11978
  }
11840
11979
  const auth = await resolveAuth();
11841
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
11980
+ const client = new GadClient({
11981
+ token: auth.token,
11982
+ getToken: auth.getToken,
11983
+ apiUrl: auth.apiUrl
11984
+ });
11842
11985
  const result = await client.mcpCall("todo_assign", built.args);
11843
11986
  if (isToolError(result)) {
11844
11987
  fmt.error(toolErrorMessage(result));
@@ -11866,7 +12009,11 @@ var runCommand = new Command("run").description("Start the agent already queued
11866
12009
  noColor: false
11867
12010
  });
11868
12011
  const auth = await resolveAuth();
11869
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
12012
+ const client = new GadClient({
12013
+ token: auth.token,
12014
+ getToken: auth.getToken,
12015
+ apiUrl: auth.apiUrl
12016
+ });
11870
12017
  const result = await client.post(
11871
12018
  `/api/todos/${encodeURIComponent(id)}/run`,
11872
12019
  {}
@@ -11954,7 +12101,11 @@ var claimCommand = new Command("claim").description("Preflight + atomically clai
11954
12101
  noColor: parentOpts.noColor || false
11955
12102
  });
11956
12103
  const auth = await resolveAuth();
11957
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
12104
+ const client = new GadClient({
12105
+ token: auth.token,
12106
+ getToken: auth.getToken,
12107
+ apiUrl: auth.apiUrl
12108
+ });
11958
12109
  if (!options.force) {
11959
12110
  tryGit(["fetch", "--quiet", "origin", "main"]);
11960
12111
  const log = tryGit(["log", "--format=%s", "-n", "10", "origin/main"]) ?? tryGit(["log", "--format=%s", "-n", "10", "FETCH_HEAD"]) ?? "";
@@ -12532,6 +12683,7 @@ function workCommand() {
12532
12683
  }
12533
12684
  const client = new GadClient({
12534
12685
  token: auth.token,
12686
+ getToken: auth.getToken,
12535
12687
  apiUrl: auth.apiUrl
12536
12688
  });
12537
12689
  const data = await client.get("/api/work/status", {
@@ -12556,6 +12708,7 @@ function workCommand() {
12556
12708
  const auth = await resolveAuth();
12557
12709
  const client = new GadClient({
12558
12710
  token: auth.token,
12711
+ getToken: auth.getToken,
12559
12712
  apiUrl: auth.apiUrl
12560
12713
  });
12561
12714
  const report = options.file ? JSON.parse(readFileSync(options.file, "utf8")) : await collectWorkStatus({
@@ -13089,7 +13242,11 @@ function normalizeResponse(value) {
13089
13242
  }
13090
13243
  async function createClient2() {
13091
13244
  const auth = await resolveAuth();
13092
- return new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
13245
+ return new GadClient({
13246
+ token: auth.token,
13247
+ getToken: auth.getToken,
13248
+ apiUrl: auth.apiUrl
13249
+ });
13093
13250
  }
13094
13251
  function renderGuardrails(data, cmd) {
13095
13252
  const output = globalOutputOpts(cmd);
@@ -14216,7 +14373,7 @@ async function ensureHookCredential(opts) {
14216
14373
  }
14217
14374
 
14218
14375
  // src/lib/mcp-doctor.ts
14219
- import { createHash as createHash2 } from "node:crypto";
14376
+ import { createHash as createHash3 } from "node:crypto";
14220
14377
 
14221
14378
  // src/lib/mcp-target.ts
14222
14379
  var GREAT_ARROW_HOSTS = /* @__PURE__ */ new Set([
@@ -14555,7 +14712,7 @@ function connectorGuidance(entries) {
14555
14712
  return 'If you ALSO installed the Great Arrow connector on claude.ai, Claude Code counts it as a second server and nags "needs authentication" forever. Silence the duplicate by adding its name to "deniedMcpServers" in ~/.claude/settings.json ("disabledMcpServers" is not a valid key; "disabledMcpjsonServers" only covers .mcp.json servers).';
14556
14713
  }
14557
14714
  function fingerprint(value) {
14558
- return createHash2("sha256").update(value).digest("hex").slice(0, 12);
14715
+ return createHash3("sha256").update(value).digest("hex").slice(0, 12);
14559
14716
  }
14560
14717
  function describeStoredCredential(raw) {
14561
14718
  const trimmed = raw?.trim();
@@ -14829,7 +14986,11 @@ async function checkApi(auth) {
14829
14986
  hint: "Fix authentication first."
14830
14987
  };
14831
14988
  }
14832
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
14989
+ const client = new GadClient({
14990
+ token: auth.token,
14991
+ getToken: auth.getToken,
14992
+ apiUrl: auth.apiUrl
14993
+ });
14833
14994
  try {
14834
14995
  await client.get("/api/health");
14835
14996
  return {
@@ -14855,7 +15016,11 @@ async function checkCliVersion(auth) {
14855
15016
  hint: "Fix authentication first, then run 'gad doctor' again."
14856
15017
  };
14857
15018
  }
14858
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
15019
+ const client = new GadClient({
15020
+ token: auth.token,
15021
+ getToken: auth.getToken,
15022
+ apiUrl: auth.apiUrl
15023
+ });
14859
15024
  try {
14860
15025
  const result = await client.get("/api/mcp/version");
14861
15026
  const cli = result.updates?.cli ?? result.cli ?? {};
@@ -14901,7 +15066,11 @@ async function checkIntegrations(auth) {
14901
15066
  hint: "Fix authentication first."
14902
15067
  };
14903
15068
  }
14904
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
15069
+ const client = new GadClient({
15070
+ token: auth.token,
15071
+ getToken: auth.getToken,
15072
+ apiUrl: auth.apiUrl
15073
+ });
14905
15074
  try {
14906
15075
  const result = await client.mcpCall("integrations_list", {});
14907
15076
  const data = result ?? {};
@@ -14942,7 +15111,11 @@ async function checkMcp(auth) {
14942
15111
  hint: "Fix authentication first."
14943
15112
  };
14944
15113
  }
14945
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
15114
+ const client = new GadClient({
15115
+ token: auth.token,
15116
+ getToken: auth.getToken,
15117
+ apiUrl: auth.apiUrl
15118
+ });
14946
15119
  try {
14947
15120
  await client.mcpCall("workspace_introspect", {});
14948
15121
  return {
@@ -15550,7 +15723,11 @@ var integrationListColumns = [
15550
15723
  ];
15551
15724
  async function createClient3() {
15552
15725
  const auth = await resolveAuth();
15553
- return new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
15726
+ return new GadClient({
15727
+ token: auth.token,
15728
+ getToken: auth.getToken,
15729
+ apiUrl: auth.apiUrl
15730
+ });
15554
15731
  }
15555
15732
  function formatErrorDetail(rawError) {
15556
15733
  if (rawError === null || rawError === void 0) return null;
@@ -15661,13 +15838,13 @@ var integrationsCommand = new Command("integrations").description("Manage connec
15661
15838
  var integrations_default = integrationsCommand;
15662
15839
 
15663
15840
  // src/commands/mcp.ts
15664
- import { createHash as createHash3 } from "node:crypto";
15841
+ import { createHash as createHash4 } from "node:crypto";
15665
15842
  import { hostname } from "node:os";
15666
15843
 
15667
15844
  // src/lib/mcp-bridge.ts
15668
15845
  import { createInterface as createInterface2 } from "node:readline";
15669
15846
  import { execFileSync as execFileSync6 } from "node:child_process";
15670
- import { randomUUID } from "node:crypto";
15847
+ import { randomUUID as randomUUID2 } from "node:crypto";
15671
15848
  var BRIDGE_CLIENT_ID = "gad-cli";
15672
15849
  var DEFAULT_API_URL2 = "https://www.greatarrow.ai";
15673
15850
  var DEFAULT_TIMEOUT_MS3 = 9e4;
@@ -15830,7 +16007,7 @@ async function runStdioBridge(options) {
15830
16007
  `));
15831
16008
  const endpoint = `${options.auth.apiUrl}/api/mcp`;
15832
16009
  const timeoutMs = Number(env4.MEMORY_TIMEOUT_MS ?? DEFAULT_TIMEOUT_MS3);
15833
- const sessionId = (env4.GAD_SESSION_ID ?? env4.MEMORY_SESSION_ID)?.trim() || `${BRIDGE_CLIENT_ID}:${Date.now()}-${randomUUID().slice(0, 8)}`;
16010
+ const sessionId = (env4.GAD_SESSION_ID ?? env4.MEMORY_SESSION_ID)?.trim() || `${BRIDGE_CLIENT_ID}:${Date.now()}-${randomUUID2().slice(0, 8)}`;
15834
16011
  const repoUrl = (env4.GAD_REPO_URL ?? env4.MEMORY_REPO_URL)?.trim() || detectGitOrigin3();
15835
16012
  const workspaceId = (env4.GAD_WORKSPACE_ID ?? env4.GAD_WORKSPACE)?.trim() || getCommandWorkspaceTarget({ hasRepo: Boolean(repoUrl) });
15836
16013
  const headers = buildBridgeHeaders({
@@ -15994,7 +16171,11 @@ async function mcpJsonAtRiskOfCommit(dir) {
15994
16171
  }
15995
16172
  async function createClient4() {
15996
16173
  const auth = await resolveAuth();
15997
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
16174
+ const client = new GadClient({
16175
+ token: auth.token,
16176
+ getToken: auth.getToken,
16177
+ apiUrl: auth.apiUrl
16178
+ });
15998
16179
  return { client, apiUrl: auth.apiUrl };
15999
16180
  }
16000
16181
  function generateMcpConfig(client, apiUrl, token, repoUrl) {
@@ -16029,7 +16210,7 @@ function generateMcpConfig(client, apiUrl, token, repoUrl) {
16029
16210
  Authorization: `Bearer ${token}`,
16030
16211
  "X-MCP-Client": CLIENT_IDS[client],
16031
16212
  "X-Device-Name": deviceName,
16032
- "X-Machine-Id": createHash3("sha256").update(rawHost).digest("hex"),
16213
+ "X-Machine-Id": createHash4("sha256").update(rawHost).digest("hex"),
16033
16214
  [GAD_INSTALL_HEADER]: GAD_INSTALL_VERSION,
16034
16215
  ...repoUrl ? { "X-Repo-URL": repoUrl } : {}
16035
16216
  };
@@ -16294,7 +16475,11 @@ var testCommand = new Command("test").description(
16294
16475
  const c = createColor(false);
16295
16476
  const auth = await resolveAuth();
16296
16477
  const apiUrl = auth.apiUrl;
16297
- const client = new GadClient({ token: auth.token, apiUrl });
16478
+ const client = new GadClient({
16479
+ token: auth.token,
16480
+ getToken: auth.getToken,
16481
+ apiUrl
16482
+ });
16298
16483
  const verbose = !jsonFlag;
16299
16484
  const checks = [
16300
16485
  await checkDns(apiUrl, verbose, c),
@@ -16833,6 +17018,7 @@ function smokeCommand() {
16833
17018
  const auth = await resolveAuth();
16834
17019
  const client = new GadClient({
16835
17020
  token: auth.token,
17021
+ getToken: auth.getToken,
16836
17022
  apiUrl: auth.apiUrl
16837
17023
  });
16838
17024
  if (!isJson && !isQuiet) {
@@ -16880,7 +17066,11 @@ var agentListColumns = [
16880
17066
  ];
16881
17067
  async function buildClient2() {
16882
17068
  const auth = await resolveAuth();
16883
- return new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
17069
+ return new GadClient({
17070
+ token: auth.token,
17071
+ getToken: auth.getToken,
17072
+ apiUrl: auth.apiUrl
17073
+ });
16884
17074
  }
16885
17075
  function invokeOutcome(data) {
16886
17076
  if (typeof data === "string") {
@@ -17030,6 +17220,7 @@ function askCommand() {
17030
17220
  const auth = await resolveAuth();
17031
17221
  const client = new GadClient({
17032
17222
  token: auth.token,
17223
+ getToken: auth.getToken,
17033
17224
  apiUrl: auth.apiUrl
17034
17225
  });
17035
17226
  const fmt = createFormatter({
@@ -18022,26 +18213,36 @@ function authHeaders(token) {
18022
18213
  function origin(apiUrl) {
18023
18214
  return apiUrl.replace(/\/+$/, "");
18024
18215
  }
18025
- function appAwareFetch(apiUrl, token, base) {
18026
- return (input, init) => {
18216
+ function appAwareFetch(apiUrl, token, base, getToken2) {
18217
+ return async (input, init) => {
18027
18218
  const target = typeof input === "string" ? input : String(input);
18028
- if (!target.startsWith("/")) return base(input, init);
18029
- return base(`${origin(apiUrl)}${target}`, {
18219
+ const relative = target.startsWith("/") && !target.startsWith("//");
18220
+ if (!relative && !target.startsWith(`${origin(apiUrl)}/`))
18221
+ return base(input, init);
18222
+ const currentToken = getToken2 ? await withTimeout(15e3, () => getToken2()) : token;
18223
+ return base(relative ? `${origin(apiUrl)}${target}` : target, {
18030
18224
  ...init,
18031
18225
  headers: {
18032
18226
  ...init?.headers,
18033
- ...authHeaders(token)
18227
+ ...authHeaders(currentToken)
18034
18228
  }
18035
18229
  });
18036
18230
  };
18037
18231
  }
18038
18232
  async function withTimeout(ms, run2) {
18039
18233
  const controller = new AbortController();
18040
- const timer = setTimeout(() => controller.abort(), ms);
18234
+ let timer;
18235
+ const deadline = new Promise((_resolve, reject) => {
18236
+ timer = setTimeout(() => {
18237
+ const error = new DOMException("Request timed out", "AbortError");
18238
+ controller.abort(error);
18239
+ reject(error);
18240
+ }, ms);
18241
+ });
18041
18242
  try {
18042
- return await run2(controller.signal);
18243
+ return await Promise.race([run2(controller.signal), deadline]);
18043
18244
  } finally {
18044
- clearTimeout(timer);
18245
+ if (timer) clearTimeout(timer);
18045
18246
  }
18046
18247
  }
18047
18248
  function readHandoff(payload) {
@@ -18106,7 +18307,7 @@ async function askForTheTurn(opts) {
18106
18307
  if (!response.ok) {
18107
18308
  throw new LocalTurnFailed(
18108
18309
  "handoff",
18109
- response.status === 401 || response.status === 403 ? "Sign in again \u2014 this computer is not allowed to ask for work." : `Great Arrow refused that (${response.status}).`,
18310
+ response.status === 401 ? "Sign in again \u2014 this computer is not allowed to ask for work." : response.status === 403 ? "This connection needs permission. Review its scopes and workspace access at /account/connections." : `Great Arrow refused that (${response.status}).`,
18110
18311
  true
18111
18312
  );
18112
18313
  }
@@ -18168,14 +18369,14 @@ async function handBack(opts) {
18168
18369
  }
18169
18370
  async function answerOnThisComputer(opts) {
18170
18371
  const base = opts.fetchImpl ?? fetch;
18171
- const doFetch = appAwareFetch(opts.apiUrl, opts.token, base);
18372
+ const doFetch = appAwareFetch(opts.apiUrl, opts.token, base, opts.getToken);
18172
18373
  const handoff = await askForTheTurn({
18173
18374
  apiUrl: opts.apiUrl,
18174
18375
  token: opts.token,
18175
18376
  question: opts.question,
18176
18377
  sessionId: opts.sessionId ?? null,
18177
18378
  timezone: opts.timezone,
18178
- doFetch: base
18379
+ doFetch
18179
18380
  });
18180
18381
  if (!handoff) {
18181
18382
  return { answeredHere: false, reason: "answered_by_great_arrow" };
@@ -18217,7 +18418,7 @@ async function answerOnThisComputer(opts) {
18217
18418
  outputTokens: result.outputTokens,
18218
18419
  promptCharacters: result.promptCharacters,
18219
18420
  latencyMs: Date.now() - startedAt,
18220
- doFetch: base
18421
+ doFetch
18221
18422
  });
18222
18423
  return {
18223
18424
  answeredHere: true,
@@ -18237,7 +18438,7 @@ async function answerOnThisComputer(opts) {
18237
18438
  outputTokens: void 0,
18238
18439
  promptCharacters: 0,
18239
18440
  latencyMs: Date.now() - startedAt,
18240
- doFetch: base
18441
+ doFetch
18241
18442
  });
18242
18443
  throw new LocalTurnFailed(
18243
18444
  "writing",
@@ -18470,7 +18671,11 @@ function checkCommand() {
18470
18671
  const provider = readProvider(options.app);
18471
18672
  const baseUrl = typeof options.url === "string" && options.url ? options.url : LOCAL_PROVIDER_PRESETS[provider].baseUrl;
18472
18673
  const auth = await resolveAuth();
18473
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
18674
+ const client = new GadClient({
18675
+ token: auth.token,
18676
+ getToken: auth.getToken,
18677
+ apiUrl: auth.apiUrl
18678
+ });
18474
18679
  const fmt = createFormatter({
18475
18680
  json: g.json,
18476
18681
  quiet: g.quiet,
@@ -18545,7 +18750,11 @@ function statusCommand2() {
18545
18750
  return new Command("status").description("Show what is set up for models on this computer").action(async (_options, command) => {
18546
18751
  const g = globalOutputOpts(command);
18547
18752
  const auth = await resolveAuth();
18548
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
18753
+ const client = new GadClient({
18754
+ token: auth.token,
18755
+ getToken: auth.getToken,
18756
+ apiUrl: auth.apiUrl
18757
+ });
18549
18758
  const payload = await client.mcpCall(
18550
18759
  "model_local_get",
18551
18760
  {},
@@ -18613,7 +18822,11 @@ function offCommand() {
18613
18822
  return new Command("off").description("Stop answering with a model on this computer").action(async (_options, command) => {
18614
18823
  const g = globalOutputOpts(command);
18615
18824
  const auth = await resolveAuth();
18616
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
18825
+ const client = new GadClient({
18826
+ token: auth.token,
18827
+ getToken: auth.getToken,
18828
+ apiUrl: auth.apiUrl
18829
+ });
18617
18830
  const fmt = createFormatter({
18618
18831
  json: g.json,
18619
18832
  quiet: g.quiet,
@@ -18644,6 +18857,7 @@ function askHereCommand() {
18644
18857
  report = await answerOnThisComputer({
18645
18858
  apiUrl: auth.apiUrl,
18646
18859
  token: auth.token,
18860
+ getToken: auth.getToken,
18647
18861
  question,
18648
18862
  sessionId: typeof options.session === "string" ? options.session : null,
18649
18863
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
@@ -18757,6 +18971,7 @@ function statusCommand3() {
18757
18971
  const auth = await resolveAuth();
18758
18972
  const client = new GadClient({
18759
18973
  token: auth.token,
18974
+ getToken: auth.getToken,
18760
18975
  apiUrl: auth.apiUrl
18761
18976
  });
18762
18977
  const fmt = createFormatter({
@@ -18934,7 +19149,11 @@ function openCommand() {
18934
19149
  ).option("--print", "Print the URL instead of opening a browser").option("--json", "Output the resolved surface as JSON").action(async (surface, options, command) => {
18935
19150
  const g = globalOutputOpts(command);
18936
19151
  const auth = await resolveAuth();
18937
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
19152
+ const client = new GadClient({
19153
+ token: auth.token,
19154
+ getToken: auth.getToken,
19155
+ apiUrl: auth.apiUrl
19156
+ });
18938
19157
  const result = await client.mcpCall("surfaces_list", {
18939
19158
  query: surface
18940
19159
  });
@@ -19022,7 +19241,11 @@ function nextCommand() {
19022
19241
  noColor: g.noColor
19023
19242
  });
19024
19243
  const auth = await resolveAuth();
19025
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
19244
+ const client = new GadClient({
19245
+ token: auth.token,
19246
+ getToken: auth.getToken,
19247
+ apiUrl: auth.apiUrl
19248
+ });
19026
19249
  let identityStatus = null;
19027
19250
  try {
19028
19251
  const who = await client.mcpCall("workspace_introspect", {});
@@ -19085,7 +19308,11 @@ function discoverCommand() {
19085
19308
  noColor: g.noColor
19086
19309
  });
19087
19310
  const auth = await resolveAuth();
19088
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
19311
+ const client = new GadClient({
19312
+ token: auth.token,
19313
+ getToken: auth.getToken,
19314
+ apiUrl: auth.apiUrl
19315
+ });
19089
19316
  const parsed = opts.limit ? Number.parseInt(opts.limit, 10) : NaN;
19090
19317
  const args = Number.isFinite(parsed) && parsed > 0 ? { limit: parsed } : {};
19091
19318
  const result = await client.mcpCall("did_you_know", args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@great-arrow/cli",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
4
4
  "description": "GreatArrow.ai CLI tool",
5
5
  "type": "module",
6
6
  "bin": {