@great-arrow/cli 0.1.49 → 0.1.51
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 +88 -22
- package/package.json +1 -1
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.51";
|
|
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";
|
|
@@ -10414,14 +10414,14 @@ async function storedPairOtherThan(ours) {
|
|
|
10414
10414
|
debugSwallow("refresh.reread", err);
|
|
10415
10415
|
}
|
|
10416
10416
|
if (stored?.kind !== "oauth") return null;
|
|
10417
|
+
if (stored.credential.clientId !== ours.clientId || stored.credential.apiUrl.replace(/\/$/, "") !== ours.apiUrl.replace(/\/$/, "")) {
|
|
10418
|
+
throw networkError(
|
|
10419
|
+
"The stored session changed while renewing it.",
|
|
10420
|
+
"Retry the command with the current session."
|
|
10421
|
+
);
|
|
10422
|
+
}
|
|
10417
10423
|
return stored.credential.refreshToken === ours.refreshToken ? null : stored.credential;
|
|
10418
10424
|
}
|
|
10419
|
-
async function rotatedByAnotherProcess(ours) {
|
|
10420
|
-
const theirs = await storedPairOtherThan(ours);
|
|
10421
|
-
if (!theirs) return null;
|
|
10422
|
-
if (Date.now() >= theirs.accessExpiresAt - REFRESH_SKEW_MS) return null;
|
|
10423
|
-
return theirs;
|
|
10424
|
-
}
|
|
10425
10425
|
async function resolveAuth() {
|
|
10426
10426
|
const config = await loadConfig();
|
|
10427
10427
|
const configApiUrl = config.api_url || DEFAULT_API_URL;
|
|
@@ -10512,20 +10512,37 @@ async function ensureFreshAccessToken(credential) {
|
|
|
10512
10512
|
}
|
|
10513
10513
|
async function doRefresh(credential) {
|
|
10514
10514
|
const held = await acquireRefreshLock();
|
|
10515
|
+
let current = credential;
|
|
10515
10516
|
try {
|
|
10516
|
-
const theirs = await
|
|
10517
|
-
if (theirs)
|
|
10517
|
+
const theirs = await storedPairOtherThan(credential);
|
|
10518
|
+
if (theirs) {
|
|
10519
|
+
current = theirs;
|
|
10520
|
+
if (Date.now() < current.accessExpiresAt - REFRESH_SKEW_MS)
|
|
10521
|
+
return current;
|
|
10522
|
+
}
|
|
10523
|
+
if (!held) {
|
|
10524
|
+
throw networkError(
|
|
10525
|
+
"Session renewal is already in progress.",
|
|
10526
|
+
"Retry shortly; your saved session has been kept."
|
|
10527
|
+
);
|
|
10528
|
+
}
|
|
10518
10529
|
const refreshed = await refreshTokens({
|
|
10519
|
-
apiUrl:
|
|
10520
|
-
clientId:
|
|
10521
|
-
refreshToken:
|
|
10530
|
+
apiUrl: current.apiUrl,
|
|
10531
|
+
clientId: current.clientId,
|
|
10532
|
+
refreshToken: current.refreshToken
|
|
10522
10533
|
});
|
|
10523
10534
|
await saveOAuthCredential(refreshed);
|
|
10524
10535
|
return refreshed;
|
|
10525
10536
|
} catch (err) {
|
|
10526
10537
|
if (err instanceof RefreshRejected) {
|
|
10527
|
-
const theirs = await storedPairOtherThan(
|
|
10528
|
-
if (theirs)
|
|
10538
|
+
const theirs = await storedPairOtherThan(current);
|
|
10539
|
+
if (theirs) {
|
|
10540
|
+
if (Date.now() < theirs.accessExpiresAt) return theirs;
|
|
10541
|
+
throw networkError(
|
|
10542
|
+
"Another process updated the session during renewal.",
|
|
10543
|
+
"Retry shortly; your saved session has been kept."
|
|
10544
|
+
);
|
|
10545
|
+
}
|
|
10529
10546
|
await clearStoredCredential();
|
|
10530
10547
|
throw sessionExpired();
|
|
10531
10548
|
}
|
|
@@ -10543,12 +10560,13 @@ var EXIT_SESSION_EXPIRED = 2;
|
|
|
10543
10560
|
var EXIT_NOT_OAUTH = 3;
|
|
10544
10561
|
function authCommand() {
|
|
10545
10562
|
const cmd = new Command("auth").description("Authentication helpers");
|
|
10546
|
-
cmd.command("token").description("Print a fresh OAuth access token, refreshing it if needed").option("--json", "Output {accessToken, accessExpiresAt, apiUrl} as JSON").action(async (options) => {
|
|
10563
|
+
cmd.command("token").description("Print a fresh OAuth access token, refreshing it if needed").option("--json", "Output {accessToken, accessExpiresAt, apiUrl} as JSON").action(async (options, actionCommand) => {
|
|
10547
10564
|
try {
|
|
10548
10565
|
const t = await getFreshOAuthAccessToken();
|
|
10549
10566
|
writeCliLocator();
|
|
10567
|
+
const jsonOutput = options.json || actionCommand.optsWithGlobals().json;
|
|
10550
10568
|
process.stdout.write(
|
|
10551
|
-
|
|
10569
|
+
jsonOutput ? `${JSON.stringify(t)}
|
|
10552
10570
|
` : `${t.accessToken}
|
|
10553
10571
|
`
|
|
10554
10572
|
);
|
|
@@ -11958,6 +11976,29 @@ import { readFileSync } from "node:fs";
|
|
|
11958
11976
|
|
|
11959
11977
|
// src/lib/work-status.ts
|
|
11960
11978
|
import { execFile as execFile2, execFileSync as execFileSync2 } from "node:child_process";
|
|
11979
|
+
|
|
11980
|
+
// ../../src/lib/work-risk/push-state.ts
|
|
11981
|
+
var REMOTE_BRANCH_REF_PREFIX = "refs/remotes/origin/";
|
|
11982
|
+
var REMOTE_BRANCH_TIPS_FORMAT = "%(refname)%09%(objectname)";
|
|
11983
|
+
function parseRemoteBranchTips(raw) {
|
|
11984
|
+
const tips = /* @__PURE__ */ new Map();
|
|
11985
|
+
for (const line of raw.split("\n")) {
|
|
11986
|
+
const [refname, sha] = line.split(" ");
|
|
11987
|
+
if (!refname?.startsWith(REMOTE_BRANCH_REF_PREFIX) || !sha) continue;
|
|
11988
|
+
const branch = refname.slice(REMOTE_BRANCH_REF_PREFIX.length);
|
|
11989
|
+
if (!branch || branch === "HEAD") continue;
|
|
11990
|
+
tips.set(branch, sha.trim());
|
|
11991
|
+
}
|
|
11992
|
+
return tips;
|
|
11993
|
+
}
|
|
11994
|
+
function remoteHasHead(branch, head, tips, isAncestor2) {
|
|
11995
|
+
const tip = tips.get(branch);
|
|
11996
|
+
if (!tip) return false;
|
|
11997
|
+
if (tip === head) return true;
|
|
11998
|
+
return isAncestor2(head, tip);
|
|
11999
|
+
}
|
|
12000
|
+
|
|
12001
|
+
// src/lib/work-status.ts
|
|
11961
12002
|
var LOCAL_COMMAND_TIMEOUT_MS = 2e4;
|
|
11962
12003
|
function run(command, args, cwd) {
|
|
11963
12004
|
try {
|
|
@@ -12070,9 +12111,31 @@ function auditLocalBranches(root) {
|
|
|
12070
12111
|
root
|
|
12071
12112
|
).split("\n").filter(Boolean)
|
|
12072
12113
|
);
|
|
12114
|
+
const tipsResult = git(
|
|
12115
|
+
[
|
|
12116
|
+
"for-each-ref",
|
|
12117
|
+
`--format=${REMOTE_BRANCH_TIPS_FORMAT}`,
|
|
12118
|
+
"refs/remotes/origin"
|
|
12119
|
+
],
|
|
12120
|
+
root
|
|
12121
|
+
);
|
|
12122
|
+
const tips = parseRemoteBranchTips(
|
|
12123
|
+
tipsResult.status === 0 ? tipsResult.stdout : ""
|
|
12124
|
+
);
|
|
12073
12125
|
return raw.split("\n").filter(Boolean).flatMap((line) => {
|
|
12074
12126
|
const parsed = parseLocalBranchLine(line, exact, unmerged);
|
|
12075
|
-
|
|
12127
|
+
if (!parsed) return [];
|
|
12128
|
+
return [
|
|
12129
|
+
{
|
|
12130
|
+
...parsed,
|
|
12131
|
+
remoteHasHead: remoteHasHead(
|
|
12132
|
+
parsed.branch,
|
|
12133
|
+
parsed.head,
|
|
12134
|
+
tips,
|
|
12135
|
+
(ancestor, descendant) => isAncestor(ancestor, descendant, root)
|
|
12136
|
+
)
|
|
12137
|
+
}
|
|
12138
|
+
];
|
|
12076
12139
|
});
|
|
12077
12140
|
}
|
|
12078
12141
|
function parseLocalBranchLine(line, exact, unmerged) {
|
|
@@ -12131,7 +12194,7 @@ function classifyWorktree(audit) {
|
|
|
12131
12194
|
if (audit.path.includes("/.claude/worktrees/")) return "agent-harness";
|
|
12132
12195
|
if (audit.mergedToMain) return "cleanup-ready";
|
|
12133
12196
|
if (audit.upstreamGone) return "upstream-gone";
|
|
12134
|
-
if (!audit.
|
|
12197
|
+
if (!audit.remoteHasHead) return "local-rescue";
|
|
12135
12198
|
return "active-unmerged";
|
|
12136
12199
|
}
|
|
12137
12200
|
async function auditWorktrees(root, localBranches) {
|
|
@@ -12159,6 +12222,7 @@ async function auditWorktrees(root, localBranches) {
|
|
|
12159
12222
|
mergedToMain: branch?.mergedToMain ?? false,
|
|
12160
12223
|
upstream: branch?.upstream ?? null,
|
|
12161
12224
|
upstreamGone: branch?.upstreamGone ?? false,
|
|
12225
|
+
remoteHasHead: branch?.remoteHasHead ?? false,
|
|
12162
12226
|
behindMain: branch?.behindMain ?? 0,
|
|
12163
12227
|
aheadMain: branch?.aheadMain ?? 0,
|
|
12164
12228
|
countsExact: branch?.countsExact ?? false
|
|
@@ -12178,6 +12242,7 @@ function auditDetachedLocalBranches(localBranches, checkedOutBranches) {
|
|
|
12178
12242
|
countsExact,
|
|
12179
12243
|
upstream,
|
|
12180
12244
|
upstreamGone,
|
|
12245
|
+
remoteHasHead: remoteHasHead2,
|
|
12181
12246
|
committedAt
|
|
12182
12247
|
}) => ({
|
|
12183
12248
|
branch,
|
|
@@ -12186,12 +12251,13 @@ function auditDetachedLocalBranches(localBranches, checkedOutBranches) {
|
|
|
12186
12251
|
behindMain,
|
|
12187
12252
|
aheadMain,
|
|
12188
12253
|
countsExact,
|
|
12189
|
-
// `
|
|
12190
|
-
// just has no PR yet" — the single bit that decides whether
|
|
12191
|
-
// actually be lost.
|
|
12192
|
-
//
|
|
12254
|
+
// `remoteHasHead` is what separates "never left this disk" from
|
|
12255
|
+
// "pushed, just has no PR yet" — the single bit that decides whether
|
|
12256
|
+
// work can actually be lost. `upstream` alone cannot: a task branch
|
|
12257
|
+
// could carry origin/main as its upstream without ever being pushed.
|
|
12193
12258
|
upstream,
|
|
12194
12259
|
upstreamGone,
|
|
12260
|
+
remoteHasHead: remoteHasHead2,
|
|
12195
12261
|
// Without this the grace window read `ageMs = Infinity` for every
|
|
12196
12262
|
// branch, so a branch was "stranded" from its first second.
|
|
12197
12263
|
committedAt
|