@great-arrow/cli 0.1.66 → 0.1.68
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/README.md +7 -5
- package/dist/index.js +287 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,11 +49,13 @@ gad logout
|
|
|
49
49
|
gad auth token [--json]
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
Credentials live in your
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
Credentials live in your mode-`0600` `~/.great-arrow/credential` store (inside
|
|
53
|
+
`0700` `~/.great-arrow/`) and/or your OS keychain (Keychain Access / libsecret /
|
|
54
|
+
Credential Vault). Keeping `~/.great-arrow/credential` synced and reading it
|
|
55
|
+
first avoids repeated macOS Keychain `cdhash` password prompts when multiple
|
|
56
|
+
Node.js versions (`nvm`, `volta`, Homebrew) or the stdio MCP proxy access
|
|
57
|
+
automatically refreshed OAuth tokens. `GAD_TOKEN` in the environment always
|
|
58
|
+
takes precedence for CI.
|
|
57
59
|
|
|
58
60
|
## Commands
|
|
59
61
|
|
package/dist/index.js
CHANGED
|
@@ -108,7 +108,7 @@ var require_polyfills = __commonJS({
|
|
|
108
108
|
}
|
|
109
109
|
if (platform2 === "win32") {
|
|
110
110
|
fs6.rename = typeof fs6.rename !== "function" ? fs6.rename : (function(fs$rename) {
|
|
111
|
-
function
|
|
111
|
+
function rename4(from, to, cb) {
|
|
112
112
|
var start = Date.now();
|
|
113
113
|
var backoff = 0;
|
|
114
114
|
fs$rename(from, to, function CB(er) {
|
|
@@ -128,8 +128,8 @@ var require_polyfills = __commonJS({
|
|
|
128
128
|
if (cb) cb(er);
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
|
-
if (Object.setPrototypeOf) Object.setPrototypeOf(
|
|
132
|
-
return
|
|
131
|
+
if (Object.setPrototypeOf) Object.setPrototypeOf(rename4, fs$rename);
|
|
132
|
+
return rename4;
|
|
133
133
|
})(fs6.rename);
|
|
134
134
|
}
|
|
135
135
|
fs6.read = typeof fs6.read !== "function" ? fs6.read : (function(fs$read) {
|
|
@@ -2082,7 +2082,7 @@ var require_polyfills2 = __commonJS({
|
|
|
2082
2082
|
}
|
|
2083
2083
|
if (platform2 === "win32") {
|
|
2084
2084
|
fs6.rename = typeof fs6.rename !== "function" ? fs6.rename : (function(fs$rename) {
|
|
2085
|
-
function
|
|
2085
|
+
function rename4(from, to, cb) {
|
|
2086
2086
|
var start = Date.now();
|
|
2087
2087
|
var backoff = 0;
|
|
2088
2088
|
fs$rename(from, to, function CB(er) {
|
|
@@ -2102,8 +2102,8 @@ var require_polyfills2 = __commonJS({
|
|
|
2102
2102
|
if (cb) cb(er);
|
|
2103
2103
|
});
|
|
2104
2104
|
}
|
|
2105
|
-
if (Object.setPrototypeOf) Object.setPrototypeOf(
|
|
2106
|
-
return
|
|
2105
|
+
if (Object.setPrototypeOf) Object.setPrototypeOf(rename4, fs$rename);
|
|
2106
|
+
return rename4;
|
|
2107
2107
|
})(fs6.rename);
|
|
2108
2108
|
}
|
|
2109
2109
|
fs6.read = typeof fs6.read !== "function" ? fs6.read : (function(fs$read) {
|
|
@@ -9437,7 +9437,7 @@ function isNodeError(value) {
|
|
|
9437
9437
|
|
|
9438
9438
|
// src/lib/version.ts
|
|
9439
9439
|
var CLI_PACKAGE_NAME = "@great-arrow/cli";
|
|
9440
|
-
var CLI_VERSION = "0.1.
|
|
9440
|
+
var CLI_VERSION = "0.1.68";
|
|
9441
9441
|
var GAD_INSTALL_VERSION = "2026.08.08";
|
|
9442
9442
|
var GAD_INSTALL_MIN_VERSION = "2026.06.22";
|
|
9443
9443
|
var GAD_INSTALL_HEADER = "X-GAD-Install-Version";
|
|
@@ -9450,7 +9450,14 @@ import { execFile } from "node:child_process";
|
|
|
9450
9450
|
import { randomUUID } from "node:crypto";
|
|
9451
9451
|
|
|
9452
9452
|
// src/lib/keychain.ts
|
|
9453
|
-
import {
|
|
9453
|
+
import {
|
|
9454
|
+
chmod,
|
|
9455
|
+
mkdir,
|
|
9456
|
+
readFile,
|
|
9457
|
+
rename,
|
|
9458
|
+
unlink,
|
|
9459
|
+
writeFile
|
|
9460
|
+
} from "node:fs/promises";
|
|
9454
9461
|
import { homedir } from "node:os";
|
|
9455
9462
|
import { join, dirname } from "node:path";
|
|
9456
9463
|
|
|
@@ -9465,10 +9472,70 @@ function debugSwallow(scope, err2) {
|
|
|
9465
9472
|
// src/lib/keychain.ts
|
|
9466
9473
|
var SERVICE = "GreatArrowDigital";
|
|
9467
9474
|
var ACCOUNT = "api-token";
|
|
9475
|
+
var LOGGED_OUT_TOMBSTONE = "LOGGED_OUT";
|
|
9468
9476
|
function slotFilePath(account) {
|
|
9469
9477
|
if (account === ACCOUNT) return join(homedir(), ".great-arrow", "credential");
|
|
9470
9478
|
return join(homedir(), ".great-arrow", "slots", encodeURIComponent(account));
|
|
9471
9479
|
}
|
|
9480
|
+
function tokenMigratedMarkerPath() {
|
|
9481
|
+
return join(homedir(), ".great-arrow", "credential.migrated");
|
|
9482
|
+
}
|
|
9483
|
+
function isWellFormedTokenContent(trimmed) {
|
|
9484
|
+
if (trimmed === "" || trimmed === LOGGED_OUT_TOMBSTONE) return false;
|
|
9485
|
+
if (trimmed.startsWith("{") || /\s/.test(trimmed)) {
|
|
9486
|
+
try {
|
|
9487
|
+
const parsed = JSON.parse(trimmed);
|
|
9488
|
+
if (typeof parsed !== "object" || parsed === null) return false;
|
|
9489
|
+
const o = parsed;
|
|
9490
|
+
return o.tokenType === "oauth" && typeof o.accessToken === "string" && o.accessToken.length > 0 && typeof o.refreshToken === "string" && o.refreshToken.length > 0 && typeof o.accessExpiresAt === "number" && typeof o.refreshExpiresAt === "number" && typeof o.clientId === "string" && typeof o.apiUrl === "string";
|
|
9491
|
+
} catch {
|
|
9492
|
+
return false;
|
|
9493
|
+
}
|
|
9494
|
+
}
|
|
9495
|
+
return true;
|
|
9496
|
+
}
|
|
9497
|
+
async function inspectTokenFile() {
|
|
9498
|
+
const filePath = slotFilePath(ACCOUNT);
|
|
9499
|
+
try {
|
|
9500
|
+
const raw = await readFile(filePath, "utf-8");
|
|
9501
|
+
try {
|
|
9502
|
+
await chmod(dirname(filePath), 448);
|
|
9503
|
+
await chmod(filePath, 384);
|
|
9504
|
+
} catch (chmodErr) {
|
|
9505
|
+
debugSwallow("keychain.inspectTokenFile(chmod)", chmodErr);
|
|
9506
|
+
}
|
|
9507
|
+
const trimmed = raw.trim();
|
|
9508
|
+
if (trimmed === LOGGED_OUT_TOMBSTONE) {
|
|
9509
|
+
return { status: "tombstone" };
|
|
9510
|
+
}
|
|
9511
|
+
if (!isWellFormedTokenContent(trimmed)) {
|
|
9512
|
+
return { status: "corrupt" };
|
|
9513
|
+
}
|
|
9514
|
+
return { status: "valid", value: trimmed };
|
|
9515
|
+
} catch (err2) {
|
|
9516
|
+
const code = err2?.code;
|
|
9517
|
+
const msg = err2 instanceof Error ? err2.message : "";
|
|
9518
|
+
if (code === "ENOENT" || msg.includes("ENOENT")) {
|
|
9519
|
+
return { status: "missing" };
|
|
9520
|
+
}
|
|
9521
|
+
debugSwallow("keychain.inspectTokenFile", err2);
|
|
9522
|
+
return { status: "corrupt" };
|
|
9523
|
+
}
|
|
9524
|
+
}
|
|
9525
|
+
async function hasTokenMigratedMarker() {
|
|
9526
|
+
try {
|
|
9527
|
+
const raw = await readFile(tokenMigratedMarkerPath(), "utf-8");
|
|
9528
|
+
return raw.trim() !== "";
|
|
9529
|
+
} catch (err2) {
|
|
9530
|
+
const code = err2?.code;
|
|
9531
|
+
const msg = err2 instanceof Error ? err2.message : "";
|
|
9532
|
+
if (code === "ENOENT" || msg.includes("ENOENT")) {
|
|
9533
|
+
return false;
|
|
9534
|
+
}
|
|
9535
|
+
debugSwallow("keychain.hasTokenMigratedMarker", err2);
|
|
9536
|
+
return true;
|
|
9537
|
+
}
|
|
9538
|
+
}
|
|
9472
9539
|
async function loadEntry(account) {
|
|
9473
9540
|
try {
|
|
9474
9541
|
const { AsyncEntry } = await import("@napi-rs/keyring");
|
|
@@ -9486,24 +9553,119 @@ async function readFileFallback(account) {
|
|
|
9486
9553
|
return null;
|
|
9487
9554
|
}
|
|
9488
9555
|
}
|
|
9556
|
+
async function writeAtomicProtectedFile(path10, value) {
|
|
9557
|
+
const dir = dirname(path10);
|
|
9558
|
+
const tmpPath = `${path10}.tmp.${process.pid}.${Date.now()}`;
|
|
9559
|
+
try {
|
|
9560
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
9561
|
+
await chmod(dir, 448);
|
|
9562
|
+
await writeFile(tmpPath, value, { mode: 384 });
|
|
9563
|
+
await chmod(tmpPath, 384);
|
|
9564
|
+
await rename(tmpPath, path10);
|
|
9565
|
+
return true;
|
|
9566
|
+
} catch (err2) {
|
|
9567
|
+
try {
|
|
9568
|
+
await unlink(tmpPath);
|
|
9569
|
+
} catch (cleanupErr) {
|
|
9570
|
+
debugSwallow("keychain.writeFileFallback(cleanup)", cleanupErr);
|
|
9571
|
+
}
|
|
9572
|
+
debugSwallow("keychain.writeFileFallback", err2);
|
|
9573
|
+
return false;
|
|
9574
|
+
}
|
|
9575
|
+
}
|
|
9576
|
+
async function writeFileFallback(account, value) {
|
|
9577
|
+
if (account === ACCOUNT && preferFileStoreWithoutKeyring()) {
|
|
9578
|
+
const priorState = await inspectTokenFile();
|
|
9579
|
+
if (priorState.status === "valid" || priorState.status === "tombstone") {
|
|
9580
|
+
if (!await writeAtomicProtectedFile(tokenMigratedMarkerPath(), "1")) {
|
|
9581
|
+
return false;
|
|
9582
|
+
}
|
|
9583
|
+
return writeAtomicProtectedFile(slotFilePath(ACCOUNT), value);
|
|
9584
|
+
}
|
|
9585
|
+
if (!await writeAtomicProtectedFile(slotFilePath(ACCOUNT), value)) {
|
|
9586
|
+
return false;
|
|
9587
|
+
}
|
|
9588
|
+
if (!await writeAtomicProtectedFile(tokenMigratedMarkerPath(), "1")) {
|
|
9589
|
+
await removeFileFallback(ACCOUNT);
|
|
9590
|
+
return false;
|
|
9591
|
+
}
|
|
9592
|
+
return true;
|
|
9593
|
+
}
|
|
9594
|
+
return writeAtomicProtectedFile(slotFilePath(account), value);
|
|
9595
|
+
}
|
|
9489
9596
|
async function getSecretSlot(account) {
|
|
9490
|
-
|
|
9597
|
+
if (account !== ACCOUNT) {
|
|
9598
|
+
const entry2 = await loadEntry(account);
|
|
9599
|
+
if (entry2) {
|
|
9600
|
+
try {
|
|
9601
|
+
const value = await entry2.getPassword();
|
|
9602
|
+
if (value) return value;
|
|
9603
|
+
} catch (err2) {
|
|
9604
|
+
debugSwallow("keychain.getSecretSlot", err2);
|
|
9605
|
+
}
|
|
9606
|
+
}
|
|
9607
|
+
return readFileFallback(account);
|
|
9608
|
+
}
|
|
9609
|
+
const fileState = await inspectTokenFile();
|
|
9610
|
+
if (fileState.status === "tombstone" || fileState.status === "corrupt") {
|
|
9611
|
+
return null;
|
|
9612
|
+
}
|
|
9613
|
+
if (preferFileStoreWithoutKeyring()) {
|
|
9614
|
+
if (fileState.status === "valid") return fileState.value;
|
|
9615
|
+
if (await hasTokenMigratedMarker()) {
|
|
9616
|
+
return null;
|
|
9617
|
+
}
|
|
9618
|
+
const entry2 = await loadEntry(ACCOUNT);
|
|
9619
|
+
if (entry2) {
|
|
9620
|
+
try {
|
|
9621
|
+
const value = await entry2.getPassword();
|
|
9622
|
+
const trimmed = value?.trim() ?? "";
|
|
9623
|
+
if (trimmed && isWellFormedTokenContent(trimmed)) {
|
|
9624
|
+
const migrated = await writeFileFallback(ACCOUNT, trimmed);
|
|
9625
|
+
if (!migrated) {
|
|
9626
|
+
return null;
|
|
9627
|
+
}
|
|
9628
|
+
try {
|
|
9629
|
+
await entry2.deletePassword();
|
|
9630
|
+
} catch (err2) {
|
|
9631
|
+
debugSwallow("keychain.getSecretSlot(delete migrated)", err2);
|
|
9632
|
+
}
|
|
9633
|
+
return trimmed;
|
|
9634
|
+
}
|
|
9635
|
+
} catch (err2) {
|
|
9636
|
+
debugSwallow("keychain.getSecretSlot", err2);
|
|
9637
|
+
}
|
|
9638
|
+
}
|
|
9639
|
+
return null;
|
|
9640
|
+
}
|
|
9641
|
+
const entry = await loadEntry(ACCOUNT);
|
|
9491
9642
|
if (entry) {
|
|
9492
9643
|
try {
|
|
9493
9644
|
const value = await entry.getPassword();
|
|
9494
|
-
|
|
9645
|
+
const trimmed = value?.trim() ?? "";
|
|
9646
|
+
if (trimmed && isWellFormedTokenContent(trimmed)) {
|
|
9647
|
+
return trimmed;
|
|
9648
|
+
}
|
|
9495
9649
|
} catch (err2) {
|
|
9496
9650
|
debugSwallow("keychain.getSecretSlot", err2);
|
|
9497
9651
|
}
|
|
9498
9652
|
}
|
|
9499
|
-
return
|
|
9653
|
+
return fileState.status === "valid" ? fileState.value : null;
|
|
9654
|
+
}
|
|
9655
|
+
function preferFileStoreWithoutKeyring() {
|
|
9656
|
+
return process.platform === "darwin" && !process.env.VITEST && process.env.GAD_FORCE_KEYRING !== "1";
|
|
9500
9657
|
}
|
|
9501
9658
|
async function setSecretSlot(account, value) {
|
|
9659
|
+
if (account === ACCOUNT && preferFileStoreWithoutKeyring()) {
|
|
9660
|
+
return writeFileFallback(ACCOUNT, value);
|
|
9661
|
+
}
|
|
9502
9662
|
const entry = await loadEntry(account);
|
|
9503
9663
|
if (entry) {
|
|
9504
9664
|
try {
|
|
9505
9665
|
await entry.setPassword(value);
|
|
9506
|
-
await removeFileFallback(account)
|
|
9666
|
+
if (!await removeFileFallback(account)) {
|
|
9667
|
+
return false;
|
|
9668
|
+
}
|
|
9507
9669
|
return true;
|
|
9508
9670
|
} catch (err2) {
|
|
9509
9671
|
debugSwallow("keychain.setSecretSlot", err2);
|
|
@@ -9515,25 +9677,47 @@ async function setSecretSlot(account, value) {
|
|
|
9515
9677
|
return false;
|
|
9516
9678
|
}
|
|
9517
9679
|
}
|
|
9518
|
-
|
|
9519
|
-
const path10 = slotFilePath(account);
|
|
9520
|
-
await mkdir(dirname(path10), { recursive: true, mode: 448 });
|
|
9521
|
-
await writeFile(path10, value, { mode: 384 });
|
|
9522
|
-
return true;
|
|
9523
|
-
} catch {
|
|
9524
|
-
return false;
|
|
9525
|
-
}
|
|
9680
|
+
return writeFileFallback(account, value);
|
|
9526
9681
|
}
|
|
9527
9682
|
async function deleteSecretSlot(account) {
|
|
9528
|
-
|
|
9683
|
+
if (account !== ACCOUNT) {
|
|
9684
|
+
const entry2 = await loadEntry(account);
|
|
9685
|
+
if (entry2) {
|
|
9686
|
+
try {
|
|
9687
|
+
await entry2.deletePassword();
|
|
9688
|
+
} catch (err2) {
|
|
9689
|
+
debugSwallow("keychain.deleteSecretSlot", err2);
|
|
9690
|
+
}
|
|
9691
|
+
}
|
|
9692
|
+
await removeFileFallback(account);
|
|
9693
|
+
return;
|
|
9694
|
+
}
|
|
9695
|
+
let keychainCleared = false;
|
|
9696
|
+
const entry = await loadEntry(ACCOUNT);
|
|
9529
9697
|
if (entry) {
|
|
9530
9698
|
try {
|
|
9531
9699
|
await entry.deletePassword();
|
|
9700
|
+
keychainCleared = true;
|
|
9532
9701
|
} catch (err2) {
|
|
9533
9702
|
debugSwallow("keychain.deleteSecretSlot", err2);
|
|
9534
9703
|
}
|
|
9704
|
+
} else {
|
|
9705
|
+
keychainCleared = true;
|
|
9706
|
+
}
|
|
9707
|
+
const tombstoned = await writeFileFallback(ACCOUNT, LOGGED_OUT_TOMBSTONE);
|
|
9708
|
+
if (!tombstoned) {
|
|
9709
|
+
if (preferFileStoreWithoutKeyring()) {
|
|
9710
|
+
throw new Error(
|
|
9711
|
+
"Could not remove stored credentials from keychain or credential file."
|
|
9712
|
+
);
|
|
9713
|
+
}
|
|
9714
|
+
const removed = await removeFileFallback(ACCOUNT);
|
|
9715
|
+
if (!keychainCleared || !removed) {
|
|
9716
|
+
throw new Error(
|
|
9717
|
+
"Could not remove stored credentials from keychain or credential file."
|
|
9718
|
+
);
|
|
9719
|
+
}
|
|
9535
9720
|
}
|
|
9536
|
-
await removeFileFallback(account);
|
|
9537
9721
|
}
|
|
9538
9722
|
function getToken() {
|
|
9539
9723
|
return getSecretSlot(ACCOUNT);
|
|
@@ -9547,10 +9731,13 @@ function deleteToken() {
|
|
|
9547
9731
|
async function removeFileFallback(account) {
|
|
9548
9732
|
try {
|
|
9549
9733
|
await unlink(slotFilePath(account));
|
|
9734
|
+
return true;
|
|
9550
9735
|
} catch (err2) {
|
|
9551
|
-
if (err2?.code
|
|
9552
|
-
|
|
9736
|
+
if (err2?.code === "ENOENT") {
|
|
9737
|
+
return true;
|
|
9553
9738
|
}
|
|
9739
|
+
debugSwallow("keychain.removeFileFallback", err2);
|
|
9740
|
+
return false;
|
|
9554
9741
|
}
|
|
9555
9742
|
}
|
|
9556
9743
|
|
|
@@ -9563,16 +9750,17 @@ function isOAuthCredential(value) {
|
|
|
9563
9750
|
function parseStoredValue(raw) {
|
|
9564
9751
|
if (!raw) return null;
|
|
9565
9752
|
const trimmed = raw.trim();
|
|
9566
|
-
if (trimmed === "") return null;
|
|
9567
|
-
if (trimmed.startsWith("{")) {
|
|
9753
|
+
if (trimmed === "" || trimmed === "LOGGED_OUT") return null;
|
|
9754
|
+
if (trimmed.startsWith("{") || /\s/.test(trimmed)) {
|
|
9568
9755
|
try {
|
|
9569
9756
|
const parsed = JSON.parse(trimmed);
|
|
9570
9757
|
if (isOAuthCredential(parsed)) {
|
|
9571
9758
|
return { kind: "oauth", credential: parsed };
|
|
9572
9759
|
}
|
|
9573
9760
|
} catch {
|
|
9574
|
-
return
|
|
9761
|
+
return null;
|
|
9575
9762
|
}
|
|
9763
|
+
return null;
|
|
9576
9764
|
}
|
|
9577
9765
|
return { kind: "pat", token: trimmed };
|
|
9578
9766
|
}
|
|
@@ -10782,12 +10970,24 @@ var logoutCommand = new Command("logout").description("Remove stored authenticat
|
|
|
10782
10970
|
tokenTypeHint: "refresh_token"
|
|
10783
10971
|
});
|
|
10784
10972
|
}
|
|
10785
|
-
|
|
10973
|
+
let clearError = null;
|
|
10974
|
+
try {
|
|
10975
|
+
await clearStoredCredential();
|
|
10976
|
+
} catch (err2) {
|
|
10977
|
+
clearError = err2;
|
|
10978
|
+
}
|
|
10786
10979
|
const config2 = await loadConfig();
|
|
10787
10980
|
if (config2.token) {
|
|
10788
10981
|
config2.token = "";
|
|
10789
10982
|
await saveConfig(config2);
|
|
10790
10983
|
}
|
|
10984
|
+
if (clearError) {
|
|
10985
|
+
fmt.error(
|
|
10986
|
+
clearError instanceof Error ? clearError.message : "Could not remove stored authentication credentials."
|
|
10987
|
+
);
|
|
10988
|
+
process.exitCode = 1 /* AUTH_ERROR */;
|
|
10989
|
+
return;
|
|
10990
|
+
}
|
|
10791
10991
|
fmt.success("Logged out successfully.");
|
|
10792
10992
|
});
|
|
10793
10993
|
|
|
@@ -14673,11 +14873,11 @@ function inspectConfiguredHooks(hooks, deps) {
|
|
|
14673
14873
|
// src/lib/suggest-hook.ts
|
|
14674
14874
|
import { existsSync as existsSync3 } from "node:fs";
|
|
14675
14875
|
import {
|
|
14676
|
-
chmod,
|
|
14876
|
+
chmod as chmod2,
|
|
14677
14877
|
copyFile,
|
|
14678
14878
|
mkdir as mkdir4,
|
|
14679
14879
|
readFile as readFile3,
|
|
14680
|
-
rename,
|
|
14880
|
+
rename as rename2,
|
|
14681
14881
|
unlink as unlink2,
|
|
14682
14882
|
writeFile as writeFile3
|
|
14683
14883
|
} from "node:fs/promises";
|
|
@@ -14768,7 +14968,7 @@ async function atomicWrite(path10, content, mode) {
|
|
|
14768
14968
|
await mkdir4(dirname4(path10), { recursive: true });
|
|
14769
14969
|
const tmp = `${path10}.tmp-${process.pid}`;
|
|
14770
14970
|
await writeFile3(tmp, content, mode === void 0 ? "utf8" : { mode });
|
|
14771
|
-
await
|
|
14971
|
+
await rename2(tmp, path10);
|
|
14772
14972
|
}
|
|
14773
14973
|
function hasHookCredential(content) {
|
|
14774
14974
|
try {
|
|
@@ -14795,7 +14995,7 @@ async function enableSuggestHook(opts) {
|
|
|
14795
14995
|
);
|
|
14796
14996
|
await mkdir4(dirname4(p.scriptPath), { recursive: true, mode: 448 });
|
|
14797
14997
|
await copyFile(opts.packagedScript, p.scriptPath);
|
|
14798
|
-
await
|
|
14998
|
+
await chmod2(p.scriptPath, 493);
|
|
14799
14999
|
if (plan.changed) await atomicWrite(p.settingsPath, plan.content);
|
|
14800
15000
|
return { changed: plan.changed };
|
|
14801
15001
|
}
|
|
@@ -15293,8 +15493,8 @@ function fingerprint(value) {
|
|
|
15293
15493
|
}
|
|
15294
15494
|
function describeStoredCredential(raw) {
|
|
15295
15495
|
const trimmed = raw?.trim();
|
|
15296
|
-
if (!trimmed) return null;
|
|
15297
|
-
if (trimmed.startsWith("{")) {
|
|
15496
|
+
if (!trimmed || trimmed === "LOGGED_OUT") return null;
|
|
15497
|
+
if (trimmed.startsWith("{") || /\s/.test(trimmed)) {
|
|
15298
15498
|
try {
|
|
15299
15499
|
const parsed = asObject3(JSON.parse(trimmed));
|
|
15300
15500
|
const refreshToken = parsed?.refreshToken;
|
|
@@ -15332,7 +15532,9 @@ function freshness(shape) {
|
|
|
15332
15532
|
return shape.refreshExpiresAt ?? shape.accessExpiresAt ?? 0;
|
|
15333
15533
|
}
|
|
15334
15534
|
function auditCredentialStores(input2) {
|
|
15335
|
-
const
|
|
15535
|
+
const loggedOut = input2.file?.trim() === "LOGGED_OUT";
|
|
15536
|
+
const effectiveKeychain = loggedOut || input2.fileAuthoritative ? null : input2.keychain;
|
|
15537
|
+
const keychain = describeStoredCredential(effectiveKeychain);
|
|
15336
15538
|
const file2 = describeStoredCredential(input2.file);
|
|
15337
15539
|
const findings = [];
|
|
15338
15540
|
if (!keychain && !file2) {
|
|
@@ -15463,14 +15665,16 @@ function planMigration(input2) {
|
|
|
15463
15665
|
if ((store.shape.refreshExpiresAt ?? 0) <= input2.now) return true;
|
|
15464
15666
|
return Boolean(effective) && freshness(store.shape) <= freshness(effective);
|
|
15465
15667
|
}).map((store) => store.path);
|
|
15466
|
-
const
|
|
15668
|
+
const loggedOut = input2.file?.trim() === "LOGGED_OUT";
|
|
15669
|
+
const keychain = loggedOut || input2.fileAuthoritative ? null : describeStoredCredential(input2.keychain);
|
|
15467
15670
|
const file2 = describeStoredCredential(input2.file);
|
|
15468
15671
|
let syncKeychainToFile = false;
|
|
15469
15672
|
if (keychain && keychain.kind !== "corrupt") {
|
|
15470
15673
|
const keychainUsable = keychain.kind !== "oauth" || (keychain.refreshExpiresAt ?? 0) > input2.now;
|
|
15471
15674
|
if (keychainUsable) {
|
|
15472
|
-
if (!file2
|
|
15473
|
-
|
|
15675
|
+
if (!file2 && !loggedOut && !input2.fileAuthoritative) {
|
|
15676
|
+
syncKeychainToFile = true;
|
|
15677
|
+
} else if (file2 && keychain.fingerprint !== file2.fingerprint) {
|
|
15474
15678
|
syncKeychainToFile = freshness(keychain) > freshness(file2);
|
|
15475
15679
|
}
|
|
15476
15680
|
}
|
|
@@ -16837,7 +17041,7 @@ async function pathExists(path10) {
|
|
|
16837
17041
|
async function installCodexConfig(apiUrl, token, repoUrl) {
|
|
16838
17042
|
const { homedir: homedir11 } = await import("node:os");
|
|
16839
17043
|
const { join: join13 } = await import("node:path");
|
|
16840
|
-
const { chmod:
|
|
17044
|
+
const { chmod: chmod4, copyFile: copyFile3, mkdir: mkdir9, readFile: readFile9, writeFile: writeFile7 } = await import("node:fs/promises");
|
|
16841
17045
|
const codexDir = join13(homedir11(), ".codex");
|
|
16842
17046
|
const configPath = join13(codexDir, "config.toml");
|
|
16843
17047
|
const envPath = join13(codexDir, ".env");
|
|
@@ -16861,7 +17065,7 @@ async function installCodexConfig(apiUrl, token, repoUrl) {
|
|
|
16861
17065
|
upsertEnvValue(existingEnv, "GREAT_ARROW_TOKEN", token),
|
|
16862
17066
|
"utf8"
|
|
16863
17067
|
);
|
|
16864
|
-
await
|
|
17068
|
+
await chmod4(envPath, 384);
|
|
16865
17069
|
return { configPath, envPath };
|
|
16866
17070
|
}
|
|
16867
17071
|
var startCommand = new Command("start").description(
|
|
@@ -17353,17 +17557,31 @@ async function probeEndpoint() {
|
|
|
17353
17557
|
async function applyMigration(plan) {
|
|
17354
17558
|
const { homedir: homedir11 } = await import("node:os");
|
|
17355
17559
|
const { join: join13 } = await import("node:path");
|
|
17356
|
-
const { mkdir: mkdir9, rename:
|
|
17560
|
+
const { chmod: chmod4, mkdir: mkdir9, rename: rename4, unlink: unlink3, writeFile: writeFile7 } = await import("node:fs/promises");
|
|
17357
17561
|
const applied = [];
|
|
17358
17562
|
const dir = join13(homedir11(), ".great-arrow");
|
|
17359
17563
|
await mkdir9(dir, { recursive: true, mode: 448 });
|
|
17564
|
+
await chmod4(dir, 448);
|
|
17565
|
+
const writeAtomic0600 = async (target, raw) => {
|
|
17566
|
+
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
17567
|
+
try {
|
|
17568
|
+
await writeFile7(tmp, raw, { mode: 384, flag: "wx" });
|
|
17569
|
+
await chmod4(tmp, 384);
|
|
17570
|
+
await rename4(tmp, target);
|
|
17571
|
+
} catch (err2) {
|
|
17572
|
+
try {
|
|
17573
|
+
await unlink3(tmp);
|
|
17574
|
+
} catch (cleanupErr) {
|
|
17575
|
+
debugSwallow("mcp.applyMigration(cleanup)", cleanupErr);
|
|
17576
|
+
}
|
|
17577
|
+
throw err2;
|
|
17578
|
+
}
|
|
17579
|
+
};
|
|
17360
17580
|
if (plan.seedFrom) {
|
|
17361
17581
|
const raw = await readIfExists(plan.seedFrom);
|
|
17362
17582
|
if (raw !== null) {
|
|
17363
17583
|
const target = join13(dir, OAUTH_CRED_FILENAME);
|
|
17364
|
-
|
|
17365
|
-
await writeFile7(tmp, raw, { mode: 384 });
|
|
17366
|
-
await rename3(tmp, target);
|
|
17584
|
+
await writeAtomic0600(target, raw);
|
|
17367
17585
|
applied.push(`Copied ${plan.seedFrom} \u2192 ${target}`);
|
|
17368
17586
|
}
|
|
17369
17587
|
}
|
|
@@ -17371,9 +17589,8 @@ async function applyMigration(plan) {
|
|
|
17371
17589
|
const raw = await readKeychainOnly();
|
|
17372
17590
|
if (raw) {
|
|
17373
17591
|
const target = join13(dir, "credential");
|
|
17374
|
-
|
|
17375
|
-
await
|
|
17376
|
-
await rename3(tmp, target);
|
|
17592
|
+
await writeAtomic0600(target, raw);
|
|
17593
|
+
await writeAtomic0600(join13(dir, "credential.migrated"), "1");
|
|
17377
17594
|
applied.push(`Wrote the keychain credential to ${target}`);
|
|
17378
17595
|
}
|
|
17379
17596
|
}
|
|
@@ -17403,10 +17620,14 @@ var doctorSubcommand = new Command("doctor").description(
|
|
|
17403
17620
|
const diagnostics = proxyPath ? await runProxyDiagnostics(proxyPath) : null;
|
|
17404
17621
|
const entries = await scanConnections();
|
|
17405
17622
|
const duplicates = detectDuplicates(entries);
|
|
17406
|
-
const keychain = await readKeychainOnly();
|
|
17407
17623
|
const credentialFile = await readIfExists(
|
|
17408
17624
|
join13(home, ".great-arrow", "credential")
|
|
17409
17625
|
);
|
|
17626
|
+
const credentialMigrated = await readIfExists(
|
|
17627
|
+
join13(home, ".great-arrow", "credential.migrated")
|
|
17628
|
+
) !== null;
|
|
17629
|
+
const fileAuthoritative = credentialFile?.trim() === "LOGGED_OUT" || credentialMigrated || process.platform === "darwin" && credentialFile !== null;
|
|
17630
|
+
const keychain = fileAuthoritative ? null : await readKeychainOnly();
|
|
17410
17631
|
const userStore = await readIfExists(
|
|
17411
17632
|
join13(home, ".great-arrow", OAUTH_CRED_FILENAME)
|
|
17412
17633
|
);
|
|
@@ -17416,6 +17637,7 @@ var doctorSubcommand = new Command("doctor").description(
|
|
|
17416
17637
|
...auditCredentialStores({
|
|
17417
17638
|
keychain,
|
|
17418
17639
|
file: credentialFile,
|
|
17640
|
+
fileAuthoritative,
|
|
17419
17641
|
now: Date.now()
|
|
17420
17642
|
}),
|
|
17421
17643
|
await probeEndpoint()
|
|
@@ -17441,6 +17663,7 @@ var doctorSubcommand = new Command("doctor").description(
|
|
|
17441
17663
|
userStore,
|
|
17442
17664
|
keychain,
|
|
17443
17665
|
file: credentialFile,
|
|
17666
|
+
fileAuthoritative,
|
|
17444
17667
|
now: Date.now()
|
|
17445
17668
|
});
|
|
17446
17669
|
const applied = options.migrate ? await applyMigration(plan) : [];
|
|
@@ -18465,6 +18688,12 @@ function normalizeGlance(raw) {
|
|
|
18465
18688
|
});
|
|
18466
18689
|
return repos.length > 0 ? { landing: { alertActive: l.alertActive === true, repos } } : {};
|
|
18467
18690
|
})(),
|
|
18691
|
+
...(() => {
|
|
18692
|
+
const raw2 = obj.setup;
|
|
18693
|
+
if (typeof raw2 !== "object" || raw2 === null) return {};
|
|
18694
|
+
const r = raw2;
|
|
18695
|
+
return typeof r.step === "string" && typeof r.line === "string" ? { setup: { step: r.step, line: r.line } } : {};
|
|
18696
|
+
})(),
|
|
18468
18697
|
...Array.isArray(obj.sections) ? { sections: normalizeTodaySections(obj.sections) } : {},
|
|
18469
18698
|
...Array.isArray(obj.degradedSections) ? {
|
|
18470
18699
|
degradedSections: obj.degradedSections.filter(
|
|
@@ -49541,7 +49770,7 @@ function renderStatus2(payload, plain, apiUrl) {
|
|
|
49541
49770
|
lines.push(c.badge(` ${payload.workspace} `));
|
|
49542
49771
|
lines.push("");
|
|
49543
49772
|
}
|
|
49544
|
-
const hasAny = payload.needsYou.length > 0 || payload.topGoal !== null || payload.integrations.length > 0 || payload.agents.length > 0 || Boolean(payload.healthTriage) || Boolean(payload.credentialRotation) || Boolean(payload.keyRotation) || Boolean(payload.landing) || payload.warRoom !== null && payload.warRoom.priorityCount > 0;
|
|
49773
|
+
const hasAny = payload.needsYou.length > 0 || payload.topGoal !== null || payload.integrations.length > 0 || payload.agents.length > 0 || Boolean(payload.healthTriage) || Boolean(payload.credentialRotation) || Boolean(payload.keyRotation) || Boolean(payload.landing) || Boolean(payload.setup) || payload.warRoom !== null && payload.warRoom.priorityCount > 0;
|
|
49545
49774
|
if (!hasAny) {
|
|
49546
49775
|
lines.push(
|
|
49547
49776
|
`${c.green("\u2713")} You're all clear \u2014 nothing needs you right now.`
|
|
@@ -49615,6 +49844,11 @@ function renderStatus2(payload, plain, apiUrl) {
|
|
|
49615
49844
|
if (url2) lines.push(` ${c.dim("\u2192")} ${link("Open rotation", url2)}`);
|
|
49616
49845
|
lines.push("");
|
|
49617
49846
|
}
|
|
49847
|
+
if (payload.setup) {
|
|
49848
|
+
lines.push(c.bold("Setup"));
|
|
49849
|
+
lines.push(` ${payload.setup.line}`);
|
|
49850
|
+
lines.push("");
|
|
49851
|
+
}
|
|
49618
49852
|
if (payload.keyRotation) {
|
|
49619
49853
|
const k = payload.keyRotation;
|
|
49620
49854
|
lines.push(c.bold("Key rotation"));
|
|
@@ -49948,11 +50182,11 @@ import { Writable } from "node:stream";
|
|
|
49948
50182
|
// src/lib/keys-apply.ts
|
|
49949
50183
|
import { spawn } from "node:child_process";
|
|
49950
50184
|
import {
|
|
49951
|
-
chmod as
|
|
50185
|
+
chmod as chmod3,
|
|
49952
50186
|
mkdir as mkdir8,
|
|
49953
50187
|
readFile as readFile7,
|
|
49954
50188
|
realpath,
|
|
49955
|
-
rename as
|
|
50189
|
+
rename as rename3,
|
|
49956
50190
|
stat as stat2,
|
|
49957
50191
|
writeFile as writeFile6
|
|
49958
50192
|
} from "node:fs/promises";
|
|
@@ -50058,8 +50292,8 @@ function formatEnvValue(value, quote) {
|
|
|
50058
50292
|
async function writeAtomic(path10, content, mode) {
|
|
50059
50293
|
const tmp = `${path10}.tmp`;
|
|
50060
50294
|
await writeFile6(tmp, content, { mode });
|
|
50061
|
-
await
|
|
50062
|
-
await
|
|
50295
|
+
await chmod3(tmp, mode);
|
|
50296
|
+
await rename3(tmp, path10);
|
|
50063
50297
|
}
|
|
50064
50298
|
async function applyEnvFile(path10, key, value) {
|
|
50065
50299
|
const file2 = expandHome(path10);
|