@newrelic/preflight 1.67.3 → 1.69.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/README.md +2 -2
- package/dist/config.d.ts +75 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +201 -22
- package/dist/config.js.map +1 -1
- package/dist/dashboard/routes/api-handler.d.ts +1 -0
- package/dist/dashboard/routes/api-handler.d.ts.map +1 -1
- package/dist/dashboard/routes/api-handler.js +120 -94
- package/dist/dashboard/routes/api-handler.js.map +1 -1
- package/dist/data/dashboards/ai-coding-assistant-adoption-cost.json +291 -1
- package/dist/data/dashboards/ai-coding-assistant-team-view.json +6 -6
- package/dist/hooks/billed-turn-ledger.d.ts +46 -0
- package/dist/hooks/billed-turn-ledger.d.ts.map +1 -0
- package/dist/hooks/billed-turn-ledger.js +274 -0
- package/dist/hooks/billed-turn-ledger.js.map +1 -0
- package/dist/hooks/collector-script.d.ts.map +1 -1
- package/dist/hooks/collector-script.js +5 -1
- package/dist/hooks/collector-script.js.map +1 -1
- package/dist/hooks/event-processor.d.ts +3 -7
- package/dist/hooks/event-processor.d.ts.map +1 -1
- package/dist/hooks/event-processor.js +21 -7
- package/dist/hooks/event-processor.js.map +1 -1
- package/dist/hooks/parent-transcript-watcher.d.ts +6 -5
- package/dist/hooks/parent-transcript-watcher.d.ts.map +1 -1
- package/dist/hooks/parent-transcript-watcher.js +64 -62
- package/dist/hooks/parent-transcript-watcher.js.map +1 -1
- package/dist/hooks/throttled-warning.d.ts +16 -0
- package/dist/hooks/throttled-warning.d.ts.map +1 -0
- package/dist/hooks/throttled-warning.js +32 -0
- package/dist/hooks/throttled-warning.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +90 -83
- package/dist/index.js.map +1 -1
- package/dist/install/cli.d.ts.map +1 -1
- package/dist/install/cli.js +69 -1
- package/dist/install/cli.js.map +1 -1
- package/dist/install/dashboard-health.js +1 -1
- package/dist/install/dashboard-health.js.map +1 -1
- package/dist/install/diagnostics.d.ts +2 -0
- package/dist/install/diagnostics.d.ts.map +1 -1
- package/dist/install/diagnostics.js +132 -28
- package/dist/install/diagnostics.js.map +1 -1
- package/dist/install/headless-install.d.ts +26 -0
- package/dist/install/headless-install.d.ts.map +1 -1
- package/dist/install/headless-install.js +44 -1
- package/dist/install/headless-install.js.map +1 -1
- package/dist/install/install-helper.d.ts +2 -0
- package/dist/install/install-helper.d.ts.map +1 -1
- package/dist/install/install-helper.js +5 -0
- package/dist/install/install-helper.js.map +1 -1
- package/dist/install/keychain.d.ts +18 -0
- package/dist/install/keychain.d.ts.map +1 -0
- package/dist/install/keychain.js +60 -0
- package/dist/install/keychain.js.map +1 -0
- package/dist/install/kiro-install-helper.d.ts +27 -0
- package/dist/install/kiro-install-helper.d.ts.map +1 -0
- package/dist/install/kiro-install-helper.js +53 -0
- package/dist/install/kiro-install-helper.js.map +1 -0
- package/dist/install/setup-wizard.d.ts +4 -0
- package/dist/install/setup-wizard.d.ts.map +1 -1
- package/dist/install/setup-wizard.js +94 -9
- package/dist/install/setup-wizard.js.map +1 -1
- package/dist/lib/child-env.d.ts +11 -0
- package/dist/lib/child-env.d.ts.map +1 -0
- package/dist/lib/child-env.js +35 -0
- package/dist/lib/child-env.js.map +1 -0
- package/dist/lib/git-remote.d.ts +7 -0
- package/dist/lib/git-remote.d.ts.map +1 -1
- package/dist/lib/git-remote.js +13 -0
- package/dist/lib/git-remote.js.map +1 -1
- package/dist/metrics/cost-tracker-seed.d.ts +16 -0
- package/dist/metrics/cost-tracker-seed.d.ts.map +1 -1
- package/dist/metrics/cost-tracker-seed.js +42 -7
- package/dist/metrics/cost-tracker-seed.js.map +1 -1
- package/dist/metrics/cost-tracker.d.ts +11 -3
- package/dist/metrics/cost-tracker.d.ts.map +1 -1
- package/dist/metrics/cost-tracker.js +4 -1
- package/dist/metrics/cost-tracker.js.map +1 -1
- package/dist/metrics/git-activity-recorder.d.ts +3 -3
- package/dist/metrics/git-activity-recorder.d.ts.map +1 -1
- package/dist/metrics/git-activity-recorder.js +3 -3
- package/dist/metrics/git-activity-recorder.js.map +1 -1
- package/dist/metrics/git-workspace-identity.d.ts +6 -1
- package/dist/metrics/git-workspace-identity.d.ts.map +1 -1
- package/dist/metrics/git-workspace-identity.js +56 -32
- package/dist/metrics/git-workspace-identity.js.map +1 -1
- package/dist/metrics/git-workspace-reporter.d.ts +4 -3
- package/dist/metrics/git-workspace-reporter.d.ts.map +1 -1
- package/dist/metrics/git-workspace-reporter.js +4 -3
- package/dist/metrics/git-workspace-reporter.js.map +1 -1
- package/dist/metrics/pricing-init.d.ts +13 -0
- package/dist/metrics/pricing-init.d.ts.map +1 -0
- package/dist/metrics/pricing-init.js +19 -0
- package/dist/metrics/pricing-init.js.map +1 -0
- package/dist/metrics/skill-activation-tracker.d.ts +27 -0
- package/dist/metrics/skill-activation-tracker.d.ts.map +1 -0
- package/dist/metrics/skill-activation-tracker.js +92 -0
- package/dist/metrics/skill-activation-tracker.js.map +1 -0
- package/dist/metrics/turn-cost-attributor.d.ts +34 -16
- package/dist/metrics/turn-cost-attributor.d.ts.map +1 -1
- package/dist/metrics/turn-cost-attributor.js +95 -60
- package/dist/metrics/turn-cost-attributor.js.map +1 -1
- package/dist/proxy/proxy-manager.d.ts +3 -0
- package/dist/proxy/proxy-manager.d.ts.map +1 -1
- package/dist/proxy/proxy-manager.js +43 -3
- package/dist/proxy/proxy-manager.js.map +1 -1
- package/dist/proxy/types.d.ts +7 -0
- package/dist/proxy/types.d.ts.map +1 -1
- package/dist/proxy/types.js.map +1 -1
- package/dist/proxy/upstream-http.d.ts.map +1 -1
- package/dist/proxy/upstream-http.js +44 -1
- package/dist/proxy/upstream-http.js.map +1 -1
- package/dist/proxy/upstream-stdio.d.ts +0 -1
- package/dist/proxy/upstream-stdio.d.ts.map +1 -1
- package/dist/proxy/upstream-stdio.js +2 -26
- package/dist/proxy/upstream-stdio.js.map +1 -1
- package/dist/redaction-patterns.d.ts.map +1 -1
- package/dist/redaction-patterns.js +1 -0
- package/dist/redaction-patterns.js.map +1 -1
- package/dist/secret-command.d.ts +59 -0
- package/dist/secret-command.d.ts.map +1 -0
- package/dist/secret-command.js +123 -0
- package/dist/secret-command.js.map +1 -0
- package/dist/security/audit-trail.d.ts +7 -1
- package/dist/security/audit-trail.d.ts.map +1 -1
- package/dist/security/audit-trail.js +24 -0
- package/dist/security/audit-trail.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +9 -4
- package/dist/server.js.map +1 -1
- package/dist/shared/pricing-data.d.ts.map +1 -1
- package/dist/shared/pricing-data.js +223 -32
- package/dist/shared/pricing-data.js.map +1 -1
- package/dist/shared/pricing.d.ts +25 -3
- package/dist/shared/pricing.d.ts.map +1 -1
- package/dist/shared/pricing.js +46 -9
- package/dist/shared/pricing.js.map +1 -1
- package/dist/shared/tokens.d.ts +16 -0
- package/dist/shared/tokens.d.ts.map +1 -1
- package/dist/shared/tokens.js +18 -0
- package/dist/shared/tokens.js.map +1 -1
- package/dist/shared/transport/otlp-shared.d.ts.map +1 -1
- package/dist/shared/transport/otlp-shared.js +7 -14
- package/dist/shared/transport/otlp-shared.js.map +1 -1
- package/dist/storage/local-store.d.ts +6 -4
- package/dist/storage/local-store.d.ts.map +1 -1
- package/dist/storage/local-store.js +21 -7
- package/dist/storage/local-store.js.map +1 -1
- package/dist/storage/session-store.d.ts +24 -1
- package/dist/storage/session-store.d.ts.map +1 -1
- package/dist/storage/session-store.js +38 -7
- package/dist/storage/session-store.js.map +1 -1
- package/dist/storage/types.d.ts +13 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/tools/cross-session-tools.d.ts +7 -1
- package/dist/tools/cross-session-tools.d.ts.map +1 -1
- package/dist/tools/cross-session-tools.js +47 -14
- package/dist/tools/cross-session-tools.js.map +1 -1
- package/dist/tools/session-stats.d.ts +6 -0
- package/dist/tools/session-stats.d.ts.map +1 -1
- package/dist/tools/session-stats.js +3 -2
- package/dist/tools/session-stats.js.map +1 -1
- package/dist/transport/audit-record-sender.d.ts +20 -0
- package/dist/transport/audit-record-sender.d.ts.map +1 -0
- package/dist/transport/audit-record-sender.js +54 -0
- package/dist/transport/audit-record-sender.js.map +1 -0
- package/dist/transport/nr-ingest.d.ts +7 -0
- package/dist/transport/nr-ingest.d.ts.map +1 -1
- package/dist/transport/nr-ingest.js +64 -27
- package/dist/transport/nr-ingest.js.map +1 -1
- package/dist/transport/tier-types.d.ts +11 -1
- package/dist/transport/tier-types.d.ts.map +1 -1
- package/dist/transport/tier-types.js +28 -6
- package/dist/transport/tier-types.js.map +1 -1
- package/dist/transport/unforwarded-session-monitor.d.ts +12 -6
- package/dist/transport/unforwarded-session-monitor.d.ts.map +1 -1
- package/dist/transport/unforwarded-session-monitor.js +24 -5
- package/dist/transport/unforwarded-session-monitor.js.map +1 -1
- package/dist/web/assets/{index-lvdXUntr.js → index-U9DPZ-Tb.js} +3 -3
- package/dist/web/index.html +1 -1
- package/examples/local-alert-rules.json +28 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ See cost breakdown, efficiency scoring, anti-patterns, and live session tracking
|
|
|
63
63
|
- **Session Detail** — deep-dive into a single session's tool calls
|
|
64
64
|
- **Team View** — aggregated cost and efficiency across developers
|
|
65
65
|
- **Manager View** — high-level team metrics, no tool-call content
|
|
66
|
-
- **Adoption & Cost** — adoption momentum, spend, MCP usage, and per-developer outcomes for engineering managers
|
|
66
|
+
- **Adoption & Cost** — adoption momentum, spend, skill and MCP usage, and per-developer outcomes for engineering managers
|
|
67
67
|
- **Platform Comparison** — Claude Code vs. Cursor vs. Windsurf, etc.
|
|
68
68
|
- **Security Audit** — audit trail of sensitive file access
|
|
69
69
|
|
|
@@ -77,7 +77,7 @@ See cost breakdown, efficiency scoring, anti-patterns, and live session tracking
|
|
|
77
77
|
npm install -g @newrelic/preflight
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
> **Using [Smithery](https://smithery.ai)?** Installing Preflight from the Smithery MCP registry wires up the MCP server for you, but Smithery has no mechanism to write Claude Code hooks. After install, ask Claude Code to call the `nr_observe_install_hooks` MCP tool
|
|
80
|
+
> **Using [Smithery](https://smithery.ai)?** Installing Preflight from the Smithery MCP registry wires up the MCP server for you, but Smithery has no mechanism to write Claude Code hooks. After install, either run `preflight install` in a terminal, or set `"allowHeadlessInstall": true` in your Preflight config (or `NEW_RELIC_AI_ALLOW_HEADLESS_INSTALL=true`) and ask Claude Code to call the `nr_observe_install_hooks` MCP tool. The tool refuses by default, because any connected MCP client can call it. With the flag on, it offers to do this automatically once `nr_observe_health` reports `setup_required: true`. Then restart Claude Code to activate monitoring.
|
|
81
81
|
|
|
82
82
|
### 2. Run setup
|
|
83
83
|
|
package/dist/config.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { CliOptions } from './types.js';
|
|
|
4
4
|
import type { UpstreamConfig } from './proxy/types.js';
|
|
5
5
|
import type { PersonalAlertThresholds } from './alerts/types.js';
|
|
6
6
|
import type { ResolvedTier } from './transport/tier-types.js';
|
|
7
|
+
import { SecretCommandError } from './secret-command.js';
|
|
8
|
+
import type { SecretSources } from './secret-command.js';
|
|
7
9
|
export declare const VALID_MODES: readonly ["cloud", "local", "both"];
|
|
8
10
|
export type Mode = (typeof VALID_MODES)[number];
|
|
9
11
|
export type CompanionModeSource = 'env NR_AI_COMPANION_MODE' | 'config file' | 'detected Claude Code OTel export' | 'default';
|
|
@@ -11,6 +13,16 @@ export interface ResolvedCompanionMode {
|
|
|
11
13
|
readonly value: boolean;
|
|
12
14
|
readonly source: CompanionModeSource;
|
|
13
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Detects the setup where companion mode is off yet Claude Code's cost metrics
|
|
18
|
+
* reach New Relic through Preflight's receiver, so New Relic counts cost twice.
|
|
19
|
+
* Deliberately imprecise, and used only for a `preflight doctor` warning:
|
|
20
|
+
* auto-enabling companion mode on this signal would drop cost from New Relic
|
|
21
|
+
* whenever the proxy is not actually forwarding.
|
|
22
|
+
*/
|
|
23
|
+
export declare function findReceiverDoubleCount(env: NodeJS.ProcessEnv, config: Pick<McpServerConfig, 'otlp' | 'companionMode'>): {
|
|
24
|
+
readonly grpc: boolean;
|
|
25
|
+
} | null;
|
|
14
26
|
/**
|
|
15
27
|
* Companion mode only prevents double-counting when Claude Code's own OTel
|
|
16
28
|
* metrics also land in New Relic. Detection requires an nr-data.net endpoint:
|
|
@@ -43,6 +55,14 @@ export interface McpServerConfig {
|
|
|
43
55
|
* (session_id === OTel's session.id). See docs/ADVANCED.md § Companion mode.
|
|
44
56
|
*/
|
|
45
57
|
readonly companionMode: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Lets the `nr_observe_install_hooks` MCP tool rewrite ~/.claude/settings.json.
|
|
60
|
+
* Default false: any connected MCP client can call the tool, so it is opt-in.
|
|
61
|
+
* See docs/ADVANCED.md § allowHeadlessInstall.
|
|
62
|
+
*/
|
|
63
|
+
readonly allowHeadlessInstall: boolean;
|
|
64
|
+
/** Source of the resolved companionMode value (env, config file, detected, or default). */
|
|
65
|
+
readonly companionModeSource: CompanionModeSource;
|
|
46
66
|
readonly redactionPatterns: readonly RegExp[];
|
|
47
67
|
readonly hookBufferPath: string;
|
|
48
68
|
readonly storagePath: string;
|
|
@@ -58,6 +78,8 @@ export interface McpServerConfig {
|
|
|
58
78
|
readonly collectorHost: string | null;
|
|
59
79
|
readonly proxyUpstreams: readonly UpstreamConfig[];
|
|
60
80
|
readonly nrApiKey: string | null;
|
|
81
|
+
/** Which source supplied each secret. Never the value. */
|
|
82
|
+
readonly secretSources: SecretSources;
|
|
61
83
|
/** Path to a custom pricing JSON file, applied via `initPricing()`. See `NEW_RELIC_AI_CUSTOM_PRICING_FILE`. */
|
|
62
84
|
readonly customPricingFile: string | null;
|
|
63
85
|
/**
|
|
@@ -154,6 +176,7 @@ export interface McpServerConfig {
|
|
|
154
176
|
}
|
|
155
177
|
export declare const DEFAULT_STORAGE_PATH: string;
|
|
156
178
|
export type PlatformTarget = 'native' | 'wsl-windows-cc' | 'wsl-linux-cc';
|
|
179
|
+
export declare const SecretCommandSchema: z.ZodArray<z.ZodString>;
|
|
157
180
|
export declare const ConfigFileSchema: z.ZodObject<{
|
|
158
181
|
configVersion: z.ZodOptional<z.ZodNumber>;
|
|
159
182
|
licenseKey: z.ZodOptional<z.ZodString>;
|
|
@@ -171,6 +194,7 @@ export declare const ConfigFileSchema: z.ZodObject<{
|
|
|
171
194
|
recordContent: z.ZodOptional<z.ZodBoolean>;
|
|
172
195
|
pricingRefresh: z.ZodOptional<z.ZodBoolean>;
|
|
173
196
|
companionMode: z.ZodOptional<z.ZodBoolean>;
|
|
197
|
+
allowHeadlessInstall: z.ZodOptional<z.ZodBoolean>;
|
|
174
198
|
storagePath: z.ZodOptional<z.ZodString>;
|
|
175
199
|
hookBufferPath: z.ZodOptional<z.ZodString>;
|
|
176
200
|
harvestEventsMs: z.ZodOptional<z.ZodNumber>;
|
|
@@ -189,6 +213,9 @@ export declare const ConfigFileSchema: z.ZodObject<{
|
|
|
189
213
|
proxyUpstreams: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
190
214
|
tiers: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
191
215
|
nrApiKey: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
216
|
+
licenseKeyCommand: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
217
|
+
nrApiKeyCommand: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
218
|
+
digestWebhookUrlCommand: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
192
219
|
customPricingFile: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
193
220
|
costRateMultiplier: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
194
221
|
dataResidencyPremium: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -273,7 +300,40 @@ export declare class MissingCloudCredentialError extends Error {
|
|
|
273
300
|
readonly mode: Mode;
|
|
274
301
|
constructor(missingField: 'licenseKey' | 'accountId', mode: Mode, configFilePath: string);
|
|
275
302
|
}
|
|
276
|
-
|
|
303
|
+
/**
|
|
304
|
+
* A `SecretCommandError` raised while resolving a secret that cloud export
|
|
305
|
+
* needs. It keeps the helper's own message for stdio users and adds the mode
|
|
306
|
+
* the config asked for, so `--local` can tell a failing helper from a missing
|
|
307
|
+
* credential and still fall back to local mode.
|
|
308
|
+
*/
|
|
309
|
+
export declare class CloudSecretCommandError extends SecretCommandError {
|
|
310
|
+
readonly mode: Mode;
|
|
311
|
+
constructor(original: SecretCommandError, mode: Mode);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* The startup-resolved digest webhook URL when a source that outranks the
|
|
315
|
+
* file's `digestWebhookUrl` (env var or digestWebhookUrlCommand) supplied it.
|
|
316
|
+
* Undefined means "read the file at send time".
|
|
317
|
+
*/
|
|
318
|
+
export declare function digestWebhookUrlOverride(config: Pick<McpServerConfig, 'digestWebhookUrl' | 'secretSources'>): string | undefined;
|
|
319
|
+
/** Which source is overriding the file's `digestWebhookUrl`, if any. */
|
|
320
|
+
export declare function digestWebhookUrlOverrideSource(config: Pick<McpServerConfig, 'digestWebhookUrl' | 'secretSources'>): 'env' | 'command' | undefined;
|
|
321
|
+
export interface LoadMcpConfigOptions {
|
|
322
|
+
/**
|
|
323
|
+
* Run the `*Command` secret helpers. Pass false from callers that poll or
|
|
324
|
+
* only read non-secret fields (dashboard diagnostics, host/port lookup):
|
|
325
|
+
* `secretSources` still reports `command`, but the secrets resolve to
|
|
326
|
+
* undefined/null and `tiers` stays empty unless explicit.
|
|
327
|
+
*/
|
|
328
|
+
readonly resolveSecrets?: boolean;
|
|
329
|
+
/**
|
|
330
|
+
* Skip the `tiers` array entirely. The `--local` fallback sets this after a
|
|
331
|
+
* cloud credential failure: it reloads with `NR_AI_MODE=local`, and a
|
|
332
|
+
* `tiers` array under local mode is otherwise a config error.
|
|
333
|
+
*/
|
|
334
|
+
readonly ignoreTiers?: boolean;
|
|
335
|
+
}
|
|
336
|
+
export declare function loadMcpConfig(cliOptions?: Partial<CliOptions>, loadOptions?: LoadMcpConfigOptions): Readonly<McpServerConfig>;
|
|
277
337
|
export interface ConfigValidationResult {
|
|
278
338
|
readonly filePath: string;
|
|
279
339
|
readonly fileExists: boolean;
|
|
@@ -285,7 +345,7 @@ export interface ConfigValidationResult {
|
|
|
285
345
|
readonly storagePath?: string;
|
|
286
346
|
/** The `companionMode` field from the parsed config, if present and a boolean. */
|
|
287
347
|
readonly companionMode?: boolean;
|
|
288
|
-
/** True when a non-blank licenseKey is present in the parsed config. Raw value is intentionally not exposed. */
|
|
348
|
+
/** True when a non-blank licenseKey or a licenseKeyCommand is present in the parsed config. Raw value is intentionally not exposed. */
|
|
289
349
|
readonly hasLicenseKey: boolean;
|
|
290
350
|
/** Fatal problems that will prevent the MCP server from starting. */
|
|
291
351
|
readonly errors: readonly string[];
|
|
@@ -299,4 +359,17 @@ export interface ConfigValidationResult {
|
|
|
299
359
|
*/
|
|
300
360
|
export declare function validateConfigFile(filePath: string): ConfigValidationResult;
|
|
301
361
|
export declare function redactSensitive(value: string, patterns?: readonly RegExp[]): string;
|
|
362
|
+
/**
|
|
363
|
+
* redactSensitive() for identifiers (project_id, repo_url, repo names), which
|
|
364
|
+
* carry a token only as a whole path segment or a segment's prefix. A match that
|
|
365
|
+
* starts right after a letter or digit is part of an ordinary name
|
|
366
|
+
* (`acme/risk-assessment-…` holds `sk-assessment-…`) and is left alone. The scan
|
|
367
|
+
* resumes one character later, so a token further into that span
|
|
368
|
+
* (`acme/risk-ghp_…`) is still caught.
|
|
369
|
+
*
|
|
370
|
+
* Free-form content keeps redactSensitive()'s plain matching: there a token
|
|
371
|
+
* routinely follows a letter or digit, after a JSON escape (`\nghp_…`), an ANSI
|
|
372
|
+
* colour code (`ESC[32m`) or `%3A`.
|
|
373
|
+
*/
|
|
374
|
+
export declare function redactIdentifier(value: string): string;
|
|
302
375
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAMjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/config.js
CHANGED
|
@@ -9,6 +9,7 @@ import { REDACTION_PATTERNS as DEFAULT_REDACTION_PATTERNS } from './redaction-pa
|
|
|
9
9
|
import { resolveRecordContent } from './record-content-gate.js';
|
|
10
10
|
import { projectIdFromRemote, stripRemoteCredentials } from './lib/git-remote.js';
|
|
11
11
|
import { validateTiers, DEFAULT_TIER_NAME, WILDCARD_EVENT_TYPE } from './transport/tier-types.js';
|
|
12
|
+
import { SecretCommandError, isSecretConfigured, resolveSecret, resolveSecretCommand, } from './secret-command.js';
|
|
12
13
|
const logger = createLogger('mcp-config');
|
|
13
14
|
export const VALID_MODES = ['cloud', 'local', 'both'];
|
|
14
15
|
function otlpEndpointIsNewRelic(endpoint) {
|
|
@@ -22,6 +23,55 @@ function otlpEndpointIsNewRelic(endpoint) {
|
|
|
22
23
|
return false;
|
|
23
24
|
}
|
|
24
25
|
}
|
|
26
|
+
const SDK_DEFAULT_OTLP_HTTP_ENDPOINT = 'http://localhost:4318';
|
|
27
|
+
const SDK_DEFAULT_OTLP_GRPC_ENDPOINT = 'http://localhost:4317';
|
|
28
|
+
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1']);
|
|
29
|
+
const WILDCARD_HOSTS = new Set(['0.0.0.0', '::']);
|
|
30
|
+
function bareHost(host) {
|
|
31
|
+
return host.replace(/^\[|\]$/g, '');
|
|
32
|
+
}
|
|
33
|
+
function reachesReceiverHost(clientHost, bindAddress) {
|
|
34
|
+
const client = bareHost(clientHost);
|
|
35
|
+
const bind = bareHost(bindAddress);
|
|
36
|
+
if (client === bind)
|
|
37
|
+
return true;
|
|
38
|
+
return LOOPBACK_HOSTS.has(client) && (LOOPBACK_HOSTS.has(bind) || WILDCARD_HOSTS.has(bind));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Detects the setup where companion mode is off yet Claude Code's cost metrics
|
|
42
|
+
* reach New Relic through Preflight's receiver, so New Relic counts cost twice.
|
|
43
|
+
* Deliberately imprecise, and used only for a `preflight doctor` warning:
|
|
44
|
+
* auto-enabling companion mode on this signal would drop cost from New Relic
|
|
45
|
+
* whenever the proxy is not actually forwarding.
|
|
46
|
+
*/
|
|
47
|
+
export function findReceiverDoubleCount(env, config) {
|
|
48
|
+
const { receiverEnabled, receiverPort, receiverBindAddress, forwardEndpoint } = config.otlp;
|
|
49
|
+
if (config.companionMode ||
|
|
50
|
+
!receiverEnabled ||
|
|
51
|
+
!otlpEndpointIsNewRelic(forwardEndpoint ?? undefined)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const exporters = (env.OTEL_METRICS_EXPORTER ?? '').split(',').map((e) => e.trim());
|
|
55
|
+
if (parseEnvBool(env.CLAUDE_CODE_ENABLE_TELEMETRY) !== true || !exporters.includes('otlp')) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const protocol = env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL || env.OTEL_EXPORTER_OTLP_PROTOCOL;
|
|
59
|
+
const grpc = protocol === 'grpc';
|
|
60
|
+
const endpoint = env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT ||
|
|
61
|
+
env.OTEL_EXPORTER_OTLP_ENDPOINT ||
|
|
62
|
+
(grpc ? SDK_DEFAULT_OTLP_GRPC_ENDPOINT : SDK_DEFAULT_OTLP_HTTP_ENDPOINT);
|
|
63
|
+
try {
|
|
64
|
+
const url = new URL(endpoint.includes('://') ? endpoint : `http://${endpoint}`);
|
|
65
|
+
const port = Number(url.port || (url.protocol === 'https:' ? 443 : 80));
|
|
66
|
+
if (port !== receiverPort || !reachesReceiverHost(url.hostname, receiverBindAddress)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return { grpc };
|
|
74
|
+
}
|
|
25
75
|
/**
|
|
26
76
|
* Companion mode only prevents double-counting when Claude Code's own OTel
|
|
27
77
|
* metrics also land in New Relic. Detection requires an nr-data.net endpoint:
|
|
@@ -59,6 +109,9 @@ const DEFAULT_RETAIN_SESSIONS_DAYS = 90;
|
|
|
59
109
|
* rather than a breaking rename — so this has never moved past 1.
|
|
60
110
|
*/
|
|
61
111
|
const CURRENT_CONFIG_VERSION = 1;
|
|
112
|
+
// argv array only: a single string would invite shell-style quoting that
|
|
113
|
+
// Preflight never interprets, since the command runs without a shell.
|
|
114
|
+
export const SecretCommandSchema = z.array(z.string().min(1)).min(1);
|
|
62
115
|
export const ConfigFileSchema = z
|
|
63
116
|
.object({
|
|
64
117
|
configVersion: z.number().int().optional(),
|
|
@@ -77,6 +130,7 @@ export const ConfigFileSchema = z
|
|
|
77
130
|
recordContent: z.boolean().optional(),
|
|
78
131
|
pricingRefresh: z.boolean().optional(),
|
|
79
132
|
companionMode: z.boolean().optional(),
|
|
133
|
+
allowHeadlessInstall: z.boolean().optional(),
|
|
80
134
|
storagePath: z.string().optional(),
|
|
81
135
|
hookBufferPath: z.string().optional(),
|
|
82
136
|
harvestEventsMs: z.number().optional(),
|
|
@@ -94,6 +148,9 @@ export const ConfigFileSchema = z
|
|
|
94
148
|
// generic union-mismatch issue.
|
|
95
149
|
tiers: z.array(z.unknown()).optional(),
|
|
96
150
|
nrApiKey: z.string().nullable().optional(),
|
|
151
|
+
licenseKeyCommand: SecretCommandSchema.optional(),
|
|
152
|
+
nrApiKeyCommand: SecretCommandSchema.optional(),
|
|
153
|
+
digestWebhookUrlCommand: SecretCommandSchema.optional(),
|
|
97
154
|
customPricingFile: z.string().nullable().optional(),
|
|
98
155
|
costRateMultiplier: z.number().nullable().optional(),
|
|
99
156
|
dataResidencyPremium: z.boolean().optional(),
|
|
@@ -494,7 +551,40 @@ export class MissingCloudCredentialError extends Error {
|
|
|
494
551
|
this.mode = mode;
|
|
495
552
|
}
|
|
496
553
|
}
|
|
497
|
-
|
|
554
|
+
/**
|
|
555
|
+
* A `SecretCommandError` raised while resolving a secret that cloud export
|
|
556
|
+
* needs. It keeps the helper's own message for stdio users and adds the mode
|
|
557
|
+
* the config asked for, so `--local` can tell a failing helper from a missing
|
|
558
|
+
* credential and still fall back to local mode.
|
|
559
|
+
*/
|
|
560
|
+
export class CloudSecretCommandError extends SecretCommandError {
|
|
561
|
+
mode;
|
|
562
|
+
constructor(original, mode) {
|
|
563
|
+
super(original.field, original.failure);
|
|
564
|
+
this.message = original.message;
|
|
565
|
+
this.cause = original;
|
|
566
|
+
this.mode = mode;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* The startup-resolved digest webhook URL when a source that outranks the
|
|
571
|
+
* file's `digestWebhookUrl` (env var or digestWebhookUrlCommand) supplied it.
|
|
572
|
+
* Undefined means "read the file at send time".
|
|
573
|
+
*/
|
|
574
|
+
export function digestWebhookUrlOverride(config) {
|
|
575
|
+
return digestWebhookUrlOverrideSource(config)
|
|
576
|
+
? (config.digestWebhookUrl ?? undefined)
|
|
577
|
+
: undefined;
|
|
578
|
+
}
|
|
579
|
+
/** Which source is overriding the file's `digestWebhookUrl`, if any. */
|
|
580
|
+
export function digestWebhookUrlOverrideSource(config) {
|
|
581
|
+
const source = config.secretSources.digestWebhookUrl;
|
|
582
|
+
return (source === 'env' || source === 'command') && config.digestWebhookUrl !== null
|
|
583
|
+
? source
|
|
584
|
+
: undefined;
|
|
585
|
+
}
|
|
586
|
+
export function loadMcpConfig(cliOptions, loadOptions = {}) {
|
|
587
|
+
const skipCommand = loadOptions.resolveSecrets === false;
|
|
498
588
|
const configFilePath = cliOptions?.config ?? resolve(DEFAULT_STORAGE_PATH, 'config.json');
|
|
499
589
|
const file = loadConfigFile(configFilePath);
|
|
500
590
|
// Warn (don't crash) when the user's config file contains keys we don't
|
|
@@ -578,11 +668,17 @@ export function loadMcpConfig(cliOptions) {
|
|
|
578
668
|
});
|
|
579
669
|
}
|
|
580
670
|
}
|
|
581
|
-
// --- licenseKey: CLI has no flag for this, so env > file ---
|
|
582
|
-
//
|
|
583
|
-
// config must fail closed instead of silently
|
|
584
|
-
|
|
585
|
-
|
|
671
|
+
// --- licenseKey: CLI has no flag for this, so env > licenseKeyCommand > file ---
|
|
672
|
+
// Presence is checked ahead of mode resolution: an ambiguous
|
|
673
|
+
// licenseKey-without-mode config must fail closed instead of silently
|
|
674
|
+
// defaulting to 'cloud'. The command itself runs only after mode resolves,
|
|
675
|
+
// so a local-mode process never runs it.
|
|
676
|
+
const licenseKeySources = {
|
|
677
|
+
env: process.env.NEW_RELIC_LICENSE_KEY,
|
|
678
|
+
command: file.licenseKeyCommand,
|
|
679
|
+
file: typeof file.licenseKey === 'string' ? file.licenseKey : undefined,
|
|
680
|
+
};
|
|
681
|
+
const licenseKeyConfigured = isSecretConfigured(licenseKeySources);
|
|
586
682
|
// --- Resolve mode early so we can gate licenseKey/accountId requirements ---
|
|
587
683
|
// File mode is already validated by the zod schema in loadConfigFile.
|
|
588
684
|
const isValidMode = (v) => v !== undefined && VALID_MODES.includes(v);
|
|
@@ -598,7 +694,7 @@ export function loadMcpConfig(cliOptions) {
|
|
|
598
694
|
else if (fileMode !== undefined) {
|
|
599
695
|
mode = fileMode;
|
|
600
696
|
}
|
|
601
|
-
else if (
|
|
697
|
+
else if (licenseKeyConfigured) {
|
|
602
698
|
// Local-first default (README): telemetry export is opt-in, so a config
|
|
603
699
|
// with credentials but no explicit mode must not silently export.
|
|
604
700
|
throw new Error('Config has a licenseKey but no explicit mode. Telemetry export is opt-in: set "mode": "cloud" (or "both") in the config file, set NR_AI_MODE, or remove the credentials for local-only use. Previous versions implicitly defaulted to cloud.');
|
|
@@ -606,7 +702,19 @@ export function loadMcpConfig(cliOptions) {
|
|
|
606
702
|
else {
|
|
607
703
|
mode = 'local';
|
|
608
704
|
}
|
|
609
|
-
|
|
705
|
+
let licenseKeySecret;
|
|
706
|
+
try {
|
|
707
|
+
licenseKeySecret = resolveSecret('licenseKey', mode === 'local' ? { ...licenseKeySources, command: undefined } : licenseKeySources, { skipCommand });
|
|
708
|
+
}
|
|
709
|
+
catch (err) {
|
|
710
|
+
if (err instanceof SecretCommandError)
|
|
711
|
+
throw new CloudSecretCommandError(err, mode);
|
|
712
|
+
throw err;
|
|
713
|
+
}
|
|
714
|
+
const licenseKeyRaw = licenseKeySecret.value;
|
|
715
|
+
if (mode !== 'local' &&
|
|
716
|
+
!licenseKeyRaw &&
|
|
717
|
+
!(skipCommand && licenseKeySecret.source === 'command')) {
|
|
610
718
|
throw new MissingCloudCredentialError('licenseKey', mode, configFilePath);
|
|
611
719
|
}
|
|
612
720
|
// Reject the literal string "null" — it's truthy but not a valid key
|
|
@@ -617,8 +725,8 @@ export function loadMcpConfig(cliOptions) {
|
|
|
617
725
|
}
|
|
618
726
|
// In local mode, undefined if licenseKey is missing (NR transport won't be used)
|
|
619
727
|
const licenseKey = licenseKeyRaw;
|
|
620
|
-
// --- accountId: env > file ---
|
|
621
|
-
const accountIdRaw = process.env.NEW_RELIC_ACCOUNT_ID ??
|
|
728
|
+
// --- accountId: env > file; a blank env var counts as unset, as for the secrets ---
|
|
729
|
+
const accountIdRaw = (process.env.NEW_RELIC_ACCOUNT_ID?.trim() ? process.env.NEW_RELIC_ACCOUNT_ID : undefined) ??
|
|
622
730
|
(typeof file.accountId === 'string' ? file.accountId : undefined);
|
|
623
731
|
if (mode !== 'local' && !accountIdRaw) {
|
|
624
732
|
throw new MissingCloudCredentialError('accountId', mode, configFilePath);
|
|
@@ -632,9 +740,7 @@ export function loadMcpConfig(cliOptions) {
|
|
|
632
740
|
// --- tiers: config file only (no env var / CLI flag equivalent exists) ---
|
|
633
741
|
// Fail closed on tiers-under-local BEFORE validating tier shape, so the
|
|
634
742
|
// mode conflict is always the reported error even for a malformed array.
|
|
635
|
-
const rawTiers = Array.isArray(file.tiers)
|
|
636
|
-
? file.tiers
|
|
637
|
-
: undefined;
|
|
743
|
+
const rawTiers = Array.isArray(file.tiers) && !loadOptions.ignoreTiers ? file.tiers : undefined;
|
|
638
744
|
let resolvedTiers;
|
|
639
745
|
if (rawTiers !== undefined) {
|
|
640
746
|
if (mode === 'local') {
|
|
@@ -642,7 +748,24 @@ export function loadMcpConfig(cliOptions) {
|
|
|
642
748
|
'Multi-tier telemetry routing fans out the cloud export path, so it requires ' +
|
|
643
749
|
"mode='cloud' or mode='both'. Remove the tiers array for local-only use, or set the mode.");
|
|
644
750
|
}
|
|
645
|
-
|
|
751
|
+
try {
|
|
752
|
+
resolvedTiers = validateTiers(rawTiers, {
|
|
753
|
+
resolveLicenseKey: skipCommand
|
|
754
|
+
? undefined
|
|
755
|
+
: (tier, argv) => resolveSecretCommand('licenseKey', argv, undefined, tier),
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
catch (err) {
|
|
759
|
+
if (err instanceof SecretCommandError)
|
|
760
|
+
throw new CloudSecretCommandError(err, mode);
|
|
761
|
+
throw err;
|
|
762
|
+
}
|
|
763
|
+
// A tier whose key was never read has no usable destination, so none are
|
|
764
|
+
// reported; the caller asked not to run helpers.
|
|
765
|
+
if (skipCommand &&
|
|
766
|
+
resolvedTiers.some((t) => t.destination.type === 'nr' && t.destination.licenseKey === '')) {
|
|
767
|
+
resolvedTiers = [];
|
|
768
|
+
}
|
|
646
769
|
}
|
|
647
770
|
else if (mode !== 'local' && licenseKey !== undefined && accountId !== undefined) {
|
|
648
771
|
// Implicit single tier — byte-identical to the existing single-tier default.
|
|
@@ -662,8 +785,9 @@ export function loadMcpConfig(cliOptions) {
|
|
|
662
785
|
(typeof file.storagePath === 'string' ? file.storagePath : DEFAULT_STORAGE_PATH);
|
|
663
786
|
// highSecurity must be resolved before recordContent so it can override it
|
|
664
787
|
const highSecurity = envBool('NEW_RELIC_AI_HIGH_SECURITY', typeof file.highSecurity === 'boolean' ? file.highSecurity : false);
|
|
665
|
-
const
|
|
666
|
-
(typeof file.projectId === 'string' ? file.projectId : inferProjectId())
|
|
788
|
+
const rawProjectId = process.env.NEW_RELIC_AI_PROJECT_ID ??
|
|
789
|
+
(typeof file.projectId === 'string' ? file.projectId : inferProjectId());
|
|
790
|
+
const resolvedProjectId = sanitizeOrgField(rawProjectId === null ? null : redactIdentifier(rawProjectId));
|
|
667
791
|
// Dedicated opt-out, independent of projectId's — repo_url can carry an
|
|
668
792
|
// internal git host that org/repo alone doesn't, so it gets its own toggle
|
|
669
793
|
// rather than riding along on projectId's. Defaults on; the setup wizard
|
|
@@ -677,17 +801,32 @@ export function loadMcpConfig(cliOptions) {
|
|
|
677
801
|
const rawRepoUrl = process.env.NEW_RELIC_AI_REPO_URL ??
|
|
678
802
|
(typeof file.repoUrl === 'string' ? file.repoUrl : getGitRemoteUrl());
|
|
679
803
|
// stripRemoteCredentials drops URL userinfo and query strings;
|
|
680
|
-
//
|
|
804
|
+
// redactIdentifier then catches any token elsewhere in the value.
|
|
681
805
|
const strippedRepoUrl = stripRemoteCredentials(rawRepoUrl);
|
|
682
|
-
const resolvedRepoUrl = sanitizeOrgField(strippedRepoUrl === null ? null :
|
|
806
|
+
const resolvedRepoUrl = sanitizeOrgField(strippedRepoUrl === null ? null : redactIdentifier(strippedRepoUrl));
|
|
683
807
|
const resolvedCompanionMode = resolveCompanionMode(process.env, typeof file.companionMode === 'boolean' ? file.companionMode : undefined);
|
|
684
808
|
logger.info('Companion mode resolved', {
|
|
685
809
|
value: resolvedCompanionMode.value,
|
|
686
810
|
source: resolvedCompanionMode.source,
|
|
687
811
|
});
|
|
812
|
+
const nrApiKeySecret = resolveSecret('nrApiKey', {
|
|
813
|
+
env: process.env.NEW_RELIC_API_KEY,
|
|
814
|
+
command: mode === 'local' ? undefined : file.nrApiKeyCommand,
|
|
815
|
+
file: typeof file.nrApiKey === 'string' ? file.nrApiKey : undefined,
|
|
816
|
+
}, { optional: true, skipCommand });
|
|
817
|
+
const digestWebhookUrlSecret = resolveSecret('digestWebhookUrl', {
|
|
818
|
+
env: process.env.NEW_RELIC_AI_DIGEST_WEBHOOK_URL,
|
|
819
|
+
command: file.digestWebhookUrlCommand,
|
|
820
|
+
file: typeof file.digestWebhookUrl === 'string' ? file.digestWebhookUrl : undefined,
|
|
821
|
+
}, { optional: true, skipCommand });
|
|
688
822
|
const config = {
|
|
689
823
|
licenseKey,
|
|
690
824
|
accountId,
|
|
825
|
+
secretSources: {
|
|
826
|
+
licenseKey: licenseKeySecret.source,
|
|
827
|
+
nrApiKey: nrApiKeySecret.source,
|
|
828
|
+
digestWebhookUrl: digestWebhookUrlSecret.source,
|
|
829
|
+
},
|
|
691
830
|
configVersion: typeof file.configVersion === 'number' ? file.configVersion : CURRENT_CONFIG_VERSION,
|
|
692
831
|
appName: process.env.NEW_RELIC_AI_MCP_APP_NAME ??
|
|
693
832
|
(typeof file.appName === 'string' ? file.appName : 'preflight'),
|
|
@@ -707,6 +846,10 @@ export function loadMcpConfig(cliOptions) {
|
|
|
707
846
|
? false
|
|
708
847
|
: envBool('NEW_RELIC_AI_PRICING_REFRESH', typeof file.pricingRefresh === 'boolean' ? file.pricingRefresh : true),
|
|
709
848
|
companionMode: resolvedCompanionMode.value,
|
|
849
|
+
companionModeSource: resolvedCompanionMode.source,
|
|
850
|
+
allowHeadlessInstall: highSecurity
|
|
851
|
+
? false
|
|
852
|
+
: envBool('NEW_RELIC_AI_ALLOW_HEADLESS_INSTALL', typeof file.allowHeadlessInstall === 'boolean' ? file.allowHeadlessInstall : false),
|
|
710
853
|
redactionPatterns: DEFAULT_REDACTION_PATTERNS,
|
|
711
854
|
hookBufferPath: process.env.NEW_RELIC_AI_MCP_BUFFER_PATH ??
|
|
712
855
|
(typeof file.hookBufferPath === 'string'
|
|
@@ -772,7 +915,7 @@ export function loadMcpConfig(cliOptions) {
|
|
|
772
915
|
collectorHost: resolveCollectorHost(licenseKey, process.env.NEW_RELIC_HOST ??
|
|
773
916
|
(typeof file.collectorHost === 'string' ? file.collectorHost : null)),
|
|
774
917
|
proxyUpstreams: parseProxyUpstreams(process.env.NEW_RELIC_AI_MCP_PROXY_UPSTREAMS, file.proxyUpstreams),
|
|
775
|
-
nrApiKey:
|
|
918
|
+
nrApiKey: nrApiKeySecret.value ?? null,
|
|
776
919
|
customPricingFile: process.env.NEW_RELIC_AI_CUSTOM_PRICING_FILE ??
|
|
777
920
|
(typeof file.customPricingFile === 'string' ? file.customPricingFile : null),
|
|
778
921
|
costRateMultiplier: (() => {
|
|
@@ -789,8 +932,7 @@ export function loadMcpConfig(cliOptions) {
|
|
|
789
932
|
: null;
|
|
790
933
|
})(),
|
|
791
934
|
dataResidencyPremium: envBool('NEW_RELIC_AI_DATA_RESIDENCY_PREMIUM', typeof file.dataResidencyPremium === 'boolean' ? file.dataResidencyPremium : false),
|
|
792
|
-
digestWebhookUrl:
|
|
793
|
-
(typeof file.digestWebhookUrl === 'string' ? file.digestWebhookUrl : null),
|
|
935
|
+
digestWebhookUrl: digestWebhookUrlSecret.value ?? null,
|
|
794
936
|
digestSchedule: process.env.NEW_RELIC_AI_DIGEST_SCHEDULE ??
|
|
795
937
|
(typeof file.digestSchedule === 'string' ? file.digestSchedule : '0 9 * * 1'),
|
|
796
938
|
retainSessionsDays: (() => {
|
|
@@ -1177,7 +1319,8 @@ export function validateConfigFile(filePath) {
|
|
|
1177
1319
|
mode: typeof file.mode === 'string' ? file.mode : undefined,
|
|
1178
1320
|
storagePath: typeof file.storagePath === 'string' ? file.storagePath : undefined,
|
|
1179
1321
|
companionMode: typeof file.companionMode === 'boolean' ? file.companionMode : undefined,
|
|
1180
|
-
hasLicenseKey: typeof file.licenseKey === 'string' && file.licenseKey.trim().length > 0
|
|
1322
|
+
hasLicenseKey: (typeof file.licenseKey === 'string' && file.licenseKey.trim().length > 0) ||
|
|
1323
|
+
Array.isArray(file.licenseKeyCommand),
|
|
1181
1324
|
errors,
|
|
1182
1325
|
warnings,
|
|
1183
1326
|
};
|
|
@@ -1224,4 +1367,40 @@ export function redactSensitive(value, patterns) {
|
|
|
1224
1367
|
}
|
|
1225
1368
|
return result;
|
|
1226
1369
|
}
|
|
1370
|
+
const WORD_CHAR_RE = /[A-Za-z0-9]/;
|
|
1371
|
+
/**
|
|
1372
|
+
* redactSensitive() for identifiers (project_id, repo_url, repo names), which
|
|
1373
|
+
* carry a token only as a whole path segment or a segment's prefix. A match that
|
|
1374
|
+
* starts right after a letter or digit is part of an ordinary name
|
|
1375
|
+
* (`acme/risk-assessment-…` holds `sk-assessment-…`) and is left alone. The scan
|
|
1376
|
+
* resumes one character later, so a token further into that span
|
|
1377
|
+
* (`acme/risk-ghp_…`) is still caught.
|
|
1378
|
+
*
|
|
1379
|
+
* Free-form content keeps redactSensitive()'s plain matching: there a token
|
|
1380
|
+
* routinely follows a letter or digit, after a JSON escape (`\nghp_…`), an ANSI
|
|
1381
|
+
* colour code (`ESC[32m`) or `%3A`.
|
|
1382
|
+
*/
|
|
1383
|
+
export function redactIdentifier(value) {
|
|
1384
|
+
let result = value;
|
|
1385
|
+
for (const pattern of DEFAULT_REDACTION_PATTERNS) {
|
|
1386
|
+
const re = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`);
|
|
1387
|
+
let out = '';
|
|
1388
|
+
let last = 0;
|
|
1389
|
+
let match;
|
|
1390
|
+
while ((match = re.exec(result)) !== null) {
|
|
1391
|
+
if (match[0] === '') {
|
|
1392
|
+
re.lastIndex++;
|
|
1393
|
+
continue;
|
|
1394
|
+
}
|
|
1395
|
+
if (match.index > 0 && WORD_CHAR_RE.test(result[match.index - 1])) {
|
|
1396
|
+
re.lastIndex = match.index + 1;
|
|
1397
|
+
continue;
|
|
1398
|
+
}
|
|
1399
|
+
out += `${result.slice(last, match.index)}[REDACTED]`;
|
|
1400
|
+
last = re.lastIndex;
|
|
1401
|
+
}
|
|
1402
|
+
result = out + result.slice(last);
|
|
1403
|
+
}
|
|
1404
|
+
return result;
|
|
1405
|
+
}
|
|
1227
1406
|
//# sourceMappingURL=config.js.map
|