@newrelic/preflight 0.0.1-pre.1 → 1.0.1

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 +868 -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 +123 -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 +129 -1
package/dist/config.js ADDED
@@ -0,0 +1,868 @@
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
+ const normalized = 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
+ // Trim leading/trailing underscores with an index walk instead of
119
+ // anchored-quantifier regexes (/^_+/ and /_+$/) — the latter causes
120
+ // O(n²) backtracking on underscore-heavy strings (CodeQL js/polynomial-redos).
121
+ let start = 0;
122
+ let end = normalized.length;
123
+ while (start < end && normalized[start] === '_')
124
+ start++;
125
+ while (end > start && normalized[end - 1] === '_')
126
+ end--;
127
+ return (normalized.slice(start, end) || 'unknown').slice(0, 64);
128
+ }
129
+ function sanitizeOrgField(value) {
130
+ if (!value)
131
+ return null;
132
+ const sanitized = value
133
+ .replace(/[\x00-\x1f\x7f]/g, '')
134
+ .trim()
135
+ .slice(0, 128);
136
+ return sanitized || null;
137
+ }
138
+ function inferDeveloper() {
139
+ if (process.env.USER)
140
+ return sanitizeDeveloper(process.env.USER);
141
+ if (process.env.USERNAME)
142
+ return sanitizeDeveloper(process.env.USERNAME);
143
+ try {
144
+ return sanitizeDeveloper(execSync('git config user.name', {
145
+ encoding: 'utf-8',
146
+ timeout: 2000,
147
+ env: { ...process.env },
148
+ }).trim());
149
+ }
150
+ catch {
151
+ return 'unknown';
152
+ }
153
+ }
154
+ function inferProjectId() {
155
+ try {
156
+ const remote = execSync('git remote get-url origin', {
157
+ encoding: 'utf-8',
158
+ timeout: 2000,
159
+ env: { ...process.env },
160
+ }).trim();
161
+ // Extract "org/repo" from HTTPS or SSH remotes:
162
+ // https://github.com/org/repo.git → org/repo
163
+ // git@github.com:org/repo.git → org/repo
164
+ const match = remote.match(/[/:]([\w.-]+\/[\w.-]+?)(?:\.git)?$/);
165
+ return match ? match[1] : null;
166
+ }
167
+ catch {
168
+ return null;
169
+ }
170
+ }
171
+ function envBool(key, defaultValue) {
172
+ const val = process.env[key]?.trim().toLowerCase();
173
+ if (val === 'true' || val === '1' || val === 'yes' || val === 'y' || val === 'on')
174
+ return true;
175
+ if (val === 'false' || val === '0' || val === 'no' || val === 'n' || val === 'off')
176
+ return false;
177
+ return defaultValue;
178
+ }
179
+ function envInt(key, defaultValue, bounds) {
180
+ const val = process.env[key];
181
+ if (val === undefined)
182
+ return defaultValue;
183
+ const parsed = parseInt(val, 10);
184
+ if (Number.isNaN(parsed))
185
+ return defaultValue;
186
+ if (bounds?.min !== undefined && parsed < bounds.min)
187
+ return bounds.min;
188
+ if (bounds?.max !== undefined && parsed > bounds.max)
189
+ return bounds.max;
190
+ return parsed;
191
+ }
192
+ function envLogLevel(key, defaultValue) {
193
+ const val = process.env[key]?.toLowerCase();
194
+ if (val === 'debug' || val === 'info' || val === 'warn' || val === 'error')
195
+ return val;
196
+ return defaultValue;
197
+ }
198
+ function loadConfigFile(filePath) {
199
+ let raw;
200
+ try {
201
+ raw = readFileSync(filePath, 'utf-8');
202
+ }
203
+ catch {
204
+ return {};
205
+ }
206
+ let parsed;
207
+ try {
208
+ parsed = JSON.parse(raw);
209
+ }
210
+ catch (err) {
211
+ const errorMsg = err instanceof Error ? err.message : String(err);
212
+ logger.error('Invalid JSON in config file', {
213
+ filePath,
214
+ error: errorMsg,
215
+ });
216
+ throw new Error(`Config file parsing failed at ${filePath}: ${errorMsg}`);
217
+ }
218
+ const validation = ConfigFileSchema.safeParse(parsed);
219
+ if (!validation.success) {
220
+ const issues = validation.error.issues
221
+ .map((issue) => {
222
+ const path = issue.path.join('.');
223
+ return `${path || 'root'}: ${issue.message}`;
224
+ })
225
+ .join('; ');
226
+ logger.error('Config file validation failed', {
227
+ filePath,
228
+ issues,
229
+ });
230
+ throw new Error(`Config file validation failed at ${filePath}: ${issues}`);
231
+ }
232
+ return validation.data;
233
+ }
234
+ function resolveCollectorHost(licenseKey, explicit) {
235
+ if (explicit)
236
+ return explicit;
237
+ if (licenseKey?.toLowerCase().startsWith('eu01')) {
238
+ return 'eu';
239
+ }
240
+ return null;
241
+ }
242
+ function isValidUpstream(u) {
243
+ if (typeof u !== 'object' || u === null)
244
+ return false;
245
+ const obj = u;
246
+ if (typeof obj.name !== 'string')
247
+ return false;
248
+ if (obj.transportType !== 'http' && obj.transportType !== 'stdio')
249
+ return false;
250
+ if (obj.transportType === 'http' && typeof obj.url !== 'string')
251
+ return false;
252
+ if (obj.transportType === 'stdio' && typeof obj.command !== 'string')
253
+ return false;
254
+ return true;
255
+ }
256
+ function parseOtlpHeaders(headerString) {
257
+ if (!headerString)
258
+ return {};
259
+ const result = {};
260
+ const pairs = headerString.split(',');
261
+ for (const pair of pairs) {
262
+ const eqIdx = pair.indexOf('=');
263
+ if (eqIdx < 1)
264
+ continue;
265
+ const key = pair.slice(0, eqIdx).trim();
266
+ const value = pair.slice(eqIdx + 1).trim();
267
+ if (key && value) {
268
+ result[key] = value;
269
+ }
270
+ }
271
+ return result;
272
+ }
273
+ /**
274
+ * Validate the alerts.rulesPath value. The path is read from the user's
275
+ * config file or NR_AI_ALERTS_RULES_PATH env var; both are user-controlled
276
+ * but worth a defensive guard to keep an accidental misconfiguration from
277
+ * pointing fs.watch at /etc/hosts or similar. Rules:
278
+ *
279
+ * 1. Must end in `.json` (case-insensitive).
280
+ * 2. Must resolve under `storagePath` (the configured storage root).
281
+ *
282
+ * The default rules path is `${storagePath}/alerts/rules.json`, which
283
+ * always passes both checks. A bad path falls back to the default with a
284
+ * logged warning rather than throwing — one bad config field shouldn't
285
+ * brick the whole server.
286
+ */
287
+ function validateRulesPath(rawPath, storagePath) {
288
+ const fallback = resolve(storagePath, 'alerts', 'rules.json');
289
+ if (!rawPath.toLowerCase().endsWith('.json')) {
290
+ logger.warn('alerts.rulesPath does not end in .json — falling back to default', {
291
+ rawPath,
292
+ fallback,
293
+ });
294
+ return fallback;
295
+ }
296
+ const resolved = resolve(rawPath);
297
+ const storageResolved = resolve(storagePath);
298
+ // Match prefix only on full path segments to avoid `/foo/bar` matching
299
+ // `/foo/barbaz`.
300
+ const prefix = storageResolved.endsWith('/') ? storageResolved : storageResolved + '/';
301
+ if (resolved !== storageResolved && !resolved.startsWith(prefix)) {
302
+ logger.warn('alerts.rulesPath resolves outside storagePath — falling back to default', {
303
+ rawPath,
304
+ resolved,
305
+ storagePath: storageResolved,
306
+ fallback,
307
+ });
308
+ return fallback;
309
+ }
310
+ return resolved;
311
+ }
312
+ function parseProxyUpstreams(envValue, fileValue) {
313
+ // Env var takes precedence (JSON string)
314
+ if (envValue) {
315
+ try {
316
+ const parsed = JSON.parse(envValue);
317
+ if (!Array.isArray(parsed)) {
318
+ logger.warn('NEW_RELIC_AI_MCP_PROXY_UPSTREAMS must be a JSON array — ignoring env var value');
319
+ }
320
+ else {
321
+ const valid = parsed.filter((u) => {
322
+ if (isValidUpstream(u))
323
+ return true;
324
+ logger.warn('Skipping invalid proxy upstream entry (missing name, transportType, or url/command)', { entry: u });
325
+ return false;
326
+ });
327
+ return valid;
328
+ }
329
+ }
330
+ catch {
331
+ logger.warn('Invalid JSON in NEW_RELIC_AI_MCP_PROXY_UPSTREAMS env var');
332
+ }
333
+ }
334
+ // Config file
335
+ if (Array.isArray(fileValue)) {
336
+ const valid = fileValue.filter((u) => {
337
+ if (isValidUpstream(u))
338
+ return true;
339
+ logger.warn('Skipping invalid proxy upstream entry (missing name, transportType, or url/command)', { entry: u });
340
+ return false;
341
+ });
342
+ return valid;
343
+ }
344
+ return [];
345
+ }
346
+ export function loadMcpConfig(cliOptions) {
347
+ const configFilePath = cliOptions?.config ?? resolve(DEFAULT_STORAGE_PATH, 'config.json');
348
+ const file = loadConfigFile(configFilePath);
349
+ // Warn (don't crash) when the user's config file contains keys we don't
350
+ // recognize. Schema is `.passthrough()` at every level so unknown keys come
351
+ // through intact — surface them in logs so a typo or stale field is visible
352
+ // to the user without bricking the server on upgrade.
353
+ const knownTopKeys = new Set(Object.keys(ConfigFileSchema.shape));
354
+ const unknownTopKeys = Object.keys(file).filter((k) => !knownTopKeys.has(k));
355
+ if (unknownTopKeys.length > 0) {
356
+ logger.warn('Unknown keys in config file (ignored)', {
357
+ unknownKeys: unknownTopKeys,
358
+ path: configFilePath,
359
+ });
360
+ }
361
+ // Nested objects: alerts, dashboard. Hardcoded known-key sets keep this
362
+ // simple and avoid Zod schema introspection (the typed shape requires
363
+ // unwrapping .optional() and .passthrough()). If you add a new field to
364
+ // these schemas, add it here too.
365
+ const knownAlertsKeys = new Set([
366
+ 'personal',
367
+ 'enabled',
368
+ 'evaluationIntervalSeconds',
369
+ 'osNotifications',
370
+ 'logRetentionMb',
371
+ 'rulesPath',
372
+ ]);
373
+ const knownAlertsPersonalKeys = new Set([
374
+ 'dailyCostUsd',
375
+ 'sessionCostUsd',
376
+ 'efficiencyScoreMin',
377
+ 'stuckLoopCountMax',
378
+ 'antiPatternCountMax',
379
+ ]);
380
+ const knownDashboardKeys = new Set(['port', 'host', 'openOnStart']);
381
+ if (file.alerts && typeof file.alerts === 'object') {
382
+ const alerts = file.alerts;
383
+ const unknownAlertsKeys = Object.keys(alerts).filter((k) => !knownAlertsKeys.has(k));
384
+ if (unknownAlertsKeys.length > 0) {
385
+ logger.warn('Unknown keys in alerts config (ignored)', {
386
+ unknownKeys: unknownAlertsKeys,
387
+ path: configFilePath,
388
+ });
389
+ }
390
+ if (alerts.personal && typeof alerts.personal === 'object') {
391
+ const personal = alerts.personal;
392
+ const unknownAlertsPersonalKeys = Object.keys(personal).filter((k) => !knownAlertsPersonalKeys.has(k));
393
+ if (unknownAlertsPersonalKeys.length > 0) {
394
+ logger.warn('Unknown keys in alerts.personal config (ignored)', {
395
+ unknownKeys: unknownAlertsPersonalKeys,
396
+ path: configFilePath,
397
+ });
398
+ }
399
+ }
400
+ }
401
+ if (file.dashboard && typeof file.dashboard === 'object') {
402
+ const dashboard = file.dashboard;
403
+ const unknownDashboardKeys = Object.keys(dashboard).filter((k) => !knownDashboardKeys.has(k));
404
+ if (unknownDashboardKeys.length > 0) {
405
+ logger.warn('Unknown keys in dashboard config (ignored)', {
406
+ unknownKeys: unknownDashboardKeys,
407
+ path: configFilePath,
408
+ });
409
+ }
410
+ }
411
+ // --- Resolve mode early so we can gate licenseKey/accountId requirements ---
412
+ // File mode is already validated by the zod schema in loadConfigFile.
413
+ const VALID_MODES = ['cloud', 'local', 'both'];
414
+ const isValidMode = (v) => typeof v === 'string' && VALID_MODES.includes(v);
415
+ const envMode = process.env.NR_AI_MODE;
416
+ if (envMode !== undefined && envMode !== '' && !isValidMode(envMode)) {
417
+ throw new Error(`Invalid NR_AI_MODE='${envMode}'. Must be one of: ${VALID_MODES.join(', ')}.`);
418
+ }
419
+ const mode = (isValidMode(envMode) ? envMode : undefined) ?? file.mode ?? 'cloud';
420
+ // --- licenseKey: CLI has no flag for this, so env > file ---
421
+ const licenseKeyRaw = process.env.NEW_RELIC_LICENSE_KEY ??
422
+ (typeof file.licenseKey === 'string' ? file.licenseKey : undefined);
423
+ if (mode !== 'local' && !licenseKeyRaw) {
424
+ throw new Error(`Missing required configuration: licenseKey (mode='${mode}'). ` +
425
+ 'Set the NEW_RELIC_LICENSE_KEY environment variable or add "licenseKey" to ' +
426
+ configFilePath +
427
+ ", or switch to mode='local' to skip cloud transport.");
428
+ }
429
+ // Reject the literal string "null" — it's truthy but not a valid key
430
+ // and would cause silent auth failures at transport time.
431
+ if (licenseKeyRaw === 'null') {
432
+ throw new Error('Invalid licenseKey: the string "null" is not a valid New Relic license key. ' +
433
+ 'Remove it from the config file or set the correct key in NEW_RELIC_LICENSE_KEY.');
434
+ }
435
+ // In local mode, undefined if licenseKey is missing (NR transport won't be used)
436
+ const licenseKey = licenseKeyRaw;
437
+ // --- accountId: env > file ---
438
+ const accountIdRaw = process.env.NEW_RELIC_ACCOUNT_ID ??
439
+ (typeof file.accountId === 'string' ? file.accountId : undefined);
440
+ if (mode !== 'local' && !accountIdRaw) {
441
+ throw new Error(`Missing required configuration: accountId (mode='${mode}'). ` +
442
+ 'Set the NEW_RELIC_ACCOUNT_ID environment variable or add "accountId" to ' +
443
+ configFilePath +
444
+ ", or switch to mode='local' to skip cloud transport.");
445
+ }
446
+ if (accountIdRaw && !/^\d{1,12}$/.test(accountIdRaw)) {
447
+ throw new Error('Invalid configuration: accountId must be 1–12 decimal digits. ' +
448
+ `Received: "${accountIdRaw}"`);
449
+ }
450
+ // In local mode, undefined if accountId is missing (NR transport won't be used)
451
+ const accountId = accountIdRaw;
452
+ // --- Build config with priority: CLI > env > file > defaults ---
453
+ const storagePath = process.env.NEW_RELIC_AI_MCP_STORAGE_PATH ??
454
+ (typeof file.storagePath === 'string' ? file.storagePath : DEFAULT_STORAGE_PATH);
455
+ // N-10: highSecurity must be resolved before recordContent so it can override it
456
+ const highSecurity = envBool('NEW_RELIC_AI_HIGH_SECURITY', typeof file.highSecurity === 'boolean' ? file.highSecurity : false);
457
+ const config = {
458
+ licenseKey,
459
+ accountId,
460
+ appName: process.env.NEW_RELIC_AI_MCP_APP_NAME ??
461
+ (typeof file.appName === 'string' ? file.appName : 'preflight'),
462
+ model: process.env.NEW_RELIC_AI_MODEL ??
463
+ (typeof file.model === 'string' ? file.model : 'claude-sonnet-4-6'),
464
+ developer: normalizeDeveloperName(process.env.NEW_RELIC_AI_MCP_DEVELOPER ??
465
+ (typeof file.developer === 'string' ? file.developer : inferDeveloper())),
466
+ teamId: sanitizeOrgField(process.env.NEW_RELIC_AI_TEAM_ID ?? (typeof file.teamId === 'string' ? file.teamId : null)),
467
+ projectId: sanitizeOrgField(process.env.NEW_RELIC_AI_PROJECT_ID ??
468
+ (typeof file.projectId === 'string' ? file.projectId : inferProjectId())),
469
+ orgId: sanitizeOrgField(process.env.NEW_RELIC_AI_ORG_ID ?? (typeof file.orgId === 'string' ? file.orgId : null)),
470
+ enabled: envBool('NEW_RELIC_AI_MCP_ENABLED', typeof file.enabled === 'boolean' ? file.enabled : true),
471
+ highSecurity,
472
+ // N-10: highSecurity forces recordContent off regardless of other settings
473
+ recordContent: highSecurity
474
+ ? false
475
+ : envBool('NEW_RELIC_AI_MCP_RECORD_CONTENT', typeof file.recordContent === 'boolean' ? file.recordContent : false),
476
+ redactionPatterns: DEFAULT_REDACTION_PATTERNS,
477
+ hookBufferPath: process.env.NEW_RELIC_AI_MCP_BUFFER_PATH ??
478
+ (typeof file.hookBufferPath === 'string'
479
+ ? file.hookBufferPath
480
+ : resolve(storagePath, 'buffer.jsonl')),
481
+ storagePath,
482
+ harvestIntervalMs: {
483
+ events: envInt('NEW_RELIC_AI_MCP_HARVEST_EVENTS_MS', typeof file.harvestEventsMs === 'number' ? file.harvestEventsMs : 5000, { min: 100, max: 3_600_000 }),
484
+ metrics: envInt('NEW_RELIC_AI_MCP_HARVEST_METRICS_MS', typeof file.harvestMetricsMs === 'number' ? file.harvestMetricsMs : 60000, { min: 100, max: 3_600_000 }),
485
+ },
486
+ sessionBudgetUsd: (() => {
487
+ const raw = process.env.NEW_RELIC_AI_SESSION_BUDGET_USD;
488
+ if (raw !== undefined && raw !== '') {
489
+ const v = parseFloat(raw);
490
+ if (Number.isFinite(v) && v > 0)
491
+ return v;
492
+ }
493
+ return typeof file.sessionBudgetUsd === 'number' ? file.sessionBudgetUsd : null;
494
+ })(),
495
+ dailyBudgetUsd: (() => {
496
+ const raw = process.env.NEW_RELIC_AI_DAILY_BUDGET_USD;
497
+ if (raw !== undefined && raw !== '') {
498
+ const v = parseFloat(raw);
499
+ if (Number.isFinite(v) && v > 0)
500
+ return v;
501
+ }
502
+ return typeof file.dailyBudgetUsd === 'number' ? file.dailyBudgetUsd : null;
503
+ })(),
504
+ weeklyBudgetUsd: (() => {
505
+ const raw = process.env.NEW_RELIC_AI_WEEKLY_BUDGET_USD;
506
+ if (raw !== undefined && raw !== '') {
507
+ const v = parseFloat(raw);
508
+ if (Number.isFinite(v) && v > 0)
509
+ return v;
510
+ }
511
+ return typeof file.weeklyBudgetUsd === 'number' ? file.weeklyBudgetUsd : null;
512
+ })(),
513
+ port: cliOptions?.port ??
514
+ envInt('NEW_RELIC_AI_MCP_PORT', typeof file.port === 'number' ? file.port : 9847, {
515
+ min: 1,
516
+ max: 65535,
517
+ }),
518
+ logLevel: cliOptions?.logLevel ??
519
+ envLogLevel('NEW_RELIC_AI_MCP_LOG_LEVEL', typeof file.logLevel === 'string' &&
520
+ ['debug', 'info', 'warn', 'error'].includes(file.logLevel)
521
+ ? file.logLevel
522
+ : 'info'),
523
+ collectorHost: resolveCollectorHost(licenseKey, process.env.NEW_RELIC_HOST ??
524
+ (typeof file.collectorHost === 'string' ? file.collectorHost : null)),
525
+ proxyUpstreams: parseProxyUpstreams(process.env.NEW_RELIC_AI_MCP_PROXY_UPSTREAMS, file.proxyUpstreams),
526
+ nrApiKey: process.env.NEW_RELIC_API_KEY ?? (typeof file.nrApiKey === 'string' ? file.nrApiKey : null),
527
+ digestWebhookUrl: process.env.NEW_RELIC_AI_DIGEST_WEBHOOK_URL ??
528
+ (typeof file.digestWebhookUrl === 'string' ? file.digestWebhookUrl : null),
529
+ digestSchedule: process.env.NEW_RELIC_AI_DIGEST_SCHEDULE ??
530
+ (typeof file.digestSchedule === 'string' ? file.digestSchedule : '0 9 * * 1'),
531
+ retainSessionsDays: (() => {
532
+ const raw = process.env.NEW_RELIC_AI_RETAIN_SESSIONS_DAYS;
533
+ if (raw !== undefined && raw !== '') {
534
+ const v = parseInt(raw, 10);
535
+ if (Number.isFinite(v) && v > 0)
536
+ return v;
537
+ }
538
+ return typeof file.retainSessionsDays === 'number' ? file.retainSessionsDays : null;
539
+ })(),
540
+ otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ??
541
+ (typeof file.otlpEndpoint === 'string' ? file.otlpEndpoint : null),
542
+ otlpHeaders: (() => {
543
+ const envValue = process.env.OTEL_EXPORTER_OTLP_HEADERS;
544
+ if (envValue)
545
+ return parseOtlpHeaders(envValue);
546
+ return typeof file.otlpHeaders === 'object' && file.otlpHeaders !== null
547
+ ? file.otlpHeaders
548
+ : {};
549
+ })(),
550
+ transport: process.env.NEW_RELIC_AI_TRANSPORT === 'otlp'
551
+ ? 'otlp'
552
+ : process.env.NEW_RELIC_AI_TRANSPORT === 'both'
553
+ ? 'both'
554
+ : typeof file.transport === 'string' &&
555
+ (file.transport === 'otlp' || file.transport === 'both')
556
+ ? file.transport
557
+ : 'nr-events-api',
558
+ mode,
559
+ otlpReceiverEnabled: envBool('NR_AI_OTLP_RECEIVER_ENABLED', typeof file.otlpReceiverEnabled === 'boolean' ? file.otlpReceiverEnabled : false),
560
+ otlpReceiverPort: envInt('NR_AI_OTLP_RECEIVER_PORT', typeof file.otlpReceiverPort === 'number' ? file.otlpReceiverPort : 4318, { min: 1, max: 65535 }),
561
+ otlpReceiverBindAddress: (() => {
562
+ const envVal = process.env.NR_AI_OTLP_RECEIVER_BIND_ADDRESS;
563
+ if (envVal !== undefined && envVal !== '')
564
+ return envVal;
565
+ if (typeof file.otlpReceiverBindAddress === 'string' && file.otlpReceiverBindAddress !== '') {
566
+ return file.otlpReceiverBindAddress;
567
+ }
568
+ return '127.0.0.1';
569
+ })(),
570
+ otlpForwardEndpoint: (() => {
571
+ const envVal = process.env.NR_AI_OTLP_FORWARD_ENDPOINT;
572
+ // Default to the NR OTLP endpoint only when not in local mode. Local users
573
+ // may still set the value explicitly via env or config file (e.g. to point
574
+ // at a self-hosted collector); we just don't synthesize a NR default for them.
575
+ const defaultEndpoint = mode !== 'local' && licenseKey !== undefined ? 'https://otlp.nr-data.net' : null;
576
+ const endpoint = envVal !== undefined
577
+ ? envVal || null
578
+ : typeof file.otlpForwardEndpoint === 'string'
579
+ ? file.otlpForwardEndpoint || null
580
+ : defaultEndpoint;
581
+ if (endpoint === null)
582
+ return null;
583
+ try {
584
+ const url = new URL(endpoint);
585
+ if (url.protocol === 'https:')
586
+ return endpoint;
587
+ if (url.protocol === 'http:') {
588
+ logger.warn('OTLP forward endpoint uses http:// instead of https:// — TLS not enabled', {
589
+ endpoint,
590
+ });
591
+ return endpoint;
592
+ }
593
+ logger.warn('OTLP forward endpoint has unexpected protocol', {
594
+ endpoint,
595
+ protocol: url.protocol,
596
+ });
597
+ return endpoint;
598
+ }
599
+ catch (_err) {
600
+ logger.error('OTLP forward endpoint is not a valid URL', { endpoint });
601
+ return null;
602
+ }
603
+ })(),
604
+ otlpForwardHeaders: (() => {
605
+ const envValue = process.env.NR_AI_OTLP_FORWARD_HEADERS;
606
+ if (envValue !== undefined)
607
+ return parseOtlpHeaders(envValue);
608
+ if (typeof file.otlpForwardHeaders === 'object' && file.otlpForwardHeaders !== null) {
609
+ return file.otlpForwardHeaders;
610
+ }
611
+ // Don't synthesize a NR api-key header default in local mode — the licenseKey
612
+ // may be present in the env from another tool, and we must not leak it to a
613
+ // forward target the user didn't explicitly configure.
614
+ return mode !== 'local' && licenseKey !== undefined ? { 'api-key': licenseKey } : {};
615
+ })(),
616
+ personalAlertThresholds: (() => {
617
+ const fileThresholds = typeof file.alerts === 'object' && file.alerts !== null
618
+ ? file.alerts.personal
619
+ : undefined;
620
+ if (typeof fileThresholds !== 'object' || fileThresholds === null) {
621
+ return DEFAULT_PERSONAL_THRESHOLDS;
622
+ }
623
+ const t = fileThresholds;
624
+ return {
625
+ dailyCostUsd: typeof t.dailyCostUsd === 'number'
626
+ ? t.dailyCostUsd
627
+ : DEFAULT_PERSONAL_THRESHOLDS.dailyCostUsd,
628
+ sessionCostUsd: typeof t.sessionCostUsd === 'number'
629
+ ? t.sessionCostUsd
630
+ : DEFAULT_PERSONAL_THRESHOLDS.sessionCostUsd,
631
+ efficiencyScoreMin: typeof t.efficiencyScoreMin === 'number'
632
+ ? t.efficiencyScoreMin
633
+ : DEFAULT_PERSONAL_THRESHOLDS.efficiencyScoreMin,
634
+ stuckLoopCountMax: typeof t.stuckLoopCountMax === 'number'
635
+ ? t.stuckLoopCountMax
636
+ : DEFAULT_PERSONAL_THRESHOLDS.stuckLoopCountMax,
637
+ antiPatternCountMax: typeof t.antiPatternCountMax === 'number'
638
+ ? t.antiPatternCountMax
639
+ : DEFAULT_PERSONAL_THRESHOLDS.antiPatternCountMax,
640
+ };
641
+ })(),
642
+ dashboard: (() => {
643
+ const dashboardFile = (file.dashboard ?? {});
644
+ const dashboardPortRaw = process.env.NR_AI_DASHBOARD_PORT
645
+ ? parseInt(process.env.NR_AI_DASHBOARD_PORT, 10)
646
+ : dashboardFile.port;
647
+ // Allow port 0 — Node's server.listen(0) assigns an OS-ephemeral port,
648
+ // which is essential for parallel test runs and useful when the user
649
+ // wants to avoid hard-coding a port. Negative or out-of-range values
650
+ // still fall back to the default 7777.
651
+ const dashboardPort = Number.isFinite(dashboardPortRaw) && dashboardPortRaw >= 0 && dashboardPortRaw <= 65535
652
+ ? dashboardPortRaw
653
+ : 7777;
654
+ const requestedHost = process.env.NR_AI_DASHBOARD_HOST ?? dashboardFile.host ?? '127.0.0.1';
655
+ let dashboardHost = '127.0.0.1';
656
+ if (requestedHost !== '127.0.0.1' && requestedHost !== 'localhost') {
657
+ logger.warn(`dashboard.host '${requestedHost}' is non-loopback; v1 only supports loopback. Forcing 127.0.0.1.`);
658
+ }
659
+ else {
660
+ dashboardHost = requestedHost === 'localhost' ? '127.0.0.1' : requestedHost;
661
+ }
662
+ const dashboardOpenOnStart = envBool('NR_AI_DASHBOARD_OPEN', dashboardFile.openOnStart === true);
663
+ return {
664
+ port: dashboardPort,
665
+ host: dashboardHost,
666
+ openOnStart: dashboardOpenOnStart,
667
+ };
668
+ })(),
669
+ alerts: (() => {
670
+ // The alerts config is a separate top-level block from
671
+ // `personalAlertThresholds` (above) — they share the `alerts` key in
672
+ // the file schema for backwards compatibility, but expose different
673
+ // fields. `enabled` defaults to true outside of cloud-only mode.
674
+ const alertsFile = typeof file.alerts === 'object' && file.alerts !== null
675
+ ? file.alerts
676
+ : {};
677
+ const fileEnabled = typeof alertsFile.enabled === 'boolean' ? alertsFile.enabled : undefined;
678
+ const fileInterval = typeof alertsFile.evaluationIntervalSeconds === 'number'
679
+ ? alertsFile.evaluationIntervalSeconds
680
+ : undefined;
681
+ const fileOsNotifications = typeof alertsFile.osNotifications === 'boolean' ? alertsFile.osNotifications : undefined;
682
+ const fileLogRetention = typeof alertsFile.logRetentionMb === 'number' ? alertsFile.logRetentionMb : undefined;
683
+ const fileRulesPath = typeof alertsFile.rulesPath === 'string' ? alertsFile.rulesPath : undefined;
684
+ const enabledDefault = mode !== 'cloud';
685
+ const enabled = envBool('NR_AI_ALERTS_ENABLED', fileEnabled ?? enabledDefault);
686
+ const intervalSeconds = envInt('NR_AI_ALERTS_INTERVAL_SECONDS', fileInterval ?? 30, {
687
+ min: 5,
688
+ max: 300,
689
+ });
690
+ const osNotifications = envBool('NR_AI_ALERTS_OS_NOTIFICATIONS', fileOsNotifications ?? false);
691
+ const logRetentionMb = envInt('NR_AI_ALERTS_LOG_RETENTION_MB', fileLogRetention ?? 10, {
692
+ min: 1,
693
+ max: 1024,
694
+ });
695
+ const envRulesPath = process.env.NR_AI_ALERTS_RULES_PATH;
696
+ const rawRulesPath = envRulesPath !== undefined && envRulesPath !== ''
697
+ ? envRulesPath
698
+ : (fileRulesPath ?? resolve(storagePath, 'alerts', 'rules.json'));
699
+ // Defensive validation: rulesPath is read from user config, so reject
700
+ // values that don't end in .json or that resolve outside storagePath.
701
+ // Prevents accidental fs.watch handles on system files like /etc/hosts
702
+ // or path-traversal probes via env-var injection. The default path is
703
+ // always permitted.
704
+ const rulesPath = validateRulesPath(rawRulesPath, storagePath);
705
+ return {
706
+ enabled,
707
+ evaluationIntervalSeconds: intervalSeconds,
708
+ osNotifications,
709
+ logRetentionMb,
710
+ rulesPath,
711
+ };
712
+ })(),
713
+ };
714
+ logger.debug('Configuration loaded', {
715
+ appName: config.appName,
716
+ developer: config.developer,
717
+ teamId: config.teamId,
718
+ projectId: config.projectId,
719
+ orgId: config.orgId,
720
+ enabled: config.enabled,
721
+ highSecurity: config.highSecurity,
722
+ recordContent: config.recordContent,
723
+ storagePath: config.storagePath,
724
+ port: config.port,
725
+ collectorHost: config.collectorHost ?? 'us (default)',
726
+ sessionBudgetUsd: config.sessionBudgetUsd,
727
+ dailyBudgetUsd: config.dailyBudgetUsd,
728
+ weeklyBudgetUsd: config.weeklyBudgetUsd,
729
+ });
730
+ return Object.freeze(config);
731
+ }
732
+ /**
733
+ * Validate a config file and return structured results — no throws, no logging.
734
+ * The CLI `validate` command uses this to surface issues in human-readable form
735
+ * before the user attempts to start the MCP server.
736
+ */
737
+ export function validateConfigFile(filePath) {
738
+ const errors = [];
739
+ const warnings = [];
740
+ if (!existsSync(filePath)) {
741
+ return { filePath, fileExists: false, errors, warnings };
742
+ }
743
+ // Read and parse the file
744
+ let raw;
745
+ try {
746
+ raw = readFileSync(filePath, 'utf-8');
747
+ }
748
+ catch (err) {
749
+ errors.push(`Could not read file: ${err instanceof Error ? err.message : String(err)}`);
750
+ return { filePath, fileExists: true, errors, warnings };
751
+ }
752
+ let parsed;
753
+ try {
754
+ parsed = JSON.parse(raw);
755
+ }
756
+ catch (err) {
757
+ errors.push(`Invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
758
+ return { filePath, fileExists: true, errors, warnings };
759
+ }
760
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
761
+ errors.push('Config must be a JSON object, not an array or primitive value');
762
+ return { filePath, fileExists: true, errors, warnings };
763
+ }
764
+ // Zod type/value errors
765
+ const validation = ConfigFileSchema.safeParse(parsed);
766
+ if (!validation.success) {
767
+ for (const issue of validation.error.issues) {
768
+ const path = issue.path.length > 0 ? issue.path.join('.') : 'root';
769
+ errors.push(`${path}: ${issue.message}`);
770
+ }
771
+ }
772
+ // Unknown key detection with "did you mean" suggestions
773
+ const file = parsed;
774
+ const knownTopKeys = new Set(Object.keys(ConfigFileSchema.shape));
775
+ for (const key of Object.keys(file)) {
776
+ if (!knownTopKeys.has(key)) {
777
+ const suggestion = findSuggestion(key, knownTopKeys);
778
+ warnings.push(`Unknown key "${key}"${suggestion ? ` — did you mean "${suggestion}"?` : ' — not a recognized config field'}`);
779
+ }
780
+ }
781
+ const knownAlertsKeys = new Set([
782
+ 'personal',
783
+ 'enabled',
784
+ 'evaluationIntervalSeconds',
785
+ 'osNotifications',
786
+ 'logRetentionMb',
787
+ 'rulesPath',
788
+ ]);
789
+ const knownAlertsPersonalKeys = new Set([
790
+ 'dailyCostUsd',
791
+ 'sessionCostUsd',
792
+ 'efficiencyScoreMin',
793
+ 'stuckLoopCountMax',
794
+ 'antiPatternCountMax',
795
+ ]);
796
+ const knownDashboardKeys = new Set(['port', 'host', 'openOnStart']);
797
+ if (file.alerts && typeof file.alerts === 'object' && !Array.isArray(file.alerts)) {
798
+ const alerts = file.alerts;
799
+ for (const key of Object.keys(alerts)) {
800
+ if (!knownAlertsKeys.has(key)) {
801
+ const suggestion = findSuggestion(key, knownAlertsKeys);
802
+ warnings.push(`Unknown key "alerts.${key}"${suggestion ? ` — did you mean "alerts.${suggestion}"?` : ''}`);
803
+ }
804
+ }
805
+ if (alerts.personal && typeof alerts.personal === 'object' && !Array.isArray(alerts.personal)) {
806
+ const personal = alerts.personal;
807
+ for (const key of Object.keys(personal)) {
808
+ if (!knownAlertsPersonalKeys.has(key)) {
809
+ const suggestion = findSuggestion(key, knownAlertsPersonalKeys);
810
+ warnings.push(`Unknown key "alerts.personal.${key}"${suggestion ? ` — did you mean "alerts.personal.${suggestion}"?` : ''}`);
811
+ }
812
+ }
813
+ }
814
+ }
815
+ if (file.dashboard && typeof file.dashboard === 'object' && !Array.isArray(file.dashboard)) {
816
+ const dashboard = file.dashboard;
817
+ for (const key of Object.keys(dashboard)) {
818
+ if (!knownDashboardKeys.has(key)) {
819
+ const suggestion = findSuggestion(key, knownDashboardKeys);
820
+ warnings.push(`Unknown key "dashboard.${key}"${suggestion ? ` — did you mean "dashboard.${suggestion}"?` : ''}`);
821
+ }
822
+ }
823
+ }
824
+ return { filePath, fileExists: true, errors, warnings };
825
+ }
826
+ /** Return a suggestion from known keys if the unknown key is a case-insensitive
827
+ * match or a single-character edit away (insertion, deletion, or substitution). */
828
+ function findSuggestion(unknown, knownKeys) {
829
+ const lower = unknown.toLowerCase();
830
+ // Exact case-insensitive match — covers the most common typo (e.g. licensekey → licenseKey)
831
+ for (const k of knownKeys) {
832
+ if (k.toLowerCase() === lower)
833
+ return k;
834
+ }
835
+ // Single-character difference (insertion, deletion, or substitution)
836
+ for (const k of knownKeys) {
837
+ if (editDistance(unknown, k) <= 1)
838
+ return k;
839
+ }
840
+ return null;
841
+ }
842
+ function editDistance(a, b) {
843
+ const m = a.length;
844
+ const n = b.length;
845
+ const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)));
846
+ for (let i = 1; i <= m; i++) {
847
+ for (let j = 1; j <= n; j++) {
848
+ dp[i][j] =
849
+ a[i - 1] === b[j - 1]
850
+ ? dp[i - 1][j - 1]
851
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
852
+ }
853
+ }
854
+ return dp[m][n];
855
+ }
856
+ const MAX_REDACT_LEN = 1_048_576; // 1 MB
857
+ export function redactSensitive(value, patterns) {
858
+ if (typeof value !== 'string')
859
+ return String(value ?? '');
860
+ const pats = patterns ?? DEFAULT_REDACTION_PATTERNS;
861
+ let result = value.length > MAX_REDACT_LEN ? value.slice(0, MAX_REDACT_LEN) : value;
862
+ for (const pattern of pats) {
863
+ pattern.lastIndex = 0;
864
+ result = result.replace(pattern, '[REDACTED]');
865
+ }
866
+ return result;
867
+ }
868
+ //# sourceMappingURL=config.js.map