@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
689 lines • 32.2 kB
JavaScript
#!/usr/bin/env node
/* eslint-disable no-console, curly */
/**
* NeuroLink CLI Telemetry Commands
*
* Commands for managing telemetry and observability exporters:
* - status: Show exporter status
* - configure: Configure an exporter
* - list-exporters: List configured exporters
* - flush: Flush pending spans
* - stats: Show token/cost stats
*/
import chalk from "chalk";
import ora from "ora";
import { logger } from "../../lib/utils/logger.js";
import { NeuroLink } from "../../lib/neurolink.js";
import { flushOpenTelemetry } from "../../lib/services/server/ai/observability/instrumentation.js";
import { formatRow, formatCost } from "../utils/formatters.js";
/**
* Available exporter names
*/
const AVAILABLE_EXPORTERS = [
"langfuse",
"langsmith",
"otel",
"datadog",
"sentry",
"braintrust",
"arize",
"posthog",
"laminar",
];
/**
* Telemetry Command Factory
*/
export class TelemetryCommandFactory {
/**
* Create the telemetry command group
*/
static createTelemetryCommands() {
return {
command: "telemetry <subcommand>",
aliases: ["tel"],
describe: "Telemetry and exporter management",
builder: (yargs) => {
return yargs
.command(TelemetryCommandFactory.createStatusCommand())
.command(TelemetryCommandFactory.createConfigureCommand())
.command(TelemetryCommandFactory.createListExportersCommand())
.command(TelemetryCommandFactory.createFlushCommand())
.command(TelemetryCommandFactory.createStatsCommand())
.demandCommand(1, "Please specify a subcommand")
.strict();
},
handler: () => {
// This handler is not called directly due to demandCommand
},
};
}
/**
* Create the status subcommand
*/
static createStatusCommand() {
return {
command: "status",
describe: "Show exporter status and health",
builder: (yargs) => {
return yargs
.option("format", {
alias: "f",
type: "string",
choices: ["text", "json", "table"],
default: "text",
describe: "Output format",
})
.option("quiet", {
alias: "q",
type: "boolean",
default: false,
describe: "Minimal output",
});
},
handler: async (args) => {
const spinner = args.quiet
? null
: ora("Checking exporter status...").start();
try {
const neurolink = new NeuroLink();
const status = neurolink.getTelemetryStatus();
if (spinner)
spinner.succeed("Status retrieved");
if (args.format === "json") {
console.log(JSON.stringify(status, null, 2));
}
else {
console.log("");
console.log(chalk.bold.cyan("=== Telemetry Status ==="));
console.log("");
// Telemetry enabled status
const enabledIcon = status.enabled
? chalk.green("ENABLED")
: chalk.red("DISABLED");
console.log(formatRow("Telemetry:", enabledIcon));
// OpenTelemetry status
if (status.openTelemetry) {
console.log("");
console.log(chalk.bold("OpenTelemetry:"));
const otelStatus = status.openTelemetry.enabled
? chalk.green("Active")
: chalk.gray("Inactive");
console.log(formatRow(" Status:", otelStatus));
if (status.openTelemetry.endpoint) {
console.log(formatRow(" Endpoint:", status.openTelemetry.endpoint));
}
if (status.openTelemetry.serviceName) {
console.log(formatRow(" Service:", status.openTelemetry.serviceName));
}
}
// Langfuse status
if (status.langfuse) {
console.log("");
console.log(chalk.bold("Langfuse:"));
const lfStatus = status.langfuse.enabled
? chalk.green("Active")
: chalk.gray("Inactive");
console.log(formatRow(" Status:", lfStatus));
if (status.langfuse.baseUrl) {
console.log(formatRow(" URL:", status.langfuse.baseUrl));
}
if (status.langfuse.environment) {
console.log(formatRow(" Environment:", status.langfuse.environment));
}
}
// Exporters health summary
if (status.exporters && status.exporters.length > 0) {
console.log("");
console.log(chalk.bold("Exporter Health:"));
for (const exporter of status.exporters) {
const healthIcon = exporter.healthy
? chalk.green("[OK]")
: chalk.red("[ERROR]");
const pendingInfo = exporter.pendingSpans
? chalk.gray(` (${exporter.pendingSpans} pending)`)
: "";
console.log(` ${healthIcon} ${exporter.name}${pendingInfo}`);
if (exporter.errors && exporter.errors.length > 0) {
for (const error of exporter.errors.slice(0, 2)) {
console.log(chalk.red(` Error: ${error}`));
}
}
}
}
else {
console.log("");
console.log(chalk.gray("No exporters configured."));
}
console.log("");
}
}
catch (error) {
if (spinner)
spinner.fail("Failed to get status");
logger.error("Error:", error instanceof Error ? error.message : String(error));
process.exit(1);
}
},
};
}
/**
* Create the configure subcommand
*/
static createConfigureCommand() {
return {
command: "configure",
describe: "Configure an exporter with JSON settings",
builder: (yargs) => {
return yargs
.option("exporter", {
alias: "e",
type: "string",
demandOption: true,
choices: AVAILABLE_EXPORTERS,
describe: "Exporter name to configure",
})
.option("config", {
alias: "c",
type: "string",
demandOption: true,
describe: "JSON configuration string",
})
.option("format", {
alias: "f",
type: "string",
choices: ["text", "json", "table"],
default: "text",
describe: "Output format",
})
.option("quiet", {
alias: "q",
type: "boolean",
default: false,
describe: "Minimal output",
});
},
handler: async (args) => {
const spinner = args.quiet
? null
: ora(`Configuring ${args.exporter} exporter...`).start();
try {
// Parse the JSON config
let config;
try {
config = JSON.parse(args.config);
}
catch {
if (spinner)
spinner.fail("Invalid JSON configuration");
console.log(chalk.red("Error: Configuration must be valid JSON"));
console.log("");
console.log("Example:");
console.log(chalk.gray(` neurolink telemetry configure --exporter langfuse --config '{"publicKey":"pk-...", "secretKey":"sk-..."}'`));
process.exit(1);
}
// Validate required fields based on exporter type
const validationResult = validateExporterConfig(args.exporter, config);
if (!validationResult.valid) {
if (spinner)
spinner.fail("Configuration validation failed");
console.log(chalk.red(`Error: ${validationResult.error}`));
console.log("");
console.log(chalk.yellow(`Required fields for ${args.exporter}:`));
for (const field of validationResult.requiredFields ?? []) {
console.log(chalk.gray(` - ${field}`));
}
process.exit(1);
}
// Currently, exporter configuration is done via environment variables
// or SDK initialization. This command provides guidance on how to configure.
if (spinner)
spinner.succeed(`${args.exporter} configuration validated`);
if (args.format === "json") {
console.log(JSON.stringify({
exporter: args.exporter,
config: config,
valid: true,
message: "Configuration validated. Set environment variables or use SDK initialization.",
}, null, 2));
}
else {
console.log("");
console.log(chalk.bold.cyan(`=== ${args.exporter} Configuration ===`));
console.log("");
console.log(chalk.green("Configuration validated successfully!"));
console.log("");
console.log(chalk.bold("To apply this configuration:"));
console.log("");
// Show environment variable instructions
const envVars = getExporterEnvVars(args.exporter);
console.log(chalk.yellow("Option 1: Set environment variables"));
for (const [key, description] of Object.entries(envVars)) {
console.log(chalk.gray(` export ${key}="<${description}>"`));
}
console.log("");
console.log(chalk.yellow("Option 2: SDK initialization"));
console.log(chalk.gray(` const neurolink = new NeuroLink({`));
console.log(chalk.gray(` observability: {`));
console.log(chalk.gray(` ${args.exporter}: ${JSON.stringify(config, null, 6).split("\n").join("\n ")}`));
console.log(chalk.gray(` }`));
console.log(chalk.gray(` });`));
console.log("");
}
}
catch (error) {
if (spinner)
spinner.fail("Failed to configure exporter");
logger.error("Error:", error instanceof Error ? error.message : String(error));
process.exit(1);
}
},
};
}
/**
* Create the list-exporters subcommand
*/
static createListExportersCommand() {
return {
command: "list-exporters",
aliases: ["list", "ls"],
describe: "List all available and configured exporters",
builder: (yargs) => {
return yargs
.option("format", {
alias: "f",
type: "string",
choices: ["text", "json", "table"],
default: "text",
describe: "Output format",
})
.option("quiet", {
alias: "q",
type: "boolean",
default: false,
describe: "Minimal output",
});
},
handler: async (args) => {
const spinner = args.quiet ? null : ora("Listing exporters...").start();
try {
const neurolink = new NeuroLink();
const status = neurolink.getTelemetryStatus();
if (spinner)
spinner.succeed("Exporters listed");
const configuredExporters = status.exporters ?? [];
const configuredNames = new Set(configuredExporters.map((e) => e.name.toLowerCase()));
if (args.format === "json") {
console.log(JSON.stringify({
available: AVAILABLE_EXPORTERS,
configured: configuredExporters,
}, null, 2));
}
else {
console.log("");
console.log(chalk.bold.cyan("=== Available Exporters ==="));
console.log("");
for (const exporter of AVAILABLE_EXPORTERS) {
const isConfigured = configuredNames.has(exporter);
const configuredExporter = configuredExporters.find((e) => e.name.toLowerCase() === exporter);
const statusIcon = isConfigured
? configuredExporter?.healthy
? chalk.green("[ACTIVE]")
: chalk.yellow("[CONFIGURED]")
: chalk.gray("[AVAILABLE]");
const description = getExporterDescription(exporter);
console.log(`${statusIcon} ${chalk.bold(exporter)}`);
console.log(chalk.gray(` ${description}`));
if (isConfigured && configuredExporter) {
if (configuredExporter.pendingSpans) {
console.log(chalk.gray(` Pending spans: ${configuredExporter.pendingSpans}`));
}
if (configuredExporter.lastExportTime) {
const lastExport = new Date(configuredExporter.lastExportTime);
console.log(chalk.gray(` Last export: ${lastExport.toISOString()}`));
}
}
console.log("");
}
console.log(chalk.bold("Configuration Help:"));
console.log(chalk.gray(" Use 'neurolink telemetry configure --exporter <name> --config <json>' to configure an exporter"));
console.log("");
}
}
catch (error) {
if (spinner)
spinner.fail("Failed to list exporters");
logger.error("Error:", error instanceof Error ? error.message : String(error));
process.exit(1);
}
},
};
}
/**
* Create the flush subcommand
*/
static createFlushCommand() {
return {
command: "flush",
describe: "Flush all pending spans to exporters",
builder: (yargs) => {
return yargs
.option("timeout", {
alias: "t",
type: "number",
default: 30000,
describe: "Timeout in milliseconds",
})
.option("format", {
alias: "f",
type: "string",
choices: ["text", "json", "table"],
default: "text",
describe: "Output format",
})
.option("quiet", {
alias: "q",
type: "boolean",
default: false,
describe: "Minimal output",
});
},
handler: async (args) => {
const spinner = args.quiet
? null
: ora("Flushing pending spans...").start();
try {
const neurolink = new NeuroLink();
const statusBefore = neurolink.getTelemetryStatus();
// Count pending spans before flush
const pendingBefore = statusBefore.exporters?.reduce((sum, e) => sum + (e.pendingSpans ?? 0), 0) ?? 0;
// Create a timeout promise
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Flush operation timed out")), args.timeout ?? 30000);
});
// Flush OpenTelemetry spans
const flushPromise = flushOpenTelemetry();
// Race between flush and timeout
await Promise.race([flushPromise, timeoutPromise]);
// Get status after flush
const statusAfter = neurolink.getTelemetryStatus();
const pendingAfter = statusAfter.exporters?.reduce((sum, e) => sum + (e.pendingSpans ?? 0), 0) ?? 0;
const flushedCount = Math.max(0, pendingBefore - pendingAfter);
if (spinner)
spinner.succeed("Flush completed");
if (args.format === "json") {
console.log(JSON.stringify({
success: true,
pendingBefore,
pendingAfter,
flushed: flushedCount,
}, null, 2));
}
else {
console.log("");
console.log(chalk.bold.cyan("=== Flush Complete ==="));
console.log("");
console.log(formatRow("Spans before:", pendingBefore.toString()));
console.log(formatRow("Spans after:", pendingAfter.toString()));
console.log(formatRow("Flushed:", chalk.green(flushedCount.toString())));
console.log("");
}
}
catch (error) {
if (spinner)
spinner.fail("Failed to flush spans");
logger.error("Error:", error instanceof Error ? error.message : String(error));
process.exit(1);
}
},
};
}
/**
* Create the stats subcommand
*/
static createStatsCommand() {
return {
command: "stats",
describe: "Show token usage and cost statistics",
builder: (yargs) => {
return yargs
.option("format", {
alias: "f",
type: "string",
choices: ["text", "json", "table"],
default: "text",
describe: "Output format",
})
.option("detailed", {
alias: "d",
type: "boolean",
default: false,
describe: "Show detailed statistics",
})
.option("by-model", {
alias: "m",
type: "boolean",
default: true,
describe: "Show breakdown by model",
})
.option("by-provider", {
alias: "p",
type: "boolean",
default: true,
describe: "Show breakdown by provider",
})
.option("quiet", {
alias: "q",
type: "boolean",
default: false,
describe: "Minimal output",
});
},
handler: async (args) => {
const spinner = args.quiet
? null
: ora("Gathering statistics...").start();
try {
const neurolink = new NeuroLink();
const metrics = neurolink.getMetrics();
if (spinner)
spinner.succeed("Statistics retrieved");
if (args.format === "json") {
console.log(JSON.stringify({
tokens: {
input: metrics.tokens.totalInputTokens,
output: metrics.tokens.totalOutputTokens,
total: metrics.tokens.totalTokens,
cacheRead: metrics.tokens.cacheReadTokens,
reasoning: metrics.tokens.reasoningTokens,
},
cost: {
total: metrics.totalCost,
byProvider: metrics.costByProvider,
byModel: metrics.costByModel,
},
requests: {
total: metrics.totalSpans,
successful: metrics.successfulSpans,
failed: metrics.failedSpans,
successRate: metrics.successRate,
},
latency: metrics.latency,
}, null, 2));
}
else {
console.log("");
console.log(chalk.bold.cyan("=== Token & Cost Statistics ==="));
console.log("");
// Token usage
console.log(chalk.bold("Token Usage:"));
console.log(formatRow(" Input tokens:", metrics.tokens.totalInputTokens.toLocaleString()));
console.log(formatRow(" Output tokens:", metrics.tokens.totalOutputTokens.toLocaleString()));
console.log(formatRow(" Total tokens:", metrics.tokens.totalTokens.toLocaleString()));
if (args.detailed) {
if (metrics.tokens.cacheReadTokens > 0) {
console.log(formatRow(" Cache read:", metrics.tokens.cacheReadTokens.toLocaleString()));
}
if (metrics.tokens.reasoningTokens > 0) {
console.log(formatRow(" Reasoning:", metrics.tokens.reasoningTokens.toLocaleString()));
}
}
// Cost summary
console.log("");
console.log(chalk.bold("Cost Summary:"));
console.log(formatRow(" Total cost:", formatCost(metrics.totalCost ?? 0)));
// Cost by provider
if (args.byProvider !== false &&
metrics.costByProvider &&
metrics.costByProvider.length > 0) {
console.log("");
console.log(chalk.bold("Cost by Provider:"));
const sortedProviders = [...metrics.costByProvider].sort((a, b) => b.totalCost - a.totalCost);
for (const provider of sortedProviders) {
console.log(` ${chalk.cyan(provider.provider.padEnd(15))} ${formatCost(provider.totalCost)}`);
console.log(chalk.gray(` ${provider.requestCount} requests, avg ${formatCost(provider.avgCostPerRequest)}/req`));
}
}
// Cost by model
if (args.byModel !== false &&
metrics.costByModel &&
metrics.costByModel.length > 0) {
console.log("");
console.log(chalk.bold("Cost by Model:"));
const sortedModels = [...metrics.costByModel].sort((a, b) => b.totalCost - a.totalCost);
for (const model of sortedModels) {
console.log(` ${chalk.cyan(model.model)}`);
console.log(` Cost: ${formatCost(model.totalCost)}`);
console.log(chalk.gray(` ${model.requestCount} requests, avg ${formatCost(model.avgCostPerRequest)}/req`));
if (args.detailed) {
console.log(chalk.gray(` ${model.inputTokens.toLocaleString()} input, ${model.outputTokens.toLocaleString()} output tokens`));
}
}
}
// Request statistics
console.log("");
console.log(chalk.bold("Request Statistics:"));
console.log(formatRow(" Total requests:", metrics.totalSpans.toLocaleString()));
console.log(formatRow(" Successful:", metrics.successfulSpans.toLocaleString()));
console.log(formatRow(" Failed:", metrics.failedSpans.toLocaleString()));
console.log(formatRow(" Success rate:", `${(metrics.successRate * 100).toFixed(2)}%`));
// Latency (if detailed)
if (args.detailed && metrics.latency.count > 0) {
console.log("");
console.log(chalk.bold("Latency (ms):"));
console.log(formatRow(" P50:", metrics.latency.p50.toFixed(2)));
console.log(formatRow(" P95:", metrics.latency.p95.toFixed(2)));
console.log(formatRow(" P99:", metrics.latency.p99.toFixed(2)));
}
// Tracking duration
if (metrics.trackingDurationMs) {
const durationSec = metrics.trackingDurationMs / 1000;
const throughput = metrics.totalSpans > 0 ? metrics.totalSpans / durationSec : 0;
console.log("");
console.log(chalk.gray(`Tracking: ${durationSec.toFixed(1)}s (${throughput.toFixed(2)} req/s)`));
}
console.log("");
}
}
catch (error) {
if (spinner)
spinner.fail("Failed to get statistics");
logger.error("Error:", error instanceof Error ? error.message : String(error));
process.exit(1);
}
},
};
}
}
/**
* Validate exporter configuration
*/
function validateExporterConfig(exporter, config) {
const requiredFieldsMap = {
langfuse: ["publicKey", "secretKey"],
langsmith: ["apiKey"],
otel: ["endpoint"],
datadog: ["apiKey"],
sentry: ["dsn"],
braintrust: ["apiKey", "projectName"],
arize: ["spaceKey", "apiKey"],
posthog: ["apiKey"],
laminar: ["apiKey"],
};
const requiredFields = requiredFieldsMap[exporter];
const missingFields = requiredFields.filter((field) => !(field in config) || !config[field]);
if (missingFields.length > 0) {
return {
valid: false,
error: `Missing required fields: ${missingFields.join(", ")}`,
requiredFields,
};
}
return { valid: true };
}
/**
* Get environment variable names for an exporter
*/
function getExporterEnvVars(exporter) {
const envVarsMap = {
langfuse: {
LANGFUSE_PUBLIC_KEY: "your-public-key",
LANGFUSE_SECRET_KEY: "your-secret-key",
LANGFUSE_BASEURL: "https://cloud.langfuse.com (optional)",
},
langsmith: {
LANGCHAIN_API_KEY: "your-api-key",
LANGCHAIN_PROJECT: "your-project-name (optional)",
LANGCHAIN_ENDPOINT: "https://api.smith.langchain.com (optional)",
},
otel: {
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
OTEL_SERVICE_NAME: "your-service-name",
OTEL_EXPORTER_OTLP_PROTOCOL: "http (or grpc)",
},
datadog: {
DD_API_KEY: "your-api-key",
DD_SITE: "datadoghq.com (or regional site)",
DD_SERVICE: "your-service-name",
},
sentry: {
SENTRY_DSN: "your-dsn-url",
SENTRY_TRACES_SAMPLE_RATE: "1.0 (optional)",
SENTRY_RELEASE: "your-release-version (optional)",
},
braintrust: {
BRAINTRUST_API_KEY: "your-api-key",
BRAINTRUST_PROJECT: "your-project-name",
},
arize: {
ARIZE_SPACE_KEY: "your-space-key",
ARIZE_API_KEY: "your-api-key",
},
posthog: {
POSTHOG_API_KEY: "your-api-key",
POSTHOG_HOST: "https://app.posthog.com (optional)",
},
laminar: {
LAMINAR_API_KEY: "your-api-key",
LAMINAR_BASE_URL: "https://api.laminar.run (optional)",
},
};
return envVarsMap[exporter];
}
/**
* Get description for an exporter
*/
function getExporterDescription(exporter) {
const descriptions = {
langfuse: "Open-source LLM observability platform with traces and analytics",
langsmith: "LangChain's platform for LLM application debugging and monitoring",
otel: "OpenTelemetry Protocol (OTLP) for distributed tracing",
datadog: "APM and infrastructure monitoring platform",
sentry: "Error tracking and performance monitoring",
braintrust: "AI evaluation and experimentation platform",
arize: "ML observability platform for model monitoring",
posthog: "Product analytics with LLM event tracking",
laminar: "LLM application monitoring and debugging",
};
return descriptions[exporter];
}
//# sourceMappingURL=telemetry.js.map