@juspay/neurolink 10.0.0 → 10.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [10.0.1](https://github.com/juspay/neurolink/compare/v10.0.0...v10.0.1) (2026-07-20)
2
+
3
+ ### Bug Fixes
4
+
5
+ - **(cli):** redact URL credentials in observability/telemetry output and clear flush timer leak ([cbb91a4](https://github.com/juspay/neurolink/commit/cbb91a4957520e798a13b09f2486e63b27ed3590))
6
+
1
7
  ## [10.0.0](https://github.com/juspay/neurolink/compare/v9.95.3...v10.0.0) (2026-07-20)
2
8
 
3
9
  ### ⚠ BREAKING CHANGES
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /* eslint-disable no-console, curly */
3
2
  /**
4
3
  * NeuroLink CLI Observability Commands
5
4
  *
@@ -14,6 +13,7 @@ import ora from "ora";
14
13
  import { logger } from "../../lib/utils/logger.js";
15
14
  import { NeuroLink } from "../../lib/neurolink.js";
16
15
  import { formatRow, formatCost } from "../utils/formatters.js";
16
+ import { redactUrlCredentials } from "../../lib/utils/logSanitize.js";
17
17
  /**
18
18
  * Observability Command Factory
19
19
  */
@@ -70,67 +70,72 @@ export class ObservabilityCommandFactory {
70
70
  try {
71
71
  const neurolink = new NeuroLink();
72
72
  const status = neurolink.getTelemetryStatus();
73
- if (spinner)
73
+ if (spinner) {
74
74
  spinner.succeed("Status retrieved");
75
+ }
75
76
  if (args.format === "json") {
76
- console.log(JSON.stringify(status, null, 2));
77
+ logger.always(JSON.stringify(status, (_key, value) => typeof value === "string" &&
78
+ (_key === "baseUrl" || _key === "endpoint")
79
+ ? redactUrlCredentials(value)
80
+ : value, 2));
77
81
  }
78
82
  else {
79
- console.log("");
80
- console.log(chalk.bold.cyan("=== Observability Status ==="));
81
- console.log("");
83
+ logger.always("");
84
+ logger.always(chalk.bold.cyan("=== Observability Status ==="));
85
+ logger.always("");
82
86
  // Telemetry enabled status
83
87
  const enabledIcon = status.enabled
84
88
  ? chalk.green("ON")
85
89
  : chalk.red("OFF");
86
- console.log(formatRow("Telemetry:", enabledIcon));
90
+ logger.always(formatRow("Telemetry:", enabledIcon));
87
91
  // Langfuse status
88
92
  if (status.langfuse) {
89
- console.log("");
90
- console.log(chalk.bold("Langfuse:"));
93
+ logger.always("");
94
+ logger.always(chalk.bold("Langfuse:"));
91
95
  const lfStatus = status.langfuse.enabled
92
96
  ? chalk.green("Enabled")
93
97
  : chalk.gray("Disabled");
94
- console.log(formatRow(" Status:", lfStatus));
98
+ logger.always(formatRow(" Status:", lfStatus));
95
99
  if (status.langfuse.baseUrl) {
96
- console.log(formatRow(" URL:", status.langfuse.baseUrl));
100
+ logger.always(formatRow(" URL:", redactUrlCredentials(status.langfuse.baseUrl)));
97
101
  }
98
102
  if (status.langfuse.environment) {
99
- console.log(formatRow(" Environment:", status.langfuse.environment));
103
+ logger.always(formatRow(" Environment:", status.langfuse.environment));
100
104
  }
101
105
  }
102
106
  // OpenTelemetry status
103
107
  if (status.openTelemetry) {
104
- console.log("");
105
- console.log(chalk.bold("OpenTelemetry:"));
108
+ logger.always("");
109
+ logger.always(chalk.bold("OpenTelemetry:"));
106
110
  const otelStatus = status.openTelemetry.enabled
107
111
  ? chalk.green("Enabled")
108
112
  : chalk.gray("Disabled");
109
- console.log(formatRow(" Status:", otelStatus));
113
+ logger.always(formatRow(" Status:", otelStatus));
110
114
  if (status.openTelemetry.endpoint) {
111
- console.log(formatRow(" Endpoint:", status.openTelemetry.endpoint));
115
+ logger.always(formatRow(" Endpoint:", redactUrlCredentials(status.openTelemetry.endpoint)));
112
116
  }
113
117
  if (status.openTelemetry.serviceName) {
114
- console.log(formatRow(" Service:", status.openTelemetry.serviceName));
118
+ logger.always(formatRow(" Service:", status.openTelemetry.serviceName));
115
119
  }
116
120
  }
117
121
  // Exporters summary
118
122
  if (status.exporters && status.exporters.length > 0) {
119
- console.log("");
120
- console.log(chalk.bold("Active Exporters:"));
123
+ logger.always("");
124
+ logger.always(chalk.bold("Active Exporters:"));
121
125
  for (const exporter of status.exporters) {
122
126
  const exporterStatus = exporter.healthy
123
127
  ? chalk.green("Healthy")
124
128
  : chalk.red("Unhealthy");
125
- console.log(formatRow(` ${exporter.name}:`, exporterStatus));
129
+ logger.always(formatRow(` ${exporter.name}:`, exporterStatus));
126
130
  }
127
131
  }
128
- console.log("");
132
+ logger.always("");
129
133
  }
130
134
  }
131
135
  catch (error) {
132
- if (spinner)
136
+ if (spinner) {
133
137
  spinner.fail("Failed to get status");
138
+ }
134
139
  logger.error("Error:", error instanceof Error ? error.message : String(error));
135
140
  process.exit(1);
136
141
  }
@@ -171,74 +176,76 @@ export class ObservabilityCommandFactory {
171
176
  try {
172
177
  const neurolink = new NeuroLink();
173
178
  const metrics = neurolink.getMetrics();
174
- if (spinner)
179
+ if (spinner) {
175
180
  spinner.succeed("Metrics retrieved");
181
+ }
176
182
  if (args.format === "json") {
177
- console.log(JSON.stringify(metrics, null, 2));
183
+ logger.always(JSON.stringify(metrics, null, 2));
178
184
  }
179
185
  else {
180
- console.log("");
181
- console.log(chalk.bold.cyan("=== Metrics Summary ==="));
182
- console.log("");
186
+ logger.always("");
187
+ logger.always(chalk.bold.cyan("=== Metrics Summary ==="));
188
+ logger.always("");
183
189
  // Request statistics
184
- console.log(chalk.bold("Request Statistics:"));
185
- console.log(formatRow(" Total requests:", metrics.totalSpans.toLocaleString()));
186
- console.log(formatRow(" Successful:", metrics.successfulSpans.toLocaleString()));
187
- console.log(formatRow(" Failed:", metrics.failedSpans.toLocaleString()));
188
- console.log(formatRow(" Success rate:", `${(metrics.successRate * 100).toFixed(2)}%`));
190
+ logger.always(chalk.bold("Request Statistics:"));
191
+ logger.always(formatRow(" Total requests:", metrics.totalSpans.toLocaleString()));
192
+ logger.always(formatRow(" Successful:", metrics.successfulSpans.toLocaleString()));
193
+ logger.always(formatRow(" Failed:", metrics.failedSpans.toLocaleString()));
194
+ logger.always(formatRow(" Success rate:", `${(metrics.successRate * 100).toFixed(2)}%`));
189
195
  // Latency statistics
190
196
  if (metrics.latency && metrics.latency.count > 0) {
191
- console.log("");
192
- console.log(chalk.bold("Latency (ms):"));
193
- console.log(formatRow(" Min:", metrics.latency.min.toFixed(2)));
194
- console.log(formatRow(" Max:", metrics.latency.max.toFixed(2)));
195
- console.log(formatRow(" Mean:", metrics.latency.mean.toFixed(2)));
196
- console.log(formatRow(" P50:", metrics.latency.p50.toFixed(2)));
197
- console.log(formatRow(" P95:", metrics.latency.p95.toFixed(2)));
198
- console.log(formatRow(" P99:", metrics.latency.p99.toFixed(2)));
197
+ logger.always("");
198
+ logger.always(chalk.bold("Latency (ms):"));
199
+ logger.always(formatRow(" Min:", metrics.latency.min.toFixed(2)));
200
+ logger.always(formatRow(" Max:", metrics.latency.max.toFixed(2)));
201
+ logger.always(formatRow(" Mean:", metrics.latency.mean.toFixed(2)));
202
+ logger.always(formatRow(" P50:", metrics.latency.p50.toFixed(2)));
203
+ logger.always(formatRow(" P95:", metrics.latency.p95.toFixed(2)));
204
+ logger.always(formatRow(" P99:", metrics.latency.p99.toFixed(2)));
199
205
  if (args.detailed) {
200
- console.log(formatRow(" P75:", metrics.latency.p75.toFixed(2)));
201
- console.log(formatRow(" P90:", metrics.latency.p90.toFixed(2)));
202
- console.log(formatRow(" Std Dev:", metrics.latency.stdDev.toFixed(2)));
206
+ logger.always(formatRow(" P75:", metrics.latency.p75.toFixed(2)));
207
+ logger.always(formatRow(" P90:", metrics.latency.p90.toFixed(2)));
208
+ logger.always(formatRow(" Std Dev:", metrics.latency.stdDev.toFixed(2)));
203
209
  }
204
210
  }
205
211
  // Token statistics
206
212
  if (metrics.tokens) {
207
- console.log("");
208
- console.log(chalk.bold("Token Usage:"));
209
- console.log(formatRow(" Input tokens:", metrics.tokens.totalInputTokens.toLocaleString()));
210
- console.log(formatRow(" Output tokens:", metrics.tokens.totalOutputTokens.toLocaleString()));
211
- console.log(formatRow(" Total tokens:", metrics.tokens.totalTokens.toLocaleString()));
213
+ logger.always("");
214
+ logger.always(chalk.bold("Token Usage:"));
215
+ logger.always(formatRow(" Input tokens:", metrics.tokens.totalInputTokens.toLocaleString()));
216
+ logger.always(formatRow(" Output tokens:", metrics.tokens.totalOutputTokens.toLocaleString()));
217
+ logger.always(formatRow(" Total tokens:", metrics.tokens.totalTokens.toLocaleString()));
212
218
  if (args.detailed &&
213
219
  metrics.tokens.cacheReadTokens &&
214
220
  metrics.tokens.cacheReadTokens > 0) {
215
- console.log(formatRow(" Cache read:", metrics.tokens.cacheReadTokens.toLocaleString()));
221
+ logger.always(formatRow(" Cache read:", metrics.tokens.cacheReadTokens.toLocaleString()));
216
222
  }
217
223
  if (args.detailed &&
218
224
  metrics.tokens.reasoningTokens &&
219
225
  metrics.tokens.reasoningTokens > 0) {
220
- console.log(formatRow(" Reasoning:", metrics.tokens.reasoningTokens.toLocaleString()));
226
+ logger.always(formatRow(" Reasoning:", metrics.tokens.reasoningTokens.toLocaleString()));
221
227
  }
222
228
  }
223
229
  // Cost
224
230
  if (metrics.totalCost !== undefined && metrics.totalCost > 0) {
225
- console.log("");
226
- console.log(chalk.bold("Cost:"));
227
- console.log(formatRow(" Total:", formatCost(metrics.totalCost)));
231
+ logger.always("");
232
+ logger.always(chalk.bold("Cost:"));
233
+ logger.always(formatRow(" Total:", formatCost(metrics.totalCost)));
228
234
  }
229
235
  // Tracking duration
230
236
  if (metrics.trackingDurationMs) {
231
237
  const durationSec = metrics.trackingDurationMs / 1000;
232
238
  const throughput = metrics.totalSpans > 0 ? metrics.totalSpans / durationSec : 0;
233
- console.log("");
234
- console.log(chalk.gray(`Tracking: ${durationSec.toFixed(1)}s (${throughput.toFixed(2)} req/s)`));
239
+ logger.always("");
240
+ logger.always(chalk.gray(`Tracking: ${durationSec.toFixed(1)}s (${throughput.toFixed(2)} req/s)`));
235
241
  }
236
- console.log("");
242
+ logger.always("");
237
243
  }
238
244
  }
239
245
  catch (error) {
240
- if (spinner)
246
+ if (spinner) {
241
247
  spinner.fail("Failed to get metrics");
248
+ }
242
249
  logger.error("Error:", error instanceof Error ? error.message : String(error));
243
250
  process.exit(1);
244
251
  }
@@ -276,60 +283,62 @@ export class ObservabilityCommandFactory {
276
283
  try {
277
284
  const neurolink = new NeuroLink();
278
285
  const status = neurolink.getTelemetryStatus();
279
- if (spinner)
286
+ if (spinner) {
280
287
  spinner.succeed("Exporters retrieved");
288
+ }
281
289
  const exporters = status.exporters ?? [];
282
290
  if (args.format === "json") {
283
- console.log(JSON.stringify(exporters, null, 2));
291
+ logger.always(JSON.stringify(exporters, null, 2));
284
292
  }
285
293
  else {
286
- console.log("");
287
- console.log(chalk.bold.cyan("=== Configured Exporters ==="));
288
- console.log("");
294
+ logger.always("");
295
+ logger.always(chalk.bold.cyan("=== Configured Exporters ==="));
296
+ logger.always("");
289
297
  if (exporters.length === 0) {
290
- console.log(chalk.gray("No exporters configured."));
291
- console.log("");
292
- console.log("Available exporters:");
293
- console.log(" - Langfuse (langfuse)");
294
- console.log(" - LangSmith (langsmith)");
295
- console.log(" - OpenTelemetry (otel)");
296
- console.log(" - Datadog (datadog)");
297
- console.log(" - Sentry (sentry)");
298
- console.log(" - Braintrust (braintrust)");
299
- console.log(" - Arize (arize)");
300
- console.log(" - PostHog (posthog)");
301
- console.log(" - Laminar (laminar)");
298
+ logger.always(chalk.gray("No exporters configured."));
299
+ logger.always("");
300
+ logger.always("Available exporters:");
301
+ logger.always(" - Langfuse (langfuse)");
302
+ logger.always(" - LangSmith (langsmith)");
303
+ logger.always(" - OpenTelemetry (otel)");
304
+ logger.always(" - Datadog (datadog)");
305
+ logger.always(" - Sentry (sentry)");
306
+ logger.always(" - Braintrust (braintrust)");
307
+ logger.always(" - Arize (arize)");
308
+ logger.always(" - PostHog (posthog)");
309
+ logger.always(" - Laminar (laminar)");
302
310
  }
303
311
  else {
304
312
  for (const exporter of exporters) {
305
313
  const healthIcon = exporter.healthy
306
314
  ? chalk.green("[OK]")
307
315
  : chalk.red("[ERROR]");
308
- console.log(`${healthIcon} ${chalk.bold(exporter.name)}`);
316
+ logger.always(`${healthIcon} ${chalk.bold(exporter.name)}`);
309
317
  if (exporter.latencyMs !== undefined) {
310
- console.log(formatRow(" Latency:", `${exporter.latencyMs.toFixed(2)}ms`));
318
+ logger.always(formatRow(" Latency:", `${exporter.latencyMs.toFixed(2)}ms`));
311
319
  }
312
320
  if (exporter.pendingSpans !== undefined) {
313
- console.log(formatRow(" Pending spans:", exporter.pendingSpans.toLocaleString()));
321
+ logger.always(formatRow(" Pending spans:", exporter.pendingSpans.toLocaleString()));
314
322
  }
315
323
  if (exporter.lastExportTime) {
316
324
  const lastExport = new Date(exporter.lastExportTime);
317
- console.log(formatRow(" Last export:", lastExport.toISOString()));
325
+ logger.always(formatRow(" Last export:", lastExport.toISOString()));
318
326
  }
319
327
  if (exporter.errors && exporter.errors.length > 0) {
320
- console.log(chalk.yellow(" Errors:"));
328
+ logger.always(chalk.yellow(" Errors:"));
321
329
  for (const error of exporter.errors.slice(0, 3)) {
322
- console.log(chalk.red(` - ${error}`));
330
+ logger.always(chalk.red(` - ${error}`));
323
331
  }
324
332
  }
325
- console.log("");
333
+ logger.always("");
326
334
  }
327
335
  }
328
336
  }
329
337
  }
330
338
  catch (error) {
331
- if (spinner)
339
+ if (spinner) {
332
340
  spinner.fail("Failed to get exporters");
341
+ }
333
342
  logger.error("Error:", error instanceof Error ? error.message : String(error));
334
343
  process.exit(1);
335
344
  }
@@ -377,8 +386,9 @@ export class ObservabilityCommandFactory {
377
386
  try {
378
387
  const neurolink = new NeuroLink();
379
388
  const metrics = neurolink.getMetrics();
380
- if (spinner)
389
+ if (spinner) {
381
390
  spinner.succeed("Costs calculated");
391
+ }
382
392
  if (args.format === "json") {
383
393
  const jsonOutput = {
384
394
  totalCost: metrics.totalCost,
@@ -389,60 +399,61 @@ export class ObservabilityCommandFactory {
389
399
  if (args.byModel !== false) {
390
400
  jsonOutput.costByModel = metrics.costByModel;
391
401
  }
392
- console.log(JSON.stringify(jsonOutput, null, 2));
402
+ logger.always(JSON.stringify(jsonOutput, null, 2));
393
403
  }
394
404
  else {
395
- console.log("");
396
- console.log(chalk.bold.cyan("=== Cost Breakdown ==="));
397
- console.log("");
405
+ logger.always("");
406
+ logger.always(chalk.bold.cyan("=== Cost Breakdown ==="));
407
+ logger.always("");
398
408
  // Total cost
399
- console.log(chalk.bold(`Total Cost: ${formatCost(metrics.totalCost ?? 0)}`));
409
+ logger.always(chalk.bold(`Total Cost: ${formatCost(metrics.totalCost ?? 0)}`));
400
410
  // Cost by provider
401
411
  if (args.byProvider !== false &&
402
412
  metrics.costByProvider &&
403
413
  metrics.costByProvider.length > 0) {
404
- console.log("");
405
- console.log(chalk.bold("By Provider:"));
414
+ logger.always("");
415
+ logger.always(chalk.bold("By Provider:"));
406
416
  // Sort by cost descending
407
417
  const sortedProviders = [...metrics.costByProvider].sort((a, b) => b.totalCost - a.totalCost);
408
418
  for (const provider of sortedProviders) {
409
419
  const costStr = formatCost(provider.totalCost);
410
420
  const avgCost = formatCost(provider.avgCostPerRequest);
411
- console.log(` ${chalk.cyan(provider.provider.padEnd(15))} ${costStr}`);
412
- console.log(chalk.gray(` ${provider.requestCount} requests, avg ${avgCost}/req`));
421
+ logger.always(` ${chalk.cyan(provider.provider.padEnd(15))} ${costStr}`);
422
+ logger.always(chalk.gray(` ${provider.requestCount} requests, avg ${avgCost}/req`));
413
423
  }
414
424
  }
415
425
  // Cost by model
416
426
  if (args.byModel !== false &&
417
427
  metrics.costByModel &&
418
428
  metrics.costByModel.length > 0) {
419
- console.log("");
420
- console.log(chalk.bold("By Model:"));
429
+ logger.always("");
430
+ logger.always(chalk.bold("By Model:"));
421
431
  // Sort by cost descending
422
432
  const sortedModels = [...metrics.costByModel].sort((a, b) => b.totalCost - a.totalCost);
423
433
  for (const model of sortedModels) {
424
434
  const costStr = formatCost(model.totalCost);
425
435
  const avgCost = formatCost(model.avgCostPerRequest);
426
- console.log(` ${chalk.cyan(model.model)}`);
427
- console.log(` Cost: ${costStr}`);
428
- console.log(chalk.gray(` ${model.requestCount} requests, avg ${avgCost}/req`));
429
- console.log(chalk.gray(` ${model.inputTokens.toLocaleString()} input, ${model.outputTokens.toLocaleString()} output tokens`));
436
+ logger.always(` ${chalk.cyan(model.model)}`);
437
+ logger.always(` Cost: ${costStr}`);
438
+ logger.always(chalk.gray(` ${model.requestCount} requests, avg ${avgCost}/req`));
439
+ logger.always(chalk.gray(` ${model.inputTokens.toLocaleString()} input, ${model.outputTokens.toLocaleString()} output tokens`));
430
440
  }
431
441
  }
432
442
  // No cost data
433
443
  if ((!metrics.costByProvider ||
434
444
  metrics.costByProvider.length === 0) &&
435
445
  (!metrics.costByModel || metrics.costByModel.length === 0)) {
436
- console.log("");
437
- console.log(chalk.gray("No cost data available."));
438
- console.log(chalk.gray("Cost tracking is recorded when observability is enabled."));
446
+ logger.always("");
447
+ logger.always(chalk.gray("No cost data available."));
448
+ logger.always(chalk.gray("Cost tracking is recorded when observability is enabled."));
439
449
  }
440
- console.log("");
450
+ logger.always("");
441
451
  }
442
452
  }
443
453
  catch (error) {
444
- if (spinner)
454
+ if (spinner) {
445
455
  spinner.fail("Failed to get costs");
456
+ }
446
457
  logger.error("Error:", error instanceof Error ? error.message : String(error));
447
458
  process.exit(1);
448
459
  }
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "10.0.0",
3
+ "version": "10.0.1",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {