@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/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 rename(from, to, cb) {
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(rename, fs$rename);
128
- return rename;
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 = readFile5;
533
- function readFile5(path10, options, cb) {
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 = writeFile5;
550
- function writeFile5(path10, data, options, cb) {
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 = copyFile2;
586
- function copyFile2(src, dest, flags, cb) {
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 join9 = __require("path").join;
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
- join9(etc, name, "config"),
1993
- join9(etc, name + "rc")
1992
+ join10(etc, name, "config"),
1993
+ join10(etc, name + "rc")
1994
1994
  ].forEach(addConfigFile);
1995
1995
  if (home)
1996
1996
  [
1997
- join9(home, ".config", name, "config"),
1998
- join9(home, ".config", name),
1999
- join9(home, "." + name, "config"),
2000
- join9(home, "." + name + "rc")
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 rename(from, to, cb) {
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(rename, fs$rename);
2102
- return rename;
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 = readFile5;
2507
- function readFile5(path10, options, cb) {
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 = writeFile5;
2524
- function writeFile5(path10, data, options, cb) {
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 = copyFile2;
2560
- function copyFile2(src, dest, flags, cb) {
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 dirname6 = path10.dirname(name2);
3339
- if (dirname6 === name2) {
3338
+ const dirname7 = path10.dirname(name2);
3339
+ if (dirname7 === name2) {
3340
3340
  return original;
3341
3341
  }
3342
- return find(dirname6, original);
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.48";
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 existsSync3 } from "node:fs";
13029
- import { copyFile, mkdir as mkdir4, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
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 dirname4, join as join7 } from "node:path";
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 asObject2(value) {
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 = asObject2(parsed[serversKey]);
14289
+ const top = asObject3(parsed[serversKey]);
14011
14290
  if (top) maps.push({ servers: top });
14012
- const projects = asObject2(parsed.projects);
14291
+ const projects = asObject3(parsed.projects);
14013
14292
  if (projects) {
14014
14293
  for (const [dir, value] of Object.entries(projects)) {
14015
- const section = asObject2(value);
14016
- const servers = section ? asObject2(section[serversKey]) : null;
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 = asObject2(JSON.parse(content));
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 = asObject2(value);
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 = asObject2(JSON.parse(settingsContent));
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 = asObject2(JSON.parse(trimmed));
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 join7(homedir5(), ".codex", "config.toml");
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 = existsSync3(configPath) ? await readFile3(configPath, "utf8") : "";
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 mkdir4(dirname4(configPath), { recursive: true });
14885
+ await mkdir5(dirname5(configPath), { recursive: true });
14607
14886
  if (content.trim()) {
14608
14887
  backupPath = `${configPath}.bak.${timestamp}`;
14609
- await copyFile(configPath, backupPath);
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 writeFile3(configPath, content, "utf8");
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 = join7(home, ".great-arrow");
14702
- const userConfigPath = join7(home, ".claude.json");
14703
- const settingsPath = join7(home, ".claude", "settings.json");
14704
- const hookConfigPath = join7(gaDir, "hook-config.json");
14705
- const glanceHookPath = join7(gaDir, "claude-glance-hook.mjs");
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 (existsSync3(path10)) {
15001
+ if (existsSync5(path10)) {
14723
15002
  const backupPath = `${path10}.bak.${timestamp}`;
14724
- await copyFile(path10, backupPath);
15003
+ await copyFile2(path10, backupPath);
14725
15004
  backups.push(backupPath);
14726
15005
  }
14727
- await mkdir4(dirname4(path10), { recursive: true });
14728
- await writeFile3(path10, content, "utf8");
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 (existsSync3(userConfigPath)) {
14738
- let content = await readFile3(userConfigPath, "utf8");
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 = existsSync3(settingsPath) ? await readFile3(settingsPath, "utf8") : "";
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 (!existsSync3(glanceHookPath)) {
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 mkdir4(gaDir, { recursive: true });
14775
- await writeFile3(glanceHookPath, await res.text(), {
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 && !existsSync3(glanceHookPath)) {
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
- let url = null;
14807
- let token = null;
14808
- if (existsSync3(hookConfigPath)) {
14809
- const inspection = inspectHookConfig(
14810
- await readFile3(hookConfigPath, "utf8")
14811
- );
14812
- url = inspection.url;
14813
- token = inspection.token;
14814
- toChecks("Hook credential", inspection.issues);
14815
- } else {
14816
- checks.push({
14817
- name: "Hook credential: not found",
14818
- status: "warn",
14819
- message: `${hookConfigPath} does not exist \u2014 hooks cannot authenticate`,
14820
- hint: "Run with --enforce to write it from your gad login credential."
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
- let tokenOk = false;
14831
- if (token) {
14832
- try {
14833
- const status = await probe(token, url ?? apiUrl);
14834
- tokenOk = status >= 200 && status < 300;
14835
- if (status === 401 || status === 403) {
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: join7(cwd, ".claude", "settings.json") },
14914
- { scope: "local", file: join7(cwd, ".claude", "settings.local.json") }
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 (!existsSync3(file)) continue;
15132
+ if (!existsSync5(file)) continue;
14919
15133
  configured.push(
14920
15134
  ...collectConfiguredHooks(
14921
15135
  scope,
14922
15136
  file,
14923
- await readFile3(file, "utf8")
15137
+ await readFile5(file, "utf8")
14924
15138
  )
14925
15139
  );
14926
15140
  }
14927
15141
  const hookIssues = inspectConfiguredHooks(configured, {
14928
15142
  knownGadVerbs,
14929
- fileExists: existsSync3
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: join7(home, ".claude.json") },
14986
- { name: "Cursor", path: join7(home, ".cursor", "mcp.json") },
14987
- { name: "Kiro", path: join7(home, ".kiro", "settings", "mcp.json") },
14988
- { name: "Gemini CLI", path: join7(home, ".gemini", "settings.json") }
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 (!existsSync3(path10)) continue;
15015
- let content = await readFile3(path10, "utf8");
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 copyFile(path10, backupPath);
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 writeFile3(path10, content, "utf8");
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: homedir7 } = await import("node:os");
15635
- const { join: join9 } = await import("node:path");
15636
- const { chmod, copyFile: copyFile2, mkdir: mkdir6, readFile: readFile5, writeFile: writeFile5 } = await import("node:fs/promises");
15637
- const codexDir = join9(homedir7(), ".codex");
15638
- const configPath = join9(codexDir, "config.toml");
15639
- const envPath = join9(codexDir, ".env");
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 mkdir6(codexDir, { recursive: true });
15642
- const existingConfig = await pathExists(configPath) ? await readFile5(configPath, "utf8") : "";
15956
+ await mkdir7(codexDir, { recursive: true });
15957
+ const existingConfig = await pathExists(configPath) ? await readFile7(configPath, "utf8") : "";
15643
15958
  if (existingConfig.trim()) {
15644
- await copyFile2(configPath, `${configPath}.bak.${timestamp}`);
15959
+ await copyFile3(configPath, `${configPath}.bak.${timestamp}`);
15645
15960
  }
15646
- await writeFile5(
15961
+ await writeFile6(
15647
15962
  configPath,
15648
15963
  upsertCodexServerBlock(existingConfig, apiUrl, repoUrl),
15649
15964
  "utf8"
15650
15965
  );
15651
- const existingEnv = await pathExists(envPath) ? await readFile5(envPath, "utf8") : "";
15966
+ const existingEnv = await pathExists(envPath) ? await readFile7(envPath, "utf8") : "";
15652
15967
  if (existingEnv.trim()) {
15653
- await copyFile2(envPath, `${envPath}.bak.${timestamp}`);
15968
+ await copyFile3(envPath, `${envPath}.bak.${timestamp}`);
15654
15969
  }
15655
- await writeFile5(
15970
+ await writeFile6(
15656
15971
  envPath,
15657
15972
  upsertEnvValue(existingEnv, "GREAT_ARROW_TOKEN", token),
15658
15973
  "utf8"
15659
15974
  );
15660
- await chmod(envPath, 384);
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: homedir7 } = await import("node:os");
15939
- const { join: join10 } = await import("node:path");
15940
- const { mkdir: mkdir6, readFile: readFile5, writeFile: writeFile6 } = await import("node:fs/promises");
15941
- const geminiDir = join10(homedir7(), ".gemini");
15942
- const mcpPath = join10(geminiDir, "settings.json");
15943
- await mkdir6(geminiDir, { recursive: true });
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 readFile5(mcpPath, "utf-8");
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 writeFile6(
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 = join10(
16285
+ const agyMcpDir = join11(
15965
16286
  geminiDir,
15966
16287
  "antigravity-cli",
15967
16288
  "mcp",
15968
16289
  MCP_SERVER_NAME
15969
16290
  );
15970
- await mkdir6(agyMcpDir, { recursive: true });
15971
- const instructionsPath = join10(agyMcpDir, "instructions.md");
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 writeFile6(instructionsPath, instructionsContent, "utf-8");
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: writeFile5 } = await import("node:fs/promises");
16003
- const { join: join9 } = await import("node:path");
16004
- const outputPath = join9(process.cwd(), ".mcp.json");
16005
- await writeFile5(outputPath, configJson + "\n", { mode: 384 });
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: readFile5 } = await import("node:fs/promises");
16342
+ const { readFile: readFile7 } = await import("node:fs/promises");
16019
16343
  try {
16020
- return await readFile5(path10, "utf8");
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: homedir7 } = await import("node:os");
16036
- const { join: join9 } = await import("node:path");
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
- join9(homedir7(), ".great-arrow", "mcp-server.mjs"),
16041
- join9(process.cwd(), "scripts", "mcp-server.mjs")
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: join9 } = await import("node:path");
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 = join9(cwd, ".claude", "worktrees");
16393
+ const worktreeRoot = join10(cwd, ".claude", "worktrees");
16070
16394
  for (const entry of await readdir(worktreeRoot, { withFileTypes: true })) {
16071
- if (entry.isDirectory()) dirs.add(join9(worktreeRoot, entry.name));
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 = join9(dir, ".mcp", OAUTH_CRED_FILENAME);
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: homedir7 } = await import("node:os");
16101
- const { join: join9 } = await import("node:path");
16102
- const home = homedir7();
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
- join9(home, ".claude", "settings.json")
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: homedir7 } = await import("node:os");
16138
- const { join: join9 } = await import("node:path");
16139
- const { mkdir: mkdir6, rename, writeFile: writeFile5 } = await import("node:fs/promises");
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 = join9(homedir7(), ".great-arrow");
16142
- await mkdir6(dir, { recursive: true, mode: 448 });
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 = join9(dir, OAUTH_CRED_FILENAME);
16470
+ const target = join10(dir, OAUTH_CRED_FILENAME);
16147
16471
  const tmp = `${target}.tmp`;
16148
- await writeFile5(tmp, raw, { mode: 384 });
16149
- await rename(tmp, target);
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 = join9(dir, "credential");
16480
+ const target = join10(dir, "credential");
16157
16481
  const tmp = `${target}.tmp`;
16158
- await writeFile5(tmp, raw, { mode: 384 });
16159
- await rename(tmp, target);
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: homedir7 } = await import("node:os");
16183
- const { join: join9 } = await import("node:path");
16184
- const home = homedir7();
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
- join9(home, ".great-arrow", "credential")
16515
+ join10(home, ".great-arrow", "credential")
16192
16516
  );
16193
16517
  const userStore = await readIfExists(
16194
- join9(home, ".great-arrow", OAUTH_CRED_FILENAME)
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 readFile4, writeFile as writeFile4, mkdir as mkdir5 } from "node:fs/promises";
16619
- import { homedir as homedir6 } from "node:os";
16620
- import { join as join8, dirname as dirname5 } from "node:path";
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 join8(homedir6(), ".gad", "glance-state.json");
17331
+ return join9(homedir7(), ".gad", "glance-state.json");
17008
17332
  }
17009
17333
  async function loadGlanceState() {
17010
17334
  try {
17011
- const content = await readFile4(getGlanceStatePath(), "utf-8");
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 mkdir5(dirname5(path10), { recursive: true });
17024
- await writeFile4(path10, JSON.stringify(state), "utf-8");
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 fileURLToPath2 } from "node:url";
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(fileURLToPath(import.meta.url));
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(fileURLToPath2(import.meta.url));
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