token-goat 2.9.30 → 2.9.31

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.
Files changed (172) hide show
  1. package/README.md +24 -7
  2. package/SECURITY.md +10 -2
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/dist/native/linux-arm64/tg-hook +0 -0
  5. package/dist/native/linux-x64/tg-hook +0 -0
  6. package/dist/native/win32-arm64/tg-hook.exe +0 -0
  7. package/dist/native/win32-x64/tg-hook.exe +0 -0
  8. package/dist/{token-goat-chunk-QSBRTGM2.mjs → token-goat-chunk-25TRAWET.mjs} +17 -7
  9. package/dist/token-goat-chunk-2KXO6V2B.mjs +203 -0
  10. package/dist/{token-goat-chunk-IVKIZWAB.mjs → token-goat-chunk-2M7E4PF7.mjs} +41 -12
  11. package/dist/{token-goat-chunk-SMVRN2HU.mjs → token-goat-chunk-2UPZGKKY.mjs} +2 -2
  12. package/dist/token-goat-chunk-2WLPWKHV.mjs +135 -0
  13. package/dist/token-goat-chunk-2XBMMDBD.mjs +126 -0
  14. package/dist/token-goat-chunk-32ZOY4N5.mjs +23 -0
  15. package/dist/token-goat-chunk-3EZRNKTK.mjs +188 -0
  16. package/dist/token-goat-chunk-4ADZHI7J.mjs +63 -0
  17. package/dist/token-goat-chunk-4GBVPH3D.mjs +242 -0
  18. package/dist/{token-goat-chunk-KWPNJBDH.mjs → token-goat-chunk-4GILLLWY.mjs} +7 -4
  19. package/dist/{token-goat-chunk-67TQJ76B.mjs → token-goat-chunk-4NAPX7BA.mjs} +36 -3
  20. package/dist/{token-goat-chunk-PU7V3FD6.mjs → token-goat-chunk-4SC7UJYV.mjs} +3354 -2553
  21. package/dist/token-goat-chunk-4SMRO4TO.mjs +366 -0
  22. package/dist/{token-goat-chunk-AFLYLHDH.mjs → token-goat-chunk-4ZBFWLXF.mjs} +283 -427
  23. package/dist/{token-goat-chunk-JDWCRD6M.mjs → token-goat-chunk-57BIAXJL.mjs} +73 -40
  24. package/dist/token-goat-chunk-5AC66HNP.mjs +115 -0
  25. package/dist/token-goat-chunk-5IMLKSOP.mjs +145 -0
  26. package/dist/token-goat-chunk-646E6U44.mjs +398 -0
  27. package/dist/token-goat-chunk-65N4RHP4.mjs +33 -0
  28. package/dist/{token-goat-chunk-JN4XMLSD.mjs → token-goat-chunk-6DG6HWP3.mjs} +8 -2
  29. package/dist/token-goat-chunk-6ENQSNRZ.mjs +573 -0
  30. package/dist/token-goat-chunk-6ZY4GNF2.mjs +91 -0
  31. package/dist/{token-goat-chunk-VPBBTSJR.mjs → token-goat-chunk-76FGDLRO.mjs} +262 -255
  32. package/dist/{token-goat-chunk-7IN2MKDS.mjs → token-goat-chunk-7EZTZOMC.mjs} +121 -289
  33. package/dist/{token-goat-chunk-OJBNYTJP.mjs → token-goat-chunk-7MAOA6X4.mjs} +640 -80
  34. package/dist/token-goat-chunk-7R2ZVVAX.mjs +218 -0
  35. package/dist/{token-goat-chunk-PD5VKH46.mjs → token-goat-chunk-AUNGYYQX.mjs} +2 -2
  36. package/dist/{token-goat-chunk-HG45AF2B.mjs → token-goat-chunk-AUQYRMMH.mjs} +794 -37
  37. package/dist/token-goat-chunk-B3PEKBYG.mjs +128 -0
  38. package/dist/{token-goat-chunk-A3CK6N5Z.mjs → token-goat-chunk-BA4HI6BW.mjs} +1309 -110
  39. package/dist/{token-goat-chunk-JB6QN6VF.mjs → token-goat-chunk-BFCWY73P.mjs} +59 -23
  40. package/dist/{token-goat-chunk-B45VMIIH.mjs → token-goat-chunk-BWGFUZZE.mjs} +7 -5
  41. package/dist/{token-goat-chunk-P4QMM4WK.mjs → token-goat-chunk-C75NA7PL.mjs} +50 -23
  42. package/dist/token-goat-chunk-CBIQ7WTD.mjs +87 -0
  43. package/dist/token-goat-chunk-CETM74KC.mjs +307 -0
  44. package/dist/{token-goat-chunk-H2PHHGM5.mjs → token-goat-chunk-CIMQ2BB3.mjs} +57 -12
  45. package/dist/{token-goat-chunk-N2NMER56.mjs → token-goat-chunk-CJLS3CDL.mjs} +46 -4
  46. package/dist/{token-goat-chunk-VES7EL7O.mjs → token-goat-chunk-CO3UYX6G.mjs} +1 -1
  47. package/dist/token-goat-chunk-COPNXRJM.mjs +154 -0
  48. package/dist/token-goat-chunk-CSJ5L3NL.mjs +479 -0
  49. package/dist/{token-goat-chunk-4LCCNRTG.mjs → token-goat-chunk-CVCEVUGK.mjs} +8 -6
  50. package/dist/{token-goat-chunk-B2RZZCSS.mjs → token-goat-chunk-CXHOFN7R.mjs} +37 -20
  51. package/dist/token-goat-chunk-D735C4QM.mjs +603 -0
  52. package/dist/{token-goat-chunk-APGZXSPS.mjs → token-goat-chunk-DGS2LVNA.mjs} +47 -30
  53. package/dist/{token-goat-chunk-54K3EUV3.mjs → token-goat-chunk-DOWRSMWO.mjs} +1 -1
  54. package/dist/token-goat-chunk-E45INMY3.mjs +71 -0
  55. package/dist/token-goat-chunk-EC5F37F4.mjs +244 -0
  56. package/dist/{token-goat-chunk-XTQAOTSO.mjs → token-goat-chunk-EDBDRVH5.mjs} +4 -1
  57. package/dist/{token-goat-chunk-5BCIJXMO.mjs → token-goat-chunk-EELTBKDM.mjs} +14 -10
  58. package/dist/{token-goat-chunk-W3QCJS6G.mjs → token-goat-chunk-F4HYL64R.mjs} +8 -6
  59. package/dist/token-goat-chunk-F6PAUIEK.mjs +624 -0
  60. package/dist/token-goat-chunk-FDSREWLX.mjs +692 -0
  61. package/dist/token-goat-chunk-GGGG7RWB.mjs +236 -0
  62. package/dist/{token-goat-chunk-C63JQV5K.mjs → token-goat-chunk-GL3ETKSC.mjs} +233 -15
  63. package/dist/token-goat-chunk-GVB2MUST.mjs +1298 -0
  64. package/dist/token-goat-chunk-GWFLAUMQ.mjs +307 -0
  65. package/dist/{token-goat-chunk-ZEJVMUQH.mjs → token-goat-chunk-HLPW63A7.mjs} +2 -2
  66. package/dist/{token-goat-chunk-GOLEOXZP.mjs → token-goat-chunk-HU4TORC5.mjs} +44 -26
  67. package/dist/{token-goat-chunk-Z57YODA2.mjs → token-goat-chunk-I7VKZZAW.mjs} +2 -2
  68. package/dist/{token-goat-chunk-NYKZLXMK.mjs → token-goat-chunk-IA6XSPE3.mjs} +6 -6
  69. package/dist/{token-goat-chunk-E3OQX2XT.mjs → token-goat-chunk-IAGXMHIK.mjs} +81 -42
  70. package/dist/{token-goat-chunk-HYBFA5X3.mjs → token-goat-chunk-IK2DRMFX.mjs} +24 -68
  71. package/dist/{token-goat-chunk-COBQGL5S.mjs → token-goat-chunk-INNPTRUX.mjs} +249 -299
  72. package/dist/{token-goat-chunk-7NADEDKJ.mjs → token-goat-chunk-JJ5BX4F4.mjs} +6 -1
  73. package/dist/{token-goat-chunk-PEPTHRFF.mjs → token-goat-chunk-JVFSHVFJ.mjs} +6 -6
  74. package/dist/token-goat-chunk-KGEFFSHD.mjs +50 -0
  75. package/dist/token-goat-chunk-KQBLRPBN.mjs +433 -0
  76. package/dist/{token-goat-chunk-Q5VTGRH7.mjs → token-goat-chunk-LCNQ2CNN.mjs} +55 -319
  77. package/dist/token-goat-chunk-M3LFJDPU.mjs +58 -0
  78. package/dist/token-goat-chunk-M52KPT3C.mjs +87 -0
  79. package/dist/token-goat-chunk-MBEG42H7.mjs +150 -0
  80. package/dist/{token-goat-chunk-EVC4TOLE.mjs → token-goat-chunk-METECDOH.mjs} +3 -12
  81. package/dist/{token-goat-chunk-T4FK7VOV.mjs → token-goat-chunk-MHX7XCC3.mjs} +2 -1
  82. package/dist/token-goat-chunk-N5LSOVSU.mjs +137 -0
  83. package/dist/{token-goat-chunk-LOBGHXPB.mjs → token-goat-chunk-N5MMR2IW.mjs} +1 -1
  84. package/dist/token-goat-chunk-N7B3JQ2C.mjs +160 -0
  85. package/dist/{token-goat-chunk-RHXKCZX3.mjs → token-goat-chunk-NISNMD7T.mjs} +2182 -2959
  86. package/dist/token-goat-chunk-NMD42SFQ.mjs +434 -0
  87. package/dist/token-goat-chunk-NOSS5VWY.mjs +174 -0
  88. package/dist/{token-goat-chunk-KQNZDCBD.mjs → token-goat-chunk-NSNSPPBG.mjs} +25 -3
  89. package/dist/token-goat-chunk-OBOFIHKN.mjs +38 -0
  90. package/dist/token-goat-chunk-OBRFJ5O5.mjs +76 -0
  91. package/dist/{token-goat-chunk-JTQQGXZH.mjs → token-goat-chunk-ODBT2USM.mjs} +124 -57
  92. package/dist/token-goat-chunk-OS5EM5PP.mjs +107 -0
  93. package/dist/token-goat-chunk-OUKDTVNU.mjs +1178 -0
  94. package/dist/token-goat-chunk-OXFMP75N.mjs +566 -0
  95. package/dist/{token-goat-chunk-DZX2YDPT.mjs → token-goat-chunk-OYIYKDDT.mjs} +2 -2
  96. package/dist/{token-goat-chunk-QCLDAQTS.mjs → token-goat-chunk-P6HQSWQ3.mjs} +50 -53
  97. package/dist/token-goat-chunk-P6VGNTQT.mjs +71 -0
  98. package/dist/{token-goat-chunk-7BXSNBY5.mjs → token-goat-chunk-PFWIMH2E.mjs} +23 -15
  99. package/dist/token-goat-chunk-PJZXIS3G.mjs +39 -0
  100. package/dist/token-goat-chunk-PUO5INXP.mjs +606 -0
  101. package/dist/{token-goat-chunk-GFBSN7BD.mjs → token-goat-chunk-PX5PQWTY.mjs} +22 -250
  102. package/dist/token-goat-chunk-Q5QDBC6O.mjs +111 -0
  103. package/dist/token-goat-chunk-QFNQXSDT.mjs +40 -0
  104. package/dist/{token-goat-chunk-FBNQNSJK.mjs → token-goat-chunk-QH7YVYOY.mjs} +2198 -2782
  105. package/dist/{token-goat-chunk-64FBPDTK.mjs → token-goat-chunk-QLMUHEPB.mjs} +12 -9
  106. package/dist/{token-goat-chunk-RZRTDST7.mjs → token-goat-chunk-QQOJIHL3.mjs} +37 -11
  107. package/dist/{token-goat-chunk-XKI4SEMX.mjs → token-goat-chunk-QQTWJY2O.mjs} +19 -0
  108. package/dist/{token-goat-chunk-U4N3BN2W.mjs → token-goat-chunk-QYHO5CPH.mjs} +3 -2
  109. package/dist/token-goat-chunk-RELAVWAD.mjs +27 -0
  110. package/dist/{token-goat-chunk-ETBHVDB6.mjs → token-goat-chunk-RSLPOBKQ.mjs} +22 -11
  111. package/dist/token-goat-chunk-SAMFDIS2.mjs +712 -0
  112. package/dist/token-goat-chunk-SDELXLDM.mjs +48 -0
  113. package/dist/{token-goat-chunk-4JJS54TR.mjs → token-goat-chunk-SOAFZNNZ.mjs} +161 -38
  114. package/dist/{token-goat-chunk-664RTTUV.mjs → token-goat-chunk-SRTHT7KV.mjs} +21 -16
  115. package/dist/{token-goat-chunk-4FVIAHSI.mjs → token-goat-chunk-T5P5ACNQ.mjs} +7 -6
  116. package/dist/token-goat-chunk-T5YGG4V3.mjs +45 -0
  117. package/dist/token-goat-chunk-TAUZ4OFS.mjs +110 -0
  118. package/dist/{token-goat-chunk-COTXZHTS.mjs → token-goat-chunk-TM6MC754.mjs} +4 -3
  119. package/dist/token-goat-chunk-TWUKBOMP.mjs +72 -0
  120. package/dist/{token-goat-chunk-3CHUNJ2O.mjs → token-goat-chunk-U2KSB4SG.mjs} +8 -5
  121. package/dist/{token-goat-chunk-3Z5S4KGM.mjs → token-goat-chunk-U4WHMG6Y.mjs} +29 -49
  122. package/dist/{token-goat-chunk-PY533T7F.mjs → token-goat-chunk-UGJHGR5A.mjs} +21 -15
  123. package/dist/{token-goat-chunk-CYCEYAEO.mjs → token-goat-chunk-UOUDVA5U.mjs} +1 -1
  124. package/dist/{token-goat-chunk-JDHE26RP.mjs → token-goat-chunk-URRJ33GR.mjs} +16 -5
  125. package/dist/{token-goat-chunk-56EIMS3E.mjs → token-goat-chunk-UZOROKIS.mjs} +23 -17
  126. package/dist/{token-goat-chunk-CZ3LUXYZ.mjs → token-goat-chunk-VIHGGGEI.mjs} +427 -308
  127. package/dist/token-goat-chunk-VTJLAA7P.mjs +21 -0
  128. package/dist/token-goat-chunk-VWDXLT4F.mjs +678 -0
  129. package/dist/{token-goat-chunk-YARZFCZS.mjs → token-goat-chunk-VWZA2H4P.mjs} +62 -53
  130. package/dist/{token-goat-chunk-SQSSNQPE.mjs → token-goat-chunk-VXEXMJDL.mjs} +3979 -4745
  131. package/dist/token-goat-chunk-WBA5SKT4.mjs +547 -0
  132. package/dist/{token-goat-chunk-SJHWVNDQ.mjs → token-goat-chunk-WDH44VAU.mjs} +7 -4
  133. package/dist/token-goat-chunk-X2SUP3EI.mjs +107 -0
  134. package/dist/token-goat-chunk-X3BELZSJ.mjs +125 -0
  135. package/dist/{token-goat-chunk-FZ3NEW5O.mjs → token-goat-chunk-XLRBNZCC.mjs} +13 -8
  136. package/dist/{token-goat-chunk-Z44YO3QZ.mjs → token-goat-chunk-XRKUX6CM.mjs} +1 -1
  137. package/dist/token-goat-chunk-XX6O25RN.mjs +82 -0
  138. package/dist/token-goat-chunk-XZSUHBVS.mjs +65 -0
  139. package/dist/{token-goat-chunk-WY3L4DVT.mjs → token-goat-chunk-Y2ZTQOCL.mjs} +13 -8
  140. package/dist/{token-goat-chunk-UQ5UK2T7.mjs → token-goat-chunk-YB4VMGK2.mjs} +1 -1
  141. package/dist/token-goat-chunk-YBIIY3HR.mjs +51 -0
  142. package/dist/{token-goat-chunk-ZQSJMO2R.mjs → token-goat-chunk-YHVDKQBR.mjs} +105 -105
  143. package/dist/{token-goat-chunk-H2K3QP6H.mjs → token-goat-chunk-YJB5XLWI.mjs} +8 -6
  144. package/dist/{token-goat-chunk-ZCDTCVIS.mjs → token-goat-chunk-YKB6AQG3.mjs} +5 -2
  145. package/dist/token-goat-chunk-YMTG2YA5.mjs +35 -0
  146. package/dist/token-goat-chunk-YPBZYQRV.mjs +40 -0
  147. package/dist/{token-goat-chunk-HOJLCGDP.mjs → token-goat-chunk-YY6AVUFL.mjs} +2033 -2251
  148. package/dist/token-goat-chunk-Z7N5OMMP.mjs +98 -0
  149. package/dist/token-goat-chunk-ZNMNUPAZ.mjs +938 -0
  150. package/dist/token-goat-hook-client.cjs +1 -1
  151. package/dist/token-goat-hook-client.mjs +3 -3
  152. package/dist/token-goat-hook.mjs +67 -38
  153. package/dist/token-goat.core.mjs +4 -4
  154. package/docs/C4_RUNTIME_ARCHITECTURE.md +1 -1
  155. package/docs/cli.md +40 -21
  156. package/docs/install.md +19 -9
  157. package/docs/security.md +5 -3
  158. package/package.json +2 -1
  159. package/dist/token-goat-chunk-5KDMCMF2.mjs +0 -362
  160. package/dist/token-goat-chunk-6QMAS7AB.mjs +0 -42
  161. package/dist/token-goat-chunk-7F2UBPD3.mjs +0 -397
  162. package/dist/token-goat-chunk-ARDNEC2U.mjs +0 -248
  163. package/dist/token-goat-chunk-ATCPY7EQ.mjs +0 -134
  164. package/dist/token-goat-chunk-EFHMMIGP.mjs +0 -68
  165. package/dist/token-goat-chunk-IVALLT3S.mjs +0 -48
  166. package/dist/token-goat-chunk-JDU3EXSU.mjs +0 -65
  167. package/dist/token-goat-chunk-MKPF4CL5.mjs +0 -37
  168. package/dist/token-goat-chunk-RC35RBUL.mjs +0 -312
  169. package/dist/token-goat-chunk-USILZ3T3.mjs +0 -53
  170. package/dist/token-goat-chunk-VL3YIFHZ.mjs +0 -2975
  171. package/dist/token-goat-chunk-XU4P6Z24.mjs +0 -133
  172. package/dist/token-goat-chunk-Y776AENC.mjs +0 -1597
@@ -14,34 +14,61 @@ import {
14
14
  anchoredMarkerPattern,
15
15
  buildGuidanceBlock,
16
16
  buildGuidanceBody,
17
+ bundledCliPath,
18
+ canonicalProjectRoot,
19
+ claudeHooksInstalledAnyScope,
20
+ copilotHooksFilePaths,
21
+ copilotMcpConfigPath,
22
+ dropEmptyServers,
17
23
  editAt,
24
+ findStrayClaudeMdBlocks,
18
25
  finishHookUninstall,
19
26
  groupHasTokenGoat,
27
+ hasManagedServer,
20
28
  hookCommandRewrittenSpan,
29
+ installCopilotHooksFile,
30
+ isManagedServer,
31
+ isResidueServersJson,
21
32
  legacyShimForwarder,
33
+ managedServer,
34
+ managedServerEntry,
22
35
  nativeHookBinary,
23
36
  nativeHookCommandLine,
37
+ noteRootKeyCreation,
38
+ readServersJson,
39
+ releaseCopilotHooksFile,
40
+ serversOf,
41
+ setTokenGoatServer,
42
+ shimFallbackTimeoutConst,
24
43
  skillDescriptionLine,
25
- splitHookCommand
26
- } from "./token-goat-chunk-OJBNYTJP.mjs";
44
+ splitHookCommand,
45
+ syncVisualStudioProjectGuidance,
46
+ visualStudioManagedEntry
47
+ } from "./token-goat-chunk-7MAOA6X4.mjs";
27
48
  import {
28
49
  parseJsonOrJsonc,
29
50
  stripBom
30
- } from "./token-goat-chunk-XKI4SEMX.mjs";
51
+ } from "./token-goat-chunk-QQTWJY2O.mjs";
52
+ import {
53
+ readCopilotHooksOwners
54
+ } from "./token-goat-chunk-G5PJ7AGM.mjs";
31
55
  import {
32
56
  toolMatcherFor
33
- } from "./token-goat-chunk-P4QMM4WK.mjs";
57
+ } from "./token-goat-chunk-C75NA7PL.mjs";
34
58
  import {
35
59
  GEMINI_TOOL_NAME_MAP
36
- } from "./token-goat-chunk-VPBBTSJR.mjs";
60
+ } from "./token-goat-chunk-76FGDLRO.mjs";
37
61
  import {
38
62
  loadConfig,
39
63
  parse,
40
64
  stringify
41
- } from "./token-goat-chunk-7IN2MKDS.mjs";
65
+ } from "./token-goat-chunk-7EZTZOMC.mjs";
42
66
  import {
67
+ assertProjectScopeTarget,
68
+ atomicWriteBytes,
43
69
  atomicWriteText,
44
70
  backupFile,
71
+ countNoun,
45
72
  ensureDirRecordingCreation,
46
73
  ensureDirSync,
47
74
  extractErrorMessage,
@@ -49,28 +76,41 @@ import {
49
76
  hookCommandFor,
50
77
  hookPowershellCommand,
51
78
  installSingleFilePlugin,
79
+ projectPathIsConsultable,
52
80
  projectScopeRoot,
53
81
  recordCreatedBy,
54
82
  recordCreatedConfig,
83
+ recordTurnedOffSetting,
55
84
  removeCreatedBackups,
56
85
  removeCreatedIfEmpty,
57
86
  removeCreatedTree,
87
+ removeEmptyDirInScope,
88
+ removeFileInScope,
58
89
  stripDelimitedBlock,
59
90
  stripOwnHooksFromMap,
60
91
  stripStaleGroupHooks,
61
92
  takeCreatedConfig,
93
+ takeTurnedOffSetting,
62
94
  uninstallSingleFilePlugin,
63
95
  upsertDelimitedBlock,
64
96
  withInstallScope,
65
97
  writeConfigText,
66
98
  writeIfDifferent,
67
99
  writeJsonSettings
68
- } from "./token-goat-chunk-GOLEOXZP.mjs";
100
+ } from "./token-goat-chunk-HU4TORC5.mjs";
101
+ import {
102
+ echoedValue,
103
+ fencedCommand
104
+ } from "./token-goat-chunk-GGGG7RWB.mjs";
69
105
  import {
70
106
  foldPath,
71
107
  powershellHookLine,
72
108
  quoteShellPath
73
109
  } from "./token-goat-chunk-AM5V6GSX.mjs";
110
+ import {
111
+ displaySafeText,
112
+ normalizePath
113
+ } from "./token-goat-chunk-FNFBKLRD.mjs";
74
114
  import {
75
115
  init_define_import_meta_env
76
116
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -157,7 +197,7 @@ function geminiSettingsPath() {
157
197
  }
158
198
  function readGeminiSettings(p, opts = {}) {
159
199
  const refuse = (problem, detail) => new GeminiSettingsParseError(
160
- (opts.command === "uninstall" ? `Gemini settings file '${p}' is unreadable: it ${problem}. Uninstall left it and its backups untouched; fix the file and run uninstall again.` : `Gemini settings file '${p}' ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
200
+ (opts.command === "uninstall" ? `Gemini settings file ${echoedValue(p)} is unreadable: it ${problem}. Uninstall left it and its backups untouched; fix the file and run uninstall again.` : `Gemini settings file ${echoedValue(p)} ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
161
201
  );
162
202
  let raw;
163
203
  try {
@@ -290,7 +330,7 @@ function qwenSettingsPath() {
290
330
  }
291
331
  function readQwenSettings(p, opts = {}) {
292
332
  const refuse = (problem, detail) => new QwenSettingsParseError(
293
- (opts.command === "uninstall" ? `Qwen Code settings file '${p}' is unreadable: it ${problem}. Uninstall left it and its backups untouched; fix the file and run uninstall again.` : `Qwen Code settings file '${p}' ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
333
+ (opts.command === "uninstall" ? `Qwen Code settings file ${echoedValue(p)} is unreadable: it ${problem}. Uninstall left it and its backups untouched; fix the file and run uninstall again.` : `Qwen Code settings file ${echoedValue(p)} ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
294
334
  );
295
335
  let raw;
296
336
  try {
@@ -431,6 +471,7 @@ ${SHIM_TRY_SERVER}
431
471
  ${SHIM_TRY_IN_PROCESS}
432
472
 
433
473
  ${SHIM_MAX_BUFFER_CONST}
474
+ ${shimFallbackTimeoutConst("kimi")}
434
475
 
435
476
  async function main() {
436
477
  const eventName = process.argv[2] || ''
@@ -444,7 +485,7 @@ async function main() {
444
485
  // process.argv[3], when present, is the absolute path to the token-goat CLI entry that ran
445
486
  // 'token-goat install --kimi' (baked in by hookCommandFor in kimi_install.ts). Same three-step
446
487
  // ladder as the Codex shim: in-process hook lib, then the baked entry via process.execPath,
447
- // then a PATH-based shell:true invocation for an older cached hook config. The 3000ms
488
+ // then a PATH-based shell:true invocation for an older cached hook config. The SHIM_FALLBACK_TIMEOUT_MS
448
489
  // timeout keeps both spawn fallbacks well inside Kimi's own 30s default hook timeout
449
490
  // (docs/en/customization/hooks.md), so token-goat degrades to its own fail-open no-op.
450
491
  const entryPath = process.argv[3]
@@ -455,7 +496,7 @@ ${SHIM_SPAWN_LADDER}
455
496
  input,
456
497
  encoding: 'utf8',
457
498
  shell: true,
458
- timeout: 3000,
499
+ timeout: SHIM_FALLBACK_TIMEOUT_MS,
459
500
  killSignal: 'SIGKILL',
460
501
  maxBuffer: SHIM_MAX_BUFFER_BYTES,
461
502
  })
@@ -514,7 +555,7 @@ function kimiSkillPath() {
514
555
  }
515
556
  function readKimiConfig(p, opts = {}) {
516
557
  const refuse = (problem, detail) => new KimiConfigParseError(
517
- (opts.command === "uninstall" ? `Kimi Code config file '${p}' is unreadable: it ${problem}. Uninstall left it untouched, along with the hook shim it may still name, the AGENTS.md block, the skill and the file's backups; fix the file and run uninstall again.` : `Kimi Code config file '${p}' ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
558
+ (opts.command === "uninstall" ? `Kimi Code config file ${echoedValue(p)} is unreadable: it ${problem}. Uninstall left it untouched, along with the hook shim it may still name, the AGENTS.md block, the skill and the file's backups; fix the file and run uninstall again.` : `Kimi Code config file ${echoedValue(p)} ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
518
559
  );
519
560
  let raw;
520
561
  try {
@@ -586,7 +627,7 @@ function installKimiFiles() {
586
627
  for (const { command } of process.platform === "win32" ? desired : []) {
587
628
  const span = hookCommandRewrittenSpan(command, "cmd");
588
629
  if (span !== void 0) {
589
- throw new HookCommandRewriteError(`Kimi Code runs hook commands through cmd.exe, which replaces ${span} with the value of the environment variable of that name whenever one is set, before it reads any quotes, so the hook command token-goat would write (${command}) would not run token-goat. Nothing was written. Move the path that contains "${span}" (the Kimi Code home, token-goat's install directory, or Node's) to one without a %...% pair, then run 'token-goat install --kimi' again.`);
630
+ throw new HookCommandRewriteError(`Kimi Code runs hook commands through cmd.exe, which replaces ${span} with the value of the environment variable of that name whenever one is set, before it reads any quotes, so the hook command token-goat would write (${command}) would not run token-goat. Nothing was written. Move the path that contains ${echoedValue(span)} (the Kimi Code home, token-goat's install directory, or Node's) to one without a %...% pair, then run \`token-goat install --kimi\` again.`);
590
631
  }
591
632
  }
592
633
  ensureDirSync(path4.dirname(scriptPath));
@@ -771,6 +812,8 @@ async function resolveRelayInProcess(): Promise<RelayInProcessFn | undefined> {
771
812
  }
772
813
  }
773
814
 
815
+ ${shimFallbackTimeoutConst("pi")}
816
+
774
817
  function callHookViaSpawn(event: string, payload: Record<string, unknown>): Record<string, unknown> | null {
775
818
  try {
776
819
  // TOKEN_GOAT_HARNESS_OVERRIDE=pi guarantees detectHarness() resolves to
@@ -789,7 +832,7 @@ function callHookViaSpawn(event: string, payload: Record<string, unknown>): Reco
789
832
  ? spawnSync(process.execPath, [entryPath, "hook", event], {
790
833
  input: JSON.stringify(payload),
791
834
  encoding: "utf8",
792
- timeout: 3000,
835
+ timeout: SHIM_FALLBACK_TIMEOUT_MS,
793
836
  killSignal: "SIGKILL",
794
837
  windowsHide: true,
795
838
  maxBuffer: 32 * 1024 * 1024,
@@ -798,7 +841,7 @@ function callHookViaSpawn(event: string, payload: Record<string, unknown>): Reco
798
841
  : spawnSync('token-goat hook ' + event, {
799
842
  input: JSON.stringify(payload),
800
843
  encoding: "utf8",
801
- timeout: 3000,
844
+ timeout: SHIM_FALLBACK_TIMEOUT_MS,
802
845
  killSignal: "SIGKILL",
803
846
  shell: true,
804
847
  windowsHide: true,
@@ -817,9 +860,8 @@ function callHookViaSpawn(event: string, payload: Record<string, unknown>): Reco
817
860
  // Tries the in-process hook call first (resolveRelayInProcess above), which avoids
818
861
  // spawning a second node process altogether for every single tool call in this
819
862
  // long-lived agent process. Falls back to callHookViaSpawn (the original
820
- // spawnSync-based path, now with a 3000ms timeout/killSignal so token-goat degrades to
821
- // its own fail-open null rather than being force-killed by pi's own hook timeout
822
- // budget, ~5000ms) when the in-process path is unavailable or throws.
863
+ // spawnSync-based path, now bounded by SHIM_FALLBACK_TIMEOUT_MS and a killSignal so token-goat degrades to
864
+ // its own fail-open null rather than hanging pi, which applies no hook timeout of its own) when the in-process path is unavailable or throws.
823
865
  async function callHook(event: string, payload: Record<string, unknown>): Promise<Record<string, unknown> | null> {
824
866
  // Timed from here, not from process start: this host lives far longer than one call, so the hook library's default clock (process age) would record the host's uptime as this call's duration.
825
867
  const start = performance.now();
@@ -1184,6 +1226,8 @@ async function resolveRelayInProcess() {
1184
1226
  }
1185
1227
  }
1186
1228
 
1229
+ ${shimFallbackTimeoutConst("relay")}
1230
+
1187
1231
  function callHookViaSpawn(event, payload) {
1188
1232
  try {
1189
1233
  // Invoking "token-goat" as a bare command here depends on PATH resolution
@@ -1199,7 +1243,7 @@ function callHookViaSpawn(event, payload) {
1199
1243
  ? spawnSync(process.execPath, [entryPath, "hook", event], {
1200
1244
  input: JSON.stringify(payload),
1201
1245
  encoding: "utf8",
1202
- timeout: 3000,
1246
+ timeout: SHIM_FALLBACK_TIMEOUT_MS,
1203
1247
  killSignal: "SIGKILL",
1204
1248
  windowsHide: true,
1205
1249
  maxBuffer: 32 * 1024 * 1024,
@@ -1207,7 +1251,7 @@ function callHookViaSpawn(event, payload) {
1207
1251
  : spawnSync("token-goat hook " + event, {
1208
1252
  input: JSON.stringify(payload),
1209
1253
  encoding: "utf8",
1210
- timeout: 3000,
1254
+ timeout: SHIM_FALLBACK_TIMEOUT_MS,
1211
1255
  killSignal: "SIGKILL",
1212
1256
  shell: true,
1213
1257
  windowsHide: true,
@@ -1225,9 +1269,9 @@ function callHookViaSpawn(event, payload) {
1225
1269
  // Tries the in-process hook call first (resolveRelayInProcess above), which avoids
1226
1270
  // spawning a second node process altogether for every single tool call in this
1227
1271
  // long-lived host process. Falls back to callHookViaSpawn (the original
1228
- // spawnSync-based path, now with a 3000ms timeout/killSignal so token-goat degrades to
1272
+ // spawnSync-based path, now bounded by SHIM_FALLBACK_TIMEOUT_MS and a killSignal so token-goat degrades to
1229
1273
  // its own fail-open null rather than being force-killed by the host's own hook timeout
1230
- // budget, ~5000ms) when the in-process path is unavailable or throws.
1274
+ // budget, 15000ms for OpenClaw's before_tool_call) when the in-process path is unavailable or throws.
1231
1275
  async function callHook(event, payload) {
1232
1276
  // Timed from here, not from process start: this host lives far longer than one call, so the hook library's default clock (process age) would record the host's uptime as this call's duration.
1233
1277
  const start = performance.now();
@@ -1341,8 +1385,6 @@ function extractUpdatedInput(resp) {
1341
1385
  return hso && typeof hso === "object" ? hso.updatedInput : undefined
1342
1386
  }
1343
1387
 
1344
- ${MATERIALIZE_SHRUNK_IMAGE_JS}
1345
-
1346
1388
  export const TokenGoatPlugin = async ({ directory }) => {
1347
1389
  return {
1348
1390
  "tool.execute.before": async (input, output) => {
@@ -1367,14 +1409,6 @@ export const TokenGoatPlugin = async ({ directory }) => {
1367
1409
  for (const [tgKey, val] of Object.entries(updated)) {
1368
1410
  output.args[rev[tgKey] ?? tgKey] = val
1369
1411
  }
1370
- return
1371
- }
1372
-
1373
- // Image shrink has no context channel here -- translate it into a
1374
- // rewritten filePath pointing at a materialized shrunk copy instead.
1375
- if (tg === "Read") {
1376
- const shrunkPath = materializeShrunkImage(extractContext(resp))
1377
- if (shrunkPath) output.args.filePath = shrunkPath
1378
1412
  }
1379
1413
  },
1380
1414
 
@@ -1692,7 +1726,7 @@ function openclawEntrySidecarPath() {
1692
1726
  }
1693
1727
  function readOpenclawConfig(p, opts = {}) {
1694
1728
  const refuse = (problem, detail) => new OpenclawConfigParseError(
1695
- (opts.command === "uninstall" ? `OpenClaw config file '${p}' is unreadable: it ${problem}. Uninstall left it untouched, along with the plugin it may still load, the plugin's entry sidecar and the file's backups; fix the file and run uninstall again.` : `OpenClaw config file '${p}' ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
1729
+ (opts.command === "uninstall" ? `OpenClaw config file ${echoedValue(p)} is unreadable: it ${problem}. Uninstall left it untouched, along with the plugin it may still load, the plugin's entry sidecar and the file's backups; fix the file and run uninstall again.` : `OpenClaw config file ${echoedValue(p)} ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
1696
1730
  );
1697
1731
  let raw;
1698
1732
  try {
@@ -1855,6 +1889,7 @@ ${SHIM_TRY_SERVER}
1855
1889
  ${SHIM_TRY_IN_PROCESS}
1856
1890
 
1857
1891
  ${SHIM_MAX_BUFFER_CONST}
1892
+ ${shimFallbackTimeoutConst("grok")}
1858
1893
 
1859
1894
  async function main() {
1860
1895
  const eventName = process.argv[2] || ''
@@ -1887,7 +1922,7 @@ ${SHIM_SPAWN_LADDER}
1887
1922
  input,
1888
1923
  encoding: 'utf8',
1889
1924
  shell: true,
1890
- timeout: 3000,
1925
+ timeout: SHIM_FALLBACK_TIMEOUT_MS,
1891
1926
  killSignal: 'SIGKILL',
1892
1927
  maxBuffer: SHIM_MAX_BUFFER_BYTES,
1893
1928
  })
@@ -2000,7 +2035,7 @@ function installGrokFiles() {
2000
2035
  for (const command of Object.values(config.hooks).flatMap((groups) => (groups ?? []).flatMap((g) => g.hooks.map((h) => h.command)))) {
2001
2036
  const span = hookCommandRewrittenSpan(command, "grok");
2002
2037
  if (span !== void 0) {
2003
- throw new HookCommandRewriteError(`Grok CLI substitutes ${span} in a hook command with the value of that environment variable when it loads the hook, whatever the quoting, and refuses to run the hook when it is unset, so the hook command token-goat would write (${command}) would not run token-goat. Nothing was written. Move the path that contains "${span}" (the home directory, token-goat's install directory, or Node's) to one without a $ name, then run 'token-goat install --grok' again.`);
2038
+ throw new HookCommandRewriteError(`Grok CLI substitutes ${span} in a hook command with the value of that environment variable when it loads the hook, whatever the quoting, and refuses to run the hook when it is unset, so the hook command token-goat would write (${command}) would not run token-goat. Nothing was written. Move the path that contains ${echoedValue(span)} (the home directory, token-goat's install directory, or Node's) to one without a $ name, then run \`token-goat install --grok\` again.`);
2004
2039
  }
2005
2040
  }
2006
2041
  const forwarderChanged = writeIfDifferent(grokLegacyHookScriptPath(), legacyShimForwarder("{}"));
@@ -2074,7 +2109,7 @@ function shimPath() {
2074
2109
  }
2075
2110
  function readJsonObject(p, label, opts = {}) {
2076
2111
  const refuse = (problem, detail) => new AntigravitySettingsParseError(
2077
- (opts.command === "uninstall" ? `Antigravity ${label} '${p}' is unreadable: it ${problem}. Uninstall left it and its backups untouched; fix the file and run uninstall again.` : `Antigravity ${label} '${p}' ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
2112
+ (opts.command === "uninstall" ? `Antigravity ${label} ${echoedValue(p)} is unreadable: it ${problem}. Uninstall left it and its backups untouched; fix the file and run uninstall again.` : `Antigravity ${label} ${echoedValue(p)} ${problem}. Fix or back up the file before running install.`) + (detail === void 0 ? "" : ` (${detail})`)
2078
2113
  );
2079
2114
  let raw;
2080
2115
  try {
@@ -2193,6 +2228,692 @@ function isAntigravityInstalled() {
2193
2228
  return readShim()?.includes(SHIM_MARKER) === true;
2194
2229
  }
2195
2230
 
2231
+ // src/bridges/vscode_install.ts
2232
+ init_define_import_meta_env();
2233
+ import * as fs10 from "node:fs";
2234
+ import * as os9 from "node:os";
2235
+ import * as path10 from "node:path";
2236
+ var VSCODE_GUIDANCE_BEGIN = "<!-- token-goat-vscode-begin -->";
2237
+ var VSCODE_GUIDANCE_END = "<!-- token-goat-vscode-end -->";
2238
+ var BEGIN = VSCODE_GUIDANCE_BEGIN;
2239
+ var END = VSCODE_GUIDANCE_END;
2240
+ function vscodeUserConfigDir() {
2241
+ if (process.platform === "win32") {
2242
+ const appData = process.env["APPDATA"];
2243
+ const base = appData !== void 0 && appData.trim() !== "" ? appData : path10.join(os9.homedir(), "AppData", "Roaming");
2244
+ return path10.join(base, "Code", "User");
2245
+ }
2246
+ if (process.platform === "darwin") {
2247
+ return path10.join(os9.homedir(), "Library", "Application Support", "Code", "User");
2248
+ }
2249
+ const xdg = process.env["XDG_CONFIG_HOME"];
2250
+ return path10.join(xdg !== void 0 && path10.isAbsolute(xdg) ? xdg : path10.join(os9.homedir(), ".config"), "Code", "User");
2251
+ }
2252
+ function vscodeUserMcpPath() {
2253
+ return path10.join(vscodeUserConfigDir(), "mcp.json");
2254
+ }
2255
+ function vscodeProjectMcpPath(projectRoot = process.cwd()) {
2256
+ return path10.join(path10.resolve(projectRoot), ".vscode", "mcp.json");
2257
+ }
2258
+ function vscodeMcpPath(opts = {}) {
2259
+ return opts.project === true ? vscodeProjectMcpPath(opts.projectRoot) : vscodeUserMcpPath();
2260
+ }
2261
+ function otherScopeMcpPath(opts) {
2262
+ return opts.project === true ? vscodeUserMcpPath() : vscodeProjectMcpPath(opts.projectRoot);
2263
+ }
2264
+ function vscodeInstructionsPath(opts = {}) {
2265
+ return opts.project === true ? path10.join(path10.resolve(opts.projectRoot ?? process.cwd()), ".github", "copilot-instructions.md") : path10.join(os9.homedir(), ".copilot", "instructions", "token-goat.instructions.md");
2266
+ }
2267
+ var USER_INSTRUCTIONS_FRONTMATTER = "---\ndescription: 'token-goat: when to use its MCP tools and CLI instead of reading whole files'\napplyTo: '**'\n---\n";
2268
+ function vscodeHooksDir(opts = {}) {
2269
+ return opts.project === true ? path10.join(canonicalProjectRoot(path10.resolve(opts.projectRoot ?? process.cwd())), ".github", "hooks") : path10.join(os9.homedir(), ".copilot", "hooks");
2270
+ }
2271
+ function vscodeScopeFromFlags(flags) {
2272
+ return { project: flags.user !== true };
2273
+ }
2274
+ function vscodeHooksInstalled(opts = {}) {
2275
+ return readCopilotHooksOwners(vscodeHooksDir(opts)).has("vscode");
2276
+ }
2277
+ var CLAUDE_HOOKS_SETTING = "chat.useClaudeHooks";
2278
+ function vscodeUserSettingsPath() {
2279
+ return path10.join(vscodeUserConfigDir(), "settings.json");
2280
+ }
2281
+ function vscodeUsesClaudeHooks(settingsPath = vscodeUserSettingsPath()) {
2282
+ return readSettingsObject(settingsPath)?.[CLAUDE_HOOKS_SETTING] === true;
2283
+ }
2284
+ function vscodeRunsTokenGoatClaudeHooks(settingsPath = vscodeUserSettingsPath()) {
2285
+ return vscodeUsesClaudeHooks(settingsPath) && claudeHooksInstalledAnyScope();
2286
+ }
2287
+ function readSettingsObject(settingsPath) {
2288
+ try {
2289
+ const prev = parseJsonOrJsonc(stripBom(fs10.readFileSync(settingsPath, "utf8")), { allowTrailingComma: true });
2290
+ return prev === null || typeof prev !== "object" || Array.isArray(prev) ? null : prev;
2291
+ } catch {
2292
+ return null;
2293
+ }
2294
+ }
2295
+ function disableVscodeClaudeHooks(settingsPath = vscodeUserSettingsPath()) {
2296
+ const prev = readSettingsObject(settingsPath);
2297
+ if (prev?.[CLAUDE_HOOKS_SETTING] !== true) return false;
2298
+ withInstallScope(void 0, () => {
2299
+ writeSettingsKeepingComments(settingsPath, { ...prev, [CLAUDE_HOOKS_SETTING]: false }, { allowTrailingComma: true });
2300
+ });
2301
+ recordTurnedOffSetting(settingsPath, CLAUDE_HOOKS_SETTING);
2302
+ return true;
2303
+ }
2304
+ function restoreVscodeClaudeHooks(settingsPath = vscodeUserSettingsPath()) {
2305
+ if (!takeTurnedOffSetting(settingsPath, CLAUDE_HOOKS_SETTING)) return false;
2306
+ const current = readSettingsObject(settingsPath);
2307
+ if (current === null || current[CLAUDE_HOOKS_SETTING] !== false) return false;
2308
+ try {
2309
+ withInstallScope(void 0, () => {
2310
+ writeSettingsKeepingComments(settingsPath, { ...current, [CLAUDE_HOOKS_SETTING]: true }, { allowTrailingComma: true });
2311
+ });
2312
+ } catch (e) {
2313
+ recordTurnedOffSetting(settingsPath, CLAUDE_HOOKS_SETTING);
2314
+ throw e;
2315
+ }
2316
+ return true;
2317
+ }
2318
+ function readConfig(filePath) {
2319
+ return readServersJson(filePath, "VS Code");
2320
+ }
2321
+ var updateConfig = setTokenGoatServer;
2322
+ function otherScopeHasManagedServer(opts) {
2323
+ const otherPath = otherScopeMcpPath(opts);
2324
+ if (opts.project !== true && !projectPathIsConsultable(otherPath, path10.resolve(opts.projectRoot ?? process.cwd()))) return false;
2325
+ return hasManagedServer(otherPath, "VS Code");
2326
+ }
2327
+ function vscodeDecoderConfigured(opts = {}) {
2328
+ const checkedPaths = [vscodeUserMcpPath()];
2329
+ if (opts.projectRoot !== void 0) {
2330
+ const projectPath = vscodeProjectMcpPath(opts.projectRoot);
2331
+ if (projectPathIsConsultable(projectPath, path10.resolve(opts.projectRoot))) checkedPaths.push(projectPath);
2332
+ }
2333
+ return { configured: checkedPaths.some((candidate) => hasManagedServer(candidate, "VS Code")), checkedPaths };
2334
+ }
2335
+ function writeGuidance(filePath, userScope) {
2336
+ let created = false;
2337
+ if (userScope && !fs10.existsSync(filePath)) {
2338
+ ensureDirSync(path10.dirname(filePath));
2339
+ atomicWriteText(filePath, USER_INSTRUCTIONS_FRONTMATTER);
2340
+ created = true;
2341
+ }
2342
+ const body = [
2343
+ BEGIN,
2344
+ buildGuidanceBody("VS Code\u2019s supported MCP integration and its built-in file-read tools", { gdrive: loadConfig().gdrive?.enabled ?? false }),
2345
+ "",
2346
+ "**Compressed payloads:** a message containing a token-goat payload block (recognizable by a `recovery: token-goat retrieve <id>` line) is compressed text, not an answer. Call the MCP tool `retrieve_text` with that id to recover the original text, then answer the question the message asks using the recovered text. Never present the raw payload to the user as the response; if the `retrieve_text` tool is unavailable (the MCP server is not running, or the chat is not in Agent mode), say so plainly and ask the user to switch to Agent mode or run `token-goat install --vscode`.",
2347
+ "",
2348
+ "VS Code support: token-goat install --vscode configures a stdio MCP server under the servers root key in your user-profile mcp.json by default (add --project for the workspace .vscode/mcp.json instead), and agent hooks that see VS Code\u2019s built-in tool calls. The hooks can deny a repeated read, add a hint, and shrink an image before view_image loads it; they cannot fold or trim what a built-in read returns, and they leave terminal commands unchanged.",
2349
+ END
2350
+ ].join("\n");
2351
+ return upsertDelimitedBlock(filePath, BEGIN, END, body) || created;
2352
+ }
2353
+ function assertProjectTargetsAreInTheProject(opts) {
2354
+ if (opts.project !== true) return;
2355
+ const root = path10.resolve(opts.projectRoot ?? process.cwd());
2356
+ for (const target of [vscodeMcpPath(opts), vscodeInstructionsPath(opts), ...copilotHooksFilePaths(vscodeHooksDir(opts))]) {
2357
+ assertProjectScopeTarget(target, root);
2358
+ }
2359
+ }
2360
+ function installVscode(opts = {}) {
2361
+ return withInstallScope(projectScopeRoot(opts), () => installVscodeScoped(opts));
2362
+ }
2363
+ function installVscodeScoped(opts) {
2364
+ assertProjectTargetsAreInTheProject(opts);
2365
+ const scope = opts.project === true ? "project" : "user";
2366
+ const mcpPath = vscodeMcpPath(opts);
2367
+ const instructionsPath = vscodeInstructionsPath(opts);
2368
+ let migratedFromUserScope = false;
2369
+ if (scope === "project" && (otherScopeHasManagedServer(opts) || vscodeHooksInstalled())) {
2370
+ migratedFromUserScope = uninstallVscode({ keepBackups: true });
2371
+ } else if (otherScopeHasManagedServer(opts)) {
2372
+ const otherPath = otherScopeMcpPath(opts);
2373
+ throw new Error(
2374
+ `token-goat is already registered in VS Code project scope (${otherPath}). Installing into user scope too would register it twice and duplicate its tool schemas in this workspace. Run \`token-goat uninstall --vscode --project\` first if you want to move it, or drop --vscode from this run.`
2375
+ );
2376
+ }
2377
+ const config = readConfig(mcpPath);
2378
+ const existingServers = config.value["servers"];
2379
+ if (existingServers !== void 0 && (existingServers === null || typeof existingServers !== "object" || Array.isArray(existingServers))) {
2380
+ throw new Error(`malformed VS Code MCP JSON at ${mcpPath}: servers must be an object`);
2381
+ }
2382
+ const servers = existingServers ?? {};
2383
+ const current = servers["token-goat"];
2384
+ if (current !== void 0 && !isManagedServer(current)) {
2385
+ throw new Error(`VS Code MCP JSON at ${mcpPath} already has a non-token-goat-managed server named "token-goat"`);
2386
+ }
2387
+ const next = updateConfig(config.text, managedServer());
2388
+ const mcpExisted = fs10.existsSync(mcpPath);
2389
+ ensureDirSync(path10.dirname(mcpPath));
2390
+ if (config.text !== next) {
2391
+ backupFile(mcpPath);
2392
+ writeConfigText(mcpPath, next);
2393
+ noteRootKeyCreation(mcpPath, config);
2394
+ if (!mcpExisted) recordCreatedConfig(mcpPath);
2395
+ }
2396
+ const guidanceDir = path10.dirname(instructionsPath);
2397
+ const guidanceDirExisted = fs10.existsSync(guidanceDir);
2398
+ const guidanceExisted = fs10.existsSync(instructionsPath);
2399
+ const guidanceChanged = writeGuidance(instructionsPath, scope === "user");
2400
+ if (scope === "project") {
2401
+ if (!guidanceDirExisted && fs10.existsSync(guidanceDir)) recordCreatedConfig(guidanceDir);
2402
+ if (!guidanceExisted && fs10.existsSync(instructionsPath)) recordCreatedConfig(instructionsPath);
2403
+ }
2404
+ if (scope === "project") syncVisualStudioProjectGuidance(instructionsPath);
2405
+ const hooks = installCopilotHooksFile(vscodeHooksDir(opts), "vscode");
2406
+ let disabledClaudeHooks = false;
2407
+ if (scope === "user" && vscodeRunsTokenGoatClaudeHooks()) {
2408
+ disabledClaudeHooks = disableVscodeClaudeHooks();
2409
+ }
2410
+ return {
2411
+ mcpPath,
2412
+ instructionsPath,
2413
+ hooksConfigPath: hooks.configPath,
2414
+ alreadyInstalled: config.text === next && !guidanceChanged && !hooks.changed && !migratedFromUserScope && !disabledClaudeHooks,
2415
+ scope,
2416
+ migratedFromUserScope,
2417
+ disabledClaudeHooks
2418
+ };
2419
+ }
2420
+ function uninstallVscode(opts = {}) {
2421
+ return withInstallScope(projectScopeRoot(opts), () => uninstallVscodeScoped(opts));
2422
+ }
2423
+ function uninstallVscodeScoped(opts) {
2424
+ assertProjectTargetsAreInTheProject(opts);
2425
+ const mcpPath = vscodeMcpPath(opts);
2426
+ let removed = false;
2427
+ if (fs10.existsSync(mcpPath)) {
2428
+ const config = readConfig(mcpPath);
2429
+ const servers = config.value["servers"];
2430
+ if (servers !== void 0 && (servers === null || typeof servers !== "object" || Array.isArray(servers))) {
2431
+ throw new Error(`malformed VS Code MCP JSON at ${mcpPath}: servers must be an object`);
2432
+ }
2433
+ if (servers && isManagedServer(servers["token-goat"])) {
2434
+ const next = dropEmptyServers(updateConfig(config.text, void 0), mcpPath);
2435
+ if (/^\s*\{\s*\}\s*$/.test(next) && takeCreatedConfig(mcpPath)) {
2436
+ removeFileInScope(mcpPath);
2437
+ if (opts.project === true) {
2438
+ removeEmptyDirInScope(path10.dirname(mcpPath));
2439
+ }
2440
+ } else {
2441
+ backupFile(mcpPath);
2442
+ writeConfigText(mcpPath, next);
2443
+ }
2444
+ if (opts.keepBackups !== true) removeCreatedBackups(mcpPath);
2445
+ removed = true;
2446
+ }
2447
+ }
2448
+ const instructionsPath = vscodeInstructionsPath(opts);
2449
+ if (stripDelimitedBlock(instructionsPath, BEGIN, END, opts.keepBackups === true)) {
2450
+ removed = true;
2451
+ if (opts.project !== true && fs10.readFileSync(instructionsPath, "utf8").trim() === USER_INSTRUCTIONS_FRONTMATTER.trim()) removeFileInScope(instructionsPath);
2452
+ }
2453
+ if (opts.project === true) syncVisualStudioProjectGuidance(instructionsPath);
2454
+ if (releaseCopilotHooksFile(vscodeHooksDir(opts), "vscode", opts.keepBackups === true)) removed = true;
2455
+ if (opts.project === true) {
2456
+ removeCreatedIfEmpty(instructionsPath);
2457
+ removeCreatedIfEmpty(path10.dirname(instructionsPath));
2458
+ }
2459
+ return removed;
2460
+ }
2461
+ function cleanupDeprecatedVscodeProjectMcp(projectRoot) {
2462
+ const root = path10.resolve(projectRoot ?? process.cwd());
2463
+ return withInstallScope(root, () => {
2464
+ const projectMcp = path10.join(root, ".vscode", "mcp.json");
2465
+ if (!fs10.existsSync(projectMcp)) return false;
2466
+ const text = fs10.readFileSync(projectMcp, "utf8");
2467
+ if (isResidueServersJson(text) && takeCreatedConfig(projectMcp)) {
2468
+ removeFileInScope(projectMcp);
2469
+ removeEmptyDirInScope(path10.dirname(projectMcp));
2470
+ removeCreatedBackups(projectMcp);
2471
+ return true;
2472
+ }
2473
+ return false;
2474
+ });
2475
+ }
2476
+
2477
+ // src/cli_doctor_platforms.ts
2478
+ init_define_import_meta_env();
2479
+ import * as fs13 from "node:fs";
2480
+ import * as path13 from "node:path";
2481
+
2482
+ // src/bridges/cursor_install.ts
2483
+ init_define_import_meta_env();
2484
+ import * as fs11 from "node:fs";
2485
+ import * as os10 from "node:os";
2486
+ import * as path11 from "node:path";
2487
+ var MCP_SERVERS_KEY = "mcpServers";
2488
+ var TOKEN_GOAT_ENTRY_KEY = "token-goat";
2489
+ function cursorUserConfigDir() {
2490
+ return path11.join(os10.homedir(), ".cursor");
2491
+ }
2492
+ function cursorMcpPath(opts = {}) {
2493
+ if (opts.project === true) {
2494
+ return path11.join(opts.projectRoot ?? process.cwd(), ".cursor", "mcp.json");
2495
+ }
2496
+ return path11.join(cursorUserConfigDir(), "mcp.json");
2497
+ }
2498
+ function cursorManagedServer() {
2499
+ return { command: process.execPath, args: [bundledCliPath(), "mcp-serve"] };
2500
+ }
2501
+ function isCursorManagedServer(value) {
2502
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
2503
+ const entry = value;
2504
+ const args = entry["args"];
2505
+ return entry["command"] === process.execPath && Array.isArray(args) && args.length === 2 && typeof args[0] === "string" && path11.basename(args[0]).toLowerCase() === "token-goat.mjs" && args[1] === "mcp-serve";
2506
+ }
2507
+ function installCursor(opts = {}) {
2508
+ return withInstallScope(projectScopeRoot(opts), () => installCursorScoped(opts));
2509
+ }
2510
+ function installCursorScoped(opts) {
2511
+ const scope = opts.project === true ? "project" : "user";
2512
+ const mcpPath = cursorMcpPath(opts);
2513
+ const config = readServersJson(mcpPath, "Cursor");
2514
+ const current = serversOf(config, mcpPath, "Cursor", MCP_SERVERS_KEY)[TOKEN_GOAT_ENTRY_KEY];
2515
+ if (current !== void 0 && !isCursorManagedServer(current)) {
2516
+ throw new Error(`Cursor mcp.json already has a ${echoedValue(TOKEN_GOAT_ENTRY_KEY)} MCP server entry token-goat did not write, at ${mcpPath}; remove it manually first`);
2517
+ }
2518
+ const desired = cursorManagedServer();
2519
+ const nextText = setTokenGoatServer(config.text, desired, MCP_SERVERS_KEY);
2520
+ const alreadyInstalled = config.text === nextText;
2521
+ if (!alreadyInstalled) {
2522
+ recordCreatedBy([mcpPath], () => {
2523
+ ensureDirSync(path11.dirname(mcpPath));
2524
+ backupFile(mcpPath);
2525
+ writeConfigText(mcpPath, nextText);
2526
+ noteRootKeyCreation(mcpPath, config, MCP_SERVERS_KEY);
2527
+ });
2528
+ }
2529
+ return { mcpPath, alreadyInstalled, scope };
2530
+ }
2531
+ function uninstallCursor(opts = {}) {
2532
+ return withInstallScope(projectScopeRoot(opts), () => uninstallCursorScoped(opts));
2533
+ }
2534
+ function uninstallCursorScoped(opts) {
2535
+ const mcpPath = cursorMcpPath(opts);
2536
+ if (!fs11.existsSync(mcpPath)) return false;
2537
+ const config = readServersJson(mcpPath, "Cursor");
2538
+ if (!isCursorManagedServer(serversOf(config, mcpPath, "Cursor", MCP_SERVERS_KEY)[TOKEN_GOAT_ENTRY_KEY])) return false;
2539
+ const next = dropEmptyServers(setTokenGoatServer(config.text, void 0, MCP_SERVERS_KEY), mcpPath, MCP_SERVERS_KEY);
2540
+ if (/^\s*\{\s*\}\s*$/.test(next) && takeCreatedConfig(mcpPath)) {
2541
+ removeFileInScope(mcpPath);
2542
+ } else {
2543
+ backupFile(mcpPath);
2544
+ writeConfigText(mcpPath, next);
2545
+ }
2546
+ removeCreatedBackups(mcpPath);
2547
+ removeCreatedTree([mcpPath]);
2548
+ return true;
2549
+ }
2550
+ function isCursorInstalled(opts = {}) {
2551
+ return hasManagedServer(cursorMcpPath(opts), "Cursor", MCP_SERVERS_KEY, isCursorManagedServer);
2552
+ }
2553
+ function cursorManagedEntry(mcpPath) {
2554
+ const entry = managedServerEntry(mcpPath, "Cursor", MCP_SERVERS_KEY, isCursorManagedServer);
2555
+ return entry === null ? null : { command: entry["command"] };
2556
+ }
2557
+
2558
+ // src/bridges/zed_install.ts
2559
+ init_define_import_meta_env();
2560
+ import * as fs12 from "node:fs";
2561
+ import * as os11 from "node:os";
2562
+ import * as path12 from "node:path";
2563
+ var CONTEXT_SERVERS_KEY = "context_servers";
2564
+ var TOKEN_GOAT_ENTRY_KEY2 = "token-goat";
2565
+ var ZED_TIMEOUT_MS = 3e4;
2566
+ function zedConfigDir() {
2567
+ if (process.platform === "win32") {
2568
+ const appData = process.env["APPDATA"];
2569
+ const base2 = appData !== void 0 && appData.trim() !== "" ? appData : path12.join(os11.homedir(), "AppData", "Roaming");
2570
+ return path12.join(base2, "Zed");
2571
+ }
2572
+ const xdgConfig = process.env["XDG_CONFIG_HOME"];
2573
+ const base = xdgConfig !== void 0 && xdgConfig.trim() !== "" ? xdgConfig : path12.join(os11.homedir(), ".config");
2574
+ return path12.join(base, "zed");
2575
+ }
2576
+ function zedSettingsPath() {
2577
+ return path12.join(zedConfigDir(), "settings.json");
2578
+ }
2579
+ function zedShimPath() {
2580
+ return path12.join(zedConfigDir(), process.platform === "win32" ? "token-goat-mcp.cmd" : "token-goat-mcp.sh");
2581
+ }
2582
+ function buildShimScript() {
2583
+ const nodePath = process.execPath;
2584
+ const cliPath = bundledCliPath();
2585
+ if (process.platform === "win32") {
2586
+ return `@echo off\r
2587
+ "${nodePath}" "${cliPath}" mcp-serve\r
2588
+ `;
2589
+ }
2590
+ return `#!/bin/sh
2591
+ exec "${nodePath}" "${cliPath}" mcp-serve
2592
+ `;
2593
+ }
2594
+ function zedManagedServer() {
2595
+ return { command: zedShimPath(), timeout: ZED_TIMEOUT_MS };
2596
+ }
2597
+ function isZedManagedServer(value) {
2598
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
2599
+ const entry = value;
2600
+ return entry["command"] === zedShimPath() && typeof entry["timeout"] === "number";
2601
+ }
2602
+ function installZed() {
2603
+ return recordCreatedBy([zedSettingsPath(), zedShimPath()], installZedFiles);
2604
+ }
2605
+ function installZedFiles() {
2606
+ const settingsPath = zedSettingsPath();
2607
+ const shimPath2 = zedShimPath();
2608
+ const config = readServersJson(settingsPath, "Zed");
2609
+ const current = serversOf(config, settingsPath, "Zed", CONTEXT_SERVERS_KEY)[TOKEN_GOAT_ENTRY_KEY2];
2610
+ if (current !== void 0 && !isZedManagedServer(current)) {
2611
+ throw new Error(`Zed settings.json already has a ${echoedValue(TOKEN_GOAT_ENTRY_KEY2)} context server entry token-goat did not write, at ${settingsPath}; remove it manually first`);
2612
+ }
2613
+ const priorShim = snapshotShim(shimPath2);
2614
+ const shimChanged = writeIfDifferent(shimPath2, buildShimScript());
2615
+ if (process.platform !== "win32") fs12.chmodSync(shimPath2, 493);
2616
+ const desired = zedManagedServer();
2617
+ const alreadyInstalled = current !== void 0 && !shimChanged;
2618
+ if (!alreadyInstalled) {
2619
+ try {
2620
+ const nextText = setTokenGoatServer(config.text, desired, CONTEXT_SERVERS_KEY);
2621
+ backupFile(settingsPath);
2622
+ writeConfigText(settingsPath, nextText);
2623
+ } catch (err) {
2624
+ try {
2625
+ restoreShim(shimPath2, priorShim, shimChanged);
2626
+ } catch {
2627
+ }
2628
+ throw err;
2629
+ }
2630
+ noteRootKeyCreation(settingsPath, config, CONTEXT_SERVERS_KEY);
2631
+ }
2632
+ return { settingsPath, shimPath: shimPath2, alreadyInstalled };
2633
+ }
2634
+ function snapshotShim(shimPath2) {
2635
+ let mode;
2636
+ try {
2637
+ mode = fs12.statSync(shimPath2).mode & 511;
2638
+ } catch (err) {
2639
+ if (err.code === "ENOENT") return { existed: false, bytes: void 0, mode: void 0 };
2640
+ }
2641
+ let bytes;
2642
+ try {
2643
+ bytes = fs12.readFileSync(shimPath2);
2644
+ } catch {
2645
+ bytes = void 0;
2646
+ }
2647
+ return { existed: true, bytes, mode };
2648
+ }
2649
+ function restoreShim(shimPath2, prior, shimChanged) {
2650
+ if (!prior.existed) {
2651
+ fs12.rmSync(shimPath2, { force: true });
2652
+ return;
2653
+ }
2654
+ if (shimChanged && prior.bytes !== void 0) atomicWriteBytes(shimPath2, prior.bytes);
2655
+ if (prior.mode !== void 0 && process.platform !== "win32") fs12.chmodSync(shimPath2, prior.mode);
2656
+ }
2657
+ function uninstallZed() {
2658
+ const settingsPath = zedSettingsPath();
2659
+ const shimPath2 = zedShimPath();
2660
+ let entryRemoved = false;
2661
+ let shimRemoved;
2662
+ try {
2663
+ if (fs12.existsSync(settingsPath)) {
2664
+ const config = readServersJson(settingsPath, "Zed");
2665
+ if (isZedManagedServer(serversOf(config, settingsPath, "Zed", CONTEXT_SERVERS_KEY)[TOKEN_GOAT_ENTRY_KEY2])) {
2666
+ const next = dropEmptyServers(setTokenGoatServer(config.text, void 0, CONTEXT_SERVERS_KEY), settingsPath, CONTEXT_SERVERS_KEY);
2667
+ if (/^\s*\{\s*\}\s*$/.test(next) && takeCreatedConfig(settingsPath)) {
2668
+ fs12.rmSync(settingsPath, { force: true });
2669
+ } else {
2670
+ backupFile(settingsPath);
2671
+ writeConfigText(settingsPath, next);
2672
+ }
2673
+ removeCreatedBackups(settingsPath);
2674
+ entryRemoved = true;
2675
+ }
2676
+ }
2677
+ } finally {
2678
+ shimRemoved = fs12.existsSync(shimPath2);
2679
+ if (shimRemoved) fs12.rmSync(shimPath2, { force: true });
2680
+ removeCreatedTree([settingsPath, shimPath2]);
2681
+ }
2682
+ return entryRemoved || shimRemoved;
2683
+ }
2684
+ function isZedInstalled() {
2685
+ return hasManagedServer(zedSettingsPath(), "Zed", CONTEXT_SERVERS_KEY, isZedManagedServer);
2686
+ }
2687
+ function zedManagedEntry(settingsPath) {
2688
+ const entry = managedServerEntry(settingsPath, "Zed", CONTEXT_SERVERS_KEY, isZedManagedServer);
2689
+ return entry === null ? null : { command: entry["command"] };
2690
+ }
2691
+
2692
+ // src/cli_doctor_platforms.ts
2693
+ function globalMcpConfigPath() {
2694
+ return copilotMcpConfigPath();
2695
+ }
2696
+ function checkGlobalMcpConfig(configPath = globalMcpConfigPath()) {
2697
+ if (!fs13.existsSync(configPath)) {
2698
+ return { name: "Global MCP configuration", status: "ok", message: `no global Copilot MCP configuration found at ${displaySafeText(configPath)}` };
2699
+ }
2700
+ let parsed;
2701
+ try {
2702
+ parsed = JSON.parse(stripBom(fs13.readFileSync(configPath, "utf8")));
2703
+ } catch {
2704
+ return {
2705
+ name: "Global MCP configuration",
2706
+ status: "warn",
2707
+ message: `could not read global Copilot MCP configuration at ${displaySafeText(configPath)}; unable to audit heavy launchers.`
2708
+ };
2709
+ }
2710
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2711
+ return {
2712
+ name: "Global MCP configuration",
2713
+ status: "warn",
2714
+ message: `global Copilot MCP configuration at ${displaySafeText(configPath)} has an unsupported format; unable to audit heavy launchers.`
2715
+ };
2716
+ }
2717
+ const configuredServers = parsed["mcpServers"];
2718
+ if (typeof configuredServers !== "object" || configuredServers === null || Array.isArray(configuredServers)) {
2719
+ return { name: "Global MCP configuration", status: "ok", message: `no global stdio MCP servers configured at ${displaySafeText(configPath)}` };
2720
+ }
2721
+ let chromeDevTools = 0;
2722
+ let playwright = 0;
2723
+ for (const server of Object.values(configuredServers)) {
2724
+ if (typeof server !== "object" || server === null || Array.isArray(server)) continue;
2725
+ const entry = server;
2726
+ const rawCommand = entry["command"];
2727
+ const command = typeof rawCommand === "string" ? rawCommand : "";
2728
+ const args = entry["args"];
2729
+ if (!/\bnpx(?:\.cmd)?\b/i.test(command) || !Array.isArray(args)) continue;
2730
+ const invocation = args.filter((arg) => typeof arg === "string").join(" ");
2731
+ if (/\bchrome-devtools-mcp\b/i.test(invocation)) chromeDevTools += 1;
2732
+ if (/@playwright[\\/]mcp\b/i.test(invocation)) playwright += 1;
2733
+ }
2734
+ if (chromeDevTools > 0 || playwright > 0) {
2735
+ const launchers = [];
2736
+ if (chromeDevTools > 0) launchers.push(`${chromeDevTools} Chrome DevTools MCP launcher${chromeDevTools === 1 ? "" : "s"}`);
2737
+ if (playwright > 0) launchers.push(`${playwright} Playwright MCP launcher${playwright === 1 ? "" : "s"}`);
2738
+ return {
2739
+ name: "Global MCP configuration",
2740
+ status: "warn",
2741
+ message: `${launchers.join(" and ")} configured at ${displaySafeText(configPath)}. Move heavy launchers to project scope or remove them when not actively needed.`
2742
+ };
2743
+ }
2744
+ return { name: "Global MCP configuration", status: "ok", message: `no known heavy global MCP launchers configured at ${displaySafeText(configPath)}` };
2745
+ }
2746
+ var VSCODE_USER_SCOPE_MIGRATED_NOTE = "Moved the VS Code integration from user scope to this project. It used to live in ~/.copilot/hooks, where VS Code resolves its working directory to the FIRST folder of a multi-root workspace and nothing else, so read hints, image shrinking and edit interception were silently doing nothing for every other folder. The shared ~/.copilot/hooks files stay in place if `token-goat install --copilot` still needs them.";
2747
+ var VSCODE_PROJECT_SCOPE_COVERAGE_NOTE = "This covers this project only. Run `token-goat install --vscode` once in each project you want it in, or `token-goat install --vscode --user` for one install covering every project (single-root workspaces only \u2014 see below).";
2748
+ var VSCODE_USER_SCOPE_MULTIROOT_NOTE = "NOTE: a user-scope install works in every project, but VS Code runs it with the first folder of a multi-root workspace as its working directory, so it does nothing for the other folders. Use `token-goat install --vscode` (project scope, the default) in each folder that needs it.";
2749
+ function checkVscodeUserScopeHooks(userScope, projectScope) {
2750
+ if (!userScope) return null;
2751
+ return {
2752
+ name: "VS Code hooks scope",
2753
+ status: "warn",
2754
+ message: projectScope ? "token-goat VS Code hooks are installed in BOTH ~/.copilot/hooks and this project's .github/hooks, and VS Code runs every hooks file it finds, so each hook fires twice. The user-scope copy is also pinned to the first folder of a multi-root workspace. Run `token-goat uninstall --vscode --user` to keep only the project install." : "token-goat VS Code hooks are installed in user scope (~/.copilot/hooks). VS Code runs them with the FIRST folder of a multi-root workspace as their working directory, so read hints, image shrinking and edit interception do nothing for any other folder. Run `token-goat install --vscode` in each project to move it to project scope."
2755
+ };
2756
+ }
2757
+ var VSCODE_DOUBLE_FIRE_NOTE = 'NOTE: VS Code has chat.useClaudeHooks turned on, so it also runs the token-goat hooks in ~/.claude/settings.json and each one fires twice. token-goat left it alone because it is a user-wide VS Code setting, and turning it off also stops every other Claude hook you have in VS Code. If you see hooks run twice, set "chat.useClaudeHooks": false in your VS Code user settings.';
2758
+ var VSCODE_CLAUDE_HOOKS_TURNED_OFF_TEXT = "Turned off chat.useClaudeHooks in your VS Code user settings so token-goat hooks do not run twice. That setting covers every Claude hook in VS Code, not only the token-goat ones. To turn it back on, set " + fencedCommand('"chat.useClaudeHooks": true') + " in VS Code settings, or run " + fencedCommand("token-goat uninstall --vscode --user") + " to put it back.";
2759
+ var VSCODE_CLAUDE_HOOKS_TURNED_OFF_NOTE = `NOTE: ${VSCODE_CLAUDE_HOOKS_TURNED_OFF_TEXT}`;
2760
+ var VSCODE_CLAUDE_HOOKS_RESTORED_NOTE = "Turned chat.useClaudeHooks back on in your VS Code user settings, as it was before token-goat turned it off.";
2761
+ function checkVscodeClaudeHooks(useClaudeHooks, claudeHooksInstalled, vscodeHooks) {
2762
+ if (!useClaudeHooks || !claudeHooksInstalled) return null;
2763
+ const vscodeHooksInstalled2 = vscodeHooks.user || vscodeHooks.project;
2764
+ if (vscodeHooks.project && !vscodeHooks.user) {
2765
+ return {
2766
+ name: "VS Code hooks",
2767
+ status: "ok",
2768
+ message: `VS Code has chat.useClaudeHooks on and token-goat hooks are in both ~/.claude/settings.json and this project's .github/hooks, so each hook fires twice in VS Code. token-goat leaves the setting alone for a project-scope install because it is user-wide and also stops every other Claude hook you have in VS Code. If you see hooks run twice, set "chat.useClaudeHooks": false in your VS Code user settings.`
2769
+ };
2770
+ }
2771
+ return {
2772
+ name: "VS Code hooks",
2773
+ status: "warn",
2774
+ message: vscodeHooksInstalled2 ? "VS Code has chat.useClaudeHooks on and token-goat hooks are in both ~/.claude/settings.json and the Copilot hooks file, so each hook fires twice in VS Code. Turn chat.useClaudeHooks off in VS Code settings, or run `token-goat doctor --repair` to do it." : "VS Code has chat.useClaudeHooks on, so it runs the token-goat hooks from ~/.claude/settings.json in Claude Code wire format, which VS Code reads only in part. Run `token-goat install --vscode` and turn chat.useClaudeHooks off in VS Code settings."
2775
+ };
2776
+ }
2777
+ function checkClaudeHookEvents(gaps) {
2778
+ const name = "Claude Code hook events";
2779
+ if (gaps.user === null && gaps.project === null) return null;
2780
+ const scopes = [
2781
+ { scope: "user", events: gaps.user, run: "token-goat install" },
2782
+ { scope: "project", events: gaps.project, run: "token-goat install --project" }
2783
+ ];
2784
+ const missing = [];
2785
+ const outdated = [];
2786
+ const broken = [];
2787
+ for (const { scope, events, run } of scopes) {
2788
+ if (events === null) continue;
2789
+ if (events.broken.length > 0) broken.push(`${scope} scope wires ${events.broken.join(", ")} to a native hook client binary that no longer exists; run: ${run}`);
2790
+ if (events.missing.length > 0) missing.push(`${scope} scope lacks ${events.missing.join(", ")}; run: ${run}`);
2791
+ if (events.outdated.length > 0) outdated.push(`${scope} scope wires ${events.outdated.join(", ")} to a token-goat hook command this build no longer writes; run: ${run}`);
2792
+ }
2793
+ if (broken.length === 0 && missing.length === 0 && outdated.length === 0) return { name, status: "ok", message: "every event this build handles is wired" };
2794
+ const sentences = [];
2795
+ if (broken.length > 0) sentences.push(`${broken.join(". ")}. Claude Code cannot start those hooks, so each of those events reports a hook error until then`);
2796
+ if (missing.length > 0) sentences.push(`${missing.join(". ")}. Those events never reach token-goat until then`);
2797
+ if (outdated.length > 0) sentences.push(`${outdated.join(". ")}. Those events still reach token-goat through the older command until the install rewrites it`);
2798
+ return { name, status: broken.length > 0 ? "fail" : "warn", message: `${sentences.join(". ")}; restart any running session afterwards.` };
2799
+ }
2800
+ function dedupeByResolvedPath(paths) {
2801
+ const seen = /* @__PURE__ */ new Set();
2802
+ return paths.filter((p) => {
2803
+ const key = normalizePath(path13.resolve(p)).toLowerCase();
2804
+ if (seen.has(key)) return false;
2805
+ seen.add(key);
2806
+ return true;
2807
+ });
2808
+ }
2809
+ function checkVisualStudio(mcpPaths, alsoReadPaths = []) {
2810
+ const found = dedupeByResolvedPath(mcpPaths).flatMap((mcpPath) => {
2811
+ const entry = visualStudioManagedEntry(mcpPath);
2812
+ return entry === null ? [] : [{ mcpPath, ...entry }];
2813
+ });
2814
+ if (found.length === 0) return null;
2815
+ const stale = found.find((e) => !fs13.existsSync(e.command) || !fs13.existsSync(e.bundlePath));
2816
+ if (stale !== void 0) {
2817
+ return {
2818
+ name: "Visual Studio",
2819
+ status: "warn",
2820
+ message: `the token-goat MCP entry in ${displaySafeText(stale.mcpPath)} points at ${displaySafeText(stale.command)} ${displaySafeText(stale.bundlePath)}, which no longer exists; run ${fencedCommand("token-goat uninstall --visualstudio")} and then ${fencedCommand("token-goat install --visualstudio")} again (add -p for the project entry).`
2821
+ };
2822
+ }
2823
+ const registered = dedupeByResolvedPath([...found.map((e) => e.mcpPath), ...alsoReadPaths.filter((p) => visualStudioManagedEntry(p) !== null)]);
2824
+ if (registered.length > 1) {
2825
+ return {
2826
+ name: "Visual Studio",
2827
+ status: "warn",
2828
+ message: `Visual Studio reads ${registered.map(displaySafeText).join(" and ")}, and each registers token-goat, so it lists the token-goat server more than once. Keep one: ${fencedCommand("token-goat uninstall --vscode -p")} drops the .vscode/mcp.json entry, ${fencedCommand("token-goat uninstall --visualstudio")} (add -p for the project entry) drops a Visual Studio one.`
2829
+ };
2830
+ }
2831
+ return {
2832
+ name: "Visual Studio",
2833
+ status: "ok",
2834
+ message: `MCP server registered in ${found.map((e) => displaySafeText(e.mcpPath)).join(", ")}. Visual Studio runs no token-goat hooks: it gets the MCP tools and instructions only, and only once the token-goat tools are ticked in the chat Tools picker.`
2835
+ };
2836
+ }
2837
+ function checkZed(settingsPath) {
2838
+ const entry = zedManagedEntry(settingsPath);
2839
+ if (entry === null) return null;
2840
+ const shimPath2 = entry.command;
2841
+ if (!fs13.existsSync(shimPath2)) {
2842
+ return {
2843
+ name: "Zed",
2844
+ status: "warn",
2845
+ message: `the token-goat MCP entry in ${displaySafeText(settingsPath)} points at ${displaySafeText(shimPath2)}, which no longer exists; run \`token-goat uninstall --zed\` and then \`token-goat install --zed\` again.`
2846
+ };
2847
+ }
2848
+ const script = fs13.readFileSync(shimPath2, "utf8");
2849
+ const referencedPaths = [...script.matchAll(/"([^"]+)"/g)].map((m) => m[1]).filter((p) => typeof p === "string" && p.length > 0);
2850
+ const stalePath = referencedPaths.find((p) => !fs13.existsSync(p));
2851
+ if (stalePath !== void 0) {
2852
+ return {
2853
+ name: "Zed",
2854
+ status: "warn",
2855
+ message: `the token-goat shim at ${displaySafeText(shimPath2)} points at ${displaySafeText(stalePath)}, which no longer exists; run \`token-goat uninstall --zed\` and then \`token-goat install --zed\` again.`
2856
+ };
2857
+ }
2858
+ return {
2859
+ name: "Zed",
2860
+ status: "ok",
2861
+ message: `MCP context server registered in ${displaySafeText(settingsPath)}. Zed runs no token-goat hooks: it gets the MCP tools only, and only once the token-goat server is enabled in Zed's Agent panel.`
2862
+ };
2863
+ }
2864
+ function checkCursor(mcpPath, claudeHooksInstalled) {
2865
+ const entry = cursorManagedEntry(mcpPath);
2866
+ if (entry === null) return null;
2867
+ return {
2868
+ name: "Cursor",
2869
+ status: "ok",
2870
+ message: claudeHooksInstalled ? `MCP server registered in ${displaySafeText(mcpPath)}. Cursor also imports the token-goat hooks already installed in ~/.claude/settings.json by default, so hooks work in Cursor too with no separate hooks.json entry.` : `MCP server registered in ${displaySafeText(mcpPath)}. Cursor writes and runs no token-goat hooks here: it imports Claude Code hooks from ~/.claude/settings.json by default, but none are installed there yet -- run \`token-goat install\` to get hooks in Cursor too.`
2871
+ };
2872
+ }
2873
+ function checkStrayClaudeMdBlocks(searchRoot) {
2874
+ const strays = findStrayClaudeMdBlocks(searchRoot);
2875
+ if (strays.length === 0) {
2876
+ return { name: "CLAUDE.md block", status: "ok", message: "no stray copies outside CLAUDE.md" };
2877
+ }
2878
+ return {
2879
+ name: "CLAUDE.md block",
2880
+ status: "warn",
2881
+ message: `${strays.length} stray cop${strays.length === 1 ? "y" : "ies"} outside CLAUDE.md (never refreshed by install, never removed by uninstall, will go stale): ${strays.join(", ")}`
2882
+ };
2883
+ }
2884
+ function checkVscodeProjectMcp(projectRoot = process.cwd()) {
2885
+ const mcpPath = path13.join(path13.resolve(projectRoot), ".vscode", "mcp.json");
2886
+ if (!fs13.existsSync(mcpPath)) return null;
2887
+ if (hasManagedServer(mcpPath, "VS Code")) return null;
2888
+ try {
2889
+ const content = fs13.readFileSync(mcpPath, "utf8");
2890
+ if (isResidueServersJson(content)) {
2891
+ return {
2892
+ name: "VS Code project MCP",
2893
+ status: "warn",
2894
+ message: `empty residue file at ${displaySafeText(mcpPath)} triggers Copilot CLI deprecation warnings; delete it or run \`token-goat uninstall --vscode\``
2895
+ };
2896
+ }
2897
+ return {
2898
+ name: "VS Code project MCP",
2899
+ status: "warn",
2900
+ message: `${displaySafeText(mcpPath)} is deprecated by Copilot CLI v1.0.84+; migrate configured servers to .mcp.json or .github/mcp.json (https://gh.io/copilotcli-mcpmigrate)`
2901
+ };
2902
+ } catch {
2903
+ return null;
2904
+ }
2905
+ }
2906
+ function checkClaudeHooksGone(wired, activity) {
2907
+ if (wired || activity === null || activity.count === 0 || activity.lastTs === null) return null;
2908
+ if (activity.uninstalledTs !== null && activity.uninstalledTs >= activity.lastTs) return null;
2909
+ const last = new Date(activity.lastTs * 1e3).toISOString().replace("T", " ").slice(0, 16);
2910
+ return {
2911
+ name: "Claude Code hooks",
2912
+ status: "warn",
2913
+ message: `Claude Code ran ${countNoun(activity.count, "token-goat hook")} in the last 7 days, the last at ${last} UTC, but no token-goat hook is wired in ~/.claude/settings.json or this project's .claude/settings.json now, and no uninstall was recorded. The file was probably overwritten or deleted. Run: token-goat install (or token-goat install --project), then restart any running session.`
2914
+ };
2915
+ }
2916
+
2196
2917
  export {
2197
2918
  installGemini,
2198
2919
  uninstallGemini,
@@ -2224,5 +2945,41 @@ export {
2224
2945
  isGrokInstalled,
2225
2946
  installAntigravity,
2226
2947
  uninstallAntigravity,
2227
- isAntigravityInstalled
2948
+ isAntigravityInstalled,
2949
+ vscodeScopeFromFlags,
2950
+ vscodeHooksInstalled,
2951
+ vscodeUsesClaudeHooks,
2952
+ vscodeRunsTokenGoatClaudeHooks,
2953
+ disableVscodeClaudeHooks,
2954
+ restoreVscodeClaudeHooks,
2955
+ otherScopeHasManagedServer,
2956
+ vscodeDecoderConfigured,
2957
+ installVscode,
2958
+ uninstallVscode,
2959
+ cleanupDeprecatedVscodeProjectMcp,
2960
+ cursorMcpPath,
2961
+ installCursor,
2962
+ uninstallCursor,
2963
+ isCursorInstalled,
2964
+ zedSettingsPath,
2965
+ installZed,
2966
+ uninstallZed,
2967
+ isZedInstalled,
2968
+ checkGlobalMcpConfig,
2969
+ VSCODE_USER_SCOPE_MIGRATED_NOTE,
2970
+ VSCODE_PROJECT_SCOPE_COVERAGE_NOTE,
2971
+ VSCODE_USER_SCOPE_MULTIROOT_NOTE,
2972
+ checkVscodeUserScopeHooks,
2973
+ VSCODE_DOUBLE_FIRE_NOTE,
2974
+ VSCODE_CLAUDE_HOOKS_TURNED_OFF_TEXT,
2975
+ VSCODE_CLAUDE_HOOKS_TURNED_OFF_NOTE,
2976
+ VSCODE_CLAUDE_HOOKS_RESTORED_NOTE,
2977
+ checkVscodeClaudeHooks,
2978
+ checkClaudeHookEvents,
2979
+ checkVisualStudio,
2980
+ checkZed,
2981
+ checkCursor,
2982
+ checkStrayClaudeMdBlocks,
2983
+ checkVscodeProjectMcp,
2984
+ checkClaudeHooksGone
2228
2985
  };