@great-arrow/cli 0.1.48 → 0.1.50
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 +616 -243
- 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.50";
|
|
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";
|
|
@@ -10543,12 +10543,13 @@ var EXIT_SESSION_EXPIRED = 2;
|
|
|
10543
10543
|
var EXIT_NOT_OAUTH = 3;
|
|
10544
10544
|
function authCommand() {
|
|
10545
10545
|
const cmd = new Command("auth").description("Authentication helpers");
|
|
10546
|
-
cmd.command("token").description("Print a fresh OAuth access token, refreshing it if needed").option("--json", "Output {accessToken, accessExpiresAt, apiUrl} as JSON").action(async (options) => {
|
|
10546
|
+
cmd.command("token").description("Print a fresh OAuth access token, refreshing it if needed").option("--json", "Output {accessToken, accessExpiresAt, apiUrl} as JSON").action(async (options, actionCommand) => {
|
|
10547
10547
|
try {
|
|
10548
10548
|
const t = await getFreshOAuthAccessToken();
|
|
10549
10549
|
writeCliLocator();
|
|
10550
|
+
const jsonOutput = options.json || actionCommand.optsWithGlobals().json;
|
|
10550
10551
|
process.stdout.write(
|
|
10551
|
-
|
|
10552
|
+
jsonOutput ? `${JSON.stringify(t)}
|
|
10552
10553
|
` : `${t.accessToken}
|
|
10553
10554
|
`
|
|
10554
10555
|
);
|
|
@@ -11958,6 +11959,29 @@ import { readFileSync } from "node:fs";
|
|
|
11958
11959
|
|
|
11959
11960
|
// src/lib/work-status.ts
|
|
11960
11961
|
import { execFile as execFile2, execFileSync as execFileSync2 } from "node:child_process";
|
|
11962
|
+
|
|
11963
|
+
// ../../src/lib/work-risk/push-state.ts
|
|
11964
|
+
var REMOTE_BRANCH_REF_PREFIX = "refs/remotes/origin/";
|
|
11965
|
+
var REMOTE_BRANCH_TIPS_FORMAT = "%(refname)%09%(objectname)";
|
|
11966
|
+
function parseRemoteBranchTips(raw) {
|
|
11967
|
+
const tips = /* @__PURE__ */ new Map();
|
|
11968
|
+
for (const line of raw.split("\n")) {
|
|
11969
|
+
const [refname, sha] = line.split(" ");
|
|
11970
|
+
if (!refname?.startsWith(REMOTE_BRANCH_REF_PREFIX) || !sha) continue;
|
|
11971
|
+
const branch = refname.slice(REMOTE_BRANCH_REF_PREFIX.length);
|
|
11972
|
+
if (!branch || branch === "HEAD") continue;
|
|
11973
|
+
tips.set(branch, sha.trim());
|
|
11974
|
+
}
|
|
11975
|
+
return tips;
|
|
11976
|
+
}
|
|
11977
|
+
function remoteHasHead(branch, head, tips, isAncestor2) {
|
|
11978
|
+
const tip = tips.get(branch);
|
|
11979
|
+
if (!tip) return false;
|
|
11980
|
+
if (tip === head) return true;
|
|
11981
|
+
return isAncestor2(head, tip);
|
|
11982
|
+
}
|
|
11983
|
+
|
|
11984
|
+
// src/lib/work-status.ts
|
|
11961
11985
|
var LOCAL_COMMAND_TIMEOUT_MS = 2e4;
|
|
11962
11986
|
function run(command, args, cwd) {
|
|
11963
11987
|
try {
|
|
@@ -12070,9 +12094,31 @@ function auditLocalBranches(root) {
|
|
|
12070
12094
|
root
|
|
12071
12095
|
).split("\n").filter(Boolean)
|
|
12072
12096
|
);
|
|
12097
|
+
const tipsResult = git(
|
|
12098
|
+
[
|
|
12099
|
+
"for-each-ref",
|
|
12100
|
+
`--format=${REMOTE_BRANCH_TIPS_FORMAT}`,
|
|
12101
|
+
"refs/remotes/origin"
|
|
12102
|
+
],
|
|
12103
|
+
root
|
|
12104
|
+
);
|
|
12105
|
+
const tips = parseRemoteBranchTips(
|
|
12106
|
+
tipsResult.status === 0 ? tipsResult.stdout : ""
|
|
12107
|
+
);
|
|
12073
12108
|
return raw.split("\n").filter(Boolean).flatMap((line) => {
|
|
12074
12109
|
const parsed = parseLocalBranchLine(line, exact, unmerged);
|
|
12075
|
-
|
|
12110
|
+
if (!parsed) return [];
|
|
12111
|
+
return [
|
|
12112
|
+
{
|
|
12113
|
+
...parsed,
|
|
12114
|
+
remoteHasHead: remoteHasHead(
|
|
12115
|
+
parsed.branch,
|
|
12116
|
+
parsed.head,
|
|
12117
|
+
tips,
|
|
12118
|
+
(ancestor, descendant) => isAncestor(ancestor, descendant, root)
|
|
12119
|
+
)
|
|
12120
|
+
}
|
|
12121
|
+
];
|
|
12076
12122
|
});
|
|
12077
12123
|
}
|
|
12078
12124
|
function parseLocalBranchLine(line, exact, unmerged) {
|
|
@@ -12131,7 +12177,7 @@ function classifyWorktree(audit) {
|
|
|
12131
12177
|
if (audit.path.includes("/.claude/worktrees/")) return "agent-harness";
|
|
12132
12178
|
if (audit.mergedToMain) return "cleanup-ready";
|
|
12133
12179
|
if (audit.upstreamGone) return "upstream-gone";
|
|
12134
|
-
if (!audit.
|
|
12180
|
+
if (!audit.remoteHasHead) return "local-rescue";
|
|
12135
12181
|
return "active-unmerged";
|
|
12136
12182
|
}
|
|
12137
12183
|
async function auditWorktrees(root, localBranches) {
|
|
@@ -12159,6 +12205,7 @@ async function auditWorktrees(root, localBranches) {
|
|
|
12159
12205
|
mergedToMain: branch?.mergedToMain ?? false,
|
|
12160
12206
|
upstream: branch?.upstream ?? null,
|
|
12161
12207
|
upstreamGone: branch?.upstreamGone ?? false,
|
|
12208
|
+
remoteHasHead: branch?.remoteHasHead ?? false,
|
|
12162
12209
|
behindMain: branch?.behindMain ?? 0,
|
|
12163
12210
|
aheadMain: branch?.aheadMain ?? 0,
|
|
12164
12211
|
countsExact: branch?.countsExact ?? false
|
|
@@ -12178,6 +12225,7 @@ function auditDetachedLocalBranches(localBranches, checkedOutBranches) {
|
|
|
12178
12225
|
countsExact,
|
|
12179
12226
|
upstream,
|
|
12180
12227
|
upstreamGone,
|
|
12228
|
+
remoteHasHead: remoteHasHead2,
|
|
12181
12229
|
committedAt
|
|
12182
12230
|
}) => ({
|
|
12183
12231
|
branch,
|
|
@@ -12186,12 +12234,13 @@ function auditDetachedLocalBranches(localBranches, checkedOutBranches) {
|
|
|
12186
12234
|
behindMain,
|
|
12187
12235
|
aheadMain,
|
|
12188
12236
|
countsExact,
|
|
12189
|
-
// `
|
|
12190
|
-
// just has no PR yet" — the single bit that decides whether
|
|
12191
|
-
// actually be lost.
|
|
12192
|
-
//
|
|
12237
|
+
// `remoteHasHead` is what separates "never left this disk" from
|
|
12238
|
+
// "pushed, just has no PR yet" — the single bit that decides whether
|
|
12239
|
+
// work can actually be lost. `upstream` alone cannot: a task branch
|
|
12240
|
+
// could carry origin/main as its upstream without ever being pushed.
|
|
12193
12241
|
upstream,
|
|
12194
12242
|
upstreamGone,
|
|
12243
|
+
remoteHasHead: remoteHasHead2,
|
|
12195
12244
|
// Without this the grace window read `ageMs = Infinity` for every
|
|
12196
12245
|
// branch, so a branch was "stranded" from its first second.
|
|
12197
12246
|
committedAt
|
|
@@ -13025,10 +13074,10 @@ function guardrailsCommand() {
|
|
|
13025
13074
|
}
|
|
13026
13075
|
|
|
13027
13076
|
// src/commands/doctor.ts
|
|
13028
|
-
import { existsSync as
|
|
13029
|
-
import { copyFile, mkdir as
|
|
13077
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
13078
|
+
import { copyFile as copyFile2, mkdir as mkdir5, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
13030
13079
|
import { homedir as homedir5 } from "node:os";
|
|
13031
|
-
import { dirname as
|
|
13080
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
13032
13081
|
|
|
13033
13082
|
// src/lib/codex-config.ts
|
|
13034
13083
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
@@ -13815,6 +13864,285 @@ function inspectConfiguredHooks(hooks, deps) {
|
|
|
13815
13864
|
return issues;
|
|
13816
13865
|
}
|
|
13817
13866
|
|
|
13867
|
+
// src/lib/suggest-hook.ts
|
|
13868
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
13869
|
+
import {
|
|
13870
|
+
chmod,
|
|
13871
|
+
copyFile,
|
|
13872
|
+
mkdir as mkdir4,
|
|
13873
|
+
readFile as readFile3,
|
|
13874
|
+
rename,
|
|
13875
|
+
unlink as unlink2,
|
|
13876
|
+
writeFile as writeFile3
|
|
13877
|
+
} from "node:fs/promises";
|
|
13878
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
13879
|
+
import { fileURLToPath } from "node:url";
|
|
13880
|
+
var SUGGEST_HOOK_FILE = "gad-suggest-tools.mjs";
|
|
13881
|
+
var SUGGEST_HOOK_EVENT = "UserPromptSubmit";
|
|
13882
|
+
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`.";
|
|
13883
|
+
function asObject2(value) {
|
|
13884
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
13885
|
+
}
|
|
13886
|
+
function isOurs(hook) {
|
|
13887
|
+
return !!hook && typeof hook.command === "string" && hook.command.includes(SUGGEST_HOOK_FILE);
|
|
13888
|
+
}
|
|
13889
|
+
function parseSettings(content) {
|
|
13890
|
+
const parsed = content.trim() ? JSON.parse(content) : {};
|
|
13891
|
+
const cfg = asObject2(parsed);
|
|
13892
|
+
if (!cfg) throw new Error("settings.json is not a JSON object");
|
|
13893
|
+
return cfg;
|
|
13894
|
+
}
|
|
13895
|
+
function promptGroups(cfg) {
|
|
13896
|
+
const groups = asObject2(cfg.hooks)?.[SUGGEST_HOOK_EVENT];
|
|
13897
|
+
return Array.isArray(groups) ? groups : [];
|
|
13898
|
+
}
|
|
13899
|
+
function isSuggestHookEnabled(settingsContent) {
|
|
13900
|
+
try {
|
|
13901
|
+
return promptGroups(parseSettings(settingsContent)).some(
|
|
13902
|
+
(g) => Array.isArray(g?.hooks) && g.hooks.some(isOurs)
|
|
13903
|
+
);
|
|
13904
|
+
} catch {
|
|
13905
|
+
return false;
|
|
13906
|
+
}
|
|
13907
|
+
}
|
|
13908
|
+
function suggestHookCommand(scriptPath) {
|
|
13909
|
+
return `node "${scriptPath}"`;
|
|
13910
|
+
}
|
|
13911
|
+
function planEnableSuggestHook(settingsContent, command) {
|
|
13912
|
+
if (isSuggestHookEnabled(settingsContent)) {
|
|
13913
|
+
return { content: settingsContent, changed: false };
|
|
13914
|
+
}
|
|
13915
|
+
const cfg = parseSettings(settingsContent);
|
|
13916
|
+
const hooks = asObject2(cfg.hooks) ?? {};
|
|
13917
|
+
cfg.hooks = hooks;
|
|
13918
|
+
const groups = promptGroups(cfg);
|
|
13919
|
+
groups.push({ hooks: [{ type: "command", command, timeout: 5 }] });
|
|
13920
|
+
hooks[SUGGEST_HOOK_EVENT] = groups;
|
|
13921
|
+
return { content: JSON.stringify(cfg, null, 2) + "\n", changed: true };
|
|
13922
|
+
}
|
|
13923
|
+
function planDisableSuggestHook(settingsContent) {
|
|
13924
|
+
if (!isSuggestHookEnabled(settingsContent)) {
|
|
13925
|
+
return { content: settingsContent, changed: false };
|
|
13926
|
+
}
|
|
13927
|
+
const cfg = parseSettings(settingsContent);
|
|
13928
|
+
const hooks = asObject2(cfg.hooks);
|
|
13929
|
+
const kept = promptGroups(cfg).map(
|
|
13930
|
+
(g) => Array.isArray(g?.hooks) ? { ...g, hooks: g.hooks.filter((h) => !isOurs(h)) } : g
|
|
13931
|
+
).filter((g) => !Array.isArray(g?.hooks) || g.hooks.length > 0);
|
|
13932
|
+
if (kept.length > 0) hooks[SUGGEST_HOOK_EVENT] = kept;
|
|
13933
|
+
else delete hooks[SUGGEST_HOOK_EVENT];
|
|
13934
|
+
if (Object.keys(hooks).length === 0) delete cfg.hooks;
|
|
13935
|
+
return { content: JSON.stringify(cfg, null, 2) + "\n", changed: true };
|
|
13936
|
+
}
|
|
13937
|
+
function suggestHookPaths(home) {
|
|
13938
|
+
return {
|
|
13939
|
+
settingsPath: join7(home, ".claude", "settings.json"),
|
|
13940
|
+
scriptPath: join7(home, ".great-arrow", SUGGEST_HOOK_FILE),
|
|
13941
|
+
hookConfigPath: join7(home, ".great-arrow", "hook-config.json")
|
|
13942
|
+
};
|
|
13943
|
+
}
|
|
13944
|
+
function packagedSuggestHookPath() {
|
|
13945
|
+
const here = dirname4(fileURLToPath(import.meta.url));
|
|
13946
|
+
for (const candidate of [
|
|
13947
|
+
join7(here, "hooks", SUGGEST_HOOK_FILE),
|
|
13948
|
+
join7(here, "..", "..", "dist", "hooks", SUGGEST_HOOK_FILE)
|
|
13949
|
+
]) {
|
|
13950
|
+
if (existsSync3(candidate)) return candidate;
|
|
13951
|
+
}
|
|
13952
|
+
return null;
|
|
13953
|
+
}
|
|
13954
|
+
async function readOrEmpty(path10) {
|
|
13955
|
+
try {
|
|
13956
|
+
return await readFile3(path10, "utf8");
|
|
13957
|
+
} catch {
|
|
13958
|
+
return "";
|
|
13959
|
+
}
|
|
13960
|
+
}
|
|
13961
|
+
async function atomicWrite(path10, content, mode) {
|
|
13962
|
+
await mkdir4(dirname4(path10), { recursive: true });
|
|
13963
|
+
const tmp = `${path10}.tmp-${process.pid}`;
|
|
13964
|
+
await writeFile3(tmp, content, mode === void 0 ? "utf8" : { mode });
|
|
13965
|
+
await rename(tmp, path10);
|
|
13966
|
+
}
|
|
13967
|
+
function hasHookCredential(content) {
|
|
13968
|
+
try {
|
|
13969
|
+
const cfg = asObject2(JSON.parse(content));
|
|
13970
|
+
return typeof cfg?.token === "string" && cfg.token.trim().length > 0;
|
|
13971
|
+
} catch {
|
|
13972
|
+
return false;
|
|
13973
|
+
}
|
|
13974
|
+
}
|
|
13975
|
+
async function suggestHookStatus(home) {
|
|
13976
|
+
const p = suggestHookPaths(home);
|
|
13977
|
+
return {
|
|
13978
|
+
enabled: isSuggestHookEnabled(await readOrEmpty(p.settingsPath)),
|
|
13979
|
+
scriptPresent: existsSync3(p.scriptPath),
|
|
13980
|
+
credentialPresent: hasHookCredential(await readOrEmpty(p.hookConfigPath))
|
|
13981
|
+
};
|
|
13982
|
+
}
|
|
13983
|
+
async function enableSuggestHook(opts) {
|
|
13984
|
+
const p = suggestHookPaths(opts.home);
|
|
13985
|
+
const settings = await readOrEmpty(p.settingsPath);
|
|
13986
|
+
const plan = planEnableSuggestHook(
|
|
13987
|
+
settings,
|
|
13988
|
+
suggestHookCommand(p.scriptPath)
|
|
13989
|
+
);
|
|
13990
|
+
await mkdir4(dirname4(p.scriptPath), { recursive: true, mode: 448 });
|
|
13991
|
+
await copyFile(opts.packagedScript, p.scriptPath);
|
|
13992
|
+
await chmod(p.scriptPath, 493);
|
|
13993
|
+
if (plan.changed) await atomicWrite(p.settingsPath, plan.content);
|
|
13994
|
+
return { changed: plan.changed };
|
|
13995
|
+
}
|
|
13996
|
+
async function disableSuggestHook(opts) {
|
|
13997
|
+
const p = suggestHookPaths(opts.home);
|
|
13998
|
+
const plan = planDisableSuggestHook(await readOrEmpty(p.settingsPath));
|
|
13999
|
+
if (plan.changed) await atomicWrite(p.settingsPath, plan.content);
|
|
14000
|
+
if (existsSync3(p.scriptPath)) await unlink2(p.scriptPath);
|
|
14001
|
+
return { changed: plan.changed };
|
|
14002
|
+
}
|
|
14003
|
+
async function runSuggestHookOptIn(opts) {
|
|
14004
|
+
if (opts.choice === false) {
|
|
14005
|
+
await opts.disable();
|
|
14006
|
+
return "disabled";
|
|
14007
|
+
}
|
|
14008
|
+
if (opts.choice === void 0 && !opts.interactive) return "unchanged";
|
|
14009
|
+
opts.print(SUGGEST_HOOK_DISCLOSURE);
|
|
14010
|
+
if (opts.choice === void 0) {
|
|
14011
|
+
const yes = await opts.confirm("Turn on tool suggestions?");
|
|
14012
|
+
if (!yes) return "unchanged";
|
|
14013
|
+
}
|
|
14014
|
+
await opts.enable();
|
|
14015
|
+
return "enabled";
|
|
14016
|
+
}
|
|
14017
|
+
|
|
14018
|
+
// src/lib/hook-credential.ts
|
|
14019
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
14020
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
14021
|
+
function issuesToChecks(issues) {
|
|
14022
|
+
return issues.map((issue) => {
|
|
14023
|
+
const check = {
|
|
14024
|
+
name: `Hook credential: ${issue.code}`,
|
|
14025
|
+
status: issue.level,
|
|
14026
|
+
message: issue.message
|
|
14027
|
+
};
|
|
14028
|
+
if (issue.hint) check.hint = issue.hint;
|
|
14029
|
+
return check;
|
|
14030
|
+
});
|
|
14031
|
+
}
|
|
14032
|
+
function mergeHookConfig(existing, url, token) {
|
|
14033
|
+
let base = {};
|
|
14034
|
+
if (existing) {
|
|
14035
|
+
try {
|
|
14036
|
+
const parsed = JSON.parse(existing);
|
|
14037
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
14038
|
+
base = parsed;
|
|
14039
|
+
}
|
|
14040
|
+
} catch {
|
|
14041
|
+
base = {};
|
|
14042
|
+
}
|
|
14043
|
+
}
|
|
14044
|
+
return JSON.stringify({ ...base, url, token }, null, 2);
|
|
14045
|
+
}
|
|
14046
|
+
async function ensureHookCredential(opts) {
|
|
14047
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
14048
|
+
const { hookConfigPath, apiUrl, auth } = opts;
|
|
14049
|
+
const checks = [];
|
|
14050
|
+
const repairs = [];
|
|
14051
|
+
let url = null;
|
|
14052
|
+
let token = null;
|
|
14053
|
+
let existing = null;
|
|
14054
|
+
if (existsSync4(hookConfigPath)) {
|
|
14055
|
+
existing = await readFile4(hookConfigPath, "utf8");
|
|
14056
|
+
const inspection = inspectHookConfig(existing);
|
|
14057
|
+
url = inspection.url;
|
|
14058
|
+
token = inspection.token;
|
|
14059
|
+
checks.push(...issuesToChecks(inspection.issues));
|
|
14060
|
+
} else {
|
|
14061
|
+
checks.push({
|
|
14062
|
+
name: "Hook credential: not found",
|
|
14063
|
+
status: "warn",
|
|
14064
|
+
message: `${hookConfigPath} does not exist \u2014 hooks cannot authenticate`,
|
|
14065
|
+
hint: "Run with --enforce to write it from your gad login credential."
|
|
14066
|
+
});
|
|
14067
|
+
}
|
|
14068
|
+
const probe = async (t, base) => {
|
|
14069
|
+
const res = await fetchImpl(`${base}/api/mcp/ping`, {
|
|
14070
|
+
headers: { Authorization: `Bearer ${t}` },
|
|
14071
|
+
signal: AbortSignal.timeout(1e4)
|
|
14072
|
+
});
|
|
14073
|
+
return res.status;
|
|
14074
|
+
};
|
|
14075
|
+
let tokenOk = false;
|
|
14076
|
+
if (token) {
|
|
14077
|
+
try {
|
|
14078
|
+
const status = await probe(token, url ?? apiUrl);
|
|
14079
|
+
tokenOk = status >= 200 && status < 300;
|
|
14080
|
+
if (status === 401 || status === 403) {
|
|
14081
|
+
checks.push({
|
|
14082
|
+
name: "Hook credential: token rejected",
|
|
14083
|
+
status: "fail",
|
|
14084
|
+
message: `The hook-config.json token was rejected (HTTP ${status}) \u2014 transcript capture and the session glance are silently failing`,
|
|
14085
|
+
hint: "Run with --enforce to rewrite it from your gad login credential."
|
|
14086
|
+
});
|
|
14087
|
+
} else if (!tokenOk) {
|
|
14088
|
+
checks.push({
|
|
14089
|
+
name: "Hook credential: probe inconclusive",
|
|
14090
|
+
status: "warn",
|
|
14091
|
+
message: `Ping returned HTTP ${status} \u2014 could not confirm the hook token`
|
|
14092
|
+
});
|
|
14093
|
+
} else {
|
|
14094
|
+
checks.push({
|
|
14095
|
+
name: "Hook credential: token valid",
|
|
14096
|
+
status: "pass",
|
|
14097
|
+
message: "hook-config.json token authenticates"
|
|
14098
|
+
});
|
|
14099
|
+
}
|
|
14100
|
+
} catch (e) {
|
|
14101
|
+
checks.push({
|
|
14102
|
+
name: "Hook credential: probe failed",
|
|
14103
|
+
status: "warn",
|
|
14104
|
+
message: `Could not reach ${url ?? apiUrl}: ${String(
|
|
14105
|
+
e instanceof Error ? e.message : e
|
|
14106
|
+
)}`
|
|
14107
|
+
});
|
|
14108
|
+
}
|
|
14109
|
+
}
|
|
14110
|
+
let verified = tokenOk;
|
|
14111
|
+
if (opts.repair && !tokenOk) {
|
|
14112
|
+
if (auth?.token) {
|
|
14113
|
+
await opts.write(
|
|
14114
|
+
hookConfigPath,
|
|
14115
|
+
mergeHookConfig(existing, apiUrl, auth.token)
|
|
14116
|
+
);
|
|
14117
|
+
repairs.push("hook-config.json: rewrote from gad credential");
|
|
14118
|
+
try {
|
|
14119
|
+
const status = await probe(auth.token, apiUrl);
|
|
14120
|
+
verified = status >= 200 && status < 300;
|
|
14121
|
+
checks.push({
|
|
14122
|
+
name: "Hook credential: after repair",
|
|
14123
|
+
status: verified ? "pass" : "fail",
|
|
14124
|
+
message: verified ? "Rewritten token authenticates" : `Rewritten token still failing (HTTP ${status})`
|
|
14125
|
+
});
|
|
14126
|
+
} catch (err) {
|
|
14127
|
+
checks.push({
|
|
14128
|
+
name: "Hook credential: after repair",
|
|
14129
|
+
status: "warn",
|
|
14130
|
+
message: `Rewrote the token but could not re-probe it (${err instanceof Error ? err.message : String(err)})`,
|
|
14131
|
+
hint: "Re-run `gad doctor` once the network is reachable to confirm."
|
|
14132
|
+
});
|
|
14133
|
+
}
|
|
14134
|
+
} else {
|
|
14135
|
+
checks.push({
|
|
14136
|
+
name: "Hook credential: no repair source",
|
|
14137
|
+
status: "fail",
|
|
14138
|
+
message: "No gad login credential available to rewrite hook-config.json",
|
|
14139
|
+
hint: "Run `gad login` first, then re-run with --enforce."
|
|
14140
|
+
});
|
|
14141
|
+
}
|
|
14142
|
+
}
|
|
14143
|
+
return { checks, repairs, verified };
|
|
14144
|
+
}
|
|
14145
|
+
|
|
13818
14146
|
// src/lib/mcp-doctor.ts
|
|
13819
14147
|
import { createHash as createHash2 } from "node:crypto";
|
|
13820
14148
|
|
|
@@ -13986,7 +14314,7 @@ function knownConfigLocations(home, cwd, platform = process.platform, appData) {
|
|
|
13986
14314
|
];
|
|
13987
14315
|
}
|
|
13988
14316
|
var GREAT_ARROW_NAME = /great[-_ ]?arrow|ai[-_]memory/i;
|
|
13989
|
-
function
|
|
14317
|
+
function asObject3(value) {
|
|
13990
14318
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
13991
14319
|
}
|
|
13992
14320
|
function isGreatArrowServer(name, entry) {
|
|
@@ -14007,13 +14335,13 @@ function classifySurface(entry, clientId, hooksInstalled) {
|
|
|
14007
14335
|
}
|
|
14008
14336
|
function serverMapsFromJson(parsed, serversKey) {
|
|
14009
14337
|
const maps = [];
|
|
14010
|
-
const top =
|
|
14338
|
+
const top = asObject3(parsed[serversKey]);
|
|
14011
14339
|
if (top) maps.push({ servers: top });
|
|
14012
|
-
const projects =
|
|
14340
|
+
const projects = asObject3(parsed.projects);
|
|
14013
14341
|
if (projects) {
|
|
14014
14342
|
for (const [dir, value] of Object.entries(projects)) {
|
|
14015
|
-
const section =
|
|
14016
|
-
const servers = section ?
|
|
14343
|
+
const section = asObject3(value);
|
|
14344
|
+
const servers = section ? asObject3(section[serversKey]) : null;
|
|
14017
14345
|
if (servers && Object.keys(servers).length > 0) {
|
|
14018
14346
|
maps.push({ servers, projectDir: dir });
|
|
14019
14347
|
}
|
|
@@ -14051,7 +14379,7 @@ function findGreatArrowEntries(location, content, options = {}) {
|
|
|
14051
14379
|
if (location.format === "toml") return findCodexEntries(location, content);
|
|
14052
14380
|
let parsed = null;
|
|
14053
14381
|
try {
|
|
14054
|
-
parsed =
|
|
14382
|
+
parsed = asObject3(JSON.parse(content));
|
|
14055
14383
|
} catch {
|
|
14056
14384
|
return [];
|
|
14057
14385
|
}
|
|
@@ -14063,7 +14391,7 @@ function findGreatArrowEntries(location, content, options = {}) {
|
|
|
14063
14391
|
serversKey
|
|
14064
14392
|
)) {
|
|
14065
14393
|
for (const [name, value] of Object.entries(servers)) {
|
|
14066
|
-
const entry =
|
|
14394
|
+
const entry = asObject3(value);
|
|
14067
14395
|
if (!entry) continue;
|
|
14068
14396
|
if (!isGreatArrowServer(name, entry)) continue;
|
|
14069
14397
|
entries.push({
|
|
@@ -14086,7 +14414,7 @@ function findGreatArrowEntries(location, content, options = {}) {
|
|
|
14086
14414
|
function hasGreatArrowHooks(settingsContent) {
|
|
14087
14415
|
let parsed = null;
|
|
14088
14416
|
try {
|
|
14089
|
-
parsed =
|
|
14417
|
+
parsed = asObject3(JSON.parse(settingsContent));
|
|
14090
14418
|
} catch {
|
|
14091
14419
|
return false;
|
|
14092
14420
|
}
|
|
@@ -14160,7 +14488,7 @@ function describeStoredCredential(raw) {
|
|
|
14160
14488
|
if (!trimmed) return null;
|
|
14161
14489
|
if (trimmed.startsWith("{")) {
|
|
14162
14490
|
try {
|
|
14163
|
-
const parsed =
|
|
14491
|
+
const parsed = asObject3(JSON.parse(trimmed));
|
|
14164
14492
|
const refreshToken = parsed?.refreshToken;
|
|
14165
14493
|
if (typeof refreshToken === "string" && refreshToken.length > 0) {
|
|
14166
14494
|
return {
|
|
@@ -14559,7 +14887,7 @@ async function checkMcp(auth) {
|
|
|
14559
14887
|
}
|
|
14560
14888
|
}
|
|
14561
14889
|
function defaultCodexConfigPath() {
|
|
14562
|
-
return
|
|
14890
|
+
return join8(homedir5(), ".codex", "config.toml");
|
|
14563
14891
|
}
|
|
14564
14892
|
function codexIssuesToChecks(issues) {
|
|
14565
14893
|
return issues.map((issue) => {
|
|
@@ -14597,16 +14925,16 @@ function codexCommand() {
|
|
|
14597
14925
|
});
|
|
14598
14926
|
const configPath = options.config ?? defaultCodexConfigPath();
|
|
14599
14927
|
const expectedRepoUrl = options.expectedRepo ?? detectGitOrigin2();
|
|
14600
|
-
let content =
|
|
14928
|
+
let content = existsSync5(configPath) ? await readFile5(configPath, "utf8") : "";
|
|
14601
14929
|
let backupPath = null;
|
|
14602
14930
|
let removedServers = [];
|
|
14603
14931
|
if (options.enforce) {
|
|
14604
14932
|
const apiUrl = extractCodexGreatArrowApiUrl(content) ?? (await resolveAuth()).apiUrl;
|
|
14605
14933
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
14606
|
-
await
|
|
14934
|
+
await mkdir5(dirname5(configPath), { recursive: true });
|
|
14607
14935
|
if (content.trim()) {
|
|
14608
14936
|
backupPath = `${configPath}.bak.${timestamp}`;
|
|
14609
|
-
await
|
|
14937
|
+
await copyFile2(configPath, backupPath);
|
|
14610
14938
|
}
|
|
14611
14939
|
content = upsertCodexServerBlock(content, apiUrl, expectedRepoUrl);
|
|
14612
14940
|
const preRepair = inspectCodexConfig(content, expectedRepoUrl);
|
|
@@ -14616,7 +14944,7 @@ function codexCommand() {
|
|
|
14616
14944
|
);
|
|
14617
14945
|
content = repaired.content;
|
|
14618
14946
|
removedServers = repaired.removedServers;
|
|
14619
|
-
await
|
|
14947
|
+
await writeFile4(configPath, content, "utf8");
|
|
14620
14948
|
}
|
|
14621
14949
|
const inspection = inspectCodexConfig(content, expectedRepoUrl);
|
|
14622
14950
|
const checks = codexIssuesToChecks(inspection.issues);
|
|
@@ -14698,11 +15026,11 @@ function claudeCommand() {
|
|
|
14698
15026
|
noColor: g.noColor
|
|
14699
15027
|
});
|
|
14700
15028
|
const home = homedir5();
|
|
14701
|
-
const gaDir =
|
|
14702
|
-
const userConfigPath =
|
|
14703
|
-
const settingsPath =
|
|
14704
|
-
const hookConfigPath =
|
|
14705
|
-
const glanceHookPath =
|
|
15029
|
+
const gaDir = join8(home, ".great-arrow");
|
|
15030
|
+
const userConfigPath = join8(home, ".claude.json");
|
|
15031
|
+
const settingsPath = join8(home, ".claude", "settings.json");
|
|
15032
|
+
const hookConfigPath = join8(gaDir, "hook-config.json");
|
|
15033
|
+
const glanceHookPath = join8(gaDir, "claude-glance-hook.mjs");
|
|
14706
15034
|
const checks = [];
|
|
14707
15035
|
const repairs = [];
|
|
14708
15036
|
const backups = [];
|
|
@@ -14719,13 +15047,13 @@ function claudeCommand() {
|
|
|
14719
15047
|
}
|
|
14720
15048
|
};
|
|
14721
15049
|
const backupAndWrite = async (path10, content) => {
|
|
14722
|
-
if (
|
|
15050
|
+
if (existsSync5(path10)) {
|
|
14723
15051
|
const backupPath = `${path10}.bak.${timestamp}`;
|
|
14724
|
-
await
|
|
15052
|
+
await copyFile2(path10, backupPath);
|
|
14725
15053
|
backups.push(backupPath);
|
|
14726
15054
|
}
|
|
14727
|
-
await
|
|
14728
|
-
await
|
|
15055
|
+
await mkdir5(dirname5(path10), { recursive: true });
|
|
15056
|
+
await writeFile4(path10, content, "utf8");
|
|
14729
15057
|
};
|
|
14730
15058
|
let auth = null;
|
|
14731
15059
|
try {
|
|
@@ -14734,8 +15062,8 @@ function claudeCommand() {
|
|
|
14734
15062
|
auth = null;
|
|
14735
15063
|
}
|
|
14736
15064
|
const apiUrl = auth?.apiUrl ?? "https://www.greatarrow.ai";
|
|
14737
|
-
if (
|
|
14738
|
-
let content = await
|
|
15065
|
+
if (existsSync5(userConfigPath)) {
|
|
15066
|
+
let content = await readFile5(userConfigPath, "utf8");
|
|
14739
15067
|
if (options.enforce) {
|
|
14740
15068
|
const repaired = repairClaudeUserConfig(content);
|
|
14741
15069
|
if (repaired.changed.length > 0) {
|
|
@@ -14755,7 +15083,7 @@ function claudeCommand() {
|
|
|
14755
15083
|
});
|
|
14756
15084
|
}
|
|
14757
15085
|
{
|
|
14758
|
-
let content =
|
|
15086
|
+
let content = existsSync5(settingsPath) ? await readFile5(settingsPath, "utf8") : "";
|
|
14759
15087
|
if (options.enforce) {
|
|
14760
15088
|
const glanceCommand = `node "${glanceHookPath}"`;
|
|
14761
15089
|
const repaired = repairClaudeHookSettings(content, glanceCommand);
|
|
@@ -14764,15 +15092,15 @@ function claudeCommand() {
|
|
|
14764
15092
|
content = repaired.content;
|
|
14765
15093
|
repairs.push(...repaired.changed.map((c) => `settings.json: ${c}`));
|
|
14766
15094
|
}
|
|
14767
|
-
if (!
|
|
15095
|
+
if (!existsSync5(glanceHookPath)) {
|
|
14768
15096
|
try {
|
|
14769
15097
|
const res = await fetch(
|
|
14770
15098
|
`${apiUrl}/api/install/claude-glance-hook`,
|
|
14771
15099
|
{ signal: AbortSignal.timeout(1e4) }
|
|
14772
15100
|
);
|
|
14773
15101
|
if (res.ok) {
|
|
14774
|
-
await
|
|
14775
|
-
await
|
|
15102
|
+
await mkdir5(gaDir, { recursive: true });
|
|
15103
|
+
await writeFile4(glanceHookPath, await res.text(), {
|
|
14776
15104
|
mode: 493
|
|
14777
15105
|
});
|
|
14778
15106
|
repairs.push(`downloaded ${glanceHookPath}`);
|
|
@@ -14793,7 +15121,7 @@ function claudeCommand() {
|
|
|
14793
15121
|
}
|
|
14794
15122
|
}
|
|
14795
15123
|
toChecks("Claude hooks", inspectClaudeHookSettings(content).issues);
|
|
14796
|
-
if (!options.enforce && !
|
|
15124
|
+
if (!options.enforce && !existsSync5(glanceHookPath)) {
|
|
14797
15125
|
checks.push({
|
|
14798
15126
|
name: "Glance hook script: missing",
|
|
14799
15127
|
status: "warn",
|
|
@@ -14803,96 +15131,31 @@ function claudeCommand() {
|
|
|
14803
15131
|
}
|
|
14804
15132
|
}
|
|
14805
15133
|
{
|
|
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;
|
|
15134
|
+
const credential = await ensureHookCredential({
|
|
15135
|
+
hookConfigPath,
|
|
15136
|
+
apiUrl,
|
|
15137
|
+
auth,
|
|
15138
|
+
repair: Boolean(options.enforce),
|
|
15139
|
+
write: backupAndWrite
|
|
15140
|
+
});
|
|
15141
|
+
checks.push(...credential.checks);
|
|
15142
|
+
repairs.push(...credential.repairs);
|
|
15143
|
+
}
|
|
15144
|
+
{
|
|
15145
|
+
const suggest = await suggestHookStatus(home);
|
|
15146
|
+
const check = {
|
|
15147
|
+
name: "Tool suggestions (opt-in)",
|
|
15148
|
+
status: "pass",
|
|
15149
|
+
message: suggest.enabled ? "ON \u2014 each prompt's text is sent to Great Arrow for tool suggestions" : "OFF"
|
|
14829
15150
|
};
|
|
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
|
-
}
|
|
15151
|
+
if (suggest.enabled && !suggest.credentialPresent) {
|
|
15152
|
+
check.status = "warn";
|
|
15153
|
+
check.message = "ON but no hook credential \u2014 the hook sends nothing";
|
|
15154
|
+
check.hint = "Run `gad login`, then `gad mcp suggest-tools on`.";
|
|
15155
|
+
} else if (!suggest.enabled) {
|
|
15156
|
+
check.hint = "Opt in with `gad mcp suggest-tools on`.";
|
|
14895
15157
|
}
|
|
15158
|
+
checks.push(check);
|
|
14896
15159
|
}
|
|
14897
15160
|
{
|
|
14898
15161
|
let root = command;
|
|
@@ -14910,23 +15173,23 @@ function claudeCommand() {
|
|
|
14910
15173
|
file: "/Library/Application Support/ClaudeCode/managed-settings.json"
|
|
14911
15174
|
},
|
|
14912
15175
|
{ scope: "user", file: settingsPath },
|
|
14913
|
-
{ scope: "project", file:
|
|
14914
|
-
{ scope: "local", file:
|
|
15176
|
+
{ scope: "project", file: join8(cwd, ".claude", "settings.json") },
|
|
15177
|
+
{ scope: "local", file: join8(cwd, ".claude", "settings.local.json") }
|
|
14915
15178
|
];
|
|
14916
15179
|
const configured = [];
|
|
14917
15180
|
for (const { scope, file } of scopes) {
|
|
14918
|
-
if (!
|
|
15181
|
+
if (!existsSync5(file)) continue;
|
|
14919
15182
|
configured.push(
|
|
14920
15183
|
...collectConfiguredHooks(
|
|
14921
15184
|
scope,
|
|
14922
15185
|
file,
|
|
14923
|
-
await
|
|
15186
|
+
await readFile5(file, "utf8")
|
|
14924
15187
|
)
|
|
14925
15188
|
);
|
|
14926
15189
|
}
|
|
14927
15190
|
const hookIssues = inspectConfiguredHooks(configured, {
|
|
14928
15191
|
knownGadVerbs,
|
|
14929
|
-
fileExists:
|
|
15192
|
+
fileExists: existsSync5
|
|
14930
15193
|
});
|
|
14931
15194
|
toChecks("Configured hooks", hookIssues);
|
|
14932
15195
|
if (configured.length > 0 && hookIssues.length === 0) {
|
|
@@ -14982,10 +15245,10 @@ function claudeCommand() {
|
|
|
14982
15245
|
function defaultJsonClientConfigs() {
|
|
14983
15246
|
const home = homedir5();
|
|
14984
15247
|
return [
|
|
14985
|
-
{ name: "Claude Code", path:
|
|
14986
|
-
{ name: "Cursor", path:
|
|
14987
|
-
{ name: "Kiro", path:
|
|
14988
|
-
{ name: "Gemini CLI", path:
|
|
15248
|
+
{ name: "Claude Code", path: join8(home, ".claude.json") },
|
|
15249
|
+
{ name: "Cursor", path: join8(home, ".cursor", "mcp.json") },
|
|
15250
|
+
{ name: "Kiro", path: join8(home, ".kiro", "settings", "mcp.json") },
|
|
15251
|
+
{ name: "Gemini CLI", path: join8(home, ".gemini", "settings.json") }
|
|
14989
15252
|
];
|
|
14990
15253
|
}
|
|
14991
15254
|
function clientsCommand() {
|
|
@@ -15011,8 +15274,8 @@ function clientsCommand() {
|
|
|
15011
15274
|
const checks = [];
|
|
15012
15275
|
const results = [];
|
|
15013
15276
|
for (const { name, path: path10 } of defaultJsonClientConfigs()) {
|
|
15014
|
-
if (!
|
|
15015
|
-
let content = await
|
|
15277
|
+
if (!existsSync5(path10)) continue;
|
|
15278
|
+
let content = await readFile5(path10, "utf8");
|
|
15016
15279
|
let backupPath = null;
|
|
15017
15280
|
let removedServers = [];
|
|
15018
15281
|
if (options.enforce) {
|
|
@@ -15020,14 +15283,14 @@ function clientsCommand() {
|
|
|
15020
15283
|
if (!pre.parseError && pre.staleRepoServers.length > 0) {
|
|
15021
15284
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
15022
15285
|
backupPath = `${path10}.bak.${timestamp}`;
|
|
15023
|
-
await
|
|
15286
|
+
await copyFile2(path10, backupPath);
|
|
15024
15287
|
const repaired = repairJsonMcpConfig(
|
|
15025
15288
|
content,
|
|
15026
15289
|
pre.staleRepoServers
|
|
15027
15290
|
);
|
|
15028
15291
|
content = repaired.content;
|
|
15029
15292
|
removedServers = repaired.removedServers;
|
|
15030
|
-
await
|
|
15293
|
+
await writeFile4(path10, content, "utf8");
|
|
15031
15294
|
}
|
|
15032
15295
|
}
|
|
15033
15296
|
const inspection = inspectJsonMcpConfig(content, expectedRepoUrl);
|
|
@@ -15522,6 +15785,107 @@ async function runStdioBridge(options) {
|
|
|
15522
15785
|
});
|
|
15523
15786
|
}
|
|
15524
15787
|
|
|
15788
|
+
// src/commands/suggest-tools.ts
|
|
15789
|
+
import { homedir as homedir6 } from "node:os";
|
|
15790
|
+
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.";
|
|
15791
|
+
async function enableSuggestTools(home, deps = {}) {
|
|
15792
|
+
const packagedScript = deps.packagedScript === void 0 ? packagedSuggestHookPath() : deps.packagedScript;
|
|
15793
|
+
if (!packagedScript) {
|
|
15794
|
+
throw validationError(
|
|
15795
|
+
"The tool-suggestion hook script is missing from this @great-arrow/cli install.",
|
|
15796
|
+
"Reinstall with `npm install -g @great-arrow/cli@latest`."
|
|
15797
|
+
);
|
|
15798
|
+
}
|
|
15799
|
+
await enableSuggestHook({ home, packagedScript });
|
|
15800
|
+
let auth = null;
|
|
15801
|
+
try {
|
|
15802
|
+
auth = await (deps.resolveAuth ?? resolveAuth)();
|
|
15803
|
+
} catch {
|
|
15804
|
+
auth = null;
|
|
15805
|
+
}
|
|
15806
|
+
const credential = await ensureHookCredential({
|
|
15807
|
+
hookConfigPath: suggestHookPaths(home).hookConfigPath,
|
|
15808
|
+
apiUrl: auth?.apiUrl ?? DEFAULT_API_URL,
|
|
15809
|
+
auth,
|
|
15810
|
+
repair: true,
|
|
15811
|
+
write: (path10, content) => atomicWrite(path10, content, 384),
|
|
15812
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
15813
|
+
});
|
|
15814
|
+
return { verified: credential.verified };
|
|
15815
|
+
}
|
|
15816
|
+
async function applySuggestToolsChoice(choice, home = homedir6(), deps = {}) {
|
|
15817
|
+
const out = deps.out ?? createFormatter({
|
|
15818
|
+
json: false,
|
|
15819
|
+
quiet: false,
|
|
15820
|
+
isTTY: isTTY(),
|
|
15821
|
+
noColor: false
|
|
15822
|
+
});
|
|
15823
|
+
let verified = true;
|
|
15824
|
+
const outcome = await runSuggestHookOptIn({
|
|
15825
|
+
choice,
|
|
15826
|
+
interactive: deps.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
15827
|
+
print: (line) => out.info(line),
|
|
15828
|
+
confirm: deps.confirm ?? confirm,
|
|
15829
|
+
enable: async () => {
|
|
15830
|
+
verified = (await enableSuggestTools(home, deps)).verified;
|
|
15831
|
+
},
|
|
15832
|
+
disable: () => disableSuggestHook({ home })
|
|
15833
|
+
});
|
|
15834
|
+
const { settingsPath } = suggestHookPaths(home);
|
|
15835
|
+
if (outcome === "enabled") {
|
|
15836
|
+
out.success(`Tool suggestions ON (registered in ${settingsPath}).`);
|
|
15837
|
+
if (!verified) out.warn(HOOK_CREDENTIAL_FIX_LINE);
|
|
15838
|
+
} else if (outcome === "disabled") {
|
|
15839
|
+
out.success("Tool suggestions OFF (our hook entry removed).");
|
|
15840
|
+
}
|
|
15841
|
+
return outcome;
|
|
15842
|
+
}
|
|
15843
|
+
function suggestToolsCommand() {
|
|
15844
|
+
return new Command("suggest-tools").description(
|
|
15845
|
+
"Opt-in Claude Code prompt hook that names Great Arrow tools which may help (sends each prompt's text)"
|
|
15846
|
+
).argument("[action]", "on | off | status", "status").option("--yes", "Accept the disclosure without an interactive prompt").option("--json", "Output status as JSON").action(
|
|
15847
|
+
async (action, options) => {
|
|
15848
|
+
const home = homedir6();
|
|
15849
|
+
if (action === "status") {
|
|
15850
|
+
const status = await suggestHookStatus(home);
|
|
15851
|
+
if (options.json) {
|
|
15852
|
+
process.stdout.write(JSON.stringify(status) + "\n");
|
|
15853
|
+
return;
|
|
15854
|
+
}
|
|
15855
|
+
process.stdout.write(
|
|
15856
|
+
`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"
|
|
15857
|
+
);
|
|
15858
|
+
return;
|
|
15859
|
+
}
|
|
15860
|
+
if (action === "off") {
|
|
15861
|
+
await applySuggestToolsChoice(false, home);
|
|
15862
|
+
return;
|
|
15863
|
+
}
|
|
15864
|
+
if (action === "on") {
|
|
15865
|
+
const outcome = await applySuggestToolsChoice(
|
|
15866
|
+
options.yes ? true : void 0,
|
|
15867
|
+
home
|
|
15868
|
+
);
|
|
15869
|
+
if (outcome === "unchanged") {
|
|
15870
|
+
if (!(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
15871
|
+
process.stdout.write(`${SUGGEST_HOOK_DISCLOSURE}
|
|
15872
|
+
|
|
15873
|
+
`);
|
|
15874
|
+
}
|
|
15875
|
+
process.stdout.write(
|
|
15876
|
+
"Tool suggestions left OFF. To turn on without a prompt: `gad mcp suggest-tools on --yes`.\n"
|
|
15877
|
+
);
|
|
15878
|
+
}
|
|
15879
|
+
return;
|
|
15880
|
+
}
|
|
15881
|
+
throw validationError(
|
|
15882
|
+
`Unknown action "${action}".`,
|
|
15883
|
+
"Use one of: on, off, status."
|
|
15884
|
+
);
|
|
15885
|
+
}
|
|
15886
|
+
);
|
|
15887
|
+
}
|
|
15888
|
+
|
|
15525
15889
|
// src/commands/mcp.ts
|
|
15526
15890
|
var SUPPORTED_CLIENTS = [
|
|
15527
15891
|
"claude",
|
|
@@ -15631,33 +15995,33 @@ async function pathExists(path10) {
|
|
|
15631
15995
|
}
|
|
15632
15996
|
}
|
|
15633
15997
|
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 =
|
|
15998
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
15999
|
+
const { join: join10 } = await import("node:path");
|
|
16000
|
+
const { chmod: chmod2, copyFile: copyFile3, mkdir: mkdir7, readFile: readFile7, writeFile: writeFile6 } = await import("node:fs/promises");
|
|
16001
|
+
const codexDir = join10(homedir8(), ".codex");
|
|
16002
|
+
const configPath = join10(codexDir, "config.toml");
|
|
16003
|
+
const envPath = join10(codexDir, ".env");
|
|
15640
16004
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
15641
|
-
await
|
|
15642
|
-
const existingConfig = await pathExists(configPath) ? await
|
|
16005
|
+
await mkdir7(codexDir, { recursive: true });
|
|
16006
|
+
const existingConfig = await pathExists(configPath) ? await readFile7(configPath, "utf8") : "";
|
|
15643
16007
|
if (existingConfig.trim()) {
|
|
15644
|
-
await
|
|
16008
|
+
await copyFile3(configPath, `${configPath}.bak.${timestamp}`);
|
|
15645
16009
|
}
|
|
15646
|
-
await
|
|
16010
|
+
await writeFile6(
|
|
15647
16011
|
configPath,
|
|
15648
16012
|
upsertCodexServerBlock(existingConfig, apiUrl, repoUrl),
|
|
15649
16013
|
"utf8"
|
|
15650
16014
|
);
|
|
15651
|
-
const existingEnv = await pathExists(envPath) ? await
|
|
16015
|
+
const existingEnv = await pathExists(envPath) ? await readFile7(envPath, "utf8") : "";
|
|
15652
16016
|
if (existingEnv.trim()) {
|
|
15653
|
-
await
|
|
16017
|
+
await copyFile3(envPath, `${envPath}.bak.${timestamp}`);
|
|
15654
16018
|
}
|
|
15655
|
-
await
|
|
16019
|
+
await writeFile6(
|
|
15656
16020
|
envPath,
|
|
15657
16021
|
upsertEnvValue(existingEnv, "GREAT_ARROW_TOKEN", token),
|
|
15658
16022
|
"utf8"
|
|
15659
16023
|
);
|
|
15660
|
-
await
|
|
16024
|
+
await chmod2(envPath, 384);
|
|
15661
16025
|
return { configPath, envPath };
|
|
15662
16026
|
}
|
|
15663
16027
|
var startCommand = new Command("start").description(
|
|
@@ -15884,6 +16248,12 @@ var installCommand = new Command("install").description("Generate MCP configurat
|
|
|
15884
16248
|
).option("--stdout", "Print config to stdout instead of writing file").option(
|
|
15885
16249
|
"--repo-url <url>",
|
|
15886
16250
|
"Repo URL to pin MCP requests to (defaults to current git origin)"
|
|
16251
|
+
).option(
|
|
16252
|
+
"--suggest-tools",
|
|
16253
|
+
"claude only: turn on the opt-in prompt hook that sends each prompt's text to Great Arrow for tool suggestions"
|
|
16254
|
+
).option(
|
|
16255
|
+
"--no-suggest-tools",
|
|
16256
|
+
"claude only: turn the tool-suggestion prompt hook off (removes only its entry)"
|
|
15887
16257
|
).action(
|
|
15888
16258
|
async (clientName, options) => {
|
|
15889
16259
|
const fmt = createFormatter({
|
|
@@ -15935,15 +16305,15 @@ var installCommand = new Command("install").description("Generate MCP configurat
|
|
|
15935
16305
|
process.stdout.write(configJson2 + "\n");
|
|
15936
16306
|
return;
|
|
15937
16307
|
}
|
|
15938
|
-
const { homedir:
|
|
15939
|
-
const { join:
|
|
15940
|
-
const { mkdir:
|
|
15941
|
-
const geminiDir =
|
|
15942
|
-
const mcpPath =
|
|
15943
|
-
await
|
|
16308
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
16309
|
+
const { join: join11 } = await import("node:path");
|
|
16310
|
+
const { mkdir: mkdir7, readFile: readFile7, writeFile: writeFile7 } = await import("node:fs/promises");
|
|
16311
|
+
const geminiDir = join11(homedir8(), ".gemini");
|
|
16312
|
+
const mcpPath = join11(geminiDir, "settings.json");
|
|
16313
|
+
await mkdir7(geminiDir, { recursive: true });
|
|
15944
16314
|
let existingConfig = {};
|
|
15945
16315
|
try {
|
|
15946
|
-
const content = await
|
|
16316
|
+
const content = await readFile7(mcpPath, "utf-8");
|
|
15947
16317
|
existingConfig = JSON.parse(content);
|
|
15948
16318
|
} catch {
|
|
15949
16319
|
existingConfig = {};
|
|
@@ -15955,20 +16325,20 @@ var installCommand = new Command("install").description("Generate MCP configurat
|
|
|
15955
16325
|
...config2.mcpServers
|
|
15956
16326
|
}
|
|
15957
16327
|
};
|
|
15958
|
-
await
|
|
16328
|
+
await writeFile7(
|
|
15959
16329
|
mcpPath,
|
|
15960
16330
|
JSON.stringify(existingConfig, null, 2) + "\n",
|
|
15961
16331
|
"utf-8"
|
|
15962
16332
|
);
|
|
15963
16333
|
fmt.success(`Wrote gemini MCP config to ${mcpPath}`);
|
|
15964
|
-
const agyMcpDir =
|
|
16334
|
+
const agyMcpDir = join11(
|
|
15965
16335
|
geminiDir,
|
|
15966
16336
|
"antigravity-cli",
|
|
15967
16337
|
"mcp",
|
|
15968
16338
|
MCP_SERVER_NAME
|
|
15969
16339
|
);
|
|
15970
|
-
await
|
|
15971
|
-
const instructionsPath =
|
|
16340
|
+
await mkdir7(agyMcpDir, { recursive: true });
|
|
16341
|
+
const instructionsPath = join11(agyMcpDir, "instructions.md");
|
|
15972
16342
|
const instructionsContent = `You are connected to greatarrow.ai - the user's private workspace memory + integrations brain.
|
|
15973
16343
|
|
|
15974
16344
|
CRITICAL DIRECTIVE (MANDATORY STANDARD):
|
|
@@ -15984,7 +16354,7 @@ THREE-LAYER LIVE TASK TRACKING:
|
|
|
15984
16354
|
- Maintain client-native harness plans, Great Arrow Kanban cards (\`todo_create\`, \`todo_update\`), and resume states.
|
|
15985
16355
|
|
|
15986
16356
|
Always assume full autonomy to leverage these tools to their maximum extent.`;
|
|
15987
|
-
await
|
|
16357
|
+
await writeFile7(instructionsPath, instructionsContent, "utf-8");
|
|
15988
16358
|
fmt.success(`Wrote gemini MCP instructions to ${instructionsPath}`);
|
|
15989
16359
|
return;
|
|
15990
16360
|
}
|
|
@@ -15999,25 +16369,28 @@ Always assume full autonomy to leverage these tools to their maximum extent.`;
|
|
|
15999
16369
|
process.stdout.write(configJson + "\n");
|
|
16000
16370
|
return;
|
|
16001
16371
|
}
|
|
16002
|
-
const { writeFile:
|
|
16003
|
-
const { join:
|
|
16004
|
-
const outputPath =
|
|
16005
|
-
await
|
|
16372
|
+
const { writeFile: writeFile6 } = await import("node:fs/promises");
|
|
16373
|
+
const { join: join10 } = await import("node:path");
|
|
16374
|
+
const outputPath = join10(process.cwd(), ".mcp.json");
|
|
16375
|
+
await writeFile6(outputPath, configJson + "\n", { mode: 384 });
|
|
16006
16376
|
fmt.success(`Wrote ${clientName} MCP config to .mcp.json`);
|
|
16007
16377
|
if (await mcpJsonAtRiskOfCommit(process.cwd())) {
|
|
16008
16378
|
fmt.warn(
|
|
16009
16379
|
".mcp.json contains an API token and is NOT gitignored here \u2014 add '.mcp.json' to .gitignore before committing."
|
|
16010
16380
|
);
|
|
16011
16381
|
}
|
|
16382
|
+
if (clientName === "claude") {
|
|
16383
|
+
await applySuggestToolsChoice(options.suggestTools);
|
|
16384
|
+
}
|
|
16012
16385
|
}
|
|
16013
16386
|
);
|
|
16014
16387
|
var KEYCHAIN_SERVICE = "GreatArrowDigital";
|
|
16015
16388
|
var KEYCHAIN_ACCOUNT = "api-token";
|
|
16016
16389
|
var OAUTH_CRED_FILENAME = "oauth-credential.json";
|
|
16017
16390
|
async function readIfExists(path10) {
|
|
16018
|
-
const { readFile:
|
|
16391
|
+
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
16019
16392
|
try {
|
|
16020
|
-
return await
|
|
16393
|
+
return await readFile7(path10, "utf8");
|
|
16021
16394
|
} catch {
|
|
16022
16395
|
return null;
|
|
16023
16396
|
}
|
|
@@ -16032,13 +16405,13 @@ async function readKeychainOnly() {
|
|
|
16032
16405
|
}
|
|
16033
16406
|
}
|
|
16034
16407
|
async function resolveProxyScript(override) {
|
|
16035
|
-
const { homedir:
|
|
16036
|
-
const { join:
|
|
16408
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
16409
|
+
const { join: join10 } = await import("node:path");
|
|
16037
16410
|
const candidates = [
|
|
16038
16411
|
override,
|
|
16039
16412
|
process.env.GAD_PROXY_SCRIPT,
|
|
16040
|
-
|
|
16041
|
-
|
|
16413
|
+
join10(homedir8(), ".great-arrow", "mcp-server.mjs"),
|
|
16414
|
+
join10(process.cwd(), "scripts", "mcp-server.mjs")
|
|
16042
16415
|
].filter((c) => Boolean(c));
|
|
16043
16416
|
for (const candidate of candidates) {
|
|
16044
16417
|
if (await pathExists(candidate)) return candidate;
|
|
@@ -16062,13 +16435,13 @@ async function runProxyDiagnostics(scriptPath) {
|
|
|
16062
16435
|
}
|
|
16063
16436
|
}
|
|
16064
16437
|
async function findLegacyStores(cwd) {
|
|
16065
|
-
const { join:
|
|
16438
|
+
const { join: join10 } = await import("node:path");
|
|
16066
16439
|
const { readdir } = await import("node:fs/promises");
|
|
16067
16440
|
const dirs = /* @__PURE__ */ new Set([cwd]);
|
|
16068
16441
|
try {
|
|
16069
|
-
const worktreeRoot =
|
|
16442
|
+
const worktreeRoot = join10(cwd, ".claude", "worktrees");
|
|
16070
16443
|
for (const entry of await readdir(worktreeRoot, { withFileTypes: true })) {
|
|
16071
|
-
if (entry.isDirectory()) dirs.add(
|
|
16444
|
+
if (entry.isDirectory()) dirs.add(join10(worktreeRoot, entry.name));
|
|
16072
16445
|
}
|
|
16073
16446
|
} catch (err) {
|
|
16074
16447
|
debugSwallow("mcp.findLegacyStores(worktrees)", err);
|
|
@@ -16090,18 +16463,18 @@ async function findLegacyStores(cwd) {
|
|
|
16090
16463
|
}
|
|
16091
16464
|
const stores = [];
|
|
16092
16465
|
for (const dir of dirs) {
|
|
16093
|
-
const path10 =
|
|
16466
|
+
const path10 = join10(dir, ".mcp", OAUTH_CRED_FILENAME);
|
|
16094
16467
|
const raw = await readIfExists(path10);
|
|
16095
16468
|
if (raw !== null) stores.push({ path: path10, raw });
|
|
16096
16469
|
}
|
|
16097
16470
|
return stores;
|
|
16098
16471
|
}
|
|
16099
16472
|
async function scanConnections() {
|
|
16100
|
-
const { homedir:
|
|
16101
|
-
const { join:
|
|
16102
|
-
const home =
|
|
16473
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
16474
|
+
const { join: join10 } = await import("node:path");
|
|
16475
|
+
const home = homedir8();
|
|
16103
16476
|
const hookSettings = await readIfExists(
|
|
16104
|
-
|
|
16477
|
+
join10(home, ".claude", "settings.json")
|
|
16105
16478
|
);
|
|
16106
16479
|
const hooksInstalled = hookSettings ? hasGreatArrowHooks(hookSettings) : false;
|
|
16107
16480
|
const entries = [];
|
|
@@ -16134,29 +16507,29 @@ async function probeEndpoint() {
|
|
|
16134
16507
|
}
|
|
16135
16508
|
}
|
|
16136
16509
|
async function applyMigration(plan) {
|
|
16137
|
-
const { homedir:
|
|
16138
|
-
const { join:
|
|
16139
|
-
const { mkdir:
|
|
16510
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
16511
|
+
const { join: join10 } = await import("node:path");
|
|
16512
|
+
const { mkdir: mkdir7, rename: rename2, writeFile: writeFile6 } = await import("node:fs/promises");
|
|
16140
16513
|
const applied = [];
|
|
16141
|
-
const dir =
|
|
16142
|
-
await
|
|
16514
|
+
const dir = join10(homedir8(), ".great-arrow");
|
|
16515
|
+
await mkdir7(dir, { recursive: true, mode: 448 });
|
|
16143
16516
|
if (plan.seedFrom) {
|
|
16144
16517
|
const raw = await readIfExists(plan.seedFrom);
|
|
16145
16518
|
if (raw !== null) {
|
|
16146
|
-
const target =
|
|
16519
|
+
const target = join10(dir, OAUTH_CRED_FILENAME);
|
|
16147
16520
|
const tmp = `${target}.tmp`;
|
|
16148
|
-
await
|
|
16149
|
-
await
|
|
16521
|
+
await writeFile6(tmp, raw, { mode: 384 });
|
|
16522
|
+
await rename2(tmp, target);
|
|
16150
16523
|
applied.push(`Copied ${plan.seedFrom} \u2192 ${target}`);
|
|
16151
16524
|
}
|
|
16152
16525
|
}
|
|
16153
16526
|
if (plan.syncKeychainToFile) {
|
|
16154
16527
|
const raw = await readKeychainOnly();
|
|
16155
16528
|
if (raw) {
|
|
16156
|
-
const target =
|
|
16529
|
+
const target = join10(dir, "credential");
|
|
16157
16530
|
const tmp = `${target}.tmp`;
|
|
16158
|
-
await
|
|
16159
|
-
await
|
|
16531
|
+
await writeFile6(tmp, raw, { mode: 384 });
|
|
16532
|
+
await rename2(tmp, target);
|
|
16160
16533
|
applied.push(`Wrote the keychain credential to ${target}`);
|
|
16161
16534
|
}
|
|
16162
16535
|
}
|
|
@@ -16179,19 +16552,19 @@ var doctorSubcommand = new Command("doctor").description(
|
|
|
16179
16552
|
noColor: false
|
|
16180
16553
|
});
|
|
16181
16554
|
const c = createColor(jsonFlag);
|
|
16182
|
-
const { homedir:
|
|
16183
|
-
const { join:
|
|
16184
|
-
const home =
|
|
16555
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
16556
|
+
const { join: join10 } = await import("node:path");
|
|
16557
|
+
const home = homedir8();
|
|
16185
16558
|
const proxyPath = await resolveProxyScript(options.proxy);
|
|
16186
16559
|
const diagnostics = proxyPath ? await runProxyDiagnostics(proxyPath) : null;
|
|
16187
16560
|
const entries = await scanConnections();
|
|
16188
16561
|
const duplicates = detectDuplicates(entries);
|
|
16189
16562
|
const keychain = await readKeychainOnly();
|
|
16190
16563
|
const credentialFile = await readIfExists(
|
|
16191
|
-
|
|
16564
|
+
join10(home, ".great-arrow", "credential")
|
|
16192
16565
|
);
|
|
16193
16566
|
const userStore = await readIfExists(
|
|
16194
|
-
|
|
16567
|
+
join10(home, ".great-arrow", OAUTH_CRED_FILENAME)
|
|
16195
16568
|
);
|
|
16196
16569
|
const legacyStores = await findLegacyStores(process.cwd());
|
|
16197
16570
|
const findings = [
|
|
@@ -16294,7 +16667,7 @@ ${c.dim("Credential migration")}
|
|
|
16294
16667
|
}
|
|
16295
16668
|
}
|
|
16296
16669
|
);
|
|
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);
|
|
16670
|
+
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
16671
|
var mcp_default = mcpCommand;
|
|
16299
16672
|
|
|
16300
16673
|
// src/commands/smoke.ts
|
|
@@ -16615,9 +16988,9 @@ function askCommand() {
|
|
|
16615
16988
|
}
|
|
16616
16989
|
|
|
16617
16990
|
// src/lib/glance-footer.ts
|
|
16618
|
-
import { readFile as
|
|
16619
|
-
import { homedir as
|
|
16620
|
-
import { join as
|
|
16991
|
+
import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir6 } from "node:fs/promises";
|
|
16992
|
+
import { homedir as homedir7 } from "node:os";
|
|
16993
|
+
import { join as join9, dirname as dirname6 } from "node:path";
|
|
16621
16994
|
|
|
16622
16995
|
// src/lib/seen.ts
|
|
16623
16996
|
var SEEN_TIMEOUT_MS = 1e3;
|
|
@@ -17004,11 +17377,11 @@ function shouldRequestTip(input) {
|
|
|
17004
17377
|
return now - state.lastTipAt >= TIP_THROTTLE_INTERVAL_MS;
|
|
17005
17378
|
}
|
|
17006
17379
|
function getGlanceStatePath() {
|
|
17007
|
-
return
|
|
17380
|
+
return join9(homedir7(), ".gad", "glance-state.json");
|
|
17008
17381
|
}
|
|
17009
17382
|
async function loadGlanceState() {
|
|
17010
17383
|
try {
|
|
17011
|
-
const content = await
|
|
17384
|
+
const content = await readFile6(getGlanceStatePath(), "utf-8");
|
|
17012
17385
|
const parsed = JSON.parse(content);
|
|
17013
17386
|
if (typeof parsed === "object" && parsed !== null) {
|
|
17014
17387
|
return parsed;
|
|
@@ -17020,8 +17393,8 @@ async function loadGlanceState() {
|
|
|
17020
17393
|
}
|
|
17021
17394
|
async function saveGlanceState(state) {
|
|
17022
17395
|
const path10 = getGlanceStatePath();
|
|
17023
|
-
await
|
|
17024
|
-
await
|
|
17396
|
+
await mkdir6(dirname6(path10), { recursive: true });
|
|
17397
|
+
await writeFile5(path10, JSON.stringify(state), "utf-8");
|
|
17025
17398
|
}
|
|
17026
17399
|
function glanceItemUrl(href, apiUrl) {
|
|
17027
17400
|
if (!href) return null;
|
|
@@ -18903,7 +19276,7 @@ function completionsCommand() {
|
|
|
18903
19276
|
// node_modules/.pnpm/update-notifier@7.3.1/node_modules/update-notifier/update-notifier.js
|
|
18904
19277
|
import process11 from "node:process";
|
|
18905
19278
|
import { spawn } from "node:child_process";
|
|
18906
|
-
import { fileURLToPath as
|
|
19279
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
18907
19280
|
import path9 from "node:path";
|
|
18908
19281
|
import { format } from "node:util";
|
|
18909
19282
|
|
|
@@ -21054,7 +21427,7 @@ var isNpmOrYarn = isNpm || isYarn;
|
|
|
21054
21427
|
// node_modules/.pnpm/is-installed-globally@1.0.0/node_modules/is-installed-globally/index.js
|
|
21055
21428
|
import fs5 from "node:fs";
|
|
21056
21429
|
import path8 from "node:path";
|
|
21057
|
-
import { fileURLToPath } from "node:url";
|
|
21430
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
21058
21431
|
|
|
21059
21432
|
// node_modules/.pnpm/global-directory@4.0.1/node_modules/global-directory/index.js
|
|
21060
21433
|
var import_ini = __toESM(require_ini2(), 1);
|
|
@@ -21158,7 +21531,7 @@ function isPathInside(childPath, parentPath) {
|
|
|
21158
21531
|
}
|
|
21159
21532
|
|
|
21160
21533
|
// node_modules/.pnpm/is-installed-globally@1.0.0/node_modules/is-installed-globally/index.js
|
|
21161
|
-
var __dirname2 = path8.dirname(
|
|
21534
|
+
var __dirname2 = path8.dirname(fileURLToPath2(import.meta.url));
|
|
21162
21535
|
var isInstalledGlobally = (() => {
|
|
21163
21536
|
try {
|
|
21164
21537
|
return isPathInside(__dirname2, global_directory_default.yarn.packages) || isPathInside(__dirname2, fs5.realpathSync(global_directory_default.npm.packages));
|
|
@@ -22140,7 +22513,7 @@ function pupa(template, data, { ignoreMissing = false, transform = ({ value }) =
|
|
|
22140
22513
|
}
|
|
22141
22514
|
|
|
22142
22515
|
// node_modules/.pnpm/update-notifier@7.3.1/node_modules/update-notifier/update-notifier.js
|
|
22143
|
-
var __dirname3 = path9.dirname(
|
|
22516
|
+
var __dirname3 = path9.dirname(fileURLToPath3(import.meta.url));
|
|
22144
22517
|
var ONE_DAY = 1e3 * 60 * 60 * 24;
|
|
22145
22518
|
var UpdateNotifier = class {
|
|
22146
22519
|
// Public
|