@great-arrow/cli 0.1.25 → 0.1.32

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 +351 -44
  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.25";
9402
+ var CLI_VERSION = "0.1.32";
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
  });
@@ -17287,12 +17449,43 @@ function normalizeGlance(raw) {
17287
17449
  };
17288
17450
  }
17289
17451
  }
17452
+ let warRoom = null;
17453
+ if (typeof obj.warRoom === "object" && obj.warRoom !== null) {
17454
+ const w = obj.warRoom;
17455
+ warRoom = {
17456
+ priorityCount: typeof w.priorityCount === "number" ? w.priorityCount : 0,
17457
+ unreadTotal: typeof w.unreadTotal === "number" ? w.unreadTotal : 0,
17458
+ openRequestCount: typeof w.openRequestCount === "number" ? w.openRequestCount : 0,
17459
+ topSender: typeof w.topSender === "string" ? w.topSender : null,
17460
+ newestSender: typeof w.newestSender === "string" ? w.newestSender : null,
17461
+ newestPreview: typeof w.newestPreview === "string" ? w.newestPreview : null
17462
+ };
17463
+ }
17290
17464
  return {
17291
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
+ }) : [],
17292
17475
  needsYou,
17476
+ atRisk,
17293
17477
  topGoal,
17294
17478
  integrations,
17295
17479
  agents,
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
+ })(),
17296
17489
  urgency: typeof obj.urgency === "number" ? obj.urgency : null,
17297
17490
  generatedAt: typeof obj.generatedAt === "string" ? obj.generatedAt : (/* @__PURE__ */ new Date()).toISOString()
17298
17491
  };
@@ -17301,6 +17494,9 @@ function asArray2(value) {
17301
17494
  return Array.isArray(value) ? value : [];
17302
17495
  }
17303
17496
  function topSignature(payload) {
17497
+ if (payload.warRoom && payload.warRoom.priorityCount > 0) {
17498
+ return `warroom:${payload.warRoom.newestSender ?? "peer"}`;
17499
+ }
17304
17500
  const top = payload.needsYou[0];
17305
17501
  if (top) return `needs:${top.kind}:${top.title}`;
17306
17502
  const integration = payload.integrations[0];
@@ -17346,8 +17542,33 @@ function glanceItemUrl(href, apiUrl) {
17346
17542
  function renderFooter(payload, apiUrl) {
17347
17543
  const c = createColor(false);
17348
17544
  const lines = [];
17545
+ const printed = [];
17546
+ const wr = payload.warRoom;
17547
+ if (wr && wr.priorityCount > 0) {
17548
+ lines.push(
17549
+ `${c.yellow("!")} ${wr.priorityCount} war-room message${wr.priorityCount === 1 ? "" : "s"} for you${wr.newestSender ? ` \u2014 ${wr.newestSender}` : ""}`
17550
+ );
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
+ }
17349
17569
  for (const item of payload.needsYou.slice(0, 2)) {
17350
17570
  lines.push(`${c.yellow("!")} ${item.title}`);
17571
+ if (item.signalKey) printed.push(item.signalKey);
17351
17572
  if (item.action) {
17352
17573
  lines.push(` ${c.dim("\u2192")} ${c.cyan(item.action)}`);
17353
17574
  } else {
@@ -17365,21 +17586,21 @@ function renderFooter(payload, apiUrl) {
17365
17586
  }
17366
17587
  }
17367
17588
  }
17368
- if (lines.length === 0) return;
17589
+ if (lines.length === 0) return [];
17369
17590
  lines.push(`${c.dim("\u2192")} ${c.cyan("gad status")} for the full picture.`);
17370
17591
  process.stderr.write("\n" + brandedBlock("needs you", lines) + "\n");
17592
+ return printed;
17371
17593
  }
17372
- 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}";
17373
17595
  function brandedBlock(title, lines) {
17374
- const header = `${GAD_LOGO} **Great Arrow** \xB7 ${title}`;
17375
- const visualLength = header.replace(GAD_LOGO, " ").length;
17376
- const rule = "\u2500".repeat(Math.min(60, visualLength));
17377
- 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");
17378
17599
  }
17379
17600
 
17380
17601
  // src/commands/status.ts
17381
17602
  function statusCommand2() {
17382
- const cmd = new Command("status").description(
17603
+ const cmd = new Command("status").alias("needs").description(
17383
17604
  "Show what needs you: blockers, Goal progress, stale data, agents"
17384
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) => {
17385
17606
  const g = globalOutputOpts(command);
@@ -17415,17 +17636,24 @@ function statusCommand2() {
17415
17636
  return;
17416
17637
  }
17417
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
+ });
17418
17644
  });
17419
17645
  return cmd;
17420
17646
  }
17421
17647
  function renderStatus(payload, plain, apiUrl) {
17422
17648
  const c = createColor(plain);
17649
+ const links = supportsHyperlinks(process.env, isTTY());
17650
+ const link = (text2, url) => c.cyan(hyperlink(text2, url, { enabled: links }));
17423
17651
  const lines = [];
17424
17652
  if (payload.workspace) {
17425
17653
  lines.push(c.badge(` ${payload.workspace} `));
17426
17654
  lines.push("");
17427
17655
  }
17428
- const hasAny = payload.needsYou.length > 0 || payload.topGoal !== null || payload.integrations.length > 0 || payload.agents.length > 0;
17656
+ const hasAny = payload.needsYou.length > 0 || payload.topGoal !== null || payload.integrations.length > 0 || payload.agents.length > 0 || payload.warRoom !== null && payload.warRoom.priorityCount > 0;
17429
17657
  if (!hasAny) {
17430
17658
  lines.push(
17431
17659
  `${c.green("\u2713")} You're all clear \u2014 nothing needs you right now.`
@@ -17433,13 +17661,32 @@ function renderStatus(payload, plain, apiUrl) {
17433
17661
  process.stdout.write(brandedBlock("status", lines) + "\n");
17434
17662
  return;
17435
17663
  }
17664
+ if (payload.warRoom && payload.warRoom.priorityCount > 0) {
17665
+ const wr = payload.warRoom;
17666
+ lines.push(c.bold("War room"));
17667
+ lines.push(
17668
+ ` ${c.yellow("!")} ${wr.priorityCount} message${wr.priorityCount === 1 ? "" : "s"} for you${wr.newestSender ? ` \u2014 ${wr.newestSender}` : ""}`
17669
+ );
17670
+ if (wr.newestPreview) {
17671
+ lines.push(` ${c.dim(wr.newestPreview)}`);
17672
+ }
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
+ }
17679
+ lines.push("");
17680
+ }
17436
17681
  if (payload.needsYou.length > 0) {
17437
17682
  lines.push(c.bold("Needs you"));
17438
17683
  for (const item of payload.needsYou) {
17439
- 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)}`);
17440
17687
  if (item.action) lines.push(` ${c.dim("\u2192")} ${c.cyan(item.action)}`);
17441
17688
  const url = glanceItemUrl(item.href, apiUrl);
17442
- if (url) lines.push(` ${c.dim("\u2192")} ${c.cyan(url)}`);
17689
+ if (url) lines.push(` ${c.dim("\u2192")} ${link(item.title, url)}`);
17443
17690
  }
17444
17691
  lines.push("");
17445
17692
  }
@@ -17642,6 +17889,65 @@ function nextCommand() {
17642
17889
  });
17643
17890
  }
17644
17891
 
17892
+ // src/commands/discover.ts
17893
+ var DISCOVER_COLUMNS = [
17894
+ { key: "category", header: "Area", width: 20 },
17895
+ { key: "text", header: "Did you know", width: 78 },
17896
+ { key: "url", header: "Open" }
17897
+ ];
17898
+ var AREA_LABELS = {
17899
+ feature_discovery: "Capability",
17900
+ workflow: "Workflow",
17901
+ integration_health: "Connections",
17902
+ memory: "Memory",
17903
+ meetings: "Meetings",
17904
+ models: "Models",
17905
+ opportunity: "Setup"
17906
+ };
17907
+ function discoverRow(item) {
17908
+ const category = typeof item.category === "string" ? item.category : "";
17909
+ return {
17910
+ category: AREA_LABELS[category] ?? category,
17911
+ text: typeof item.text === "string" ? item.text : "",
17912
+ url: typeof item.url === "string" ? item.url : ""
17913
+ };
17914
+ }
17915
+ function discoverCommand() {
17916
+ 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) => {
17917
+ const g = globalOutputOpts(command);
17918
+ const fmt = createFormatter({
17919
+ json: g.json,
17920
+ quiet: g.quiet,
17921
+ isTTY: isTTY(),
17922
+ noColor: g.noColor
17923
+ });
17924
+ const auth = await resolveAuth();
17925
+ const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
17926
+ const parsed = opts.limit ? Number.parseInt(opts.limit, 10) : NaN;
17927
+ const args = Number.isFinite(parsed) && parsed > 0 ? { limit: parsed } : {};
17928
+ const result = await client.mcpCall("did_you_know", args);
17929
+ const items = Array.isArray(result?.items) ? result.items : [];
17930
+ if (items.length === 0) {
17931
+ if (!g.json) {
17932
+ fmt.info(
17933
+ "Nothing new to suggest \u2014 this workspace already uses what its connected tools support. Connect another tool with 'gad integrations' to unlock more."
17934
+ );
17935
+ } else {
17936
+ fmt.table([], DISCOVER_COLUMNS);
17937
+ }
17938
+ return;
17939
+ }
17940
+ fmt.table(items.map(discoverRow), DISCOVER_COLUMNS);
17941
+ if (!g.json) {
17942
+ const using = Number(result?.alreadyUsingCount ?? 0);
17943
+ const total = Number(result?.totalCount ?? 0);
17944
+ if (Number.isFinite(using) && Number.isFinite(total) && total > 0) {
17945
+ fmt.info(`You're already using ${using} of ${total} capabilities.`);
17946
+ }
17947
+ }
17948
+ });
17949
+ }
17950
+
17645
17951
  // src/lib/completions.ts
17646
17952
  var COMMANDS = [
17647
17953
  "login",
@@ -20151,8 +20457,8 @@ import process10 from "node:process";
20151
20457
 
20152
20458
  // node_modules/.pnpm/ansi-regex@6.2.2/node_modules/ansi-regex/index.js
20153
20459
  function ansiRegex({ onlyFirst = false } = {}) {
20154
- const ST = "(?:\\u0007|\\u001B\\u005C|\\u009C)";
20155
- const osc = `(?:\\u001B\\][\\s\\S]*?${ST})`;
20460
+ const ST2 = "(?:\\u0007|\\u001B\\u005C|\\u009C)";
20461
+ const osc = `(?:\\u001B\\][\\s\\S]*?${ST2})`;
20156
20462
  const csi = "[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]";
20157
20463
  const pattern = `${osc}|${csi}`;
20158
20464
  return new RegExp(pattern, onlyFirst ? void 0 : "g");
@@ -21282,6 +21588,7 @@ program2.addCommand(memoryCommand);
21282
21588
  program2.addCommand(workspace_default);
21283
21589
  program2.addCommand(todo_default);
21284
21590
  program2.addCommand(nextCommand());
21591
+ program2.addCommand(discoverCommand());
21285
21592
  program2.addCommand(claim_default);
21286
21593
  program2.addCommand(work_default());
21287
21594
  program2.addCommand(worktree_default);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@great-arrow/cli",
3
- "version": "0.1.25",
3
+ "version": "0.1.32",
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",