@great-arrow/cli 0.1.26 → 0.1.33

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 +322 -46
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -9399,7 +9399,7 @@ function isNodeError(value) {
9399
9399
 
9400
9400
  // src/lib/version.ts
9401
9401
  var CLI_PACKAGE_NAME = "@great-arrow/cli";
9402
- var CLI_VERSION = "0.1.26";
9402
+ var CLI_VERSION = "0.1.33";
9403
9403
  var GAD_INSTALL_VERSION = "2026.08.08";
9404
9404
  var GAD_INSTALL_MIN_VERSION = "2026.06.22";
9405
9405
  var GAD_INSTALL_HEADER = "X-GAD-Install-Version";
@@ -11741,13 +11741,30 @@ function requireGit(args, cwd) {
11741
11741
  function isAncestor(ancestor, ref, cwd) {
11742
11742
  return git(["merge-base", "--is-ancestor", ancestor, ref], cwd).status === 0;
11743
11743
  }
11744
+ var AHEAD_BEHIND_FIELD = "%(ahead-behind:origin/main)";
11745
+ var RICH_BRANCH_FORMAT = [
11746
+ "%(refname:short)",
11747
+ "%(objectname)",
11748
+ "%(upstream:short)",
11749
+ "%(upstream:track)",
11750
+ "%(committerdate:iso-strict)",
11751
+ AHEAD_BEHIND_FIELD
11752
+ ].join("%09");
11753
+ var LEGACY_BRANCH_FORMAT = [
11754
+ "%(refname:short)",
11755
+ "%(objectname)",
11756
+ "%(upstream:short)",
11757
+ "%(upstream:track)",
11758
+ "%(committerdate:iso-strict)"
11759
+ ].join("%09");
11744
11760
  function auditLocalBranches(root) {
11745
- const raw = requireGit(
11746
- [
11747
- "for-each-ref",
11748
- "--format=%(refname:short)%09%(objectname)%09%(upstream:short)%09%(upstream:track)",
11749
- "refs/heads"
11750
- ],
11761
+ const rich = git(
11762
+ ["for-each-ref", `--format=${RICH_BRANCH_FORMAT}`, "refs/heads"],
11763
+ root
11764
+ );
11765
+ const exact = rich.status === 0;
11766
+ const raw = exact ? rich.stdout.trim() : requireGit(
11767
+ ["for-each-ref", `--format=${LEGACY_BRANCH_FORMAT}`, "refs/heads"],
11751
11768
  root
11752
11769
  );
11753
11770
  const unmerged = new Set(
@@ -11762,23 +11779,32 @@ function auditLocalBranches(root) {
11762
11779
  ).split("\n").filter(Boolean)
11763
11780
  );
11764
11781
  return raw.split("\n").filter(Boolean).flatMap((line) => {
11765
- const [branch, head, upstreamRaw, tracking] = line.split(" ");
11766
- if (!branch || !head) return [];
11767
- const mergedToMain = !unmerged.has(branch);
11768
- return [
11769
- {
11770
- branch,
11771
- head,
11772
- upstream: upstreamRaw || null,
11773
- upstreamGone: tracking?.includes("gone") ?? false,
11774
- aheadMain: mergedToMain ? 0 : 1,
11775
- behindMain: 0,
11776
- countsExact: false,
11777
- mergedToMain
11778
- }
11779
- ];
11782
+ const parsed = parseLocalBranchLine(line, exact, unmerged);
11783
+ return parsed ? [parsed] : [];
11780
11784
  });
11781
11785
  }
11786
+ function parseLocalBranchLine(line, exact, unmerged) {
11787
+ const [branch, head, upstreamRaw, tracking, committedAt, aheadBehind] = line.split(" ");
11788
+ if (!branch || !head) return null;
11789
+ const mergedToMain = !unmerged.has(branch);
11790
+ const [aheadRaw, behindRaw] = (aheadBehind ?? "").trim().split(/\s+/);
11791
+ const ahead = Number(aheadRaw);
11792
+ const behind = Number(behindRaw);
11793
+ const countsExact = exact && Number.isFinite(ahead) && Number.isFinite(behind);
11794
+ return {
11795
+ branch,
11796
+ head,
11797
+ upstream: upstreamRaw || null,
11798
+ upstreamGone: tracking?.includes("gone") ?? false,
11799
+ // Fall back to the old boolean ONLY when the exact count is genuinely
11800
+ // unavailable, so a stale git degrades instead of lying.
11801
+ aheadMain: countsExact ? ahead : mergedToMain ? 0 : 1,
11802
+ behindMain: countsExact ? behind : 0,
11803
+ countsExact,
11804
+ mergedToMain,
11805
+ committedAt: committedAt || null
11806
+ };
11807
+ }
11782
11808
  function parseWorktrees(raw) {
11783
11809
  const worktrees = [];
11784
11810
  let current = {};
@@ -11851,13 +11877,32 @@ async function auditWorktrees(root, localBranches) {
11851
11877
  }
11852
11878
  function auditDetachedLocalBranches(localBranches, checkedOutBranches) {
11853
11879
  return localBranches.filter(({ branch }) => !checkedOutBranches.has(branch)).map(
11854
- ({ branch, head, mergedToMain, behindMain, aheadMain, countsExact }) => ({
11880
+ ({
11855
11881
  branch,
11856
11882
  head,
11857
11883
  mergedToMain,
11858
11884
  behindMain,
11859
11885
  aheadMain,
11860
- countsExact
11886
+ countsExact,
11887
+ upstream,
11888
+ upstreamGone,
11889
+ committedAt
11890
+ }) => ({
11891
+ branch,
11892
+ head,
11893
+ mergedToMain,
11894
+ behindMain,
11895
+ aheadMain,
11896
+ countsExact,
11897
+ // `upstream` is what separates "never left this disk" from "pushed,
11898
+ // just has no PR yet" — the single bit that decides whether work can
11899
+ // actually be lost. It was computed and then dropped here, so the
11900
+ // server classified both as one undifferentiated "stranded".
11901
+ upstream,
11902
+ upstreamGone,
11903
+ // Without this the grace window read `ageMs = Infinity` for every
11904
+ // branch, so a branch was "stranded" from its first second.
11905
+ committedAt
11861
11906
  })
11862
11907
  );
11863
11908
  }
@@ -14853,12 +14898,18 @@ function handleSyncResponse(fmt, kind, response) {
14853
14898
  }
14854
14899
  fmt.success(`Sync triggered successfully for "${kind}".`);
14855
14900
  const syncedAtStr = formatTimestamp(
14856
- response.lastSynced ?? response.last_synced
14901
+ response.lastSyncAt ?? response.lastSynced ?? response.last_synced
14857
14902
  );
14858
14903
  if (syncedAtStr) {
14859
14904
  fmt.info(` Last synced: ${syncedAtStr}`);
14860
14905
  }
14861
14906
  }
14907
+ function normalizeIntegrationRow(item) {
14908
+ return {
14909
+ ...item,
14910
+ lastSynced: item.lastSyncAt ?? item.lastSynced ?? item.last_synced ?? item.lastSyncedAt ?? null
14911
+ };
14912
+ }
14862
14913
  var listCommand3 = new Command("list").description("List all connected integrations with sync status").option("--json", "Output as JSON").action(async (options) => {
14863
14914
  const jsonFlag = Boolean(
14864
14915
  options.json || listCommand3.optsWithGlobals().json
@@ -14878,10 +14929,7 @@ var listCommand3 = new Command("list").description("List all connected integrati
14878
14929
  } else if (typeof data === "object" && data !== null && "integrations" in data) {
14879
14930
  integrations = data.integrations;
14880
14931
  }
14881
- const normalized = integrations.map((item) => ({
14882
- ...item,
14883
- lastSynced: item.lastSynced ?? item.last_synced ?? item.lastSyncedAt ?? null
14884
- }));
14932
+ const normalized = integrations.map(normalizeIntegrationRow);
14885
14933
  fmt.table(normalized, integrationListColumns);
14886
14934
  if (!jsonFlag) {
14887
14935
  displayFailedIntegrations(fmt, normalized);
@@ -17151,14 +17199,102 @@ function localCommand() {
17151
17199
  return new Command("local").description("Answer with a model running on this computer").addCommand(checkCommand()).addCommand(askHereCommand());
17152
17200
  }
17153
17201
 
17202
+ // src/lib/hyperlink.ts
17203
+ var SUPPORTED_TERM_PROGRAMS = /* @__PURE__ */ new Set([
17204
+ "ghostty",
17205
+ "iterm.app",
17206
+ "wezterm",
17207
+ "vscode",
17208
+ "hyper",
17209
+ "warpterminal",
17210
+ "rio",
17211
+ "contour",
17212
+ "tabby"
17213
+ ]);
17214
+ var SUPPORTED_TERMS = /* @__PURE__ */ new Set([
17215
+ "xterm-kitty",
17216
+ "alacritty",
17217
+ "foot",
17218
+ "contour"
17219
+ ]);
17220
+ var UNSUPPORTED_TERM_PROGRAMS = /* @__PURE__ */ new Set(["apple_terminal"]);
17221
+ var MIN_VTE_VERSION = 5e3;
17222
+ function isTruthyFlag(value) {
17223
+ if (value === void 0) return false;
17224
+ const v = value.trim().toLowerCase();
17225
+ return v !== "" && v !== "0" && v !== "false";
17226
+ }
17227
+ function supportsHyperlinks(env4, isTTY2) {
17228
+ if (isTruthyFlag(env4.FORCE_HYPERLINK)) return true;
17229
+ if (env4.NO_COLOR !== void 0 && env4.NO_COLOR !== "") return false;
17230
+ if (!isTTY2) return false;
17231
+ if (isTruthyFlag(env4.CI)) return false;
17232
+ const term = (env4.TERM ?? "").trim().toLowerCase();
17233
+ if (term === "" || term === "dumb") return false;
17234
+ if (term.startsWith("screen") || term.startsWith("tmux")) return false;
17235
+ const program3 = (env4.TERM_PROGRAM ?? "").trim().toLowerCase();
17236
+ if (UNSUPPORTED_TERM_PROGRAMS.has(program3)) return false;
17237
+ if (SUPPORTED_TERM_PROGRAMS.has(program3)) return true;
17238
+ if (SUPPORTED_TERMS.has(term)) return true;
17239
+ if (isTruthyFlag(env4.WT_SESSION)) return true;
17240
+ if (isTruthyFlag(env4.KONSOLE_VERSION)) return true;
17241
+ if (env4.TERMINAL_EMULATOR === "JetBrains-JediTerm") return true;
17242
+ const vte = Number.parseInt(env4.VTE_VERSION ?? "", 10);
17243
+ if (Number.isFinite(vte) && vte >= MIN_VTE_VERSION) return true;
17244
+ return false;
17245
+ }
17246
+ var OSC = "\x1B]8;;";
17247
+ var ST = "\x1B\\";
17248
+ function hyperlink(text2, url, opts) {
17249
+ if (!opts.enabled) return text2 === url ? url : `${text2} (${url})`;
17250
+ return `${OSC}${url}${ST}${text2}${OSC}${ST}`;
17251
+ }
17252
+
17154
17253
  // src/lib/glance-footer.ts
17155
17254
  import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
17156
17255
  import { homedir as homedir4 } from "node:os";
17157
17256
  import { join as join6, dirname as dirname5 } from "node:path";
17257
+
17258
+ // src/lib/seen.ts
17259
+ var SEEN_TIMEOUT_MS = 1e3;
17260
+ var MAX_KEYS = 50;
17261
+ async function reportSeen(opts) {
17262
+ const keys = [
17263
+ ...new Set(
17264
+ opts.keys.filter((k) => typeof k === "string" && k !== "")
17265
+ )
17266
+ ].slice(0, MAX_KEYS);
17267
+ if (keys.length === 0) return 0;
17268
+ const controller = new AbortController();
17269
+ const timer = setTimeout(() => controller.abort(), SEEN_TIMEOUT_MS);
17270
+ try {
17271
+ const response = await fetch(
17272
+ `${opts.apiUrl.replace(/\/$/, "")}/api/attention/seen`,
17273
+ {
17274
+ method: "POST",
17275
+ headers: {
17276
+ "Content-Type": "application/json",
17277
+ Authorization: `Bearer ${opts.token}`
17278
+ },
17279
+ body: JSON.stringify({ keys }),
17280
+ signal: controller.signal
17281
+ }
17282
+ );
17283
+ if (!response.ok) return 0;
17284
+ const data = await response.json().catch(() => null);
17285
+ return typeof data?.seen === "number" ? data.seen : 0;
17286
+ } catch {
17287
+ return 0;
17288
+ } finally {
17289
+ clearTimeout(timer);
17290
+ }
17291
+ }
17292
+
17293
+ // src/lib/glance-footer.ts
17158
17294
  var FETCH_TIMEOUT_MS = 1500;
17159
17295
  var THROTTLE_INTERVAL_MS = 4 * 60 * 60 * 1e3;
17160
17296
  var URGENT_THRESHOLD = 15;
17161
- var SKIP_COMMANDS = /* @__PURE__ */ new Set(["status", "login", "logout", "help"]);
17297
+ var SKIP_COMMANDS = /* @__PURE__ */ new Set(["status", "needs", "login", "logout", "help"]);
17162
17298
  async function maybeShowGlanceFooter(argv) {
17163
17299
  try {
17164
17300
  if (!shouldRunGlanceFooter(argv)) return;
@@ -17178,9 +17314,14 @@ async function maybeShowGlanceFooter(argv) {
17178
17314
  })) {
17179
17315
  return;
17180
17316
  }
17181
- renderFooter(payload, auth.apiUrl);
17317
+ const printed = renderFooter(payload, auth.apiUrl);
17182
17318
  state[workspaceKey] = { lastShownAt: now, lastTopItemId: signature };
17183
17319
  await saveGlanceState(state);
17320
+ await reportSeen({
17321
+ apiUrl: auth.apiUrl,
17322
+ token: auth.token,
17323
+ keys: printed
17324
+ });
17184
17325
  } catch (err) {
17185
17326
  debugSwallow("glance-footer", err);
17186
17327
  }
@@ -17247,7 +17388,28 @@ function normalizeGlance(raw) {
17247
17388
  title: it.title,
17248
17389
  action: typeof it.action === "string" ? it.action : null,
17249
17390
  href: typeof it.href === "string" && it.href !== "" ? it.href : null,
17250
- ...typeof it.rank === "number" ? { rank: it.rank } : {}
17391
+ ...typeof it.rank === "number" ? { rank: it.rank } : {},
17392
+ signalKey: typeof it.signalKey === "string" && it.signalKey !== "" ? it.signalKey : null,
17393
+ workspace: typeof it.workspace === "string" && it.workspace !== "" ? it.workspace : null,
17394
+ // Parsed like every other field: a non-string is dropped rather than
17395
+ // coerced, so a malformed payload cannot render as a currency claim.
17396
+ freshness: typeof it.freshness === "string" && it.freshness !== "" ? it.freshness : null,
17397
+ meta: typeof it.meta === "string" && it.meta !== "" ? it.meta : null
17398
+ }
17399
+ ];
17400
+ });
17401
+ const atRisk = asArray2(obj.atRisk).flatMap((item) => {
17402
+ if (typeof item !== "object" || item === null) return [];
17403
+ const it = item;
17404
+ if (typeof it.title !== "string") return [];
17405
+ return [
17406
+ {
17407
+ classification: typeof it.classification === "string" ? it.classification : null,
17408
+ riskLevel: typeof it.riskLevel === "string" ? it.riskLevel : "high",
17409
+ title: it.title,
17410
+ branch: typeof it.branch === "string" ? it.branch : null,
17411
+ aheadMain: typeof it.aheadMain === "number" ? it.aheadMain : null,
17412
+ recommendedAction: typeof it.recommendedAction === "string" ? it.recommendedAction : null
17251
17413
  }
17252
17414
  ];
17253
17415
  });
@@ -17301,11 +17463,29 @@ function normalizeGlance(raw) {
17301
17463
  }
17302
17464
  return {
17303
17465
  workspace: typeof obj.workspace === "string" && obj.workspace !== "" ? obj.workspace : null,
17466
+ // An older server sends no `reads` at all, so this normalizes to an empty
17467
+ // list rather than inventing the pinned workspace as the read set — the
17468
+ // CLI then renders no breadth cue, which is honest, instead of a claim it
17469
+ // was never told.
17470
+ reads: Array.isArray(obj.reads) ? obj.reads.flatMap((r) => {
17471
+ if (typeof r !== "object" || r === null) return [];
17472
+ const row = r;
17473
+ return typeof row.id === "string" && typeof row.name === "string" ? [{ id: row.id, name: row.name }] : [];
17474
+ }) : [],
17304
17475
  needsYou,
17476
+ atRisk,
17305
17477
  topGoal,
17306
17478
  integrations,
17307
17479
  agents,
17308
17480
  warRoom,
17481
+ // Parsed like every other field: both halves must be the right type or the
17482
+ // whole line is dropped, so a malformed payload cannot render as a dollar.
17483
+ spend: (() => {
17484
+ const raw2 = obj.spend;
17485
+ if (typeof raw2 !== "object" || raw2 === null) return null;
17486
+ const sp = raw2;
17487
+ return typeof sp.line === "string" && sp.line !== "" && typeof sp.totalCostUsd === "number" && Number.isFinite(sp.totalCostUsd) ? { line: sp.line, totalCostUsd: sp.totalCostUsd } : null;
17488
+ })(),
17309
17489
  urgency: typeof obj.urgency === "number" ? obj.urgency : null,
17310
17490
  generatedAt: typeof obj.generatedAt === "string" ? obj.generatedAt : (/* @__PURE__ */ new Date()).toISOString()
17311
17491
  };
@@ -17362,14 +17542,33 @@ function glanceItemUrl(href, apiUrl) {
17362
17542
  function renderFooter(payload, apiUrl) {
17363
17543
  const c = createColor(false);
17364
17544
  const lines = [];
17545
+ const printed = [];
17365
17546
  const wr = payload.warRoom;
17366
17547
  if (wr && wr.priorityCount > 0) {
17367
17548
  lines.push(
17368
17549
  `${c.yellow("!")} ${wr.priorityCount} war-room message${wr.priorityCount === 1 ? "" : "s"} for you${wr.newestSender ? ` \u2014 ${wr.newestSender}` : ""}`
17369
17550
  );
17370
17551
  }
17552
+ const unpushed = (payload.atRisk ?? []).filter(
17553
+ (item) => item.classification === "unpushed"
17554
+ );
17555
+ if (unpushed.length > 0) {
17556
+ const commits = unpushed.reduce(
17557
+ (sum, item) => sum + (item.aheadMain ?? 0),
17558
+ 0
17559
+ );
17560
+ const scale = commits > 0 ? `${commits} commit${commits === 1 ? "" : "s"} on ${unpushed.length} branch${unpushed.length === 1 ? "" : "es"}` : `${unpushed.length} branch${unpushed.length === 1 ? "" : "es"}`;
17561
+ lines.push(`${c.yellow("!")} ${scale} exist on this machine only`);
17562
+ const first = unpushed[0];
17563
+ if (first?.branch) {
17564
+ lines.push(
17565
+ ` ${c.dim("\u2192")} ${c.cyan(`git push -u origin ${first.branch}`)}`
17566
+ );
17567
+ }
17568
+ }
17371
17569
  for (const item of payload.needsYou.slice(0, 2)) {
17372
17570
  lines.push(`${c.yellow("!")} ${item.title}`);
17571
+ if (item.signalKey) printed.push(item.signalKey);
17373
17572
  if (item.action) {
17374
17573
  lines.push(` ${c.dim("\u2192")} ${c.cyan(item.action)}`);
17375
17574
  } else {
@@ -17387,21 +17586,21 @@ function renderFooter(payload, apiUrl) {
17387
17586
  }
17388
17587
  }
17389
17588
  }
17390
- if (lines.length === 0) return;
17589
+ if (lines.length === 0) return [];
17391
17590
  lines.push(`${c.dim("\u2192")} ${c.cyan("gad status")} for the full picture.`);
17392
17591
  process.stderr.write("\n" + brandedBlock("needs you", lines) + "\n");
17592
+ return printed;
17393
17593
  }
17394
- var GAD_LOGO = `<img src="https://www.greatarrow.ai/icon.svg" width="16" alt="Great Arrow" style="vertical-align:middle;display:inline-block;margin-right:4px;" />`;
17594
+ var GAD_MARK = "\u{1F3F9}";
17395
17595
  function brandedBlock(title, lines) {
17396
- const header = `${GAD_LOGO} **Great Arrow** \xB7 ${title}`;
17397
- const visualLength = header.replace(GAD_LOGO, " ").length;
17398
- const rule = "\u2500".repeat(Math.min(60, visualLength));
17399
- return [header, rule, ...lines].join("\\n");
17596
+ const header = `${GAD_MARK} Great Arrow \xB7 ${title}`;
17597
+ const rule = "\u2500".repeat(Math.min(60, [...header].length));
17598
+ return [header, rule, ...lines].join("\n");
17400
17599
  }
17401
17600
 
17402
17601
  // src/commands/status.ts
17403
17602
  function statusCommand2() {
17404
- const cmd = new Command("status").description(
17603
+ const cmd = new Command("status").alias("needs").description(
17405
17604
  "Show what needs you: blockers, Goal progress, stale data, agents"
17406
17605
  ).option("--json", "Output the raw glance payload as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output").action(async (options, command) => {
17407
17606
  const g = globalOutputOpts(command);
@@ -17437,11 +17636,18 @@ function statusCommand2() {
17437
17636
  return;
17438
17637
  }
17439
17638
  renderStatus(payload, g.noColor || g.quiet, auth.apiUrl);
17639
+ await reportSeen({
17640
+ apiUrl: auth.apiUrl,
17641
+ token: auth.token,
17642
+ keys: payload.needsYou.map((item) => item.signalKey)
17643
+ });
17440
17644
  });
17441
17645
  return cmd;
17442
17646
  }
17443
17647
  function renderStatus(payload, plain, apiUrl) {
17444
17648
  const c = createColor(plain);
17649
+ const links = supportsHyperlinks(process.env, isTTY());
17650
+ const link = (text2, url) => c.cyan(hyperlink(text2, url, { enabled: links }));
17445
17651
  const lines = [];
17446
17652
  if (payload.workspace) {
17447
17653
  lines.push(c.badge(` ${payload.workspace} `));
@@ -17464,18 +17670,28 @@ function renderStatus(payload, plain, apiUrl) {
17464
17670
  if (wr.newestPreview) {
17465
17671
  lines.push(` ${c.dim(wr.newestPreview)}`);
17466
17672
  }
17467
- lines.push(
17468
- ` ${c.dim("\u2192")} ${c.cyan(glanceItemUrl("/team?tab=war-room", apiUrl) ?? "")}`
17469
- );
17673
+ const warRoomUrl = glanceItemUrl("/team?tab=war-room", apiUrl);
17674
+ if (warRoomUrl) {
17675
+ lines.push(
17676
+ ` ${c.dim("\u2192")} ${link("Open the war room", warRoomUrl)}`
17677
+ );
17678
+ }
17470
17679
  lines.push("");
17471
17680
  }
17472
17681
  if (payload.needsYou.length > 0) {
17473
17682
  lines.push(c.bold("Needs you"));
17474
17683
  for (const item of payload.needsYou) {
17475
- lines.push(` ${c.yellow("!")} ${item.title}`);
17684
+ const from = item.workspace ? ` ${c.dim(`(${item.workspace})`)}` : "";
17685
+ lines.push(` ${c.yellow("!")} ${item.title}${from}`);
17686
+ if (item.freshness) lines.push(` ${c.dim(item.freshness)}`);
17476
17687
  if (item.action) lines.push(` ${c.dim("\u2192")} ${c.cyan(item.action)}`);
17477
17688
  const url = glanceItemUrl(item.href, apiUrl);
17478
- if (url) lines.push(` ${c.dim("\u2192")} ${c.cyan(url)}`);
17689
+ if (url) lines.push(` ${c.dim("\u2192")} ${link(item.title, url)}`);
17690
+ if (item.externalHref) {
17691
+ lines.push(
17692
+ ` ${c.dim("\u2192")} ${link("Open site", item.externalHref)}`
17693
+ );
17694
+ }
17479
17695
  }
17480
17696
  lines.push("");
17481
17697
  }
@@ -17678,6 +17894,65 @@ function nextCommand() {
17678
17894
  });
17679
17895
  }
17680
17896
 
17897
+ // src/commands/discover.ts
17898
+ var DISCOVER_COLUMNS = [
17899
+ { key: "category", header: "Area", width: 20 },
17900
+ { key: "text", header: "Did you know", width: 78 },
17901
+ { key: "url", header: "Open" }
17902
+ ];
17903
+ var AREA_LABELS = {
17904
+ feature_discovery: "Capability",
17905
+ workflow: "Workflow",
17906
+ integration_health: "Connections",
17907
+ memory: "Memory",
17908
+ meetings: "Meetings",
17909
+ models: "Models",
17910
+ opportunity: "Setup"
17911
+ };
17912
+ function discoverRow(item) {
17913
+ const category = typeof item.category === "string" ? item.category : "";
17914
+ return {
17915
+ category: AREA_LABELS[category] ?? category,
17916
+ text: typeof item.text === "string" ? item.text : "",
17917
+ url: typeof item.url === "string" ? item.url : ""
17918
+ };
17919
+ }
17920
+ function discoverCommand() {
17921
+ return new Command("discover").description("Show what Great Arrow can do that you're not using yet").option("--limit <n>", "How many suggestions to show").option("--json", "Output as JSON").action(async (opts, command) => {
17922
+ const g = globalOutputOpts(command);
17923
+ const fmt = createFormatter({
17924
+ json: g.json,
17925
+ quiet: g.quiet,
17926
+ isTTY: isTTY(),
17927
+ noColor: g.noColor
17928
+ });
17929
+ const auth = await resolveAuth();
17930
+ const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
17931
+ const parsed = opts.limit ? Number.parseInt(opts.limit, 10) : NaN;
17932
+ const args = Number.isFinite(parsed) && parsed > 0 ? { limit: parsed } : {};
17933
+ const result = await client.mcpCall("did_you_know", args);
17934
+ const items = Array.isArray(result?.items) ? result.items : [];
17935
+ if (items.length === 0) {
17936
+ if (!g.json) {
17937
+ fmt.info(
17938
+ "Nothing new to suggest \u2014 this workspace already uses what its connected tools support. Connect another tool with 'gad integrations' to unlock more."
17939
+ );
17940
+ } else {
17941
+ fmt.table([], DISCOVER_COLUMNS);
17942
+ }
17943
+ return;
17944
+ }
17945
+ fmt.table(items.map(discoverRow), DISCOVER_COLUMNS);
17946
+ if (!g.json) {
17947
+ const using = Number(result?.alreadyUsingCount ?? 0);
17948
+ const total = Number(result?.totalCount ?? 0);
17949
+ if (Number.isFinite(using) && Number.isFinite(total) && total > 0) {
17950
+ fmt.info(`You're already using ${using} of ${total} capabilities.`);
17951
+ }
17952
+ }
17953
+ });
17954
+ }
17955
+
17681
17956
  // src/lib/completions.ts
17682
17957
  var COMMANDS = [
17683
17958
  "login",
@@ -20187,8 +20462,8 @@ import process10 from "node:process";
20187
20462
 
20188
20463
  // node_modules/.pnpm/ansi-regex@6.2.2/node_modules/ansi-regex/index.js
20189
20464
  function ansiRegex({ onlyFirst = false } = {}) {
20190
- const ST = "(?:\\u0007|\\u001B\\u005C|\\u009C)";
20191
- const osc = `(?:\\u001B\\][\\s\\S]*?${ST})`;
20465
+ const ST2 = "(?:\\u0007|\\u001B\\u005C|\\u009C)";
20466
+ const osc = `(?:\\u001B\\][\\s\\S]*?${ST2})`;
20192
20467
  const csi = "[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]";
20193
20468
  const pattern = `${osc}|${csi}`;
20194
20469
  return new RegExp(pattern, onlyFirst ? void 0 : "g");
@@ -21318,6 +21593,7 @@ program2.addCommand(memoryCommand);
21318
21593
  program2.addCommand(workspace_default);
21319
21594
  program2.addCommand(todo_default);
21320
21595
  program2.addCommand(nextCommand());
21596
+ program2.addCommand(discoverCommand());
21321
21597
  program2.addCommand(claim_default);
21322
21598
  program2.addCommand(work_default());
21323
21599
  program2.addCommand(worktree_default);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@great-arrow/cli",
3
- "version": "0.1.26",
3
+ "version": "0.1.33",
4
4
  "description": "GreatArrow.ai CLI tool",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,7 +38,7 @@
38
38
  "license": "SEE LICENSE IN LICENSE",
39
39
  "repository": {
40
40
  "type": "git",
41
- "url": "git+https://github.com/Clapptastic/great-arrow-digital.git",
41
+ "url": "git+https://github.com/GreatArrow-ai/great-arrow-digital.git",
42
42
  "directory": "packages/cli"
43
43
  },
44
44
  "homepage": "https://www.greatarrow.ai",