@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.
- package/LICENSE +183 -0
- package/README.md +498 -0
- package/dist/alerts/alert-log.d.ts +24 -0
- package/dist/alerts/alert-log.d.ts.map +1 -0
- package/dist/alerts/alert-log.js +159 -0
- package/dist/alerts/alert-log.js.map +1 -0
- package/dist/alerts/alert-snapshot-collector.d.ts +168 -0
- package/dist/alerts/alert-snapshot-collector.d.ts.map +1 -0
- package/dist/alerts/alert-snapshot-collector.js +243 -0
- package/dist/alerts/alert-snapshot-collector.js.map +1 -0
- package/dist/alerts/local-alert-engine.d.ts +86 -0
- package/dist/alerts/local-alert-engine.d.ts.map +1 -0
- package/dist/alerts/local-alert-engine.js +466 -0
- package/dist/alerts/local-alert-engine.js.map +1 -0
- package/dist/alerts/local-alert-rule.d.ts +439 -0
- package/dist/alerts/local-alert-rule.d.ts.map +1 -0
- package/dist/alerts/local-alert-rule.js +139 -0
- package/dist/alerts/local-alert-rule.js.map +1 -0
- package/dist/alerts/os-notifier.d.ts +39 -0
- package/dist/alerts/os-notifier.d.ts.map +1 -0
- package/dist/alerts/os-notifier.js +170 -0
- package/dist/alerts/os-notifier.js.map +1 -0
- package/dist/alerts/types.d.ts +35 -0
- package/dist/alerts/types.d.ts.map +1 -0
- package/dist/alerts/types.js +8 -0
- package/dist/alerts/types.js.map +1 -0
- package/dist/config.d.ts +169 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +860 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard/dashboard-server.d.ts +38 -0
- package/dist/dashboard/dashboard-server.d.ts.map +1 -0
- package/dist/dashboard/dashboard-server.js +207 -0
- package/dist/dashboard/dashboard-server.js.map +1 -0
- package/dist/dashboard/index.d.ts +3 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +2 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/live-event-bus.d.ts +99 -0
- package/dist/dashboard/live-event-bus.d.ts.map +1 -0
- package/dist/dashboard/live-event-bus.js +56 -0
- package/dist/dashboard/live-event-bus.js.map +1 -0
- package/dist/dashboard/routes/api-handler.d.ts +122 -0
- package/dist/dashboard/routes/api-handler.d.ts.map +1 -0
- package/dist/dashboard/routes/api-handler.js +1414 -0
- package/dist/dashboard/routes/api-handler.js.map +1 -0
- package/dist/dashboard/routes/replay-analyzer.d.ts +15 -0
- package/dist/dashboard/routes/replay-analyzer.d.ts.map +1 -0
- package/dist/dashboard/routes/replay-analyzer.js +227 -0
- package/dist/dashboard/routes/replay-analyzer.js.map +1 -0
- package/dist/dashboard/routes/sse-handler.d.ts +4 -0
- package/dist/dashboard/routes/sse-handler.d.ts.map +1 -0
- package/dist/dashboard/routes/sse-handler.js +122 -0
- package/dist/dashboard/routes/sse-handler.js.map +1 -0
- package/dist/dashboard/routes/static-handler.d.ts +3 -0
- package/dist/dashboard/routes/static-handler.d.ts.map +1 -0
- package/dist/dashboard/routes/static-handler.js +103 -0
- package/dist/dashboard/routes/static-handler.js.map +1 -0
- package/dist/data/alerts/conditions/01-daily-cost-spike.json +16 -0
- package/dist/data/alerts/conditions/02-low-efficiency-score.json +16 -0
- package/dist/data/alerts/conditions/03-stuck-loop-rate.json +16 -0
- package/dist/data/alerts/conditions/04-anti-pattern-rate.json +16 -0
- package/dist/data/alerts/conditions/05-session-cost-budget.json +16 -0
- package/dist/data/alerts/conditions-personal/01-personal-daily-cost.json +16 -0
- package/dist/data/alerts/conditions-personal/02-personal-session-cost.json +16 -0
- package/dist/data/alerts/conditions-personal/03-personal-low-efficiency.json +16 -0
- package/dist/data/alerts/conditions-personal/04-personal-anti-pattern-rate.json +16 -0
- package/dist/data/alerts/conditions-personal/05-personal-stuck-loop.json +16 -0
- package/dist/data/alerts/policy.json +4 -0
- package/dist/data/dashboards/ai-coding-assistant-manager-view.json +103 -0
- package/dist/data/dashboards/ai-coding-assistant-overview.json +239 -0
- package/dist/data/dashboards/ai-coding-assistant-personal.json +442 -0
- package/dist/data/dashboards/ai-coding-assistant-platform-comparison.json +320 -0
- package/dist/data/dashboards/ai-coding-assistant-security.json +275 -0
- package/dist/data/dashboards/ai-coding-assistant-session-detail.json +296 -0
- package/dist/data/dashboards/ai-coding-assistant-team-view.json +345 -0
- package/dist/deploy/data-paths.d.ts +22 -0
- package/dist/deploy/data-paths.d.ts.map +1 -0
- package/dist/deploy/data-paths.js +69 -0
- package/dist/deploy/data-paths.js.map +1 -0
- package/dist/deploy/deploy-alerts.d.ts +58 -0
- package/dist/deploy/deploy-alerts.d.ts.map +1 -0
- package/dist/deploy/deploy-alerts.js +371 -0
- package/dist/deploy/deploy-alerts.js.map +1 -0
- package/dist/deploy/deploy-dashboards.d.ts +92 -0
- package/dist/deploy/deploy-dashboards.d.ts.map +1 -0
- package/dist/deploy/deploy-dashboards.js +282 -0
- package/dist/deploy/deploy-dashboards.js.map +1 -0
- package/dist/digest/digest-formatter.d.ts +3 -0
- package/dist/digest/digest-formatter.d.ts.map +1 -0
- package/dist/digest/digest-formatter.js +37 -0
- package/dist/digest/digest-formatter.js.map +1 -0
- package/dist/digest/digest-sender.d.ts +2 -0
- package/dist/digest/digest-sender.d.ts.map +1 -0
- package/dist/digest/digest-sender.js +29 -0
- package/dist/digest/digest-sender.js.map +1 -0
- package/dist/hooks/bash-classifier.d.ts +26 -0
- package/dist/hooks/bash-classifier.d.ts.map +1 -0
- package/dist/hooks/bash-classifier.js +409 -0
- package/dist/hooks/bash-classifier.js.map +1 -0
- package/dist/hooks/collector-script.d.ts +47 -0
- package/dist/hooks/collector-script.d.ts.map +1 -0
- package/dist/hooks/collector-script.js +662 -0
- package/dist/hooks/collector-script.js.map +1 -0
- package/dist/hooks/event-processor.d.ts +65 -0
- package/dist/hooks/event-processor.d.ts.map +1 -0
- package/dist/hooks/event-processor.js +342 -0
- package/dist/hooks/event-processor.js.map +1 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/session-resolver.d.ts +66 -0
- package/dist/hooks/session-resolver.d.ts.map +1 -0
- package/dist/hooks/session-resolver.js +196 -0
- package/dist/hooks/session-resolver.js.map +1 -0
- package/dist/hooks/tool-parsers.d.ts +19 -0
- package/dist/hooks/tool-parsers.d.ts.map +1 -0
- package/dist/hooks/tool-parsers.js +260 -0
- package/dist/hooks/tool-parsers.js.map +1 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1505 -0
- package/dist/index.js.map +1 -0
- package/dist/install/cli.d.ts +11 -0
- package/dist/install/cli.d.ts.map +1 -0
- package/dist/install/cli.js +365 -0
- package/dist/install/cli.js.map +1 -0
- package/dist/install/index.d.ts +4 -0
- package/dist/install/index.d.ts.map +1 -0
- package/dist/install/index.js +3 -0
- package/dist/install/index.js.map +1 -0
- package/dist/install/install-helper.d.ts +35 -0
- package/dist/install/install-helper.d.ts.map +1 -0
- package/dist/install/install-helper.js +227 -0
- package/dist/install/install-helper.js.map +1 -0
- package/dist/install/key-validator.d.ts +19 -0
- package/dist/install/key-validator.d.ts.map +1 -0
- package/dist/install/key-validator.js +122 -0
- package/dist/install/key-validator.js.map +1 -0
- package/dist/install/migrate.d.ts +12 -0
- package/dist/install/migrate.d.ts.map +1 -0
- package/dist/install/migrate.js +115 -0
- package/dist/install/migrate.js.map +1 -0
- package/dist/install/schedule.d.ts +11 -0
- package/dist/install/schedule.d.ts.map +1 -0
- package/dist/install/schedule.js +114 -0
- package/dist/install/schedule.js.map +1 -0
- package/dist/install/setup-wizard.d.ts +40 -0
- package/dist/install/setup-wizard.d.ts.map +1 -0
- package/dist/install/setup-wizard.js +489 -0
- package/dist/install/setup-wizard.js.map +1 -0
- package/dist/lib/date.d.ts +54 -0
- package/dist/lib/date.d.ts.map +1 -0
- package/dist/lib/date.js +85 -0
- package/dist/lib/date.js.map +1 -0
- package/dist/metrics/anti-patterns.d.ts +62 -0
- package/dist/metrics/anti-patterns.d.ts.map +1 -0
- package/dist/metrics/anti-patterns.js +301 -0
- package/dist/metrics/anti-patterns.js.map +1 -0
- package/dist/metrics/api-failure-tracker.d.ts +82 -0
- package/dist/metrics/api-failure-tracker.d.ts.map +1 -0
- package/dist/metrics/api-failure-tracker.js +202 -0
- package/dist/metrics/api-failure-tracker.js.map +1 -0
- package/dist/metrics/budget-tracker.d.ts +60 -0
- package/dist/metrics/budget-tracker.d.ts.map +1 -0
- package/dist/metrics/budget-tracker.js +130 -0
- package/dist/metrics/budget-tracker.js.map +1 -0
- package/dist/metrics/claudemd-tracker.d.ts +108 -0
- package/dist/metrics/claudemd-tracker.d.ts.map +1 -0
- package/dist/metrics/claudemd-tracker.js +337 -0
- package/dist/metrics/claudemd-tracker.js.map +1 -0
- package/dist/metrics/collaboration-profile.d.ts +65 -0
- package/dist/metrics/collaboration-profile.d.ts.map +1 -0
- package/dist/metrics/collaboration-profile.js +231 -0
- package/dist/metrics/collaboration-profile.js.map +1 -0
- package/dist/metrics/context-composition-tracker.d.ts +74 -0
- package/dist/metrics/context-composition-tracker.d.ts.map +1 -0
- package/dist/metrics/context-composition-tracker.js +202 -0
- package/dist/metrics/context-composition-tracker.js.map +1 -0
- package/dist/metrics/context-tracker.d.ts +78 -0
- package/dist/metrics/context-tracker.d.ts.map +1 -0
- package/dist/metrics/context-tracker.js +222 -0
- package/dist/metrics/context-tracker.js.map +1 -0
- package/dist/metrics/context-window-tracker.d.ts +18 -0
- package/dist/metrics/context-window-tracker.d.ts.map +1 -0
- package/dist/metrics/context-window-tracker.js +35 -0
- package/dist/metrics/context-window-tracker.js.map +1 -0
- package/dist/metrics/cost-forecast.d.ts +36 -0
- package/dist/metrics/cost-forecast.d.ts.map +1 -0
- package/dist/metrics/cost-forecast.js +91 -0
- package/dist/metrics/cost-forecast.js.map +1 -0
- package/dist/metrics/cost-per-outcome.d.ts +102 -0
- package/dist/metrics/cost-per-outcome.d.ts.map +1 -0
- package/dist/metrics/cost-per-outcome.js +266 -0
- package/dist/metrics/cost-per-outcome.js.map +1 -0
- package/dist/metrics/cost-tracker.d.ts +78 -0
- package/dist/metrics/cost-tracker.d.ts.map +1 -0
- package/dist/metrics/cost-tracker.js +169 -0
- package/dist/metrics/cost-tracker.js.map +1 -0
- package/dist/metrics/decision-tracker.d.ts +49 -0
- package/dist/metrics/decision-tracker.d.ts.map +1 -0
- package/dist/metrics/decision-tracker.js +161 -0
- package/dist/metrics/decision-tracker.js.map +1 -0
- package/dist/metrics/efficiency-score.d.ts +80 -0
- package/dist/metrics/efficiency-score.d.ts.map +1 -0
- package/dist/metrics/efficiency-score.js +219 -0
- package/dist/metrics/efficiency-score.js.map +1 -0
- package/dist/metrics/git-efficiency-tracker.d.ts +165 -0
- package/dist/metrics/git-efficiency-tracker.d.ts.map +1 -0
- package/dist/metrics/git-efficiency-tracker.js +1056 -0
- package/dist/metrics/git-efficiency-tracker.js.map +1 -0
- package/dist/metrics/index.d.ts +26 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +14 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instruction-drift-tracker.d.ts +69 -0
- package/dist/metrics/instruction-drift-tracker.d.ts.map +1 -0
- package/dist/metrics/instruction-drift-tracker.js +213 -0
- package/dist/metrics/instruction-drift-tracker.js.map +1 -0
- package/dist/metrics/latency-decomposition.d.ts +50 -0
- package/dist/metrics/latency-decomposition.d.ts.map +1 -0
- package/dist/metrics/latency-decomposition.js +112 -0
- package/dist/metrics/latency-decomposition.js.map +1 -0
- package/dist/metrics/latency-tracker.d.ts +33 -0
- package/dist/metrics/latency-tracker.d.ts.map +1 -0
- package/dist/metrics/latency-tracker.js +93 -0
- package/dist/metrics/latency-tracker.js.map +1 -0
- package/dist/metrics/live-session-registry.d.ts +29 -0
- package/dist/metrics/live-session-registry.d.ts.map +1 -0
- package/dist/metrics/live-session-registry.js +103 -0
- package/dist/metrics/live-session-registry.js.map +1 -0
- package/dist/metrics/model-usage-tracker.d.ts +21 -0
- package/dist/metrics/model-usage-tracker.d.ts.map +1 -0
- package/dist/metrics/model-usage-tracker.js +53 -0
- package/dist/metrics/model-usage-tracker.js.map +1 -0
- package/dist/metrics/percentile.d.ts +5 -0
- package/dist/metrics/percentile.d.ts.map +1 -0
- package/dist/metrics/percentile.js +10 -0
- package/dist/metrics/percentile.js.map +1 -0
- package/dist/metrics/personal-coach.d.ts +47 -0
- package/dist/metrics/personal-coach.d.ts.map +1 -0
- package/dist/metrics/personal-coach.js +241 -0
- package/dist/metrics/personal-coach.js.map +1 -0
- package/dist/metrics/prompt-feedback.d.ts +75 -0
- package/dist/metrics/prompt-feedback.d.ts.map +1 -0
- package/dist/metrics/prompt-feedback.js +286 -0
- package/dist/metrics/prompt-feedback.js.map +1 -0
- package/dist/metrics/proxy-metrics.d.ts +54 -0
- package/dist/metrics/proxy-metrics.d.ts.map +1 -0
- package/dist/metrics/proxy-metrics.js +228 -0
- package/dist/metrics/proxy-metrics.js.map +1 -0
- package/dist/metrics/quality-proxy-tracker.d.ts +51 -0
- package/dist/metrics/quality-proxy-tracker.d.ts.map +1 -0
- package/dist/metrics/quality-proxy-tracker.js +162 -0
- package/dist/metrics/quality-proxy-tracker.js.map +1 -0
- package/dist/metrics/recommendation-engine.d.ts +72 -0
- package/dist/metrics/recommendation-engine.d.ts.map +1 -0
- package/dist/metrics/recommendation-engine.js +207 -0
- package/dist/metrics/recommendation-engine.js.map +1 -0
- package/dist/metrics/retry-detector.d.ts +43 -0
- package/dist/metrics/retry-detector.d.ts.map +1 -0
- package/dist/metrics/retry-detector.js +179 -0
- package/dist/metrics/retry-detector.js.map +1 -0
- package/dist/metrics/session-tracker.d.ts +75 -0
- package/dist/metrics/session-tracker.d.ts.map +1 -0
- package/dist/metrics/session-tracker.js +249 -0
- package/dist/metrics/session-tracker.js.map +1 -0
- package/dist/metrics/task-completion-tracker.d.ts +15 -0
- package/dist/metrics/task-completion-tracker.d.ts.map +1 -0
- package/dist/metrics/task-completion-tracker.js +27 -0
- package/dist/metrics/task-completion-tracker.js.map +1 -0
- package/dist/metrics/task-detector.d.ts +84 -0
- package/dist/metrics/task-detector.d.ts.map +1 -0
- package/dist/metrics/task-detector.js +302 -0
- package/dist/metrics/task-detector.js.map +1 -0
- package/dist/metrics/tool-selection-scorer.d.ts +39 -0
- package/dist/metrics/tool-selection-scorer.d.ts.map +1 -0
- package/dist/metrics/tool-selection-scorer.js +193 -0
- package/dist/metrics/tool-selection-scorer.js.map +1 -0
- package/dist/metrics/trend-analyzer.d.ts +92 -0
- package/dist/metrics/trend-analyzer.d.ts.map +1 -0
- package/dist/metrics/trend-analyzer.js +293 -0
- package/dist/metrics/trend-analyzer.js.map +1 -0
- package/dist/metrics/turn-cost-attributor.d.ts +41 -0
- package/dist/metrics/turn-cost-attributor.d.ts.map +1 -0
- package/dist/metrics/turn-cost-attributor.js +118 -0
- package/dist/metrics/turn-cost-attributor.js.map +1 -0
- package/dist/metrics/turn-tracker.d.ts +49 -0
- package/dist/metrics/turn-tracker.d.ts.map +1 -0
- package/dist/metrics/turn-tracker.js +192 -0
- package/dist/metrics/turn-tracker.js.map +1 -0
- package/dist/platforms/amazon-q-adapter.d.ts +10 -0
- package/dist/platforms/amazon-q-adapter.d.ts.map +1 -0
- package/dist/platforms/amazon-q-adapter.js +75 -0
- package/dist/platforms/amazon-q-adapter.js.map +1 -0
- package/dist/platforms/claude-code-adapter.d.ts +10 -0
- package/dist/platforms/claude-code-adapter.d.ts.map +1 -0
- package/dist/platforms/claude-code-adapter.js +48 -0
- package/dist/platforms/claude-code-adapter.js.map +1 -0
- package/dist/platforms/continue-adapter.d.ts +10 -0
- package/dist/platforms/continue-adapter.d.ts.map +1 -0
- package/dist/platforms/continue-adapter.js +73 -0
- package/dist/platforms/continue-adapter.js.map +1 -0
- package/dist/platforms/copilot-adapter.d.ts +37 -0
- package/dist/platforms/copilot-adapter.d.ts.map +1 -0
- package/dist/platforms/copilot-adapter.js +66 -0
- package/dist/platforms/copilot-adapter.js.map +1 -0
- package/dist/platforms/cursor-adapter.d.ts +10 -0
- package/dist/platforms/cursor-adapter.d.ts.map +1 -0
- package/dist/platforms/cursor-adapter.js +60 -0
- package/dist/platforms/cursor-adapter.js.map +1 -0
- package/dist/platforms/generic-mcp-adapter.d.ts +113 -0
- package/dist/platforms/generic-mcp-adapter.d.ts.map +1 -0
- package/dist/platforms/generic-mcp-adapter.js +139 -0
- package/dist/platforms/generic-mcp-adapter.js.map +1 -0
- package/dist/platforms/index.d.ts +15 -0
- package/dist/platforms/index.d.ts.map +1 -0
- package/dist/platforms/index.js +12 -0
- package/dist/platforms/index.js.map +1 -0
- package/dist/platforms/platform-registry.d.ts +11 -0
- package/dist/platforms/platform-registry.d.ts.map +1 -0
- package/dist/platforms/platform-registry.js +54 -0
- package/dist/platforms/platform-registry.js.map +1 -0
- package/dist/platforms/types.d.ts +36 -0
- package/dist/platforms/types.d.ts.map +1 -0
- package/dist/platforms/types.js +2 -0
- package/dist/platforms/types.js.map +1 -0
- package/dist/platforms/windsurf-adapter.d.ts +10 -0
- package/dist/platforms/windsurf-adapter.d.ts.map +1 -0
- package/dist/platforms/windsurf-adapter.js +63 -0
- package/dist/platforms/windsurf-adapter.js.map +1 -0
- package/dist/platforms/zed-adapter.d.ts +10 -0
- package/dist/platforms/zed-adapter.d.ts.map +1 -0
- package/dist/platforms/zed-adapter.js +72 -0
- package/dist/platforms/zed-adapter.js.map +1 -0
- package/dist/proxy/index.d.ts +7 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +5 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/proxy/otlp-receiver.d.ts +28 -0
- package/dist/proxy/otlp-receiver.d.ts.map +1 -0
- package/dist/proxy/otlp-receiver.js +319 -0
- package/dist/proxy/otlp-receiver.js.map +1 -0
- package/dist/proxy/proxy-manager.d.ts +47 -0
- package/dist/proxy/proxy-manager.d.ts.map +1 -0
- package/dist/proxy/proxy-manager.js +338 -0
- package/dist/proxy/proxy-manager.js.map +1 -0
- package/dist/proxy/types.d.ts +72 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/types.js +33 -0
- package/dist/proxy/types.js.map +1 -0
- package/dist/proxy/upstream-http.d.ts +26 -0
- package/dist/proxy/upstream-http.d.ts.map +1 -0
- package/dist/proxy/upstream-http.js +209 -0
- package/dist/proxy/upstream-http.js.map +1 -0
- package/dist/proxy/upstream-stdio.d.ts +25 -0
- package/dist/proxy/upstream-stdio.d.ts.map +1 -0
- package/dist/proxy/upstream-stdio.js +256 -0
- package/dist/proxy/upstream-stdio.js.map +1 -0
- package/dist/security/audit-trail.d.ts +74 -0
- package/dist/security/audit-trail.d.ts.map +1 -0
- package/dist/security/audit-trail.js +338 -0
- package/dist/security/audit-trail.js.map +1 -0
- package/dist/security/index.d.ts +5 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +4 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/ssrf.d.ts +2 -0
- package/dist/security/ssrf.d.ts.map +1 -0
- package/dist/security/ssrf.js +126 -0
- package/dist/security/ssrf.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +117 -0
- package/dist/server.js.map +1 -0
- package/dist/shared/__test-utils__/log-output.d.ts +49 -0
- package/dist/shared/__test-utils__/log-output.d.ts.map +1 -0
- package/dist/shared/__test-utils__/log-output.js +38 -0
- package/dist/shared/__test-utils__/log-output.js.map +1 -0
- package/dist/shared/config.d.ts +56 -0
- package/dist/shared/config.d.ts.map +1 -0
- package/dist/shared/config.js +290 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/errors.d.ts +139 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/errors.js +406 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/events/factory.d.ts +143 -0
- package/dist/shared/events/factory.d.ts.map +1 -0
- package/dist/shared/events/factory.js +351 -0
- package/dist/shared/events/factory.js.map +1 -0
- package/dist/shared/events/index.d.ts +6 -0
- package/dist/shared/events/index.d.ts.map +1 -0
- package/dist/shared/events/index.js +3 -0
- package/dist/shared/events/index.js.map +1 -0
- package/dist/shared/events/serialize.d.ts +87 -0
- package/dist/shared/events/serialize.d.ts.map +1 -0
- package/dist/shared/events/serialize.js +510 -0
- package/dist/shared/events/serialize.js.map +1 -0
- package/dist/shared/events/types.d.ts +139 -0
- package/dist/shared/events/types.d.ts.map +1 -0
- package/dist/shared/events/types.js +2 -0
- package/dist/shared/events/types.js.map +1 -0
- package/dist/shared/harvest/event-buffer.d.ts +59 -0
- package/dist/shared/harvest/event-buffer.d.ts.map +1 -0
- package/dist/shared/harvest/event-buffer.js +100 -0
- package/dist/shared/harvest/event-buffer.js.map +1 -0
- package/dist/shared/harvest/harvest-scheduler.d.ts +200 -0
- package/dist/shared/harvest/harvest-scheduler.d.ts.map +1 -0
- package/dist/shared/harvest/harvest-scheduler.js +647 -0
- package/dist/shared/harvest/harvest-scheduler.js.map +1 -0
- package/dist/shared/harvest/index.d.ts +7 -0
- package/dist/shared/harvest/index.d.ts.map +1 -0
- package/dist/shared/harvest/index.js +4 -0
- package/dist/shared/harvest/index.js.map +1 -0
- package/dist/shared/harvest/metric-aggregator.d.ts +115 -0
- package/dist/shared/harvest/metric-aggregator.d.ts.map +1 -0
- package/dist/shared/harvest/metric-aggregator.js +247 -0
- package/dist/shared/harvest/metric-aggregator.js.map +1 -0
- package/dist/shared/index.d.ts +22 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +13 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/logger.d.ts +57 -0
- package/dist/shared/logger.d.ts.map +1 -0
- package/dist/shared/logger.js +166 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/pricing-data.d.ts +4 -0
- package/dist/shared/pricing-data.d.ts.map +1 -0
- package/dist/shared/pricing-data.js +473 -0
- package/dist/shared/pricing-data.js.map +1 -0
- package/dist/shared/pricing.d.ts +148 -0
- package/dist/shared/pricing.d.ts.map +1 -0
- package/dist/shared/pricing.js +528 -0
- package/dist/shared/pricing.js.map +1 -0
- package/dist/shared/redact.d.ts +33 -0
- package/dist/shared/redact.d.ts.map +1 -0
- package/dist/shared/redact.js +110 -0
- package/dist/shared/redact.js.map +1 -0
- package/dist/shared/timing.d.ts +96 -0
- package/dist/shared/timing.d.ts.map +1 -0
- package/dist/shared/timing.js +173 -0
- package/dist/shared/timing.js.map +1 -0
- package/dist/shared/tokens.d.ts +145 -0
- package/dist/shared/tokens.d.ts.map +1 -0
- package/dist/shared/tokens.js +492 -0
- package/dist/shared/tokens.js.map +1 -0
- package/dist/shared/transport/events-api.d.ts +14 -0
- package/dist/shared/transport/events-api.d.ts.map +1 -0
- package/dist/shared/transport/events-api.js +29 -0
- package/dist/shared/transport/events-api.js.map +1 -0
- package/dist/shared/transport/http-client.d.ts +49 -0
- package/dist/shared/transport/http-client.d.ts.map +1 -0
- package/dist/shared/transport/http-client.js +381 -0
- package/dist/shared/transport/http-client.js.map +1 -0
- package/dist/shared/transport/index.d.ts +10 -0
- package/dist/shared/transport/index.d.ts.map +1 -0
- package/dist/shared/transport/index.js +6 -0
- package/dist/shared/transport/index.js.map +1 -0
- package/dist/shared/transport/logs-api.d.ts +29 -0
- package/dist/shared/transport/logs-api.d.ts.map +1 -0
- package/dist/shared/transport/logs-api.js +40 -0
- package/dist/shared/transport/logs-api.js.map +1 -0
- package/dist/shared/transport/metric-api.d.ts +9 -0
- package/dist/shared/transport/metric-api.d.ts.map +1 -0
- package/dist/shared/transport/metric-api.js +39 -0
- package/dist/shared/transport/metric-api.js.map +1 -0
- package/dist/shared/transport/otlp-event-bridge.d.ts +22 -0
- package/dist/shared/transport/otlp-event-bridge.d.ts.map +1 -0
- package/dist/shared/transport/otlp-event-bridge.js +50 -0
- package/dist/shared/transport/otlp-event-bridge.js.map +1 -0
- package/dist/shared/transport/otlp-shared.d.ts +14 -0
- package/dist/shared/transport/otlp-shared.d.ts.map +1 -0
- package/dist/shared/transport/otlp-shared.js +49 -0
- package/dist/shared/transport/otlp-shared.js.map +1 -0
- package/dist/shared/transport/otlp-transport.d.ts +58 -0
- package/dist/shared/transport/otlp-transport.d.ts.map +1 -0
- package/dist/shared/transport/otlp-transport.js +236 -0
- package/dist/shared/transport/otlp-transport.js.map +1 -0
- package/dist/shared/transport/types.d.ts +129 -0
- package/dist/shared/transport/types.d.ts.map +1 -0
- package/dist/shared/transport/types.js +2 -0
- package/dist/shared/transport/types.js.map +1 -0
- package/dist/shared/version.d.ts +2 -0
- package/dist/shared/version.d.ts.map +1 -0
- package/dist/shared/version.js +2 -0
- package/dist/shared/version.js.map +1 -0
- package/dist/storage/index.d.ts +7 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +4 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/local-store.d.ts +153 -0
- package/dist/storage/local-store.d.ts.map +1 -0
- package/dist/storage/local-store.js +719 -0
- package/dist/storage/local-store.js.map +1 -0
- package/dist/storage/retention.d.ts +2 -0
- package/dist/storage/retention.d.ts.map +1 -0
- package/dist/storage/retention.js +53 -0
- package/dist/storage/retention.js.map +1 -0
- package/dist/storage/session-store.d.ts +97 -0
- package/dist/storage/session-store.d.ts.map +1 -0
- package/dist/storage/session-store.js +391 -0
- package/dist/storage/session-store.js.map +1 -0
- package/dist/storage/types.d.ts +64 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/storage/weekly-summary.d.ts +61 -0
- package/dist/storage/weekly-summary.d.ts.map +1 -0
- package/dist/storage/weekly-summary.js +243 -0
- package/dist/storage/weekly-summary.js.map +1 -0
- package/dist/tools/analytics-tools.d.ts +101 -0
- package/dist/tools/analytics-tools.d.ts.map +1 -0
- package/dist/tools/analytics-tools.js +71 -0
- package/dist/tools/analytics-tools.js.map +1 -0
- package/dist/tools/cost-tools.d.ts +121 -0
- package/dist/tools/cost-tools.d.ts.map +1 -0
- package/dist/tools/cost-tools.js +174 -0
- package/dist/tools/cost-tools.js.map +1 -0
- package/dist/tools/cross-session-tools.d.ts +376 -0
- package/dist/tools/cross-session-tools.d.ts.map +1 -0
- package/dist/tools/cross-session-tools.js +820 -0
- package/dist/tools/cross-session-tools.js.map +1 -0
- package/dist/tools/extended-analytics-tools.d.ts +164 -0
- package/dist/tools/extended-analytics-tools.d.ts.map +1 -0
- package/dist/tools/extended-analytics-tools.js +121 -0
- package/dist/tools/extended-analytics-tools.js.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/session-stats.d.ts +162 -0
- package/dist/tools/session-stats.d.ts.map +1 -0
- package/dist/tools/session-stats.js +1054 -0
- package/dist/tools/session-stats.js.map +1 -0
- package/dist/tools/workflow-tools.d.ts +126 -0
- package/dist/tools/workflow-tools.d.ts.map +1 -0
- package/dist/tools/workflow-tools.js +274 -0
- package/dist/tools/workflow-tools.js.map +1 -0
- package/dist/tracing/mcp-tracer.d.ts +4 -0
- package/dist/tracing/mcp-tracer.d.ts.map +1 -0
- package/dist/tracing/mcp-tracer.js +14 -0
- package/dist/tracing/mcp-tracer.js.map +1 -0
- package/dist/tracing/session-span.d.ts +14 -0
- package/dist/tracing/session-span.d.ts.map +1 -0
- package/dist/tracing/session-span.js +53 -0
- package/dist/tracing/session-span.js.map +1 -0
- package/dist/tracing/task-span-tracker.d.ts +11 -0
- package/dist/tracing/task-span-tracker.d.ts.map +1 -0
- package/dist/tracing/task-span-tracker.js +59 -0
- package/dist/tracing/task-span-tracker.js.map +1 -0
- package/dist/tracing/tool-call-span.d.ts +4 -0
- package/dist/tracing/tool-call-span.d.ts.map +1 -0
- package/dist/tracing/tool-call-span.js +60 -0
- package/dist/tracing/tool-call-span.js.map +1 -0
- package/dist/transport/index.d.ts +3 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +2 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/log-ingest.d.ts +42 -0
- package/dist/transport/log-ingest.d.ts.map +1 -0
- package/dist/transport/log-ingest.js +151 -0
- package/dist/transport/log-ingest.js.map +1 -0
- package/dist/transport/nr-ingest.d.ts +171 -0
- package/dist/transport/nr-ingest.d.ts.map +1 -0
- package/dist/transport/nr-ingest.js +659 -0
- package/dist/transport/nr-ingest.js.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/web/assets/index-BrL281N-.css +2 -0
- package/dist/web/assets/index-CcaYZzXm.js +42 -0
- package/dist/web/favicon.svg +15 -0
- package/dist/web/index.html +15 -0
- package/examples/local-alert-rules.json +106 -0
- 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
|