@great-arrow/cli 0.1.53 → 0.1.54
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 +290 -63
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9402,7 +9402,7 @@ function isNodeError(value) {
|
|
|
9402
9402
|
|
|
9403
9403
|
// src/lib/version.ts
|
|
9404
9404
|
var CLI_PACKAGE_NAME = "@great-arrow/cli";
|
|
9405
|
-
var CLI_VERSION = "0.1.
|
|
9405
|
+
var CLI_VERSION = "0.1.54";
|
|
9406
9406
|
var GAD_INSTALL_VERSION = "2026.08.08";
|
|
9407
9407
|
var GAD_INSTALL_MIN_VERSION = "2026.06.22";
|
|
9408
9408
|
var GAD_INSTALL_HEADER = "X-GAD-Install-Version";
|
|
@@ -9411,6 +9411,9 @@ var GAD_CLI_VERSION_HEADER = "X-GAD-CLI-Version";
|
|
|
9411
9411
|
// src/commands/login.ts
|
|
9412
9412
|
import { execFile } from "node:child_process";
|
|
9413
9413
|
|
|
9414
|
+
// src/lib/credentials.ts
|
|
9415
|
+
import { randomUUID } from "node:crypto";
|
|
9416
|
+
|
|
9414
9417
|
// src/lib/keychain.ts
|
|
9415
9418
|
import { mkdir, writeFile, readFile, unlink } from "node:fs/promises";
|
|
9416
9419
|
import { homedir } from "node:os";
|
|
@@ -9532,7 +9535,12 @@ async function loadStoredCredential() {
|
|
|
9532
9535
|
return parseStoredValue(await getToken());
|
|
9533
9536
|
}
|
|
9534
9537
|
async function saveOAuthCredential(credential) {
|
|
9535
|
-
return setToken(
|
|
9538
|
+
return setToken(
|
|
9539
|
+
JSON.stringify({
|
|
9540
|
+
...credential,
|
|
9541
|
+
localSessionId: credential.localSessionId || randomUUID()
|
|
9542
|
+
})
|
|
9543
|
+
);
|
|
9536
9544
|
}
|
|
9537
9545
|
async function savePatToken(token) {
|
|
9538
9546
|
return setToken(token);
|
|
@@ -9903,14 +9911,33 @@ function toCredential(env4, apiUrl, clientId, now) {
|
|
|
9903
9911
|
}
|
|
9904
9912
|
var TOKEN_POST_TIMEOUT_MS = 8e3;
|
|
9905
9913
|
async function postToken(apiUrl, form, fetchImpl, timeoutMs = TOKEN_POST_TIMEOUT_MS) {
|
|
9906
|
-
const
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9914
|
+
const controller = new AbortController();
|
|
9915
|
+
let timer;
|
|
9916
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
9917
|
+
timer = setTimeout(() => {
|
|
9918
|
+
const error = new DOMException(
|
|
9919
|
+
"Token exchange timed out.",
|
|
9920
|
+
"TimeoutError"
|
|
9921
|
+
);
|
|
9922
|
+
controller.abort(error);
|
|
9923
|
+
reject(error);
|
|
9924
|
+
}, timeoutMs);
|
|
9911
9925
|
});
|
|
9912
|
-
const
|
|
9913
|
-
|
|
9926
|
+
const request = async () => {
|
|
9927
|
+
const res = await fetchImpl(`${trimSlash2(apiUrl)}/api/oauth/token`, {
|
|
9928
|
+
method: "POST",
|
|
9929
|
+
headers: TOKEN_HEADERS,
|
|
9930
|
+
body: new URLSearchParams(form).toString(),
|
|
9931
|
+
signal: controller.signal
|
|
9932
|
+
});
|
|
9933
|
+
const json = await res.json().catch(() => ({}));
|
|
9934
|
+
return { ok: res.ok, status: res.status, json };
|
|
9935
|
+
};
|
|
9936
|
+
try {
|
|
9937
|
+
return await Promise.race([request(), deadline]);
|
|
9938
|
+
} finally {
|
|
9939
|
+
if (timer) clearTimeout(timer);
|
|
9940
|
+
}
|
|
9914
9941
|
}
|
|
9915
9942
|
async function exchangeCode(opts) {
|
|
9916
9943
|
const now = opts.now ?? Date.now();
|
|
@@ -10364,6 +10391,20 @@ var login_default = loginCommand;
|
|
|
10364
10391
|
import { mkdir as mkdir3, open, rm, stat } from "node:fs/promises";
|
|
10365
10392
|
import { homedir as homedir4 } from "node:os";
|
|
10366
10393
|
import { join as join4 } from "node:path";
|
|
10394
|
+
|
|
10395
|
+
// src/lib/oauth/session-identity.ts
|
|
10396
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
10397
|
+
function credentialSessionId(credential) {
|
|
10398
|
+
return credential.localSessionId || createHash2("sha256").update(
|
|
10399
|
+
JSON.stringify([
|
|
10400
|
+
credential.clientId,
|
|
10401
|
+
credential.apiUrl,
|
|
10402
|
+
credential.refreshToken
|
|
10403
|
+
])
|
|
10404
|
+
).digest("hex");
|
|
10405
|
+
}
|
|
10406
|
+
|
|
10407
|
+
// src/lib/auth.ts
|
|
10367
10408
|
var REFRESH_SKEW_MS = 12e4;
|
|
10368
10409
|
var inflightRefresh = null;
|
|
10369
10410
|
var REFRESH_LOCK_STALE_MS = 15e3;
|
|
@@ -10387,7 +10428,12 @@ async function acquireRefreshLock() {
|
|
|
10387
10428
|
await handle.close();
|
|
10388
10429
|
return true;
|
|
10389
10430
|
} catch (err) {
|
|
10390
|
-
if (err.code !== "EEXIST")
|
|
10431
|
+
if (err.code !== "EEXIST") {
|
|
10432
|
+
throw networkError(
|
|
10433
|
+
"Could not lock session renewal.",
|
|
10434
|
+
"Check local credential storage and retry; your session has been kept."
|
|
10435
|
+
);
|
|
10436
|
+
}
|
|
10391
10437
|
}
|
|
10392
10438
|
const age = await stat(path10).then((st) => Date.now() - st.mtimeMs).catch(() => Number.POSITIVE_INFINITY);
|
|
10393
10439
|
if (age > REFRESH_LOCK_STALE_MS) {
|
|
@@ -10416,8 +10462,13 @@ async function storedPairOtherThan(ours) {
|
|
|
10416
10462
|
} catch (err) {
|
|
10417
10463
|
debugSwallow("refresh.reread", err);
|
|
10418
10464
|
}
|
|
10419
|
-
if (stored?.kind !== "oauth")
|
|
10420
|
-
|
|
10465
|
+
if (stored?.kind !== "oauth") {
|
|
10466
|
+
throw authError(
|
|
10467
|
+
"The original session changed or ended during renewal.",
|
|
10468
|
+
"Restart this command with your current connection."
|
|
10469
|
+
);
|
|
10470
|
+
}
|
|
10471
|
+
if (ours.localSessionId && credentialSessionId(stored.credential) !== credentialSessionId(ours) || stored.credential.clientId !== ours.clientId || stored.credential.apiUrl.replace(/\/$/, "") !== ours.apiUrl.replace(/\/$/, "")) {
|
|
10421
10472
|
throw networkError(
|
|
10422
10473
|
"The stored session changed while renewing it.",
|
|
10423
10474
|
"Retry the command with the current session."
|
|
@@ -10443,11 +10494,32 @@ async function resolveAuth() {
|
|
|
10443
10494
|
if (stored?.kind === "oauth") {
|
|
10444
10495
|
const fresh = await ensureFreshAccessToken(stored.credential);
|
|
10445
10496
|
warnLegacyApiUrl(fresh.apiUrl);
|
|
10497
|
+
const originalSessionId = credentialSessionId(fresh);
|
|
10498
|
+
const originalApiUrl = fresh.apiUrl;
|
|
10499
|
+
const originalClientId = fresh.clientId;
|
|
10446
10500
|
return withWorkspace({
|
|
10447
10501
|
token: fresh.accessToken,
|
|
10448
10502
|
apiUrl: fresh.apiUrl,
|
|
10449
10503
|
tokenType: "oauth",
|
|
10450
|
-
expiresAt: fresh.accessExpiresAt
|
|
10504
|
+
expiresAt: fresh.accessExpiresAt,
|
|
10505
|
+
getToken: async () => {
|
|
10506
|
+
const latestConfig = await loadConfig();
|
|
10507
|
+
const latest = await loadStoredCredential();
|
|
10508
|
+
if ((latestConfig.api_url || DEFAULT_API_URL) !== configApiUrl || (latestConfig.default_workspace || void 0) !== activeWorkspace || process.env.GAD_TOKEN || latest?.kind !== "oauth" || credentialSessionId(latest.credential) !== originalSessionId || latest.credential.apiUrl !== originalApiUrl || latest.credential.clientId !== originalClientId) {
|
|
10509
|
+
throw authError(
|
|
10510
|
+
"The original session or workspace changed or ended.",
|
|
10511
|
+
"Restart this command with your current connection."
|
|
10512
|
+
);
|
|
10513
|
+
}
|
|
10514
|
+
const renewed = await ensureFreshAccessToken(latest.credential);
|
|
10515
|
+
if (credentialSessionId(renewed) !== originalSessionId) {
|
|
10516
|
+
throw authError(
|
|
10517
|
+
"The original session changed.",
|
|
10518
|
+
"Restart this command with your current connection."
|
|
10519
|
+
);
|
|
10520
|
+
}
|
|
10521
|
+
return renewed.accessToken;
|
|
10522
|
+
}
|
|
10451
10523
|
});
|
|
10452
10524
|
}
|
|
10453
10525
|
if (stored?.kind === "pat") {
|
|
@@ -10502,10 +10574,6 @@ async function ensureFreshAccessToken(credential) {
|
|
|
10502
10574
|
if (Date.now() < credential.accessExpiresAt - REFRESH_SKEW_MS) {
|
|
10503
10575
|
return credential;
|
|
10504
10576
|
}
|
|
10505
|
-
if (Date.now() >= credential.refreshExpiresAt) {
|
|
10506
|
-
await clearStoredCredential();
|
|
10507
|
-
throw sessionExpired();
|
|
10508
|
-
}
|
|
10509
10577
|
inflightRefresh ??= doRefresh(credential);
|
|
10510
10578
|
try {
|
|
10511
10579
|
return await inflightRefresh;
|
|
@@ -10529,12 +10597,32 @@ async function doRefresh(credential) {
|
|
|
10529
10597
|
"Retry shortly; your saved session has been kept."
|
|
10530
10598
|
);
|
|
10531
10599
|
}
|
|
10532
|
-
|
|
10600
|
+
if (Date.now() >= current.refreshExpiresAt) {
|
|
10601
|
+
await clearStoredCredential();
|
|
10602
|
+
throw sessionExpired();
|
|
10603
|
+
}
|
|
10604
|
+
const rotated = await refreshTokens({
|
|
10533
10605
|
apiUrl: current.apiUrl,
|
|
10534
10606
|
clientId: current.clientId,
|
|
10535
10607
|
refreshToken: current.refreshToken
|
|
10536
10608
|
});
|
|
10537
|
-
|
|
10609
|
+
const refreshed = {
|
|
10610
|
+
...rotated,
|
|
10611
|
+
localSessionId: credentialSessionId(current)
|
|
10612
|
+
};
|
|
10613
|
+
const saved = await loadStoredCredential();
|
|
10614
|
+
if (saved?.kind !== "oauth" || credentialSessionId(saved.credential) !== credentialSessionId(current)) {
|
|
10615
|
+
throw authError(
|
|
10616
|
+
"The original session changed or ended during renewal.",
|
|
10617
|
+
"Restart this command with your current connection."
|
|
10618
|
+
);
|
|
10619
|
+
}
|
|
10620
|
+
if (!await saveOAuthCredential(refreshed)) {
|
|
10621
|
+
throw networkError(
|
|
10622
|
+
"Could not persist the renewed session.",
|
|
10623
|
+
"Check your credential storage before retrying."
|
|
10624
|
+
);
|
|
10625
|
+
}
|
|
10538
10626
|
return refreshed;
|
|
10539
10627
|
} catch (err) {
|
|
10540
10628
|
if (err instanceof RefreshRejected) {
|
|
@@ -10890,12 +10978,14 @@ function sleep(ms) {
|
|
|
10890
10978
|
}
|
|
10891
10979
|
var GadClient = class {
|
|
10892
10980
|
token;
|
|
10981
|
+
getToken;
|
|
10893
10982
|
apiUrl;
|
|
10894
10983
|
repoUrl;
|
|
10895
10984
|
workspaceId;
|
|
10896
10985
|
timeout;
|
|
10897
10986
|
constructor(options) {
|
|
10898
10987
|
this.token = options.token;
|
|
10988
|
+
this.getToken = options.getToken;
|
|
10899
10989
|
this.apiUrl = options.apiUrl.replace(/\/$/, "");
|
|
10900
10990
|
this.repoUrl = options.repoUrl ?? detectGitOrigin();
|
|
10901
10991
|
this.workspaceId = options.workspaceId?.trim() || getCommandWorkspaceTarget({ hasRepo: Boolean(this.repoUrl) }) || null;
|
|
@@ -10931,8 +11021,28 @@ var GadClient = class {
|
|
|
10931
11021
|
const controller = new AbortController();
|
|
10932
11022
|
const timer = setTimeout(() => controller.abort(), effectiveTimeout);
|
|
10933
11023
|
try {
|
|
11024
|
+
const headers = this.getToken ? new Headers(init.headers) : void 0;
|
|
11025
|
+
if (headers && this.getToken) {
|
|
11026
|
+
const token = await Promise.race([
|
|
11027
|
+
this.getToken(),
|
|
11028
|
+
new Promise((_resolve, reject) => {
|
|
11029
|
+
controller.signal.addEventListener(
|
|
11030
|
+
"abort",
|
|
11031
|
+
() => reject(
|
|
11032
|
+
networkError(
|
|
11033
|
+
"Session renewal timed out.",
|
|
11034
|
+
"Retry shortly; your saved session has been kept."
|
|
11035
|
+
)
|
|
11036
|
+
),
|
|
11037
|
+
{ once: true }
|
|
11038
|
+
);
|
|
11039
|
+
})
|
|
11040
|
+
]);
|
|
11041
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
11042
|
+
}
|
|
10934
11043
|
const response = await fetch(url, {
|
|
10935
11044
|
...init,
|
|
11045
|
+
...headers ? { headers: Object.fromEntries(headers) } : {},
|
|
10936
11046
|
signal: controller.signal
|
|
10937
11047
|
});
|
|
10938
11048
|
clearTimeout(timer);
|
|
@@ -11181,6 +11291,7 @@ function whoamiCommand() {
|
|
|
11181
11291
|
const auth = await resolveAuth();
|
|
11182
11292
|
const client = new GadClient({
|
|
11183
11293
|
token: auth.token,
|
|
11294
|
+
getToken: auth.getToken,
|
|
11184
11295
|
apiUrl: auth.apiUrl
|
|
11185
11296
|
});
|
|
11186
11297
|
let result;
|
|
@@ -11291,7 +11402,11 @@ var detailFields = [
|
|
|
11291
11402
|
];
|
|
11292
11403
|
async function buildClient() {
|
|
11293
11404
|
const auth = await resolveAuth();
|
|
11294
|
-
return new GadClient({
|
|
11405
|
+
return new GadClient({
|
|
11406
|
+
token: auth.token,
|
|
11407
|
+
getToken: auth.getToken,
|
|
11408
|
+
apiUrl: auth.apiUrl
|
|
11409
|
+
});
|
|
11295
11410
|
}
|
|
11296
11411
|
function extractPreview(item) {
|
|
11297
11412
|
if (typeof item.content === "string") {
|
|
@@ -11469,7 +11584,11 @@ var memberColumns = [
|
|
|
11469
11584
|
];
|
|
11470
11585
|
async function createClient() {
|
|
11471
11586
|
const auth = await resolveAuth();
|
|
11472
|
-
return new GadClient({
|
|
11587
|
+
return new GadClient({
|
|
11588
|
+
token: auth.token,
|
|
11589
|
+
getToken: auth.getToken,
|
|
11590
|
+
apiUrl: auth.apiUrl
|
|
11591
|
+
});
|
|
11473
11592
|
}
|
|
11474
11593
|
var listCommand = new Command("list").description("List all accessible workspaces").option("--json", "Output as JSON").action(async (options) => {
|
|
11475
11594
|
const jsonFlag = Boolean(
|
|
@@ -11663,7 +11782,11 @@ var listCommand2 = new Command("list").description("List open todos sorted by pr
|
|
|
11663
11782
|
noColor: parentOpts.noColor || false
|
|
11664
11783
|
});
|
|
11665
11784
|
const auth = await resolveAuth();
|
|
11666
|
-
const client = new GadClient({
|
|
11785
|
+
const client = new GadClient({
|
|
11786
|
+
token: auth.token,
|
|
11787
|
+
getToken: auth.getToken,
|
|
11788
|
+
apiUrl: auth.apiUrl
|
|
11789
|
+
});
|
|
11667
11790
|
const args = {
|
|
11668
11791
|
status: options.status ?? "open"
|
|
11669
11792
|
};
|
|
@@ -11689,7 +11812,11 @@ var createCommand = new Command("create").description("Create a new todo card").
|
|
|
11689
11812
|
noColor: false
|
|
11690
11813
|
});
|
|
11691
11814
|
const auth = await resolveAuth();
|
|
11692
|
-
const client = new GadClient({
|
|
11815
|
+
const client = new GadClient({
|
|
11816
|
+
token: auth.token,
|
|
11817
|
+
getToken: auth.getToken,
|
|
11818
|
+
apiUrl: auth.apiUrl
|
|
11819
|
+
});
|
|
11693
11820
|
const args = { title };
|
|
11694
11821
|
if (options.priority) {
|
|
11695
11822
|
args.priority = options.priority;
|
|
@@ -11717,7 +11844,11 @@ var doneCommand = new Command("done").description("Mark a todo as done").argumen
|
|
|
11717
11844
|
noColor: false
|
|
11718
11845
|
});
|
|
11719
11846
|
const auth = await resolveAuth();
|
|
11720
|
-
const client = new GadClient({
|
|
11847
|
+
const client = new GadClient({
|
|
11848
|
+
token: auth.token,
|
|
11849
|
+
getToken: auth.getToken,
|
|
11850
|
+
apiUrl: auth.apiUrl
|
|
11851
|
+
});
|
|
11721
11852
|
await client.mcpCall("todo_update", { id, status: "done" });
|
|
11722
11853
|
fmt.success(`Todo ${id} marked as done.`);
|
|
11723
11854
|
});
|
|
@@ -11736,7 +11867,11 @@ var updateCommand = new Command("update").description("Update a todo's status").
|
|
|
11736
11867
|
process.exit(3);
|
|
11737
11868
|
}
|
|
11738
11869
|
const auth = await resolveAuth();
|
|
11739
|
-
const client = new GadClient({
|
|
11870
|
+
const client = new GadClient({
|
|
11871
|
+
token: auth.token,
|
|
11872
|
+
getToken: auth.getToken,
|
|
11873
|
+
apiUrl: auth.apiUrl
|
|
11874
|
+
});
|
|
11740
11875
|
await client.mcpCall("todo_update", { id, status: options.status });
|
|
11741
11876
|
fmt.success(`Todo ${id} updated to status: ${options.status}`);
|
|
11742
11877
|
});
|
|
@@ -11778,7 +11913,11 @@ var getCommand = new Command("get").description("Show one card's full state (ass
|
|
|
11778
11913
|
noColor: parentOpts.noColor || false
|
|
11779
11914
|
});
|
|
11780
11915
|
const auth = await resolveAuth();
|
|
11781
|
-
const client = new GadClient({
|
|
11916
|
+
const client = new GadClient({
|
|
11917
|
+
token: auth.token,
|
|
11918
|
+
getToken: auth.getToken,
|
|
11919
|
+
apiUrl: auth.apiUrl
|
|
11920
|
+
});
|
|
11782
11921
|
const result = await client.mcpCall("todo_get", { id });
|
|
11783
11922
|
if (!result || typeof result !== "object" || result.error) {
|
|
11784
11923
|
fmt.error(`Todo ${id} not found in this workspace.`);
|
|
@@ -11838,7 +11977,11 @@ var assignCommand = new Command("assign").description("Assign a card to a specia
|
|
|
11838
11977
|
process.exit(3);
|
|
11839
11978
|
}
|
|
11840
11979
|
const auth = await resolveAuth();
|
|
11841
|
-
const client = new GadClient({
|
|
11980
|
+
const client = new GadClient({
|
|
11981
|
+
token: auth.token,
|
|
11982
|
+
getToken: auth.getToken,
|
|
11983
|
+
apiUrl: auth.apiUrl
|
|
11984
|
+
});
|
|
11842
11985
|
const result = await client.mcpCall("todo_assign", built.args);
|
|
11843
11986
|
if (isToolError(result)) {
|
|
11844
11987
|
fmt.error(toolErrorMessage(result));
|
|
@@ -11866,7 +12009,11 @@ var runCommand = new Command("run").description("Start the agent already queued
|
|
|
11866
12009
|
noColor: false
|
|
11867
12010
|
});
|
|
11868
12011
|
const auth = await resolveAuth();
|
|
11869
|
-
const client = new GadClient({
|
|
12012
|
+
const client = new GadClient({
|
|
12013
|
+
token: auth.token,
|
|
12014
|
+
getToken: auth.getToken,
|
|
12015
|
+
apiUrl: auth.apiUrl
|
|
12016
|
+
});
|
|
11870
12017
|
const result = await client.post(
|
|
11871
12018
|
`/api/todos/${encodeURIComponent(id)}/run`,
|
|
11872
12019
|
{}
|
|
@@ -11954,7 +12101,11 @@ var claimCommand = new Command("claim").description("Preflight + atomically clai
|
|
|
11954
12101
|
noColor: parentOpts.noColor || false
|
|
11955
12102
|
});
|
|
11956
12103
|
const auth = await resolveAuth();
|
|
11957
|
-
const client = new GadClient({
|
|
12104
|
+
const client = new GadClient({
|
|
12105
|
+
token: auth.token,
|
|
12106
|
+
getToken: auth.getToken,
|
|
12107
|
+
apiUrl: auth.apiUrl
|
|
12108
|
+
});
|
|
11958
12109
|
if (!options.force) {
|
|
11959
12110
|
tryGit(["fetch", "--quiet", "origin", "main"]);
|
|
11960
12111
|
const log = tryGit(["log", "--format=%s", "-n", "10", "origin/main"]) ?? tryGit(["log", "--format=%s", "-n", "10", "FETCH_HEAD"]) ?? "";
|
|
@@ -12532,6 +12683,7 @@ function workCommand() {
|
|
|
12532
12683
|
}
|
|
12533
12684
|
const client = new GadClient({
|
|
12534
12685
|
token: auth.token,
|
|
12686
|
+
getToken: auth.getToken,
|
|
12535
12687
|
apiUrl: auth.apiUrl
|
|
12536
12688
|
});
|
|
12537
12689
|
const data = await client.get("/api/work/status", {
|
|
@@ -12556,6 +12708,7 @@ function workCommand() {
|
|
|
12556
12708
|
const auth = await resolveAuth();
|
|
12557
12709
|
const client = new GadClient({
|
|
12558
12710
|
token: auth.token,
|
|
12711
|
+
getToken: auth.getToken,
|
|
12559
12712
|
apiUrl: auth.apiUrl
|
|
12560
12713
|
});
|
|
12561
12714
|
const report = options.file ? JSON.parse(readFileSync(options.file, "utf8")) : await collectWorkStatus({
|
|
@@ -13089,7 +13242,11 @@ function normalizeResponse(value) {
|
|
|
13089
13242
|
}
|
|
13090
13243
|
async function createClient2() {
|
|
13091
13244
|
const auth = await resolveAuth();
|
|
13092
|
-
return new GadClient({
|
|
13245
|
+
return new GadClient({
|
|
13246
|
+
token: auth.token,
|
|
13247
|
+
getToken: auth.getToken,
|
|
13248
|
+
apiUrl: auth.apiUrl
|
|
13249
|
+
});
|
|
13093
13250
|
}
|
|
13094
13251
|
function renderGuardrails(data, cmd) {
|
|
13095
13252
|
const output = globalOutputOpts(cmd);
|
|
@@ -14216,7 +14373,7 @@ async function ensureHookCredential(opts) {
|
|
|
14216
14373
|
}
|
|
14217
14374
|
|
|
14218
14375
|
// src/lib/mcp-doctor.ts
|
|
14219
|
-
import { createHash as
|
|
14376
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
14220
14377
|
|
|
14221
14378
|
// src/lib/mcp-target.ts
|
|
14222
14379
|
var GREAT_ARROW_HOSTS = /* @__PURE__ */ new Set([
|
|
@@ -14555,7 +14712,7 @@ function connectorGuidance(entries) {
|
|
|
14555
14712
|
return 'If you ALSO installed the Great Arrow connector on claude.ai, Claude Code counts it as a second server and nags "needs authentication" forever. Silence the duplicate by adding its name to "deniedMcpServers" in ~/.claude/settings.json ("disabledMcpServers" is not a valid key; "disabledMcpjsonServers" only covers .mcp.json servers).';
|
|
14556
14713
|
}
|
|
14557
14714
|
function fingerprint(value) {
|
|
14558
|
-
return
|
|
14715
|
+
return createHash3("sha256").update(value).digest("hex").slice(0, 12);
|
|
14559
14716
|
}
|
|
14560
14717
|
function describeStoredCredential(raw) {
|
|
14561
14718
|
const trimmed = raw?.trim();
|
|
@@ -14829,7 +14986,11 @@ async function checkApi(auth) {
|
|
|
14829
14986
|
hint: "Fix authentication first."
|
|
14830
14987
|
};
|
|
14831
14988
|
}
|
|
14832
|
-
const client = new GadClient({
|
|
14989
|
+
const client = new GadClient({
|
|
14990
|
+
token: auth.token,
|
|
14991
|
+
getToken: auth.getToken,
|
|
14992
|
+
apiUrl: auth.apiUrl
|
|
14993
|
+
});
|
|
14833
14994
|
try {
|
|
14834
14995
|
await client.get("/api/health");
|
|
14835
14996
|
return {
|
|
@@ -14855,7 +15016,11 @@ async function checkCliVersion(auth) {
|
|
|
14855
15016
|
hint: "Fix authentication first, then run 'gad doctor' again."
|
|
14856
15017
|
};
|
|
14857
15018
|
}
|
|
14858
|
-
const client = new GadClient({
|
|
15019
|
+
const client = new GadClient({
|
|
15020
|
+
token: auth.token,
|
|
15021
|
+
getToken: auth.getToken,
|
|
15022
|
+
apiUrl: auth.apiUrl
|
|
15023
|
+
});
|
|
14859
15024
|
try {
|
|
14860
15025
|
const result = await client.get("/api/mcp/version");
|
|
14861
15026
|
const cli = result.updates?.cli ?? result.cli ?? {};
|
|
@@ -14901,7 +15066,11 @@ async function checkIntegrations(auth) {
|
|
|
14901
15066
|
hint: "Fix authentication first."
|
|
14902
15067
|
};
|
|
14903
15068
|
}
|
|
14904
|
-
const client = new GadClient({
|
|
15069
|
+
const client = new GadClient({
|
|
15070
|
+
token: auth.token,
|
|
15071
|
+
getToken: auth.getToken,
|
|
15072
|
+
apiUrl: auth.apiUrl
|
|
15073
|
+
});
|
|
14905
15074
|
try {
|
|
14906
15075
|
const result = await client.mcpCall("integrations_list", {});
|
|
14907
15076
|
const data = result ?? {};
|
|
@@ -14942,7 +15111,11 @@ async function checkMcp(auth) {
|
|
|
14942
15111
|
hint: "Fix authentication first."
|
|
14943
15112
|
};
|
|
14944
15113
|
}
|
|
14945
|
-
const client = new GadClient({
|
|
15114
|
+
const client = new GadClient({
|
|
15115
|
+
token: auth.token,
|
|
15116
|
+
getToken: auth.getToken,
|
|
15117
|
+
apiUrl: auth.apiUrl
|
|
15118
|
+
});
|
|
14946
15119
|
try {
|
|
14947
15120
|
await client.mcpCall("workspace_introspect", {});
|
|
14948
15121
|
return {
|
|
@@ -15550,7 +15723,11 @@ var integrationListColumns = [
|
|
|
15550
15723
|
];
|
|
15551
15724
|
async function createClient3() {
|
|
15552
15725
|
const auth = await resolveAuth();
|
|
15553
|
-
return new GadClient({
|
|
15726
|
+
return new GadClient({
|
|
15727
|
+
token: auth.token,
|
|
15728
|
+
getToken: auth.getToken,
|
|
15729
|
+
apiUrl: auth.apiUrl
|
|
15730
|
+
});
|
|
15554
15731
|
}
|
|
15555
15732
|
function formatErrorDetail(rawError) {
|
|
15556
15733
|
if (rawError === null || rawError === void 0) return null;
|
|
@@ -15661,13 +15838,13 @@ var integrationsCommand = new Command("integrations").description("Manage connec
|
|
|
15661
15838
|
var integrations_default = integrationsCommand;
|
|
15662
15839
|
|
|
15663
15840
|
// src/commands/mcp.ts
|
|
15664
|
-
import { createHash as
|
|
15841
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
15665
15842
|
import { hostname } from "node:os";
|
|
15666
15843
|
|
|
15667
15844
|
// src/lib/mcp-bridge.ts
|
|
15668
15845
|
import { createInterface as createInterface2 } from "node:readline";
|
|
15669
15846
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
15670
|
-
import { randomUUID } from "node:crypto";
|
|
15847
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
15671
15848
|
var BRIDGE_CLIENT_ID = "gad-cli";
|
|
15672
15849
|
var DEFAULT_API_URL2 = "https://www.greatarrow.ai";
|
|
15673
15850
|
var DEFAULT_TIMEOUT_MS3 = 9e4;
|
|
@@ -15830,7 +16007,7 @@ async function runStdioBridge(options) {
|
|
|
15830
16007
|
`));
|
|
15831
16008
|
const endpoint = `${options.auth.apiUrl}/api/mcp`;
|
|
15832
16009
|
const timeoutMs = Number(env4.MEMORY_TIMEOUT_MS ?? DEFAULT_TIMEOUT_MS3);
|
|
15833
|
-
const sessionId = (env4.GAD_SESSION_ID ?? env4.MEMORY_SESSION_ID)?.trim() || `${BRIDGE_CLIENT_ID}:${Date.now()}-${
|
|
16010
|
+
const sessionId = (env4.GAD_SESSION_ID ?? env4.MEMORY_SESSION_ID)?.trim() || `${BRIDGE_CLIENT_ID}:${Date.now()}-${randomUUID2().slice(0, 8)}`;
|
|
15834
16011
|
const repoUrl = (env4.GAD_REPO_URL ?? env4.MEMORY_REPO_URL)?.trim() || detectGitOrigin3();
|
|
15835
16012
|
const workspaceId = (env4.GAD_WORKSPACE_ID ?? env4.GAD_WORKSPACE)?.trim() || getCommandWorkspaceTarget({ hasRepo: Boolean(repoUrl) });
|
|
15836
16013
|
const headers = buildBridgeHeaders({
|
|
@@ -15994,7 +16171,11 @@ async function mcpJsonAtRiskOfCommit(dir) {
|
|
|
15994
16171
|
}
|
|
15995
16172
|
async function createClient4() {
|
|
15996
16173
|
const auth = await resolveAuth();
|
|
15997
|
-
const client = new GadClient({
|
|
16174
|
+
const client = new GadClient({
|
|
16175
|
+
token: auth.token,
|
|
16176
|
+
getToken: auth.getToken,
|
|
16177
|
+
apiUrl: auth.apiUrl
|
|
16178
|
+
});
|
|
15998
16179
|
return { client, apiUrl: auth.apiUrl };
|
|
15999
16180
|
}
|
|
16000
16181
|
function generateMcpConfig(client, apiUrl, token, repoUrl) {
|
|
@@ -16029,7 +16210,7 @@ function generateMcpConfig(client, apiUrl, token, repoUrl) {
|
|
|
16029
16210
|
Authorization: `Bearer ${token}`,
|
|
16030
16211
|
"X-MCP-Client": CLIENT_IDS[client],
|
|
16031
16212
|
"X-Device-Name": deviceName,
|
|
16032
|
-
"X-Machine-Id":
|
|
16213
|
+
"X-Machine-Id": createHash4("sha256").update(rawHost).digest("hex"),
|
|
16033
16214
|
[GAD_INSTALL_HEADER]: GAD_INSTALL_VERSION,
|
|
16034
16215
|
...repoUrl ? { "X-Repo-URL": repoUrl } : {}
|
|
16035
16216
|
};
|
|
@@ -16294,7 +16475,11 @@ var testCommand = new Command("test").description(
|
|
|
16294
16475
|
const c = createColor(false);
|
|
16295
16476
|
const auth = await resolveAuth();
|
|
16296
16477
|
const apiUrl = auth.apiUrl;
|
|
16297
|
-
const client = new GadClient({
|
|
16478
|
+
const client = new GadClient({
|
|
16479
|
+
token: auth.token,
|
|
16480
|
+
getToken: auth.getToken,
|
|
16481
|
+
apiUrl
|
|
16482
|
+
});
|
|
16298
16483
|
const verbose = !jsonFlag;
|
|
16299
16484
|
const checks = [
|
|
16300
16485
|
await checkDns(apiUrl, verbose, c),
|
|
@@ -16833,6 +17018,7 @@ function smokeCommand() {
|
|
|
16833
17018
|
const auth = await resolveAuth();
|
|
16834
17019
|
const client = new GadClient({
|
|
16835
17020
|
token: auth.token,
|
|
17021
|
+
getToken: auth.getToken,
|
|
16836
17022
|
apiUrl: auth.apiUrl
|
|
16837
17023
|
});
|
|
16838
17024
|
if (!isJson && !isQuiet) {
|
|
@@ -16880,7 +17066,11 @@ var agentListColumns = [
|
|
|
16880
17066
|
];
|
|
16881
17067
|
async function buildClient2() {
|
|
16882
17068
|
const auth = await resolveAuth();
|
|
16883
|
-
return new GadClient({
|
|
17069
|
+
return new GadClient({
|
|
17070
|
+
token: auth.token,
|
|
17071
|
+
getToken: auth.getToken,
|
|
17072
|
+
apiUrl: auth.apiUrl
|
|
17073
|
+
});
|
|
16884
17074
|
}
|
|
16885
17075
|
function invokeOutcome(data) {
|
|
16886
17076
|
if (typeof data === "string") {
|
|
@@ -17030,6 +17220,7 @@ function askCommand() {
|
|
|
17030
17220
|
const auth = await resolveAuth();
|
|
17031
17221
|
const client = new GadClient({
|
|
17032
17222
|
token: auth.token,
|
|
17223
|
+
getToken: auth.getToken,
|
|
17033
17224
|
apiUrl: auth.apiUrl
|
|
17034
17225
|
});
|
|
17035
17226
|
const fmt = createFormatter({
|
|
@@ -18022,26 +18213,36 @@ function authHeaders(token) {
|
|
|
18022
18213
|
function origin(apiUrl) {
|
|
18023
18214
|
return apiUrl.replace(/\/+$/, "");
|
|
18024
18215
|
}
|
|
18025
|
-
function appAwareFetch(apiUrl, token, base) {
|
|
18026
|
-
return (input, init) => {
|
|
18216
|
+
function appAwareFetch(apiUrl, token, base, getToken2) {
|
|
18217
|
+
return async (input, init) => {
|
|
18027
18218
|
const target = typeof input === "string" ? input : String(input);
|
|
18028
|
-
|
|
18029
|
-
|
|
18219
|
+
const relative = target.startsWith("/") && !target.startsWith("//");
|
|
18220
|
+
if (!relative && !target.startsWith(`${origin(apiUrl)}/`))
|
|
18221
|
+
return base(input, init);
|
|
18222
|
+
const currentToken = getToken2 ? await withTimeout(15e3, () => getToken2()) : token;
|
|
18223
|
+
return base(relative ? `${origin(apiUrl)}${target}` : target, {
|
|
18030
18224
|
...init,
|
|
18031
18225
|
headers: {
|
|
18032
18226
|
...init?.headers,
|
|
18033
|
-
...authHeaders(
|
|
18227
|
+
...authHeaders(currentToken)
|
|
18034
18228
|
}
|
|
18035
18229
|
});
|
|
18036
18230
|
};
|
|
18037
18231
|
}
|
|
18038
18232
|
async function withTimeout(ms, run2) {
|
|
18039
18233
|
const controller = new AbortController();
|
|
18040
|
-
|
|
18234
|
+
let timer;
|
|
18235
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
18236
|
+
timer = setTimeout(() => {
|
|
18237
|
+
const error = new DOMException("Request timed out", "AbortError");
|
|
18238
|
+
controller.abort(error);
|
|
18239
|
+
reject(error);
|
|
18240
|
+
}, ms);
|
|
18241
|
+
});
|
|
18041
18242
|
try {
|
|
18042
|
-
return await run2(controller.signal);
|
|
18243
|
+
return await Promise.race([run2(controller.signal), deadline]);
|
|
18043
18244
|
} finally {
|
|
18044
|
-
clearTimeout(timer);
|
|
18245
|
+
if (timer) clearTimeout(timer);
|
|
18045
18246
|
}
|
|
18046
18247
|
}
|
|
18047
18248
|
function readHandoff(payload) {
|
|
@@ -18106,7 +18307,7 @@ async function askForTheTurn(opts) {
|
|
|
18106
18307
|
if (!response.ok) {
|
|
18107
18308
|
throw new LocalTurnFailed(
|
|
18108
18309
|
"handoff",
|
|
18109
|
-
response.status === 401
|
|
18310
|
+
response.status === 401 ? "Sign in again \u2014 this computer is not allowed to ask for work." : response.status === 403 ? "This connection needs permission. Review its scopes and workspace access at /account/connections." : `Great Arrow refused that (${response.status}).`,
|
|
18110
18311
|
true
|
|
18111
18312
|
);
|
|
18112
18313
|
}
|
|
@@ -18168,14 +18369,14 @@ async function handBack(opts) {
|
|
|
18168
18369
|
}
|
|
18169
18370
|
async function answerOnThisComputer(opts) {
|
|
18170
18371
|
const base = opts.fetchImpl ?? fetch;
|
|
18171
|
-
const doFetch = appAwareFetch(opts.apiUrl, opts.token, base);
|
|
18372
|
+
const doFetch = appAwareFetch(opts.apiUrl, opts.token, base, opts.getToken);
|
|
18172
18373
|
const handoff = await askForTheTurn({
|
|
18173
18374
|
apiUrl: opts.apiUrl,
|
|
18174
18375
|
token: opts.token,
|
|
18175
18376
|
question: opts.question,
|
|
18176
18377
|
sessionId: opts.sessionId ?? null,
|
|
18177
18378
|
timezone: opts.timezone,
|
|
18178
|
-
doFetch
|
|
18379
|
+
doFetch
|
|
18179
18380
|
});
|
|
18180
18381
|
if (!handoff) {
|
|
18181
18382
|
return { answeredHere: false, reason: "answered_by_great_arrow" };
|
|
@@ -18217,7 +18418,7 @@ async function answerOnThisComputer(opts) {
|
|
|
18217
18418
|
outputTokens: result.outputTokens,
|
|
18218
18419
|
promptCharacters: result.promptCharacters,
|
|
18219
18420
|
latencyMs: Date.now() - startedAt,
|
|
18220
|
-
doFetch
|
|
18421
|
+
doFetch
|
|
18221
18422
|
});
|
|
18222
18423
|
return {
|
|
18223
18424
|
answeredHere: true,
|
|
@@ -18237,7 +18438,7 @@ async function answerOnThisComputer(opts) {
|
|
|
18237
18438
|
outputTokens: void 0,
|
|
18238
18439
|
promptCharacters: 0,
|
|
18239
18440
|
latencyMs: Date.now() - startedAt,
|
|
18240
|
-
doFetch
|
|
18441
|
+
doFetch
|
|
18241
18442
|
});
|
|
18242
18443
|
throw new LocalTurnFailed(
|
|
18243
18444
|
"writing",
|
|
@@ -18470,7 +18671,11 @@ function checkCommand() {
|
|
|
18470
18671
|
const provider = readProvider(options.app);
|
|
18471
18672
|
const baseUrl = typeof options.url === "string" && options.url ? options.url : LOCAL_PROVIDER_PRESETS[provider].baseUrl;
|
|
18472
18673
|
const auth = await resolveAuth();
|
|
18473
|
-
const client = new GadClient({
|
|
18674
|
+
const client = new GadClient({
|
|
18675
|
+
token: auth.token,
|
|
18676
|
+
getToken: auth.getToken,
|
|
18677
|
+
apiUrl: auth.apiUrl
|
|
18678
|
+
});
|
|
18474
18679
|
const fmt = createFormatter({
|
|
18475
18680
|
json: g.json,
|
|
18476
18681
|
quiet: g.quiet,
|
|
@@ -18545,7 +18750,11 @@ function statusCommand2() {
|
|
|
18545
18750
|
return new Command("status").description("Show what is set up for models on this computer").action(async (_options, command) => {
|
|
18546
18751
|
const g = globalOutputOpts(command);
|
|
18547
18752
|
const auth = await resolveAuth();
|
|
18548
|
-
const client = new GadClient({
|
|
18753
|
+
const client = new GadClient({
|
|
18754
|
+
token: auth.token,
|
|
18755
|
+
getToken: auth.getToken,
|
|
18756
|
+
apiUrl: auth.apiUrl
|
|
18757
|
+
});
|
|
18549
18758
|
const payload = await client.mcpCall(
|
|
18550
18759
|
"model_local_get",
|
|
18551
18760
|
{},
|
|
@@ -18613,7 +18822,11 @@ function offCommand() {
|
|
|
18613
18822
|
return new Command("off").description("Stop answering with a model on this computer").action(async (_options, command) => {
|
|
18614
18823
|
const g = globalOutputOpts(command);
|
|
18615
18824
|
const auth = await resolveAuth();
|
|
18616
|
-
const client = new GadClient({
|
|
18825
|
+
const client = new GadClient({
|
|
18826
|
+
token: auth.token,
|
|
18827
|
+
getToken: auth.getToken,
|
|
18828
|
+
apiUrl: auth.apiUrl
|
|
18829
|
+
});
|
|
18617
18830
|
const fmt = createFormatter({
|
|
18618
18831
|
json: g.json,
|
|
18619
18832
|
quiet: g.quiet,
|
|
@@ -18644,6 +18857,7 @@ function askHereCommand() {
|
|
|
18644
18857
|
report = await answerOnThisComputer({
|
|
18645
18858
|
apiUrl: auth.apiUrl,
|
|
18646
18859
|
token: auth.token,
|
|
18860
|
+
getToken: auth.getToken,
|
|
18647
18861
|
question,
|
|
18648
18862
|
sessionId: typeof options.session === "string" ? options.session : null,
|
|
18649
18863
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
@@ -18757,6 +18971,7 @@ function statusCommand3() {
|
|
|
18757
18971
|
const auth = await resolveAuth();
|
|
18758
18972
|
const client = new GadClient({
|
|
18759
18973
|
token: auth.token,
|
|
18974
|
+
getToken: auth.getToken,
|
|
18760
18975
|
apiUrl: auth.apiUrl
|
|
18761
18976
|
});
|
|
18762
18977
|
const fmt = createFormatter({
|
|
@@ -18934,7 +19149,11 @@ function openCommand() {
|
|
|
18934
19149
|
).option("--print", "Print the URL instead of opening a browser").option("--json", "Output the resolved surface as JSON").action(async (surface, options, command) => {
|
|
18935
19150
|
const g = globalOutputOpts(command);
|
|
18936
19151
|
const auth = await resolveAuth();
|
|
18937
|
-
const client = new GadClient({
|
|
19152
|
+
const client = new GadClient({
|
|
19153
|
+
token: auth.token,
|
|
19154
|
+
getToken: auth.getToken,
|
|
19155
|
+
apiUrl: auth.apiUrl
|
|
19156
|
+
});
|
|
18938
19157
|
const result = await client.mcpCall("surfaces_list", {
|
|
18939
19158
|
query: surface
|
|
18940
19159
|
});
|
|
@@ -19022,7 +19241,11 @@ function nextCommand() {
|
|
|
19022
19241
|
noColor: g.noColor
|
|
19023
19242
|
});
|
|
19024
19243
|
const auth = await resolveAuth();
|
|
19025
|
-
const client = new GadClient({
|
|
19244
|
+
const client = new GadClient({
|
|
19245
|
+
token: auth.token,
|
|
19246
|
+
getToken: auth.getToken,
|
|
19247
|
+
apiUrl: auth.apiUrl
|
|
19248
|
+
});
|
|
19026
19249
|
let identityStatus = null;
|
|
19027
19250
|
try {
|
|
19028
19251
|
const who = await client.mcpCall("workspace_introspect", {});
|
|
@@ -19085,7 +19308,11 @@ function discoverCommand() {
|
|
|
19085
19308
|
noColor: g.noColor
|
|
19086
19309
|
});
|
|
19087
19310
|
const auth = await resolveAuth();
|
|
19088
|
-
const client = new GadClient({
|
|
19311
|
+
const client = new GadClient({
|
|
19312
|
+
token: auth.token,
|
|
19313
|
+
getToken: auth.getToken,
|
|
19314
|
+
apiUrl: auth.apiUrl
|
|
19315
|
+
});
|
|
19089
19316
|
const parsed = opts.limit ? Number.parseInt(opts.limit, 10) : NaN;
|
|
19090
19317
|
const args = Number.isFinite(parsed) && parsed > 0 ? { limit: parsed } : {};
|
|
19091
19318
|
const result = await client.mcpCall("did_you_know", args);
|