@juspay/neurolink 10.0.0 → 10.1.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.
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /* eslint-disable no-console, curly */
3
2
  /**
4
3
  * NeuroLink CLI Telemetry Commands
5
4
  *
@@ -16,6 +15,7 @@ import { logger } from "../../lib/utils/logger.js";
16
15
  import { NeuroLink } from "../../lib/neurolink.js";
17
16
  import { flushOpenTelemetry } from "../../lib/services/server/ai/observability/instrumentation.js";
18
17
  import { formatRow, formatCost } from "../utils/formatters.js";
18
+ import { redactUrlCredentials } from "../../lib/utils/logSanitize.js";
19
19
  /**
20
20
  * Available exporter names
21
21
  */
@@ -87,54 +87,58 @@ export class TelemetryCommandFactory {
87
87
  try {
88
88
  const neurolink = new NeuroLink();
89
89
  const status = neurolink.getTelemetryStatus();
90
- if (spinner)
90
+ if (spinner) {
91
91
  spinner.succeed("Status retrieved");
92
+ }
92
93
  if (args.format === "json") {
93
- console.log(JSON.stringify(status, null, 2));
94
+ logger.always(JSON.stringify(status, (_key, value) => typeof value === "string" &&
95
+ (_key === "baseUrl" || _key === "endpoint")
96
+ ? redactUrlCredentials(value)
97
+ : value, 2));
94
98
  }
95
99
  else {
96
- console.log("");
97
- console.log(chalk.bold.cyan("=== Telemetry Status ==="));
98
- console.log("");
100
+ logger.always("");
101
+ logger.always(chalk.bold.cyan("=== Telemetry Status ==="));
102
+ logger.always("");
99
103
  // Telemetry enabled status
100
104
  const enabledIcon = status.enabled
101
105
  ? chalk.green("ENABLED")
102
106
  : chalk.red("DISABLED");
103
- console.log(formatRow("Telemetry:", enabledIcon));
107
+ logger.always(formatRow("Telemetry:", enabledIcon));
104
108
  // OpenTelemetry status
105
109
  if (status.openTelemetry) {
106
- console.log("");
107
- console.log(chalk.bold("OpenTelemetry:"));
110
+ logger.always("");
111
+ logger.always(chalk.bold("OpenTelemetry:"));
108
112
  const otelStatus = status.openTelemetry.enabled
109
113
  ? chalk.green("Active")
110
114
  : chalk.gray("Inactive");
111
- console.log(formatRow(" Status:", otelStatus));
115
+ logger.always(formatRow(" Status:", otelStatus));
112
116
  if (status.openTelemetry.endpoint) {
113
- console.log(formatRow(" Endpoint:", status.openTelemetry.endpoint));
117
+ logger.always(formatRow(" Endpoint:", redactUrlCredentials(status.openTelemetry.endpoint)));
114
118
  }
115
119
  if (status.openTelemetry.serviceName) {
116
- console.log(formatRow(" Service:", status.openTelemetry.serviceName));
120
+ logger.always(formatRow(" Service:", status.openTelemetry.serviceName));
117
121
  }
118
122
  }
119
123
  // Langfuse status
120
124
  if (status.langfuse) {
121
- console.log("");
122
- console.log(chalk.bold("Langfuse:"));
125
+ logger.always("");
126
+ logger.always(chalk.bold("Langfuse:"));
123
127
  const lfStatus = status.langfuse.enabled
124
128
  ? chalk.green("Active")
125
129
  : chalk.gray("Inactive");
126
- console.log(formatRow(" Status:", lfStatus));
130
+ logger.always(formatRow(" Status:", lfStatus));
127
131
  if (status.langfuse.baseUrl) {
128
- console.log(formatRow(" URL:", status.langfuse.baseUrl));
132
+ logger.always(formatRow(" URL:", redactUrlCredentials(status.langfuse.baseUrl)));
129
133
  }
130
134
  if (status.langfuse.environment) {
131
- console.log(formatRow(" Environment:", status.langfuse.environment));
135
+ logger.always(formatRow(" Environment:", status.langfuse.environment));
132
136
  }
133
137
  }
134
138
  // Exporters health summary
135
139
  if (status.exporters && status.exporters.length > 0) {
136
- console.log("");
137
- console.log(chalk.bold("Exporter Health:"));
140
+ logger.always("");
141
+ logger.always(chalk.bold("Exporter Health:"));
138
142
  for (const exporter of status.exporters) {
139
143
  const healthIcon = exporter.healthy
140
144
  ? chalk.green("[OK]")
@@ -142,24 +146,25 @@ export class TelemetryCommandFactory {
142
146
  const pendingInfo = exporter.pendingSpans
143
147
  ? chalk.gray(` (${exporter.pendingSpans} pending)`)
144
148
  : "";
145
- console.log(` ${healthIcon} ${exporter.name}${pendingInfo}`);
149
+ logger.always(` ${healthIcon} ${exporter.name}${pendingInfo}`);
146
150
  if (exporter.errors && exporter.errors.length > 0) {
147
151
  for (const error of exporter.errors.slice(0, 2)) {
148
- console.log(chalk.red(` Error: ${error}`));
152
+ logger.always(chalk.red(` Error: ${error}`));
149
153
  }
150
154
  }
151
155
  }
152
156
  }
153
157
  else {
154
- console.log("");
155
- console.log(chalk.gray("No exporters configured."));
158
+ logger.always("");
159
+ logger.always(chalk.gray("No exporters configured."));
156
160
  }
157
- console.log("");
161
+ logger.always("");
158
162
  }
159
163
  }
160
164
  catch (error) {
161
- if (spinner)
165
+ if (spinner) {
162
166
  spinner.fail("Failed to get status");
167
+ }
163
168
  logger.error("Error:", error instanceof Error ? error.message : String(error));
164
169
  process.exit(1);
165
170
  }
@@ -213,33 +218,36 @@ export class TelemetryCommandFactory {
213
218
  config = JSON.parse(args.config);
214
219
  }
215
220
  catch {
216
- if (spinner)
221
+ if (spinner) {
217
222
  spinner.fail("Invalid JSON configuration");
218
- console.log(chalk.red("Error: Configuration must be valid JSON"));
219
- console.log("");
220
- console.log("Example:");
221
- console.log(chalk.gray(` neurolink telemetry configure --exporter langfuse --config '{"publicKey":"pk-...", "secretKey":"sk-..."}'`));
223
+ }
224
+ logger.always(chalk.red("Error: Configuration must be valid JSON"));
225
+ logger.always("");
226
+ logger.always("Example:");
227
+ logger.always(chalk.gray(` neurolink telemetry configure --exporter langfuse --config '{"publicKey":"pk-...", "secretKey":"sk-..."}'`));
222
228
  process.exit(1);
223
229
  }
224
230
  // Validate required fields based on exporter type
225
231
  const validationResult = validateExporterConfig(args.exporter, config);
226
232
  if (!validationResult.valid) {
227
- if (spinner)
233
+ if (spinner) {
228
234
  spinner.fail("Configuration validation failed");
229
- console.log(chalk.red(`Error: ${validationResult.error}`));
230
- console.log("");
231
- console.log(chalk.yellow(`Required fields for ${args.exporter}:`));
235
+ }
236
+ logger.always(chalk.red(`Error: ${validationResult.error}`));
237
+ logger.always("");
238
+ logger.always(chalk.yellow(`Required fields for ${args.exporter}:`));
232
239
  for (const field of validationResult.requiredFields ?? []) {
233
- console.log(chalk.gray(` - ${field}`));
240
+ logger.always(chalk.gray(` - ${field}`));
234
241
  }
235
242
  process.exit(1);
236
243
  }
237
244
  // Currently, exporter configuration is done via environment variables
238
245
  // or SDK initialization. This command provides guidance on how to configure.
239
- if (spinner)
246
+ if (spinner) {
240
247
  spinner.succeed(`${args.exporter} configuration validated`);
248
+ }
241
249
  if (args.format === "json") {
242
- console.log(JSON.stringify({
250
+ logger.always(JSON.stringify({
243
251
  exporter: args.exporter,
244
252
  config: config,
245
253
  valid: true,
@@ -247,32 +255,33 @@ export class TelemetryCommandFactory {
247
255
  }, null, 2));
248
256
  }
249
257
  else {
250
- console.log("");
251
- console.log(chalk.bold.cyan(`=== ${args.exporter} Configuration ===`));
252
- console.log("");
253
- console.log(chalk.green("Configuration validated successfully!"));
254
- console.log("");
255
- console.log(chalk.bold("To apply this configuration:"));
256
- console.log("");
258
+ logger.always("");
259
+ logger.always(chalk.bold.cyan(`=== ${args.exporter} Configuration ===`));
260
+ logger.always("");
261
+ logger.always(chalk.green("Configuration validated successfully!"));
262
+ logger.always("");
263
+ logger.always(chalk.bold("To apply this configuration:"));
264
+ logger.always("");
257
265
  // Show environment variable instructions
258
266
  const envVars = getExporterEnvVars(args.exporter);
259
- console.log(chalk.yellow("Option 1: Set environment variables"));
267
+ logger.always(chalk.yellow("Option 1: Set environment variables"));
260
268
  for (const [key, description] of Object.entries(envVars)) {
261
- console.log(chalk.gray(` export ${key}="<${description}>"`));
269
+ logger.always(chalk.gray(` export ${key}="<${description}>"`));
262
270
  }
263
- console.log("");
264
- console.log(chalk.yellow("Option 2: SDK initialization"));
265
- console.log(chalk.gray(` const neurolink = new NeuroLink({`));
266
- console.log(chalk.gray(` observability: {`));
267
- console.log(chalk.gray(` ${args.exporter}: ${JSON.stringify(config, null, 6).split("\n").join("\n ")}`));
268
- console.log(chalk.gray(` }`));
269
- console.log(chalk.gray(` });`));
270
- console.log("");
271
+ logger.always("");
272
+ logger.always(chalk.yellow("Option 2: SDK initialization"));
273
+ logger.always(chalk.gray(` const neurolink = new NeuroLink({`));
274
+ logger.always(chalk.gray(` observability: {`));
275
+ logger.always(chalk.gray(` ${args.exporter}: ${JSON.stringify(config, null, 6).split("\n").join("\n ")}`));
276
+ logger.always(chalk.gray(` }`));
277
+ logger.always(chalk.gray(` });`));
278
+ logger.always("");
271
279
  }
272
280
  }
273
281
  catch (error) {
274
- if (spinner)
282
+ if (spinner) {
275
283
  spinner.fail("Failed to configure exporter");
284
+ }
276
285
  logger.error("Error:", error instanceof Error ? error.message : String(error));
277
286
  process.exit(1);
278
287
  }
@@ -308,20 +317,21 @@ export class TelemetryCommandFactory {
308
317
  try {
309
318
  const neurolink = new NeuroLink();
310
319
  const status = neurolink.getTelemetryStatus();
311
- if (spinner)
320
+ if (spinner) {
312
321
  spinner.succeed("Exporters listed");
322
+ }
313
323
  const configuredExporters = status.exporters ?? [];
314
324
  const configuredNames = new Set(configuredExporters.map((e) => e.name.toLowerCase()));
315
325
  if (args.format === "json") {
316
- console.log(JSON.stringify({
326
+ logger.always(JSON.stringify({
317
327
  available: AVAILABLE_EXPORTERS,
318
328
  configured: configuredExporters,
319
329
  }, null, 2));
320
330
  }
321
331
  else {
322
- console.log("");
323
- console.log(chalk.bold.cyan("=== Available Exporters ==="));
324
- console.log("");
332
+ logger.always("");
333
+ logger.always(chalk.bold.cyan("=== Available Exporters ==="));
334
+ logger.always("");
325
335
  for (const exporter of AVAILABLE_EXPORTERS) {
326
336
  const isConfigured = configuredNames.has(exporter);
327
337
  const configuredExporter = configuredExporters.find((e) => e.name.toLowerCase() === exporter);
@@ -331,27 +341,28 @@ export class TelemetryCommandFactory {
331
341
  : chalk.yellow("[CONFIGURED]")
332
342
  : chalk.gray("[AVAILABLE]");
333
343
  const description = getExporterDescription(exporter);
334
- console.log(`${statusIcon} ${chalk.bold(exporter)}`);
335
- console.log(chalk.gray(` ${description}`));
344
+ logger.always(`${statusIcon} ${chalk.bold(exporter)}`);
345
+ logger.always(chalk.gray(` ${description}`));
336
346
  if (isConfigured && configuredExporter) {
337
347
  if (configuredExporter.pendingSpans) {
338
- console.log(chalk.gray(` Pending spans: ${configuredExporter.pendingSpans}`));
348
+ logger.always(chalk.gray(` Pending spans: ${configuredExporter.pendingSpans}`));
339
349
  }
340
350
  if (configuredExporter.lastExportTime) {
341
351
  const lastExport = new Date(configuredExporter.lastExportTime);
342
- console.log(chalk.gray(` Last export: ${lastExport.toISOString()}`));
352
+ logger.always(chalk.gray(` Last export: ${lastExport.toISOString()}`));
343
353
  }
344
354
  }
345
- console.log("");
355
+ logger.always("");
346
356
  }
347
- console.log(chalk.bold("Configuration Help:"));
348
- console.log(chalk.gray(" Use 'neurolink telemetry configure --exporter <name> --config <json>' to configure an exporter"));
349
- console.log("");
357
+ logger.always(chalk.bold("Configuration Help:"));
358
+ logger.always(chalk.gray(" Use 'neurolink telemetry configure --exporter <name> --config <json>' to configure an exporter"));
359
+ logger.always("");
350
360
  }
351
361
  }
352
362
  catch (error) {
353
- if (spinner)
363
+ if (spinner) {
354
364
  spinner.fail("Failed to list exporters");
365
+ }
355
366
  logger.error("Error:", error instanceof Error ? error.message : String(error));
356
367
  process.exit(1);
357
368
  }
@@ -397,21 +408,27 @@ export class TelemetryCommandFactory {
397
408
  // Count pending spans before flush
398
409
  const pendingBefore = statusBefore.exporters?.reduce((sum, e) => sum + (e.pendingSpans ?? 0), 0) ?? 0;
399
410
  // Create a timeout promise
411
+ let flushTimer;
400
412
  const timeoutPromise = new Promise((_, reject) => {
401
- setTimeout(() => reject(new Error("Flush operation timed out")), args.timeout ?? 30000);
413
+ flushTimer = setTimeout(() => reject(new Error("Flush operation timed out")), args.timeout ?? 30000);
402
414
  });
403
415
  // Flush OpenTelemetry spans
404
416
  const flushPromise = flushOpenTelemetry();
405
417
  // Race between flush and timeout
406
418
  await Promise.race([flushPromise, timeoutPromise]);
419
+ // Cancel the timeout timer to avoid unhandled rejection
420
+ if (flushTimer !== undefined) {
421
+ clearTimeout(flushTimer);
422
+ }
407
423
  // Get status after flush
408
424
  const statusAfter = neurolink.getTelemetryStatus();
409
425
  const pendingAfter = statusAfter.exporters?.reduce((sum, e) => sum + (e.pendingSpans ?? 0), 0) ?? 0;
410
426
  const flushedCount = Math.max(0, pendingBefore - pendingAfter);
411
- if (spinner)
427
+ if (spinner) {
412
428
  spinner.succeed("Flush completed");
429
+ }
413
430
  if (args.format === "json") {
414
- console.log(JSON.stringify({
431
+ logger.always(JSON.stringify({
415
432
  success: true,
416
433
  pendingBefore,
417
434
  pendingAfter,
@@ -419,18 +436,19 @@ export class TelemetryCommandFactory {
419
436
  }, null, 2));
420
437
  }
421
438
  else {
422
- console.log("");
423
- console.log(chalk.bold.cyan("=== Flush Complete ==="));
424
- console.log("");
425
- console.log(formatRow("Spans before:", pendingBefore.toString()));
426
- console.log(formatRow("Spans after:", pendingAfter.toString()));
427
- console.log(formatRow("Flushed:", chalk.green(flushedCount.toString())));
428
- console.log("");
439
+ logger.always("");
440
+ logger.always(chalk.bold.cyan("=== Flush Complete ==="));
441
+ logger.always("");
442
+ logger.always(formatRow("Spans before:", pendingBefore.toString()));
443
+ logger.always(formatRow("Spans after:", pendingAfter.toString()));
444
+ logger.always(formatRow("Flushed:", chalk.green(flushedCount.toString())));
445
+ logger.always("");
429
446
  }
430
447
  }
431
448
  catch (error) {
432
- if (spinner)
449
+ if (spinner) {
433
450
  spinner.fail("Failed to flush spans");
451
+ }
434
452
  logger.error("Error:", error instanceof Error ? error.message : String(error));
435
453
  process.exit(1);
436
454
  }
@@ -485,10 +503,11 @@ export class TelemetryCommandFactory {
485
503
  try {
486
504
  const neurolink = new NeuroLink();
487
505
  const metrics = neurolink.getMetrics();
488
- if (spinner)
506
+ if (spinner) {
489
507
  spinner.succeed("Statistics retrieved");
508
+ }
490
509
  if (args.format === "json") {
491
- console.log(JSON.stringify({
510
+ logger.always(JSON.stringify({
492
511
  tokens: {
493
512
  input: metrics.tokens.totalInputTokens,
494
513
  output: metrics.tokens.totalOutputTokens,
@@ -511,82 +530,83 @@ export class TelemetryCommandFactory {
511
530
  }, null, 2));
512
531
  }
513
532
  else {
514
- console.log("");
515
- console.log(chalk.bold.cyan("=== Token & Cost Statistics ==="));
516
- console.log("");
533
+ logger.always("");
534
+ logger.always(chalk.bold.cyan("=== Token & Cost Statistics ==="));
535
+ logger.always("");
517
536
  // Token usage
518
- console.log(chalk.bold("Token Usage:"));
519
- console.log(formatRow(" Input tokens:", metrics.tokens.totalInputTokens.toLocaleString()));
520
- console.log(formatRow(" Output tokens:", metrics.tokens.totalOutputTokens.toLocaleString()));
521
- console.log(formatRow(" Total tokens:", metrics.tokens.totalTokens.toLocaleString()));
537
+ logger.always(chalk.bold("Token Usage:"));
538
+ logger.always(formatRow(" Input tokens:", metrics.tokens.totalInputTokens.toLocaleString()));
539
+ logger.always(formatRow(" Output tokens:", metrics.tokens.totalOutputTokens.toLocaleString()));
540
+ logger.always(formatRow(" Total tokens:", metrics.tokens.totalTokens.toLocaleString()));
522
541
  if (args.detailed) {
523
542
  if (metrics.tokens.cacheReadTokens > 0) {
524
- console.log(formatRow(" Cache read:", metrics.tokens.cacheReadTokens.toLocaleString()));
543
+ logger.always(formatRow(" Cache read:", metrics.tokens.cacheReadTokens.toLocaleString()));
525
544
  }
526
545
  if (metrics.tokens.reasoningTokens > 0) {
527
- console.log(formatRow(" Reasoning:", metrics.tokens.reasoningTokens.toLocaleString()));
546
+ logger.always(formatRow(" Reasoning:", metrics.tokens.reasoningTokens.toLocaleString()));
528
547
  }
529
548
  }
530
549
  // Cost summary
531
- console.log("");
532
- console.log(chalk.bold("Cost Summary:"));
533
- console.log(formatRow(" Total cost:", formatCost(metrics.totalCost ?? 0)));
550
+ logger.always("");
551
+ logger.always(chalk.bold("Cost Summary:"));
552
+ logger.always(formatRow(" Total cost:", formatCost(metrics.totalCost ?? 0)));
534
553
  // Cost by provider
535
554
  if (args.byProvider !== false &&
536
555
  metrics.costByProvider &&
537
556
  metrics.costByProvider.length > 0) {
538
- console.log("");
539
- console.log(chalk.bold("Cost by Provider:"));
557
+ logger.always("");
558
+ logger.always(chalk.bold("Cost by Provider:"));
540
559
  const sortedProviders = [...metrics.costByProvider].sort((a, b) => b.totalCost - a.totalCost);
541
560
  for (const provider of sortedProviders) {
542
- console.log(` ${chalk.cyan(provider.provider.padEnd(15))} ${formatCost(provider.totalCost)}`);
543
- console.log(chalk.gray(` ${provider.requestCount} requests, avg ${formatCost(provider.avgCostPerRequest)}/req`));
561
+ logger.always(` ${chalk.cyan(provider.provider.padEnd(15))} ${formatCost(provider.totalCost)}`);
562
+ logger.always(chalk.gray(` ${provider.requestCount} requests, avg ${formatCost(provider.avgCostPerRequest)}/req`));
544
563
  }
545
564
  }
546
565
  // Cost by model
547
566
  if (args.byModel !== false &&
548
567
  metrics.costByModel &&
549
568
  metrics.costByModel.length > 0) {
550
- console.log("");
551
- console.log(chalk.bold("Cost by Model:"));
569
+ logger.always("");
570
+ logger.always(chalk.bold("Cost by Model:"));
552
571
  const sortedModels = [...metrics.costByModel].sort((a, b) => b.totalCost - a.totalCost);
553
572
  for (const model of sortedModels) {
554
- console.log(` ${chalk.cyan(model.model)}`);
555
- console.log(` Cost: ${formatCost(model.totalCost)}`);
556
- console.log(chalk.gray(` ${model.requestCount} requests, avg ${formatCost(model.avgCostPerRequest)}/req`));
573
+ logger.always(` ${chalk.cyan(model.model)}`);
574
+ logger.always(` Cost: ${formatCost(model.totalCost)}`);
575
+ logger.always(chalk.gray(` ${model.requestCount} requests, avg ${formatCost(model.avgCostPerRequest)}/req`));
557
576
  if (args.detailed) {
558
- console.log(chalk.gray(` ${model.inputTokens.toLocaleString()} input, ${model.outputTokens.toLocaleString()} output tokens`));
577
+ logger.always(chalk.gray(` ${model.inputTokens.toLocaleString()} input, ${model.outputTokens.toLocaleString()} output tokens`));
559
578
  }
560
579
  }
561
580
  }
562
581
  // Request statistics
563
- console.log("");
564
- console.log(chalk.bold("Request Statistics:"));
565
- console.log(formatRow(" Total requests:", metrics.totalSpans.toLocaleString()));
566
- console.log(formatRow(" Successful:", metrics.successfulSpans.toLocaleString()));
567
- console.log(formatRow(" Failed:", metrics.failedSpans.toLocaleString()));
568
- console.log(formatRow(" Success rate:", `${(metrics.successRate * 100).toFixed(2)}%`));
582
+ logger.always("");
583
+ logger.always(chalk.bold("Request Statistics:"));
584
+ logger.always(formatRow(" Total requests:", metrics.totalSpans.toLocaleString()));
585
+ logger.always(formatRow(" Successful:", metrics.successfulSpans.toLocaleString()));
586
+ logger.always(formatRow(" Failed:", metrics.failedSpans.toLocaleString()));
587
+ logger.always(formatRow(" Success rate:", `${(metrics.successRate * 100).toFixed(2)}%`));
569
588
  // Latency (if detailed)
570
589
  if (args.detailed && metrics.latency.count > 0) {
571
- console.log("");
572
- console.log(chalk.bold("Latency (ms):"));
573
- console.log(formatRow(" P50:", metrics.latency.p50.toFixed(2)));
574
- console.log(formatRow(" P95:", metrics.latency.p95.toFixed(2)));
575
- console.log(formatRow(" P99:", metrics.latency.p99.toFixed(2)));
590
+ logger.always("");
591
+ logger.always(chalk.bold("Latency (ms):"));
592
+ logger.always(formatRow(" P50:", metrics.latency.p50.toFixed(2)));
593
+ logger.always(formatRow(" P95:", metrics.latency.p95.toFixed(2)));
594
+ logger.always(formatRow(" P99:", metrics.latency.p99.toFixed(2)));
576
595
  }
577
596
  // Tracking duration
578
597
  if (metrics.trackingDurationMs) {
579
598
  const durationSec = metrics.trackingDurationMs / 1000;
580
599
  const throughput = metrics.totalSpans > 0 ? metrics.totalSpans / durationSec : 0;
581
- console.log("");
582
- console.log(chalk.gray(`Tracking: ${durationSec.toFixed(1)}s (${throughput.toFixed(2)} req/s)`));
600
+ logger.always("");
601
+ logger.always(chalk.gray(`Tracking: ${durationSec.toFixed(1)}s (${throughput.toFixed(2)} req/s)`));
583
602
  }
584
- console.log("");
603
+ logger.always("");
585
604
  }
586
605
  }
587
606
  catch (error) {
588
- if (spinner)
607
+ if (spinner) {
589
608
  spinner.fail("Failed to get statistics");
609
+ }
590
610
  logger.error("Error:", error instanceof Error ? error.message : String(error));
591
611
  process.exit(1);
592
612
  }
@@ -15,6 +15,7 @@ import { ObservabilityCommandFactory } from "./commands/observability.js";
15
15
  import { TelemetryCommandFactory } from "./commands/telemetry.js";
16
16
  import { proxyStartCommand, proxyStatusCommand, proxyTelemetryCommand, proxySetupCommand, proxyGuardCommand, proxyInstallCommand, proxyUninstallCommand, } from "./commands/proxy.js";
17
17
  import { proxyAnalyzeCommand } from "./commands/proxyAnalyze.js";
18
+ import { proxyReplayCommand } from "./commands/proxyReplay.js";
18
19
  import { EvaluateCommandFactory } from "./commands/evaluate.js";
19
20
  import { TaskCommandFactory } from "./commands/task.js";
20
21
  import { AutoresearchCommandFactory } from "./commands/autoresearch.js";
@@ -202,12 +203,13 @@ export function initializeCliParser() {
202
203
  .command(proxyStartCommand)
203
204
  .command(proxyStatusCommand)
204
205
  .command(proxyAnalyzeCommand)
206
+ .command(proxyReplayCommand)
205
207
  .command(proxyTelemetryCommand)
206
208
  .command(proxySetupCommand)
207
209
  .command(proxyGuardCommand)
208
210
  .command(proxyInstallCommand)
209
211
  .command(proxyUninstallCommand)
210
- .demandCommand(1, "Please specify a proxy subcommand: start, status, analyze, telemetry <setup|start|stop|status|logs|import-dashboard>, setup, guard, install, or uninstall"),
212
+ .demandCommand(1, "Please specify a proxy subcommand: start, status, analyze, replay <export|compare>, telemetry <setup|start|stop|status|logs|import-dashboard>, setup, guard, install, or uninstall"),
211
213
  handler: () => { },
212
214
  })
213
215
  // Evaluate Command Group - Using EvaluateCommandFactory
@@ -0,0 +1,10 @@
1
+ import type { CompareProxyReplayOptions, ExportProxyReplayOptions, ProxyReplayBundle, ProxyReplayComparison } from "../types/index.js";
2
+ export declare function isValidProxyReplayHeaderName(name: string): boolean;
3
+ export declare function serializeProxyReplayDocument(value: unknown): string;
4
+ export declare function exportProxyReplayBundle(options: ExportProxyReplayOptions): Promise<ProxyReplayBundle>;
5
+ export declare function writeProxyReplayDocument(outputPath: string, document: unknown): Promise<{
6
+ path: string;
7
+ sha256: string;
8
+ }>;
9
+ export declare function readProxyReplayBundle(bundlePath: string): Promise<ProxyReplayBundle>;
10
+ export declare function compareProxyReplayBundle(bundle: ProxyReplayBundle, options: CompareProxyReplayOptions): Promise<ProxyReplayComparison>;