index.js•28.2 kB
#!/usr/bin/env node
import dotenv from 'dotenv';
import axios from 'axios';
// Load environment variables
dotenv.config();
const API_KEY = process.env.DATADOG_API_KEY;
const APP_KEY = process.env.DATADOG_APP_KEY;
const SITE = process.env.DATADOG_SITE || 'datadoghq.com';
if (!API_KEY || !APP_KEY) {
console.error('Error: DATADOG_API_KEY and DATADOG_APP_KEY must be set in .env file');
process.exit(1);
}
const client = axios.create({
baseURL: `https://api.${SITE}`,
headers: {
'DD-API-KEY': API_KEY,
'DD-APPLICATION-KEY': APP_KEY,
'Content-Type': 'application/json'
}
});
/**
* Query Datadog metrics with custom query string (from Metric Explorer)
* Supports queries like:
* - avg:kubernetes.cpu.usage.total{kube_namespace:pijarsekolah-prod}
* - avg:kubernetes.memory.usage{kube_namespace:pijarsekolah-prod} by {kube_deployment}
* - max:kubernetes.cpu.usage.total{kube_deployment:pijarsekolah-mail-api}
*/
async function queryMetrics(query, from, to) {
try {
const response = await client.get('/api/v1/query', {
params: {
query,
from,
to
}
});
return response.data;
} catch (error) {
console.error('Error querying metrics:', error.response?.data || error.message);
throw error;
}
}
/**
* Advanced metrics query with support for metric expressions
* Handles complex queries with aggregations, filters, and grouping
*/
async function queryMetricsAdvanced(metricExpression, from, to, options = {}) {
try {
const params = {
query: metricExpression,
from,
to
};
if (options.pointCount) {
params.interpolation = options.interpolation || 'linear';
}
const response = await client.get('/api/v1/query', {
params
});
return response.data;
} catch (error) {
console.error('Error in advanced query:', error.response?.data || error.message);
throw error;
}
}
/**
* Query APM metrics (services, endpoints, traces)
* Examples:
* - trace.express.request.duration
* - trace.web.request.duration
* - trace.db.query.duration
*/
async function queryAPMMetrics(serviceName, from, to, options = {}) {
try {
const metric = options.metric || 'trace.web.request.duration';
const aggregator = options.aggregator || 'avg';
const groupBy = options.groupBy ? ` by {${options.groupBy.join(',')}}` : '';
const query = `${aggregator}:${metric}{service:${serviceName}}${groupBy}`;
const response = await client.get('/api/v1/query', {
params: {
query,
from,
to
}
});
return response.data;
} catch (error) {
console.error('Error querying APM metrics:', error.response?.data || error.message);
throw error;
}
}
/**
* Query Host metrics (CPU, Memory, Network, Disk)
* Examples:
* - system.cpu.user
* - system.memory.usable
* - system.net.bytes_rcvd
* - system.disk.used
*/
async function queryHostMetrics(hostname, from, to, options = {}) {
try {
const metric = options.metric || 'system.cpu.user';
const aggregator = options.aggregator || 'avg';
const query = `${aggregator}:${metric}{host:${hostname}}`;
const response = await client.get('/api/v1/query', {
params: {
query,
from,
to
}
});
return response.data;
} catch (error) {
console.error('Error querying host metrics:', error.response?.data || error.message);
throw error;
}
}
/**
* Query Host CPU and Memory metrics in one call
*/
async function queryHostCpuMemory(hostname, from, to, options = {}) {
try {
const aggregator = options.aggregator || 'avg';
// Query both CPU and Memory
const cpuQuery = `${aggregator}:system.cpu.user{host:${hostname}}`;
const memQuery = `${aggregator}:system.memory.usable{host:${hostname}}`;
const [cpuResponse, memResponse] = await Promise.all([
client.get('/api/v1/query', {
params: { query: cpuQuery, from, to }
}),
client.get('/api/v1/query', {
params: { query: memQuery, from, to }
})
]);
return {
cpu: cpuResponse.data,
memory: memResponse.data,
hostname,
period: { from, to }
};
} catch (error) {
console.error('Error querying host CPU/Memory:', error.response?.data || error.message);
throw error;
}
}
/**
* Query DBM (Database Monitoring) metrics
* Examples:
* - postgresql.queries.active
* - postgresql.replication_delay
* - mysql.queries
* - mysql.replication_delay
*/
async function queryDBMMetrics(dbHost, from, to, options = {}) {
try {
const dbType = options.dbType || 'postgresql';
const metric = options.metric || `${dbType}.queries.active`;
const aggregator = options.aggregator || 'avg';
const groupBy = options.groupBy ? ` by {${options.groupBy.join(',')}}` : '';
const query = `${aggregator}:${metric}{dbhost:${dbHost}}${groupBy}`;
const response = await client.get('/api/v1/query', {
params: {
query,
from,
to
}
});
return response.data;
} catch (error) {
console.error('Error querying DBM metrics:', error.response?.data || error.message);
throw error;
}
}
/**
* Get list of APM services
*/
async function getAPMServices(from, to) {
try {
const query = `avg:trace.web.request.count{*} by {service}`;
const response = await client.get('/api/v1/query', {
params: {
query,
from,
to
}
});
const services = new Set();
if (response.data.series) {
response.data.series.forEach(series => {
const match = series.scope?.match(/service:([^,}]+)/);
if (match) {
services.add(match[1]);
}
});
}
return Array.from(services);
} catch (error) {
console.error('Error getting APM services:', error.response?.data || error.message);
throw error;
}
}
/**
* Get list of monitored hosts
*/
async function getMonitoredHosts(from, to) {
try {
const query = `avg:system.cpu.user{*} by {host}`;
const response = await client.get('/api/v1/query', {
params: {
query,
from,
to
}
});
const hosts = new Set();
if (response.data.series) {
response.data.series.forEach(series => {
const match = series.scope?.match(/host:([^,}]+)/);
if (match) {
hosts.add(match[1]);
}
});
}
return Array.from(hosts);
} catch (error) {
console.error('Error getting monitored hosts:', error.response?.data || error.message);
throw error;
}
}
/**
* Get list of monitored databases (DBM)
*/
async function getMonitoredDatabases(from, to, options = {}) {
try {
const dbType = options.dbType || 'postgresql';
const query = `avg:${dbType}.queries.active{*} by {dbhost}`;
const response = await client.get('/api/v1/query', {
params: {
query,
from,
to
}
});
const databases = new Set();
if (response.data.series) {
response.data.series.forEach(series => {
const match = series.scope?.match(/dbhost:([^,}]+)/);
if (match) {
databases.add(match[1]);
}
});
}
return Array.from(databases);
} catch (error) {
console.error('Error getting monitored databases:', error.response?.data || error.message);
throw error;
}
}
/**
* Get all deployments in namespace
*/
async function getAllDeployments(namespace, from, to) {
try {
const query = `avg:kubernetes.cpu.usage.total{kube_namespace:${namespace}} by {kube_deployment}`;
const response = await client.get('/api/v1/query', {
params: {
query,
from,
to
}
});
const deployments = new Set();
if (response.data.series) {
response.data.series.forEach(series => {
const match = series.scope?.match(/kube_deployment:([^,}]+)/);
if (match) {
deployments.add(match[1]);
}
});
}
return Array.from(deployments);
} catch (error) {
console.error('Error getting deployments:', error.response?.data || error.message);
throw error;
}
}
/**
* Query metrics for specific deployment
*/
async function getDeploymentMetrics(namespace, deployment, metricType, from, to) {
try {
const queries = {
cpu: `avg:kubernetes.cpu.usage.total{kube_namespace:${namespace},kube_deployment:${deployment}}`,
memory: `avg:kubernetes.memory.usage{kube_namespace:${namespace},kube_deployment:${deployment}}`,
cpu_max: `max:kubernetes.cpu.usage.total{kube_namespace:${namespace},kube_deployment:${deployment}}`,
memory_max: `max:kubernetes.memory.usage{kube_namespace:${namespace},kube_deployment:${deployment}}`
};
const query = queries[metricType];
if (!query) {
throw new Error(`Unknown metric type: ${metricType}`);
}
const response = await client.get('/api/v1/query', {
params: {
query,
from,
to
}
});
return response.data;
} catch (error) {
console.error(`Error querying ${metricType} for ${deployment}:`, error.response?.data || error.message);
throw error;
}
}
/**
* Batch query multiple deployments
*/
async function batchQueryDeployments(namespace, deployments, metricType, from, to) {
const results = {};
for (const deployment of deployments) {
try {
console.log(`Querying ${deployment}...`);
const data = await getDeploymentMetrics(namespace, deployment, metricType, from, to);
results[deployment] = data;
} catch (error) {
console.error(`Failed to query ${deployment}:`, error.message);
results[deployment] = { error: error.message };
}
}
return results;
}
/**
* Parse metric data into structured format
*/
function parseMetricsData(data, deployment) {
if (!data.series || data.series.length === 0) {
return null;
}
const series = data.series[0];
const points = series.pointlist || [];
if (points.length === 0) {
return null;
}
const values = points.map(p => p[1]);
const timestamps = points.map(p => new Date(p[0]));
return {
deployment,
metric: series.metric,
unit: series.unit?.[0]?.shortName || 'unknown',
points: points.length,
min: Math.min(...values),
max: Math.max(...values),
avg: values.reduce((a, b) => a + b, 0) / values.length,
data: points
};
}
/**
* Aggregate data to hourly
*/
function aggregateToHourly(metricData) {
if (!metricData || !metricData.data) {
return null;
}
const hourly = {};
metricData.data.forEach(([timestamp, value]) => {
const date = new Date(timestamp);
const hourKey = date.toISOString().substring(0, 13) + ':00:00Z';
if (!hourly[hourKey]) {
hourly[hourKey] = [];
}
hourly[hourKey].push(value);
});
const result = {};
Object.entries(hourly).forEach(([hour, values]) => {
result[hour] = {
min: Math.min(...values),
max: Math.max(...values),
avg: values.reduce((a, b) => a + b, 0) / values.length,
count: values.length
};
});
return result;
}
// CLI Interface
async function main() {
const args = process.argv.slice(2);
const command = args[0];
if (!command) {
console.log(`
MCP Datadog CLI
===============
Commands:
list-deployments <namespace> <from> <to> List all deployments in namespace
query-cpu <namespace> <deployment> <from> <to> Query CPU metrics
query-memory <namespace> <deployment> <from> <to> Query memory metrics
batch-query <namespace> <from> <to> <metric-type> Query all deployments
query-metrics <query> <from> <to> Query with custom metric expression
query-metrics-export <query> <from> <to> <filename> Query and export to JSON file
Custom Query Examples (from Metric Explorer):
node index.js query-metrics "avg:kubernetes.cpu.usage.total{kube_namespace:pijarsekolah-prod}" 1732646400 1732732799
node index.js query-metrics "avg:kubernetes.cpu.usage.total{kube_namespace:pijarsekolah-prod} by {kube_deployment}" 1732646400 1732732799
node index.js query-metrics "max:kubernetes.memory.usage{kube_deployment:pijarsekolah-mail-api}" 1732646400 1732732799
node index.js query-metrics-export "avg:kubernetes.cpu.usage.total{kube_namespace:pijarsekolah-prod} by {kube_deployment}" 1732646400 1732732799 result.json
Standard Examples:
node index.js list-deployments pijarsekolah-prod 1732646400 1732732799
node index.js query-cpu pijarsekolah-prod pijarsekolah-mail-api 1732646400 1732732799
node index.js batch-query pijarsekolah-prod 1732646400 1732732799 cpu
`);
return;
}
try {
if (command === 'list-deployments') {
const [namespace, from, to] = args.slice(1);
console.log(`Fetching deployments from ${namespace}...`);
const deployments = await getAllDeployments(namespace, parseInt(from), parseInt(to));
console.log(`\nFound ${deployments.length} deployments:\n`);
deployments.forEach(d => console.log(` - ${d}`));
console.log(`\n✓ Complete list exported`);
}
else if (command === 'query-cpu') {
const [namespace, deployment, from, to] = args.slice(1);
const data = await getDeploymentMetrics(namespace, deployment, 'cpu', parseInt(from), parseInt(to));
const parsed = parseMetricsData(data, deployment);
if (parsed) {
console.log(`\nCPU Metrics for ${deployment}:`);
console.log(` Min: ${parsed.min.toFixed(2)} ${parsed.unit}`);
console.log(` Max: ${parsed.max.toFixed(2)} ${parsed.unit}`);
console.log(` Avg: ${parsed.avg.toFixed(2)} ${parsed.unit}`);
console.log(` Data points: ${parsed.points}`);
const hourly = aggregateToHourly(parsed);
console.log(`\nHourly Aggregation:`);
Object.entries(hourly).slice(0, 5).forEach(([hour, stats]) => {
console.log(` ${hour}: min=${stats.min.toFixed(2)}, avg=${stats.avg.toFixed(2)}, max=${stats.max.toFixed(2)}`);
});
} else {
console.log(`\nNo data found for ${deployment}`);
}
}
else if (command === 'query-memory') {
const [namespace, deployment, from, to] = args.slice(1);
const data = await getDeploymentMetrics(namespace, deployment, 'memory', parseInt(from), parseInt(to));
const parsed = parseMetricsData(data, deployment);
if (parsed) {
console.log(`\nMemory Metrics for ${deployment}:`);
console.log(` Min: ${(parsed.min / 1024 / 1024).toFixed(2)} MB`);
console.log(` Max: ${(parsed.max / 1024 / 1024).toFixed(2)} MB`);
console.log(` Avg: ${(parsed.avg / 1024 / 1024).toFixed(2)} MB`);
console.log(` Data points: ${parsed.points}`);
} else {
console.log(`\nNo data found for ${deployment}`);
}
}
else if (command === 'batch-query') {
const [namespace, from, to, metricType] = args.slice(1);
console.log(`Fetching all deployments...`);
const deployments = await getAllDeployments(namespace, parseInt(from), parseInt(to));
console.log(`Found ${deployments.length} deployments. Querying ${metricType} metrics...\n`);
const results = await batchQueryDeployments(namespace, deployments, metricType, parseInt(from), parseInt(to));
console.log(`\n\nResults Summary:`);
const header = ['Deployment'.padEnd(45), 'Min'.padEnd(15), 'Avg'.padEnd(15), 'Max'.padEnd(15)].join(' ');
console.log(header);
console.log('-'.repeat(90));
Object.entries(results).forEach(([deployment, data]) => {
if (data.error) {
const line = [deployment.padEnd(45), `ERROR: ${data.error}`].join(' ');
console.log(line);
} else {
const parsed = parseMetricsData(data, deployment);
if (parsed) {
const unit = metricType === 'memory' ? 'MB' : 'mcores';
const divisor = metricType === 'memory' ? 1024 * 1024 : 1000000;
const line = [
deployment.padEnd(45),
(parsed.min/divisor).toFixed(2).padEnd(15),
(parsed.avg/divisor).toFixed(2).padEnd(15),
(parsed.max/divisor).toFixed(2).padEnd(15)
].join(' ');
console.log(line);
}
}
});
}
else if (command === 'apm-services') {
// apm-services from to
// Lists all APM services in the given time period
const from = parseInt(args[1]);
const to = parseInt(args[2]);
console.log('Fetching APM services...\n');
const services = await getAPMServices(from, to);
console.log(`Found ${services.length} APM services:\n`);
services.forEach((service, index) => {
console.log(`${index + 1}. ${service}`);
});
}
else if (command === 'apm-metrics') {
// apm-metrics service_name from to [--metric metric_name] [--aggregator avg|max|min|sum]
const serviceName = args[1];
const from = parseInt(args[2]);
const to = parseInt(args[3]);
const options = {};
for (let i = 4; i < args.length; i++) {
if (args[i] === '--metric' && args[i + 1]) {
options.metric = args[++i];
}
if (args[i] === '--aggregator' && args[i + 1]) {
options.aggregator = args[++i];
}
}
console.log(`Querying APM metrics for service: ${serviceName}\n`);
const data = await queryAPMMetrics(serviceName, from, to, options);
if (data.series && data.series.length > 0) {
console.log(`Found ${data.series.length} metric series:\n`);
data.series.forEach((series, index) => {
console.log(`Series ${index + 1}: ${series.metric}`);
console.log(` Scope: ${series.scope}`);
console.log(` Data points: ${series.pointlist?.length || 0}`);
if (series.pointlist && series.pointlist.length > 0) {
const values = series.pointlist.map(p => p[1]);
console.log(` Min: ${Math.min(...values).toFixed(2)}, Max: ${Math.max(...values).toFixed(2)}, Avg: ${(values.reduce((a, b) => a + b) / values.length).toFixed(2)}`);
}
});
} else {
console.log('No data found');
}
}
else if (command === 'hosts') {
// hosts from to
// Lists all monitored hosts
const from = parseInt(args[1]);
const to = parseInt(args[2]);
console.log('Fetching monitored hosts...\n');
const hosts = await getMonitoredHosts(from, to);
console.log(`Found ${hosts.length} monitored hosts:\n`);
hosts.forEach((host, index) => {
console.log(`${index + 1}. ${host}`);
});
}
else if (command === 'host-metrics') {
// host-metrics hostname from to [--metric metric_name] [--aggregator avg|max|min|sum]
const hostname = args[1];
const from = parseInt(args[2]);
const to = parseInt(args[3]);
const options = {};
for (let i = 4; i < args.length; i++) {
if (args[i] === '--metric' && args[i + 1]) {
options.metric = args[++i];
}
if (args[i] === '--aggregator' && args[i + 1]) {
options.aggregator = args[++i];
}
}
console.log(`Querying metrics for host: ${hostname}\n`);
const data = await queryHostMetrics(hostname, from, to, options);
if (data.series && data.series.length > 0) {
console.log(`Found ${data.series.length} metric series:\n`);
data.series.forEach((series, index) => {
console.log(`Series ${index + 1}: ${series.metric}`);
console.log(` Scope: ${series.scope}`);
console.log(` Data points: ${series.pointlist?.length || 0}`);
if (series.pointlist && series.pointlist.length > 0) {
const values = series.pointlist.map(p => p[1]);
console.log(` Min: ${Math.min(...values).toFixed(2)}, Max: ${Math.max(...values).toFixed(2)}, Avg: ${(values.reduce((a, b) => a + b) / values.length).toFixed(2)}`);
}
});
} else {
console.log('No data found');
}
}
else if (command === 'host-cpu-memory') {
// host-cpu-memory hostname from to [--aggregator avg|max|min|sum]
const hostname = args[1];
const from = parseInt(args[2]);
const to = parseInt(args[3]);
const options = {};
for (let i = 4; i < args.length; i++) {
if (args[i] === '--aggregator' && args[i + 1]) {
options.aggregator = args[++i];
}
}
console.log(`Querying CPU & Memory for host: ${hostname}\n`);
const data = await queryHostCpuMemory(hostname, from, to, options);
console.log('CPU Metrics:');
if (data.cpu.series && data.cpu.series.length > 0) {
data.cpu.series.forEach(series => {
if (series.pointlist && series.pointlist.length > 0) {
const values = series.pointlist.map(p => p[1]);
console.log(` Min: ${Math.min(...values).toFixed(2)}%, Max: ${Math.max(...values).toFixed(2)}%, Avg: ${(values.reduce((a, b) => a + b) / values.length).toFixed(2)}%`);
}
});
} else {
console.log(' No data');
}
console.log('\nMemory Metrics:');
if (data.memory.series && data.memory.series.length > 0) {
data.memory.series.forEach(series => {
if (series.pointlist && series.pointlist.length > 0) {
const values = series.pointlist.map(p => p[1] / 1024 / 1024 / 1024);
console.log(` Min: ${Math.min(...values).toFixed(2)} GB, Max: ${Math.max(...values).toFixed(2)} GB, Avg: ${(values.reduce((a, b) => a + b) / values.length).toFixed(2)} GB`);
}
});
} else {
console.log(' No data');
}
}
else if (command === 'databases') {
// databases from to [--type postgresql|mysql|...]
const from = parseInt(args[1]);
const to = parseInt(args[2]);
const options = {};
for (let i = 3; i < args.length; i++) {
if (args[i] === '--type' && args[i + 1]) {
options.dbType = args[++i];
}
}
console.log(`Fetching monitored databases...\n`);
const databases = await getMonitoredDatabases(from, to, options);
console.log(`Found ${databases.length} monitored databases (${options.dbType || 'all'}):\n`);
databases.forEach((db, index) => {
console.log(`${index + 1}. ${db}`);
});
}
else if (command === 'dbm-metrics') {
// dbm-metrics db_host from to [--type postgresql|mysql] [--metric metric_name] [--aggregator avg|max|min|sum]
const dbHost = args[1];
const from = parseInt(args[2]);
const to = parseInt(args[3]);
const options = {};
for (let i = 4; i < args.length; i++) {
if (args[i] === '--type' && args[i + 1]) {
options.dbType = args[++i];
}
if (args[i] === '--metric' && args[i + 1]) {
options.metric = args[++i];
}
if (args[i] === '--aggregator' && args[i + 1]) {
options.aggregator = args[++i];
}
}
console.log(`Querying DBM metrics for database: ${dbHost}\n`);
const data = await queryDBMMetrics(dbHost, from, to, options);
if (data.series && data.series.length > 0) {
console.log(`Found ${data.series.length} metric series:\n`);
data.series.forEach((series, index) => {
console.log(`Series ${index + 1}: ${series.metric}`);
console.log(` Scope: ${series.scope}`);
console.log(` Data points: ${series.pointlist?.length || 0}`);
if (series.pointlist && series.pointlist.length > 0) {
const values = series.pointlist.map(p => p[1]);
console.log(` Min: ${Math.min(...values).toFixed(2)}, Max: ${Math.max(...values).toFixed(2)}, Avg: ${(values.reduce((a, b) => a + b) / values.length).toFixed(2)}`);
}
});
} else {
console.log('No data found');
}
}
else if (command === 'query-metrics') {
// query-metrics "metric_query" from to
const query = args[1];
const from = parseInt(args[2]);
const to = parseInt(args[3]);
if (!query || !from || !to) {
console.error('Usage: query-metrics <query> <from> <to>');
console.error('Example: query-metrics "avg:kubernetes.cpu.usage.total{kube_namespace:pijarsekolah-prod}" 1732646400 1732732799');
process.exit(1);
}
console.log(`\nExecuting query: ${query}`);
console.log(`From: ${new Date(from * 1000).toISOString()}`);
console.log(`To: ${new Date(to * 1000).toISOString()}\n`);
const data = await queryMetricsAdvanced(query, from, to);
if (data.series && data.series.length > 0) {
console.log(`✓ Query successful. Found ${data.series.length} series.\n`);
data.series.forEach((series, index) => {
console.log(`\nSeries ${index + 1}:`);
console.log(` Metric: ${series.metric}`);
console.log(` Scope: ${series.scope || 'N/A'}`);
console.log(` Expression: ${series.expression || 'N/A'}`);
console.log(` Data Points: ${series.pointlist?.length || 0}`);
if (series.pointlist && series.pointlist.length > 0) {
const values = series.pointlist.map(p => p[1]);
const min = Math.min(...values);
const max = Math.max(...values);
const avg = values.reduce((a, b) => a + b, 0) / values.length;
const unit = series.unit?.[0]?.shortName || 'unknown';
console.log(` Min: ${min.toFixed(2)} ${unit}`);
console.log(` Max: ${max.toFixed(2)} ${unit}`);
console.log(` Avg: ${avg.toFixed(2)} ${unit}`);
console.log(`\n First 5 data points:`);
series.pointlist.slice(0, 5).forEach(([ts, val]) => {
const date = new Date(ts);
console.log(` ${date.toISOString()}: ${val.toFixed(2)} ${unit}`);
});
}
});
} else {
console.log(`No data found for this query.`);
}
}
else if (command === 'query-metrics-export') {
// query-metrics-export "metric_query" from to filename
const query = args[1];
const from = parseInt(args[2]);
const to = parseInt(args[3]);
const filename = args[4] || 'metrics-export.json';
if (!query || !from || !to) {
console.error('Usage: query-metrics-export <query> <from> <to> [filename]');
process.exit(1);
}
console.log(`\nExecuting query: ${query}`);
console.log(`Exporting to: ${filename}\n`);
const data = await queryMetricsAdvanced(query, from, to);
// Format export data
const exportData = {
query: query,
timestamp: new Date().toISOString(),
from: new Date(from * 1000).toISOString(),
to: new Date(to * 1000).toISOString(),
seriesCount: data.series?.length || 0,
series: (data.series || []).map(series => ({
metric: series.metric,
scope: series.scope,
expression: series.expression,
unit: series.unit?.[0]?.shortName || 'unknown',
dataPoints: series.pointlist?.length || 0,
data: series.pointlist || []
}))
};
const fs = await import('fs/promises');
await fs.writeFile(filename, JSON.stringify(exportData, null, 2));
console.log(`✓ Data exported successfully to: ${filename}`);
console.log(` Total series: ${exportData.seriesCount}`);
exportData.series.forEach((s, i) => {
console.log(` Series ${i + 1}: ${s.scope} (${s.dataPoints} data points)`);
});
}
else {
console.error(`Unknown command: ${command}`);
process.exit(1);
}
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}
main();