@great-arrow/cli 0.1.65 → 0.1.67
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 +10 -6
- package/dist/index.js +546 -94
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,7 +31,9 @@ gad login
|
|
|
31
31
|
gad login --no-browser
|
|
32
32
|
|
|
33
33
|
# Direct personal token (CI/CD, headless). Stored as-is; no auto-refresh.
|
|
34
|
-
|
|
34
|
+
# Over a signed-in OAuth session it takes a gad_v1_ token, keeps the session
|
|
35
|
+
# when handed the session's own access token, and otherwise needs --force.
|
|
36
|
+
gad login --token gad_v1_xxxxx
|
|
35
37
|
|
|
36
38
|
# Check who you are (shows auth type + access-token expiry)
|
|
37
39
|
gad whoami
|
|
@@ -47,11 +49,13 @@ gad logout
|
|
|
47
49
|
gad auth token [--json]
|
|
48
50
|
```
|
|
49
51
|
|
|
50
|
-
Credentials live in your
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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.
|
|
55
59
|
|
|
56
60
|
## Commands
|
|
57
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.67";
|
|
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,114 @@ 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
|
+
return trimmed;
|
|
9629
|
+
}
|
|
9630
|
+
} catch (err2) {
|
|
9631
|
+
debugSwallow("keychain.getSecretSlot", err2);
|
|
9632
|
+
}
|
|
9633
|
+
}
|
|
9634
|
+
return null;
|
|
9635
|
+
}
|
|
9636
|
+
const entry = await loadEntry(ACCOUNT);
|
|
9491
9637
|
if (entry) {
|
|
9492
9638
|
try {
|
|
9493
9639
|
const value = await entry.getPassword();
|
|
9494
|
-
|
|
9640
|
+
const trimmed = value?.trim() ?? "";
|
|
9641
|
+
if (trimmed && isWellFormedTokenContent(trimmed)) {
|
|
9642
|
+
return trimmed;
|
|
9643
|
+
}
|
|
9495
9644
|
} catch (err2) {
|
|
9496
9645
|
debugSwallow("keychain.getSecretSlot", err2);
|
|
9497
9646
|
}
|
|
9498
9647
|
}
|
|
9499
|
-
return
|
|
9648
|
+
return fileState.status === "valid" ? fileState.value : null;
|
|
9649
|
+
}
|
|
9650
|
+
function preferFileStoreWithoutKeyring() {
|
|
9651
|
+
return process.platform === "darwin" && !process.env.VITEST && process.env.GAD_FORCE_KEYRING !== "1";
|
|
9500
9652
|
}
|
|
9501
9653
|
async function setSecretSlot(account, value) {
|
|
9654
|
+
if (account === ACCOUNT && preferFileStoreWithoutKeyring()) {
|
|
9655
|
+
return writeFileFallback(ACCOUNT, value);
|
|
9656
|
+
}
|
|
9502
9657
|
const entry = await loadEntry(account);
|
|
9503
9658
|
if (entry) {
|
|
9504
9659
|
try {
|
|
9505
9660
|
await entry.setPassword(value);
|
|
9506
|
-
await removeFileFallback(account)
|
|
9661
|
+
if (!await removeFileFallback(account)) {
|
|
9662
|
+
return false;
|
|
9663
|
+
}
|
|
9507
9664
|
return true;
|
|
9508
9665
|
} catch (err2) {
|
|
9509
9666
|
debugSwallow("keychain.setSecretSlot", err2);
|
|
@@ -9515,25 +9672,47 @@ async function setSecretSlot(account, value) {
|
|
|
9515
9672
|
return false;
|
|
9516
9673
|
}
|
|
9517
9674
|
}
|
|
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
|
-
}
|
|
9675
|
+
return writeFileFallback(account, value);
|
|
9526
9676
|
}
|
|
9527
9677
|
async function deleteSecretSlot(account) {
|
|
9528
|
-
|
|
9678
|
+
if (account !== ACCOUNT) {
|
|
9679
|
+
const entry2 = await loadEntry(account);
|
|
9680
|
+
if (entry2) {
|
|
9681
|
+
try {
|
|
9682
|
+
await entry2.deletePassword();
|
|
9683
|
+
} catch (err2) {
|
|
9684
|
+
debugSwallow("keychain.deleteSecretSlot", err2);
|
|
9685
|
+
}
|
|
9686
|
+
}
|
|
9687
|
+
await removeFileFallback(account);
|
|
9688
|
+
return;
|
|
9689
|
+
}
|
|
9690
|
+
let keychainCleared = false;
|
|
9691
|
+
const entry = await loadEntry(ACCOUNT);
|
|
9529
9692
|
if (entry) {
|
|
9530
9693
|
try {
|
|
9531
9694
|
await entry.deletePassword();
|
|
9695
|
+
keychainCleared = true;
|
|
9532
9696
|
} catch (err2) {
|
|
9533
9697
|
debugSwallow("keychain.deleteSecretSlot", err2);
|
|
9534
9698
|
}
|
|
9699
|
+
} else {
|
|
9700
|
+
keychainCleared = true;
|
|
9701
|
+
}
|
|
9702
|
+
const tombstoned = await writeFileFallback(ACCOUNT, LOGGED_OUT_TOMBSTONE);
|
|
9703
|
+
if (!tombstoned) {
|
|
9704
|
+
if (preferFileStoreWithoutKeyring()) {
|
|
9705
|
+
throw new Error(
|
|
9706
|
+
"Could not remove stored credentials from keychain or credential file."
|
|
9707
|
+
);
|
|
9708
|
+
}
|
|
9709
|
+
const removed = await removeFileFallback(ACCOUNT);
|
|
9710
|
+
if (!keychainCleared || !removed) {
|
|
9711
|
+
throw new Error(
|
|
9712
|
+
"Could not remove stored credentials from keychain or credential file."
|
|
9713
|
+
);
|
|
9714
|
+
}
|
|
9535
9715
|
}
|
|
9536
|
-
await removeFileFallback(account);
|
|
9537
9716
|
}
|
|
9538
9717
|
function getToken() {
|
|
9539
9718
|
return getSecretSlot(ACCOUNT);
|
|
@@ -9547,10 +9726,13 @@ function deleteToken() {
|
|
|
9547
9726
|
async function removeFileFallback(account) {
|
|
9548
9727
|
try {
|
|
9549
9728
|
await unlink(slotFilePath(account));
|
|
9729
|
+
return true;
|
|
9550
9730
|
} catch (err2) {
|
|
9551
|
-
if (err2?.code
|
|
9552
|
-
|
|
9731
|
+
if (err2?.code === "ENOENT") {
|
|
9732
|
+
return true;
|
|
9553
9733
|
}
|
|
9734
|
+
debugSwallow("keychain.removeFileFallback", err2);
|
|
9735
|
+
return false;
|
|
9554
9736
|
}
|
|
9555
9737
|
}
|
|
9556
9738
|
|
|
@@ -9563,16 +9745,17 @@ function isOAuthCredential(value) {
|
|
|
9563
9745
|
function parseStoredValue(raw) {
|
|
9564
9746
|
if (!raw) return null;
|
|
9565
9747
|
const trimmed = raw.trim();
|
|
9566
|
-
if (trimmed === "") return null;
|
|
9567
|
-
if (trimmed.startsWith("{")) {
|
|
9748
|
+
if (trimmed === "" || trimmed === "LOGGED_OUT") return null;
|
|
9749
|
+
if (trimmed.startsWith("{") || /\s/.test(trimmed)) {
|
|
9568
9750
|
try {
|
|
9569
9751
|
const parsed = JSON.parse(trimmed);
|
|
9570
9752
|
if (isOAuthCredential(parsed)) {
|
|
9571
9753
|
return { kind: "oauth", credential: parsed };
|
|
9572
9754
|
}
|
|
9573
9755
|
} catch {
|
|
9574
|
-
return
|
|
9756
|
+
return null;
|
|
9575
9757
|
}
|
|
9758
|
+
return null;
|
|
9576
9759
|
}
|
|
9577
9760
|
return { kind: "pat", token: trimmed };
|
|
9578
9761
|
}
|
|
@@ -9594,6 +9777,17 @@ async function clearStoredCredential() {
|
|
|
9594
9777
|
await deleteToken();
|
|
9595
9778
|
}
|
|
9596
9779
|
|
|
9780
|
+
// src/lib/token-login-guard.ts
|
|
9781
|
+
function decideTokenLogin(input2) {
|
|
9782
|
+
const { token, stored, force } = input2;
|
|
9783
|
+
if (stored?.kind !== "oauth") return { action: "store" };
|
|
9784
|
+
if (token === stored.credential.accessToken) {
|
|
9785
|
+
return { action: "keep-oauth", reason: "same-access-token" };
|
|
9786
|
+
}
|
|
9787
|
+
if (token.startsWith("gad_v1_") || force) return { action: "store" };
|
|
9788
|
+
return { action: "refuse", reason: "would-replace-oauth" };
|
|
9789
|
+
}
|
|
9790
|
+
|
|
9597
9791
|
// src/lib/cli-locator.ts
|
|
9598
9792
|
import {
|
|
9599
9793
|
chmodSync,
|
|
@@ -10369,7 +10563,10 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
|
|
|
10369
10563
|
).option(
|
|
10370
10564
|
"--no-browser",
|
|
10371
10565
|
"Print the authorize URL instead of opening a browser"
|
|
10372
|
-
).option("--scope <list>", "OAuth scope to request", "mcp").
|
|
10566
|
+
).option("--scope <list>", "OAuth scope to request", "mcp").option(
|
|
10567
|
+
"--force",
|
|
10568
|
+
"With --token: replace a signed-in OAuth session even if the token may be short-lived"
|
|
10569
|
+
).action(
|
|
10373
10570
|
async (options) => {
|
|
10374
10571
|
const fmt = createFormatter({
|
|
10375
10572
|
json: false,
|
|
@@ -10380,6 +10577,24 @@ var loginCommand = new Command("login").description("Authenticate the CLI with y
|
|
|
10380
10577
|
const config2 = await loadConfig();
|
|
10381
10578
|
const apiUrl = DEFAULT_API_URL;
|
|
10382
10579
|
if (options.token) {
|
|
10580
|
+
const decision = decideTokenLogin({
|
|
10581
|
+
token: options.token,
|
|
10582
|
+
stored: await loadStoredCredential(),
|
|
10583
|
+
force: options.force === true
|
|
10584
|
+
});
|
|
10585
|
+
if (decision.action === "keep-oauth") {
|
|
10586
|
+
fmt.success(
|
|
10587
|
+
"Already signed in: that is your OAuth session's current access token. Keeping the self-refreshing session."
|
|
10588
|
+
);
|
|
10589
|
+
return;
|
|
10590
|
+
}
|
|
10591
|
+
if (decision.action === "refuse") {
|
|
10592
|
+
fmt.error(
|
|
10593
|
+
"You are signed in with a self-refreshing OAuth session, and this token is not a personal API token (gad_v1_\u2026). Storing it would replace the session, and if it is an OAuth access token it stops working within the hour. Use a gad_v1_ token, or pass --force to replace the session anyway."
|
|
10594
|
+
);
|
|
10595
|
+
process.exitCode = 1;
|
|
10596
|
+
return;
|
|
10597
|
+
}
|
|
10383
10598
|
const stored = await savePatToken(options.token);
|
|
10384
10599
|
if (!stored) {
|
|
10385
10600
|
fmt.error(
|
|
@@ -10750,12 +10965,24 @@ var logoutCommand = new Command("logout").description("Remove stored authenticat
|
|
|
10750
10965
|
tokenTypeHint: "refresh_token"
|
|
10751
10966
|
});
|
|
10752
10967
|
}
|
|
10753
|
-
|
|
10968
|
+
let clearError = null;
|
|
10969
|
+
try {
|
|
10970
|
+
await clearStoredCredential();
|
|
10971
|
+
} catch (err2) {
|
|
10972
|
+
clearError = err2;
|
|
10973
|
+
}
|
|
10754
10974
|
const config2 = await loadConfig();
|
|
10755
10975
|
if (config2.token) {
|
|
10756
10976
|
config2.token = "";
|
|
10757
10977
|
await saveConfig(config2);
|
|
10758
10978
|
}
|
|
10979
|
+
if (clearError) {
|
|
10980
|
+
fmt.error(
|
|
10981
|
+
clearError instanceof Error ? clearError.message : "Could not remove stored authentication credentials."
|
|
10982
|
+
);
|
|
10983
|
+
process.exitCode = 1 /* AUTH_ERROR */;
|
|
10984
|
+
return;
|
|
10985
|
+
}
|
|
10759
10986
|
fmt.success("Logged out successfully.");
|
|
10760
10987
|
});
|
|
10761
10988
|
|
|
@@ -11072,7 +11299,7 @@ function getCommandWorkspaceTarget(options = {}) {
|
|
|
11072
11299
|
}
|
|
11073
11300
|
|
|
11074
11301
|
// ../local-runtime/src/abortable.ts
|
|
11075
|
-
function abortable(
|
|
11302
|
+
function abortable(run4, signal) {
|
|
11076
11303
|
return new Promise((resolve5, reject) => {
|
|
11077
11304
|
const abort = () => {
|
|
11078
11305
|
signal.removeEventListener("abort", abort);
|
|
@@ -11082,11 +11309,11 @@ function abortable(run3, signal) {
|
|
|
11082
11309
|
signal.addEventListener("abort", abort, { once: true });
|
|
11083
11310
|
Promise.resolve().then(() => {
|
|
11084
11311
|
signal.throwIfAborted();
|
|
11085
|
-
return
|
|
11312
|
+
return run4();
|
|
11086
11313
|
}).then(resolve5, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
11087
11314
|
});
|
|
11088
11315
|
}
|
|
11089
|
-
async function withLocalDeadline(deadlineMs,
|
|
11316
|
+
async function withLocalDeadline(deadlineMs, run4, parentSignal) {
|
|
11090
11317
|
if (!Number.isSafeInteger(deadlineMs) || deadlineMs <= 0 || deadlineMs > 36e5)
|
|
11091
11318
|
throw new Error("Invalid local operation deadline");
|
|
11092
11319
|
const controller = new AbortController();
|
|
@@ -11096,7 +11323,7 @@ async function withLocalDeadline(deadlineMs, run3, parentSignal) {
|
|
|
11096
11323
|
deadlineMs
|
|
11097
11324
|
);
|
|
11098
11325
|
try {
|
|
11099
|
-
return await abortable(() =>
|
|
11326
|
+
return await abortable(() => run4(signal), signal);
|
|
11100
11327
|
} finally {
|
|
11101
11328
|
clearTimeout(timer);
|
|
11102
11329
|
}
|
|
@@ -11258,6 +11485,7 @@ async function withNativeResponse(context2, path10, init, consume, parentSignal)
|
|
|
11258
11485
|
|
|
11259
11486
|
// src/lib/client.ts
|
|
11260
11487
|
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
11488
|
+
var PRESENCE_TIMEOUT_MS = 4e4;
|
|
11261
11489
|
var MAX_RETRIES = 5;
|
|
11262
11490
|
var BASE_DELAY_MS = 1e3;
|
|
11263
11491
|
function detectGitOrigin() {
|
|
@@ -11649,6 +11877,47 @@ var GadClient = class {
|
|
|
11649
11877
|
})();
|
|
11650
11878
|
}
|
|
11651
11879
|
}
|
|
11880
|
+
/**
|
|
11881
|
+
* One request with NO retry, bounded by `AbortSignal.timeout`. For the
|
|
11882
|
+
* agent-presence routes: a replayed long-poll doubles a hold and the caller
|
|
11883
|
+
* already owns the loop and its backoff. Returns the status and parsed body
|
|
11884
|
+
* (null when not JSON) so the caller can read a 404 itself; a timeout or
|
|
11885
|
+
* network failure throws.
|
|
11886
|
+
*/
|
|
11887
|
+
async requestOnce(method, path10, opts = {}) {
|
|
11888
|
+
const qs = opts.params && Object.keys(opts.params).length > 0 ? `?${new URLSearchParams(opts.params).toString()}` : "";
|
|
11889
|
+
const signal = AbortSignal.timeout(opts.timeoutMs ?? PRESENCE_TIMEOUT_MS);
|
|
11890
|
+
const headers = this.buildHeaders();
|
|
11891
|
+
if (method === "GET") delete headers["Content-Type"];
|
|
11892
|
+
if (this.getToken) {
|
|
11893
|
+
const getToken2 = this.getToken;
|
|
11894
|
+
headers.Authorization = `Bearer ${await new Promise(
|
|
11895
|
+
(resolve5, reject) => {
|
|
11896
|
+
signal.addEventListener(
|
|
11897
|
+
"abort",
|
|
11898
|
+
() => reject(networkError("Session renewal timed out.")),
|
|
11899
|
+
{ once: true }
|
|
11900
|
+
);
|
|
11901
|
+
getToken2().then(resolve5, reject);
|
|
11902
|
+
}
|
|
11903
|
+
)}`;
|
|
11904
|
+
}
|
|
11905
|
+
const response = await fetch(`${this.apiUrl}${path10}${qs}`, {
|
|
11906
|
+
method,
|
|
11907
|
+
headers,
|
|
11908
|
+
...opts.body !== void 0 && { body: JSON.stringify(opts.body) },
|
|
11909
|
+
redirect: "error",
|
|
11910
|
+
cache: "no-store",
|
|
11911
|
+
signal
|
|
11912
|
+
});
|
|
11913
|
+
let body = null;
|
|
11914
|
+
try {
|
|
11915
|
+
body = await response.json();
|
|
11916
|
+
} catch {
|
|
11917
|
+
body = null;
|
|
11918
|
+
}
|
|
11919
|
+
return { status: response.status, body };
|
|
11920
|
+
}
|
|
11652
11921
|
/**
|
|
11653
11922
|
* Make a GET request to a REST API endpoint.
|
|
11654
11923
|
*
|
|
@@ -12393,8 +12662,8 @@ function text(value) {
|
|
|
12393
12662
|
}
|
|
12394
12663
|
function formatTodoDetail(todo) {
|
|
12395
12664
|
const assignees = Array.isArray(todo.assignees) ? todo.assignees.map((a) => `${text(a.displayName)} (${text(a.type)})`).join(", ") : "";
|
|
12396
|
-
const
|
|
12397
|
-
const lastRun =
|
|
12665
|
+
const run4 = todo.lastRun ?? null;
|
|
12666
|
+
const lastRun = run4 ? `${text(run4.status)} \u2014 ${text(run4.agentName)}` : "";
|
|
12398
12667
|
return {
|
|
12399
12668
|
id: text(todo.id),
|
|
12400
12669
|
title: text(todo.title),
|
|
@@ -14599,11 +14868,11 @@ function inspectConfiguredHooks(hooks, deps) {
|
|
|
14599
14868
|
// src/lib/suggest-hook.ts
|
|
14600
14869
|
import { existsSync as existsSync3 } from "node:fs";
|
|
14601
14870
|
import {
|
|
14602
|
-
chmod,
|
|
14871
|
+
chmod as chmod2,
|
|
14603
14872
|
copyFile,
|
|
14604
14873
|
mkdir as mkdir4,
|
|
14605
14874
|
readFile as readFile3,
|
|
14606
|
-
rename,
|
|
14875
|
+
rename as rename2,
|
|
14607
14876
|
unlink as unlink2,
|
|
14608
14877
|
writeFile as writeFile3
|
|
14609
14878
|
} from "node:fs/promises";
|
|
@@ -14694,7 +14963,7 @@ async function atomicWrite(path10, content, mode) {
|
|
|
14694
14963
|
await mkdir4(dirname4(path10), { recursive: true });
|
|
14695
14964
|
const tmp = `${path10}.tmp-${process.pid}`;
|
|
14696
14965
|
await writeFile3(tmp, content, mode === void 0 ? "utf8" : { mode });
|
|
14697
|
-
await
|
|
14966
|
+
await rename2(tmp, path10);
|
|
14698
14967
|
}
|
|
14699
14968
|
function hasHookCredential(content) {
|
|
14700
14969
|
try {
|
|
@@ -14721,7 +14990,7 @@ async function enableSuggestHook(opts) {
|
|
|
14721
14990
|
);
|
|
14722
14991
|
await mkdir4(dirname4(p.scriptPath), { recursive: true, mode: 448 });
|
|
14723
14992
|
await copyFile(opts.packagedScript, p.scriptPath);
|
|
14724
|
-
await
|
|
14993
|
+
await chmod2(p.scriptPath, 493);
|
|
14725
14994
|
if (plan.changed) await atomicWrite(p.settingsPath, plan.content);
|
|
14726
14995
|
return { changed: plan.changed };
|
|
14727
14996
|
}
|
|
@@ -15219,8 +15488,8 @@ function fingerprint(value) {
|
|
|
15219
15488
|
}
|
|
15220
15489
|
function describeStoredCredential(raw) {
|
|
15221
15490
|
const trimmed = raw?.trim();
|
|
15222
|
-
if (!trimmed) return null;
|
|
15223
|
-
if (trimmed.startsWith("{")) {
|
|
15491
|
+
if (!trimmed || trimmed === "LOGGED_OUT") return null;
|
|
15492
|
+
if (trimmed.startsWith("{") || /\s/.test(trimmed)) {
|
|
15224
15493
|
try {
|
|
15225
15494
|
const parsed = asObject3(JSON.parse(trimmed));
|
|
15226
15495
|
const refreshToken = parsed?.refreshToken;
|
|
@@ -15258,7 +15527,9 @@ function freshness(shape) {
|
|
|
15258
15527
|
return shape.refreshExpiresAt ?? shape.accessExpiresAt ?? 0;
|
|
15259
15528
|
}
|
|
15260
15529
|
function auditCredentialStores(input2) {
|
|
15261
|
-
const
|
|
15530
|
+
const loggedOut = input2.file?.trim() === "LOGGED_OUT";
|
|
15531
|
+
const effectiveKeychain = loggedOut || input2.fileAuthoritative ? null : input2.keychain;
|
|
15532
|
+
const keychain = describeStoredCredential(effectiveKeychain);
|
|
15262
15533
|
const file2 = describeStoredCredential(input2.file);
|
|
15263
15534
|
const findings = [];
|
|
15264
15535
|
if (!keychain && !file2) {
|
|
@@ -15389,14 +15660,16 @@ function planMigration(input2) {
|
|
|
15389
15660
|
if ((store.shape.refreshExpiresAt ?? 0) <= input2.now) return true;
|
|
15390
15661
|
return Boolean(effective) && freshness(store.shape) <= freshness(effective);
|
|
15391
15662
|
}).map((store) => store.path);
|
|
15392
|
-
const
|
|
15663
|
+
const loggedOut = input2.file?.trim() === "LOGGED_OUT";
|
|
15664
|
+
const keychain = loggedOut || input2.fileAuthoritative ? null : describeStoredCredential(input2.keychain);
|
|
15393
15665
|
const file2 = describeStoredCredential(input2.file);
|
|
15394
15666
|
let syncKeychainToFile = false;
|
|
15395
15667
|
if (keychain && keychain.kind !== "corrupt") {
|
|
15396
15668
|
const keychainUsable = keychain.kind !== "oauth" || (keychain.refreshExpiresAt ?? 0) > input2.now;
|
|
15397
15669
|
if (keychainUsable) {
|
|
15398
|
-
if (!file2
|
|
15399
|
-
|
|
15670
|
+
if (!file2 && !loggedOut && !input2.fileAuthoritative) {
|
|
15671
|
+
syncKeychainToFile = true;
|
|
15672
|
+
} else if (file2 && keychain.fingerprint !== file2.fingerprint) {
|
|
15400
15673
|
syncKeychainToFile = freshness(keychain) > freshness(file2);
|
|
15401
15674
|
}
|
|
15402
15675
|
}
|
|
@@ -16668,10 +16941,10 @@ async function mcpJsonAtRiskOfCommit(dir) {
|
|
|
16668
16941
|
try {
|
|
16669
16942
|
const { execFile: execFile5 } = await import("node:child_process");
|
|
16670
16943
|
const { promisify: promisify2 } = await import("node:util");
|
|
16671
|
-
const
|
|
16672
|
-
await
|
|
16944
|
+
const run4 = promisify2(execFile5);
|
|
16945
|
+
await run4("git", ["rev-parse", "--is-inside-work-tree"], { cwd: dir });
|
|
16673
16946
|
try {
|
|
16674
|
-
await
|
|
16947
|
+
await run4("git", ["check-ignore", "-q", ".mcp.json"], { cwd: dir });
|
|
16675
16948
|
return false;
|
|
16676
16949
|
} catch {
|
|
16677
16950
|
return true;
|
|
@@ -16763,7 +17036,7 @@ async function pathExists(path10) {
|
|
|
16763
17036
|
async function installCodexConfig(apiUrl, token, repoUrl) {
|
|
16764
17037
|
const { homedir: homedir11 } = await import("node:os");
|
|
16765
17038
|
const { join: join13 } = await import("node:path");
|
|
16766
|
-
const { chmod:
|
|
17039
|
+
const { chmod: chmod4, copyFile: copyFile3, mkdir: mkdir9, readFile: readFile9, writeFile: writeFile7 } = await import("node:fs/promises");
|
|
16767
17040
|
const codexDir = join13(homedir11(), ".codex");
|
|
16768
17041
|
const configPath = join13(codexDir, "config.toml");
|
|
16769
17042
|
const envPath = join13(codexDir, ".env");
|
|
@@ -16787,7 +17060,7 @@ async function installCodexConfig(apiUrl, token, repoUrl) {
|
|
|
16787
17060
|
upsertEnvValue(existingEnv, "GREAT_ARROW_TOKEN", token),
|
|
16788
17061
|
"utf8"
|
|
16789
17062
|
);
|
|
16790
|
-
await
|
|
17063
|
+
await chmod4(envPath, 384);
|
|
16791
17064
|
return { configPath, envPath };
|
|
16792
17065
|
}
|
|
16793
17066
|
var startCommand = new Command("start").description(
|
|
@@ -17191,9 +17464,9 @@ async function resolveProxyScript(override) {
|
|
|
17191
17464
|
async function runProxyDiagnostics(scriptPath) {
|
|
17192
17465
|
const { execFile: execFile5 } = await import("node:child_process");
|
|
17193
17466
|
const { promisify: promisify2 } = await import("node:util");
|
|
17194
|
-
const
|
|
17467
|
+
const run4 = promisify2(execFile5);
|
|
17195
17468
|
try {
|
|
17196
|
-
const { stdout } = await
|
|
17469
|
+
const { stdout } = await run4(
|
|
17197
17470
|
process.execPath,
|
|
17198
17471
|
[scriptPath, "--print-diagnostics"],
|
|
17199
17472
|
{ timeout: 1e4, maxBuffer: 1024 * 1024 }
|
|
@@ -17219,8 +17492,8 @@ async function findLegacyStores(cwd) {
|
|
|
17219
17492
|
try {
|
|
17220
17493
|
const { execFile: execFile5 } = await import("node:child_process");
|
|
17221
17494
|
const { promisify: promisify2 } = await import("node:util");
|
|
17222
|
-
const
|
|
17223
|
-
const { stdout } = await
|
|
17495
|
+
const run4 = promisify2(execFile5);
|
|
17496
|
+
const { stdout } = await run4("git", ["worktree", "list", "--porcelain"], {
|
|
17224
17497
|
cwd,
|
|
17225
17498
|
timeout: 5e3
|
|
17226
17499
|
});
|
|
@@ -17279,17 +17552,31 @@ async function probeEndpoint() {
|
|
|
17279
17552
|
async function applyMigration(plan) {
|
|
17280
17553
|
const { homedir: homedir11 } = await import("node:os");
|
|
17281
17554
|
const { join: join13 } = await import("node:path");
|
|
17282
|
-
const { mkdir: mkdir9, rename:
|
|
17555
|
+
const { chmod: chmod4, mkdir: mkdir9, rename: rename4, unlink: unlink3, writeFile: writeFile7 } = await import("node:fs/promises");
|
|
17283
17556
|
const applied = [];
|
|
17284
17557
|
const dir = join13(homedir11(), ".great-arrow");
|
|
17285
17558
|
await mkdir9(dir, { recursive: true, mode: 448 });
|
|
17559
|
+
await chmod4(dir, 448);
|
|
17560
|
+
const writeAtomic0600 = async (target, raw) => {
|
|
17561
|
+
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
17562
|
+
try {
|
|
17563
|
+
await writeFile7(tmp, raw, { mode: 384, flag: "wx" });
|
|
17564
|
+
await chmod4(tmp, 384);
|
|
17565
|
+
await rename4(tmp, target);
|
|
17566
|
+
} catch (err2) {
|
|
17567
|
+
try {
|
|
17568
|
+
await unlink3(tmp);
|
|
17569
|
+
} catch (cleanupErr) {
|
|
17570
|
+
debugSwallow("mcp.applyMigration(cleanup)", cleanupErr);
|
|
17571
|
+
}
|
|
17572
|
+
throw err2;
|
|
17573
|
+
}
|
|
17574
|
+
};
|
|
17286
17575
|
if (plan.seedFrom) {
|
|
17287
17576
|
const raw = await readIfExists(plan.seedFrom);
|
|
17288
17577
|
if (raw !== null) {
|
|
17289
17578
|
const target = join13(dir, OAUTH_CRED_FILENAME);
|
|
17290
|
-
|
|
17291
|
-
await writeFile7(tmp, raw, { mode: 384 });
|
|
17292
|
-
await rename3(tmp, target);
|
|
17579
|
+
await writeAtomic0600(target, raw);
|
|
17293
17580
|
applied.push(`Copied ${plan.seedFrom} \u2192 ${target}`);
|
|
17294
17581
|
}
|
|
17295
17582
|
}
|
|
@@ -17297,9 +17584,8 @@ async function applyMigration(plan) {
|
|
|
17297
17584
|
const raw = await readKeychainOnly();
|
|
17298
17585
|
if (raw) {
|
|
17299
17586
|
const target = join13(dir, "credential");
|
|
17300
|
-
|
|
17301
|
-
await
|
|
17302
|
-
await rename3(tmp, target);
|
|
17587
|
+
await writeAtomic0600(target, raw);
|
|
17588
|
+
await writeAtomic0600(join13(dir, "credential.migrated"), "1");
|
|
17303
17589
|
applied.push(`Wrote the keychain credential to ${target}`);
|
|
17304
17590
|
}
|
|
17305
17591
|
}
|
|
@@ -17329,10 +17615,14 @@ var doctorSubcommand = new Command("doctor").description(
|
|
|
17329
17615
|
const diagnostics = proxyPath ? await runProxyDiagnostics(proxyPath) : null;
|
|
17330
17616
|
const entries = await scanConnections();
|
|
17331
17617
|
const duplicates = detectDuplicates(entries);
|
|
17332
|
-
const keychain = await readKeychainOnly();
|
|
17333
17618
|
const credentialFile = await readIfExists(
|
|
17334
17619
|
join13(home, ".great-arrow", "credential")
|
|
17335
17620
|
);
|
|
17621
|
+
const credentialMigrated = await readIfExists(
|
|
17622
|
+
join13(home, ".great-arrow", "credential.migrated")
|
|
17623
|
+
) !== null;
|
|
17624
|
+
const fileAuthoritative = credentialFile?.trim() === "LOGGED_OUT" || credentialMigrated || process.platform === "darwin" && credentialFile !== null;
|
|
17625
|
+
const keychain = fileAuthoritative ? null : await readKeychainOnly();
|
|
17336
17626
|
const userStore = await readIfExists(
|
|
17337
17627
|
join13(home, ".great-arrow", OAUTH_CRED_FILENAME)
|
|
17338
17628
|
);
|
|
@@ -17342,6 +17632,7 @@ var doctorSubcommand = new Command("doctor").description(
|
|
|
17342
17632
|
...auditCredentialStores({
|
|
17343
17633
|
keychain,
|
|
17344
17634
|
file: credentialFile,
|
|
17635
|
+
fileAuthoritative,
|
|
17345
17636
|
now: Date.now()
|
|
17346
17637
|
}),
|
|
17347
17638
|
await probeEndpoint()
|
|
@@ -17367,6 +17658,7 @@ var doctorSubcommand = new Command("doctor").description(
|
|
|
17367
17658
|
userStore,
|
|
17368
17659
|
keychain,
|
|
17369
17660
|
file: credentialFile,
|
|
17661
|
+
fileAuthoritative,
|
|
17370
17662
|
now: Date.now()
|
|
17371
17663
|
});
|
|
17372
17664
|
const applied = options.migrate ? await applyMigration(plan) : [];
|
|
@@ -17749,6 +18041,137 @@ var agentsCommand = new Command("agents").description(
|
|
|
17749
18041
|
process.stdout.write(renderAgentsNow(result) + "\n");
|
|
17750
18042
|
});
|
|
17751
18043
|
|
|
18044
|
+
// src/commands/inbox.ts
|
|
18045
|
+
var BACKOFF_BASE_MS = 15e3;
|
|
18046
|
+
var BACKOFF_MAX_MS = 12e4;
|
|
18047
|
+
var DEFAULT_MAX_MINUTES = 50;
|
|
18048
|
+
var PRESENCE_NOT_ENABLED = "Agent presence is not enabled.";
|
|
18049
|
+
function waitBackoffMs(emptyHolds) {
|
|
18050
|
+
if (emptyHolds < 3) return 0;
|
|
18051
|
+
return Math.min(2 ** (emptyHolds - 3) * BACKOFF_BASE_MS, BACKOFF_MAX_MS);
|
|
18052
|
+
}
|
|
18053
|
+
function asMessage(body) {
|
|
18054
|
+
if (!body || typeof body !== "object") return null;
|
|
18055
|
+
const b = body;
|
|
18056
|
+
return b.status === "message" && typeof b.messageId === "string" && typeof b.block === "string" ? { messageId: b.messageId, block: b.block } : null;
|
|
18057
|
+
}
|
|
18058
|
+
async function runInboxWait(o) {
|
|
18059
|
+
const sleep2 = o.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
18060
|
+
const now = o.now ?? Date.now;
|
|
18061
|
+
const write = o.write ?? ((t) => void process.stdout.write(t));
|
|
18062
|
+
const writeErr = o.writeErr ?? ((t) => void process.stderr.write(t));
|
|
18063
|
+
const stopAt = now() + (o.maxMinutes ?? DEFAULT_MAX_MINUTES) * 6e4;
|
|
18064
|
+
let emptyHolds = 0;
|
|
18065
|
+
while (now() < stopAt) {
|
|
18066
|
+
const params = { as: o.as };
|
|
18067
|
+
if (o.after) params.after = o.after;
|
|
18068
|
+
if (o.waitingOn !== void 0) params.waiting_on = o.waitingOn;
|
|
18069
|
+
let res = null;
|
|
18070
|
+
try {
|
|
18071
|
+
res = await o.request(params);
|
|
18072
|
+
} catch {
|
|
18073
|
+
res = null;
|
|
18074
|
+
}
|
|
18075
|
+
if (res?.status === 404) {
|
|
18076
|
+
writeErr(`${PRESENCE_NOT_ENABLED}
|
|
18077
|
+
`);
|
|
18078
|
+
return 2;
|
|
18079
|
+
}
|
|
18080
|
+
const msg = res && res.status === 200 ? asMessage(res.body) : null;
|
|
18081
|
+
if (msg) {
|
|
18082
|
+
const block = msg.block.endsWith("\n") ? msg.block : `${msg.block}
|
|
18083
|
+
`;
|
|
18084
|
+
write(`${block}after=${msg.messageId}
|
|
18085
|
+
`);
|
|
18086
|
+
return 0;
|
|
18087
|
+
}
|
|
18088
|
+
const gap = Math.min(waitBackoffMs(emptyHolds++), stopAt - now());
|
|
18089
|
+
if (gap > 0) await sleep2(gap);
|
|
18090
|
+
}
|
|
18091
|
+
return 3;
|
|
18092
|
+
}
|
|
18093
|
+
function parseMaxMinutes(raw) {
|
|
18094
|
+
const n = Number(raw);
|
|
18095
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_MAX_MINUTES;
|
|
18096
|
+
}
|
|
18097
|
+
var waitCommand = new Command("wait").description(
|
|
18098
|
+
"Wait for a peer's message to this connection; run as a background task"
|
|
18099
|
+
).requiredOption("--as <id>", "Your connection id (from agent_inbox)").option("--after <id>", "Only wake for messages newer than this id").option("--waiting-on <text>", "What you are waiting on, shown to peers").option("--max-minutes <n>", "Give up after this many minutes", "50").action(
|
|
18100
|
+
async (opts) => {
|
|
18101
|
+
const auth = await resolveAuth();
|
|
18102
|
+
const client2 = new GadClient({
|
|
18103
|
+
token: auth.token,
|
|
18104
|
+
getToken: auth.getToken,
|
|
18105
|
+
apiUrl: auth.apiUrl
|
|
18106
|
+
});
|
|
18107
|
+
process.exitCode = await runInboxWait({
|
|
18108
|
+
request: (params) => client2.requestOnce("GET", "/api/agent-comms/wait", { params }),
|
|
18109
|
+
as: opts.as,
|
|
18110
|
+
after: opts.after,
|
|
18111
|
+
waitingOn: opts.waitingOn,
|
|
18112
|
+
maxMinutes: parseMaxMinutes(opts.maxMinutes)
|
|
18113
|
+
});
|
|
18114
|
+
}
|
|
18115
|
+
);
|
|
18116
|
+
var inboxCommand = new Command("inbox").description("Agent inbox \u2014 wait for a peer's message").addCommand(waitCommand);
|
|
18117
|
+
|
|
18118
|
+
// src/commands/presence.ts
|
|
18119
|
+
function doNotDisturbBody(raw, as) {
|
|
18120
|
+
if (raw.trim().toLowerCase() === "off") {
|
|
18121
|
+
return { action: "do_not_disturb", as, minutes: null };
|
|
18122
|
+
}
|
|
18123
|
+
const minutes = Number(raw);
|
|
18124
|
+
if (!Number.isFinite(minutes) || minutes <= 0) {
|
|
18125
|
+
throw new CliError(
|
|
18126
|
+
`Expected a number of minutes or "off", got "${raw}".`,
|
|
18127
|
+
3 /* VALIDATION_ERROR */
|
|
18128
|
+
);
|
|
18129
|
+
}
|
|
18130
|
+
return { action: "do_not_disturb", as, minutes };
|
|
18131
|
+
}
|
|
18132
|
+
async function sendPresence(o) {
|
|
18133
|
+
const write = o.write ?? ((t) => void process.stdout.write(t));
|
|
18134
|
+
const writeErr = o.writeErr ?? ((t) => void process.stderr.write(t));
|
|
18135
|
+
const res = await o.request(o.body);
|
|
18136
|
+
if (res.status === 404) {
|
|
18137
|
+
writeErr(`${PRESENCE_NOT_ENABLED}
|
|
18138
|
+
`);
|
|
18139
|
+
return 2;
|
|
18140
|
+
}
|
|
18141
|
+
if (res.status < 200 || res.status >= 300) {
|
|
18142
|
+
writeErr(`Presence update failed (HTTP ${res.status}).
|
|
18143
|
+
`);
|
|
18144
|
+
return res.status === 401 || res.status === 403 ? 1 /* AUTH_ERROR */ : res.status === 400 ? 3 /* VALIDATION_ERROR */ : 2 /* NETWORK_ERROR */;
|
|
18145
|
+
}
|
|
18146
|
+
const b = o.body;
|
|
18147
|
+
write(
|
|
18148
|
+
b.action === "sign_off" ? "Signed off.\n" : b.minutes === null ? "Do not disturb is off.\n" : `Do not disturb for ${b.minutes} minutes.
|
|
18149
|
+
`
|
|
18150
|
+
);
|
|
18151
|
+
return 0;
|
|
18152
|
+
}
|
|
18153
|
+
async function run2(body) {
|
|
18154
|
+
const auth = await resolveAuth();
|
|
18155
|
+
const client2 = new GadClient({
|
|
18156
|
+
token: auth.token,
|
|
18157
|
+
getToken: auth.getToken,
|
|
18158
|
+
apiUrl: auth.apiUrl
|
|
18159
|
+
});
|
|
18160
|
+
process.exitCode = await sendPresence({
|
|
18161
|
+
request: (b) => client2.requestOnce("POST", "/api/agent-comms/presence", { body: b }),
|
|
18162
|
+
body
|
|
18163
|
+
});
|
|
18164
|
+
}
|
|
18165
|
+
var presenceCommand = new Command("presence").description("Tell peers you are away: do not disturb, or sign off").addCommand(
|
|
18166
|
+
new Command("do-not-disturb").description("Tell peers not to disturb you for a while, or turn it off").argument("<minutes|off>", "Minutes (up to 480), or off").requiredOption("--as <id>", "Your connection id (from agent_inbox)").action(async (raw, opts) => {
|
|
18167
|
+
await run2(doNotDisturbBody(raw, opts.as));
|
|
18168
|
+
})
|
|
18169
|
+
).addCommand(
|
|
18170
|
+
new Command("sign-off").description("Mark this connection as signed off").requiredOption("--as <id>", "Your connection id (from agent_inbox)").action(async (opts) => {
|
|
18171
|
+
await run2({ action: "sign_off", as: opts.as });
|
|
18172
|
+
})
|
|
18173
|
+
);
|
|
18174
|
+
|
|
17752
18175
|
// src/commands/ask.ts
|
|
17753
18176
|
var ORCHESTRATOR_TIMEOUT_MS = 9e4;
|
|
17754
18177
|
var ANSWER_FIELDS = [
|
|
@@ -38970,7 +39393,7 @@ function visit(schema, fnOrHandlers) {
|
|
|
38970
39393
|
return h ? h(node2, rewritten) : node2;
|
|
38971
39394
|
};
|
|
38972
39395
|
const cache = /* @__PURE__ */ new Map();
|
|
38973
|
-
function
|
|
39396
|
+
function run4(s2) {
|
|
38974
39397
|
const cached2 = cache.get(s2);
|
|
38975
39398
|
if (cached2 === RESOLVING) {
|
|
38976
39399
|
return new $ZodLazy({
|
|
@@ -38996,21 +39419,21 @@ function visit(schema, fnOrHandlers) {
|
|
|
38996
39419
|
let changed = false;
|
|
38997
39420
|
const newShape = {};
|
|
38998
39421
|
for (const k of keys) {
|
|
38999
|
-
const mapped =
|
|
39422
|
+
const mapped = run4(oldShape[k]);
|
|
39000
39423
|
if (mapped !== oldShape[k])
|
|
39001
39424
|
changed = true;
|
|
39002
39425
|
newShape[k] = mapped;
|
|
39003
39426
|
}
|
|
39004
39427
|
let newCatchall = def.catchall;
|
|
39005
39428
|
if (def.catchall) {
|
|
39006
|
-
newCatchall =
|
|
39429
|
+
newCatchall = run4(def.catchall);
|
|
39007
39430
|
if (newCatchall !== def.catchall)
|
|
39008
39431
|
changed = true;
|
|
39009
39432
|
}
|
|
39010
39433
|
return changed ? clone(s2, { ...def, shape: newShape, catchall: newCatchall }) : s2;
|
|
39011
39434
|
}
|
|
39012
39435
|
case "array": {
|
|
39013
|
-
const mapped =
|
|
39436
|
+
const mapped = run4(def.element);
|
|
39014
39437
|
return mapped === def.element ? s2 : clone(s2, { ...def, element: mapped });
|
|
39015
39438
|
}
|
|
39016
39439
|
case "tuple": {
|
|
@@ -39018,14 +39441,14 @@ function visit(schema, fnOrHandlers) {
|
|
|
39018
39441
|
let changed = false;
|
|
39019
39442
|
const newItems = [];
|
|
39020
39443
|
for (const item of oldItems) {
|
|
39021
|
-
const mapped =
|
|
39444
|
+
const mapped = run4(item);
|
|
39022
39445
|
if (mapped !== item)
|
|
39023
39446
|
changed = true;
|
|
39024
39447
|
newItems.push(mapped);
|
|
39025
39448
|
}
|
|
39026
39449
|
let newRest = def.rest;
|
|
39027
39450
|
if (def.rest) {
|
|
39028
|
-
newRest =
|
|
39451
|
+
newRest = run4(def.rest);
|
|
39029
39452
|
if (newRest !== def.rest)
|
|
39030
39453
|
changed = true;
|
|
39031
39454
|
}
|
|
@@ -39033,12 +39456,12 @@ function visit(schema, fnOrHandlers) {
|
|
|
39033
39456
|
}
|
|
39034
39457
|
case "record":
|
|
39035
39458
|
case "map": {
|
|
39036
|
-
const newKey =
|
|
39037
|
-
const newVal =
|
|
39459
|
+
const newKey = run4(def.keyType);
|
|
39460
|
+
const newVal = run4(def.valueType);
|
|
39038
39461
|
return newKey === def.keyType && newVal === def.valueType ? s2 : clone(s2, { ...def, keyType: newKey, valueType: newVal });
|
|
39039
39462
|
}
|
|
39040
39463
|
case "set": {
|
|
39041
|
-
const newVal =
|
|
39464
|
+
const newVal = run4(def.valueType);
|
|
39042
39465
|
return newVal === def.valueType ? s2 : clone(s2, { ...def, valueType: newVal });
|
|
39043
39466
|
}
|
|
39044
39467
|
case "union": {
|
|
@@ -39046,7 +39469,7 @@ function visit(schema, fnOrHandlers) {
|
|
|
39046
39469
|
let changed = false;
|
|
39047
39470
|
const newOptions = [];
|
|
39048
39471
|
for (const opt of oldOptions) {
|
|
39049
|
-
const mapped =
|
|
39472
|
+
const mapped = run4(opt);
|
|
39050
39473
|
if (mapped !== opt)
|
|
39051
39474
|
changed = true;
|
|
39052
39475
|
newOptions.push(mapped);
|
|
@@ -39054,8 +39477,8 @@ function visit(schema, fnOrHandlers) {
|
|
|
39054
39477
|
return changed ? clone(s2, { ...def, options: newOptions }) : s2;
|
|
39055
39478
|
}
|
|
39056
39479
|
case "intersection": {
|
|
39057
|
-
const newLeft =
|
|
39058
|
-
const newRight =
|
|
39480
|
+
const newLeft = run4(def.left);
|
|
39481
|
+
const newRight = run4(def.right);
|
|
39059
39482
|
return newLeft === def.left && newRight === def.right ? s2 : clone(s2, { ...def, left: newLeft, right: newRight });
|
|
39060
39483
|
}
|
|
39061
39484
|
case "optional":
|
|
@@ -39067,23 +39490,23 @@ function visit(schema, fnOrHandlers) {
|
|
|
39067
39490
|
case "nonoptional":
|
|
39068
39491
|
case "promise":
|
|
39069
39492
|
case "success": {
|
|
39070
|
-
const newInner =
|
|
39493
|
+
const newInner = run4(def.innerType);
|
|
39071
39494
|
return newInner === def.innerType ? s2 : clone(s2, { ...def, innerType: newInner });
|
|
39072
39495
|
}
|
|
39073
39496
|
case "pipe": {
|
|
39074
|
-
const newIn =
|
|
39075
|
-
const newOut =
|
|
39497
|
+
const newIn = run4(def.in);
|
|
39498
|
+
const newOut = run4(def.out);
|
|
39076
39499
|
return newIn === def.in && newOut === def.out ? s2 : clone(s2, { ...def, in: newIn, out: newOut });
|
|
39077
39500
|
}
|
|
39078
39501
|
case "function": {
|
|
39079
|
-
const newInput =
|
|
39080
|
-
const newOutput =
|
|
39502
|
+
const newInput = run4(def.input);
|
|
39503
|
+
const newOutput = run4(def.output);
|
|
39081
39504
|
return newInput === def.input && newOutput === def.output ? s2 : clone(s2, { ...def, input: newInput, output: newOutput });
|
|
39082
39505
|
}
|
|
39083
39506
|
case "lazy": {
|
|
39084
39507
|
const original = def.getter;
|
|
39085
39508
|
const { _cachedInner, ...rest } = def;
|
|
39086
|
-
return clone(s2, { ...rest, getter: () =>
|
|
39509
|
+
return clone(s2, { ...rest, getter: () => run4(original()) });
|
|
39087
39510
|
}
|
|
39088
39511
|
// A leaf by choice: `parts` are regex fragments, not data positions.
|
|
39089
39512
|
case "template_literal":
|
|
@@ -39114,7 +39537,7 @@ function visit(schema, fnOrHandlers) {
|
|
|
39114
39537
|
}
|
|
39115
39538
|
}
|
|
39116
39539
|
}
|
|
39117
|
-
return
|
|
39540
|
+
return run4(schema);
|
|
39118
39541
|
}
|
|
39119
39542
|
|
|
39120
39543
|
// ../../node_modules/zod/v4/classic/deep-partial.js
|
|
@@ -49743,11 +50166,11 @@ import { Writable } from "node:stream";
|
|
|
49743
50166
|
// src/lib/keys-apply.ts
|
|
49744
50167
|
import { spawn } from "node:child_process";
|
|
49745
50168
|
import {
|
|
49746
|
-
chmod as
|
|
50169
|
+
chmod as chmod3,
|
|
49747
50170
|
mkdir as mkdir8,
|
|
49748
50171
|
readFile as readFile7,
|
|
49749
50172
|
realpath,
|
|
49750
|
-
rename as
|
|
50173
|
+
rename as rename3,
|
|
49751
50174
|
stat as stat2,
|
|
49752
50175
|
writeFile as writeFile6
|
|
49753
50176
|
} from "node:fs/promises";
|
|
@@ -49853,8 +50276,8 @@ function formatEnvValue(value, quote) {
|
|
|
49853
50276
|
async function writeAtomic(path10, content, mode) {
|
|
49854
50277
|
const tmp = `${path10}.tmp`;
|
|
49855
50278
|
await writeFile6(tmp, content, { mode });
|
|
49856
|
-
await
|
|
49857
|
-
await
|
|
50279
|
+
await chmod3(tmp, mode);
|
|
50280
|
+
await rename3(tmp, path10);
|
|
49858
50281
|
}
|
|
49859
50282
|
async function applyEnvFile(path10, key, value) {
|
|
49860
50283
|
const file2 = expandHome(path10);
|
|
@@ -50304,7 +50727,7 @@ function helperKind(raw) {
|
|
|
50304
50727
|
if (/^store\b/.test(v)) return "store";
|
|
50305
50728
|
return "other";
|
|
50306
50729
|
}
|
|
50307
|
-
function
|
|
50730
|
+
function run3(file2, args) {
|
|
50308
50731
|
return new Promise((done) => {
|
|
50309
50732
|
execFile4(
|
|
50310
50733
|
file2,
|
|
@@ -50315,10 +50738,10 @@ function run2(file2, args) {
|
|
|
50315
50738
|
});
|
|
50316
50739
|
}
|
|
50317
50740
|
async function detectGhCli() {
|
|
50318
|
-
return await
|
|
50741
|
+
return await run3("gh", ["--version"]) !== null;
|
|
50319
50742
|
}
|
|
50320
50743
|
async function detectGitHelper() {
|
|
50321
|
-
const out2 = await
|
|
50744
|
+
const out2 = await run3("git", [
|
|
50322
50745
|
"config",
|
|
50323
50746
|
"--get-urlmatch",
|
|
50324
50747
|
"credential.helper",
|
|
@@ -50547,29 +50970,29 @@ function statusCommand4() {
|
|
|
50547
50970
|
const runId = requireRunId(runArg);
|
|
50548
50971
|
const { client: client2, apiUrl } = await keysContext();
|
|
50549
50972
|
const res = await client2.get(`/api/key-rotation/runs/${runId}`);
|
|
50550
|
-
const
|
|
50973
|
+
const run4 = res?.run ?? {};
|
|
50551
50974
|
if (g.json) {
|
|
50552
|
-
out(JSON.stringify(
|
|
50975
|
+
out(JSON.stringify(run4));
|
|
50553
50976
|
return;
|
|
50554
50977
|
}
|
|
50555
50978
|
const c = createColor(g.noColor || g.quiet);
|
|
50556
50979
|
out(
|
|
50557
|
-
`${c.bold(`Run ${runId}`)} ${s(
|
|
50980
|
+
`${c.bold(`Run ${runId}`)} ${s(run4.state)}` + (s(run4.waitingOn) ? c.dim(` waiting on ${s(run4.waitingOn)}`) : "")
|
|
50558
50981
|
);
|
|
50559
|
-
for (const st of
|
|
50982
|
+
for (const st of run4.steps ?? []) {
|
|
50560
50983
|
const detail = s(st.detailCode);
|
|
50561
50984
|
out(
|
|
50562
50985
|
` ${s(st.step)} ${c.dim(s(st.targetKind))} ${s(st.status)}` + (detail ? c.dim(` (${detail})`) : "")
|
|
50563
50986
|
);
|
|
50564
50987
|
}
|
|
50565
|
-
const open4 = (
|
|
50988
|
+
const open4 = (run4.checklist ?? []).filter((i) => i.ticked !== true);
|
|
50566
50989
|
if (open4.length > 0) {
|
|
50567
50990
|
out(c.yellow("Checklist (tick these in the browser):"));
|
|
50568
50991
|
for (const item of open4) {
|
|
50569
50992
|
out(` \u2610 ${s(item.label)} ${c.cyan(s(item.href))}`);
|
|
50570
50993
|
}
|
|
50571
50994
|
}
|
|
50572
|
-
for (const d of
|
|
50995
|
+
for (const d of run4.devices ?? []) {
|
|
50573
50996
|
out(` device ${s(d.label)} ${s(d.state)}`);
|
|
50574
50997
|
}
|
|
50575
50998
|
out(c.dim(`${apiUrl}${keyRotationRunPath(runId)}`));
|
|
@@ -50910,6 +51333,8 @@ var COMMANDS = [
|
|
|
50910
51333
|
"smoke",
|
|
50911
51334
|
"agent",
|
|
50912
51335
|
"ask",
|
|
51336
|
+
"inbox",
|
|
51337
|
+
"presence",
|
|
50913
51338
|
"runtime",
|
|
50914
51339
|
"open",
|
|
50915
51340
|
"completions"
|
|
@@ -50951,6 +51376,12 @@ _gad_completions() {
|
|
|
50951
51376
|
agent)
|
|
50952
51377
|
COMPREPLY=( $(compgen -W "list invoke" -- "\${cur}") )
|
|
50953
51378
|
;;
|
|
51379
|
+
inbox)
|
|
51380
|
+
COMPREPLY=( $(compgen -W "wait" -- "\${cur}") )
|
|
51381
|
+
;;
|
|
51382
|
+
presence)
|
|
51383
|
+
COMPREPLY=( $(compgen -W "do-not-disturb sign-off" -- "\${cur}") )
|
|
51384
|
+
;;
|
|
50954
51385
|
runtime)
|
|
50955
51386
|
COMPREPLY=( $(compgen -W "status set sync policy" -- "\${cur}") )
|
|
50956
51387
|
;;
|
|
@@ -50986,6 +51417,8 @@ _gad() {
|
|
|
50986
51417
|
'smoke:Run smoke tests'
|
|
50987
51418
|
'agent:Manage agents (list, invoke)'
|
|
50988
51419
|
'ask:Ask the orchestrator a question'
|
|
51420
|
+
'inbox:Agent inbox (wait)'
|
|
51421
|
+
'presence:Agent presence (do-not-disturb, sign-off)'
|
|
50989
51422
|
'runtime:Choose your computer or the cloud per lane'
|
|
50990
51423
|
'open:Open an app surface in the browser'
|
|
50991
51424
|
'completions:Generate shell completions'
|
|
@@ -51021,6 +51454,14 @@ _gad() {
|
|
|
51021
51454
|
subcommands=('list:List agents' 'invoke:Invoke an agent')
|
|
51022
51455
|
_describe -t subcommands 'agent subcommands' subcommands
|
|
51023
51456
|
;;
|
|
51457
|
+
inbox)
|
|
51458
|
+
subcommands=('wait:Wait for a peer message')
|
|
51459
|
+
_describe -t subcommands 'inbox subcommands' subcommands
|
|
51460
|
+
;;
|
|
51461
|
+
presence)
|
|
51462
|
+
subcommands=('do-not-disturb:Do not disturb for a while, or off' 'sign-off:Sign off this connection')
|
|
51463
|
+
_describe -t subcommands 'presence subcommands' subcommands
|
|
51464
|
+
;;
|
|
51024
51465
|
runtime)
|
|
51025
51466
|
subcommands=('status:Where each lane runs' 'set:Set a lane' 'sync:Apply to this checkout' 'policy:Workspace policy')
|
|
51026
51467
|
_describe -t subcommands 'runtime subcommands' subcommands
|
|
@@ -51049,6 +51490,8 @@ function generateFish() {
|
|
|
51049
51490
|
smoke: "Run smoke tests",
|
|
51050
51491
|
agent: "Manage agents",
|
|
51051
51492
|
ask: "Ask the orchestrator a question",
|
|
51493
|
+
inbox: "Agent inbox",
|
|
51494
|
+
presence: "Agent presence",
|
|
51052
51495
|
runtime: "Choose your computer or the cloud per lane",
|
|
51053
51496
|
open: "Open an app surface in the browser",
|
|
51054
51497
|
completions: "Generate shell completions"
|
|
@@ -51087,6 +51530,13 @@ function generateFish() {
|
|
|
51087
51530
|
list: "List agents",
|
|
51088
51531
|
invoke: "Invoke an agent"
|
|
51089
51532
|
},
|
|
51533
|
+
inbox: {
|
|
51534
|
+
wait: "Wait for a peer message"
|
|
51535
|
+
},
|
|
51536
|
+
presence: {
|
|
51537
|
+
"do-not-disturb": "Do not disturb for a while, or off",
|
|
51538
|
+
"sign-off": "Sign off this connection"
|
|
51539
|
+
},
|
|
51090
51540
|
runtime: {
|
|
51091
51541
|
status: "Where each lane runs",
|
|
51092
51542
|
set: "Set a lane",
|
|
@@ -54566,6 +55016,8 @@ program2.addCommand(integrations_default);
|
|
|
54566
55016
|
program2.addCommand(mcp_default);
|
|
54567
55017
|
program2.addCommand(agentCommand);
|
|
54568
55018
|
program2.addCommand(agentsCommand);
|
|
55019
|
+
program2.addCommand(inboxCommand);
|
|
55020
|
+
program2.addCommand(presenceCommand);
|
|
54569
55021
|
program2.addCommand(askCommand());
|
|
54570
55022
|
program2.addCommand(localCommand());
|
|
54571
55023
|
program2.addCommand(runtimeCommand());
|