lurqrun 0.1.6 → 0.1.8

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 (239) hide show
  1. package/README.md +311 -139
  2. package/dist/auditHandler-VWCXSDVO.js +24 -0
  3. package/dist/autopilotInit-Q62MX5M6.js +178 -0
  4. package/dist/bin/lurq.js +3255 -23319
  5. package/dist/browserAuth-J7APSD4P.js +96 -0
  6. package/dist/cache-AEAKCV4I.js +12 -0
  7. package/dist/check-2JTQDQA6.js +2613 -0
  8. package/dist/check-PQSQ2MMA.js +192 -0
  9. package/dist/checkUpgrade-5YW77OBL.js +24 -0
  10. package/dist/chunk-2GAYNIHG.js +40 -0
  11. package/dist/chunk-2O6Z37IT.js +423 -0
  12. package/dist/chunk-2WD4QAH4.js +116 -0
  13. package/dist/chunk-3676XOBU.js +2235 -0
  14. package/dist/chunk-3D2MPGOQ.js +181 -0
  15. package/dist/chunk-3GHJR7XR.js +2049 -0
  16. package/dist/chunk-4ESO33J6.js +34 -0
  17. package/dist/chunk-4JHDSVIN.js +1229 -0
  18. package/dist/chunk-4KCPOMG6.js +68 -0
  19. package/dist/chunk-53SG6XWA.js +141 -0
  20. package/dist/chunk-5ABNDB3I.js +37 -0
  21. package/dist/chunk-5ALIJFDA.js +114 -0
  22. package/dist/chunk-63MUJNZL.js +318 -0
  23. package/dist/chunk-64ZF7KO4.js +1571 -0
  24. package/dist/chunk-6DM6UQFD.js +318 -0
  25. package/dist/chunk-6EQ3ARXA.js +304 -0
  26. package/dist/chunk-6L67QMCP.js +92 -0
  27. package/dist/chunk-72Y2FDVS.js +172 -0
  28. package/dist/chunk-A3TYI2TF.js +288 -0
  29. package/dist/chunk-B2UUYRBX.js +536 -0
  30. package/dist/chunk-BJT2LHAR.js +320 -0
  31. package/dist/chunk-BM3LF5T6.js +132 -0
  32. package/dist/chunk-BN3NM47Q.js +214 -0
  33. package/dist/chunk-BUALU27S.js +123 -0
  34. package/dist/chunk-C6GLE626.js +160 -0
  35. package/dist/chunk-D3K6IZVR.js +12845 -0
  36. package/dist/chunk-D3VTZS7V.js +222 -0
  37. package/dist/chunk-DM37MTOO.js +636 -0
  38. package/dist/chunk-DONTW3NE.js +171 -0
  39. package/dist/chunk-DSROTZ3Z.js +38 -0
  40. package/dist/chunk-ELGCAM7P.js +184 -0
  41. package/dist/chunk-EQCXENKZ.js +130 -0
  42. package/dist/chunk-EYAKM6YN.js +96 -0
  43. package/dist/chunk-FDFPMBCA.js +112 -0
  44. package/dist/chunk-FVYFV5HA.js +154 -0
  45. package/dist/chunk-GE37NVLN.js +15 -0
  46. package/dist/chunk-GM5Y555O.js +143 -0
  47. package/dist/chunk-GRJXQBQZ.js +793 -0
  48. package/dist/chunk-HWFX4BC4.js +177 -0
  49. package/dist/chunk-I3M2KJXB.js +56 -0
  50. package/dist/chunk-IFNONGRQ.js +603 -0
  51. package/dist/chunk-J3U2CPSK.js +136 -0
  52. package/dist/chunk-J6XQ7OPZ.js +74 -0
  53. package/dist/chunk-KMV4Y6R3.js +127 -0
  54. package/dist/chunk-KP5ZDDPE.js +340 -0
  55. package/dist/chunk-KYKSZ3XZ.js +202 -0
  56. package/dist/chunk-LEGPB7DO.js +81 -0
  57. package/dist/chunk-LUAKDHLZ.js +40 -0
  58. package/dist/chunk-MHUEVBVO.js +35 -0
  59. package/dist/chunk-MYGSLK44.js +37 -0
  60. package/dist/chunk-NEZNAPPN.js +41 -0
  61. package/dist/chunk-NPVN2THM.js +197 -0
  62. package/dist/chunk-NYZU5NCW.js +23 -0
  63. package/dist/chunk-O4KHCV6I.js +320 -0
  64. package/dist/chunk-ONDVTXUO.js +240 -0
  65. package/dist/chunk-OTJFHESY.js +176 -0
  66. package/dist/chunk-P6MIKSPY.js +243 -0
  67. package/dist/chunk-Q5VPF4WI.js +26 -0
  68. package/dist/chunk-QCRBX3LV.js +165 -0
  69. package/dist/chunk-QKGYDMWB.js +7409 -0
  70. package/dist/chunk-QSL6QN5X.js +115 -0
  71. package/dist/chunk-QUIMQ7YL.js +73 -0
  72. package/dist/chunk-RG3YLU6E.js +49 -0
  73. package/dist/chunk-SHCEBP3J.js +107 -0
  74. package/dist/chunk-SHZFOSXC.js +462 -0
  75. package/dist/chunk-SOXFMMXC.js +151 -0
  76. package/dist/chunk-TDTYV6QI.js +119 -0
  77. package/dist/chunk-TNTXWSYA.js +19 -0
  78. package/dist/chunk-TUXQ32WE.js +2251 -0
  79. package/dist/chunk-VGCDRK3S.js +37 -0
  80. package/dist/chunk-VLRHFMGJ.js +58 -0
  81. package/dist/chunk-WHX2ZFCG.js +4048 -0
  82. package/dist/chunk-X4W64MHU.js +2005 -0
  83. package/dist/chunk-XBQA4I36.js +92 -0
  84. package/dist/chunk-XGOV6KV4.js +413 -0
  85. package/dist/chunk-XIFQIFSZ.js +940 -0
  86. package/dist/chunk-XLXPVN3Y.js +15393 -0
  87. package/dist/chunk-ZJMLQ37G.js +90 -0
  88. package/dist/client-MU4EUDZV.js +15 -0
  89. package/dist/commands-UOOTMBKA.js +1023 -0
  90. package/dist/compat-Q5DVPITN.js +201 -0
  91. package/dist/constants-DJFOFZ44.js +24 -0
  92. package/dist/dashboardIssue-KBKLQMGC.js +274 -0
  93. package/dist/diff-I4SNUHU3.js +13 -0
  94. package/dist/dist-4XXIXHHZ.js +12945 -0
  95. package/dist/domains-VRT4BRVZ.js +14 -0
  96. package/dist/e2b-FVPHDULZ.js +172 -0
  97. package/dist/env-UN6KGZ7S.js +15 -0
  98. package/dist/envCheck-KUPTORFQ.js +71 -0
  99. package/dist/extract-6WG265BL.js +455 -0
  100. package/dist/fetch-PJ3SC32M.js +22 -0
  101. package/dist/firstTouch-COJCMOB3.js +55 -0
  102. package/dist/fix-3EDWNKW3.js +220 -0
  103. package/dist/handlers-4Q37WIFT.js +55 -0
  104. package/dist/hook-NA66XJLC.js +412 -0
  105. package/dist/http-25GJ6SKK.js +6928 -0
  106. package/dist/ingestQueue-FSVJC3AA.js +37 -0
  107. package/dist/install-G2HHXYJ2.js +20 -0
  108. package/dist/installSkill-5TIMYIJZ.js +70 -0
  109. package/dist/inventory-5AWSLJYU.js +31 -0
  110. package/dist/local-XPZMRH5R.js +13 -0
  111. package/dist/manifest-ENMIC54B.js +13 -0
  112. package/dist/mcp-PENLL2PK.js +500 -0
  113. package/dist/mcpConfig-3LYPRY5Q.js +80 -0
  114. package/dist/mcpHandlers-PO7GENA7.js +22 -0
  115. package/dist/mcpScan-ULP626TQ.js +1735 -0
  116. package/dist/mcpScanWorkflow-27O44FOE.js +98 -0
  117. package/dist/mcpStack-ZNYIFXZ5.js +20 -0
  118. package/dist/model-ASMZWLM7.js +214 -0
  119. package/dist/packages-RWF2LXST.js +40 -0
  120. package/dist/plan-SDILIDZT.js +465 -0
  121. package/dist/planView-ZAKRTGVS.js +82 -0
  122. package/dist/policy-JAGNMQJI.js +115 -0
  123. package/dist/probe-3IK646WN.js +610 -0
  124. package/dist/publicUpgrades-WTBGLDIK.js +42 -0
  125. package/dist/references-I6Y4BSRR.js +16 -0
  126. package/dist/release-7IU2R4YG.js +128 -0
  127. package/dist/remote-FN6225AH.js +42 -0
  128. package/dist/rename-OS6JYBZQ.js +204 -0
  129. package/dist/reportRuns-O4X7ZFTX.js +77 -0
  130. package/dist/safeFetch-COERXGFN.js +19 -0
  131. package/dist/sandbox-SAPSRKEI.js +39 -0
  132. package/dist/sarif-WLVJFJ7U.js +125 -0
  133. package/dist/server-OLTY3NYT.js +59 -0
  134. package/dist/surface-7MHE3RL7.js +143 -0
  135. package/dist/surface-JS2XU3U3.js +38 -0
  136. package/dist/surfaceHandlers-4B6TMZ34.js +20 -0
  137. package/dist/typecheck-NGOKNAJY.js +282 -0
  138. package/dist/types-M5JP2W3D.js +10 -0
  139. package/dist/uninstall-I2IJP5JZ.js +200 -0
  140. package/dist/upgrade-FS26DJGG.js +650 -0
  141. package/dist/upgradePlan-ZS5IOVMZ.js +181 -0
  142. package/dist/upkeepHandler-VFGYXJKB.js +13 -0
  143. package/dist/userConfig-ICASHI47.js +20 -0
  144. package/dist/watch-OCNEUXG6.js +204 -0
  145. package/package.json +26 -20
  146. package/templates/agent-rules.md +4 -4
  147. package/templates/skill-instructions.md +35 -17
  148. package/dist/bin/lurq.js.map +0 -1
  149. package/dist/index.d.ts +0 -805
  150. package/dist/index.js +0 -23570
  151. package/dist/index.js.map +0 -1
  152. package/drizzle/0000_confused_fabian_cortez.sql +0 -54
  153. package/drizzle/0001_aspiring_timeslip.sql +0 -15
  154. package/drizzle/0002_lyrical_lila_cheney.sql +0 -14
  155. package/drizzle/0003_violet_giant_girl.sql +0 -14
  156. package/drizzle/0004_slim_valeria_richards.sql +0 -15
  157. package/drizzle/0005_petite_luke_cage.sql +0 -12
  158. package/drizzle/0006_calm_nighthawk.sql +0 -3
  159. package/drizzle/0007_slimy_naoko.sql +0 -1
  160. package/drizzle/0008_peaceful_tigra.sql +0 -1
  161. package/drizzle/0009_smooth_harrier.sql +0 -10
  162. package/drizzle/0010_curved_amphibian.sql +0 -2
  163. package/drizzle/0011_compat_provenance_closures.sql +0 -11
  164. package/drizzle/0012_api_surfaces.sql +0 -9
  165. package/drizzle/0013_nice_thanos.sql +0 -1
  166. package/drizzle/0014_luxuriant_malcolm_colcord.sql +0 -10
  167. package/drizzle/0015_past_fat_cobra.sql +0 -11
  168. package/drizzle/0016_futuristic_junta.sql +0 -50
  169. package/drizzle/0017_absent_grandmaster.sql +0 -20
  170. package/drizzle/0018_rainy_proemial_gods.sql +0 -1
  171. package/drizzle/0019_dashing_korvac.sql +0 -11
  172. package/drizzle/0020_normal_vulture.sql +0 -3
  173. package/drizzle/0021_dazzling_blue_shield.sql +0 -17
  174. package/drizzle/0022_massive_ghost_rider.sql +0 -24
  175. package/drizzle/0023_next_shatterstar.sql +0 -1
  176. package/drizzle/0024_dusty_maelstrom.sql +0 -15
  177. package/drizzle/0025_gifted_harpoon.sql +0 -5
  178. package/drizzle/0026_awesome_mordo.sql +0 -1
  179. package/drizzle/0027_charming_ulik.sql +0 -2
  180. package/drizzle/0028_romantic_loners.sql +0 -2
  181. package/drizzle/0029_busy_korvac.sql +0 -16
  182. package/drizzle/0030_steady_mathemanic.sql +0 -6
  183. package/drizzle/0031_redundant_maginty.sql +0 -2
  184. package/drizzle/0032_stack_resolutions.sql +0 -14
  185. package/drizzle/0033_graceful_tyger_tiger.sql +0 -3
  186. package/drizzle/0034_uneven_thunderbird.sql +0 -1
  187. package/drizzle/0035_api_key_scopes.sql +0 -1
  188. package/drizzle/0036_policy_audit.sql +0 -21
  189. package/drizzle/0037_subscription_seats.sql +0 -1
  190. package/drizzle/0038_symbol_offsets.sql +0 -2
  191. package/drizzle/0039_mcp_runtime_scans.sql +0 -80
  192. package/drizzle/0040_subscription_overage_and_interval.sql +0 -4
  193. package/drizzle/0041_account_email.sql +0 -38
  194. package/drizzle/0042_alert_channels.sql +0 -21
  195. package/drizzle/meta/0000_snapshot.json +0 -370
  196. package/drizzle/meta/0001_snapshot.json +0 -482
  197. package/drizzle/meta/0002_snapshot.json +0 -575
  198. package/drizzle/meta/0003_snapshot.json +0 -667
  199. package/drizzle/meta/0004_snapshot.json +0 -769
  200. package/drizzle/meta/0005_snapshot.json +0 -864
  201. package/drizzle/meta/0006_snapshot.json +0 -882
  202. package/drizzle/meta/0007_snapshot.json +0 -876
  203. package/drizzle/meta/0008_snapshot.json +0 -882
  204. package/drizzle/meta/0009_snapshot.json +0 -948
  205. package/drizzle/meta/0010_snapshot.json +0 -969
  206. package/drizzle/meta/0011_snapshot.json +0 -1049
  207. package/drizzle/meta/0012_snapshot.json +0 -1115
  208. package/drizzle/meta/0013_snapshot.json +0 -1121
  209. package/drizzle/meta/0014_snapshot.json +0 -1190
  210. package/drizzle/meta/0015_snapshot.json +0 -1280
  211. package/drizzle/meta/0016_snapshot.json +0 -1683
  212. package/drizzle/meta/0017_snapshot.json +0 -1834
  213. package/drizzle/meta/0018_snapshot.json +0 -1834
  214. package/drizzle/meta/0019_snapshot.json +0 -1909
  215. package/drizzle/meta/0020_snapshot.json +0 -1921
  216. package/drizzle/meta/0021_snapshot.json +0 -2045
  217. package/drizzle/meta/0022_snapshot.json +0 -2246
  218. package/drizzle/meta/0023_snapshot.json +0 -2252
  219. package/drizzle/meta/0024_snapshot.json +0 -2375
  220. package/drizzle/meta/0025_snapshot.json +0 -2407
  221. package/drizzle/meta/0026_snapshot.json +0 -2413
  222. package/drizzle/meta/0027_snapshot.json +0 -2425
  223. package/drizzle/meta/0028_snapshot.json +0 -2438
  224. package/drizzle/meta/0029_snapshot.json +0 -2546
  225. package/drizzle/meta/0030_snapshot.json +0 -2586
  226. package/drizzle/meta/0031_snapshot.json +0 -2598
  227. package/drizzle/meta/0032_snapshot.json +0 -2699
  228. package/drizzle/meta/0033_snapshot.json +0 -2712
  229. package/drizzle/meta/0034_snapshot.json +0 -2719
  230. package/drizzle/meta/0035_snapshot.json +0 -2726
  231. package/drizzle/meta/0036_snapshot.json +0 -2876
  232. package/drizzle/meta/0037_snapshot.json +0 -2883
  233. package/drizzle/meta/0038_snapshot.json +0 -2895
  234. package/drizzle/meta/0039_snapshot.json +0 -3501
  235. package/drizzle/meta/0040_snapshot.json +0 -3527
  236. package/drizzle/meta/0041_snapshot.json +0 -3824
  237. package/drizzle/meta/0042_snapshot.json +0 -3960
  238. package/drizzle/meta/_journal.json +0 -307
  239. package/src/data/seed.json +0 -253
@@ -0,0 +1,1735 @@
1
+ import { createRequire as __lurqCreateRequire } from 'node:module'; const require = __lurqCreateRequire(import.meta.url);
2
+ import {
3
+ ReadBuffer,
4
+ serializeMessage
5
+ } from "./chunk-NEZNAPPN.js";
6
+ import {
7
+ analyzeServer,
8
+ analyzeStackScan,
9
+ diffSnapshots,
10
+ sortFindings,
11
+ worst
12
+ } from "./chunk-IFNONGRQ.js";
13
+ import {
14
+ isCrowded
15
+ } from "./chunk-FDFPMBCA.js";
16
+ import {
17
+ Client,
18
+ LIMITS,
19
+ addPrompts,
20
+ addTemplates,
21
+ addTools,
22
+ contentHash,
23
+ emptySnapshot
24
+ } from "./chunk-XIFQIFSZ.js";
25
+ import "./chunk-XLXPVN3Y.js";
26
+ import {
27
+ readServerConfigs,
28
+ scrub,
29
+ scrubDeep
30
+ } from "./chunk-XGOV6KV4.js";
31
+ import "./chunk-63MUJNZL.js";
32
+ import "./chunk-QKGYDMWB.js";
33
+ import {
34
+ classifyError
35
+ } from "./chunk-53SG6XWA.js";
36
+ import {
37
+ SSEClientTransport,
38
+ StreamableHTTPClientTransport,
39
+ StreamableHTTPError
40
+ } from "./chunk-X4W64MHU.js";
41
+ import {
42
+ array,
43
+ looseObject,
44
+ string,
45
+ unknown
46
+ } from "./chunk-D3K6IZVR.js";
47
+ import "./chunk-6EQ3ARXA.js";
48
+ import "./chunk-SHCEBP3J.js";
49
+ import {
50
+ SEVERITY_RANK
51
+ } from "./chunk-Q5VPF4WI.js";
52
+ import {
53
+ pMap
54
+ } from "./chunk-4ESO33J6.js";
55
+ import "./chunk-WHX2ZFCG.js";
56
+ import {
57
+ bold,
58
+ dim,
59
+ green,
60
+ red,
61
+ table,
62
+ yellow
63
+ } from "./chunk-4KCPOMG6.js";
64
+ import {
65
+ RemoteError,
66
+ uploadMcpScan
67
+ } from "./chunk-OTJFHESY.js";
68
+ import {
69
+ lurqHome,
70
+ resolveApiKey
71
+ } from "./chunk-J6XQ7OPZ.js";
72
+ import {
73
+ VERSION
74
+ } from "./chunk-72Y2FDVS.js";
75
+ import {
76
+ __commonJS,
77
+ __require,
78
+ __toESM,
79
+ init_esm_shims
80
+ } from "./chunk-I3M2KJXB.js";
81
+
82
+ // node_modules/isexe/windows.js
83
+ var require_windows = __commonJS({
84
+ "node_modules/isexe/windows.js"(exports, module) {
85
+ "use strict";
86
+ init_esm_shims();
87
+ module.exports = isexe;
88
+ isexe.sync = sync;
89
+ var fs = __require("fs");
90
+ function checkPathExt(path, options) {
91
+ var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
92
+ if (!pathext) {
93
+ return true;
94
+ }
95
+ pathext = pathext.split(";");
96
+ if (pathext.indexOf("") !== -1) {
97
+ return true;
98
+ }
99
+ for (var i = 0; i < pathext.length; i++) {
100
+ var p = pathext[i].toLowerCase();
101
+ if (p && path.substr(-p.length).toLowerCase() === p) {
102
+ return true;
103
+ }
104
+ }
105
+ return false;
106
+ }
107
+ function checkStat(stat, path, options) {
108
+ if (!stat.isSymbolicLink() && !stat.isFile()) {
109
+ return false;
110
+ }
111
+ return checkPathExt(path, options);
112
+ }
113
+ function isexe(path, options, cb) {
114
+ fs.stat(path, function(er, stat) {
115
+ cb(er, er ? false : checkStat(stat, path, options));
116
+ });
117
+ }
118
+ function sync(path, options) {
119
+ return checkStat(fs.statSync(path), path, options);
120
+ }
121
+ }
122
+ });
123
+
124
+ // node_modules/isexe/mode.js
125
+ var require_mode = __commonJS({
126
+ "node_modules/isexe/mode.js"(exports, module) {
127
+ "use strict";
128
+ init_esm_shims();
129
+ module.exports = isexe;
130
+ isexe.sync = sync;
131
+ var fs = __require("fs");
132
+ function isexe(path, options, cb) {
133
+ fs.stat(path, function(er, stat) {
134
+ cb(er, er ? false : checkStat(stat, options));
135
+ });
136
+ }
137
+ function sync(path, options) {
138
+ return checkStat(fs.statSync(path), options);
139
+ }
140
+ function checkStat(stat, options) {
141
+ return stat.isFile() && checkMode(stat, options);
142
+ }
143
+ function checkMode(stat, options) {
144
+ var mod = stat.mode;
145
+ var uid = stat.uid;
146
+ var gid = stat.gid;
147
+ var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
148
+ var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
149
+ var u = parseInt("100", 8);
150
+ var g = parseInt("010", 8);
151
+ var o = parseInt("001", 8);
152
+ var ug = u | g;
153
+ var ret = mod & o || mod & g && gid === myGid || mod & u && uid === myUid || mod & ug && myUid === 0;
154
+ return ret;
155
+ }
156
+ }
157
+ });
158
+
159
+ // node_modules/isexe/index.js
160
+ var require_isexe = __commonJS({
161
+ "node_modules/isexe/index.js"(exports, module) {
162
+ "use strict";
163
+ init_esm_shims();
164
+ var fs = __require("fs");
165
+ var core;
166
+ if (process.platform === "win32" || global.TESTING_WINDOWS) {
167
+ core = require_windows();
168
+ } else {
169
+ core = require_mode();
170
+ }
171
+ module.exports = isexe;
172
+ isexe.sync = sync;
173
+ function isexe(path, options, cb) {
174
+ if (typeof options === "function") {
175
+ cb = options;
176
+ options = {};
177
+ }
178
+ if (!cb) {
179
+ if (typeof Promise !== "function") {
180
+ throw new TypeError("callback not provided");
181
+ }
182
+ return new Promise(function(resolve2, reject) {
183
+ isexe(path, options || {}, function(er, is) {
184
+ if (er) {
185
+ reject(er);
186
+ } else {
187
+ resolve2(is);
188
+ }
189
+ });
190
+ });
191
+ }
192
+ core(path, options || {}, function(er, is) {
193
+ if (er) {
194
+ if (er.code === "EACCES" || options && options.ignoreErrors) {
195
+ er = null;
196
+ is = false;
197
+ }
198
+ }
199
+ cb(er, is);
200
+ });
201
+ }
202
+ function sync(path, options) {
203
+ try {
204
+ return core.sync(path, options || {});
205
+ } catch (er) {
206
+ if (options && options.ignoreErrors || er.code === "EACCES") {
207
+ return false;
208
+ } else {
209
+ throw er;
210
+ }
211
+ }
212
+ }
213
+ }
214
+ });
215
+
216
+ // node_modules/which/which.js
217
+ var require_which = __commonJS({
218
+ "node_modules/which/which.js"(exports, module) {
219
+ "use strict";
220
+ init_esm_shims();
221
+ var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
222
+ var path = __require("path");
223
+ var COLON = isWindows ? ";" : ":";
224
+ var isexe = require_isexe();
225
+ var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
226
+ var getPathInfo = (cmd, opt) => {
227
+ const colon = opt.colon || COLON;
228
+ const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [""] : [
229
+ // windows always checks the cwd first
230
+ ...isWindows ? [process.cwd()] : [],
231
+ ...(opt.path || process.env.PATH || /* istanbul ignore next: very unusual */
232
+ "").split(colon)
233
+ ];
234
+ const pathExtExe = isWindows ? opt.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "";
235
+ const pathExt = isWindows ? pathExtExe.split(colon) : [""];
236
+ if (isWindows) {
237
+ if (cmd.indexOf(".") !== -1 && pathExt[0] !== "")
238
+ pathExt.unshift("");
239
+ }
240
+ return {
241
+ pathEnv,
242
+ pathExt,
243
+ pathExtExe
244
+ };
245
+ };
246
+ var which = (cmd, opt, cb) => {
247
+ if (typeof opt === "function") {
248
+ cb = opt;
249
+ opt = {};
250
+ }
251
+ if (!opt)
252
+ opt = {};
253
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
254
+ const found = [];
255
+ const step = (i) => new Promise((resolve2, reject) => {
256
+ if (i === pathEnv.length)
257
+ return opt.all && found.length ? resolve2(found) : reject(getNotFoundError(cmd));
258
+ const ppRaw = pathEnv[i];
259
+ const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
260
+ const pCmd = path.join(pathPart, cmd);
261
+ const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
262
+ resolve2(subStep(p, i, 0));
263
+ });
264
+ const subStep = (p, i, ii) => new Promise((resolve2, reject) => {
265
+ if (ii === pathExt.length)
266
+ return resolve2(step(i + 1));
267
+ const ext = pathExt[ii];
268
+ isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
269
+ if (!er && is) {
270
+ if (opt.all)
271
+ found.push(p + ext);
272
+ else
273
+ return resolve2(p + ext);
274
+ }
275
+ return resolve2(subStep(p, i, ii + 1));
276
+ });
277
+ });
278
+ return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
279
+ };
280
+ var whichSync = (cmd, opt) => {
281
+ opt = opt || {};
282
+ const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
283
+ const found = [];
284
+ for (let i = 0; i < pathEnv.length; i++) {
285
+ const ppRaw = pathEnv[i];
286
+ const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
287
+ const pCmd = path.join(pathPart, cmd);
288
+ const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
289
+ for (let j = 0; j < pathExt.length; j++) {
290
+ const cur = p + pathExt[j];
291
+ try {
292
+ const is = isexe.sync(cur, { pathExt: pathExtExe });
293
+ if (is) {
294
+ if (opt.all)
295
+ found.push(cur);
296
+ else
297
+ return cur;
298
+ }
299
+ } catch (ex) {
300
+ }
301
+ }
302
+ }
303
+ if (opt.all && found.length)
304
+ return found;
305
+ if (opt.nothrow)
306
+ return null;
307
+ throw getNotFoundError(cmd);
308
+ };
309
+ module.exports = which;
310
+ which.sync = whichSync;
311
+ }
312
+ });
313
+
314
+ // node_modules/path-key/index.js
315
+ var require_path_key = __commonJS({
316
+ "node_modules/path-key/index.js"(exports, module) {
317
+ "use strict";
318
+ init_esm_shims();
319
+ var pathKey = (options = {}) => {
320
+ const environment = options.env || process.env;
321
+ const platform = options.platform || process.platform;
322
+ if (platform !== "win32") {
323
+ return "PATH";
324
+ }
325
+ return Object.keys(environment).reverse().find((key) => key.toUpperCase() === "PATH") || "Path";
326
+ };
327
+ module.exports = pathKey;
328
+ module.exports.default = pathKey;
329
+ }
330
+ });
331
+
332
+ // node_modules/cross-spawn/lib/util/resolveCommand.js
333
+ var require_resolveCommand = __commonJS({
334
+ "node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
335
+ "use strict";
336
+ init_esm_shims();
337
+ var path = __require("path");
338
+ var which = require_which();
339
+ var getPathKey = require_path_key();
340
+ function resolveCommandAttempt(parsed, withoutPathExt) {
341
+ const env = parsed.options.env || process.env;
342
+ const cwd = process.cwd();
343
+ const hasCustomCwd = parsed.options.cwd != null;
344
+ const shouldSwitchCwd = hasCustomCwd && process.chdir !== void 0 && !process.chdir.disabled;
345
+ if (shouldSwitchCwd) {
346
+ try {
347
+ process.chdir(parsed.options.cwd);
348
+ } catch (err) {
349
+ }
350
+ }
351
+ let resolved;
352
+ try {
353
+ resolved = which.sync(parsed.command, {
354
+ path: env[getPathKey({ env })],
355
+ pathExt: withoutPathExt ? path.delimiter : void 0
356
+ });
357
+ } catch (e) {
358
+ } finally {
359
+ if (shouldSwitchCwd) {
360
+ process.chdir(cwd);
361
+ }
362
+ }
363
+ if (resolved) {
364
+ resolved = path.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
365
+ }
366
+ return resolved;
367
+ }
368
+ function resolveCommand(parsed) {
369
+ return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true);
370
+ }
371
+ module.exports = resolveCommand;
372
+ }
373
+ });
374
+
375
+ // node_modules/cross-spawn/lib/util/escape.js
376
+ var require_escape = __commonJS({
377
+ "node_modules/cross-spawn/lib/util/escape.js"(exports, module) {
378
+ "use strict";
379
+ init_esm_shims();
380
+ var metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
381
+ function escapeCommand(arg) {
382
+ arg = arg.replace(metaCharsRegExp, "^$1");
383
+ return arg;
384
+ }
385
+ function escapeArgument(arg, doubleEscapeMetaChars) {
386
+ arg = `${arg}`;
387
+ arg = arg.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
388
+ arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
389
+ arg = `"${arg}"`;
390
+ arg = arg.replace(metaCharsRegExp, "^$1");
391
+ if (doubleEscapeMetaChars) {
392
+ arg = arg.replace(metaCharsRegExp, "^$1");
393
+ }
394
+ return arg;
395
+ }
396
+ module.exports.command = escapeCommand;
397
+ module.exports.argument = escapeArgument;
398
+ }
399
+ });
400
+
401
+ // node_modules/shebang-regex/index.js
402
+ var require_shebang_regex = __commonJS({
403
+ "node_modules/shebang-regex/index.js"(exports, module) {
404
+ "use strict";
405
+ init_esm_shims();
406
+ module.exports = /^#!(.*)/;
407
+ }
408
+ });
409
+
410
+ // node_modules/shebang-command/index.js
411
+ var require_shebang_command = __commonJS({
412
+ "node_modules/shebang-command/index.js"(exports, module) {
413
+ "use strict";
414
+ init_esm_shims();
415
+ var shebangRegex = require_shebang_regex();
416
+ module.exports = (string2 = "") => {
417
+ const match = string2.match(shebangRegex);
418
+ if (!match) {
419
+ return null;
420
+ }
421
+ const [path, argument] = match[0].replace(/#! ?/, "").split(" ");
422
+ const binary = path.split("/").pop();
423
+ if (binary === "env") {
424
+ return argument;
425
+ }
426
+ return argument ? `${binary} ${argument}` : binary;
427
+ };
428
+ }
429
+ });
430
+
431
+ // node_modules/cross-spawn/lib/util/readShebang.js
432
+ var require_readShebang = __commonJS({
433
+ "node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
434
+ "use strict";
435
+ init_esm_shims();
436
+ var fs = __require("fs");
437
+ var shebangCommand = require_shebang_command();
438
+ function readShebang(command) {
439
+ const size = 150;
440
+ const buffer = Buffer.alloc(size);
441
+ let fd;
442
+ try {
443
+ fd = fs.openSync(command, "r");
444
+ fs.readSync(fd, buffer, 0, size, 0);
445
+ fs.closeSync(fd);
446
+ } catch (e) {
447
+ }
448
+ return shebangCommand(buffer.toString());
449
+ }
450
+ module.exports = readShebang;
451
+ }
452
+ });
453
+
454
+ // node_modules/cross-spawn/lib/parse.js
455
+ var require_parse = __commonJS({
456
+ "node_modules/cross-spawn/lib/parse.js"(exports, module) {
457
+ "use strict";
458
+ init_esm_shims();
459
+ var path = __require("path");
460
+ var resolveCommand = require_resolveCommand();
461
+ var escape = require_escape();
462
+ var readShebang = require_readShebang();
463
+ var isWin = process.platform === "win32";
464
+ var isExecutableRegExp = /\.(?:com|exe)$/i;
465
+ var isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
466
+ function detectShebang(parsed) {
467
+ parsed.file = resolveCommand(parsed);
468
+ const shebang = parsed.file && readShebang(parsed.file);
469
+ if (shebang) {
470
+ parsed.args.unshift(parsed.file);
471
+ parsed.command = shebang;
472
+ return resolveCommand(parsed);
473
+ }
474
+ return parsed.file;
475
+ }
476
+ function parseNonShell(parsed) {
477
+ if (!isWin) {
478
+ return parsed;
479
+ }
480
+ const commandFile = detectShebang(parsed);
481
+ const needsShell = !isExecutableRegExp.test(commandFile);
482
+ if (parsed.options.forceShell || needsShell) {
483
+ const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
484
+ parsed.command = path.normalize(parsed.command);
485
+ parsed.command = escape.command(parsed.command);
486
+ parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
487
+ const shellCommand = [parsed.command].concat(parsed.args).join(" ");
488
+ parsed.args = ["/d", "/s", "/c", `"${shellCommand}"`];
489
+ parsed.command = process.env.comspec || "cmd.exe";
490
+ parsed.options.windowsVerbatimArguments = true;
491
+ }
492
+ return parsed;
493
+ }
494
+ function parse(command, args, options) {
495
+ if (args && !Array.isArray(args)) {
496
+ options = args;
497
+ args = null;
498
+ }
499
+ args = args ? args.slice(0) : [];
500
+ options = Object.assign({}, options);
501
+ const parsed = {
502
+ command,
503
+ args,
504
+ options,
505
+ file: void 0,
506
+ original: {
507
+ command,
508
+ args
509
+ }
510
+ };
511
+ return options.shell ? parsed : parseNonShell(parsed);
512
+ }
513
+ module.exports = parse;
514
+ }
515
+ });
516
+
517
+ // node_modules/cross-spawn/lib/enoent.js
518
+ var require_enoent = __commonJS({
519
+ "node_modules/cross-spawn/lib/enoent.js"(exports, module) {
520
+ "use strict";
521
+ init_esm_shims();
522
+ var isWin = process.platform === "win32";
523
+ function notFoundError(original, syscall) {
524
+ return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), {
525
+ code: "ENOENT",
526
+ errno: "ENOENT",
527
+ syscall: `${syscall} ${original.command}`,
528
+ path: original.command,
529
+ spawnargs: original.args
530
+ });
531
+ }
532
+ function hookChildProcess(cp, parsed) {
533
+ if (!isWin) {
534
+ return;
535
+ }
536
+ const originalEmit = cp.emit;
537
+ cp.emit = function(name, arg1) {
538
+ if (name === "exit") {
539
+ const err = verifyENOENT(arg1, parsed);
540
+ if (err) {
541
+ return originalEmit.call(cp, "error", err);
542
+ }
543
+ }
544
+ return originalEmit.apply(cp, arguments);
545
+ };
546
+ }
547
+ function verifyENOENT(status, parsed) {
548
+ if (isWin && status === 1 && !parsed.file) {
549
+ return notFoundError(parsed.original, "spawn");
550
+ }
551
+ return null;
552
+ }
553
+ function verifyENOENTSync(status, parsed) {
554
+ if (isWin && status === 1 && !parsed.file) {
555
+ return notFoundError(parsed.original, "spawnSync");
556
+ }
557
+ return null;
558
+ }
559
+ module.exports = {
560
+ hookChildProcess,
561
+ verifyENOENT,
562
+ verifyENOENTSync,
563
+ notFoundError
564
+ };
565
+ }
566
+ });
567
+
568
+ // node_modules/cross-spawn/index.js
569
+ var require_cross_spawn = __commonJS({
570
+ "node_modules/cross-spawn/index.js"(exports, module) {
571
+ "use strict";
572
+ init_esm_shims();
573
+ var cp = __require("child_process");
574
+ var parse = require_parse();
575
+ var enoent = require_enoent();
576
+ function spawn2(command, args, options) {
577
+ const parsed = parse(command, args, options);
578
+ const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
579
+ enoent.hookChildProcess(spawned, parsed);
580
+ return spawned;
581
+ }
582
+ function spawnSync(command, args, options) {
583
+ const parsed = parse(command, args, options);
584
+ const result2 = cp.spawnSync(parsed.command, parsed.args, parsed.options);
585
+ result2.error = result2.error || enoent.verifyENOENTSync(result2.status, parsed);
586
+ return result2;
587
+ }
588
+ module.exports = spawn2;
589
+ module.exports.spawn = spawn2;
590
+ module.exports.sync = spawnSync;
591
+ module.exports._parse = parse;
592
+ module.exports._enoent = enoent;
593
+ }
594
+ });
595
+
596
+ // src/cli/mcpScan.ts
597
+ init_esm_shims();
598
+ import { resolve } from "path";
599
+
600
+ // src/mcpScan/connect.ts
601
+ init_esm_shims();
602
+
603
+ // node_modules/@modelcontextprotocol/sdk/dist/esm/client/stdio.js
604
+ init_esm_shims();
605
+ var import_cross_spawn = __toESM(require_cross_spawn(), 1);
606
+ import process2 from "process";
607
+ import { PassThrough } from "stream";
608
+ var DEFAULT_INHERITED_ENV_VARS = process2.platform === "win32" ? [
609
+ "APPDATA",
610
+ "HOMEDRIVE",
611
+ "HOMEPATH",
612
+ "LOCALAPPDATA",
613
+ "PATH",
614
+ "PROCESSOR_ARCHITECTURE",
615
+ "SYSTEMDRIVE",
616
+ "SYSTEMROOT",
617
+ "TEMP",
618
+ "USERNAME",
619
+ "USERPROFILE",
620
+ "PROGRAMFILES"
621
+ ] : (
622
+ /* list inspired by the default env inheritance of sudo */
623
+ ["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"]
624
+ );
625
+ function getDefaultEnvironment() {
626
+ const env = {};
627
+ for (const key of DEFAULT_INHERITED_ENV_VARS) {
628
+ const value = process2.env[key];
629
+ if (value === void 0) {
630
+ continue;
631
+ }
632
+ if (value.startsWith("()")) {
633
+ continue;
634
+ }
635
+ env[key] = value;
636
+ }
637
+ return env;
638
+ }
639
+ var StdioClientTransport = class {
640
+ constructor(server) {
641
+ this._readBuffer = new ReadBuffer();
642
+ this._stderrStream = null;
643
+ this._serverParams = server;
644
+ if (server.stderr === "pipe" || server.stderr === "overlapped") {
645
+ this._stderrStream = new PassThrough();
646
+ }
647
+ }
648
+ /**
649
+ * Starts the server process and prepares to communicate with it.
650
+ */
651
+ async start() {
652
+ if (this._process) {
653
+ throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
654
+ }
655
+ return new Promise((resolve2, reject) => {
656
+ this._process = (0, import_cross_spawn.default)(this._serverParams.command, this._serverParams.args ?? [], {
657
+ // merge default env with server env because mcp server needs some env vars
658
+ env: {
659
+ ...getDefaultEnvironment(),
660
+ ...this._serverParams.env
661
+ },
662
+ stdio: ["pipe", "pipe", this._serverParams.stderr ?? "inherit"],
663
+ shell: false,
664
+ windowsHide: process2.platform === "win32",
665
+ cwd: this._serverParams.cwd
666
+ });
667
+ this._process.on("error", (error) => {
668
+ reject(error);
669
+ this.onerror?.(error);
670
+ });
671
+ this._process.on("spawn", () => {
672
+ resolve2();
673
+ });
674
+ this._process.on("close", (_code) => {
675
+ this._process = void 0;
676
+ this.onclose?.();
677
+ });
678
+ this._process.stdin?.on("error", (error) => {
679
+ this.onerror?.(error);
680
+ });
681
+ this._process.stdout?.on("data", (chunk) => {
682
+ this._readBuffer.append(chunk);
683
+ this.processReadBuffer();
684
+ });
685
+ this._process.stdout?.on("error", (error) => {
686
+ this.onerror?.(error);
687
+ });
688
+ if (this._stderrStream && this._process.stderr) {
689
+ this._process.stderr.pipe(this._stderrStream);
690
+ }
691
+ });
692
+ }
693
+ /**
694
+ * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped".
695
+ *
696
+ * If stderr piping was requested, a PassThrough stream is returned _immediately_, allowing callers to
697
+ * attach listeners before the start method is invoked. This prevents loss of any early
698
+ * error output emitted by the child process.
699
+ */
700
+ get stderr() {
701
+ if (this._stderrStream) {
702
+ return this._stderrStream;
703
+ }
704
+ return this._process?.stderr ?? null;
705
+ }
706
+ /**
707
+ * The child process pid spawned by this transport.
708
+ *
709
+ * This is only available after the transport has been started.
710
+ */
711
+ get pid() {
712
+ return this._process?.pid ?? null;
713
+ }
714
+ processReadBuffer() {
715
+ while (true) {
716
+ try {
717
+ const message = this._readBuffer.readMessage();
718
+ if (message === null) {
719
+ break;
720
+ }
721
+ this.onmessage?.(message);
722
+ } catch (error) {
723
+ this.onerror?.(error);
724
+ }
725
+ }
726
+ }
727
+ async close() {
728
+ if (this._process) {
729
+ const processToClose = this._process;
730
+ this._process = void 0;
731
+ const closePromise = new Promise((resolve2) => {
732
+ processToClose.once("close", () => {
733
+ resolve2();
734
+ });
735
+ });
736
+ try {
737
+ processToClose.stdin?.end();
738
+ } catch {
739
+ }
740
+ await Promise.race([closePromise, new Promise((resolve2) => setTimeout(resolve2, 2e3).unref())]);
741
+ if (processToClose.exitCode === null) {
742
+ try {
743
+ processToClose.kill("SIGTERM");
744
+ } catch {
745
+ }
746
+ await Promise.race([closePromise, new Promise((resolve2) => setTimeout(resolve2, 2e3).unref())]);
747
+ }
748
+ if (processToClose.exitCode === null) {
749
+ try {
750
+ processToClose.kill("SIGKILL");
751
+ } catch {
752
+ }
753
+ }
754
+ }
755
+ this._readBuffer.clear();
756
+ }
757
+ send(message) {
758
+ return new Promise((resolve2) => {
759
+ if (!this._process?.stdin) {
760
+ throw new Error("Not connected");
761
+ }
762
+ const json = serializeMessage(message);
763
+ if (this._process.stdin.write(json)) {
764
+ resolve2();
765
+ } else {
766
+ this._process.stdin.once("drain", resolve2);
767
+ }
768
+ });
769
+ }
770
+ };
771
+
772
+ // src/mcpScan/connect.ts
773
+ var DEFAULTS = {
774
+ connectTimeoutMs: 6e4,
775
+ requestTimeoutMs: 2e4,
776
+ serverBudgetMs: 12e4,
777
+ concurrency: 4
778
+ };
779
+ var STDERR_KEEP = 8e3;
780
+ function childEnv(base, extra) {
781
+ const out = {};
782
+ for (const [k, v] of Object.entries(base)) {
783
+ if (v === void 0 || k.startsWith("LURQ_")) continue;
784
+ out[k] = v;
785
+ }
786
+ return { ...out, ...extra };
787
+ }
788
+ var Page = (key) => looseObject({ [key]: array(unknown()).optional(), nextCursor: string().optional() });
789
+ var Deadline = class extends Error {
790
+ constructor(what, ms) {
791
+ super(`${what} did not finish within ${Math.round(ms / 1e3)}s`);
792
+ this.name = "Deadline";
793
+ }
794
+ };
795
+ function before(p, ms, what) {
796
+ let timer;
797
+ return Promise.race([
798
+ p,
799
+ new Promise((_, reject) => {
800
+ timer = setTimeout(() => reject(new Deadline(what, ms)), ms);
801
+ timer.unref();
802
+ })
803
+ ]).finally(() => clearTimeout(timer));
804
+ }
805
+ function openStdio(spec, opts) {
806
+ const transport = new StdioClientTransport({
807
+ command: spec.command,
808
+ args: spec.args,
809
+ env: childEnv(opts.env ?? process.env, spec.env),
810
+ cwd: spec.cwd ?? void 0,
811
+ stderr: "pipe"
812
+ });
813
+ let tail = "";
814
+ transport.stderr?.on("data", (chunk) => {
815
+ tail = (tail + chunk.toString("utf8")).slice(-STDERR_KEEP);
816
+ });
817
+ return wrap(transport, "stdio", () => tail);
818
+ }
819
+ function openHttp(spec, kind) {
820
+ const url = new URL(spec.url);
821
+ const requestInit = { headers: spec.headers };
822
+ const transport = kind === "http" ? new StreamableHTTPClientTransport(url, {
823
+ requestInit,
824
+ reconnectionOptions: {
825
+ maxRetries: 0,
826
+ initialReconnectionDelay: 1e3,
827
+ maxReconnectionDelay: 1e3,
828
+ reconnectionDelayGrowFactor: 1
829
+ }
830
+ }) : new SSEClientTransport(url, {
831
+ requestInit,
832
+ eventSourceInit: {
833
+ fetch: (u, init) => fetch(u, {
834
+ ...init,
835
+ headers: { ...init?.headers, ...spec.headers }
836
+ })
837
+ }
838
+ });
839
+ return wrap(transport, kind, () => "");
840
+ }
841
+ function wrap(transport, kind, stderr) {
842
+ const client = new Client({ name: "lurq-scan", version: VERSION }, { capabilities: {} });
843
+ let noise = 0;
844
+ client.onerror = (err) => {
845
+ if (err instanceof SyntaxError || err.name === "ZodError") noise++;
846
+ };
847
+ return { client, transport, kind, stderr, noise: () => noise };
848
+ }
849
+ async function close(conn) {
850
+ await before(conn.client.close(), 6e3, "close").catch(() => {
851
+ });
852
+ }
853
+ async function paginate(conn, method, key, list, snapshot, opts, onPage, maxPages = LIMITS.pages) {
854
+ const seen = /* @__PURE__ */ new Set();
855
+ let cursor;
856
+ for (let page = 0; ; page++) {
857
+ if (page >= maxPages) {
858
+ snapshot.issues.push({
859
+ list,
860
+ kind: "truncated",
861
+ detail: `still paginating after ${maxPages} pages`
862
+ });
863
+ return;
864
+ }
865
+ const result2 = await conn.client.request(
866
+ { method, params: cursor ? { cursor } : {} },
867
+ Page(key),
868
+ { timeout: opts.requestTimeoutMs, signal: opts.signal }
869
+ );
870
+ const entries = result2[key];
871
+ if (!onPage(Array.isArray(entries) ? entries : [])) return;
872
+ const next = result2.nextCursor;
873
+ if (!next) return;
874
+ if (seen.has(next)) {
875
+ snapshot.issues.push({
876
+ list,
877
+ kind: "truncated",
878
+ detail: "the server repeated a pagination cursor"
879
+ });
880
+ return;
881
+ }
882
+ seen.add(next);
883
+ cursor = next;
884
+ }
885
+ }
886
+ var isMethodNotFound = (err) => err?.code === -32601;
887
+ async function readContract(conn, opts) {
888
+ const snapshot = emptySnapshot();
889
+ const caps = conn.client.getServerCapabilities() ?? {};
890
+ const info = conn.client.getServerVersion();
891
+ snapshot.serverInfo = {
892
+ name: typeof info?.name === "string" ? info.name.slice(0, LIMITS.name) : null,
893
+ version: typeof info?.version === "string" ? info.version.slice(0, 100) : null,
894
+ title: typeof info?.title === "string" ? info.title.slice(0, LIMITS.name) : null
895
+ };
896
+ snapshot.protocolVersion = conn.transport.protocolVersion ?? null;
897
+ snapshot.capabilities = {
898
+ tools: !!caps.tools,
899
+ prompts: !!caps.prompts,
900
+ resources: !!caps.resources,
901
+ logging: !!caps.logging,
902
+ completions: !!caps.completions,
903
+ toolsListChanged: !!caps.tools?.listChanged
904
+ };
905
+ const instructions = conn.client.getInstructions();
906
+ if (typeof instructions === "string" && instructions) {
907
+ snapshot.instructions = instructions.slice(0, LIMITS.instructions);
908
+ if (instructions.length > LIMITS.instructions) {
909
+ snapshot.issues.push({
910
+ list: "initialize",
911
+ kind: "oversized",
912
+ detail: `instructions are ${instructions.length} chars`
913
+ });
914
+ }
915
+ }
916
+ const tools = /* @__PURE__ */ new Map();
917
+ try {
918
+ await paginate(
919
+ conn,
920
+ "tools/list",
921
+ "tools",
922
+ "tools",
923
+ snapshot,
924
+ opts,
925
+ (e) => addTools(e, tools, snapshot.issues)
926
+ );
927
+ } catch (err) {
928
+ if (!(isMethodNotFound(err) && !caps.tools)) throw err;
929
+ }
930
+ snapshot.tools = [...tools.values()];
931
+ const secondary = async (list, run) => {
932
+ try {
933
+ await run();
934
+ } catch (err) {
935
+ if (isMethodNotFound(err)) return;
936
+ snapshot.issues.push({
937
+ list,
938
+ kind: "list_failed",
939
+ detail: (err instanceof Error ? err.message : String(err)).slice(0, 300)
940
+ });
941
+ }
942
+ };
943
+ if (caps.prompts) {
944
+ const prompts = /* @__PURE__ */ new Map();
945
+ await secondary(
946
+ "prompts",
947
+ () => paginate(
948
+ conn,
949
+ "prompts/list",
950
+ "prompts",
951
+ "prompts",
952
+ snapshot,
953
+ opts,
954
+ (e) => addPrompts(e, prompts, snapshot.issues)
955
+ )
956
+ );
957
+ snapshot.prompts = [...prompts.values()];
958
+ }
959
+ if (caps.resources) {
960
+ const templates = /* @__PURE__ */ new Map();
961
+ await secondary(
962
+ "resourceTemplates",
963
+ () => paginate(
964
+ conn,
965
+ "resources/templates/list",
966
+ "resourceTemplates",
967
+ "resourceTemplates",
968
+ snapshot,
969
+ opts,
970
+ (e) => addTemplates(e, templates, snapshot.issues)
971
+ )
972
+ );
973
+ snapshot.resourceTemplates = [...templates.values()];
974
+ let count = 0;
975
+ await secondary(
976
+ "resources",
977
+ () => paginate(
978
+ conn,
979
+ "resources/list",
980
+ "resources",
981
+ "resources",
982
+ snapshot,
983
+ opts,
984
+ (e) => {
985
+ count += e.length;
986
+ return true;
987
+ },
988
+ LIMITS.resourcePages
989
+ )
990
+ );
991
+ snapshot.resourceCount = count;
992
+ }
993
+ if (conn.noise() > 0) {
994
+ snapshot.issues.push({
995
+ list: "initialize",
996
+ kind: "stdout_noise",
997
+ detail: `${conn.noise()} non-protocol line(s) on stdout; some clients will fail on this`
998
+ });
999
+ }
1000
+ return snapshot;
1001
+ }
1002
+ function shouldTrySse(err) {
1003
+ if (err instanceof StreamableHTTPError)
1004
+ return [400, 404, 405].includes(err.code ?? 0) || err.code === -1;
1005
+ return false;
1006
+ }
1007
+ function result(spec, started, fields) {
1008
+ return {
1009
+ alias: spec.alias,
1010
+ serverKey: spec.serverKey,
1011
+ configFingerprint: spec.configFingerprint,
1012
+ registry: spec.registry,
1013
+ packageName: spec.packageName,
1014
+ pinnedVersion: spec.pinnedVersion,
1015
+ transport: spec.transport,
1016
+ transportUsed: null,
1017
+ error: null,
1018
+ hint: null,
1019
+ stderrTail: null,
1020
+ snapshot: null,
1021
+ durationMs: Date.now() - started,
1022
+ ...fields
1023
+ };
1024
+ }
1025
+ async function attempt(spec, kind, opts, started) {
1026
+ const connectTimeoutMs = opts.connectTimeoutMs ?? (kind === "stdio" ? DEFAULTS.connectTimeoutMs : 2e4);
1027
+ const requestTimeoutMs = opts.requestTimeoutMs ?? DEFAULTS.requestTimeoutMs;
1028
+ const budget = opts.serverBudgetMs ?? DEFAULTS.serverBudgetMs;
1029
+ let conn = null;
1030
+ let stage = "connect";
1031
+ try {
1032
+ conn = kind === "stdio" ? openStdio(spec, opts) : openHttp(spec, kind);
1033
+ const c = conn;
1034
+ const work = (async () => {
1035
+ await before(
1036
+ c.client.connect(c.transport, { timeout: connectTimeoutMs, signal: opts.signal }),
1037
+ connectTimeoutMs + 2e3,
1038
+ "connect"
1039
+ );
1040
+ stage = "list";
1041
+ return readContract(c, { requestTimeoutMs, signal: opts.signal });
1042
+ })();
1043
+ const snapshot = await before(work, Math.max(1e3, budget - (Date.now() - started)), "scan");
1044
+ const clean = scrubDeep(snapshot, spec.secrets);
1045
+ const partial = clean.issues.some((i) => i.kind === "list_failed" || i.kind === "truncated");
1046
+ return result(spec, started, {
1047
+ status: partial ? "partial" : "ok",
1048
+ transportUsed: kind,
1049
+ snapshot: clean,
1050
+ stderrTail: tailOf(conn, spec)
1051
+ });
1052
+ } catch (err) {
1053
+ if (kind === "http" && spec.transport === "auto" && shouldTrySse(err))
1054
+ return { retrySse: true };
1055
+ if (opts.signal?.aborted) {
1056
+ return result(spec, started, {
1057
+ status: "cancelled",
1058
+ transportUsed: kind,
1059
+ error: "scan interrupted"
1060
+ });
1061
+ }
1062
+ const c = err instanceof Deadline ? {
1063
+ status: "timeout",
1064
+ error: err.message,
1065
+ hint: stage === "connect" && kind === "stdio" ? "a first run of npx/uvx downloads the package; retry with a longer --timeout" : null
1066
+ } : classifyError(err, {
1067
+ stage,
1068
+ transport: kind,
1069
+ command: spec.command,
1070
+ stderrTail: conn?.stderr() ?? "",
1071
+ stdoutNoise: conn?.noise() ?? 0
1072
+ });
1073
+ return result(spec, started, {
1074
+ status: c.status,
1075
+ transportUsed: kind,
1076
+ error: scrub(c.error, spec.secrets),
1077
+ hint: c.hint,
1078
+ stderrTail: tailOf(conn, spec)
1079
+ });
1080
+ } finally {
1081
+ if (conn) await close(conn);
1082
+ }
1083
+ }
1084
+ function tailOf(conn, spec) {
1085
+ const t = conn?.stderr().trim();
1086
+ return t ? scrub(t.split("\n").slice(-20).join("\n"), spec.secrets).slice(-2e3) : null;
1087
+ }
1088
+ async function scanServer(spec, opts = {}) {
1089
+ const started = Date.now();
1090
+ if (spec.disabled)
1091
+ return result(spec, started, { status: "disabled", hint: "disabled in its config" });
1092
+ if (!spec.trusted) return result(spec, started, { status: "untrusted", hint: spec.trustReason });
1093
+ if (spec.unresolved.length) {
1094
+ return result(spec, started, {
1095
+ status: "needs_config",
1096
+ error: `no value for ${spec.unresolved.join(", ")}`,
1097
+ hint: `export ${spec.unresolved.filter((v) => !v.startsWith("input:")).join(", ") || "the missing values"} before scanning`
1098
+ });
1099
+ }
1100
+ if (opts.signal?.aborted)
1101
+ return result(spec, started, { status: "cancelled", error: "scan interrupted" });
1102
+ try {
1103
+ if (spec.transport === "stdio") {
1104
+ if (!spec.command)
1105
+ return result(spec, started, { status: "spawn_failed", error: "no command configured" });
1106
+ return await attempt(spec, "stdio", opts, started);
1107
+ }
1108
+ if (!spec.url)
1109
+ return result(spec, started, { status: "unreachable", error: "no url configured" });
1110
+ try {
1111
+ new URL(spec.url);
1112
+ } catch {
1113
+ return result(spec, started, {
1114
+ status: "unreachable",
1115
+ error: "the configured url is not valid",
1116
+ hint: "check the url in the config"
1117
+ });
1118
+ }
1119
+ const first = await attempt(spec, spec.transport === "sse" ? "sse" : "http", opts, started);
1120
+ if ("retrySse" in first) return await attempt(spec, "sse", opts, started);
1121
+ return first;
1122
+ } catch (err) {
1123
+ return result(spec, started, {
1124
+ status: "protocol_error",
1125
+ error: scrub(err instanceof Error ? err.message : String(err), spec.secrets)
1126
+ });
1127
+ }
1128
+ }
1129
+ async function scanServers(specs, opts = {}) {
1130
+ return pMap(
1131
+ specs,
1132
+ async (spec) => {
1133
+ const scan = await scanServer(spec, opts);
1134
+ opts.onResult?.(scan);
1135
+ return scan;
1136
+ },
1137
+ Math.max(1, opts.concurrency ?? DEFAULTS.concurrency)
1138
+ );
1139
+ }
1140
+
1141
+ // src/mcpScan/localHistory.ts
1142
+ init_esm_shims();
1143
+ import { createHash } from "crypto";
1144
+ import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
1145
+ import { join } from "path";
1146
+ var dir = () => join(lurqHome(), "mcp-snapshots");
1147
+ var fileFor = (serverKey, fingerprint) => join(
1148
+ dir(),
1149
+ `${createHash("sha256").update(`${serverKey}#${fingerprint}`).digest("hex").slice(0, 32)}.json`
1150
+ );
1151
+ function loadLocal(serverKey, fingerprint) {
1152
+ try {
1153
+ const parsed = JSON.parse(
1154
+ readFileSync(fileFor(serverKey, fingerprint), "utf8")
1155
+ );
1156
+ if (parsed?.version !== 1 || !Array.isArray(parsed.snapshot?.tools)) return null;
1157
+ return parsed;
1158
+ } catch {
1159
+ return null;
1160
+ }
1161
+ }
1162
+ function saveLocal(entry) {
1163
+ mkdirSync(dir(), { recursive: true, mode: 448 });
1164
+ const target = fileFor(entry.serverKey, entry.configFingerprint);
1165
+ const tmp = `${target}.${process.pid}.tmp`;
1166
+ writeFileSync(tmp, JSON.stringify(entry), { mode: 384 });
1167
+ renameSync(tmp, target);
1168
+ try {
1169
+ chmodSync(target, 384);
1170
+ } catch {
1171
+ }
1172
+ }
1173
+
1174
+ // src/cli/mcpScan.ts
1175
+ var THRESHOLDS = ["critical", "high", "moderate", "low", "info", "none"];
1176
+ function parseThreshold(v) {
1177
+ const t = (v ?? "none").toLowerCase();
1178
+ if (!THRESHOLDS.includes(t)) {
1179
+ throw new Error(`--fail-on must be one of ${THRESHOLDS.join(", ")}; got "${v}"`);
1180
+ }
1181
+ return t === "none" ? null : t;
1182
+ }
1183
+ function parsePositive(v, name, fallback, max) {
1184
+ if (v === void 0) return fallback;
1185
+ const n = Number(v);
1186
+ if (!Number.isFinite(n) || n <= 0)
1187
+ throw new Error(`${name} must be a positive number; got "${v}"`);
1188
+ return Math.min(n, max);
1189
+ }
1190
+ var canPrompt = (opts) => !!process.stdin.isTTY && !!process.stdout.isTTY && !process.env.CI && !opts.json;
1191
+ var commandLine = (s) => scrub([s.command ?? s.url ?? "", ...s.args].join(" "), s.secrets).slice(0, 200);
1192
+ async function confirmProjectServers(specs, opts) {
1193
+ const pending = specs.filter(
1194
+ (s) => !s.trusted && s.scope === "project" && !/declined/.test(s.trustReason)
1195
+ );
1196
+ if (!pending.length || !canPrompt(opts)) return;
1197
+ console.error(
1198
+ yellow(
1199
+ `
1200
+ ${pending.length} server(s) are committed to this repository and have not been approved:`
1201
+ )
1202
+ );
1203
+ for (const s of pending) console.error(` ${bold(s.alias)} ${dim(commandLine(s))}`);
1204
+ const { confirm } = await import("./dist-4XXIXHHZ.js");
1205
+ const yes = await confirm({ message: "Launch them for this scan?", default: false });
1206
+ if (!yes) return;
1207
+ for (const s of pending) {
1208
+ s.trusted = true;
1209
+ s.trustReason = "approved for this run";
1210
+ }
1211
+ }
1212
+ function comparable(prev, next) {
1213
+ if (next.issues.some((i) => i.list === "tools" && i.kind === "truncated")) return null;
1214
+ const failed = new Set(next.issues.filter((i) => i.kind === "list_failed").map((i) => i.list));
1215
+ return {
1216
+ tools: next.tools,
1217
+ prompts: failed.has("prompts") ? prev.prompts : next.prompts,
1218
+ resourceTemplates: failed.has("resourceTemplates") ? prev.resourceTemplates : next.resourceTemplates,
1219
+ instructions: next.instructions
1220
+ };
1221
+ }
1222
+ function withHistory(scan, analysis, useHistory) {
1223
+ const report = { ...scan, analysis, sinceLastScan: null };
1224
+ if (!useHistory || !scan.snapshot) return report;
1225
+ const prev = loadLocal(scan.serverKey, scan.configFingerprint);
1226
+ const next = scan.snapshot;
1227
+ if (prev) {
1228
+ const input = comparable(prev.snapshot, next);
1229
+ if (input) {
1230
+ const d = diffSnapshots(prev.snapshot, input, scan.alias);
1231
+ report.sinceLastScan = {
1232
+ ...d,
1233
+ at: prev.scannedAt,
1234
+ contract: {
1235
+ breaking: d.contract.breaking,
1236
+ removedTools: d.contract.removedTools,
1237
+ addedTools: d.contract.addedTools,
1238
+ silentDrift: d.contract.silentDrift,
1239
+ annotationFlips: d.contract.annotationFlips
1240
+ }
1241
+ };
1242
+ }
1243
+ }
1244
+ if (scan.status === "ok") {
1245
+ saveLocal({
1246
+ version: 1,
1247
+ serverKey: scan.serverKey,
1248
+ configFingerprint: scan.configFingerprint,
1249
+ scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
1250
+ contentHash: contentHash(next),
1251
+ snapshot: next
1252
+ });
1253
+ }
1254
+ return report;
1255
+ }
1256
+ async function collectScan(dir2, opts) {
1257
+ const root = resolve(dir2 ?? process.cwd());
1258
+ const cfg = readServerConfigs(root, {
1259
+ projectOnly: opts.projectOnly,
1260
+ trustProject: opts.trustProject
1261
+ });
1262
+ const notes = [...cfg.notes];
1263
+ let specs = cfg.servers;
1264
+ if (opts.only) {
1265
+ const wanted = new Set(
1266
+ opts.only.split(",").map((s) => s.trim()).filter(Boolean)
1267
+ );
1268
+ specs = specs.filter((s) => wanted.has(s.alias));
1269
+ const missing = [...wanted].filter((w) => !specs.some((s) => s.alias === w));
1270
+ if (missing.length) notes.push(`no configured server named ${missing.join(", ")}`);
1271
+ }
1272
+ await confirmProjectServers(specs, opts);
1273
+ const timeoutMs = parsePositive(opts.timeout, "--timeout", DEFAULTS.connectTimeoutMs / 1e3, 600) * 1e3;
1274
+ const concurrency = Math.floor(
1275
+ parsePositive(opts.concurrency, "--concurrency", DEFAULTS.concurrency, 16)
1276
+ );
1277
+ const ctl = new AbortController();
1278
+ const onSignal = () => {
1279
+ if (ctl.signal.aborted) process.exit(130);
1280
+ ctl.abort();
1281
+ console.error(
1282
+ yellow("\nstopping: closing the servers already running (Ctrl-C again to force)")
1283
+ );
1284
+ };
1285
+ process.on("SIGINT", onSignal);
1286
+ const progress = !opts.json && process.stderr.isTTY;
1287
+ let scans;
1288
+ try {
1289
+ scans = await scanServers(specs, {
1290
+ concurrency,
1291
+ connectTimeoutMs: timeoutMs,
1292
+ serverBudgetMs: Math.max(DEFAULTS.serverBudgetMs, timeoutMs + 6e4),
1293
+ signal: ctl.signal,
1294
+ onResult: (s) => {
1295
+ if (!progress) return;
1296
+ const mark = s.status === "ok" ? green("\u2713") : s.status === "partial" ? yellow("~") : dim("\xB7");
1297
+ const what = s.snapshot ? `${s.snapshot.tools.length} tools` : s.status;
1298
+ console.error(
1299
+ `${mark} ${s.alias} ${dim(`${what} (${(s.durationMs / 1e3).toFixed(1)}s)`)}`
1300
+ );
1301
+ }
1302
+ });
1303
+ } finally {
1304
+ process.off("SIGINT", onSignal);
1305
+ }
1306
+ const servers = scans.map(
1307
+ (s) => withHistory(s, s.snapshot ? analyzeServer(s.snapshot) : null, opts.history !== false)
1308
+ );
1309
+ const stackScan = analyzeStackScan(
1310
+ // Servers the user chose not to run are not part of the namespace being
1311
+ // assembled; servers that failed are, and make the stack unknown.
1312
+ servers.filter((s) => s.status !== "disabled" && s.status !== "untrusted").map((s) => ({ alias: s.alias, snapshot: s.snapshot }))
1313
+ );
1314
+ const severities = [
1315
+ ...servers.flatMap((s) => s.analysis?.findings ?? []),
1316
+ ...stackScan.findings,
1317
+ ...servers.flatMap(
1318
+ (s) => s.sinceLastScan && !s.sinceLastScan.unchanged ? [s.sinceLastScan] : []
1319
+ )
1320
+ ];
1321
+ return {
1322
+ root,
1323
+ filesRead: cfg.filesRead,
1324
+ notes,
1325
+ servers,
1326
+ stack: stackScan.stack,
1327
+ findings: stackScan.findings,
1328
+ // First source per alias: an alias declared in two files is still one
1329
+ // server, and the first declaration is the one that won.
1330
+ configSources: Object.fromEntries(
1331
+ specs.flatMap((s) => s.sources[0] ? [[s.alias, s.sources[0]]] : [])
1332
+ ),
1333
+ worst: worst(severities),
1334
+ account: null,
1335
+ uploadProblem: null
1336
+ };
1337
+ }
1338
+ var sevColour = (s) => s === "critical" || s === "high" ? red : s === "moderate" ? yellow : dim;
1339
+ function ago(iso) {
1340
+ const mins = Math.round((Date.now() - Date.parse(iso)) / 6e4);
1341
+ if (!Number.isFinite(mins)) return iso;
1342
+ if (mins < 60) return `${Math.max(mins, 1)}m ago`;
1343
+ if (mins < 60 * 48) return `${Math.round(mins / 60)}h ago`;
1344
+ return `${Math.round(mins / 1440)}d ago`;
1345
+ }
1346
+ function does(a) {
1347
+ const caps = Object.entries(a?.stats.capabilities ?? {}).sort((x, y) => (y[1] ?? 0) - (x[1] ?? 0)).map(([c]) => c);
1348
+ if (!caps.length) return dim("\u2014");
1349
+ return caps.length > 2 ? `${caps.slice(0, 2).join(", ")} +${caps.length - 2}` : caps.join(", ");
1350
+ }
1351
+ function statusCell(s) {
1352
+ if (s.status === "ok") return green("ok");
1353
+ if (s.status === "partial") return yellow("partial");
1354
+ if (s.status === "untrusted" || s.status === "disabled") return dim(s.status);
1355
+ return red(s.status);
1356
+ }
1357
+ function render(report) {
1358
+ console.log(bold(report.root));
1359
+ console.log(
1360
+ dim(`read ${report.filesRead.length} config file(s): ${report.filesRead.join(", ") || "\u2014"}`)
1361
+ );
1362
+ console.log("");
1363
+ console.log(
1364
+ table(
1365
+ ["Server", "Via", "Status", "Tools", "Writes", "Destroys", "Does"],
1366
+ report.servers.map((s) => [
1367
+ s.alias,
1368
+ s.transportUsed ?? s.transport,
1369
+ statusCell(s),
1370
+ s.analysis ? String(s.analysis.stats.tools) : "\u2014",
1371
+ s.analysis ? String(s.analysis.stats.writes) : "\u2014",
1372
+ s.analysis ? s.analysis.stats.destroys ? red(String(s.analysis.stats.destroys)) : "0" : "\u2014",
1373
+ does(s.analysis)
1374
+ ])
1375
+ )
1376
+ );
1377
+ const accountSince = new Map(
1378
+ (report.account?.results ?? []).filter((r) => r.since).map((r) => [r.alias, r.since])
1379
+ );
1380
+ const changes = report.servers.flatMap((s) => {
1381
+ const acc2 = accountSince.get(s.alias);
1382
+ if (acc2)
1383
+ return [
1384
+ { alias: s.alias, severity: acc2.severity, at: acc2.at, summary: acc2.summary }
1385
+ ];
1386
+ const d = s.sinceLastScan;
1387
+ return d && !d.unchanged ? [{ alias: s.alias, severity: d.severity, at: d.at, summary: d.summary }] : [];
1388
+ });
1389
+ if (changes.length) {
1390
+ console.log(`
1391
+ ${bold("Since the last scan")}`);
1392
+ for (const c of changes) {
1393
+ console.log(
1394
+ ` ${sevColour(c.severity)(c.severity.toUpperCase().padEnd(8))} ${bold(c.alias)} ${dim(ago(c.at))} ${c.summary}`
1395
+ );
1396
+ }
1397
+ }
1398
+ const findings = sortFindings([
1399
+ ...report.servers.flatMap(
1400
+ (s) => (s.analysis?.findings ?? []).map((f) => ({ ...f, server: s.alias }))
1401
+ ),
1402
+ ...report.findings
1403
+ ]).filter((f) => f.severity !== "info");
1404
+ if (findings.length) {
1405
+ console.log(`
1406
+ ${bold("Findings")}`);
1407
+ for (const f of findings.slice(0, 40)) {
1408
+ const where = [f.server, f.tool].filter(Boolean).join(" \u203A ");
1409
+ console.log(
1410
+ ` ${sevColour(f.severity)(f.severity.toUpperCase().padEnd(8))} ${bold(where || f.where)} ${f.detail}`
1411
+ );
1412
+ if (f.evidence) console.log(` ${dim(f.evidence)}`);
1413
+ }
1414
+ if (findings.length > 40) console.log(dim(` \u2026 ${findings.length - 40} more (use --json)`));
1415
+ }
1416
+ const unread = report.servers.filter((s) => !s.snapshot);
1417
+ if (unread.length) {
1418
+ console.log(`
1419
+ ${bold("Not read")}`);
1420
+ for (const s of unread) {
1421
+ console.log(` ${bold(s.alias)} ${statusCell(s)}${s.error ? ` ${s.error}` : ""}`);
1422
+ if (s.hint) console.log(` ${dim(s.hint)}`);
1423
+ }
1424
+ }
1425
+ console.log("");
1426
+ console.log(dim(report.stack.note));
1427
+ if (isCrowded(report.stack)) {
1428
+ console.log(
1429
+ yellow(
1430
+ `~${report.stack.estimatedContextTokens.toLocaleString()} tokens of tool schema ride in every request \u2014 worth trimming`
1431
+ )
1432
+ );
1433
+ }
1434
+ for (const n of report.notes) console.log(dim(`\xB7 ${n}`));
1435
+ const acc = report.account;
1436
+ if (acc) {
1437
+ if (acc.recorded) {
1438
+ console.log(
1439
+ dim(
1440
+ `recorded ${acc.recorded} server(s) to your account${acc.changed ? `, ${acc.changed} changed since the last upload` : ""}`
1441
+ )
1442
+ );
1443
+ }
1444
+ for (const r of acc.rejected)
1445
+ console.log(yellow(`not recorded: ${r.alias ?? "a server"}: ${r.reason}`));
1446
+ if (acc.error) console.log(yellow(`could not record this scan to your account: ${acc.error}`));
1447
+ } else if (!resolveApiKey()) {
1448
+ console.log(
1449
+ dim("run `lurq setup` to keep this history across machines and see it in the dashboard")
1450
+ );
1451
+ }
1452
+ }
1453
+ function toJson(report) {
1454
+ return {
1455
+ ...report,
1456
+ servers: report.servers.map(({ snapshot, ...rest }) => ({
1457
+ ...rest,
1458
+ serverInfo: snapshot?.serverInfo ?? null,
1459
+ tools: snapshot?.tools.map((t) => t.name) ?? null,
1460
+ issues: snapshot?.issues ?? []
1461
+ }))
1462
+ };
1463
+ }
1464
+ var NOT_UPLOADED = /* @__PURE__ */ new Set(["untrusted", "disabled", "cancelled"]);
1465
+ function toUpload(s) {
1466
+ return {
1467
+ alias: s.alias,
1468
+ serverKey: s.serverKey,
1469
+ configFingerprint: s.configFingerprint,
1470
+ registry: s.registry,
1471
+ packageName: s.packageName,
1472
+ pinnedVersion: s.pinnedVersion,
1473
+ transport: s.transport,
1474
+ status: s.status,
1475
+ error: s.error,
1476
+ snapshot: s.snapshot ? {
1477
+ serverInfo: s.snapshot.serverInfo,
1478
+ instructions: s.snapshot.instructions,
1479
+ tools: s.snapshot.tools,
1480
+ prompts: s.snapshot.prompts,
1481
+ resourceTemplates: s.snapshot.resourceTemplates
1482
+ } : null
1483
+ };
1484
+ }
1485
+ function chunkUploads(items, maxBytes = 3e6, maxCount = 50) {
1486
+ const chunks = [];
1487
+ let current = [];
1488
+ let bytes = 0;
1489
+ for (const item of items) {
1490
+ const size = JSON.stringify(item).length;
1491
+ if (current.length && (bytes + size > maxBytes || current.length >= maxCount)) {
1492
+ chunks.push(current);
1493
+ current = [];
1494
+ bytes = 0;
1495
+ }
1496
+ current.push(item);
1497
+ bytes += size;
1498
+ }
1499
+ if (current.length) chunks.push(current);
1500
+ return chunks;
1501
+ }
1502
+ async function syncToAccount(report, opts) {
1503
+ if (opts.upload === false || !resolveApiKey()) return null;
1504
+ const servers = report.servers.filter((s) => !NOT_UPLOADED.has(s.status)).map(toUpload);
1505
+ if (!servers.length) return null;
1506
+ const out = { recorded: 0, changed: 0, results: [], rejected: [], error: null };
1507
+ for (const chunk of chunkUploads(servers)) {
1508
+ try {
1509
+ const r = await uploadMcpScan({
1510
+ source: process.env.CI ? "ci" : "cli",
1511
+ clientVersion: VERSION,
1512
+ contribute: opts.contribute !== false,
1513
+ servers: chunk
1514
+ });
1515
+ out.results.push(...r.servers);
1516
+ out.rejected.push(...r.rejected.map((x) => ({ alias: x.alias, reason: x.reason })));
1517
+ } catch (err) {
1518
+ out.error = err instanceof RemoteError ? err.message : err instanceof Error ? err.message : String(err);
1519
+ break;
1520
+ }
1521
+ }
1522
+ out.recorded = out.results.length;
1523
+ out.changed = out.results.filter((r) => r.change === "changed").length;
1524
+ return out;
1525
+ }
1526
+ function uploadProblem(report, opts) {
1527
+ if (!opts.requireUpload) return null;
1528
+ if (!resolveApiKey()) return "no API key is configured; set LURQ_API_KEY";
1529
+ const acc = report.account;
1530
+ if (!acc) return null;
1531
+ if (acc.error) return `the upload failed: ${acc.error}`;
1532
+ if (acc.rejected.length) {
1533
+ return `${acc.rejected.length} server(s) were not recorded: ${acc.rejected.map((r) => `${r.alias ?? "a server"} (${r.reason})`).join(", ")}`;
1534
+ }
1535
+ return null;
1536
+ }
1537
+ async function runMcpScan(dir2, opts) {
1538
+ const threshold = parseThreshold(opts.failOn);
1539
+ if (opts.requireUpload && opts.upload === false) {
1540
+ throw new Error("--require-upload and --no-upload contradict each other");
1541
+ }
1542
+ const report = await collectScan(dir2, opts);
1543
+ report.account = await syncToAccount(report, opts);
1544
+ if (report.account) {
1545
+ report.worst = worst([
1546
+ ...report.worst ? [{ severity: report.worst }] : [],
1547
+ ...report.account.results.flatMap(
1548
+ (r) => r.since ? [{ severity: r.since.severity }] : []
1549
+ )
1550
+ ]);
1551
+ }
1552
+ report.uploadProblem = uploadProblem(report, opts);
1553
+ if (opts.sarif) {
1554
+ const { writeFileSync: writeFileSync2 } = await import("fs");
1555
+ const { scanFindings } = await import("./mcpConfig-3LYPRY5Q.js");
1556
+ const { toSarif } = await import("./sarif-WLVJFJ7U.js");
1557
+ const doc = toSarif(scanFindings(report), { version: VERSION, read: () => null });
1558
+ writeFileSync2(opts.sarif, `${JSON.stringify(doc, null, 2)}
1559
+ `, "utf8");
1560
+ console.error(`wrote ${opts.sarif}`);
1561
+ }
1562
+ if (opts.json) console.log(JSON.stringify(toJson(report), null, 2));
1563
+ else if (report.servers.length === 0) {
1564
+ console.log(dim(`no MCP servers configured for ${report.root}`));
1565
+ for (const n of report.notes) console.log(dim(`\xB7 ${n}`));
1566
+ } else render(report);
1567
+ if (threshold && report.worst && SEVERITY_RANK[report.worst] <= SEVERITY_RANK[threshold]) {
1568
+ process.exitCode = 1;
1569
+ }
1570
+ if (report.uploadProblem) {
1571
+ console.error(
1572
+ red(`upload required, and the scan was not fully recorded: ${report.uploadProblem}`)
1573
+ );
1574
+ process.exitCode = 1;
1575
+ }
1576
+ if (opts.githubIssue) await syncDashboardIssue(report);
1577
+ }
1578
+ async function syncDashboardIssue(report, env = process.env) {
1579
+ const { githubEnvFrom, renderDashboardIssue, upsertDashboardIssue } = await import("./dashboardIssue-KBKLQMGC.js");
1580
+ const gh = githubEnvFrom(env);
1581
+ if (!gh) {
1582
+ console.error(
1583
+ yellow("--github-issue needs GITHUB_TOKEN and GITHUB_REPOSITORY; set by GitHub Actions")
1584
+ );
1585
+ return;
1586
+ }
1587
+ let plan = null;
1588
+ let planNote = null;
1589
+ if (resolveApiKey()) {
1590
+ try {
1591
+ const { buildUpgradePlan } = await import("./upgradePlan-ZS5IOVMZ.js");
1592
+ plan = await buildUpgradePlan(report.root, { repo: gh.repo });
1593
+ } catch (err) {
1594
+ planNote = `Not checked: ${err instanceof Error ? err.message : String(err)}`.slice(0, 200);
1595
+ }
1596
+ } else {
1597
+ planNote = "Add a LURQ_API_KEY secret to include dependency upgrades here.";
1598
+ }
1599
+ const runUrl = env.GITHUB_RUN_ID ? `${env.GITHUB_SERVER_URL ?? "https://github.com"}/${gh.repo}/actions/runs/${env.GITHUB_RUN_ID}` : null;
1600
+ const rendered = renderDashboardIssue({
1601
+ repo: gh.repo,
1602
+ generatedAt: /* @__PURE__ */ new Date(),
1603
+ runUrl,
1604
+ dashboardUrl: "https://lurq.run/dashboard/mcp",
1605
+ scan: report,
1606
+ plan,
1607
+ planNote
1608
+ });
1609
+ try {
1610
+ const r = await upsertDashboardIssue(gh, rendered);
1611
+ const what = r.action === "closed" ? "is closed, so it was left alone" : r.action;
1612
+ console.error(
1613
+ dim(
1614
+ `dashboard issue ${what}${r.url ? `: ${r.url}` : ""}${r.commented ? " (commented on new urgent findings)" : ""}`
1615
+ )
1616
+ );
1617
+ } catch (err) {
1618
+ console.error(
1619
+ yellow(
1620
+ `::warning::could not update the lurq dashboard issue: ${err instanceof Error ? err.message : String(err)}`
1621
+ )
1622
+ );
1623
+ }
1624
+ }
1625
+ async function runMcpStackLive(dir2, opts) {
1626
+ const report = await collectScan(dir2, { ...opts, history: false });
1627
+ if (opts.json) {
1628
+ console.log(JSON.stringify({ ...report.stack, findings: report.findings }, null, 2));
1629
+ return;
1630
+ }
1631
+ if (report.servers.length === 0) {
1632
+ console.log(dim(`no MCP servers configured for ${report.root}`));
1633
+ return;
1634
+ }
1635
+ const colour = report.stack.overall === "conflict" ? red : report.stack.overall === "unknown" ? yellow : green;
1636
+ console.log(`${bold(report.root)} ${colour(report.stack.overall)}`);
1637
+ for (const c of report.stack.collisions) {
1638
+ const tag = c.writes ? red("writes") : yellow("read-only");
1639
+ console.log(`${red("\u2717")} ${bold(c.tool)} exposed by ${c.servers.join(" and ")} [${tag}]`);
1640
+ }
1641
+ for (const f of report.findings.filter((x) => x.kind === "cross_server_reference")) {
1642
+ console.log(`${sevColour(f.severity)("!")} ${bold(`${f.server} \u203A ${f.tool}`)} ${f.detail}`);
1643
+ }
1644
+ console.log(
1645
+ table(
1646
+ ["Server", "Status", "Tools", "Writes", "Destroys"],
1647
+ report.servers.map((s) => [
1648
+ s.alias,
1649
+ statusCell(s),
1650
+ s.analysis ? String(s.analysis.stats.tools) : "\u2014",
1651
+ s.analysis ? String(s.analysis.stats.writes) : "\u2014",
1652
+ s.analysis ? s.analysis.stats.destroys ? red(String(s.analysis.stats.destroys)) : "0" : "\u2014"
1653
+ ])
1654
+ )
1655
+ );
1656
+ console.log(dim(report.stack.note));
1657
+ if (isCrowded(report.stack)) {
1658
+ console.log(
1659
+ yellow(
1660
+ `~${report.stack.estimatedContextTokens.toLocaleString()} tokens of tool schema ride in every request \u2014 worth trimming`
1661
+ )
1662
+ );
1663
+ }
1664
+ }
1665
+ async function runMcpCi(dir2, opts) {
1666
+ const threshold = parseThreshold(opts.failOn ?? "high");
1667
+ const root = resolve(dir2 ?? process.cwd());
1668
+ const cfg = readServerConfigs(root, { projectOnly: true, trustProject: true, env: {} });
1669
+ const { MCP_SCAN_WORKFLOW_PATH, renderMcpScanWorkflow, secretNameFor } = await import("./mcpScanWorkflow-27O44FOE.js");
1670
+ const secrets = [
1671
+ ...new Set(cfg.servers.flatMap((s) => s.unresolved.filter((v) => !v.startsWith("input:"))))
1672
+ ];
1673
+ const needsUv = cfg.servers.some(
1674
+ (s) => s.registry === "pypi" || /^(uvx|uv|pipx)$/.test((s.command ?? "").split(/[\\/]/).pop() ?? "")
1675
+ );
1676
+ const yaml = renderMcpScanWorkflow({
1677
+ cron: opts.cron,
1678
+ failOn: threshold ?? "none",
1679
+ secrets,
1680
+ needsUv,
1681
+ githubIssue: opts.issue !== false,
1682
+ sarif: opts.sarif === true
1683
+ });
1684
+ if (opts.print) {
1685
+ process.stdout.write(yaml);
1686
+ return;
1687
+ }
1688
+ const { existsSync, mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("fs");
1689
+ const { dirname, join: join2 } = await import("path");
1690
+ const target = join2(root, MCP_SCAN_WORKFLOW_PATH);
1691
+ if (existsSync(target) && !opts.force) {
1692
+ throw new Error(`${MCP_SCAN_WORKFLOW_PATH} already exists; pass --force to replace it`);
1693
+ }
1694
+ mkdirSync2(dirname(target), { recursive: true });
1695
+ writeFileSync2(target, yaml);
1696
+ console.log(`wrote ${MCP_SCAN_WORKFLOW_PATH}`);
1697
+ if (cfg.servers.length === 0) {
1698
+ console.log(
1699
+ yellow(
1700
+ "no MCP servers are committed to this repository yet; the workflow scans only committed configs (.mcp.json)"
1701
+ )
1702
+ );
1703
+ } else {
1704
+ console.log(
1705
+ dim(
1706
+ `scans ${cfg.servers.length} committed server(s): ${cfg.servers.map((s) => s.alias).join(", ")}`
1707
+ )
1708
+ );
1709
+ }
1710
+ if (opts.sarif) {
1711
+ console.log(
1712
+ dim(
1713
+ "findings upload to code scanning; enable it under Settings \u2192 Code security, or drop --sarif"
1714
+ )
1715
+ );
1716
+ }
1717
+ console.log("add these repository secrets:");
1718
+ console.log(
1719
+ ` ${bold("LURQ_API_KEY")} ${dim("your lurq key, so scans are recorded to your account")}`
1720
+ );
1721
+ for (const s of secrets.sort())
1722
+ console.log(
1723
+ ` ${bold(secretNameFor(s))}${secretNameFor(s) !== s ? dim(` (read by the server as ${s})`) : ""}`
1724
+ );
1725
+ }
1726
+ export {
1727
+ chunkUploads,
1728
+ collectScan,
1729
+ runMcpCi,
1730
+ runMcpScan,
1731
+ runMcpStackLive,
1732
+ syncDashboardIssue,
1733
+ syncToAccount,
1734
+ uploadProblem
1735
+ };