@great-arrow/cli 0.1.48 → 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 +559 -235
- 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";
|
|
@@ -13025,10 +13025,10 @@ function guardrailsCommand() {
|
|
|
13025
13025
|
}
|
|
13026
13026
|
|
|
13027
13027
|
// src/commands/doctor.ts
|
|
13028
|
-
import { existsSync as
|
|
13029
|
-
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";
|
|
13030
13030
|
import { homedir as homedir5 } from "node:os";
|
|
13031
|
-
import { dirname as
|
|
13031
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
13032
13032
|
|
|
13033
13033
|
// src/lib/codex-config.ts
|
|
13034
13034
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
@@ -13815,6 +13815,285 @@ function inspectConfiguredHooks(hooks, deps) {
|
|
|
13815
13815
|
return issues;
|
|
13816
13816
|
}
|
|
13817
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
|
+
|
|
13818
14097
|
// src/lib/mcp-doctor.ts
|
|
13819
14098
|
import { createHash as createHash2 } from "node:crypto";
|
|
13820
14099
|
|
|
@@ -13986,7 +14265,7 @@ function knownConfigLocations(home, cwd, platform = process.platform, appData) {
|
|
|
13986
14265
|
];
|
|
13987
14266
|
}
|
|
13988
14267
|
var GREAT_ARROW_NAME = /great[-_ ]?arrow|ai[-_]memory/i;
|
|
13989
|
-
function
|
|
14268
|
+
function asObject3(value) {
|
|
13990
14269
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
13991
14270
|
}
|
|
13992
14271
|
function isGreatArrowServer(name, entry) {
|
|
@@ -14007,13 +14286,13 @@ function classifySurface(entry, clientId, hooksInstalled) {
|
|
|
14007
14286
|
}
|
|
14008
14287
|
function serverMapsFromJson(parsed, serversKey) {
|
|
14009
14288
|
const maps = [];
|
|
14010
|
-
const top =
|
|
14289
|
+
const top = asObject3(parsed[serversKey]);
|
|
14011
14290
|
if (top) maps.push({ servers: top });
|
|
14012
|
-
const projects =
|
|
14291
|
+
const projects = asObject3(parsed.projects);
|
|
14013
14292
|
if (projects) {
|
|
14014
14293
|
for (const [dir, value] of Object.entries(projects)) {
|
|
14015
|
-
const section =
|
|
14016
|
-
const servers = section ?
|
|
14294
|
+
const section = asObject3(value);
|
|
14295
|
+
const servers = section ? asObject3(section[serversKey]) : null;
|
|
14017
14296
|
if (servers && Object.keys(servers).length > 0) {
|
|
14018
14297
|
maps.push({ servers, projectDir: dir });
|
|
14019
14298
|
}
|
|
@@ -14051,7 +14330,7 @@ function findGreatArrowEntries(location, content, options = {}) {
|
|
|
14051
14330
|
if (location.format === "toml") return findCodexEntries(location, content);
|
|
14052
14331
|
let parsed = null;
|
|
14053
14332
|
try {
|
|
14054
|
-
parsed =
|
|
14333
|
+
parsed = asObject3(JSON.parse(content));
|
|
14055
14334
|
} catch {
|
|
14056
14335
|
return [];
|
|
14057
14336
|
}
|
|
@@ -14063,7 +14342,7 @@ function findGreatArrowEntries(location, content, options = {}) {
|
|
|
14063
14342
|
serversKey
|
|
14064
14343
|
)) {
|
|
14065
14344
|
for (const [name, value] of Object.entries(servers)) {
|
|
14066
|
-
const entry =
|
|
14345
|
+
const entry = asObject3(value);
|
|
14067
14346
|
if (!entry) continue;
|
|
14068
14347
|
if (!isGreatArrowServer(name, entry)) continue;
|
|
14069
14348
|
entries.push({
|
|
@@ -14086,7 +14365,7 @@ function findGreatArrowEntries(location, content, options = {}) {
|
|
|
14086
14365
|
function hasGreatArrowHooks(settingsContent) {
|
|
14087
14366
|
let parsed = null;
|
|
14088
14367
|
try {
|
|
14089
|
-
parsed =
|
|
14368
|
+
parsed = asObject3(JSON.parse(settingsContent));
|
|
14090
14369
|
} catch {
|
|
14091
14370
|
return false;
|
|
14092
14371
|
}
|
|
@@ -14160,7 +14439,7 @@ function describeStoredCredential(raw) {
|
|
|
14160
14439
|
if (!trimmed) return null;
|
|
14161
14440
|
if (trimmed.startsWith("{")) {
|
|
14162
14441
|
try {
|
|
14163
|
-
const parsed =
|
|
14442
|
+
const parsed = asObject3(JSON.parse(trimmed));
|
|
14164
14443
|
const refreshToken = parsed?.refreshToken;
|
|
14165
14444
|
if (typeof refreshToken === "string" && refreshToken.length > 0) {
|
|
14166
14445
|
return {
|
|
@@ -14559,7 +14838,7 @@ async function checkMcp(auth) {
|
|
|
14559
14838
|
}
|
|
14560
14839
|
}
|
|
14561
14840
|
function defaultCodexConfigPath() {
|
|
14562
|
-
return
|
|
14841
|
+
return join8(homedir5(), ".codex", "config.toml");
|
|
14563
14842
|
}
|
|
14564
14843
|
function codexIssuesToChecks(issues) {
|
|
14565
14844
|
return issues.map((issue) => {
|
|
@@ -14597,16 +14876,16 @@ function codexCommand() {
|
|
|
14597
14876
|
});
|
|
14598
14877
|
const configPath = options.config ?? defaultCodexConfigPath();
|
|
14599
14878
|
const expectedRepoUrl = options.expectedRepo ?? detectGitOrigin2();
|
|
14600
|
-
let content =
|
|
14879
|
+
let content = existsSync5(configPath) ? await readFile5(configPath, "utf8") : "";
|
|
14601
14880
|
let backupPath = null;
|
|
14602
14881
|
let removedServers = [];
|
|
14603
14882
|
if (options.enforce) {
|
|
14604
14883
|
const apiUrl = extractCodexGreatArrowApiUrl(content) ?? (await resolveAuth()).apiUrl;
|
|
14605
14884
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
14606
|
-
await
|
|
14885
|
+
await mkdir5(dirname5(configPath), { recursive: true });
|
|
14607
14886
|
if (content.trim()) {
|
|
14608
14887
|
backupPath = `${configPath}.bak.${timestamp}`;
|
|
14609
|
-
await
|
|
14888
|
+
await copyFile2(configPath, backupPath);
|
|
14610
14889
|
}
|
|
14611
14890
|
content = upsertCodexServerBlock(content, apiUrl, expectedRepoUrl);
|
|
14612
14891
|
const preRepair = inspectCodexConfig(content, expectedRepoUrl);
|
|
@@ -14616,7 +14895,7 @@ function codexCommand() {
|
|
|
14616
14895
|
);
|
|
14617
14896
|
content = repaired.content;
|
|
14618
14897
|
removedServers = repaired.removedServers;
|
|
14619
|
-
await
|
|
14898
|
+
await writeFile4(configPath, content, "utf8");
|
|
14620
14899
|
}
|
|
14621
14900
|
const inspection = inspectCodexConfig(content, expectedRepoUrl);
|
|
14622
14901
|
const checks = codexIssuesToChecks(inspection.issues);
|
|
@@ -14698,11 +14977,11 @@ function claudeCommand() {
|
|
|
14698
14977
|
noColor: g.noColor
|
|
14699
14978
|
});
|
|
14700
14979
|
const home = homedir5();
|
|
14701
|
-
const gaDir =
|
|
14702
|
-
const userConfigPath =
|
|
14703
|
-
const settingsPath =
|
|
14704
|
-
const hookConfigPath =
|
|
14705
|
-
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");
|
|
14706
14985
|
const checks = [];
|
|
14707
14986
|
const repairs = [];
|
|
14708
14987
|
const backups = [];
|
|
@@ -14719,13 +14998,13 @@ function claudeCommand() {
|
|
|
14719
14998
|
}
|
|
14720
14999
|
};
|
|
14721
15000
|
const backupAndWrite = async (path10, content) => {
|
|
14722
|
-
if (
|
|
15001
|
+
if (existsSync5(path10)) {
|
|
14723
15002
|
const backupPath = `${path10}.bak.${timestamp}`;
|
|
14724
|
-
await
|
|
15003
|
+
await copyFile2(path10, backupPath);
|
|
14725
15004
|
backups.push(backupPath);
|
|
14726
15005
|
}
|
|
14727
|
-
await
|
|
14728
|
-
await
|
|
15006
|
+
await mkdir5(dirname5(path10), { recursive: true });
|
|
15007
|
+
await writeFile4(path10, content, "utf8");
|
|
14729
15008
|
};
|
|
14730
15009
|
let auth = null;
|
|
14731
15010
|
try {
|
|
@@ -14734,8 +15013,8 @@ function claudeCommand() {
|
|
|
14734
15013
|
auth = null;
|
|
14735
15014
|
}
|
|
14736
15015
|
const apiUrl = auth?.apiUrl ?? "https://www.greatarrow.ai";
|
|
14737
|
-
if (
|
|
14738
|
-
let content = await
|
|
15016
|
+
if (existsSync5(userConfigPath)) {
|
|
15017
|
+
let content = await readFile5(userConfigPath, "utf8");
|
|
14739
15018
|
if (options.enforce) {
|
|
14740
15019
|
const repaired = repairClaudeUserConfig(content);
|
|
14741
15020
|
if (repaired.changed.length > 0) {
|
|
@@ -14755,7 +15034,7 @@ function claudeCommand() {
|
|
|
14755
15034
|
});
|
|
14756
15035
|
}
|
|
14757
15036
|
{
|
|
14758
|
-
let content =
|
|
15037
|
+
let content = existsSync5(settingsPath) ? await readFile5(settingsPath, "utf8") : "";
|
|
14759
15038
|
if (options.enforce) {
|
|
14760
15039
|
const glanceCommand = `node "${glanceHookPath}"`;
|
|
14761
15040
|
const repaired = repairClaudeHookSettings(content, glanceCommand);
|
|
@@ -14764,15 +15043,15 @@ function claudeCommand() {
|
|
|
14764
15043
|
content = repaired.content;
|
|
14765
15044
|
repairs.push(...repaired.changed.map((c) => `settings.json: ${c}`));
|
|
14766
15045
|
}
|
|
14767
|
-
if (!
|
|
15046
|
+
if (!existsSync5(glanceHookPath)) {
|
|
14768
15047
|
try {
|
|
14769
15048
|
const res = await fetch(
|
|
14770
15049
|
`${apiUrl}/api/install/claude-glance-hook`,
|
|
14771
15050
|
{ signal: AbortSignal.timeout(1e4) }
|
|
14772
15051
|
);
|
|
14773
15052
|
if (res.ok) {
|
|
14774
|
-
await
|
|
14775
|
-
await
|
|
15053
|
+
await mkdir5(gaDir, { recursive: true });
|
|
15054
|
+
await writeFile4(glanceHookPath, await res.text(), {
|
|
14776
15055
|
mode: 493
|
|
14777
15056
|
});
|
|
14778
15057
|
repairs.push(`downloaded ${glanceHookPath}`);
|
|
@@ -14793,7 +15072,7 @@ function claudeCommand() {
|
|
|
14793
15072
|
}
|
|
14794
15073
|
}
|
|
14795
15074
|
toChecks("Claude hooks", inspectClaudeHookSettings(content).issues);
|
|
14796
|
-
if (!options.enforce && !
|
|
15075
|
+
if (!options.enforce && !existsSync5(glanceHookPath)) {
|
|
14797
15076
|
checks.push({
|
|
14798
15077
|
name: "Glance hook script: missing",
|
|
14799
15078
|
status: "warn",
|
|
@@ -14803,96 +15082,31 @@ function claudeCommand() {
|
|
|
14803
15082
|
}
|
|
14804
15083
|
}
|
|
14805
15084
|
{
|
|
14806
|
-
|
|
14807
|
-
|
|
14808
|
-
|
|
14809
|
-
|
|
14810
|
-
|
|
14811
|
-
|
|
14812
|
-
|
|
14813
|
-
|
|
14814
|
-
|
|
14815
|
-
|
|
14816
|
-
|
|
14817
|
-
|
|
14818
|
-
|
|
14819
|
-
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
}
|
|
14823
|
-
const probe = async (t, base) => {
|
|
14824
|
-
const res = await fetch(`${base}/api/mcp/ping`, {
|
|
14825
|
-
headers: { Authorization: `Bearer ${t}` },
|
|
14826
|
-
signal: AbortSignal.timeout(1e4)
|
|
14827
|
-
});
|
|
14828
|
-
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"
|
|
14829
15101
|
};
|
|
14830
|
-
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
|
|
14834
|
-
|
|
14835
|
-
|
|
14836
|
-
checks.push({
|
|
14837
|
-
name: "Hook credential: token rejected",
|
|
14838
|
-
status: "fail",
|
|
14839
|
-
message: `The hook-config.json token was rejected (HTTP ${status}) \u2014 transcript capture and the session glance are silently failing`,
|
|
14840
|
-
hint: "Run with --enforce to rewrite it from your gad login credential."
|
|
14841
|
-
});
|
|
14842
|
-
} else if (!tokenOk) {
|
|
14843
|
-
checks.push({
|
|
14844
|
-
name: "Hook credential: probe inconclusive",
|
|
14845
|
-
status: "warn",
|
|
14846
|
-
message: `Ping returned HTTP ${status} \u2014 could not confirm the hook token`
|
|
14847
|
-
});
|
|
14848
|
-
} else {
|
|
14849
|
-
checks.push({
|
|
14850
|
-
name: "Hook credential: token valid",
|
|
14851
|
-
status: "pass",
|
|
14852
|
-
message: "hook-config.json token authenticates"
|
|
14853
|
-
});
|
|
14854
|
-
}
|
|
14855
|
-
} catch (e) {
|
|
14856
|
-
checks.push({
|
|
14857
|
-
name: "Hook credential: probe failed",
|
|
14858
|
-
status: "warn",
|
|
14859
|
-
message: `Could not reach ${url ?? apiUrl}: ${String(
|
|
14860
|
-
e instanceof Error ? e.message : e
|
|
14861
|
-
)}`
|
|
14862
|
-
});
|
|
14863
|
-
}
|
|
14864
|
-
}
|
|
14865
|
-
if (options.enforce && !tokenOk) {
|
|
14866
|
-
if (auth?.token) {
|
|
14867
|
-
await backupAndWrite(
|
|
14868
|
-
hookConfigPath,
|
|
14869
|
-
JSON.stringify({ url: apiUrl, token: auth.token }, null, 2)
|
|
14870
|
-
);
|
|
14871
|
-
repairs.push("hook-config.json: rewrote from gad credential");
|
|
14872
|
-
try {
|
|
14873
|
-
const status = await probe(auth.token, apiUrl);
|
|
14874
|
-
checks.push({
|
|
14875
|
-
name: "Hook credential: after repair",
|
|
14876
|
-
status: status >= 200 && status < 300 ? "pass" : "fail",
|
|
14877
|
-
message: status >= 200 && status < 300 ? "Rewritten token authenticates" : `Rewritten token still failing (HTTP ${status})`
|
|
14878
|
-
});
|
|
14879
|
-
} catch (err) {
|
|
14880
|
-
checks.push({
|
|
14881
|
-
name: "Hook credential: after repair",
|
|
14882
|
-
status: "warn",
|
|
14883
|
-
message: `Rewrote the token but could not re-probe it (${err instanceof Error ? err.message : String(err)})`,
|
|
14884
|
-
hint: "Re-run `gad doctor` once the network is reachable to confirm."
|
|
14885
|
-
});
|
|
14886
|
-
}
|
|
14887
|
-
} else {
|
|
14888
|
-
checks.push({
|
|
14889
|
-
name: "Hook credential: no repair source",
|
|
14890
|
-
status: "fail",
|
|
14891
|
-
message: "No gad login credential available to rewrite hook-config.json",
|
|
14892
|
-
hint: "Run `gad login` first, then re-run with --enforce."
|
|
14893
|
-
});
|
|
14894
|
-
}
|
|
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`.";
|
|
14895
15108
|
}
|
|
15109
|
+
checks.push(check);
|
|
14896
15110
|
}
|
|
14897
15111
|
{
|
|
14898
15112
|
let root = command;
|
|
@@ -14910,23 +15124,23 @@ function claudeCommand() {
|
|
|
14910
15124
|
file: "/Library/Application Support/ClaudeCode/managed-settings.json"
|
|
14911
15125
|
},
|
|
14912
15126
|
{ scope: "user", file: settingsPath },
|
|
14913
|
-
{ scope: "project", file:
|
|
14914
|
-
{ scope: "local", file:
|
|
15127
|
+
{ scope: "project", file: join8(cwd, ".claude", "settings.json") },
|
|
15128
|
+
{ scope: "local", file: join8(cwd, ".claude", "settings.local.json") }
|
|
14915
15129
|
];
|
|
14916
15130
|
const configured = [];
|
|
14917
15131
|
for (const { scope, file } of scopes) {
|
|
14918
|
-
if (!
|
|
15132
|
+
if (!existsSync5(file)) continue;
|
|
14919
15133
|
configured.push(
|
|
14920
15134
|
...collectConfiguredHooks(
|
|
14921
15135
|
scope,
|
|
14922
15136
|
file,
|
|
14923
|
-
await
|
|
15137
|
+
await readFile5(file, "utf8")
|
|
14924
15138
|
)
|
|
14925
15139
|
);
|
|
14926
15140
|
}
|
|
14927
15141
|
const hookIssues = inspectConfiguredHooks(configured, {
|
|
14928
15142
|
knownGadVerbs,
|
|
14929
|
-
fileExists:
|
|
15143
|
+
fileExists: existsSync5
|
|
14930
15144
|
});
|
|
14931
15145
|
toChecks("Configured hooks", hookIssues);
|
|
14932
15146
|
if (configured.length > 0 && hookIssues.length === 0) {
|
|
@@ -14982,10 +15196,10 @@ function claudeCommand() {
|
|
|
14982
15196
|
function defaultJsonClientConfigs() {
|
|
14983
15197
|
const home = homedir5();
|
|
14984
15198
|
return [
|
|
14985
|
-
{ name: "Claude Code", path:
|
|
14986
|
-
{ name: "Cursor", path:
|
|
14987
|
-
{ name: "Kiro", path:
|
|
14988
|
-
{ 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") }
|
|
14989
15203
|
];
|
|
14990
15204
|
}
|
|
14991
15205
|
function clientsCommand() {
|
|
@@ -15011,8 +15225,8 @@ function clientsCommand() {
|
|
|
15011
15225
|
const checks = [];
|
|
15012
15226
|
const results = [];
|
|
15013
15227
|
for (const { name, path: path10 } of defaultJsonClientConfigs()) {
|
|
15014
|
-
if (!
|
|
15015
|
-
let content = await
|
|
15228
|
+
if (!existsSync5(path10)) continue;
|
|
15229
|
+
let content = await readFile5(path10, "utf8");
|
|
15016
15230
|
let backupPath = null;
|
|
15017
15231
|
let removedServers = [];
|
|
15018
15232
|
if (options.enforce) {
|
|
@@ -15020,14 +15234,14 @@ function clientsCommand() {
|
|
|
15020
15234
|
if (!pre.parseError && pre.staleRepoServers.length > 0) {
|
|
15021
15235
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
15022
15236
|
backupPath = `${path10}.bak.${timestamp}`;
|
|
15023
|
-
await
|
|
15237
|
+
await copyFile2(path10, backupPath);
|
|
15024
15238
|
const repaired = repairJsonMcpConfig(
|
|
15025
15239
|
content,
|
|
15026
15240
|
pre.staleRepoServers
|
|
15027
15241
|
);
|
|
15028
15242
|
content = repaired.content;
|
|
15029
15243
|
removedServers = repaired.removedServers;
|
|
15030
|
-
await
|
|
15244
|
+
await writeFile4(path10, content, "utf8");
|
|
15031
15245
|
}
|
|
15032
15246
|
}
|
|
15033
15247
|
const inspection = inspectJsonMcpConfig(content, expectedRepoUrl);
|
|
@@ -15522,6 +15736,107 @@ async function runStdioBridge(options) {
|
|
|
15522
15736
|
});
|
|
15523
15737
|
}
|
|
15524
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
|
+
|
|
15525
15840
|
// src/commands/mcp.ts
|
|
15526
15841
|
var SUPPORTED_CLIENTS = [
|
|
15527
15842
|
"claude",
|
|
@@ -15631,33 +15946,33 @@ async function pathExists(path10) {
|
|
|
15631
15946
|
}
|
|
15632
15947
|
}
|
|
15633
15948
|
async function installCodexConfig(apiUrl, token, repoUrl) {
|
|
15634
|
-
const { homedir:
|
|
15635
|
-
const { join:
|
|
15636
|
-
const { chmod, copyFile:
|
|
15637
|
-
const codexDir =
|
|
15638
|
-
const configPath =
|
|
15639
|
-
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");
|
|
15640
15955
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
15641
|
-
await
|
|
15642
|
-
const existingConfig = await pathExists(configPath) ? await
|
|
15956
|
+
await mkdir7(codexDir, { recursive: true });
|
|
15957
|
+
const existingConfig = await pathExists(configPath) ? await readFile7(configPath, "utf8") : "";
|
|
15643
15958
|
if (existingConfig.trim()) {
|
|
15644
|
-
await
|
|
15959
|
+
await copyFile3(configPath, `${configPath}.bak.${timestamp}`);
|
|
15645
15960
|
}
|
|
15646
|
-
await
|
|
15961
|
+
await writeFile6(
|
|
15647
15962
|
configPath,
|
|
15648
15963
|
upsertCodexServerBlock(existingConfig, apiUrl, repoUrl),
|
|
15649
15964
|
"utf8"
|
|
15650
15965
|
);
|
|
15651
|
-
const existingEnv = await pathExists(envPath) ? await
|
|
15966
|
+
const existingEnv = await pathExists(envPath) ? await readFile7(envPath, "utf8") : "";
|
|
15652
15967
|
if (existingEnv.trim()) {
|
|
15653
|
-
await
|
|
15968
|
+
await copyFile3(envPath, `${envPath}.bak.${timestamp}`);
|
|
15654
15969
|
}
|
|
15655
|
-
await
|
|
15970
|
+
await writeFile6(
|
|
15656
15971
|
envPath,
|
|
15657
15972
|
upsertEnvValue(existingEnv, "GREAT_ARROW_TOKEN", token),
|
|
15658
15973
|
"utf8"
|
|
15659
15974
|
);
|
|
15660
|
-
await
|
|
15975
|
+
await chmod2(envPath, 384);
|
|
15661
15976
|
return { configPath, envPath };
|
|
15662
15977
|
}
|
|
15663
15978
|
var startCommand = new Command("start").description(
|
|
@@ -15884,6 +16199,12 @@ var installCommand = new Command("install").description("Generate MCP configurat
|
|
|
15884
16199
|
).option("--stdout", "Print config to stdout instead of writing file").option(
|
|
15885
16200
|
"--repo-url <url>",
|
|
15886
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)"
|
|
15887
16208
|
).action(
|
|
15888
16209
|
async (clientName, options) => {
|
|
15889
16210
|
const fmt = createFormatter({
|
|
@@ -15935,15 +16256,15 @@ var installCommand = new Command("install").description("Generate MCP configurat
|
|
|
15935
16256
|
process.stdout.write(configJson2 + "\n");
|
|
15936
16257
|
return;
|
|
15937
16258
|
}
|
|
15938
|
-
const { homedir:
|
|
15939
|
-
const { join:
|
|
15940
|
-
const { mkdir:
|
|
15941
|
-
const geminiDir =
|
|
15942
|
-
const mcpPath =
|
|
15943
|
-
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 });
|
|
15944
16265
|
let existingConfig = {};
|
|
15945
16266
|
try {
|
|
15946
|
-
const content = await
|
|
16267
|
+
const content = await readFile7(mcpPath, "utf-8");
|
|
15947
16268
|
existingConfig = JSON.parse(content);
|
|
15948
16269
|
} catch {
|
|
15949
16270
|
existingConfig = {};
|
|
@@ -15955,20 +16276,20 @@ var installCommand = new Command("install").description("Generate MCP configurat
|
|
|
15955
16276
|
...config2.mcpServers
|
|
15956
16277
|
}
|
|
15957
16278
|
};
|
|
15958
|
-
await
|
|
16279
|
+
await writeFile7(
|
|
15959
16280
|
mcpPath,
|
|
15960
16281
|
JSON.stringify(existingConfig, null, 2) + "\n",
|
|
15961
16282
|
"utf-8"
|
|
15962
16283
|
);
|
|
15963
16284
|
fmt.success(`Wrote gemini MCP config to ${mcpPath}`);
|
|
15964
|
-
const agyMcpDir =
|
|
16285
|
+
const agyMcpDir = join11(
|
|
15965
16286
|
geminiDir,
|
|
15966
16287
|
"antigravity-cli",
|
|
15967
16288
|
"mcp",
|
|
15968
16289
|
MCP_SERVER_NAME
|
|
15969
16290
|
);
|
|
15970
|
-
await
|
|
15971
|
-
const instructionsPath =
|
|
16291
|
+
await mkdir7(agyMcpDir, { recursive: true });
|
|
16292
|
+
const instructionsPath = join11(agyMcpDir, "instructions.md");
|
|
15972
16293
|
const instructionsContent = `You are connected to greatarrow.ai - the user's private workspace memory + integrations brain.
|
|
15973
16294
|
|
|
15974
16295
|
CRITICAL DIRECTIVE (MANDATORY STANDARD):
|
|
@@ -15984,7 +16305,7 @@ THREE-LAYER LIVE TASK TRACKING:
|
|
|
15984
16305
|
- Maintain client-native harness plans, Great Arrow Kanban cards (\`todo_create\`, \`todo_update\`), and resume states.
|
|
15985
16306
|
|
|
15986
16307
|
Always assume full autonomy to leverage these tools to their maximum extent.`;
|
|
15987
|
-
await
|
|
16308
|
+
await writeFile7(instructionsPath, instructionsContent, "utf-8");
|
|
15988
16309
|
fmt.success(`Wrote gemini MCP instructions to ${instructionsPath}`);
|
|
15989
16310
|
return;
|
|
15990
16311
|
}
|
|
@@ -15999,25 +16320,28 @@ Always assume full autonomy to leverage these tools to their maximum extent.`;
|
|
|
15999
16320
|
process.stdout.write(configJson + "\n");
|
|
16000
16321
|
return;
|
|
16001
16322
|
}
|
|
16002
|
-
const { writeFile:
|
|
16003
|
-
const { join:
|
|
16004
|
-
const outputPath =
|
|
16005
|
-
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 });
|
|
16006
16327
|
fmt.success(`Wrote ${clientName} MCP config to .mcp.json`);
|
|
16007
16328
|
if (await mcpJsonAtRiskOfCommit(process.cwd())) {
|
|
16008
16329
|
fmt.warn(
|
|
16009
16330
|
".mcp.json contains an API token and is NOT gitignored here \u2014 add '.mcp.json' to .gitignore before committing."
|
|
16010
16331
|
);
|
|
16011
16332
|
}
|
|
16333
|
+
if (clientName === "claude") {
|
|
16334
|
+
await applySuggestToolsChoice(options.suggestTools);
|
|
16335
|
+
}
|
|
16012
16336
|
}
|
|
16013
16337
|
);
|
|
16014
16338
|
var KEYCHAIN_SERVICE = "GreatArrowDigital";
|
|
16015
16339
|
var KEYCHAIN_ACCOUNT = "api-token";
|
|
16016
16340
|
var OAUTH_CRED_FILENAME = "oauth-credential.json";
|
|
16017
16341
|
async function readIfExists(path10) {
|
|
16018
|
-
const { readFile:
|
|
16342
|
+
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
16019
16343
|
try {
|
|
16020
|
-
return await
|
|
16344
|
+
return await readFile7(path10, "utf8");
|
|
16021
16345
|
} catch {
|
|
16022
16346
|
return null;
|
|
16023
16347
|
}
|
|
@@ -16032,13 +16356,13 @@ async function readKeychainOnly() {
|
|
|
16032
16356
|
}
|
|
16033
16357
|
}
|
|
16034
16358
|
async function resolveProxyScript(override) {
|
|
16035
|
-
const { homedir:
|
|
16036
|
-
const { join:
|
|
16359
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
16360
|
+
const { join: join10 } = await import("node:path");
|
|
16037
16361
|
const candidates = [
|
|
16038
16362
|
override,
|
|
16039
16363
|
process.env.GAD_PROXY_SCRIPT,
|
|
16040
|
-
|
|
16041
|
-
|
|
16364
|
+
join10(homedir8(), ".great-arrow", "mcp-server.mjs"),
|
|
16365
|
+
join10(process.cwd(), "scripts", "mcp-server.mjs")
|
|
16042
16366
|
].filter((c) => Boolean(c));
|
|
16043
16367
|
for (const candidate of candidates) {
|
|
16044
16368
|
if (await pathExists(candidate)) return candidate;
|
|
@@ -16062,13 +16386,13 @@ async function runProxyDiagnostics(scriptPath) {
|
|
|
16062
16386
|
}
|
|
16063
16387
|
}
|
|
16064
16388
|
async function findLegacyStores(cwd) {
|
|
16065
|
-
const { join:
|
|
16389
|
+
const { join: join10 } = await import("node:path");
|
|
16066
16390
|
const { readdir } = await import("node:fs/promises");
|
|
16067
16391
|
const dirs = /* @__PURE__ */ new Set([cwd]);
|
|
16068
16392
|
try {
|
|
16069
|
-
const worktreeRoot =
|
|
16393
|
+
const worktreeRoot = join10(cwd, ".claude", "worktrees");
|
|
16070
16394
|
for (const entry of await readdir(worktreeRoot, { withFileTypes: true })) {
|
|
16071
|
-
if (entry.isDirectory()) dirs.add(
|
|
16395
|
+
if (entry.isDirectory()) dirs.add(join10(worktreeRoot, entry.name));
|
|
16072
16396
|
}
|
|
16073
16397
|
} catch (err) {
|
|
16074
16398
|
debugSwallow("mcp.findLegacyStores(worktrees)", err);
|
|
@@ -16090,18 +16414,18 @@ async function findLegacyStores(cwd) {
|
|
|
16090
16414
|
}
|
|
16091
16415
|
const stores = [];
|
|
16092
16416
|
for (const dir of dirs) {
|
|
16093
|
-
const path10 =
|
|
16417
|
+
const path10 = join10(dir, ".mcp", OAUTH_CRED_FILENAME);
|
|
16094
16418
|
const raw = await readIfExists(path10);
|
|
16095
16419
|
if (raw !== null) stores.push({ path: path10, raw });
|
|
16096
16420
|
}
|
|
16097
16421
|
return stores;
|
|
16098
16422
|
}
|
|
16099
16423
|
async function scanConnections() {
|
|
16100
|
-
const { homedir:
|
|
16101
|
-
const { join:
|
|
16102
|
-
const home =
|
|
16424
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
16425
|
+
const { join: join10 } = await import("node:path");
|
|
16426
|
+
const home = homedir8();
|
|
16103
16427
|
const hookSettings = await readIfExists(
|
|
16104
|
-
|
|
16428
|
+
join10(home, ".claude", "settings.json")
|
|
16105
16429
|
);
|
|
16106
16430
|
const hooksInstalled = hookSettings ? hasGreatArrowHooks(hookSettings) : false;
|
|
16107
16431
|
const entries = [];
|
|
@@ -16134,29 +16458,29 @@ async function probeEndpoint() {
|
|
|
16134
16458
|
}
|
|
16135
16459
|
}
|
|
16136
16460
|
async function applyMigration(plan) {
|
|
16137
|
-
const { homedir:
|
|
16138
|
-
const { join:
|
|
16139
|
-
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");
|
|
16140
16464
|
const applied = [];
|
|
16141
|
-
const dir =
|
|
16142
|
-
await
|
|
16465
|
+
const dir = join10(homedir8(), ".great-arrow");
|
|
16466
|
+
await mkdir7(dir, { recursive: true, mode: 448 });
|
|
16143
16467
|
if (plan.seedFrom) {
|
|
16144
16468
|
const raw = await readIfExists(plan.seedFrom);
|
|
16145
16469
|
if (raw !== null) {
|
|
16146
|
-
const target =
|
|
16470
|
+
const target = join10(dir, OAUTH_CRED_FILENAME);
|
|
16147
16471
|
const tmp = `${target}.tmp`;
|
|
16148
|
-
await
|
|
16149
|
-
await
|
|
16472
|
+
await writeFile6(tmp, raw, { mode: 384 });
|
|
16473
|
+
await rename2(tmp, target);
|
|
16150
16474
|
applied.push(`Copied ${plan.seedFrom} \u2192 ${target}`);
|
|
16151
16475
|
}
|
|
16152
16476
|
}
|
|
16153
16477
|
if (plan.syncKeychainToFile) {
|
|
16154
16478
|
const raw = await readKeychainOnly();
|
|
16155
16479
|
if (raw) {
|
|
16156
|
-
const target =
|
|
16480
|
+
const target = join10(dir, "credential");
|
|
16157
16481
|
const tmp = `${target}.tmp`;
|
|
16158
|
-
await
|
|
16159
|
-
await
|
|
16482
|
+
await writeFile6(tmp, raw, { mode: 384 });
|
|
16483
|
+
await rename2(tmp, target);
|
|
16160
16484
|
applied.push(`Wrote the keychain credential to ${target}`);
|
|
16161
16485
|
}
|
|
16162
16486
|
}
|
|
@@ -16179,19 +16503,19 @@ var doctorSubcommand = new Command("doctor").description(
|
|
|
16179
16503
|
noColor: false
|
|
16180
16504
|
});
|
|
16181
16505
|
const c = createColor(jsonFlag);
|
|
16182
|
-
const { homedir:
|
|
16183
|
-
const { join:
|
|
16184
|
-
const home =
|
|
16506
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
16507
|
+
const { join: join10 } = await import("node:path");
|
|
16508
|
+
const home = homedir8();
|
|
16185
16509
|
const proxyPath = await resolveProxyScript(options.proxy);
|
|
16186
16510
|
const diagnostics = proxyPath ? await runProxyDiagnostics(proxyPath) : null;
|
|
16187
16511
|
const entries = await scanConnections();
|
|
16188
16512
|
const duplicates = detectDuplicates(entries);
|
|
16189
16513
|
const keychain = await readKeychainOnly();
|
|
16190
16514
|
const credentialFile = await readIfExists(
|
|
16191
|
-
|
|
16515
|
+
join10(home, ".great-arrow", "credential")
|
|
16192
16516
|
);
|
|
16193
16517
|
const userStore = await readIfExists(
|
|
16194
|
-
|
|
16518
|
+
join10(home, ".great-arrow", OAUTH_CRED_FILENAME)
|
|
16195
16519
|
);
|
|
16196
16520
|
const legacyStores = await findLegacyStores(process.cwd());
|
|
16197
16521
|
const findings = [
|
|
@@ -16294,7 +16618,7 @@ ${c.dim("Credential migration")}
|
|
|
16294
16618
|
}
|
|
16295
16619
|
}
|
|
16296
16620
|
);
|
|
16297
|
-
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());
|
|
16298
16622
|
var mcp_default = mcpCommand;
|
|
16299
16623
|
|
|
16300
16624
|
// src/commands/smoke.ts
|
|
@@ -16615,9 +16939,9 @@ function askCommand() {
|
|
|
16615
16939
|
}
|
|
16616
16940
|
|
|
16617
16941
|
// src/lib/glance-footer.ts
|
|
16618
|
-
import { readFile as
|
|
16619
|
-
import { homedir as
|
|
16620
|
-
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";
|
|
16621
16945
|
|
|
16622
16946
|
// src/lib/seen.ts
|
|
16623
16947
|
var SEEN_TIMEOUT_MS = 1e3;
|
|
@@ -17004,11 +17328,11 @@ function shouldRequestTip(input) {
|
|
|
17004
17328
|
return now - state.lastTipAt >= TIP_THROTTLE_INTERVAL_MS;
|
|
17005
17329
|
}
|
|
17006
17330
|
function getGlanceStatePath() {
|
|
17007
|
-
return
|
|
17331
|
+
return join9(homedir7(), ".gad", "glance-state.json");
|
|
17008
17332
|
}
|
|
17009
17333
|
async function loadGlanceState() {
|
|
17010
17334
|
try {
|
|
17011
|
-
const content = await
|
|
17335
|
+
const content = await readFile6(getGlanceStatePath(), "utf-8");
|
|
17012
17336
|
const parsed = JSON.parse(content);
|
|
17013
17337
|
if (typeof parsed === "object" && parsed !== null) {
|
|
17014
17338
|
return parsed;
|
|
@@ -17020,8 +17344,8 @@ async function loadGlanceState() {
|
|
|
17020
17344
|
}
|
|
17021
17345
|
async function saveGlanceState(state) {
|
|
17022
17346
|
const path10 = getGlanceStatePath();
|
|
17023
|
-
await
|
|
17024
|
-
await
|
|
17347
|
+
await mkdir6(dirname6(path10), { recursive: true });
|
|
17348
|
+
await writeFile5(path10, JSON.stringify(state), "utf-8");
|
|
17025
17349
|
}
|
|
17026
17350
|
function glanceItemUrl(href, apiUrl) {
|
|
17027
17351
|
if (!href) return null;
|
|
@@ -18903,7 +19227,7 @@ function completionsCommand() {
|
|
|
18903
19227
|
// node_modules/.pnpm/update-notifier@7.3.1/node_modules/update-notifier/update-notifier.js
|
|
18904
19228
|
import process11 from "node:process";
|
|
18905
19229
|
import { spawn } from "node:child_process";
|
|
18906
|
-
import { fileURLToPath as
|
|
19230
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
18907
19231
|
import path9 from "node:path";
|
|
18908
19232
|
import { format } from "node:util";
|
|
18909
19233
|
|
|
@@ -21054,7 +21378,7 @@ var isNpmOrYarn = isNpm || isYarn;
|
|
|
21054
21378
|
// node_modules/.pnpm/is-installed-globally@1.0.0/node_modules/is-installed-globally/index.js
|
|
21055
21379
|
import fs5 from "node:fs";
|
|
21056
21380
|
import path8 from "node:path";
|
|
21057
|
-
import { fileURLToPath } from "node:url";
|
|
21381
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
21058
21382
|
|
|
21059
21383
|
// node_modules/.pnpm/global-directory@4.0.1/node_modules/global-directory/index.js
|
|
21060
21384
|
var import_ini = __toESM(require_ini2(), 1);
|
|
@@ -21158,7 +21482,7 @@ function isPathInside(childPath, parentPath) {
|
|
|
21158
21482
|
}
|
|
21159
21483
|
|
|
21160
21484
|
// node_modules/.pnpm/is-installed-globally@1.0.0/node_modules/is-installed-globally/index.js
|
|
21161
|
-
var __dirname2 = path8.dirname(
|
|
21485
|
+
var __dirname2 = path8.dirname(fileURLToPath2(import.meta.url));
|
|
21162
21486
|
var isInstalledGlobally = (() => {
|
|
21163
21487
|
try {
|
|
21164
21488
|
return isPathInside(__dirname2, global_directory_default.yarn.packages) || isPathInside(__dirname2, fs5.realpathSync(global_directory_default.npm.packages));
|
|
@@ -22140,7 +22464,7 @@ function pupa(template, data, { ignoreMissing = false, transform = ({ value }) =
|
|
|
22140
22464
|
}
|
|
22141
22465
|
|
|
22142
22466
|
// node_modules/.pnpm/update-notifier@7.3.1/node_modules/update-notifier/update-notifier.js
|
|
22143
|
-
var __dirname3 = path9.dirname(
|
|
22467
|
+
var __dirname3 = path9.dirname(fileURLToPath3(import.meta.url));
|
|
22144
22468
|
var ONE_DAY = 1e3 * 60 * 60 * 24;
|
|
22145
22469
|
var UpdateNotifier = class {
|
|
22146
22470
|
// Public
|