@great-arrow/cli 0.1.47 → 0.1.49
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 +18 -0
- package/dist/hooks/gad-suggest-tools.mjs +98 -0
- package/dist/index.js +629 -246
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -104,7 +104,7 @@ var require_polyfills = __commonJS({
|
|
|
104
104
|
}
|
|
105
105
|
if (platform === "win32") {
|
|
106
106
|
fs6.rename = typeof fs6.rename !== "function" ? fs6.rename : (function(fs$rename) {
|
|
107
|
-
function
|
|
107
|
+
function rename2(from, to, cb) {
|
|
108
108
|
var start = Date.now();
|
|
109
109
|
var backoff = 0;
|
|
110
110
|
fs$rename(from, to, function CB(er) {
|
|
@@ -124,8 +124,8 @@ var require_polyfills = __commonJS({
|
|
|
124
124
|
if (cb) cb(er);
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
|
-
if (Object.setPrototypeOf) Object.setPrototypeOf(
|
|
128
|
-
return
|
|
127
|
+
if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
|
|
128
|
+
return rename2;
|
|
129
129
|
})(fs6.rename);
|
|
130
130
|
}
|
|
131
131
|
fs6.read = typeof fs6.read !== "function" ? fs6.read : (function(fs$read) {
|
|
@@ -529,8 +529,8 @@ var require_graceful_fs = __commonJS({
|
|
|
529
529
|
fs7.createReadStream = createReadStream;
|
|
530
530
|
fs7.createWriteStream = createWriteStream;
|
|
531
531
|
var fs$readFile = fs7.readFile;
|
|
532
|
-
fs7.readFile =
|
|
533
|
-
function
|
|
532
|
+
fs7.readFile = readFile7;
|
|
533
|
+
function readFile7(path10, options, cb) {
|
|
534
534
|
if (typeof options === "function")
|
|
535
535
|
cb = options, options = null;
|
|
536
536
|
return go$readFile(path10, options, cb);
|
|
@@ -546,8 +546,8 @@ var require_graceful_fs = __commonJS({
|
|
|
546
546
|
}
|
|
547
547
|
}
|
|
548
548
|
var fs$writeFile = fs7.writeFile;
|
|
549
|
-
fs7.writeFile =
|
|
550
|
-
function
|
|
549
|
+
fs7.writeFile = writeFile6;
|
|
550
|
+
function writeFile6(path10, data, options, cb) {
|
|
551
551
|
if (typeof options === "function")
|
|
552
552
|
cb = options, options = null;
|
|
553
553
|
return go$writeFile(path10, data, options, cb);
|
|
@@ -582,8 +582,8 @@ var require_graceful_fs = __commonJS({
|
|
|
582
582
|
}
|
|
583
583
|
var fs$copyFile = fs7.copyFile;
|
|
584
584
|
if (fs$copyFile)
|
|
585
|
-
fs7.copyFile =
|
|
586
|
-
function
|
|
585
|
+
fs7.copyFile = copyFile3;
|
|
586
|
+
function copyFile3(src, dest, flags, cb) {
|
|
587
587
|
if (typeof flags === "function") {
|
|
588
588
|
cb = flags;
|
|
589
589
|
flags = 0;
|
|
@@ -1964,7 +1964,7 @@ var require_minimist = __commonJS({
|
|
|
1964
1964
|
var require_rc = __commonJS({
|
|
1965
1965
|
"node_modules/.pnpm/rc@1.2.8/node_modules/rc/index.js"(exports, module) {
|
|
1966
1966
|
var cc = require_utils();
|
|
1967
|
-
var
|
|
1967
|
+
var join10 = __require("path").join;
|
|
1968
1968
|
var deepExtend = require_deep_extend();
|
|
1969
1969
|
var etc = "/etc";
|
|
1970
1970
|
var win = process.platform === "win32";
|
|
@@ -1989,15 +1989,15 @@ var require_rc = __commonJS({
|
|
|
1989
1989
|
}
|
|
1990
1990
|
if (!win)
|
|
1991
1991
|
[
|
|
1992
|
-
|
|
1993
|
-
|
|
1992
|
+
join10(etc, name, "config"),
|
|
1993
|
+
join10(etc, name + "rc")
|
|
1994
1994
|
].forEach(addConfigFile);
|
|
1995
1995
|
if (home)
|
|
1996
1996
|
[
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
1997
|
+
join10(home, ".config", name, "config"),
|
|
1998
|
+
join10(home, ".config", name),
|
|
1999
|
+
join10(home, "." + name, "config"),
|
|
2000
|
+
join10(home, "." + name + "rc")
|
|
2001
2001
|
].forEach(addConfigFile);
|
|
2002
2002
|
addConfigFile(cc.find("." + name + "rc"));
|
|
2003
2003
|
if (env4.config) addConfigFile(env4.config);
|
|
@@ -2078,7 +2078,7 @@ var require_polyfills2 = __commonJS({
|
|
|
2078
2078
|
}
|
|
2079
2079
|
if (platform === "win32") {
|
|
2080
2080
|
fs6.rename = typeof fs6.rename !== "function" ? fs6.rename : (function(fs$rename) {
|
|
2081
|
-
function
|
|
2081
|
+
function rename2(from, to, cb) {
|
|
2082
2082
|
var start = Date.now();
|
|
2083
2083
|
var backoff = 0;
|
|
2084
2084
|
fs$rename(from, to, function CB(er) {
|
|
@@ -2098,8 +2098,8 @@ var require_polyfills2 = __commonJS({
|
|
|
2098
2098
|
if (cb) cb(er);
|
|
2099
2099
|
});
|
|
2100
2100
|
}
|
|
2101
|
-
if (Object.setPrototypeOf) Object.setPrototypeOf(
|
|
2102
|
-
return
|
|
2101
|
+
if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
|
|
2102
|
+
return rename2;
|
|
2103
2103
|
})(fs6.rename);
|
|
2104
2104
|
}
|
|
2105
2105
|
fs6.read = typeof fs6.read !== "function" ? fs6.read : (function(fs$read) {
|
|
@@ -2503,8 +2503,8 @@ var require_graceful_fs2 = __commonJS({
|
|
|
2503
2503
|
fs7.createReadStream = createReadStream;
|
|
2504
2504
|
fs7.createWriteStream = createWriteStream;
|
|
2505
2505
|
var fs$readFile = fs7.readFile;
|
|
2506
|
-
fs7.readFile =
|
|
2507
|
-
function
|
|
2506
|
+
fs7.readFile = readFile7;
|
|
2507
|
+
function readFile7(path10, options, cb) {
|
|
2508
2508
|
if (typeof options === "function")
|
|
2509
2509
|
cb = options, options = null;
|
|
2510
2510
|
return go$readFile(path10, options, cb);
|
|
@@ -2520,8 +2520,8 @@ var require_graceful_fs2 = __commonJS({
|
|
|
2520
2520
|
}
|
|
2521
2521
|
}
|
|
2522
2522
|
var fs$writeFile = fs7.writeFile;
|
|
2523
|
-
fs7.writeFile =
|
|
2524
|
-
function
|
|
2523
|
+
fs7.writeFile = writeFile6;
|
|
2524
|
+
function writeFile6(path10, data, options, cb) {
|
|
2525
2525
|
if (typeof options === "function")
|
|
2526
2526
|
cb = options, options = null;
|
|
2527
2527
|
return go$writeFile(path10, data, options, cb);
|
|
@@ -2556,8 +2556,8 @@ var require_graceful_fs2 = __commonJS({
|
|
|
2556
2556
|
}
|
|
2557
2557
|
var fs$copyFile = fs7.copyFile;
|
|
2558
2558
|
if (fs$copyFile)
|
|
2559
|
-
fs7.copyFile =
|
|
2560
|
-
function
|
|
2559
|
+
fs7.copyFile = copyFile3;
|
|
2560
|
+
function copyFile3(src, dest, flags, cb) {
|
|
2561
2561
|
if (typeof flags === "function") {
|
|
2562
2562
|
cb = flags;
|
|
2563
2563
|
flags = 0;
|
|
@@ -3335,11 +3335,11 @@ var require_util = __commonJS({
|
|
|
3335
3335
|
if (files.includes("node_modules") || files.includes("package.json") || files.includes("package.json5") || files.includes("package.yaml") || files.includes("pnpm-workspace.yaml")) {
|
|
3336
3336
|
return name2;
|
|
3337
3337
|
}
|
|
3338
|
-
const
|
|
3339
|
-
if (
|
|
3338
|
+
const dirname7 = path10.dirname(name2);
|
|
3339
|
+
if (dirname7 === name2) {
|
|
3340
3340
|
return original;
|
|
3341
3341
|
}
|
|
3342
|
-
return find(
|
|
3342
|
+
return find(dirname7, original);
|
|
3343
3343
|
} catch (error) {
|
|
3344
3344
|
if (name2 === original) {
|
|
3345
3345
|
if (error.code === "ENOENT") {
|
|
@@ -9399,7 +9399,7 @@ function isNodeError(value) {
|
|
|
9399
9399
|
|
|
9400
9400
|
// src/lib/version.ts
|
|
9401
9401
|
var CLI_PACKAGE_NAME = "@great-arrow/cli";
|
|
9402
|
-
var CLI_VERSION = "0.1.
|
|
9402
|
+
var CLI_VERSION = "0.1.49";
|
|
9403
9403
|
var GAD_INSTALL_VERSION = "2026.08.08";
|
|
9404
9404
|
var GAD_INSTALL_MIN_VERSION = "2026.06.22";
|
|
9405
9405
|
var GAD_INSTALL_HEADER = "X-GAD-Install-Version";
|
|
@@ -9865,6 +9865,10 @@ function trimSlash(url) {
|
|
|
9865
9865
|
}
|
|
9866
9866
|
|
|
9867
9867
|
// src/lib/oauth/token-exchange.ts
|
|
9868
|
+
var TOKEN_HEADERS = {
|
|
9869
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
9870
|
+
"user-agent": `gad-cli/${CLI_VERSION}`
|
|
9871
|
+
};
|
|
9868
9872
|
var RefreshRejected = class extends Error {
|
|
9869
9873
|
constructor(message) {
|
|
9870
9874
|
super(message);
|
|
@@ -9898,7 +9902,7 @@ var TOKEN_POST_TIMEOUT_MS = 8e3;
|
|
|
9898
9902
|
async function postToken(apiUrl, form, fetchImpl, timeoutMs = TOKEN_POST_TIMEOUT_MS) {
|
|
9899
9903
|
const res = await fetchImpl(`${trimSlash2(apiUrl)}/api/oauth/token`, {
|
|
9900
9904
|
method: "POST",
|
|
9901
|
-
headers:
|
|
9905
|
+
headers: TOKEN_HEADERS,
|
|
9902
9906
|
body: new URLSearchParams(form).toString(),
|
|
9903
9907
|
signal: AbortSignal.timeout(timeoutMs)
|
|
9904
9908
|
});
|
|
@@ -9940,7 +9944,7 @@ async function revokeToken(opts) {
|
|
|
9940
9944
|
try {
|
|
9941
9945
|
const res = await doFetch(`${trimSlash2(opts.apiUrl)}/api/oauth/revoke`, {
|
|
9942
9946
|
method: "POST",
|
|
9943
|
-
headers:
|
|
9947
|
+
headers: TOKEN_HEADERS,
|
|
9944
9948
|
body: new URLSearchParams(form).toString(),
|
|
9945
9949
|
signal: AbortSignal.timeout(TOKEN_POST_TIMEOUT_MS)
|
|
9946
9950
|
});
|
|
@@ -10457,6 +10461,14 @@ async function resolveAuth() {
|
|
|
10457
10461
|
tokenType: "personal"
|
|
10458
10462
|
});
|
|
10459
10463
|
}
|
|
10464
|
+
const codexEnvToken = process.env.GREAT_ARROW_TOKEN;
|
|
10465
|
+
if (codexEnvToken) {
|
|
10466
|
+
return withWorkspace({
|
|
10467
|
+
token: codexEnvToken,
|
|
10468
|
+
apiUrl: configApiUrl,
|
|
10469
|
+
tokenType: "personal"
|
|
10470
|
+
});
|
|
10471
|
+
}
|
|
10460
10472
|
throw authError(
|
|
10461
10473
|
"No authentication token found.",
|
|
10462
10474
|
"Run 'gad login' to authenticate, or set the GAD_TOKEN environment variable."
|
|
@@ -10751,6 +10763,46 @@ function isRetryable(error) {
|
|
|
10751
10763
|
}
|
|
10752
10764
|
return false;
|
|
10753
10765
|
}
|
|
10766
|
+
var RETRY_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
10767
|
+
"workspace_introspect",
|
|
10768
|
+
"mcp_capabilities",
|
|
10769
|
+
"tools_discover",
|
|
10770
|
+
"memory_search",
|
|
10771
|
+
"memory_list",
|
|
10772
|
+
"memory_get",
|
|
10773
|
+
"memory_tags",
|
|
10774
|
+
"todo_list",
|
|
10775
|
+
"todo_get",
|
|
10776
|
+
"workspace_list",
|
|
10777
|
+
"workspace_settings_get",
|
|
10778
|
+
"surfaces_list",
|
|
10779
|
+
"agent_list",
|
|
10780
|
+
"engineering_guardrails_list",
|
|
10781
|
+
"model_local_get",
|
|
10782
|
+
"glance",
|
|
10783
|
+
"did_you_know"
|
|
10784
|
+
]);
|
|
10785
|
+
var CONNECTION_PHASE_CODES = /* @__PURE__ */ new Set([
|
|
10786
|
+
"ECONNREFUSED",
|
|
10787
|
+
"ENOTFOUND",
|
|
10788
|
+
"EAI_AGAIN",
|
|
10789
|
+
"ENETUNREACH",
|
|
10790
|
+
"EHOSTUNREACH",
|
|
10791
|
+
"UND_ERR_CONNECT_TIMEOUT"
|
|
10792
|
+
]);
|
|
10793
|
+
function hasConnectionPhaseCode(value) {
|
|
10794
|
+
if (!value || typeof value !== "object") return false;
|
|
10795
|
+
const errors = value.errors;
|
|
10796
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
|
10797
|
+
return errors.every(hasConnectionPhaseCode);
|
|
10798
|
+
}
|
|
10799
|
+
const code = value.code;
|
|
10800
|
+
return typeof code === "string" && CONNECTION_PHASE_CODES.has(code);
|
|
10801
|
+
}
|
|
10802
|
+
function isConnectionPhaseFailure(error) {
|
|
10803
|
+
if (!(error instanceof TypeError)) return false;
|
|
10804
|
+
return hasConnectionPhaseCode(error.cause);
|
|
10805
|
+
}
|
|
10754
10806
|
function isRetryableStatus(status) {
|
|
10755
10807
|
return status >= 500 && status < 600;
|
|
10756
10808
|
}
|
|
@@ -10806,7 +10858,7 @@ var GadClient = class {
|
|
|
10806
10858
|
/**
|
|
10807
10859
|
* Execute a fetch request with timeout and retry logic.
|
|
10808
10860
|
*/
|
|
10809
|
-
async fetchWithRetry(url, init, timeoutMs) {
|
|
10861
|
+
async fetchWithRetry(url, init, timeoutMs, policy = "transient") {
|
|
10810
10862
|
const effectiveTimeout = timeoutMs ?? this.timeout;
|
|
10811
10863
|
let lastError;
|
|
10812
10864
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
@@ -10818,7 +10870,7 @@ var GadClient = class {
|
|
|
10818
10870
|
signal: controller.signal
|
|
10819
10871
|
});
|
|
10820
10872
|
clearTimeout(timer);
|
|
10821
|
-
if (isRetryableStatus(response.status)) {
|
|
10873
|
+
if (policy === "transient" && isRetryableStatus(response.status)) {
|
|
10822
10874
|
lastError = response;
|
|
10823
10875
|
if (attempt < MAX_RETRIES - 1) {
|
|
10824
10876
|
await sleep(retryDelay(attempt));
|
|
@@ -10830,7 +10882,8 @@ var GadClient = class {
|
|
|
10830
10882
|
} catch (error) {
|
|
10831
10883
|
clearTimeout(timer);
|
|
10832
10884
|
lastError = error;
|
|
10833
|
-
|
|
10885
|
+
const retryable = policy === "transient" ? isRetryable(error) : isConnectionPhaseFailure(error);
|
|
10886
|
+
if (retryable && attempt < MAX_RETRIES - 1) {
|
|
10834
10887
|
await sleep(retryDelay(attempt));
|
|
10835
10888
|
continue;
|
|
10836
10889
|
}
|
|
@@ -10899,7 +10952,8 @@ var GadClient = class {
|
|
|
10899
10952
|
headers: this.buildHeaders(),
|
|
10900
10953
|
body
|
|
10901
10954
|
},
|
|
10902
|
-
timeoutMs
|
|
10955
|
+
timeoutMs,
|
|
10956
|
+
RETRY_SAFE_TOOLS.has(toolName) ? "transient" : "connection-only"
|
|
10903
10957
|
);
|
|
10904
10958
|
} catch (error) {
|
|
10905
10959
|
if (error instanceof CliError) throw error;
|
|
@@ -10991,11 +11045,16 @@ var GadClient = class {
|
|
|
10991
11045
|
const url = `${this.apiUrl}${path10}`;
|
|
10992
11046
|
let response;
|
|
10993
11047
|
try {
|
|
10994
|
-
response = await this.fetchWithRetry(
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
11048
|
+
response = await this.fetchWithRetry(
|
|
11049
|
+
url,
|
|
11050
|
+
{
|
|
11051
|
+
method: "POST",
|
|
11052
|
+
headers: this.buildHeaders(),
|
|
11053
|
+
body: JSON.stringify(body)
|
|
11054
|
+
},
|
|
11055
|
+
void 0,
|
|
11056
|
+
"connection-only"
|
|
11057
|
+
);
|
|
10999
11058
|
} catch (error) {
|
|
11000
11059
|
if (error instanceof CliError) throw error;
|
|
11001
11060
|
void captureCliEvent({
|
|
@@ -12966,10 +13025,10 @@ function guardrailsCommand() {
|
|
|
12966
13025
|
}
|
|
12967
13026
|
|
|
12968
13027
|
// src/commands/doctor.ts
|
|
12969
|
-
import { existsSync as
|
|
12970
|
-
import { copyFile, mkdir as
|
|
13028
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
13029
|
+
import { copyFile as copyFile2, mkdir as mkdir5, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
12971
13030
|
import { homedir as homedir5 } from "node:os";
|
|
12972
|
-
import { dirname as
|
|
13031
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
12973
13032
|
|
|
12974
13033
|
// src/lib/codex-config.ts
|
|
12975
13034
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
@@ -13756,6 +13815,285 @@ function inspectConfiguredHooks(hooks, deps) {
|
|
|
13756
13815
|
return issues;
|
|
13757
13816
|
}
|
|
13758
13817
|
|
|
13818
|
+
// src/lib/suggest-hook.ts
|
|
13819
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
13820
|
+
import {
|
|
13821
|
+
chmod,
|
|
13822
|
+
copyFile,
|
|
13823
|
+
mkdir as mkdir4,
|
|
13824
|
+
readFile as readFile3,
|
|
13825
|
+
rename,
|
|
13826
|
+
unlink as unlink2,
|
|
13827
|
+
writeFile as writeFile3
|
|
13828
|
+
} from "node:fs/promises";
|
|
13829
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
13830
|
+
import { fileURLToPath } from "node:url";
|
|
13831
|
+
var SUGGEST_HOOK_FILE = "gad-suggest-tools.mjs";
|
|
13832
|
+
var SUGGEST_HOOK_EVENT = "UserPromptSubmit";
|
|
13833
|
+
var SUGGEST_HOOK_DISCLOSURE = "Tool suggestions (optional, off by default): when on, every prompt you type in Claude Code is sent \u2014 its text only, up to 2,000 characters, never your session id, transcript or working directory \u2014 to greatarrow.ai over your own Great Arrow token, so it can name up to three Great Arrow tools that may help. The prompt is ranked and dropped: it is not stored and not logged. The hook gives up after 1.5 seconds and never blocks a prompt. Turn it off any time with `gad mcp suggest-tools off`.";
|
|
13834
|
+
function asObject2(value) {
|
|
13835
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
13836
|
+
}
|
|
13837
|
+
function isOurs(hook) {
|
|
13838
|
+
return !!hook && typeof hook.command === "string" && hook.command.includes(SUGGEST_HOOK_FILE);
|
|
13839
|
+
}
|
|
13840
|
+
function parseSettings(content) {
|
|
13841
|
+
const parsed = content.trim() ? JSON.parse(content) : {};
|
|
13842
|
+
const cfg = asObject2(parsed);
|
|
13843
|
+
if (!cfg) throw new Error("settings.json is not a JSON object");
|
|
13844
|
+
return cfg;
|
|
13845
|
+
}
|
|
13846
|
+
function promptGroups(cfg) {
|
|
13847
|
+
const groups = asObject2(cfg.hooks)?.[SUGGEST_HOOK_EVENT];
|
|
13848
|
+
return Array.isArray(groups) ? groups : [];
|
|
13849
|
+
}
|
|
13850
|
+
function isSuggestHookEnabled(settingsContent) {
|
|
13851
|
+
try {
|
|
13852
|
+
return promptGroups(parseSettings(settingsContent)).some(
|
|
13853
|
+
(g) => Array.isArray(g?.hooks) && g.hooks.some(isOurs)
|
|
13854
|
+
);
|
|
13855
|
+
} catch {
|
|
13856
|
+
return false;
|
|
13857
|
+
}
|
|
13858
|
+
}
|
|
13859
|
+
function suggestHookCommand(scriptPath) {
|
|
13860
|
+
return `node "${scriptPath}"`;
|
|
13861
|
+
}
|
|
13862
|
+
function planEnableSuggestHook(settingsContent, command) {
|
|
13863
|
+
if (isSuggestHookEnabled(settingsContent)) {
|
|
13864
|
+
return { content: settingsContent, changed: false };
|
|
13865
|
+
}
|
|
13866
|
+
const cfg = parseSettings(settingsContent);
|
|
13867
|
+
const hooks = asObject2(cfg.hooks) ?? {};
|
|
13868
|
+
cfg.hooks = hooks;
|
|
13869
|
+
const groups = promptGroups(cfg);
|
|
13870
|
+
groups.push({ hooks: [{ type: "command", command, timeout: 5 }] });
|
|
13871
|
+
hooks[SUGGEST_HOOK_EVENT] = groups;
|
|
13872
|
+
return { content: JSON.stringify(cfg, null, 2) + "\n", changed: true };
|
|
13873
|
+
}
|
|
13874
|
+
function planDisableSuggestHook(settingsContent) {
|
|
13875
|
+
if (!isSuggestHookEnabled(settingsContent)) {
|
|
13876
|
+
return { content: settingsContent, changed: false };
|
|
13877
|
+
}
|
|
13878
|
+
const cfg = parseSettings(settingsContent);
|
|
13879
|
+
const hooks = asObject2(cfg.hooks);
|
|
13880
|
+
const kept = promptGroups(cfg).map(
|
|
13881
|
+
(g) => Array.isArray(g?.hooks) ? { ...g, hooks: g.hooks.filter((h) => !isOurs(h)) } : g
|
|
13882
|
+
).filter((g) => !Array.isArray(g?.hooks) || g.hooks.length > 0);
|
|
13883
|
+
if (kept.length > 0) hooks[SUGGEST_HOOK_EVENT] = kept;
|
|
13884
|
+
else delete hooks[SUGGEST_HOOK_EVENT];
|
|
13885
|
+
if (Object.keys(hooks).length === 0) delete cfg.hooks;
|
|
13886
|
+
return { content: JSON.stringify(cfg, null, 2) + "\n", changed: true };
|
|
13887
|
+
}
|
|
13888
|
+
function suggestHookPaths(home) {
|
|
13889
|
+
return {
|
|
13890
|
+
settingsPath: join7(home, ".claude", "settings.json"),
|
|
13891
|
+
scriptPath: join7(home, ".great-arrow", SUGGEST_HOOK_FILE),
|
|
13892
|
+
hookConfigPath: join7(home, ".great-arrow", "hook-config.json")
|
|
13893
|
+
};
|
|
13894
|
+
}
|
|
13895
|
+
function packagedSuggestHookPath() {
|
|
13896
|
+
const here = dirname4(fileURLToPath(import.meta.url));
|
|
13897
|
+
for (const candidate of [
|
|
13898
|
+
join7(here, "hooks", SUGGEST_HOOK_FILE),
|
|
13899
|
+
join7(here, "..", "..", "dist", "hooks", SUGGEST_HOOK_FILE)
|
|
13900
|
+
]) {
|
|
13901
|
+
if (existsSync3(candidate)) return candidate;
|
|
13902
|
+
}
|
|
13903
|
+
return null;
|
|
13904
|
+
}
|
|
13905
|
+
async function readOrEmpty(path10) {
|
|
13906
|
+
try {
|
|
13907
|
+
return await readFile3(path10, "utf8");
|
|
13908
|
+
} catch {
|
|
13909
|
+
return "";
|
|
13910
|
+
}
|
|
13911
|
+
}
|
|
13912
|
+
async function atomicWrite(path10, content, mode) {
|
|
13913
|
+
await mkdir4(dirname4(path10), { recursive: true });
|
|
13914
|
+
const tmp = `${path10}.tmp-${process.pid}`;
|
|
13915
|
+
await writeFile3(tmp, content, mode === void 0 ? "utf8" : { mode });
|
|
13916
|
+
await rename(tmp, path10);
|
|
13917
|
+
}
|
|
13918
|
+
function hasHookCredential(content) {
|
|
13919
|
+
try {
|
|
13920
|
+
const cfg = asObject2(JSON.parse(content));
|
|
13921
|
+
return typeof cfg?.token === "string" && cfg.token.trim().length > 0;
|
|
13922
|
+
} catch {
|
|
13923
|
+
return false;
|
|
13924
|
+
}
|
|
13925
|
+
}
|
|
13926
|
+
async function suggestHookStatus(home) {
|
|
13927
|
+
const p = suggestHookPaths(home);
|
|
13928
|
+
return {
|
|
13929
|
+
enabled: isSuggestHookEnabled(await readOrEmpty(p.settingsPath)),
|
|
13930
|
+
scriptPresent: existsSync3(p.scriptPath),
|
|
13931
|
+
credentialPresent: hasHookCredential(await readOrEmpty(p.hookConfigPath))
|
|
13932
|
+
};
|
|
13933
|
+
}
|
|
13934
|
+
async function enableSuggestHook(opts) {
|
|
13935
|
+
const p = suggestHookPaths(opts.home);
|
|
13936
|
+
const settings = await readOrEmpty(p.settingsPath);
|
|
13937
|
+
const plan = planEnableSuggestHook(
|
|
13938
|
+
settings,
|
|
13939
|
+
suggestHookCommand(p.scriptPath)
|
|
13940
|
+
);
|
|
13941
|
+
await mkdir4(dirname4(p.scriptPath), { recursive: true, mode: 448 });
|
|
13942
|
+
await copyFile(opts.packagedScript, p.scriptPath);
|
|
13943
|
+
await chmod(p.scriptPath, 493);
|
|
13944
|
+
if (plan.changed) await atomicWrite(p.settingsPath, plan.content);
|
|
13945
|
+
return { changed: plan.changed };
|
|
13946
|
+
}
|
|
13947
|
+
async function disableSuggestHook(opts) {
|
|
13948
|
+
const p = suggestHookPaths(opts.home);
|
|
13949
|
+
const plan = planDisableSuggestHook(await readOrEmpty(p.settingsPath));
|
|
13950
|
+
if (plan.changed) await atomicWrite(p.settingsPath, plan.content);
|
|
13951
|
+
if (existsSync3(p.scriptPath)) await unlink2(p.scriptPath);
|
|
13952
|
+
return { changed: plan.changed };
|
|
13953
|
+
}
|
|
13954
|
+
async function runSuggestHookOptIn(opts) {
|
|
13955
|
+
if (opts.choice === false) {
|
|
13956
|
+
await opts.disable();
|
|
13957
|
+
return "disabled";
|
|
13958
|
+
}
|
|
13959
|
+
if (opts.choice === void 0 && !opts.interactive) return "unchanged";
|
|
13960
|
+
opts.print(SUGGEST_HOOK_DISCLOSURE);
|
|
13961
|
+
if (opts.choice === void 0) {
|
|
13962
|
+
const yes = await opts.confirm("Turn on tool suggestions?");
|
|
13963
|
+
if (!yes) return "unchanged";
|
|
13964
|
+
}
|
|
13965
|
+
await opts.enable();
|
|
13966
|
+
return "enabled";
|
|
13967
|
+
}
|
|
13968
|
+
|
|
13969
|
+
// src/lib/hook-credential.ts
|
|
13970
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
13971
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
13972
|
+
function issuesToChecks(issues) {
|
|
13973
|
+
return issues.map((issue) => {
|
|
13974
|
+
const check = {
|
|
13975
|
+
name: `Hook credential: ${issue.code}`,
|
|
13976
|
+
status: issue.level,
|
|
13977
|
+
message: issue.message
|
|
13978
|
+
};
|
|
13979
|
+
if (issue.hint) check.hint = issue.hint;
|
|
13980
|
+
return check;
|
|
13981
|
+
});
|
|
13982
|
+
}
|
|
13983
|
+
function mergeHookConfig(existing, url, token) {
|
|
13984
|
+
let base = {};
|
|
13985
|
+
if (existing) {
|
|
13986
|
+
try {
|
|
13987
|
+
const parsed = JSON.parse(existing);
|
|
13988
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
13989
|
+
base = parsed;
|
|
13990
|
+
}
|
|
13991
|
+
} catch {
|
|
13992
|
+
base = {};
|
|
13993
|
+
}
|
|
13994
|
+
}
|
|
13995
|
+
return JSON.stringify({ ...base, url, token }, null, 2);
|
|
13996
|
+
}
|
|
13997
|
+
async function ensureHookCredential(opts) {
|
|
13998
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
13999
|
+
const { hookConfigPath, apiUrl, auth } = opts;
|
|
14000
|
+
const checks = [];
|
|
14001
|
+
const repairs = [];
|
|
14002
|
+
let url = null;
|
|
14003
|
+
let token = null;
|
|
14004
|
+
let existing = null;
|
|
14005
|
+
if (existsSync4(hookConfigPath)) {
|
|
14006
|
+
existing = await readFile4(hookConfigPath, "utf8");
|
|
14007
|
+
const inspection = inspectHookConfig(existing);
|
|
14008
|
+
url = inspection.url;
|
|
14009
|
+
token = inspection.token;
|
|
14010
|
+
checks.push(...issuesToChecks(inspection.issues));
|
|
14011
|
+
} else {
|
|
14012
|
+
checks.push({
|
|
14013
|
+
name: "Hook credential: not found",
|
|
14014
|
+
status: "warn",
|
|
14015
|
+
message: `${hookConfigPath} does not exist \u2014 hooks cannot authenticate`,
|
|
14016
|
+
hint: "Run with --enforce to write it from your gad login credential."
|
|
14017
|
+
});
|
|
14018
|
+
}
|
|
14019
|
+
const probe = async (t, base) => {
|
|
14020
|
+
const res = await fetchImpl(`${base}/api/mcp/ping`, {
|
|
14021
|
+
headers: { Authorization: `Bearer ${t}` },
|
|
14022
|
+
signal: AbortSignal.timeout(1e4)
|
|
14023
|
+
});
|
|
14024
|
+
return res.status;
|
|
14025
|
+
};
|
|
14026
|
+
let tokenOk = false;
|
|
14027
|
+
if (token) {
|
|
14028
|
+
try {
|
|
14029
|
+
const status = await probe(token, url ?? apiUrl);
|
|
14030
|
+
tokenOk = status >= 200 && status < 300;
|
|
14031
|
+
if (status === 401 || status === 403) {
|
|
14032
|
+
checks.push({
|
|
14033
|
+
name: "Hook credential: token rejected",
|
|
14034
|
+
status: "fail",
|
|
14035
|
+
message: `The hook-config.json token was rejected (HTTP ${status}) \u2014 transcript capture and the session glance are silently failing`,
|
|
14036
|
+
hint: "Run with --enforce to rewrite it from your gad login credential."
|
|
14037
|
+
});
|
|
14038
|
+
} else if (!tokenOk) {
|
|
14039
|
+
checks.push({
|
|
14040
|
+
name: "Hook credential: probe inconclusive",
|
|
14041
|
+
status: "warn",
|
|
14042
|
+
message: `Ping returned HTTP ${status} \u2014 could not confirm the hook token`
|
|
14043
|
+
});
|
|
14044
|
+
} else {
|
|
14045
|
+
checks.push({
|
|
14046
|
+
name: "Hook credential: token valid",
|
|
14047
|
+
status: "pass",
|
|
14048
|
+
message: "hook-config.json token authenticates"
|
|
14049
|
+
});
|
|
14050
|
+
}
|
|
14051
|
+
} catch (e) {
|
|
14052
|
+
checks.push({
|
|
14053
|
+
name: "Hook credential: probe failed",
|
|
14054
|
+
status: "warn",
|
|
14055
|
+
message: `Could not reach ${url ?? apiUrl}: ${String(
|
|
14056
|
+
e instanceof Error ? e.message : e
|
|
14057
|
+
)}`
|
|
14058
|
+
});
|
|
14059
|
+
}
|
|
14060
|
+
}
|
|
14061
|
+
let verified = tokenOk;
|
|
14062
|
+
if (opts.repair && !tokenOk) {
|
|
14063
|
+
if (auth?.token) {
|
|
14064
|
+
await opts.write(
|
|
14065
|
+
hookConfigPath,
|
|
14066
|
+
mergeHookConfig(existing, apiUrl, auth.token)
|
|
14067
|
+
);
|
|
14068
|
+
repairs.push("hook-config.json: rewrote from gad credential");
|
|
14069
|
+
try {
|
|
14070
|
+
const status = await probe(auth.token, apiUrl);
|
|
14071
|
+
verified = status >= 200 && status < 300;
|
|
14072
|
+
checks.push({
|
|
14073
|
+
name: "Hook credential: after repair",
|
|
14074
|
+
status: verified ? "pass" : "fail",
|
|
14075
|
+
message: verified ? "Rewritten token authenticates" : `Rewritten token still failing (HTTP ${status})`
|
|
14076
|
+
});
|
|
14077
|
+
} catch (err) {
|
|
14078
|
+
checks.push({
|
|
14079
|
+
name: "Hook credential: after repair",
|
|
14080
|
+
status: "warn",
|
|
14081
|
+
message: `Rewrote the token but could not re-probe it (${err instanceof Error ? err.message : String(err)})`,
|
|
14082
|
+
hint: "Re-run `gad doctor` once the network is reachable to confirm."
|
|
14083
|
+
});
|
|
14084
|
+
}
|
|
14085
|
+
} else {
|
|
14086
|
+
checks.push({
|
|
14087
|
+
name: "Hook credential: no repair source",
|
|
14088
|
+
status: "fail",
|
|
14089
|
+
message: "No gad login credential available to rewrite hook-config.json",
|
|
14090
|
+
hint: "Run `gad login` first, then re-run with --enforce."
|
|
14091
|
+
});
|
|
14092
|
+
}
|
|
14093
|
+
}
|
|
14094
|
+
return { checks, repairs, verified };
|
|
14095
|
+
}
|
|
14096
|
+
|
|
13759
14097
|
// src/lib/mcp-doctor.ts
|
|
13760
14098
|
import { createHash as createHash2 } from "node:crypto";
|
|
13761
14099
|
|
|
@@ -13927,7 +14265,7 @@ function knownConfigLocations(home, cwd, platform = process.platform, appData) {
|
|
|
13927
14265
|
];
|
|
13928
14266
|
}
|
|
13929
14267
|
var GREAT_ARROW_NAME = /great[-_ ]?arrow|ai[-_]memory/i;
|
|
13930
|
-
function
|
|
14268
|
+
function asObject3(value) {
|
|
13931
14269
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
13932
14270
|
}
|
|
13933
14271
|
function isGreatArrowServer(name, entry) {
|
|
@@ -13948,13 +14286,13 @@ function classifySurface(entry, clientId, hooksInstalled) {
|
|
|
13948
14286
|
}
|
|
13949
14287
|
function serverMapsFromJson(parsed, serversKey) {
|
|
13950
14288
|
const maps = [];
|
|
13951
|
-
const top =
|
|
14289
|
+
const top = asObject3(parsed[serversKey]);
|
|
13952
14290
|
if (top) maps.push({ servers: top });
|
|
13953
|
-
const projects =
|
|
14291
|
+
const projects = asObject3(parsed.projects);
|
|
13954
14292
|
if (projects) {
|
|
13955
14293
|
for (const [dir, value] of Object.entries(projects)) {
|
|
13956
|
-
const section =
|
|
13957
|
-
const servers = section ?
|
|
14294
|
+
const section = asObject3(value);
|
|
14295
|
+
const servers = section ? asObject3(section[serversKey]) : null;
|
|
13958
14296
|
if (servers && Object.keys(servers).length > 0) {
|
|
13959
14297
|
maps.push({ servers, projectDir: dir });
|
|
13960
14298
|
}
|
|
@@ -13992,7 +14330,7 @@ function findGreatArrowEntries(location, content, options = {}) {
|
|
|
13992
14330
|
if (location.format === "toml") return findCodexEntries(location, content);
|
|
13993
14331
|
let parsed = null;
|
|
13994
14332
|
try {
|
|
13995
|
-
parsed =
|
|
14333
|
+
parsed = asObject3(JSON.parse(content));
|
|
13996
14334
|
} catch {
|
|
13997
14335
|
return [];
|
|
13998
14336
|
}
|
|
@@ -14004,7 +14342,7 @@ function findGreatArrowEntries(location, content, options = {}) {
|
|
|
14004
14342
|
serversKey
|
|
14005
14343
|
)) {
|
|
14006
14344
|
for (const [name, value] of Object.entries(servers)) {
|
|
14007
|
-
const entry =
|
|
14345
|
+
const entry = asObject3(value);
|
|
14008
14346
|
if (!entry) continue;
|
|
14009
14347
|
if (!isGreatArrowServer(name, entry)) continue;
|
|
14010
14348
|
entries.push({
|
|
@@ -14027,7 +14365,7 @@ function findGreatArrowEntries(location, content, options = {}) {
|
|
|
14027
14365
|
function hasGreatArrowHooks(settingsContent) {
|
|
14028
14366
|
let parsed = null;
|
|
14029
14367
|
try {
|
|
14030
|
-
parsed =
|
|
14368
|
+
parsed = asObject3(JSON.parse(settingsContent));
|
|
14031
14369
|
} catch {
|
|
14032
14370
|
return false;
|
|
14033
14371
|
}
|
|
@@ -14101,7 +14439,7 @@ function describeStoredCredential(raw) {
|
|
|
14101
14439
|
if (!trimmed) return null;
|
|
14102
14440
|
if (trimmed.startsWith("{")) {
|
|
14103
14441
|
try {
|
|
14104
|
-
const parsed =
|
|
14442
|
+
const parsed = asObject3(JSON.parse(trimmed));
|
|
14105
14443
|
const refreshToken = parsed?.refreshToken;
|
|
14106
14444
|
if (typeof refreshToken === "string" && refreshToken.length > 0) {
|
|
14107
14445
|
return {
|
|
@@ -14500,7 +14838,7 @@ async function checkMcp(auth) {
|
|
|
14500
14838
|
}
|
|
14501
14839
|
}
|
|
14502
14840
|
function defaultCodexConfigPath() {
|
|
14503
|
-
return
|
|
14841
|
+
return join8(homedir5(), ".codex", "config.toml");
|
|
14504
14842
|
}
|
|
14505
14843
|
function codexIssuesToChecks(issues) {
|
|
14506
14844
|
return issues.map((issue) => {
|
|
@@ -14538,16 +14876,16 @@ function codexCommand() {
|
|
|
14538
14876
|
});
|
|
14539
14877
|
const configPath = options.config ?? defaultCodexConfigPath();
|
|
14540
14878
|
const expectedRepoUrl = options.expectedRepo ?? detectGitOrigin2();
|
|
14541
|
-
let content =
|
|
14879
|
+
let content = existsSync5(configPath) ? await readFile5(configPath, "utf8") : "";
|
|
14542
14880
|
let backupPath = null;
|
|
14543
14881
|
let removedServers = [];
|
|
14544
14882
|
if (options.enforce) {
|
|
14545
14883
|
const apiUrl = extractCodexGreatArrowApiUrl(content) ?? (await resolveAuth()).apiUrl;
|
|
14546
14884
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
14547
|
-
await
|
|
14885
|
+
await mkdir5(dirname5(configPath), { recursive: true });
|
|
14548
14886
|
if (content.trim()) {
|
|
14549
14887
|
backupPath = `${configPath}.bak.${timestamp}`;
|
|
14550
|
-
await
|
|
14888
|
+
await copyFile2(configPath, backupPath);
|
|
14551
14889
|
}
|
|
14552
14890
|
content = upsertCodexServerBlock(content, apiUrl, expectedRepoUrl);
|
|
14553
14891
|
const preRepair = inspectCodexConfig(content, expectedRepoUrl);
|
|
@@ -14557,7 +14895,7 @@ function codexCommand() {
|
|
|
14557
14895
|
);
|
|
14558
14896
|
content = repaired.content;
|
|
14559
14897
|
removedServers = repaired.removedServers;
|
|
14560
|
-
await
|
|
14898
|
+
await writeFile4(configPath, content, "utf8");
|
|
14561
14899
|
}
|
|
14562
14900
|
const inspection = inspectCodexConfig(content, expectedRepoUrl);
|
|
14563
14901
|
const checks = codexIssuesToChecks(inspection.issues);
|
|
@@ -14639,11 +14977,11 @@ function claudeCommand() {
|
|
|
14639
14977
|
noColor: g.noColor
|
|
14640
14978
|
});
|
|
14641
14979
|
const home = homedir5();
|
|
14642
|
-
const gaDir =
|
|
14643
|
-
const userConfigPath =
|
|
14644
|
-
const settingsPath =
|
|
14645
|
-
const hookConfigPath =
|
|
14646
|
-
const glanceHookPath =
|
|
14980
|
+
const gaDir = join8(home, ".great-arrow");
|
|
14981
|
+
const userConfigPath = join8(home, ".claude.json");
|
|
14982
|
+
const settingsPath = join8(home, ".claude", "settings.json");
|
|
14983
|
+
const hookConfigPath = join8(gaDir, "hook-config.json");
|
|
14984
|
+
const glanceHookPath = join8(gaDir, "claude-glance-hook.mjs");
|
|
14647
14985
|
const checks = [];
|
|
14648
14986
|
const repairs = [];
|
|
14649
14987
|
const backups = [];
|
|
@@ -14660,13 +14998,13 @@ function claudeCommand() {
|
|
|
14660
14998
|
}
|
|
14661
14999
|
};
|
|
14662
15000
|
const backupAndWrite = async (path10, content) => {
|
|
14663
|
-
if (
|
|
15001
|
+
if (existsSync5(path10)) {
|
|
14664
15002
|
const backupPath = `${path10}.bak.${timestamp}`;
|
|
14665
|
-
await
|
|
15003
|
+
await copyFile2(path10, backupPath);
|
|
14666
15004
|
backups.push(backupPath);
|
|
14667
15005
|
}
|
|
14668
|
-
await
|
|
14669
|
-
await
|
|
15006
|
+
await mkdir5(dirname5(path10), { recursive: true });
|
|
15007
|
+
await writeFile4(path10, content, "utf8");
|
|
14670
15008
|
};
|
|
14671
15009
|
let auth = null;
|
|
14672
15010
|
try {
|
|
@@ -14675,8 +15013,8 @@ function claudeCommand() {
|
|
|
14675
15013
|
auth = null;
|
|
14676
15014
|
}
|
|
14677
15015
|
const apiUrl = auth?.apiUrl ?? "https://www.greatarrow.ai";
|
|
14678
|
-
if (
|
|
14679
|
-
let content = await
|
|
15016
|
+
if (existsSync5(userConfigPath)) {
|
|
15017
|
+
let content = await readFile5(userConfigPath, "utf8");
|
|
14680
15018
|
if (options.enforce) {
|
|
14681
15019
|
const repaired = repairClaudeUserConfig(content);
|
|
14682
15020
|
if (repaired.changed.length > 0) {
|
|
@@ -14696,7 +15034,7 @@ function claudeCommand() {
|
|
|
14696
15034
|
});
|
|
14697
15035
|
}
|
|
14698
15036
|
{
|
|
14699
|
-
let content =
|
|
15037
|
+
let content = existsSync5(settingsPath) ? await readFile5(settingsPath, "utf8") : "";
|
|
14700
15038
|
if (options.enforce) {
|
|
14701
15039
|
const glanceCommand = `node "${glanceHookPath}"`;
|
|
14702
15040
|
const repaired = repairClaudeHookSettings(content, glanceCommand);
|
|
@@ -14705,15 +15043,15 @@ function claudeCommand() {
|
|
|
14705
15043
|
content = repaired.content;
|
|
14706
15044
|
repairs.push(...repaired.changed.map((c) => `settings.json: ${c}`));
|
|
14707
15045
|
}
|
|
14708
|
-
if (!
|
|
15046
|
+
if (!existsSync5(glanceHookPath)) {
|
|
14709
15047
|
try {
|
|
14710
15048
|
const res = await fetch(
|
|
14711
15049
|
`${apiUrl}/api/install/claude-glance-hook`,
|
|
14712
15050
|
{ signal: AbortSignal.timeout(1e4) }
|
|
14713
15051
|
);
|
|
14714
15052
|
if (res.ok) {
|
|
14715
|
-
await
|
|
14716
|
-
await
|
|
15053
|
+
await mkdir5(gaDir, { recursive: true });
|
|
15054
|
+
await writeFile4(glanceHookPath, await res.text(), {
|
|
14717
15055
|
mode: 493
|
|
14718
15056
|
});
|
|
14719
15057
|
repairs.push(`downloaded ${glanceHookPath}`);
|
|
@@ -14734,7 +15072,7 @@ function claudeCommand() {
|
|
|
14734
15072
|
}
|
|
14735
15073
|
}
|
|
14736
15074
|
toChecks("Claude hooks", inspectClaudeHookSettings(content).issues);
|
|
14737
|
-
if (!options.enforce && !
|
|
15075
|
+
if (!options.enforce && !existsSync5(glanceHookPath)) {
|
|
14738
15076
|
checks.push({
|
|
14739
15077
|
name: "Glance hook script: missing",
|
|
14740
15078
|
status: "warn",
|
|
@@ -14744,96 +15082,31 @@ function claudeCommand() {
|
|
|
14744
15082
|
}
|
|
14745
15083
|
}
|
|
14746
15084
|
{
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
|
|
14751
|
-
|
|
14752
|
-
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
|
|
14757
|
-
|
|
14758
|
-
|
|
14759
|
-
|
|
14760
|
-
|
|
14761
|
-
|
|
14762
|
-
|
|
14763
|
-
}
|
|
14764
|
-
const probe = async (t, base) => {
|
|
14765
|
-
const res = await fetch(`${base}/api/mcp/ping`, {
|
|
14766
|
-
headers: { Authorization: `Bearer ${t}` },
|
|
14767
|
-
signal: AbortSignal.timeout(1e4)
|
|
14768
|
-
});
|
|
14769
|
-
return res.status;
|
|
15085
|
+
const credential = await ensureHookCredential({
|
|
15086
|
+
hookConfigPath,
|
|
15087
|
+
apiUrl,
|
|
15088
|
+
auth,
|
|
15089
|
+
repair: Boolean(options.enforce),
|
|
15090
|
+
write: backupAndWrite
|
|
15091
|
+
});
|
|
15092
|
+
checks.push(...credential.checks);
|
|
15093
|
+
repairs.push(...credential.repairs);
|
|
15094
|
+
}
|
|
15095
|
+
{
|
|
15096
|
+
const suggest = await suggestHookStatus(home);
|
|
15097
|
+
const check = {
|
|
15098
|
+
name: "Tool suggestions (opt-in)",
|
|
15099
|
+
status: "pass",
|
|
15100
|
+
message: suggest.enabled ? "ON \u2014 each prompt's text is sent to Great Arrow for tool suggestions" : "OFF"
|
|
14770
15101
|
};
|
|
14771
|
-
|
|
14772
|
-
|
|
14773
|
-
|
|
14774
|
-
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
checks.push({
|
|
14778
|
-
name: "Hook credential: token rejected",
|
|
14779
|
-
status: "fail",
|
|
14780
|
-
message: `The hook-config.json token was rejected (HTTP ${status}) \u2014 transcript capture and the session glance are silently failing`,
|
|
14781
|
-
hint: "Run with --enforce to rewrite it from your gad login credential."
|
|
14782
|
-
});
|
|
14783
|
-
} else if (!tokenOk) {
|
|
14784
|
-
checks.push({
|
|
14785
|
-
name: "Hook credential: probe inconclusive",
|
|
14786
|
-
status: "warn",
|
|
14787
|
-
message: `Ping returned HTTP ${status} \u2014 could not confirm the hook token`
|
|
14788
|
-
});
|
|
14789
|
-
} else {
|
|
14790
|
-
checks.push({
|
|
14791
|
-
name: "Hook credential: token valid",
|
|
14792
|
-
status: "pass",
|
|
14793
|
-
message: "hook-config.json token authenticates"
|
|
14794
|
-
});
|
|
14795
|
-
}
|
|
14796
|
-
} catch (e) {
|
|
14797
|
-
checks.push({
|
|
14798
|
-
name: "Hook credential: probe failed",
|
|
14799
|
-
status: "warn",
|
|
14800
|
-
message: `Could not reach ${url ?? apiUrl}: ${String(
|
|
14801
|
-
e instanceof Error ? e.message : e
|
|
14802
|
-
)}`
|
|
14803
|
-
});
|
|
14804
|
-
}
|
|
14805
|
-
}
|
|
14806
|
-
if (options.enforce && !tokenOk) {
|
|
14807
|
-
if (auth?.token) {
|
|
14808
|
-
await backupAndWrite(
|
|
14809
|
-
hookConfigPath,
|
|
14810
|
-
JSON.stringify({ url: apiUrl, token: auth.token }, null, 2)
|
|
14811
|
-
);
|
|
14812
|
-
repairs.push("hook-config.json: rewrote from gad credential");
|
|
14813
|
-
try {
|
|
14814
|
-
const status = await probe(auth.token, apiUrl);
|
|
14815
|
-
checks.push({
|
|
14816
|
-
name: "Hook credential: after repair",
|
|
14817
|
-
status: status >= 200 && status < 300 ? "pass" : "fail",
|
|
14818
|
-
message: status >= 200 && status < 300 ? "Rewritten token authenticates" : `Rewritten token still failing (HTTP ${status})`
|
|
14819
|
-
});
|
|
14820
|
-
} catch (err) {
|
|
14821
|
-
checks.push({
|
|
14822
|
-
name: "Hook credential: after repair",
|
|
14823
|
-
status: "warn",
|
|
14824
|
-
message: `Rewrote the token but could not re-probe it (${err instanceof Error ? err.message : String(err)})`,
|
|
14825
|
-
hint: "Re-run `gad doctor` once the network is reachable to confirm."
|
|
14826
|
-
});
|
|
14827
|
-
}
|
|
14828
|
-
} else {
|
|
14829
|
-
checks.push({
|
|
14830
|
-
name: "Hook credential: no repair source",
|
|
14831
|
-
status: "fail",
|
|
14832
|
-
message: "No gad login credential available to rewrite hook-config.json",
|
|
14833
|
-
hint: "Run `gad login` first, then re-run with --enforce."
|
|
14834
|
-
});
|
|
14835
|
-
}
|
|
15102
|
+
if (suggest.enabled && !suggest.credentialPresent) {
|
|
15103
|
+
check.status = "warn";
|
|
15104
|
+
check.message = "ON but no hook credential \u2014 the hook sends nothing";
|
|
15105
|
+
check.hint = "Run `gad login`, then `gad mcp suggest-tools on`.";
|
|
15106
|
+
} else if (!suggest.enabled) {
|
|
15107
|
+
check.hint = "Opt in with `gad mcp suggest-tools on`.";
|
|
14836
15108
|
}
|
|
15109
|
+
checks.push(check);
|
|
14837
15110
|
}
|
|
14838
15111
|
{
|
|
14839
15112
|
let root = command;
|
|
@@ -14851,23 +15124,23 @@ function claudeCommand() {
|
|
|
14851
15124
|
file: "/Library/Application Support/ClaudeCode/managed-settings.json"
|
|
14852
15125
|
},
|
|
14853
15126
|
{ scope: "user", file: settingsPath },
|
|
14854
|
-
{ scope: "project", file:
|
|
14855
|
-
{ scope: "local", file:
|
|
15127
|
+
{ scope: "project", file: join8(cwd, ".claude", "settings.json") },
|
|
15128
|
+
{ scope: "local", file: join8(cwd, ".claude", "settings.local.json") }
|
|
14856
15129
|
];
|
|
14857
15130
|
const configured = [];
|
|
14858
15131
|
for (const { scope, file } of scopes) {
|
|
14859
|
-
if (!
|
|
15132
|
+
if (!existsSync5(file)) continue;
|
|
14860
15133
|
configured.push(
|
|
14861
15134
|
...collectConfiguredHooks(
|
|
14862
15135
|
scope,
|
|
14863
15136
|
file,
|
|
14864
|
-
await
|
|
15137
|
+
await readFile5(file, "utf8")
|
|
14865
15138
|
)
|
|
14866
15139
|
);
|
|
14867
15140
|
}
|
|
14868
15141
|
const hookIssues = inspectConfiguredHooks(configured, {
|
|
14869
15142
|
knownGadVerbs,
|
|
14870
|
-
fileExists:
|
|
15143
|
+
fileExists: existsSync5
|
|
14871
15144
|
});
|
|
14872
15145
|
toChecks("Configured hooks", hookIssues);
|
|
14873
15146
|
if (configured.length > 0 && hookIssues.length === 0) {
|
|
@@ -14923,10 +15196,10 @@ function claudeCommand() {
|
|
|
14923
15196
|
function defaultJsonClientConfigs() {
|
|
14924
15197
|
const home = homedir5();
|
|
14925
15198
|
return [
|
|
14926
|
-
{ name: "Claude Code", path:
|
|
14927
|
-
{ name: "Cursor", path:
|
|
14928
|
-
{ name: "Kiro", path:
|
|
14929
|
-
{ name: "Gemini CLI", path:
|
|
15199
|
+
{ name: "Claude Code", path: join8(home, ".claude.json") },
|
|
15200
|
+
{ name: "Cursor", path: join8(home, ".cursor", "mcp.json") },
|
|
15201
|
+
{ name: "Kiro", path: join8(home, ".kiro", "settings", "mcp.json") },
|
|
15202
|
+
{ name: "Gemini CLI", path: join8(home, ".gemini", "settings.json") }
|
|
14930
15203
|
];
|
|
14931
15204
|
}
|
|
14932
15205
|
function clientsCommand() {
|
|
@@ -14952,8 +15225,8 @@ function clientsCommand() {
|
|
|
14952
15225
|
const checks = [];
|
|
14953
15226
|
const results = [];
|
|
14954
15227
|
for (const { name, path: path10 } of defaultJsonClientConfigs()) {
|
|
14955
|
-
if (!
|
|
14956
|
-
let content = await
|
|
15228
|
+
if (!existsSync5(path10)) continue;
|
|
15229
|
+
let content = await readFile5(path10, "utf8");
|
|
14957
15230
|
let backupPath = null;
|
|
14958
15231
|
let removedServers = [];
|
|
14959
15232
|
if (options.enforce) {
|
|
@@ -14961,14 +15234,14 @@ function clientsCommand() {
|
|
|
14961
15234
|
if (!pre.parseError && pre.staleRepoServers.length > 0) {
|
|
14962
15235
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
14963
15236
|
backupPath = `${path10}.bak.${timestamp}`;
|
|
14964
|
-
await
|
|
15237
|
+
await copyFile2(path10, backupPath);
|
|
14965
15238
|
const repaired = repairJsonMcpConfig(
|
|
14966
15239
|
content,
|
|
14967
15240
|
pre.staleRepoServers
|
|
14968
15241
|
);
|
|
14969
15242
|
content = repaired.content;
|
|
14970
15243
|
removedServers = repaired.removedServers;
|
|
14971
|
-
await
|
|
15244
|
+
await writeFile4(path10, content, "utf8");
|
|
14972
15245
|
}
|
|
14973
15246
|
}
|
|
14974
15247
|
const inspection = inspectJsonMcpConfig(content, expectedRepoUrl);
|
|
@@ -15463,6 +15736,107 @@ async function runStdioBridge(options) {
|
|
|
15463
15736
|
});
|
|
15464
15737
|
}
|
|
15465
15738
|
|
|
15739
|
+
// src/commands/suggest-tools.ts
|
|
15740
|
+
import { homedir as homedir6 } from "node:os";
|
|
15741
|
+
var HOOK_CREDENTIAL_FIX_LINE = "Tool suggestions are on, but the hook's Great Arrow token could not be confirmed \u2014 run `gad doctor claude --enforce` (after `gad login` if needed) to fix it.";
|
|
15742
|
+
async function enableSuggestTools(home, deps = {}) {
|
|
15743
|
+
const packagedScript = deps.packagedScript === void 0 ? packagedSuggestHookPath() : deps.packagedScript;
|
|
15744
|
+
if (!packagedScript) {
|
|
15745
|
+
throw validationError(
|
|
15746
|
+
"The tool-suggestion hook script is missing from this @great-arrow/cli install.",
|
|
15747
|
+
"Reinstall with `npm install -g @great-arrow/cli@latest`."
|
|
15748
|
+
);
|
|
15749
|
+
}
|
|
15750
|
+
await enableSuggestHook({ home, packagedScript });
|
|
15751
|
+
let auth = null;
|
|
15752
|
+
try {
|
|
15753
|
+
auth = await (deps.resolveAuth ?? resolveAuth)();
|
|
15754
|
+
} catch {
|
|
15755
|
+
auth = null;
|
|
15756
|
+
}
|
|
15757
|
+
const credential = await ensureHookCredential({
|
|
15758
|
+
hookConfigPath: suggestHookPaths(home).hookConfigPath,
|
|
15759
|
+
apiUrl: auth?.apiUrl ?? DEFAULT_API_URL,
|
|
15760
|
+
auth,
|
|
15761
|
+
repair: true,
|
|
15762
|
+
write: (path10, content) => atomicWrite(path10, content, 384),
|
|
15763
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
15764
|
+
});
|
|
15765
|
+
return { verified: credential.verified };
|
|
15766
|
+
}
|
|
15767
|
+
async function applySuggestToolsChoice(choice, home = homedir6(), deps = {}) {
|
|
15768
|
+
const out = deps.out ?? createFormatter({
|
|
15769
|
+
json: false,
|
|
15770
|
+
quiet: false,
|
|
15771
|
+
isTTY: isTTY(),
|
|
15772
|
+
noColor: false
|
|
15773
|
+
});
|
|
15774
|
+
let verified = true;
|
|
15775
|
+
const outcome = await runSuggestHookOptIn({
|
|
15776
|
+
choice,
|
|
15777
|
+
interactive: deps.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
15778
|
+
print: (line) => out.info(line),
|
|
15779
|
+
confirm: deps.confirm ?? confirm,
|
|
15780
|
+
enable: async () => {
|
|
15781
|
+
verified = (await enableSuggestTools(home, deps)).verified;
|
|
15782
|
+
},
|
|
15783
|
+
disable: () => disableSuggestHook({ home })
|
|
15784
|
+
});
|
|
15785
|
+
const { settingsPath } = suggestHookPaths(home);
|
|
15786
|
+
if (outcome === "enabled") {
|
|
15787
|
+
out.success(`Tool suggestions ON (registered in ${settingsPath}).`);
|
|
15788
|
+
if (!verified) out.warn(HOOK_CREDENTIAL_FIX_LINE);
|
|
15789
|
+
} else if (outcome === "disabled") {
|
|
15790
|
+
out.success("Tool suggestions OFF (our hook entry removed).");
|
|
15791
|
+
}
|
|
15792
|
+
return outcome;
|
|
15793
|
+
}
|
|
15794
|
+
function suggestToolsCommand() {
|
|
15795
|
+
return new Command("suggest-tools").description(
|
|
15796
|
+
"Opt-in Claude Code prompt hook that names Great Arrow tools which may help (sends each prompt's text)"
|
|
15797
|
+
).argument("[action]", "on | off | status", "status").option("--yes", "Accept the disclosure without an interactive prompt").option("--json", "Output status as JSON").action(
|
|
15798
|
+
async (action, options) => {
|
|
15799
|
+
const home = homedir6();
|
|
15800
|
+
if (action === "status") {
|
|
15801
|
+
const status = await suggestHookStatus(home);
|
|
15802
|
+
if (options.json) {
|
|
15803
|
+
process.stdout.write(JSON.stringify(status) + "\n");
|
|
15804
|
+
return;
|
|
15805
|
+
}
|
|
15806
|
+
process.stdout.write(
|
|
15807
|
+
`Tool suggestions: ${status.enabled ? "ON" : "OFF"}` + (status.enabled && !status.scriptPresent ? " (hook script missing \u2014 run `gad mcp suggest-tools on`)" : "") + (status.enabled && !status.credentialPresent ? " (no hook credential \u2014 run `gad login`, then `gad mcp suggest-tools on`)" : "") + "\n"
|
|
15808
|
+
);
|
|
15809
|
+
return;
|
|
15810
|
+
}
|
|
15811
|
+
if (action === "off") {
|
|
15812
|
+
await applySuggestToolsChoice(false, home);
|
|
15813
|
+
return;
|
|
15814
|
+
}
|
|
15815
|
+
if (action === "on") {
|
|
15816
|
+
const outcome = await applySuggestToolsChoice(
|
|
15817
|
+
options.yes ? true : void 0,
|
|
15818
|
+
home
|
|
15819
|
+
);
|
|
15820
|
+
if (outcome === "unchanged") {
|
|
15821
|
+
if (!(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
15822
|
+
process.stdout.write(`${SUGGEST_HOOK_DISCLOSURE}
|
|
15823
|
+
|
|
15824
|
+
`);
|
|
15825
|
+
}
|
|
15826
|
+
process.stdout.write(
|
|
15827
|
+
"Tool suggestions left OFF. To turn on without a prompt: `gad mcp suggest-tools on --yes`.\n"
|
|
15828
|
+
);
|
|
15829
|
+
}
|
|
15830
|
+
return;
|
|
15831
|
+
}
|
|
15832
|
+
throw validationError(
|
|
15833
|
+
`Unknown action "${action}".`,
|
|
15834
|
+
"Use one of: on, off, status."
|
|
15835
|
+
);
|
|
15836
|
+
}
|
|
15837
|
+
);
|
|
15838
|
+
}
|
|
15839
|
+
|
|
15466
15840
|
// src/commands/mcp.ts
|
|
15467
15841
|
var SUPPORTED_CLIENTS = [
|
|
15468
15842
|
"claude",
|
|
@@ -15572,33 +15946,33 @@ async function pathExists(path10) {
|
|
|
15572
15946
|
}
|
|
15573
15947
|
}
|
|
15574
15948
|
async function installCodexConfig(apiUrl, token, repoUrl) {
|
|
15575
|
-
const { homedir:
|
|
15576
|
-
const { join:
|
|
15577
|
-
const { chmod, copyFile:
|
|
15578
|
-
const codexDir =
|
|
15579
|
-
const configPath =
|
|
15580
|
-
const envPath =
|
|
15949
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
15950
|
+
const { join: join10 } = await import("node:path");
|
|
15951
|
+
const { chmod: chmod2, copyFile: copyFile3, mkdir: mkdir7, readFile: readFile7, writeFile: writeFile6 } = await import("node:fs/promises");
|
|
15952
|
+
const codexDir = join10(homedir8(), ".codex");
|
|
15953
|
+
const configPath = join10(codexDir, "config.toml");
|
|
15954
|
+
const envPath = join10(codexDir, ".env");
|
|
15581
15955
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
15582
|
-
await
|
|
15583
|
-
const existingConfig = await pathExists(configPath) ? await
|
|
15956
|
+
await mkdir7(codexDir, { recursive: true });
|
|
15957
|
+
const existingConfig = await pathExists(configPath) ? await readFile7(configPath, "utf8") : "";
|
|
15584
15958
|
if (existingConfig.trim()) {
|
|
15585
|
-
await
|
|
15959
|
+
await copyFile3(configPath, `${configPath}.bak.${timestamp}`);
|
|
15586
15960
|
}
|
|
15587
|
-
await
|
|
15961
|
+
await writeFile6(
|
|
15588
15962
|
configPath,
|
|
15589
15963
|
upsertCodexServerBlock(existingConfig, apiUrl, repoUrl),
|
|
15590
15964
|
"utf8"
|
|
15591
15965
|
);
|
|
15592
|
-
const existingEnv = await pathExists(envPath) ? await
|
|
15966
|
+
const existingEnv = await pathExists(envPath) ? await readFile7(envPath, "utf8") : "";
|
|
15593
15967
|
if (existingEnv.trim()) {
|
|
15594
|
-
await
|
|
15968
|
+
await copyFile3(envPath, `${envPath}.bak.${timestamp}`);
|
|
15595
15969
|
}
|
|
15596
|
-
await
|
|
15970
|
+
await writeFile6(
|
|
15597
15971
|
envPath,
|
|
15598
15972
|
upsertEnvValue(existingEnv, "GREAT_ARROW_TOKEN", token),
|
|
15599
15973
|
"utf8"
|
|
15600
15974
|
);
|
|
15601
|
-
await
|
|
15975
|
+
await chmod2(envPath, 384);
|
|
15602
15976
|
return { configPath, envPath };
|
|
15603
15977
|
}
|
|
15604
15978
|
var startCommand = new Command("start").description(
|
|
@@ -15825,6 +16199,12 @@ var installCommand = new Command("install").description("Generate MCP configurat
|
|
|
15825
16199
|
).option("--stdout", "Print config to stdout instead of writing file").option(
|
|
15826
16200
|
"--repo-url <url>",
|
|
15827
16201
|
"Repo URL to pin MCP requests to (defaults to current git origin)"
|
|
16202
|
+
).option(
|
|
16203
|
+
"--suggest-tools",
|
|
16204
|
+
"claude only: turn on the opt-in prompt hook that sends each prompt's text to Great Arrow for tool suggestions"
|
|
16205
|
+
).option(
|
|
16206
|
+
"--no-suggest-tools",
|
|
16207
|
+
"claude only: turn the tool-suggestion prompt hook off (removes only its entry)"
|
|
15828
16208
|
).action(
|
|
15829
16209
|
async (clientName, options) => {
|
|
15830
16210
|
const fmt = createFormatter({
|
|
@@ -15876,15 +16256,15 @@ var installCommand = new Command("install").description("Generate MCP configurat
|
|
|
15876
16256
|
process.stdout.write(configJson2 + "\n");
|
|
15877
16257
|
return;
|
|
15878
16258
|
}
|
|
15879
|
-
const { homedir:
|
|
15880
|
-
const { join:
|
|
15881
|
-
const { mkdir:
|
|
15882
|
-
const geminiDir =
|
|
15883
|
-
const mcpPath =
|
|
15884
|
-
await
|
|
16259
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
16260
|
+
const { join: join11 } = await import("node:path");
|
|
16261
|
+
const { mkdir: mkdir7, readFile: readFile7, writeFile: writeFile7 } = await import("node:fs/promises");
|
|
16262
|
+
const geminiDir = join11(homedir8(), ".gemini");
|
|
16263
|
+
const mcpPath = join11(geminiDir, "settings.json");
|
|
16264
|
+
await mkdir7(geminiDir, { recursive: true });
|
|
15885
16265
|
let existingConfig = {};
|
|
15886
16266
|
try {
|
|
15887
|
-
const content = await
|
|
16267
|
+
const content = await readFile7(mcpPath, "utf-8");
|
|
15888
16268
|
existingConfig = JSON.parse(content);
|
|
15889
16269
|
} catch {
|
|
15890
16270
|
existingConfig = {};
|
|
@@ -15896,20 +16276,20 @@ var installCommand = new Command("install").description("Generate MCP configurat
|
|
|
15896
16276
|
...config2.mcpServers
|
|
15897
16277
|
}
|
|
15898
16278
|
};
|
|
15899
|
-
await
|
|
16279
|
+
await writeFile7(
|
|
15900
16280
|
mcpPath,
|
|
15901
16281
|
JSON.stringify(existingConfig, null, 2) + "\n",
|
|
15902
16282
|
"utf-8"
|
|
15903
16283
|
);
|
|
15904
16284
|
fmt.success(`Wrote gemini MCP config to ${mcpPath}`);
|
|
15905
|
-
const agyMcpDir =
|
|
16285
|
+
const agyMcpDir = join11(
|
|
15906
16286
|
geminiDir,
|
|
15907
16287
|
"antigravity-cli",
|
|
15908
16288
|
"mcp",
|
|
15909
16289
|
MCP_SERVER_NAME
|
|
15910
16290
|
);
|
|
15911
|
-
await
|
|
15912
|
-
const instructionsPath =
|
|
16291
|
+
await mkdir7(agyMcpDir, { recursive: true });
|
|
16292
|
+
const instructionsPath = join11(agyMcpDir, "instructions.md");
|
|
15913
16293
|
const instructionsContent = `You are connected to greatarrow.ai - the user's private workspace memory + integrations brain.
|
|
15914
16294
|
|
|
15915
16295
|
CRITICAL DIRECTIVE (MANDATORY STANDARD):
|
|
@@ -15925,7 +16305,7 @@ THREE-LAYER LIVE TASK TRACKING:
|
|
|
15925
16305
|
- Maintain client-native harness plans, Great Arrow Kanban cards (\`todo_create\`, \`todo_update\`), and resume states.
|
|
15926
16306
|
|
|
15927
16307
|
Always assume full autonomy to leverage these tools to their maximum extent.`;
|
|
15928
|
-
await
|
|
16308
|
+
await writeFile7(instructionsPath, instructionsContent, "utf-8");
|
|
15929
16309
|
fmt.success(`Wrote gemini MCP instructions to ${instructionsPath}`);
|
|
15930
16310
|
return;
|
|
15931
16311
|
}
|
|
@@ -15940,25 +16320,28 @@ Always assume full autonomy to leverage these tools to their maximum extent.`;
|
|
|
15940
16320
|
process.stdout.write(configJson + "\n");
|
|
15941
16321
|
return;
|
|
15942
16322
|
}
|
|
15943
|
-
const { writeFile:
|
|
15944
|
-
const { join:
|
|
15945
|
-
const outputPath =
|
|
15946
|
-
await
|
|
16323
|
+
const { writeFile: writeFile6 } = await import("node:fs/promises");
|
|
16324
|
+
const { join: join10 } = await import("node:path");
|
|
16325
|
+
const outputPath = join10(process.cwd(), ".mcp.json");
|
|
16326
|
+
await writeFile6(outputPath, configJson + "\n", { mode: 384 });
|
|
15947
16327
|
fmt.success(`Wrote ${clientName} MCP config to .mcp.json`);
|
|
15948
16328
|
if (await mcpJsonAtRiskOfCommit(process.cwd())) {
|
|
15949
16329
|
fmt.warn(
|
|
15950
16330
|
".mcp.json contains an API token and is NOT gitignored here \u2014 add '.mcp.json' to .gitignore before committing."
|
|
15951
16331
|
);
|
|
15952
16332
|
}
|
|
16333
|
+
if (clientName === "claude") {
|
|
16334
|
+
await applySuggestToolsChoice(options.suggestTools);
|
|
16335
|
+
}
|
|
15953
16336
|
}
|
|
15954
16337
|
);
|
|
15955
16338
|
var KEYCHAIN_SERVICE = "GreatArrowDigital";
|
|
15956
16339
|
var KEYCHAIN_ACCOUNT = "api-token";
|
|
15957
16340
|
var OAUTH_CRED_FILENAME = "oauth-credential.json";
|
|
15958
16341
|
async function readIfExists(path10) {
|
|
15959
|
-
const { readFile:
|
|
16342
|
+
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
15960
16343
|
try {
|
|
15961
|
-
return await
|
|
16344
|
+
return await readFile7(path10, "utf8");
|
|
15962
16345
|
} catch {
|
|
15963
16346
|
return null;
|
|
15964
16347
|
}
|
|
@@ -15973,13 +16356,13 @@ async function readKeychainOnly() {
|
|
|
15973
16356
|
}
|
|
15974
16357
|
}
|
|
15975
16358
|
async function resolveProxyScript(override) {
|
|
15976
|
-
const { homedir:
|
|
15977
|
-
const { join:
|
|
16359
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
16360
|
+
const { join: join10 } = await import("node:path");
|
|
15978
16361
|
const candidates = [
|
|
15979
16362
|
override,
|
|
15980
16363
|
process.env.GAD_PROXY_SCRIPT,
|
|
15981
|
-
|
|
15982
|
-
|
|
16364
|
+
join10(homedir8(), ".great-arrow", "mcp-server.mjs"),
|
|
16365
|
+
join10(process.cwd(), "scripts", "mcp-server.mjs")
|
|
15983
16366
|
].filter((c) => Boolean(c));
|
|
15984
16367
|
for (const candidate of candidates) {
|
|
15985
16368
|
if (await pathExists(candidate)) return candidate;
|
|
@@ -16003,13 +16386,13 @@ async function runProxyDiagnostics(scriptPath) {
|
|
|
16003
16386
|
}
|
|
16004
16387
|
}
|
|
16005
16388
|
async function findLegacyStores(cwd) {
|
|
16006
|
-
const { join:
|
|
16389
|
+
const { join: join10 } = await import("node:path");
|
|
16007
16390
|
const { readdir } = await import("node:fs/promises");
|
|
16008
16391
|
const dirs = /* @__PURE__ */ new Set([cwd]);
|
|
16009
16392
|
try {
|
|
16010
|
-
const worktreeRoot =
|
|
16393
|
+
const worktreeRoot = join10(cwd, ".claude", "worktrees");
|
|
16011
16394
|
for (const entry of await readdir(worktreeRoot, { withFileTypes: true })) {
|
|
16012
|
-
if (entry.isDirectory()) dirs.add(
|
|
16395
|
+
if (entry.isDirectory()) dirs.add(join10(worktreeRoot, entry.name));
|
|
16013
16396
|
}
|
|
16014
16397
|
} catch (err) {
|
|
16015
16398
|
debugSwallow("mcp.findLegacyStores(worktrees)", err);
|
|
@@ -16031,18 +16414,18 @@ async function findLegacyStores(cwd) {
|
|
|
16031
16414
|
}
|
|
16032
16415
|
const stores = [];
|
|
16033
16416
|
for (const dir of dirs) {
|
|
16034
|
-
const path10 =
|
|
16417
|
+
const path10 = join10(dir, ".mcp", OAUTH_CRED_FILENAME);
|
|
16035
16418
|
const raw = await readIfExists(path10);
|
|
16036
16419
|
if (raw !== null) stores.push({ path: path10, raw });
|
|
16037
16420
|
}
|
|
16038
16421
|
return stores;
|
|
16039
16422
|
}
|
|
16040
16423
|
async function scanConnections() {
|
|
16041
|
-
const { homedir:
|
|
16042
|
-
const { join:
|
|
16043
|
-
const home =
|
|
16424
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
16425
|
+
const { join: join10 } = await import("node:path");
|
|
16426
|
+
const home = homedir8();
|
|
16044
16427
|
const hookSettings = await readIfExists(
|
|
16045
|
-
|
|
16428
|
+
join10(home, ".claude", "settings.json")
|
|
16046
16429
|
);
|
|
16047
16430
|
const hooksInstalled = hookSettings ? hasGreatArrowHooks(hookSettings) : false;
|
|
16048
16431
|
const entries = [];
|
|
@@ -16075,29 +16458,29 @@ async function probeEndpoint() {
|
|
|
16075
16458
|
}
|
|
16076
16459
|
}
|
|
16077
16460
|
async function applyMigration(plan) {
|
|
16078
|
-
const { homedir:
|
|
16079
|
-
const { join:
|
|
16080
|
-
const { mkdir:
|
|
16461
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
16462
|
+
const { join: join10 } = await import("node:path");
|
|
16463
|
+
const { mkdir: mkdir7, rename: rename2, writeFile: writeFile6 } = await import("node:fs/promises");
|
|
16081
16464
|
const applied = [];
|
|
16082
|
-
const dir =
|
|
16083
|
-
await
|
|
16465
|
+
const dir = join10(homedir8(), ".great-arrow");
|
|
16466
|
+
await mkdir7(dir, { recursive: true, mode: 448 });
|
|
16084
16467
|
if (plan.seedFrom) {
|
|
16085
16468
|
const raw = await readIfExists(plan.seedFrom);
|
|
16086
16469
|
if (raw !== null) {
|
|
16087
|
-
const target =
|
|
16470
|
+
const target = join10(dir, OAUTH_CRED_FILENAME);
|
|
16088
16471
|
const tmp = `${target}.tmp`;
|
|
16089
|
-
await
|
|
16090
|
-
await
|
|
16472
|
+
await writeFile6(tmp, raw, { mode: 384 });
|
|
16473
|
+
await rename2(tmp, target);
|
|
16091
16474
|
applied.push(`Copied ${plan.seedFrom} \u2192 ${target}`);
|
|
16092
16475
|
}
|
|
16093
16476
|
}
|
|
16094
16477
|
if (plan.syncKeychainToFile) {
|
|
16095
16478
|
const raw = await readKeychainOnly();
|
|
16096
16479
|
if (raw) {
|
|
16097
|
-
const target =
|
|
16480
|
+
const target = join10(dir, "credential");
|
|
16098
16481
|
const tmp = `${target}.tmp`;
|
|
16099
|
-
await
|
|
16100
|
-
await
|
|
16482
|
+
await writeFile6(tmp, raw, { mode: 384 });
|
|
16483
|
+
await rename2(tmp, target);
|
|
16101
16484
|
applied.push(`Wrote the keychain credential to ${target}`);
|
|
16102
16485
|
}
|
|
16103
16486
|
}
|
|
@@ -16120,19 +16503,19 @@ var doctorSubcommand = new Command("doctor").description(
|
|
|
16120
16503
|
noColor: false
|
|
16121
16504
|
});
|
|
16122
16505
|
const c = createColor(jsonFlag);
|
|
16123
|
-
const { homedir:
|
|
16124
|
-
const { join:
|
|
16125
|
-
const home =
|
|
16506
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
16507
|
+
const { join: join10 } = await import("node:path");
|
|
16508
|
+
const home = homedir8();
|
|
16126
16509
|
const proxyPath = await resolveProxyScript(options.proxy);
|
|
16127
16510
|
const diagnostics = proxyPath ? await runProxyDiagnostics(proxyPath) : null;
|
|
16128
16511
|
const entries = await scanConnections();
|
|
16129
16512
|
const duplicates = detectDuplicates(entries);
|
|
16130
16513
|
const keychain = await readKeychainOnly();
|
|
16131
16514
|
const credentialFile = await readIfExists(
|
|
16132
|
-
|
|
16515
|
+
join10(home, ".great-arrow", "credential")
|
|
16133
16516
|
);
|
|
16134
16517
|
const userStore = await readIfExists(
|
|
16135
|
-
|
|
16518
|
+
join10(home, ".great-arrow", OAUTH_CRED_FILENAME)
|
|
16136
16519
|
);
|
|
16137
16520
|
const legacyStores = await findLegacyStores(process.cwd());
|
|
16138
16521
|
const findings = [
|
|
@@ -16235,7 +16618,7 @@ ${c.dim("Credential migration")}
|
|
|
16235
16618
|
}
|
|
16236
16619
|
}
|
|
16237
16620
|
);
|
|
16238
|
-
var mcpCommand = new Command("mcp").description("Manage and diagnose the MCP server connection").addCommand(startCommand).addCommand(statusCommand).addCommand(logsCommand).addCommand(testCommand).addCommand(doctorSubcommand).addCommand(installCommand);
|
|
16621
|
+
var mcpCommand = new Command("mcp").description("Manage and diagnose the MCP server connection").addCommand(startCommand).addCommand(statusCommand).addCommand(logsCommand).addCommand(testCommand).addCommand(doctorSubcommand).addCommand(installCommand).addCommand(suggestToolsCommand());
|
|
16239
16622
|
var mcp_default = mcpCommand;
|
|
16240
16623
|
|
|
16241
16624
|
// src/commands/smoke.ts
|
|
@@ -16556,9 +16939,9 @@ function askCommand() {
|
|
|
16556
16939
|
}
|
|
16557
16940
|
|
|
16558
16941
|
// src/lib/glance-footer.ts
|
|
16559
|
-
import { readFile as
|
|
16560
|
-
import { homedir as
|
|
16561
|
-
import { join as
|
|
16942
|
+
import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir6 } from "node:fs/promises";
|
|
16943
|
+
import { homedir as homedir7 } from "node:os";
|
|
16944
|
+
import { join as join9, dirname as dirname6 } from "node:path";
|
|
16562
16945
|
|
|
16563
16946
|
// src/lib/seen.ts
|
|
16564
16947
|
var SEEN_TIMEOUT_MS = 1e3;
|
|
@@ -16945,11 +17328,11 @@ function shouldRequestTip(input) {
|
|
|
16945
17328
|
return now - state.lastTipAt >= TIP_THROTTLE_INTERVAL_MS;
|
|
16946
17329
|
}
|
|
16947
17330
|
function getGlanceStatePath() {
|
|
16948
|
-
return
|
|
17331
|
+
return join9(homedir7(), ".gad", "glance-state.json");
|
|
16949
17332
|
}
|
|
16950
17333
|
async function loadGlanceState() {
|
|
16951
17334
|
try {
|
|
16952
|
-
const content = await
|
|
17335
|
+
const content = await readFile6(getGlanceStatePath(), "utf-8");
|
|
16953
17336
|
const parsed = JSON.parse(content);
|
|
16954
17337
|
if (typeof parsed === "object" && parsed !== null) {
|
|
16955
17338
|
return parsed;
|
|
@@ -16961,8 +17344,8 @@ async function loadGlanceState() {
|
|
|
16961
17344
|
}
|
|
16962
17345
|
async function saveGlanceState(state) {
|
|
16963
17346
|
const path10 = getGlanceStatePath();
|
|
16964
|
-
await
|
|
16965
|
-
await
|
|
17347
|
+
await mkdir6(dirname6(path10), { recursive: true });
|
|
17348
|
+
await writeFile5(path10, JSON.stringify(state), "utf-8");
|
|
16966
17349
|
}
|
|
16967
17350
|
function glanceItemUrl(href, apiUrl) {
|
|
16968
17351
|
if (!href) return null;
|
|
@@ -18844,7 +19227,7 @@ function completionsCommand() {
|
|
|
18844
19227
|
// node_modules/.pnpm/update-notifier@7.3.1/node_modules/update-notifier/update-notifier.js
|
|
18845
19228
|
import process11 from "node:process";
|
|
18846
19229
|
import { spawn } from "node:child_process";
|
|
18847
|
-
import { fileURLToPath as
|
|
19230
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
18848
19231
|
import path9 from "node:path";
|
|
18849
19232
|
import { format } from "node:util";
|
|
18850
19233
|
|
|
@@ -20995,7 +21378,7 @@ var isNpmOrYarn = isNpm || isYarn;
|
|
|
20995
21378
|
// node_modules/.pnpm/is-installed-globally@1.0.0/node_modules/is-installed-globally/index.js
|
|
20996
21379
|
import fs5 from "node:fs";
|
|
20997
21380
|
import path8 from "node:path";
|
|
20998
|
-
import { fileURLToPath } from "node:url";
|
|
21381
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
20999
21382
|
|
|
21000
21383
|
// node_modules/.pnpm/global-directory@4.0.1/node_modules/global-directory/index.js
|
|
21001
21384
|
var import_ini = __toESM(require_ini2(), 1);
|
|
@@ -21099,7 +21482,7 @@ function isPathInside(childPath, parentPath) {
|
|
|
21099
21482
|
}
|
|
21100
21483
|
|
|
21101
21484
|
// node_modules/.pnpm/is-installed-globally@1.0.0/node_modules/is-installed-globally/index.js
|
|
21102
|
-
var __dirname2 = path8.dirname(
|
|
21485
|
+
var __dirname2 = path8.dirname(fileURLToPath2(import.meta.url));
|
|
21103
21486
|
var isInstalledGlobally = (() => {
|
|
21104
21487
|
try {
|
|
21105
21488
|
return isPathInside(__dirname2, global_directory_default.yarn.packages) || isPathInside(__dirname2, fs5.realpathSync(global_directory_default.npm.packages));
|
|
@@ -22081,7 +22464,7 @@ function pupa(template, data, { ignoreMissing = false, transform = ({ value }) =
|
|
|
22081
22464
|
}
|
|
22082
22465
|
|
|
22083
22466
|
// node_modules/.pnpm/update-notifier@7.3.1/node_modules/update-notifier/update-notifier.js
|
|
22084
|
-
var __dirname3 = path9.dirname(
|
|
22467
|
+
var __dirname3 = path9.dirname(fileURLToPath3(import.meta.url));
|
|
22085
22468
|
var ONE_DAY = 1e3 * 60 * 60 * 24;
|
|
22086
22469
|
var UpdateNotifier = class {
|
|
22087
22470
|
// Public
|