@newrelic/preflight 0.0.1-pre.1 → 1.0.0

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 (578) hide show
  1. package/LICENSE +183 -0
  2. package/README.md +498 -0
  3. package/dist/alerts/alert-log.d.ts +24 -0
  4. package/dist/alerts/alert-log.d.ts.map +1 -0
  5. package/dist/alerts/alert-log.js +159 -0
  6. package/dist/alerts/alert-log.js.map +1 -0
  7. package/dist/alerts/alert-snapshot-collector.d.ts +168 -0
  8. package/dist/alerts/alert-snapshot-collector.d.ts.map +1 -0
  9. package/dist/alerts/alert-snapshot-collector.js +243 -0
  10. package/dist/alerts/alert-snapshot-collector.js.map +1 -0
  11. package/dist/alerts/local-alert-engine.d.ts +86 -0
  12. package/dist/alerts/local-alert-engine.d.ts.map +1 -0
  13. package/dist/alerts/local-alert-engine.js +466 -0
  14. package/dist/alerts/local-alert-engine.js.map +1 -0
  15. package/dist/alerts/local-alert-rule.d.ts +439 -0
  16. package/dist/alerts/local-alert-rule.d.ts.map +1 -0
  17. package/dist/alerts/local-alert-rule.js +139 -0
  18. package/dist/alerts/local-alert-rule.js.map +1 -0
  19. package/dist/alerts/os-notifier.d.ts +39 -0
  20. package/dist/alerts/os-notifier.d.ts.map +1 -0
  21. package/dist/alerts/os-notifier.js +170 -0
  22. package/dist/alerts/os-notifier.js.map +1 -0
  23. package/dist/alerts/types.d.ts +35 -0
  24. package/dist/alerts/types.d.ts.map +1 -0
  25. package/dist/alerts/types.js +8 -0
  26. package/dist/alerts/types.js.map +1 -0
  27. package/dist/config.d.ts +169 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +860 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/dashboard/dashboard-server.d.ts +38 -0
  32. package/dist/dashboard/dashboard-server.d.ts.map +1 -0
  33. package/dist/dashboard/dashboard-server.js +207 -0
  34. package/dist/dashboard/dashboard-server.js.map +1 -0
  35. package/dist/dashboard/index.d.ts +3 -0
  36. package/dist/dashboard/index.d.ts.map +1 -0
  37. package/dist/dashboard/index.js +2 -0
  38. package/dist/dashboard/index.js.map +1 -0
  39. package/dist/dashboard/live-event-bus.d.ts +99 -0
  40. package/dist/dashboard/live-event-bus.d.ts.map +1 -0
  41. package/dist/dashboard/live-event-bus.js +56 -0
  42. package/dist/dashboard/live-event-bus.js.map +1 -0
  43. package/dist/dashboard/routes/api-handler.d.ts +122 -0
  44. package/dist/dashboard/routes/api-handler.d.ts.map +1 -0
  45. package/dist/dashboard/routes/api-handler.js +1414 -0
  46. package/dist/dashboard/routes/api-handler.js.map +1 -0
  47. package/dist/dashboard/routes/replay-analyzer.d.ts +15 -0
  48. package/dist/dashboard/routes/replay-analyzer.d.ts.map +1 -0
  49. package/dist/dashboard/routes/replay-analyzer.js +227 -0
  50. package/dist/dashboard/routes/replay-analyzer.js.map +1 -0
  51. package/dist/dashboard/routes/sse-handler.d.ts +4 -0
  52. package/dist/dashboard/routes/sse-handler.d.ts.map +1 -0
  53. package/dist/dashboard/routes/sse-handler.js +122 -0
  54. package/dist/dashboard/routes/sse-handler.js.map +1 -0
  55. package/dist/dashboard/routes/static-handler.d.ts +3 -0
  56. package/dist/dashboard/routes/static-handler.d.ts.map +1 -0
  57. package/dist/dashboard/routes/static-handler.js +103 -0
  58. package/dist/dashboard/routes/static-handler.js.map +1 -0
  59. package/dist/data/alerts/conditions/01-daily-cost-spike.json +16 -0
  60. package/dist/data/alerts/conditions/02-low-efficiency-score.json +16 -0
  61. package/dist/data/alerts/conditions/03-stuck-loop-rate.json +16 -0
  62. package/dist/data/alerts/conditions/04-anti-pattern-rate.json +16 -0
  63. package/dist/data/alerts/conditions/05-session-cost-budget.json +16 -0
  64. package/dist/data/alerts/conditions-personal/01-personal-daily-cost.json +16 -0
  65. package/dist/data/alerts/conditions-personal/02-personal-session-cost.json +16 -0
  66. package/dist/data/alerts/conditions-personal/03-personal-low-efficiency.json +16 -0
  67. package/dist/data/alerts/conditions-personal/04-personal-anti-pattern-rate.json +16 -0
  68. package/dist/data/alerts/conditions-personal/05-personal-stuck-loop.json +16 -0
  69. package/dist/data/alerts/policy.json +4 -0
  70. package/dist/data/dashboards/ai-coding-assistant-manager-view.json +103 -0
  71. package/dist/data/dashboards/ai-coding-assistant-overview.json +239 -0
  72. package/dist/data/dashboards/ai-coding-assistant-personal.json +442 -0
  73. package/dist/data/dashboards/ai-coding-assistant-platform-comparison.json +320 -0
  74. package/dist/data/dashboards/ai-coding-assistant-security.json +275 -0
  75. package/dist/data/dashboards/ai-coding-assistant-session-detail.json +296 -0
  76. package/dist/data/dashboards/ai-coding-assistant-team-view.json +345 -0
  77. package/dist/deploy/data-paths.d.ts +22 -0
  78. package/dist/deploy/data-paths.d.ts.map +1 -0
  79. package/dist/deploy/data-paths.js +69 -0
  80. package/dist/deploy/data-paths.js.map +1 -0
  81. package/dist/deploy/deploy-alerts.d.ts +58 -0
  82. package/dist/deploy/deploy-alerts.d.ts.map +1 -0
  83. package/dist/deploy/deploy-alerts.js +371 -0
  84. package/dist/deploy/deploy-alerts.js.map +1 -0
  85. package/dist/deploy/deploy-dashboards.d.ts +92 -0
  86. package/dist/deploy/deploy-dashboards.d.ts.map +1 -0
  87. package/dist/deploy/deploy-dashboards.js +282 -0
  88. package/dist/deploy/deploy-dashboards.js.map +1 -0
  89. package/dist/digest/digest-formatter.d.ts +3 -0
  90. package/dist/digest/digest-formatter.d.ts.map +1 -0
  91. package/dist/digest/digest-formatter.js +37 -0
  92. package/dist/digest/digest-formatter.js.map +1 -0
  93. package/dist/digest/digest-sender.d.ts +2 -0
  94. package/dist/digest/digest-sender.d.ts.map +1 -0
  95. package/dist/digest/digest-sender.js +29 -0
  96. package/dist/digest/digest-sender.js.map +1 -0
  97. package/dist/hooks/bash-classifier.d.ts +26 -0
  98. package/dist/hooks/bash-classifier.d.ts.map +1 -0
  99. package/dist/hooks/bash-classifier.js +409 -0
  100. package/dist/hooks/bash-classifier.js.map +1 -0
  101. package/dist/hooks/collector-script.d.ts +47 -0
  102. package/dist/hooks/collector-script.d.ts.map +1 -0
  103. package/dist/hooks/collector-script.js +662 -0
  104. package/dist/hooks/collector-script.js.map +1 -0
  105. package/dist/hooks/event-processor.d.ts +65 -0
  106. package/dist/hooks/event-processor.d.ts.map +1 -0
  107. package/dist/hooks/event-processor.js +342 -0
  108. package/dist/hooks/event-processor.js.map +1 -0
  109. package/dist/hooks/index.d.ts +7 -0
  110. package/dist/hooks/index.d.ts.map +1 -0
  111. package/dist/hooks/index.js +5 -0
  112. package/dist/hooks/index.js.map +1 -0
  113. package/dist/hooks/session-resolver.d.ts +66 -0
  114. package/dist/hooks/session-resolver.d.ts.map +1 -0
  115. package/dist/hooks/session-resolver.js +196 -0
  116. package/dist/hooks/session-resolver.js.map +1 -0
  117. package/dist/hooks/tool-parsers.d.ts +19 -0
  118. package/dist/hooks/tool-parsers.d.ts.map +1 -0
  119. package/dist/hooks/tool-parsers.js +260 -0
  120. package/dist/hooks/tool-parsers.js.map +1 -0
  121. package/dist/index.d.ts +107 -0
  122. package/dist/index.d.ts.map +1 -0
  123. package/dist/index.js +1505 -0
  124. package/dist/index.js.map +1 -0
  125. package/dist/install/cli.d.ts +11 -0
  126. package/dist/install/cli.d.ts.map +1 -0
  127. package/dist/install/cli.js +365 -0
  128. package/dist/install/cli.js.map +1 -0
  129. package/dist/install/index.d.ts +4 -0
  130. package/dist/install/index.d.ts.map +1 -0
  131. package/dist/install/index.js +3 -0
  132. package/dist/install/index.js.map +1 -0
  133. package/dist/install/install-helper.d.ts +35 -0
  134. package/dist/install/install-helper.d.ts.map +1 -0
  135. package/dist/install/install-helper.js +227 -0
  136. package/dist/install/install-helper.js.map +1 -0
  137. package/dist/install/key-validator.d.ts +19 -0
  138. package/dist/install/key-validator.d.ts.map +1 -0
  139. package/dist/install/key-validator.js +122 -0
  140. package/dist/install/key-validator.js.map +1 -0
  141. package/dist/install/migrate.d.ts +12 -0
  142. package/dist/install/migrate.d.ts.map +1 -0
  143. package/dist/install/migrate.js +115 -0
  144. package/dist/install/migrate.js.map +1 -0
  145. package/dist/install/schedule.d.ts +11 -0
  146. package/dist/install/schedule.d.ts.map +1 -0
  147. package/dist/install/schedule.js +114 -0
  148. package/dist/install/schedule.js.map +1 -0
  149. package/dist/install/setup-wizard.d.ts +40 -0
  150. package/dist/install/setup-wizard.d.ts.map +1 -0
  151. package/dist/install/setup-wizard.js +489 -0
  152. package/dist/install/setup-wizard.js.map +1 -0
  153. package/dist/lib/date.d.ts +54 -0
  154. package/dist/lib/date.d.ts.map +1 -0
  155. package/dist/lib/date.js +85 -0
  156. package/dist/lib/date.js.map +1 -0
  157. package/dist/metrics/anti-patterns.d.ts +62 -0
  158. package/dist/metrics/anti-patterns.d.ts.map +1 -0
  159. package/dist/metrics/anti-patterns.js +301 -0
  160. package/dist/metrics/anti-patterns.js.map +1 -0
  161. package/dist/metrics/api-failure-tracker.d.ts +82 -0
  162. package/dist/metrics/api-failure-tracker.d.ts.map +1 -0
  163. package/dist/metrics/api-failure-tracker.js +202 -0
  164. package/dist/metrics/api-failure-tracker.js.map +1 -0
  165. package/dist/metrics/budget-tracker.d.ts +60 -0
  166. package/dist/metrics/budget-tracker.d.ts.map +1 -0
  167. package/dist/metrics/budget-tracker.js +130 -0
  168. package/dist/metrics/budget-tracker.js.map +1 -0
  169. package/dist/metrics/claudemd-tracker.d.ts +108 -0
  170. package/dist/metrics/claudemd-tracker.d.ts.map +1 -0
  171. package/dist/metrics/claudemd-tracker.js +337 -0
  172. package/dist/metrics/claudemd-tracker.js.map +1 -0
  173. package/dist/metrics/collaboration-profile.d.ts +65 -0
  174. package/dist/metrics/collaboration-profile.d.ts.map +1 -0
  175. package/dist/metrics/collaboration-profile.js +231 -0
  176. package/dist/metrics/collaboration-profile.js.map +1 -0
  177. package/dist/metrics/context-composition-tracker.d.ts +74 -0
  178. package/dist/metrics/context-composition-tracker.d.ts.map +1 -0
  179. package/dist/metrics/context-composition-tracker.js +202 -0
  180. package/dist/metrics/context-composition-tracker.js.map +1 -0
  181. package/dist/metrics/context-tracker.d.ts +78 -0
  182. package/dist/metrics/context-tracker.d.ts.map +1 -0
  183. package/dist/metrics/context-tracker.js +222 -0
  184. package/dist/metrics/context-tracker.js.map +1 -0
  185. package/dist/metrics/context-window-tracker.d.ts +18 -0
  186. package/dist/metrics/context-window-tracker.d.ts.map +1 -0
  187. package/dist/metrics/context-window-tracker.js +35 -0
  188. package/dist/metrics/context-window-tracker.js.map +1 -0
  189. package/dist/metrics/cost-forecast.d.ts +36 -0
  190. package/dist/metrics/cost-forecast.d.ts.map +1 -0
  191. package/dist/metrics/cost-forecast.js +91 -0
  192. package/dist/metrics/cost-forecast.js.map +1 -0
  193. package/dist/metrics/cost-per-outcome.d.ts +102 -0
  194. package/dist/metrics/cost-per-outcome.d.ts.map +1 -0
  195. package/dist/metrics/cost-per-outcome.js +266 -0
  196. package/dist/metrics/cost-per-outcome.js.map +1 -0
  197. package/dist/metrics/cost-tracker.d.ts +78 -0
  198. package/dist/metrics/cost-tracker.d.ts.map +1 -0
  199. package/dist/metrics/cost-tracker.js +169 -0
  200. package/dist/metrics/cost-tracker.js.map +1 -0
  201. package/dist/metrics/decision-tracker.d.ts +49 -0
  202. package/dist/metrics/decision-tracker.d.ts.map +1 -0
  203. package/dist/metrics/decision-tracker.js +161 -0
  204. package/dist/metrics/decision-tracker.js.map +1 -0
  205. package/dist/metrics/efficiency-score.d.ts +80 -0
  206. package/dist/metrics/efficiency-score.d.ts.map +1 -0
  207. package/dist/metrics/efficiency-score.js +219 -0
  208. package/dist/metrics/efficiency-score.js.map +1 -0
  209. package/dist/metrics/git-efficiency-tracker.d.ts +165 -0
  210. package/dist/metrics/git-efficiency-tracker.d.ts.map +1 -0
  211. package/dist/metrics/git-efficiency-tracker.js +1056 -0
  212. package/dist/metrics/git-efficiency-tracker.js.map +1 -0
  213. package/dist/metrics/index.d.ts +26 -0
  214. package/dist/metrics/index.d.ts.map +1 -0
  215. package/dist/metrics/index.js +14 -0
  216. package/dist/metrics/index.js.map +1 -0
  217. package/dist/metrics/instruction-drift-tracker.d.ts +69 -0
  218. package/dist/metrics/instruction-drift-tracker.d.ts.map +1 -0
  219. package/dist/metrics/instruction-drift-tracker.js +213 -0
  220. package/dist/metrics/instruction-drift-tracker.js.map +1 -0
  221. package/dist/metrics/latency-decomposition.d.ts +50 -0
  222. package/dist/metrics/latency-decomposition.d.ts.map +1 -0
  223. package/dist/metrics/latency-decomposition.js +112 -0
  224. package/dist/metrics/latency-decomposition.js.map +1 -0
  225. package/dist/metrics/latency-tracker.d.ts +33 -0
  226. package/dist/metrics/latency-tracker.d.ts.map +1 -0
  227. package/dist/metrics/latency-tracker.js +93 -0
  228. package/dist/metrics/latency-tracker.js.map +1 -0
  229. package/dist/metrics/live-session-registry.d.ts +29 -0
  230. package/dist/metrics/live-session-registry.d.ts.map +1 -0
  231. package/dist/metrics/live-session-registry.js +103 -0
  232. package/dist/metrics/live-session-registry.js.map +1 -0
  233. package/dist/metrics/model-usage-tracker.d.ts +21 -0
  234. package/dist/metrics/model-usage-tracker.d.ts.map +1 -0
  235. package/dist/metrics/model-usage-tracker.js +53 -0
  236. package/dist/metrics/model-usage-tracker.js.map +1 -0
  237. package/dist/metrics/percentile.d.ts +5 -0
  238. package/dist/metrics/percentile.d.ts.map +1 -0
  239. package/dist/metrics/percentile.js +10 -0
  240. package/dist/metrics/percentile.js.map +1 -0
  241. package/dist/metrics/personal-coach.d.ts +47 -0
  242. package/dist/metrics/personal-coach.d.ts.map +1 -0
  243. package/dist/metrics/personal-coach.js +241 -0
  244. package/dist/metrics/personal-coach.js.map +1 -0
  245. package/dist/metrics/prompt-feedback.d.ts +75 -0
  246. package/dist/metrics/prompt-feedback.d.ts.map +1 -0
  247. package/dist/metrics/prompt-feedback.js +286 -0
  248. package/dist/metrics/prompt-feedback.js.map +1 -0
  249. package/dist/metrics/proxy-metrics.d.ts +54 -0
  250. package/dist/metrics/proxy-metrics.d.ts.map +1 -0
  251. package/dist/metrics/proxy-metrics.js +228 -0
  252. package/dist/metrics/proxy-metrics.js.map +1 -0
  253. package/dist/metrics/quality-proxy-tracker.d.ts +51 -0
  254. package/dist/metrics/quality-proxy-tracker.d.ts.map +1 -0
  255. package/dist/metrics/quality-proxy-tracker.js +162 -0
  256. package/dist/metrics/quality-proxy-tracker.js.map +1 -0
  257. package/dist/metrics/recommendation-engine.d.ts +72 -0
  258. package/dist/metrics/recommendation-engine.d.ts.map +1 -0
  259. package/dist/metrics/recommendation-engine.js +207 -0
  260. package/dist/metrics/recommendation-engine.js.map +1 -0
  261. package/dist/metrics/retry-detector.d.ts +43 -0
  262. package/dist/metrics/retry-detector.d.ts.map +1 -0
  263. package/dist/metrics/retry-detector.js +179 -0
  264. package/dist/metrics/retry-detector.js.map +1 -0
  265. package/dist/metrics/session-tracker.d.ts +75 -0
  266. package/dist/metrics/session-tracker.d.ts.map +1 -0
  267. package/dist/metrics/session-tracker.js +249 -0
  268. package/dist/metrics/session-tracker.js.map +1 -0
  269. package/dist/metrics/task-completion-tracker.d.ts +15 -0
  270. package/dist/metrics/task-completion-tracker.d.ts.map +1 -0
  271. package/dist/metrics/task-completion-tracker.js +27 -0
  272. package/dist/metrics/task-completion-tracker.js.map +1 -0
  273. package/dist/metrics/task-detector.d.ts +84 -0
  274. package/dist/metrics/task-detector.d.ts.map +1 -0
  275. package/dist/metrics/task-detector.js +302 -0
  276. package/dist/metrics/task-detector.js.map +1 -0
  277. package/dist/metrics/tool-selection-scorer.d.ts +39 -0
  278. package/dist/metrics/tool-selection-scorer.d.ts.map +1 -0
  279. package/dist/metrics/tool-selection-scorer.js +193 -0
  280. package/dist/metrics/tool-selection-scorer.js.map +1 -0
  281. package/dist/metrics/trend-analyzer.d.ts +92 -0
  282. package/dist/metrics/trend-analyzer.d.ts.map +1 -0
  283. package/dist/metrics/trend-analyzer.js +293 -0
  284. package/dist/metrics/trend-analyzer.js.map +1 -0
  285. package/dist/metrics/turn-cost-attributor.d.ts +41 -0
  286. package/dist/metrics/turn-cost-attributor.d.ts.map +1 -0
  287. package/dist/metrics/turn-cost-attributor.js +118 -0
  288. package/dist/metrics/turn-cost-attributor.js.map +1 -0
  289. package/dist/metrics/turn-tracker.d.ts +49 -0
  290. package/dist/metrics/turn-tracker.d.ts.map +1 -0
  291. package/dist/metrics/turn-tracker.js +192 -0
  292. package/dist/metrics/turn-tracker.js.map +1 -0
  293. package/dist/platforms/amazon-q-adapter.d.ts +10 -0
  294. package/dist/platforms/amazon-q-adapter.d.ts.map +1 -0
  295. package/dist/platforms/amazon-q-adapter.js +75 -0
  296. package/dist/platforms/amazon-q-adapter.js.map +1 -0
  297. package/dist/platforms/claude-code-adapter.d.ts +10 -0
  298. package/dist/platforms/claude-code-adapter.d.ts.map +1 -0
  299. package/dist/platforms/claude-code-adapter.js +48 -0
  300. package/dist/platforms/claude-code-adapter.js.map +1 -0
  301. package/dist/platforms/continue-adapter.d.ts +10 -0
  302. package/dist/platforms/continue-adapter.d.ts.map +1 -0
  303. package/dist/platforms/continue-adapter.js +73 -0
  304. package/dist/platforms/continue-adapter.js.map +1 -0
  305. package/dist/platforms/copilot-adapter.d.ts +37 -0
  306. package/dist/platforms/copilot-adapter.d.ts.map +1 -0
  307. package/dist/platforms/copilot-adapter.js +66 -0
  308. package/dist/platforms/copilot-adapter.js.map +1 -0
  309. package/dist/platforms/cursor-adapter.d.ts +10 -0
  310. package/dist/platforms/cursor-adapter.d.ts.map +1 -0
  311. package/dist/platforms/cursor-adapter.js +60 -0
  312. package/dist/platforms/cursor-adapter.js.map +1 -0
  313. package/dist/platforms/generic-mcp-adapter.d.ts +113 -0
  314. package/dist/platforms/generic-mcp-adapter.d.ts.map +1 -0
  315. package/dist/platforms/generic-mcp-adapter.js +139 -0
  316. package/dist/platforms/generic-mcp-adapter.js.map +1 -0
  317. package/dist/platforms/index.d.ts +15 -0
  318. package/dist/platforms/index.d.ts.map +1 -0
  319. package/dist/platforms/index.js +12 -0
  320. package/dist/platforms/index.js.map +1 -0
  321. package/dist/platforms/platform-registry.d.ts +11 -0
  322. package/dist/platforms/platform-registry.d.ts.map +1 -0
  323. package/dist/platforms/platform-registry.js +54 -0
  324. package/dist/platforms/platform-registry.js.map +1 -0
  325. package/dist/platforms/types.d.ts +36 -0
  326. package/dist/platforms/types.d.ts.map +1 -0
  327. package/dist/platforms/types.js +2 -0
  328. package/dist/platforms/types.js.map +1 -0
  329. package/dist/platforms/windsurf-adapter.d.ts +10 -0
  330. package/dist/platforms/windsurf-adapter.d.ts.map +1 -0
  331. package/dist/platforms/windsurf-adapter.js +63 -0
  332. package/dist/platforms/windsurf-adapter.js.map +1 -0
  333. package/dist/platforms/zed-adapter.d.ts +10 -0
  334. package/dist/platforms/zed-adapter.d.ts.map +1 -0
  335. package/dist/platforms/zed-adapter.js +72 -0
  336. package/dist/platforms/zed-adapter.js.map +1 -0
  337. package/dist/proxy/index.d.ts +7 -0
  338. package/dist/proxy/index.d.ts.map +1 -0
  339. package/dist/proxy/index.js +5 -0
  340. package/dist/proxy/index.js.map +1 -0
  341. package/dist/proxy/otlp-receiver.d.ts +28 -0
  342. package/dist/proxy/otlp-receiver.d.ts.map +1 -0
  343. package/dist/proxy/otlp-receiver.js +319 -0
  344. package/dist/proxy/otlp-receiver.js.map +1 -0
  345. package/dist/proxy/proxy-manager.d.ts +47 -0
  346. package/dist/proxy/proxy-manager.d.ts.map +1 -0
  347. package/dist/proxy/proxy-manager.js +338 -0
  348. package/dist/proxy/proxy-manager.js.map +1 -0
  349. package/dist/proxy/types.d.ts +72 -0
  350. package/dist/proxy/types.d.ts.map +1 -0
  351. package/dist/proxy/types.js +33 -0
  352. package/dist/proxy/types.js.map +1 -0
  353. package/dist/proxy/upstream-http.d.ts +26 -0
  354. package/dist/proxy/upstream-http.d.ts.map +1 -0
  355. package/dist/proxy/upstream-http.js +209 -0
  356. package/dist/proxy/upstream-http.js.map +1 -0
  357. package/dist/proxy/upstream-stdio.d.ts +25 -0
  358. package/dist/proxy/upstream-stdio.d.ts.map +1 -0
  359. package/dist/proxy/upstream-stdio.js +256 -0
  360. package/dist/proxy/upstream-stdio.js.map +1 -0
  361. package/dist/security/audit-trail.d.ts +74 -0
  362. package/dist/security/audit-trail.d.ts.map +1 -0
  363. package/dist/security/audit-trail.js +338 -0
  364. package/dist/security/audit-trail.js.map +1 -0
  365. package/dist/security/index.d.ts +5 -0
  366. package/dist/security/index.d.ts.map +1 -0
  367. package/dist/security/index.js +4 -0
  368. package/dist/security/index.js.map +1 -0
  369. package/dist/security/ssrf.d.ts +2 -0
  370. package/dist/security/ssrf.d.ts.map +1 -0
  371. package/dist/security/ssrf.js +126 -0
  372. package/dist/security/ssrf.js.map +1 -0
  373. package/dist/server.d.ts +14 -0
  374. package/dist/server.d.ts.map +1 -0
  375. package/dist/server.js +117 -0
  376. package/dist/server.js.map +1 -0
  377. package/dist/shared/__test-utils__/log-output.d.ts +49 -0
  378. package/dist/shared/__test-utils__/log-output.d.ts.map +1 -0
  379. package/dist/shared/__test-utils__/log-output.js +38 -0
  380. package/dist/shared/__test-utils__/log-output.js.map +1 -0
  381. package/dist/shared/config.d.ts +56 -0
  382. package/dist/shared/config.d.ts.map +1 -0
  383. package/dist/shared/config.js +290 -0
  384. package/dist/shared/config.js.map +1 -0
  385. package/dist/shared/errors.d.ts +139 -0
  386. package/dist/shared/errors.d.ts.map +1 -0
  387. package/dist/shared/errors.js +406 -0
  388. package/dist/shared/errors.js.map +1 -0
  389. package/dist/shared/events/factory.d.ts +143 -0
  390. package/dist/shared/events/factory.d.ts.map +1 -0
  391. package/dist/shared/events/factory.js +351 -0
  392. package/dist/shared/events/factory.js.map +1 -0
  393. package/dist/shared/events/index.d.ts +6 -0
  394. package/dist/shared/events/index.d.ts.map +1 -0
  395. package/dist/shared/events/index.js +3 -0
  396. package/dist/shared/events/index.js.map +1 -0
  397. package/dist/shared/events/serialize.d.ts +87 -0
  398. package/dist/shared/events/serialize.d.ts.map +1 -0
  399. package/dist/shared/events/serialize.js +510 -0
  400. package/dist/shared/events/serialize.js.map +1 -0
  401. package/dist/shared/events/types.d.ts +139 -0
  402. package/dist/shared/events/types.d.ts.map +1 -0
  403. package/dist/shared/events/types.js +2 -0
  404. package/dist/shared/events/types.js.map +1 -0
  405. package/dist/shared/harvest/event-buffer.d.ts +59 -0
  406. package/dist/shared/harvest/event-buffer.d.ts.map +1 -0
  407. package/dist/shared/harvest/event-buffer.js +100 -0
  408. package/dist/shared/harvest/event-buffer.js.map +1 -0
  409. package/dist/shared/harvest/harvest-scheduler.d.ts +200 -0
  410. package/dist/shared/harvest/harvest-scheduler.d.ts.map +1 -0
  411. package/dist/shared/harvest/harvest-scheduler.js +647 -0
  412. package/dist/shared/harvest/harvest-scheduler.js.map +1 -0
  413. package/dist/shared/harvest/index.d.ts +7 -0
  414. package/dist/shared/harvest/index.d.ts.map +1 -0
  415. package/dist/shared/harvest/index.js +4 -0
  416. package/dist/shared/harvest/index.js.map +1 -0
  417. package/dist/shared/harvest/metric-aggregator.d.ts +115 -0
  418. package/dist/shared/harvest/metric-aggregator.d.ts.map +1 -0
  419. package/dist/shared/harvest/metric-aggregator.js +247 -0
  420. package/dist/shared/harvest/metric-aggregator.js.map +1 -0
  421. package/dist/shared/index.d.ts +22 -0
  422. package/dist/shared/index.d.ts.map +1 -0
  423. package/dist/shared/index.js +13 -0
  424. package/dist/shared/index.js.map +1 -0
  425. package/dist/shared/logger.d.ts +57 -0
  426. package/dist/shared/logger.d.ts.map +1 -0
  427. package/dist/shared/logger.js +166 -0
  428. package/dist/shared/logger.js.map +1 -0
  429. package/dist/shared/pricing-data.d.ts +4 -0
  430. package/dist/shared/pricing-data.d.ts.map +1 -0
  431. package/dist/shared/pricing-data.js +473 -0
  432. package/dist/shared/pricing-data.js.map +1 -0
  433. package/dist/shared/pricing.d.ts +148 -0
  434. package/dist/shared/pricing.d.ts.map +1 -0
  435. package/dist/shared/pricing.js +528 -0
  436. package/dist/shared/pricing.js.map +1 -0
  437. package/dist/shared/redact.d.ts +33 -0
  438. package/dist/shared/redact.d.ts.map +1 -0
  439. package/dist/shared/redact.js +110 -0
  440. package/dist/shared/redact.js.map +1 -0
  441. package/dist/shared/timing.d.ts +96 -0
  442. package/dist/shared/timing.d.ts.map +1 -0
  443. package/dist/shared/timing.js +173 -0
  444. package/dist/shared/timing.js.map +1 -0
  445. package/dist/shared/tokens.d.ts +145 -0
  446. package/dist/shared/tokens.d.ts.map +1 -0
  447. package/dist/shared/tokens.js +492 -0
  448. package/dist/shared/tokens.js.map +1 -0
  449. package/dist/shared/transport/events-api.d.ts +14 -0
  450. package/dist/shared/transport/events-api.d.ts.map +1 -0
  451. package/dist/shared/transport/events-api.js +29 -0
  452. package/dist/shared/transport/events-api.js.map +1 -0
  453. package/dist/shared/transport/http-client.d.ts +49 -0
  454. package/dist/shared/transport/http-client.d.ts.map +1 -0
  455. package/dist/shared/transport/http-client.js +381 -0
  456. package/dist/shared/transport/http-client.js.map +1 -0
  457. package/dist/shared/transport/index.d.ts +10 -0
  458. package/dist/shared/transport/index.d.ts.map +1 -0
  459. package/dist/shared/transport/index.js +6 -0
  460. package/dist/shared/transport/index.js.map +1 -0
  461. package/dist/shared/transport/logs-api.d.ts +29 -0
  462. package/dist/shared/transport/logs-api.d.ts.map +1 -0
  463. package/dist/shared/transport/logs-api.js +40 -0
  464. package/dist/shared/transport/logs-api.js.map +1 -0
  465. package/dist/shared/transport/metric-api.d.ts +9 -0
  466. package/dist/shared/transport/metric-api.d.ts.map +1 -0
  467. package/dist/shared/transport/metric-api.js +39 -0
  468. package/dist/shared/transport/metric-api.js.map +1 -0
  469. package/dist/shared/transport/otlp-event-bridge.d.ts +22 -0
  470. package/dist/shared/transport/otlp-event-bridge.d.ts.map +1 -0
  471. package/dist/shared/transport/otlp-event-bridge.js +50 -0
  472. package/dist/shared/transport/otlp-event-bridge.js.map +1 -0
  473. package/dist/shared/transport/otlp-shared.d.ts +14 -0
  474. package/dist/shared/transport/otlp-shared.d.ts.map +1 -0
  475. package/dist/shared/transport/otlp-shared.js +49 -0
  476. package/dist/shared/transport/otlp-shared.js.map +1 -0
  477. package/dist/shared/transport/otlp-transport.d.ts +58 -0
  478. package/dist/shared/transport/otlp-transport.d.ts.map +1 -0
  479. package/dist/shared/transport/otlp-transport.js +236 -0
  480. package/dist/shared/transport/otlp-transport.js.map +1 -0
  481. package/dist/shared/transport/types.d.ts +129 -0
  482. package/dist/shared/transport/types.d.ts.map +1 -0
  483. package/dist/shared/transport/types.js +2 -0
  484. package/dist/shared/transport/types.js.map +1 -0
  485. package/dist/shared/version.d.ts +2 -0
  486. package/dist/shared/version.d.ts.map +1 -0
  487. package/dist/shared/version.js +2 -0
  488. package/dist/shared/version.js.map +1 -0
  489. package/dist/storage/index.d.ts +7 -0
  490. package/dist/storage/index.d.ts.map +1 -0
  491. package/dist/storage/index.js +4 -0
  492. package/dist/storage/index.js.map +1 -0
  493. package/dist/storage/local-store.d.ts +153 -0
  494. package/dist/storage/local-store.d.ts.map +1 -0
  495. package/dist/storage/local-store.js +719 -0
  496. package/dist/storage/local-store.js.map +1 -0
  497. package/dist/storage/retention.d.ts +2 -0
  498. package/dist/storage/retention.d.ts.map +1 -0
  499. package/dist/storage/retention.js +53 -0
  500. package/dist/storage/retention.js.map +1 -0
  501. package/dist/storage/session-store.d.ts +97 -0
  502. package/dist/storage/session-store.d.ts.map +1 -0
  503. package/dist/storage/session-store.js +391 -0
  504. package/dist/storage/session-store.js.map +1 -0
  505. package/dist/storage/types.d.ts +64 -0
  506. package/dist/storage/types.d.ts.map +1 -0
  507. package/dist/storage/types.js +2 -0
  508. package/dist/storage/types.js.map +1 -0
  509. package/dist/storage/weekly-summary.d.ts +61 -0
  510. package/dist/storage/weekly-summary.d.ts.map +1 -0
  511. package/dist/storage/weekly-summary.js +243 -0
  512. package/dist/storage/weekly-summary.js.map +1 -0
  513. package/dist/tools/analytics-tools.d.ts +101 -0
  514. package/dist/tools/analytics-tools.d.ts.map +1 -0
  515. package/dist/tools/analytics-tools.js +71 -0
  516. package/dist/tools/analytics-tools.js.map +1 -0
  517. package/dist/tools/cost-tools.d.ts +121 -0
  518. package/dist/tools/cost-tools.d.ts.map +1 -0
  519. package/dist/tools/cost-tools.js +174 -0
  520. package/dist/tools/cost-tools.js.map +1 -0
  521. package/dist/tools/cross-session-tools.d.ts +376 -0
  522. package/dist/tools/cross-session-tools.d.ts.map +1 -0
  523. package/dist/tools/cross-session-tools.js +820 -0
  524. package/dist/tools/cross-session-tools.js.map +1 -0
  525. package/dist/tools/extended-analytics-tools.d.ts +164 -0
  526. package/dist/tools/extended-analytics-tools.d.ts.map +1 -0
  527. package/dist/tools/extended-analytics-tools.js +121 -0
  528. package/dist/tools/extended-analytics-tools.js.map +1 -0
  529. package/dist/tools/index.d.ts +7 -0
  530. package/dist/tools/index.d.ts.map +1 -0
  531. package/dist/tools/index.js +4 -0
  532. package/dist/tools/index.js.map +1 -0
  533. package/dist/tools/session-stats.d.ts +162 -0
  534. package/dist/tools/session-stats.d.ts.map +1 -0
  535. package/dist/tools/session-stats.js +1054 -0
  536. package/dist/tools/session-stats.js.map +1 -0
  537. package/dist/tools/workflow-tools.d.ts +126 -0
  538. package/dist/tools/workflow-tools.d.ts.map +1 -0
  539. package/dist/tools/workflow-tools.js +274 -0
  540. package/dist/tools/workflow-tools.js.map +1 -0
  541. package/dist/tracing/mcp-tracer.d.ts +4 -0
  542. package/dist/tracing/mcp-tracer.d.ts.map +1 -0
  543. package/dist/tracing/mcp-tracer.js +14 -0
  544. package/dist/tracing/mcp-tracer.js.map +1 -0
  545. package/dist/tracing/session-span.d.ts +14 -0
  546. package/dist/tracing/session-span.d.ts.map +1 -0
  547. package/dist/tracing/session-span.js +53 -0
  548. package/dist/tracing/session-span.js.map +1 -0
  549. package/dist/tracing/task-span-tracker.d.ts +11 -0
  550. package/dist/tracing/task-span-tracker.d.ts.map +1 -0
  551. package/dist/tracing/task-span-tracker.js +59 -0
  552. package/dist/tracing/task-span-tracker.js.map +1 -0
  553. package/dist/tracing/tool-call-span.d.ts +4 -0
  554. package/dist/tracing/tool-call-span.d.ts.map +1 -0
  555. package/dist/tracing/tool-call-span.js +60 -0
  556. package/dist/tracing/tool-call-span.js.map +1 -0
  557. package/dist/transport/index.d.ts +3 -0
  558. package/dist/transport/index.d.ts.map +1 -0
  559. package/dist/transport/index.js +2 -0
  560. package/dist/transport/index.js.map +1 -0
  561. package/dist/transport/log-ingest.d.ts +42 -0
  562. package/dist/transport/log-ingest.d.ts.map +1 -0
  563. package/dist/transport/log-ingest.js +151 -0
  564. package/dist/transport/log-ingest.js.map +1 -0
  565. package/dist/transport/nr-ingest.d.ts +171 -0
  566. package/dist/transport/nr-ingest.d.ts.map +1 -0
  567. package/dist/transport/nr-ingest.js +659 -0
  568. package/dist/transport/nr-ingest.js.map +1 -0
  569. package/dist/types.d.ts +45 -0
  570. package/dist/types.d.ts.map +1 -0
  571. package/dist/types.js +2 -0
  572. package/dist/types.js.map +1 -0
  573. package/dist/web/assets/index-BrL281N-.css +2 -0
  574. package/dist/web/assets/index-CcaYZzXm.js +42 -0
  575. package/dist/web/favicon.svg +15 -0
  576. package/dist/web/index.html +15 -0
  577. package/examples/local-alert-rules.json +106 -0
  578. package/package.json +125 -1
package/dist/config.js ADDED
@@ -0,0 +1,860 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { execSync } from 'node:child_process';
3
+ import { resolve } from 'node:path';
4
+ import { homedir } from 'node:os';
5
+ import { z } from 'zod';
6
+ import { createLogger } from './shared/index.js';
7
+ import { DEFAULT_PERSONAL_THRESHOLDS } from './alerts/types.js';
8
+ const logger = createLogger('mcp-config');
9
+ export const DEFAULT_STORAGE_PATH = resolve(homedir(), '.newrelic-preflight');
10
+ const DEFAULT_REDACTION_PATTERNS = [
11
+ /(?<![a-zA-Z])(?:API_KEY|SECRET|TOKEN|PASSWORD|PASSPHRASE|PRIVATE_KEY)(?![a-zA-Z])[\s]*[=:]\s*\S+/gi,
12
+ /(?:sk-|ghp_|gho_|ghs_|github_pat_|xoxb-|xoxp-|Bearer\s+)[A-Za-z0-9_-]{20,200}/g,
13
+ /-----BEGIN[^-\n]{0,100}-----[A-Za-z0-9+/=\r\n. ]{0,65536}-----END[^-\n]{0,100}-----/g,
14
+ /\bAKIA[0-9A-Z]{16}\b/g,
15
+ /\bAIzaSy[0-9A-Za-z_-]{33}\b/g,
16
+ /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g,
17
+ /\bnpm_[A-Za-z0-9]{36}\b/g,
18
+ /\bxox[a-z]-[0-9A-Za-z-]+/g,
19
+ /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g,
20
+ /\bpypi-[A-Za-z0-9_-]{20,}\b/g,
21
+ /\bhf_[A-Za-z0-9]{30,}\b/g,
22
+ /(?:mongodb(?:\+srv)?|postgres(?:ql)?|mysql|redis):\/\/[^:\/\s]+:[^\@\/\s]+@[^\s\/]+/gi,
23
+ /https?:\/\/[^\s:\/]+:[^\s@\/]+@[^\s\/]+/gi,
24
+ /\b(?:AC|SK)[a-f0-9]{32}\b/g,
25
+ /(?:[?&])(?:sig|se|sp|srt|ss|sv|st)=[A-Za-z0-9%_-]+/gi,
26
+ /\b(?:vercel_|heroku_|dd_|pk_)[A-Za-z0-9_-]{20,}\b/gi,
27
+ ];
28
+ export const ConfigFileSchema = z
29
+ .object({
30
+ licenseKey: z.string().optional(),
31
+ accountId: z.string().optional(),
32
+ appName: z.string().optional(),
33
+ developer: z.string().optional(),
34
+ teamId: z.string().nullable().optional(),
35
+ projectId: z.string().nullable().optional(),
36
+ orgId: z.string().nullable().optional(),
37
+ model: z.string().optional(),
38
+ enabled: z.boolean().optional(),
39
+ highSecurity: z.boolean().optional(),
40
+ recordContent: z.boolean().optional(),
41
+ storagePath: z.string().optional(),
42
+ hookBufferPath: z.string().optional(),
43
+ harvestEventsMs: z.number().optional(),
44
+ harvestMetricsMs: z.number().optional(),
45
+ sessionBudgetUsd: z.number().nullable().optional(),
46
+ dailyBudgetUsd: z.number().nullable().optional(),
47
+ weeklyBudgetUsd: z.number().nullable().optional(),
48
+ port: z.number().optional(),
49
+ logLevel: z.enum(['debug', 'info', 'warn', 'error']).optional(),
50
+ collectorHost: z.string().nullable().optional(),
51
+ proxyUpstreams: z.array(z.unknown()).optional(),
52
+ nrApiKey: z.string().nullable().optional(),
53
+ digestWebhookUrl: z.string().nullable().optional(),
54
+ digestSchedule: z.string().optional(),
55
+ retainSessionsDays: z.number().nullable().optional(),
56
+ otlpEndpoint: z.string().nullable().optional(),
57
+ otlpHeaders: z.record(z.string(), z.string()).optional(),
58
+ transport: z.enum(['nr-events-api', 'otlp', 'both']).optional(),
59
+ mode: z.enum(['cloud', 'local', 'both']).optional(),
60
+ otlpReceiverEnabled: z.boolean().optional(),
61
+ otlpReceiverPort: z.number().optional(),
62
+ otlpReceiverBindAddress: z.string().optional(),
63
+ otlpForwardEndpoint: z.string().nullable().optional(),
64
+ otlpForwardHeaders: z.record(z.string(), z.string()).optional(),
65
+ alerts: z
66
+ .object({
67
+ personal: z
68
+ .object({
69
+ dailyCostUsd: z.number().optional(),
70
+ sessionCostUsd: z.number().optional(),
71
+ efficiencyScoreMin: z.number().optional(),
72
+ stuckLoopCountMax: z.number().optional(),
73
+ antiPatternCountMax: z.number().optional(),
74
+ })
75
+ .passthrough()
76
+ .optional(),
77
+ enabled: z.boolean().optional(),
78
+ evaluationIntervalSeconds: z.number().int().min(5).max(300).optional(),
79
+ osNotifications: z.boolean().optional(),
80
+ logRetentionMb: z.number().min(1).max(1024).optional(),
81
+ rulesPath: z.string().nullable().optional(),
82
+ })
83
+ .passthrough()
84
+ .optional(),
85
+ dashboard: z
86
+ .object({
87
+ port: z.number().int().min(1).max(65535).optional(),
88
+ host: z.string().optional(),
89
+ openOnStart: z.boolean().optional(),
90
+ })
91
+ .passthrough()
92
+ .optional(),
93
+ })
94
+ // Pre-launch we want graceful tolerance of unknown keys so that an older
95
+ // ~/.newrelic-preflight/config.json doesn't brick the server on upgrade. Unknown
96
+ // keys at every level (top-level, alerts, dashboard) are reported via
97
+ // logger.warn at load time (see loadMcpConfig) so users still get a hint
98
+ // without a fatal crash. Nested objects also use .passthrough() so the
99
+ // warn block can see typos like `dashboard.openOnStarrt`.
100
+ .passthrough();
101
+ // N-07: strip control chars and truncate before the value reaches any NR event field or log
102
+ export function sanitizeDeveloper(raw) {
103
+ return (raw
104
+ .replace(/[\x00-\x1f\x7f]/g, '')
105
+ .trim()
106
+ .slice(0, 128) || 'unknown');
107
+ }
108
+ /**
109
+ * Produces a lowercase, NRQL-safe identifier from a raw developer name.
110
+ * "John Doe" → "john_doe", "my.user@host" → "my_user_host"
111
+ */
112
+ export function normalizeDeveloperName(raw) {
113
+ return (raw
114
+ .replace(/[\x00-\x1f\x7f]/g, '') // strip control chars
115
+ .trim()
116
+ .toLowerCase()
117
+ .replace(/[^a-z0-9-]+/g, '_') // collapse non-alphanumeric runs to _
118
+ .replace(/^_+|_+$/g, '') // strip leading/trailing underscores
119
+ .slice(0, 64) || 'unknown');
120
+ }
121
+ function sanitizeOrgField(value) {
122
+ if (!value)
123
+ return null;
124
+ const sanitized = value
125
+ .replace(/[\x00-\x1f\x7f]/g, '')
126
+ .trim()
127
+ .slice(0, 128);
128
+ return sanitized || null;
129
+ }
130
+ function inferDeveloper() {
131
+ if (process.env.USER)
132
+ return sanitizeDeveloper(process.env.USER);
133
+ if (process.env.USERNAME)
134
+ return sanitizeDeveloper(process.env.USERNAME);
135
+ try {
136
+ return sanitizeDeveloper(execSync('git config user.name', {
137
+ encoding: 'utf-8',
138
+ timeout: 2000,
139
+ env: { ...process.env },
140
+ }).trim());
141
+ }
142
+ catch {
143
+ return 'unknown';
144
+ }
145
+ }
146
+ function inferProjectId() {
147
+ try {
148
+ const remote = execSync('git remote get-url origin', {
149
+ encoding: 'utf-8',
150
+ timeout: 2000,
151
+ env: { ...process.env },
152
+ }).trim();
153
+ // Extract "org/repo" from HTTPS or SSH remotes:
154
+ // https://github.com/org/repo.git → org/repo
155
+ // git@github.com:org/repo.git → org/repo
156
+ const match = remote.match(/[/:]([\w.-]+\/[\w.-]+?)(?:\.git)?$/);
157
+ return match ? match[1] : null;
158
+ }
159
+ catch {
160
+ return null;
161
+ }
162
+ }
163
+ function envBool(key, defaultValue) {
164
+ const val = process.env[key]?.trim().toLowerCase();
165
+ if (val === 'true' || val === '1' || val === 'yes' || val === 'y' || val === 'on')
166
+ return true;
167
+ if (val === 'false' || val === '0' || val === 'no' || val === 'n' || val === 'off')
168
+ return false;
169
+ return defaultValue;
170
+ }
171
+ function envInt(key, defaultValue, bounds) {
172
+ const val = process.env[key];
173
+ if (val === undefined)
174
+ return defaultValue;
175
+ const parsed = parseInt(val, 10);
176
+ if (Number.isNaN(parsed))
177
+ return defaultValue;
178
+ if (bounds?.min !== undefined && parsed < bounds.min)
179
+ return bounds.min;
180
+ if (bounds?.max !== undefined && parsed > bounds.max)
181
+ return bounds.max;
182
+ return parsed;
183
+ }
184
+ function envLogLevel(key, defaultValue) {
185
+ const val = process.env[key]?.toLowerCase();
186
+ if (val === 'debug' || val === 'info' || val === 'warn' || val === 'error')
187
+ return val;
188
+ return defaultValue;
189
+ }
190
+ function loadConfigFile(filePath) {
191
+ let raw;
192
+ try {
193
+ raw = readFileSync(filePath, 'utf-8');
194
+ }
195
+ catch {
196
+ return {};
197
+ }
198
+ let parsed;
199
+ try {
200
+ parsed = JSON.parse(raw);
201
+ }
202
+ catch (err) {
203
+ const errorMsg = err instanceof Error ? err.message : String(err);
204
+ logger.error('Invalid JSON in config file', {
205
+ filePath,
206
+ error: errorMsg,
207
+ });
208
+ throw new Error(`Config file parsing failed at ${filePath}: ${errorMsg}`);
209
+ }
210
+ const validation = ConfigFileSchema.safeParse(parsed);
211
+ if (!validation.success) {
212
+ const issues = validation.error.issues
213
+ .map((issue) => {
214
+ const path = issue.path.join('.');
215
+ return `${path || 'root'}: ${issue.message}`;
216
+ })
217
+ .join('; ');
218
+ logger.error('Config file validation failed', {
219
+ filePath,
220
+ issues,
221
+ });
222
+ throw new Error(`Config file validation failed at ${filePath}: ${issues}`);
223
+ }
224
+ return validation.data;
225
+ }
226
+ function resolveCollectorHost(licenseKey, explicit) {
227
+ if (explicit)
228
+ return explicit;
229
+ if (licenseKey?.toLowerCase().startsWith('eu01')) {
230
+ return 'eu';
231
+ }
232
+ return null;
233
+ }
234
+ function isValidUpstream(u) {
235
+ if (typeof u !== 'object' || u === null)
236
+ return false;
237
+ const obj = u;
238
+ if (typeof obj.name !== 'string')
239
+ return false;
240
+ if (obj.transportType !== 'http' && obj.transportType !== 'stdio')
241
+ return false;
242
+ if (obj.transportType === 'http' && typeof obj.url !== 'string')
243
+ return false;
244
+ if (obj.transportType === 'stdio' && typeof obj.command !== 'string')
245
+ return false;
246
+ return true;
247
+ }
248
+ function parseOtlpHeaders(headerString) {
249
+ if (!headerString)
250
+ return {};
251
+ const result = {};
252
+ const pairs = headerString.split(',');
253
+ for (const pair of pairs) {
254
+ const eqIdx = pair.indexOf('=');
255
+ if (eqIdx < 1)
256
+ continue;
257
+ const key = pair.slice(0, eqIdx).trim();
258
+ const value = pair.slice(eqIdx + 1).trim();
259
+ if (key && value) {
260
+ result[key] = value;
261
+ }
262
+ }
263
+ return result;
264
+ }
265
+ /**
266
+ * Validate the alerts.rulesPath value. The path is read from the user's
267
+ * config file or NR_AI_ALERTS_RULES_PATH env var; both are user-controlled
268
+ * but worth a defensive guard to keep an accidental misconfiguration from
269
+ * pointing fs.watch at /etc/hosts or similar. Rules:
270
+ *
271
+ * 1. Must end in `.json` (case-insensitive).
272
+ * 2. Must resolve under `storagePath` (the configured storage root).
273
+ *
274
+ * The default rules path is `${storagePath}/alerts/rules.json`, which
275
+ * always passes both checks. A bad path falls back to the default with a
276
+ * logged warning rather than throwing — one bad config field shouldn't
277
+ * brick the whole server.
278
+ */
279
+ function validateRulesPath(rawPath, storagePath) {
280
+ const fallback = resolve(storagePath, 'alerts', 'rules.json');
281
+ if (!rawPath.toLowerCase().endsWith('.json')) {
282
+ logger.warn('alerts.rulesPath does not end in .json — falling back to default', {
283
+ rawPath,
284
+ fallback,
285
+ });
286
+ return fallback;
287
+ }
288
+ const resolved = resolve(rawPath);
289
+ const storageResolved = resolve(storagePath);
290
+ // Match prefix only on full path segments to avoid `/foo/bar` matching
291
+ // `/foo/barbaz`.
292
+ const prefix = storageResolved.endsWith('/') ? storageResolved : storageResolved + '/';
293
+ if (resolved !== storageResolved && !resolved.startsWith(prefix)) {
294
+ logger.warn('alerts.rulesPath resolves outside storagePath — falling back to default', {
295
+ rawPath,
296
+ resolved,
297
+ storagePath: storageResolved,
298
+ fallback,
299
+ });
300
+ return fallback;
301
+ }
302
+ return resolved;
303
+ }
304
+ function parseProxyUpstreams(envValue, fileValue) {
305
+ // Env var takes precedence (JSON string)
306
+ if (envValue) {
307
+ try {
308
+ const parsed = JSON.parse(envValue);
309
+ if (!Array.isArray(parsed)) {
310
+ logger.warn('NEW_RELIC_AI_MCP_PROXY_UPSTREAMS must be a JSON array — ignoring env var value');
311
+ }
312
+ else {
313
+ const valid = parsed.filter((u) => {
314
+ if (isValidUpstream(u))
315
+ return true;
316
+ logger.warn('Skipping invalid proxy upstream entry (missing name, transportType, or url/command)', { entry: u });
317
+ return false;
318
+ });
319
+ return valid;
320
+ }
321
+ }
322
+ catch {
323
+ logger.warn('Invalid JSON in NEW_RELIC_AI_MCP_PROXY_UPSTREAMS env var');
324
+ }
325
+ }
326
+ // Config file
327
+ if (Array.isArray(fileValue)) {
328
+ const valid = fileValue.filter((u) => {
329
+ if (isValidUpstream(u))
330
+ return true;
331
+ logger.warn('Skipping invalid proxy upstream entry (missing name, transportType, or url/command)', { entry: u });
332
+ return false;
333
+ });
334
+ return valid;
335
+ }
336
+ return [];
337
+ }
338
+ export function loadMcpConfig(cliOptions) {
339
+ const configFilePath = cliOptions?.config ?? resolve(DEFAULT_STORAGE_PATH, 'config.json');
340
+ const file = loadConfigFile(configFilePath);
341
+ // Warn (don't crash) when the user's config file contains keys we don't
342
+ // recognize. Schema is `.passthrough()` at every level so unknown keys come
343
+ // through intact — surface them in logs so a typo or stale field is visible
344
+ // to the user without bricking the server on upgrade.
345
+ const knownTopKeys = new Set(Object.keys(ConfigFileSchema.shape));
346
+ const unknownTopKeys = Object.keys(file).filter((k) => !knownTopKeys.has(k));
347
+ if (unknownTopKeys.length > 0) {
348
+ logger.warn('Unknown keys in config file (ignored)', {
349
+ unknownKeys: unknownTopKeys,
350
+ path: configFilePath,
351
+ });
352
+ }
353
+ // Nested objects: alerts, dashboard. Hardcoded known-key sets keep this
354
+ // simple and avoid Zod schema introspection (the typed shape requires
355
+ // unwrapping .optional() and .passthrough()). If you add a new field to
356
+ // these schemas, add it here too.
357
+ const knownAlertsKeys = new Set([
358
+ 'personal',
359
+ 'enabled',
360
+ 'evaluationIntervalSeconds',
361
+ 'osNotifications',
362
+ 'logRetentionMb',
363
+ 'rulesPath',
364
+ ]);
365
+ const knownAlertsPersonalKeys = new Set([
366
+ 'dailyCostUsd',
367
+ 'sessionCostUsd',
368
+ 'efficiencyScoreMin',
369
+ 'stuckLoopCountMax',
370
+ 'antiPatternCountMax',
371
+ ]);
372
+ const knownDashboardKeys = new Set(['port', 'host', 'openOnStart']);
373
+ if (file.alerts && typeof file.alerts === 'object') {
374
+ const alerts = file.alerts;
375
+ const unknownAlertsKeys = Object.keys(alerts).filter((k) => !knownAlertsKeys.has(k));
376
+ if (unknownAlertsKeys.length > 0) {
377
+ logger.warn('Unknown keys in alerts config (ignored)', {
378
+ unknownKeys: unknownAlertsKeys,
379
+ path: configFilePath,
380
+ });
381
+ }
382
+ if (alerts.personal && typeof alerts.personal === 'object') {
383
+ const personal = alerts.personal;
384
+ const unknownAlertsPersonalKeys = Object.keys(personal).filter((k) => !knownAlertsPersonalKeys.has(k));
385
+ if (unknownAlertsPersonalKeys.length > 0) {
386
+ logger.warn('Unknown keys in alerts.personal config (ignored)', {
387
+ unknownKeys: unknownAlertsPersonalKeys,
388
+ path: configFilePath,
389
+ });
390
+ }
391
+ }
392
+ }
393
+ if (file.dashboard && typeof file.dashboard === 'object') {
394
+ const dashboard = file.dashboard;
395
+ const unknownDashboardKeys = Object.keys(dashboard).filter((k) => !knownDashboardKeys.has(k));
396
+ if (unknownDashboardKeys.length > 0) {
397
+ logger.warn('Unknown keys in dashboard config (ignored)', {
398
+ unknownKeys: unknownDashboardKeys,
399
+ path: configFilePath,
400
+ });
401
+ }
402
+ }
403
+ // --- Resolve mode early so we can gate licenseKey/accountId requirements ---
404
+ // File mode is already validated by the zod schema in loadConfigFile.
405
+ const VALID_MODES = ['cloud', 'local', 'both'];
406
+ const isValidMode = (v) => typeof v === 'string' && VALID_MODES.includes(v);
407
+ const envMode = process.env.NR_AI_MODE;
408
+ if (envMode !== undefined && envMode !== '' && !isValidMode(envMode)) {
409
+ throw new Error(`Invalid NR_AI_MODE='${envMode}'. Must be one of: ${VALID_MODES.join(', ')}.`);
410
+ }
411
+ const mode = (isValidMode(envMode) ? envMode : undefined) ?? file.mode ?? 'cloud';
412
+ // --- licenseKey: CLI has no flag for this, so env > file ---
413
+ const licenseKeyRaw = process.env.NEW_RELIC_LICENSE_KEY ??
414
+ (typeof file.licenseKey === 'string' ? file.licenseKey : undefined);
415
+ if (mode !== 'local' && !licenseKeyRaw) {
416
+ throw new Error(`Missing required configuration: licenseKey (mode='${mode}'). ` +
417
+ 'Set the NEW_RELIC_LICENSE_KEY environment variable or add "licenseKey" to ' +
418
+ configFilePath +
419
+ ", or switch to mode='local' to skip cloud transport.");
420
+ }
421
+ // Reject the literal string "null" — it's truthy but not a valid key
422
+ // and would cause silent auth failures at transport time.
423
+ if (licenseKeyRaw === 'null') {
424
+ throw new Error('Invalid licenseKey: the string "null" is not a valid New Relic license key. ' +
425
+ 'Remove it from the config file or set the correct key in NEW_RELIC_LICENSE_KEY.');
426
+ }
427
+ // In local mode, undefined if licenseKey is missing (NR transport won't be used)
428
+ const licenseKey = licenseKeyRaw;
429
+ // --- accountId: env > file ---
430
+ const accountIdRaw = process.env.NEW_RELIC_ACCOUNT_ID ??
431
+ (typeof file.accountId === 'string' ? file.accountId : undefined);
432
+ if (mode !== 'local' && !accountIdRaw) {
433
+ throw new Error(`Missing required configuration: accountId (mode='${mode}'). ` +
434
+ 'Set the NEW_RELIC_ACCOUNT_ID environment variable or add "accountId" to ' +
435
+ configFilePath +
436
+ ", or switch to mode='local' to skip cloud transport.");
437
+ }
438
+ if (accountIdRaw && !/^\d{1,12}$/.test(accountIdRaw)) {
439
+ throw new Error('Invalid configuration: accountId must be 1–12 decimal digits. ' +
440
+ `Received: "${accountIdRaw}"`);
441
+ }
442
+ // In local mode, undefined if accountId is missing (NR transport won't be used)
443
+ const accountId = accountIdRaw;
444
+ // --- Build config with priority: CLI > env > file > defaults ---
445
+ const storagePath = process.env.NEW_RELIC_AI_MCP_STORAGE_PATH ??
446
+ (typeof file.storagePath === 'string' ? file.storagePath : DEFAULT_STORAGE_PATH);
447
+ // N-10: highSecurity must be resolved before recordContent so it can override it
448
+ const highSecurity = envBool('NEW_RELIC_AI_HIGH_SECURITY', typeof file.highSecurity === 'boolean' ? file.highSecurity : false);
449
+ const config = {
450
+ licenseKey,
451
+ accountId,
452
+ appName: process.env.NEW_RELIC_AI_MCP_APP_NAME ??
453
+ (typeof file.appName === 'string' ? file.appName : 'preflight'),
454
+ model: process.env.NEW_RELIC_AI_MODEL ??
455
+ (typeof file.model === 'string' ? file.model : 'claude-sonnet-4-6'),
456
+ developer: normalizeDeveloperName(process.env.NEW_RELIC_AI_MCP_DEVELOPER ??
457
+ (typeof file.developer === 'string' ? file.developer : inferDeveloper())),
458
+ teamId: sanitizeOrgField(process.env.NEW_RELIC_AI_TEAM_ID ?? (typeof file.teamId === 'string' ? file.teamId : null)),
459
+ projectId: sanitizeOrgField(process.env.NEW_RELIC_AI_PROJECT_ID ??
460
+ (typeof file.projectId === 'string' ? file.projectId : inferProjectId())),
461
+ orgId: sanitizeOrgField(process.env.NEW_RELIC_AI_ORG_ID ?? (typeof file.orgId === 'string' ? file.orgId : null)),
462
+ enabled: envBool('NEW_RELIC_AI_MCP_ENABLED', typeof file.enabled === 'boolean' ? file.enabled : true),
463
+ highSecurity,
464
+ // N-10: highSecurity forces recordContent off regardless of other settings
465
+ recordContent: highSecurity
466
+ ? false
467
+ : envBool('NEW_RELIC_AI_MCP_RECORD_CONTENT', typeof file.recordContent === 'boolean' ? file.recordContent : false),
468
+ redactionPatterns: DEFAULT_REDACTION_PATTERNS,
469
+ hookBufferPath: process.env.NEW_RELIC_AI_MCP_BUFFER_PATH ??
470
+ (typeof file.hookBufferPath === 'string'
471
+ ? file.hookBufferPath
472
+ : resolve(storagePath, 'buffer.jsonl')),
473
+ storagePath,
474
+ harvestIntervalMs: {
475
+ events: envInt('NEW_RELIC_AI_MCP_HARVEST_EVENTS_MS', typeof file.harvestEventsMs === 'number' ? file.harvestEventsMs : 5000, { min: 100, max: 3_600_000 }),
476
+ metrics: envInt('NEW_RELIC_AI_MCP_HARVEST_METRICS_MS', typeof file.harvestMetricsMs === 'number' ? file.harvestMetricsMs : 60000, { min: 100, max: 3_600_000 }),
477
+ },
478
+ sessionBudgetUsd: (() => {
479
+ const raw = process.env.NEW_RELIC_AI_SESSION_BUDGET_USD;
480
+ if (raw !== undefined && raw !== '') {
481
+ const v = parseFloat(raw);
482
+ if (Number.isFinite(v) && v > 0)
483
+ return v;
484
+ }
485
+ return typeof file.sessionBudgetUsd === 'number' ? file.sessionBudgetUsd : null;
486
+ })(),
487
+ dailyBudgetUsd: (() => {
488
+ const raw = process.env.NEW_RELIC_AI_DAILY_BUDGET_USD;
489
+ if (raw !== undefined && raw !== '') {
490
+ const v = parseFloat(raw);
491
+ if (Number.isFinite(v) && v > 0)
492
+ return v;
493
+ }
494
+ return typeof file.dailyBudgetUsd === 'number' ? file.dailyBudgetUsd : null;
495
+ })(),
496
+ weeklyBudgetUsd: (() => {
497
+ const raw = process.env.NEW_RELIC_AI_WEEKLY_BUDGET_USD;
498
+ if (raw !== undefined && raw !== '') {
499
+ const v = parseFloat(raw);
500
+ if (Number.isFinite(v) && v > 0)
501
+ return v;
502
+ }
503
+ return typeof file.weeklyBudgetUsd === 'number' ? file.weeklyBudgetUsd : null;
504
+ })(),
505
+ port: cliOptions?.port ??
506
+ envInt('NEW_RELIC_AI_MCP_PORT', typeof file.port === 'number' ? file.port : 9847, {
507
+ min: 1,
508
+ max: 65535,
509
+ }),
510
+ logLevel: cliOptions?.logLevel ??
511
+ envLogLevel('NEW_RELIC_AI_MCP_LOG_LEVEL', typeof file.logLevel === 'string' &&
512
+ ['debug', 'info', 'warn', 'error'].includes(file.logLevel)
513
+ ? file.logLevel
514
+ : 'info'),
515
+ collectorHost: resolveCollectorHost(licenseKey, process.env.NEW_RELIC_HOST ??
516
+ (typeof file.collectorHost === 'string' ? file.collectorHost : null)),
517
+ proxyUpstreams: parseProxyUpstreams(process.env.NEW_RELIC_AI_MCP_PROXY_UPSTREAMS, file.proxyUpstreams),
518
+ nrApiKey: process.env.NEW_RELIC_API_KEY ?? (typeof file.nrApiKey === 'string' ? file.nrApiKey : null),
519
+ digestWebhookUrl: process.env.NEW_RELIC_AI_DIGEST_WEBHOOK_URL ??
520
+ (typeof file.digestWebhookUrl === 'string' ? file.digestWebhookUrl : null),
521
+ digestSchedule: process.env.NEW_RELIC_AI_DIGEST_SCHEDULE ??
522
+ (typeof file.digestSchedule === 'string' ? file.digestSchedule : '0 9 * * 1'),
523
+ retainSessionsDays: (() => {
524
+ const raw = process.env.NEW_RELIC_AI_RETAIN_SESSIONS_DAYS;
525
+ if (raw !== undefined && raw !== '') {
526
+ const v = parseInt(raw, 10);
527
+ if (Number.isFinite(v) && v > 0)
528
+ return v;
529
+ }
530
+ return typeof file.retainSessionsDays === 'number' ? file.retainSessionsDays : null;
531
+ })(),
532
+ otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ??
533
+ (typeof file.otlpEndpoint === 'string' ? file.otlpEndpoint : null),
534
+ otlpHeaders: (() => {
535
+ const envValue = process.env.OTEL_EXPORTER_OTLP_HEADERS;
536
+ if (envValue)
537
+ return parseOtlpHeaders(envValue);
538
+ return typeof file.otlpHeaders === 'object' && file.otlpHeaders !== null
539
+ ? file.otlpHeaders
540
+ : {};
541
+ })(),
542
+ transport: process.env.NEW_RELIC_AI_TRANSPORT === 'otlp'
543
+ ? 'otlp'
544
+ : process.env.NEW_RELIC_AI_TRANSPORT === 'both'
545
+ ? 'both'
546
+ : typeof file.transport === 'string' &&
547
+ (file.transport === 'otlp' || file.transport === 'both')
548
+ ? file.transport
549
+ : 'nr-events-api',
550
+ mode,
551
+ otlpReceiverEnabled: envBool('NR_AI_OTLP_RECEIVER_ENABLED', typeof file.otlpReceiverEnabled === 'boolean' ? file.otlpReceiverEnabled : false),
552
+ otlpReceiverPort: envInt('NR_AI_OTLP_RECEIVER_PORT', typeof file.otlpReceiverPort === 'number' ? file.otlpReceiverPort : 4318, { min: 1, max: 65535 }),
553
+ otlpReceiverBindAddress: (() => {
554
+ const envVal = process.env.NR_AI_OTLP_RECEIVER_BIND_ADDRESS;
555
+ if (envVal !== undefined && envVal !== '')
556
+ return envVal;
557
+ if (typeof file.otlpReceiverBindAddress === 'string' && file.otlpReceiverBindAddress !== '') {
558
+ return file.otlpReceiverBindAddress;
559
+ }
560
+ return '127.0.0.1';
561
+ })(),
562
+ otlpForwardEndpoint: (() => {
563
+ const envVal = process.env.NR_AI_OTLP_FORWARD_ENDPOINT;
564
+ // Default to the NR OTLP endpoint only when not in local mode. Local users
565
+ // may still set the value explicitly via env or config file (e.g. to point
566
+ // at a self-hosted collector); we just don't synthesize a NR default for them.
567
+ const defaultEndpoint = mode !== 'local' && licenseKey !== undefined ? 'https://otlp.nr-data.net' : null;
568
+ const endpoint = envVal !== undefined
569
+ ? envVal || null
570
+ : typeof file.otlpForwardEndpoint === 'string'
571
+ ? file.otlpForwardEndpoint || null
572
+ : defaultEndpoint;
573
+ if (endpoint === null)
574
+ return null;
575
+ try {
576
+ const url = new URL(endpoint);
577
+ if (url.protocol === 'https:')
578
+ return endpoint;
579
+ if (url.protocol === 'http:') {
580
+ logger.warn('OTLP forward endpoint uses http:// instead of https:// — TLS not enabled', {
581
+ endpoint,
582
+ });
583
+ return endpoint;
584
+ }
585
+ logger.warn('OTLP forward endpoint has unexpected protocol', {
586
+ endpoint,
587
+ protocol: url.protocol,
588
+ });
589
+ return endpoint;
590
+ }
591
+ catch (_err) {
592
+ logger.error('OTLP forward endpoint is not a valid URL', { endpoint });
593
+ return null;
594
+ }
595
+ })(),
596
+ otlpForwardHeaders: (() => {
597
+ const envValue = process.env.NR_AI_OTLP_FORWARD_HEADERS;
598
+ if (envValue !== undefined)
599
+ return parseOtlpHeaders(envValue);
600
+ if (typeof file.otlpForwardHeaders === 'object' && file.otlpForwardHeaders !== null) {
601
+ return file.otlpForwardHeaders;
602
+ }
603
+ // Don't synthesize a NR api-key header default in local mode — the licenseKey
604
+ // may be present in the env from another tool, and we must not leak it to a
605
+ // forward target the user didn't explicitly configure.
606
+ return mode !== 'local' && licenseKey !== undefined ? { 'api-key': licenseKey } : {};
607
+ })(),
608
+ personalAlertThresholds: (() => {
609
+ const fileThresholds = typeof file.alerts === 'object' && file.alerts !== null
610
+ ? file.alerts.personal
611
+ : undefined;
612
+ if (typeof fileThresholds !== 'object' || fileThresholds === null) {
613
+ return DEFAULT_PERSONAL_THRESHOLDS;
614
+ }
615
+ const t = fileThresholds;
616
+ return {
617
+ dailyCostUsd: typeof t.dailyCostUsd === 'number'
618
+ ? t.dailyCostUsd
619
+ : DEFAULT_PERSONAL_THRESHOLDS.dailyCostUsd,
620
+ sessionCostUsd: typeof t.sessionCostUsd === 'number'
621
+ ? t.sessionCostUsd
622
+ : DEFAULT_PERSONAL_THRESHOLDS.sessionCostUsd,
623
+ efficiencyScoreMin: typeof t.efficiencyScoreMin === 'number'
624
+ ? t.efficiencyScoreMin
625
+ : DEFAULT_PERSONAL_THRESHOLDS.efficiencyScoreMin,
626
+ stuckLoopCountMax: typeof t.stuckLoopCountMax === 'number'
627
+ ? t.stuckLoopCountMax
628
+ : DEFAULT_PERSONAL_THRESHOLDS.stuckLoopCountMax,
629
+ antiPatternCountMax: typeof t.antiPatternCountMax === 'number'
630
+ ? t.antiPatternCountMax
631
+ : DEFAULT_PERSONAL_THRESHOLDS.antiPatternCountMax,
632
+ };
633
+ })(),
634
+ dashboard: (() => {
635
+ const dashboardFile = (file.dashboard ?? {});
636
+ const dashboardPortRaw = process.env.NR_AI_DASHBOARD_PORT
637
+ ? parseInt(process.env.NR_AI_DASHBOARD_PORT, 10)
638
+ : dashboardFile.port;
639
+ // Allow port 0 — Node's server.listen(0) assigns an OS-ephemeral port,
640
+ // which is essential for parallel test runs and useful when the user
641
+ // wants to avoid hard-coding a port. Negative or out-of-range values
642
+ // still fall back to the default 7777.
643
+ const dashboardPort = Number.isFinite(dashboardPortRaw) && dashboardPortRaw >= 0 && dashboardPortRaw <= 65535
644
+ ? dashboardPortRaw
645
+ : 7777;
646
+ const requestedHost = process.env.NR_AI_DASHBOARD_HOST ?? dashboardFile.host ?? '127.0.0.1';
647
+ let dashboardHost = '127.0.0.1';
648
+ if (requestedHost !== '127.0.0.1' && requestedHost !== 'localhost') {
649
+ logger.warn(`dashboard.host '${requestedHost}' is non-loopback; v1 only supports loopback. Forcing 127.0.0.1.`);
650
+ }
651
+ else {
652
+ dashboardHost = requestedHost === 'localhost' ? '127.0.0.1' : requestedHost;
653
+ }
654
+ const dashboardOpenOnStart = envBool('NR_AI_DASHBOARD_OPEN', dashboardFile.openOnStart === true);
655
+ return {
656
+ port: dashboardPort,
657
+ host: dashboardHost,
658
+ openOnStart: dashboardOpenOnStart,
659
+ };
660
+ })(),
661
+ alerts: (() => {
662
+ // The alerts config is a separate top-level block from
663
+ // `personalAlertThresholds` (above) — they share the `alerts` key in
664
+ // the file schema for backwards compatibility, but expose different
665
+ // fields. `enabled` defaults to true outside of cloud-only mode.
666
+ const alertsFile = typeof file.alerts === 'object' && file.alerts !== null
667
+ ? file.alerts
668
+ : {};
669
+ const fileEnabled = typeof alertsFile.enabled === 'boolean' ? alertsFile.enabled : undefined;
670
+ const fileInterval = typeof alertsFile.evaluationIntervalSeconds === 'number'
671
+ ? alertsFile.evaluationIntervalSeconds
672
+ : undefined;
673
+ const fileOsNotifications = typeof alertsFile.osNotifications === 'boolean' ? alertsFile.osNotifications : undefined;
674
+ const fileLogRetention = typeof alertsFile.logRetentionMb === 'number' ? alertsFile.logRetentionMb : undefined;
675
+ const fileRulesPath = typeof alertsFile.rulesPath === 'string' ? alertsFile.rulesPath : undefined;
676
+ const enabledDefault = mode !== 'cloud';
677
+ const enabled = envBool('NR_AI_ALERTS_ENABLED', fileEnabled ?? enabledDefault);
678
+ const intervalSeconds = envInt('NR_AI_ALERTS_INTERVAL_SECONDS', fileInterval ?? 30, {
679
+ min: 5,
680
+ max: 300,
681
+ });
682
+ const osNotifications = envBool('NR_AI_ALERTS_OS_NOTIFICATIONS', fileOsNotifications ?? false);
683
+ const logRetentionMb = envInt('NR_AI_ALERTS_LOG_RETENTION_MB', fileLogRetention ?? 10, {
684
+ min: 1,
685
+ max: 1024,
686
+ });
687
+ const envRulesPath = process.env.NR_AI_ALERTS_RULES_PATH;
688
+ const rawRulesPath = envRulesPath !== undefined && envRulesPath !== ''
689
+ ? envRulesPath
690
+ : (fileRulesPath ?? resolve(storagePath, 'alerts', 'rules.json'));
691
+ // Defensive validation: rulesPath is read from user config, so reject
692
+ // values that don't end in .json or that resolve outside storagePath.
693
+ // Prevents accidental fs.watch handles on system files like /etc/hosts
694
+ // or path-traversal probes via env-var injection. The default path is
695
+ // always permitted.
696
+ const rulesPath = validateRulesPath(rawRulesPath, storagePath);
697
+ return {
698
+ enabled,
699
+ evaluationIntervalSeconds: intervalSeconds,
700
+ osNotifications,
701
+ logRetentionMb,
702
+ rulesPath,
703
+ };
704
+ })(),
705
+ };
706
+ logger.debug('Configuration loaded', {
707
+ appName: config.appName,
708
+ developer: config.developer,
709
+ teamId: config.teamId,
710
+ projectId: config.projectId,
711
+ orgId: config.orgId,
712
+ enabled: config.enabled,
713
+ highSecurity: config.highSecurity,
714
+ recordContent: config.recordContent,
715
+ storagePath: config.storagePath,
716
+ port: config.port,
717
+ collectorHost: config.collectorHost ?? 'us (default)',
718
+ sessionBudgetUsd: config.sessionBudgetUsd,
719
+ dailyBudgetUsd: config.dailyBudgetUsd,
720
+ weeklyBudgetUsd: config.weeklyBudgetUsd,
721
+ });
722
+ return Object.freeze(config);
723
+ }
724
+ /**
725
+ * Validate a config file and return structured results — no throws, no logging.
726
+ * The CLI `validate` command uses this to surface issues in human-readable form
727
+ * before the user attempts to start the MCP server.
728
+ */
729
+ export function validateConfigFile(filePath) {
730
+ const errors = [];
731
+ const warnings = [];
732
+ if (!existsSync(filePath)) {
733
+ return { filePath, fileExists: false, errors, warnings };
734
+ }
735
+ // Read and parse the file
736
+ let raw;
737
+ try {
738
+ raw = readFileSync(filePath, 'utf-8');
739
+ }
740
+ catch (err) {
741
+ errors.push(`Could not read file: ${err instanceof Error ? err.message : String(err)}`);
742
+ return { filePath, fileExists: true, errors, warnings };
743
+ }
744
+ let parsed;
745
+ try {
746
+ parsed = JSON.parse(raw);
747
+ }
748
+ catch (err) {
749
+ errors.push(`Invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
750
+ return { filePath, fileExists: true, errors, warnings };
751
+ }
752
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
753
+ errors.push('Config must be a JSON object, not an array or primitive value');
754
+ return { filePath, fileExists: true, errors, warnings };
755
+ }
756
+ // Zod type/value errors
757
+ const validation = ConfigFileSchema.safeParse(parsed);
758
+ if (!validation.success) {
759
+ for (const issue of validation.error.issues) {
760
+ const path = issue.path.length > 0 ? issue.path.join('.') : 'root';
761
+ errors.push(`${path}: ${issue.message}`);
762
+ }
763
+ }
764
+ // Unknown key detection with "did you mean" suggestions
765
+ const file = parsed;
766
+ const knownTopKeys = new Set(Object.keys(ConfigFileSchema.shape));
767
+ for (const key of Object.keys(file)) {
768
+ if (!knownTopKeys.has(key)) {
769
+ const suggestion = findSuggestion(key, knownTopKeys);
770
+ warnings.push(`Unknown key "${key}"${suggestion ? ` — did you mean "${suggestion}"?` : ' — not a recognized config field'}`);
771
+ }
772
+ }
773
+ const knownAlertsKeys = new Set([
774
+ 'personal',
775
+ 'enabled',
776
+ 'evaluationIntervalSeconds',
777
+ 'osNotifications',
778
+ 'logRetentionMb',
779
+ 'rulesPath',
780
+ ]);
781
+ const knownAlertsPersonalKeys = new Set([
782
+ 'dailyCostUsd',
783
+ 'sessionCostUsd',
784
+ 'efficiencyScoreMin',
785
+ 'stuckLoopCountMax',
786
+ 'antiPatternCountMax',
787
+ ]);
788
+ const knownDashboardKeys = new Set(['port', 'host', 'openOnStart']);
789
+ if (file.alerts && typeof file.alerts === 'object' && !Array.isArray(file.alerts)) {
790
+ const alerts = file.alerts;
791
+ for (const key of Object.keys(alerts)) {
792
+ if (!knownAlertsKeys.has(key)) {
793
+ const suggestion = findSuggestion(key, knownAlertsKeys);
794
+ warnings.push(`Unknown key "alerts.${key}"${suggestion ? ` — did you mean "alerts.${suggestion}"?` : ''}`);
795
+ }
796
+ }
797
+ if (alerts.personal && typeof alerts.personal === 'object' && !Array.isArray(alerts.personal)) {
798
+ const personal = alerts.personal;
799
+ for (const key of Object.keys(personal)) {
800
+ if (!knownAlertsPersonalKeys.has(key)) {
801
+ const suggestion = findSuggestion(key, knownAlertsPersonalKeys);
802
+ warnings.push(`Unknown key "alerts.personal.${key}"${suggestion ? ` — did you mean "alerts.personal.${suggestion}"?` : ''}`);
803
+ }
804
+ }
805
+ }
806
+ }
807
+ if (file.dashboard && typeof file.dashboard === 'object' && !Array.isArray(file.dashboard)) {
808
+ const dashboard = file.dashboard;
809
+ for (const key of Object.keys(dashboard)) {
810
+ if (!knownDashboardKeys.has(key)) {
811
+ const suggestion = findSuggestion(key, knownDashboardKeys);
812
+ warnings.push(`Unknown key "dashboard.${key}"${suggestion ? ` — did you mean "dashboard.${suggestion}"?` : ''}`);
813
+ }
814
+ }
815
+ }
816
+ return { filePath, fileExists: true, errors, warnings };
817
+ }
818
+ /** Return a suggestion from known keys if the unknown key is a case-insensitive
819
+ * match or a single-character edit away (insertion, deletion, or substitution). */
820
+ function findSuggestion(unknown, knownKeys) {
821
+ const lower = unknown.toLowerCase();
822
+ // Exact case-insensitive match — covers the most common typo (e.g. licensekey → licenseKey)
823
+ for (const k of knownKeys) {
824
+ if (k.toLowerCase() === lower)
825
+ return k;
826
+ }
827
+ // Single-character difference (insertion, deletion, or substitution)
828
+ for (const k of knownKeys) {
829
+ if (editDistance(unknown, k) <= 1)
830
+ return k;
831
+ }
832
+ return null;
833
+ }
834
+ function editDistance(a, b) {
835
+ const m = a.length;
836
+ const n = b.length;
837
+ const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)));
838
+ for (let i = 1; i <= m; i++) {
839
+ for (let j = 1; j <= n; j++) {
840
+ dp[i][j] =
841
+ a[i - 1] === b[j - 1]
842
+ ? dp[i - 1][j - 1]
843
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
844
+ }
845
+ }
846
+ return dp[m][n];
847
+ }
848
+ const MAX_REDACT_LEN = 1_048_576; // 1 MB
849
+ export function redactSensitive(value, patterns) {
850
+ if (typeof value !== 'string')
851
+ return String(value ?? '');
852
+ const pats = patterns ?? DEFAULT_REDACTION_PATTERNS;
853
+ let result = value.length > MAX_REDACT_LEN ? value.slice(0, MAX_REDACT_LEN) : value;
854
+ for (const pattern of pats) {
855
+ pattern.lastIndex = 0;
856
+ result = result.replace(pattern, '[REDACTED]');
857
+ }
858
+ return result;
859
+ }
860
+ //# sourceMappingURL=config.js.map