@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.
- package/dist/index.js +351 -44
- 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.
|
|
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
|
|
11746
|
-
[
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
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
|
|
11766
|
-
|
|
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
|
-
({
|
|
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(
|
|
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
|
|
17594
|
+
var GAD_MARK = "\u{1F3F9}";
|
|
17373
17595
|
function brandedBlock(title, lines) {
|
|
17374
|
-
const header = `${
|
|
17375
|
-
const
|
|
17376
|
-
|
|
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
|
-
|
|
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")} ${
|
|
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
|
|
20155
|
-
const osc = `(?:\\u001B\\][\\s\\S]*?${
|
|
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.
|
|
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/
|
|
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",
|