@lanonasis/cli
Version:
LanOnasis Enterprise CLI - Memory as a Service, API Key Management, and Infrastructure Orchestration
813 lines (812 loc) • 35.7 kB
JavaScript
import { Command } from 'commander';
import inquirer from 'inquirer';
import chalk from 'chalk';
import Table from 'cli-table3';
import { apiClient } from '../utils/api.js';
import { formatDate, truncateText } from '../utils/formatting.js';
// Enhanced VPS-style color scheme
const colors = {
primary: chalk.blue.bold,
success: chalk.green,
warning: chalk.yellow,
error: chalk.red,
info: chalk.cyan,
accent: chalk.magenta,
muted: chalk.gray,
highlight: chalk.white.bold
};
const apiKeysCommand = new Command('api-keys')
.alias('keys')
.description(colors.info('🔐 Manage API keys securely with enterprise-grade encryption'));
// ============================================================================
// PROJECT COMMANDS
// ============================================================================
const projectsCommand = new Command('projects')
.description(colors.accent('📁 Manage API key projects and organization'));
projectsCommand
.command('create')
.description('Create a new API key project')
.option('-n, --name <name>', 'Project name')
.option('-d, --description [description]', 'Project description')
.option('-o, --organization-id <id>', 'Organization ID')
.option('--interactive', 'Interactive mode')
.action(async (options) => {
try {
let projectData = {
name: options.name,
description: options.description,
organizationId: options.organizationId
};
if (options.interactive || !projectData.name || !projectData.organizationId) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Project name:',
when: !projectData.name,
validate: (input) => input.length > 0 || 'Project name is required'
},
{
type: 'input',
name: 'description',
message: 'Project description (optional):',
when: !projectData.description
},
{
type: 'input',
name: 'organizationId',
message: 'Organization ID:',
when: !projectData.organizationId,
validate: (input) => {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(input) || 'Please enter a valid UUID';
}
}
]);
projectData = { ...projectData, ...answers };
}
const project = await apiClient.post('/api-keys/projects', projectData);
console.log(chalk.green('✅ Project created successfully!'));
console.log(chalk.blue(`Project ID: ${project.id}`));
console.log(chalk.blue(`Name: ${project.name}`));
if (project.description) {
console.log(chalk.blue(`Description: ${project.description}`));
}
}
catch (error) {
console.error(chalk.red('❌ Failed to create project:'), error.message);
process.exit(1);
}
});
projectsCommand
.command('list')
.alias('ls')
.description('List all API key projects')
.option('--json', 'Output as JSON')
.action(async (options) => {
try {
const projects = await apiClient.get('/api-keys/projects');
if (options.json) {
console.log(JSON.stringify(projects, null, 2));
return;
}
if (projects.length === 0) {
console.log(chalk.yellow('No projects found'));
return;
}
const table = new Table({
head: ['ID', 'Name', 'Description', 'Owner', 'Created'].map(h => chalk.cyan(h)),
style: { head: [], border: [] }
});
projects.forEach((project) => {
table.push([
truncateText(project.id, 20),
project.name,
truncateText(project.description || '-', 30),
truncateText(project.ownerId, 20),
formatDate(project.createdAt)
]);
});
console.log(table.toString());
console.log(chalk.gray(`Total: ${projects.length} projects`));
}
catch (error) {
console.error(chalk.red('❌ Failed to list projects:'), error.message);
process.exit(1);
}
});
// ============================================================================
// API KEY COMMANDS
// ============================================================================
apiKeysCommand
.command('create')
.description('Create a new API key')
.option('-n, --name <name>', 'API key name')
.option('-v, --value <value>', 'API key value')
.option('-t, --type <type>', 'Key type (api_key, database_url, oauth_token, etc.)')
.option('-e, --environment <env>', 'Environment (development, staging, production)')
.option('-p, --project-id <id>', 'Project ID')
.option('--access-level <level>', 'Access level (public, authenticated, team, admin, enterprise)')
.option('--tags <tags>', 'Comma-separated tags')
.option('--expires-at <date>', 'Expiration date (ISO format)')
.option('--rotation-frequency <days>', 'Rotation frequency in days', '90')
.option('--interactive', 'Interactive mode')
.action(async (options) => {
try {
let keyData = {
name: options.name,
value: options.value,
keyType: options.type,
environment: options.environment || 'development',
projectId: options.projectId,
accessLevel: options.accessLevel || 'team',
tags: options.tags ? options.tags.split(',').map((tag) => tag.trim()) : [],
expiresAt: options.expiresAt,
rotationFrequency: parseInt(options.rotationFrequency)
};
if (options.interactive || !keyData.name || !keyData.value || !keyData.projectId) {
const projects = await apiClient.get('/api-keys/projects');
const answers = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'API key name:',
when: !keyData.name,
validate: (input) => input.length > 0 || 'Name is required'
},
{
type: 'password',
name: 'value',
message: 'API key value:',
when: !keyData.value,
validate: (input) => input.length > 0 || 'Value is required'
},
{
type: 'list',
name: 'keyType',
message: 'Key type:',
when: !keyData.keyType,
choices: [
'api_key',
'database_url',
'oauth_token',
'certificate',
'ssh_key',
'webhook_secret',
'encryption_key'
]
},
{
type: 'list',
name: 'environment',
message: 'Environment:',
choices: ['development', 'staging', 'production'],
default: 'development'
},
{
type: 'list',
name: 'projectId',
message: 'Select project:',
when: !keyData.projectId && projects.length > 0,
choices: projects.map((p) => ({ name: `${p.name} (${p.id})`, value: p.id }))
},
{
type: 'list',
name: 'accessLevel',
message: 'Access level:',
choices: ['public', 'authenticated', 'team', 'admin', 'enterprise'],
default: 'team'
},
{
type: 'input',
name: 'tags',
message: 'Tags (comma-separated, optional):',
filter: (input) => input ? input.split(',').map((tag) => tag.trim()) : []
},
{
type: 'input',
name: 'expiresAt',
message: 'Expiration date (YYYY-MM-DD, optional):',
validate: (input) => {
if (!input)
return true;
const date = new Date(input);
return !isNaN(date.getTime()) || 'Please enter a valid date';
},
filter: (input) => input ? new Date(input).toISOString() : undefined
},
{
type: 'number',
name: 'rotationFrequency',
message: 'Rotation frequency (days):',
default: 90,
validate: (input) => input > 0 && input <= 365 || 'Must be between 1 and 365 days'
}
]);
keyData = { ...keyData, ...answers };
}
const apiKey = await apiClient.post('/api-keys', keyData);
console.log(colors.success('🔐 API key created successfully!'));
console.log(colors.info('━'.repeat(50)));
console.log(`${colors.highlight('Key ID:')} ${colors.primary(apiKey.id)}`);
console.log(`${colors.highlight('Name:')} ${colors.accent(apiKey.name)}`);
console.log(`${colors.highlight('Type:')} ${colors.info(apiKey.keyType)}`);
console.log(`${colors.highlight('Environment:')} ${colors.accent(apiKey.environment)}`);
console.log(colors.info('━'.repeat(50)));
console.log(colors.warning('⚠️ The key value is securely encrypted and cannot be retrieved later.'));
}
catch (error) {
console.error(colors.error('✖ Failed to create API key:'), colors.muted(error.message));
process.exit(1);
}
});
apiKeysCommand
.command('list')
.alias('ls')
.description('List API keys')
.option('-p, --project-id <id>', 'Filter by project ID')
.option('--json', 'Output as JSON')
.action(async (options) => {
try {
let url = '/api-keys';
if (options.projectId) {
url += `?projectId=${options.projectId}`;
}
const apiKeys = await apiClient.get(url);
if (options.json) {
console.log(JSON.stringify(apiKeys, null, 2));
return;
}
if (apiKeys.length === 0) {
console.log(colors.warning('⚠️ No API keys found'));
console.log(colors.muted('Run: lanonasis api-keys create'));
return;
}
console.log(colors.primary('🔐 API Key Management'));
console.log(colors.info('═'.repeat(80)));
const table = new Table({
head: ['ID', 'Name', 'Type', 'Environment', 'Status', 'Usage', 'Last Rotated'].map(h => colors.accent(h)),
style: { head: [], border: [] }
});
apiKeys.forEach((key) => {
const statusColor = key.status === 'active' ? colors.success :
key.status === 'rotating' ? colors.warning : colors.error;
table.push([
truncateText(key.id, 20),
key.name,
key.keyType,
key.environment,
statusColor(key.status),
colors.highlight(key.usageCount.toString()),
formatDate(key.lastRotated)
]);
});
console.log(table.toString());
console.log(colors.info('═'.repeat(80)));
console.log(colors.muted(`🔢 Total: ${colors.highlight(apiKeys.length)} API keys`));
}
catch (error) {
console.error(colors.error('✖ Failed to list API keys:'), colors.muted(error.message));
process.exit(1);
}
});
apiKeysCommand
.command('get')
.description('Get details of a specific API key')
.argument('<keyId>', 'API key ID')
.option('--json', 'Output as JSON')
.action(async (keyId, options) => {
try {
const apiKey = await apiClient.get(`/api-keys/${keyId}`);
if (options.json) {
console.log(JSON.stringify(apiKey, null, 2));
return;
}
console.log(colors.primary('🔍 API Key Details'));
console.log(colors.info('═'.repeat(60)));
console.log(`${colors.highlight('ID:')} ${colors.primary(apiKey.id)}`);
console.log(`${colors.highlight('Name:')} ${colors.accent(apiKey.name)}`);
console.log(`${colors.highlight('Type:')} ${colors.info(apiKey.keyType)}`);
console.log(`${colors.highlight('Environment:')} ${colors.accent(apiKey.environment)}`);
console.log(`${colors.highlight('Project ID:')} ${colors.muted(apiKey.projectId)}`);
console.log(`${colors.highlight('Access Level:')} ${colors.warning(apiKey.accessLevel)}`);
const statusColor = apiKey.status === 'active' ? colors.success :
apiKey.status === 'rotating' ? colors.warning : colors.error;
console.log(`${colors.highlight('Status:')} ${statusColor(apiKey.status)}`);
console.log(`${colors.highlight('Usage Count:')} ${colors.accent(apiKey.usageCount)}`);
console.log(`${colors.highlight('Tags:')} ${colors.muted(apiKey.tags.join(', ') || 'None')}`);
console.log(`${colors.highlight('Rotation Frequency:')} ${colors.info(apiKey.rotationFrequency)} days`);
console.log(`${colors.highlight('Last Rotated:')} ${colors.muted(formatDate(apiKey.lastRotated))}`);
console.log(`${colors.highlight('Created:')} ${colors.muted(formatDate(apiKey.createdAt))}`);
console.log(`${colors.highlight('Updated:')} ${colors.muted(formatDate(apiKey.updatedAt))}`);
if (apiKey.expiresAt) {
console.log(`${colors.highlight('Expires:')} ${colors.warning(formatDate(apiKey.expiresAt))}`);
}
console.log(colors.info('═'.repeat(60)));
}
catch (error) {
console.error(colors.error('✖ Failed to get API key:'), colors.muted(error.message));
process.exit(1);
}
});
apiKeysCommand
.command('update')
.description('Update an API key')
.argument('<keyId>', 'API key ID')
.option('-n, --name <name>', 'New name')
.option('-v, --value <value>', 'New value')
.option('--tags <tags>', 'Comma-separated tags')
.option('--rotation-frequency <days>', 'Rotation frequency in days')
.option('--interactive', 'Interactive mode')
.action(async (keyId, options) => {
try {
let updateData = {};
if (options.name)
updateData.name = options.name;
if (options.value)
updateData.value = options.value;
if (options.tags)
updateData.tags = options.tags.split(',').map((tag) => tag.trim());
if (options.rotationFrequency)
updateData.rotationFrequency = parseInt(options.rotationFrequency);
if (options.interactive || Object.keys(updateData).length === 0) {
const current = await apiClient.get(`/api-keys/${keyId}`);
const answers = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Name:',
default: current.name,
when: !updateData.name
},
{
type: 'confirm',
name: 'updateValue',
message: 'Update the key value?',
default: false,
when: !updateData.value
},
{
type: 'password',
name: 'value',
message: 'New key value:',
when: (answers) => answers.updateValue
},
{
type: 'input',
name: 'tags',
message: 'Tags (comma-separated):',
default: current.tags.join(', '),
filter: (input) => input.split(',').map((tag) => tag.trim()),
when: !updateData.tags
},
{
type: 'number',
name: 'rotationFrequency',
message: 'Rotation frequency (days):',
default: current.rotationFrequency,
validate: (input) => input > 0 && input <= 365 || 'Must be between 1 and 365 days',
when: !updateData.rotationFrequency
}
]);
updateData = { ...updateData, ...answers };
delete updateData.updateValue;
}
const updatedKey = await apiClient.put(`/api-keys/${keyId}`, updateData);
console.log(colors.success('🔄 API key updated successfully!'));
console.log(colors.info('━'.repeat(40)));
console.log(`${colors.highlight('Name:')} ${colors.accent(updatedKey.name)}`);
console.log(`${colors.highlight('Status:')} ${colors.success(updatedKey.status)}`);
if (updateData.value) {
console.log(colors.warning('⚠️ The key value has been updated and re-encrypted.'));
}
console.log(colors.info('━'.repeat(40)));
}
catch (error) {
console.error(colors.error('✖ Failed to update API key:'), colors.muted(error.message));
process.exit(1);
}
});
apiKeysCommand
.command('delete')
.alias('rm')
.description('Delete an API key')
.argument('<keyId>', 'API key ID')
.option('-f, --force', 'Skip confirmation')
.action(async (keyId, options) => {
try {
if (!options.force) {
const apiKey = await apiClient.get(`/api-keys/${keyId}`);
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: `Are you sure you want to delete "${apiKey.name}"? This action cannot be undone.`,
default: false
}
]);
if (!confirm) {
console.log(colors.warning('🚫 Operation cancelled'));
return;
}
}
await apiClient.delete(`/api-keys/${keyId}`);
console.log(colors.success('🗑️ API key deleted successfully!'));
}
catch (error) {
console.error(colors.error('✖ Failed to delete API key:'), colors.muted(error.message));
process.exit(1);
}
});
// ============================================================================
// MCP COMMANDS
// ============================================================================
const mcpCommand = new Command('mcp')
.description(colors.accent('🤖 Model Context Protocol (MCP) - Secure AI agent access'));
mcpCommand
.command('register-tool')
.description('Register a new MCP tool')
.option('--tool-id <id>', 'Tool ID')
.option('--tool-name <name>', 'Tool name')
.option('--organization-id <id>', 'Organization ID')
.option('--keys <keys>', 'Comma-separated list of accessible key names')
.option('--environments <envs>', 'Comma-separated list of environments')
.option('--max-sessions <num>', 'Maximum concurrent sessions', '3')
.option('--max-duration <seconds>', 'Maximum session duration in seconds', '900')
.option('--webhook-url <url>', 'Webhook URL for notifications')
.option('--auto-approve', 'Enable auto-approval for low-risk requests')
.option('--risk-level <level>', 'Risk level (low, medium, high, critical)', 'medium')
.option('--interactive', 'Interactive mode')
.action(async (options) => {
try {
let toolData = {
toolId: options.toolId,
toolName: options.toolName,
organizationId: options.organizationId,
permissions: {
keys: options.keys ? options.keys.split(',').map((k) => k.trim()) : [],
environments: options.environments ? options.environments.split(',').map((e) => e.trim()) : ['development'],
maxConcurrentSessions: parseInt(options.maxSessions),
maxSessionDuration: parseInt(options.maxDuration)
},
webhookUrl: options.webhookUrl,
autoApprove: options.autoApprove || false,
riskLevel: options.riskLevel
};
if (options.interactive || !toolData.toolId || !toolData.toolName || !toolData.organizationId) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'toolId',
message: 'Tool ID:',
when: !toolData.toolId,
validate: (input) => input.length > 0 || 'Tool ID is required'
},
{
type: 'input',
name: 'toolName',
message: 'Tool name:',
when: !toolData.toolName,
validate: (input) => input.length > 0 || 'Tool name is required'
},
{
type: 'input',
name: 'organizationId',
message: 'Organization ID:',
when: !toolData.organizationId,
validate: (input) => {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(input) || 'Please enter a valid UUID';
}
},
{
type: 'input',
name: 'keys',
message: 'Accessible key names (comma-separated):',
filter: (input) => input.split(',').map((k) => k.trim()),
when: toolData.permissions.keys.length === 0
},
{
type: 'checkbox',
name: 'environments',
message: 'Accessible environments:',
choices: ['development', 'staging', 'production'],
default: ['development']
},
{
type: 'number',
name: 'maxConcurrentSessions',
message: 'Maximum concurrent sessions:',
default: 3,
validate: (input) => input > 0 && input <= 10 || 'Must be between 1 and 10'
},
{
type: 'number',
name: 'maxSessionDuration',
message: 'Maximum session duration (seconds):',
default: 900,
validate: (input) => input >= 60 && input <= 3600 || 'Must be between 60 and 3600 seconds'
},
{
type: 'input',
name: 'webhookUrl',
message: 'Webhook URL (optional):'
},
{
type: 'confirm',
name: 'autoApprove',
message: 'Enable auto-approval?',
default: false
},
{
type: 'list',
name: 'riskLevel',
message: 'Risk level:',
choices: ['low', 'medium', 'high', 'critical'],
default: 'medium'
}
]);
if (answers.keys)
toolData.permissions.keys = answers.keys;
if (answers.environments)
toolData.permissions.environments = answers.environments;
if (answers.maxConcurrentSessions)
toolData.permissions.maxConcurrentSessions = answers.maxConcurrentSessions;
if (answers.maxSessionDuration)
toolData.permissions.maxSessionDuration = answers.maxSessionDuration;
toolData = { ...toolData, ...answers };
delete toolData.keys;
delete toolData.environments;
delete toolData.maxConcurrentSessions;
delete toolData.maxSessionDuration;
}
const tool = await apiClient.post('/api-keys/mcp/tools', toolData);
console.log(colors.success('🤖 MCP tool registered successfully!'));
console.log(colors.info('━'.repeat(50)));
console.log(`${colors.highlight('Tool ID:')} ${colors.primary(tool.toolId)}`);
console.log(`${colors.highlight('Name:')} ${colors.accent(tool.toolName)}`);
console.log(`${colors.highlight('Risk Level:')} ${colors.warning(tool.riskLevel)}`);
console.log(`${colors.highlight('Auto Approve:')} ${tool.autoApprove ? colors.success('Yes') : colors.error('No')}`);
console.log(colors.info('━'.repeat(50)));
}
catch (error) {
console.error(colors.error('✖ Failed to register MCP tool:'), colors.muted(error.message));
process.exit(1);
}
});
mcpCommand
.command('list-tools')
.description('List registered MCP tools')
.option('--json', 'Output as JSON')
.action(async (options) => {
try {
const tools = await apiClient.get('/api-keys/mcp/tools');
if (options.json) {
console.log(JSON.stringify(tools, null, 2));
return;
}
if (tools.length === 0) {
console.log(colors.warning('⚠️ No MCP tools found'));
console.log(colors.muted('Run: lanonasis api-keys mcp register-tool'));
return;
}
console.log(colors.primary('🤖 Registered MCP Tools'));
console.log(colors.info('═'.repeat(80)));
const table = new Table({
head: ['Tool ID', 'Name', 'Risk Level', 'Status', 'Auto Approve', 'Created'].map(h => colors.accent(h)),
style: { head: [], border: [] }
});
tools.forEach((tool) => {
const statusColor = tool.status === 'active' ? colors.success :
tool.status === 'suspended' ? colors.error : colors.warning;
table.push([
tool.toolId,
tool.toolName,
tool.riskLevel,
statusColor(tool.status),
tool.autoApprove ? colors.success('Yes') : colors.error('No'),
formatDate(tool.createdAt)
]);
});
console.log(table.toString());
console.log(colors.info('═'.repeat(80)));
console.log(colors.muted(`🤖 Total: ${colors.highlight(tools.length)} MCP tools`));
}
catch (error) {
console.error(colors.error('✖ Failed to list MCP tools:'), colors.muted(error.message));
process.exit(1);
}
});
mcpCommand
.command('request-access')
.description('Request access to API keys via MCP')
.option('--tool-id <id>', 'Tool ID')
.option('--organization-id <id>', 'Organization ID')
.option('--keys <keys>', 'Comma-separated list of key names')
.option('--environment <env>', 'Environment (development, staging, production)')
.option('--justification <text>', 'Justification for access')
.option('--duration <seconds>', 'Estimated duration in seconds', '900')
.option('--interactive', 'Interactive mode')
.action(async (options) => {
try {
let requestData = {
toolId: options.toolId,
organizationId: options.organizationId,
keyNames: options.keys ? options.keys.split(',').map((k) => k.trim()) : [],
environment: options.environment,
justification: options.justification,
estimatedDuration: parseInt(options.duration),
context: {}
};
if (options.interactive || !requestData.toolId || !requestData.organizationId ||
requestData.keyNames.length === 0 || !requestData.environment || !requestData.justification) {
const tools = await apiClient.get('/api-keys/mcp/tools');
const answers = await inquirer.prompt([
{
type: 'list',
name: 'toolId',
message: 'Select MCP tool:',
when: !requestData.toolId && tools.length > 0,
choices: tools.map((t) => ({ name: `${t.toolName} (${t.toolId})`, value: t.toolId }))
},
{
type: 'input',
name: 'organizationId',
message: 'Organization ID:',
when: !requestData.organizationId,
validate: (input) => {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(input) || 'Please enter a valid UUID';
}
},
{
type: 'input',
name: 'keyNames',
message: 'Key names to access (comma-separated):',
when: requestData.keyNames.length === 0,
filter: (input) => input.split(',').map((k) => k.trim()),
validate: (input) => input.length > 0 || 'At least one key name is required'
},
{
type: 'list',
name: 'environment',
message: 'Environment:',
when: !requestData.environment,
choices: ['development', 'staging', 'production']
},
{
type: 'input',
name: 'justification',
message: 'Justification for access:',
when: !requestData.justification,
validate: (input) => input.length > 0 || 'Justification is required'
},
{
type: 'number',
name: 'estimatedDuration',
message: 'Estimated duration (seconds):',
default: 900,
validate: (input) => input >= 60 && input <= 3600 || 'Must be between 60 and 3600 seconds'
}
]);
requestData = { ...requestData, ...answers };
}
const response = await apiClient.post('/api-keys/mcp/request-access', requestData);
console.log(colors.success('🔐 Access request created successfully!'));
console.log(colors.info('━'.repeat(50)));
console.log(`${colors.highlight('Request ID:')} ${colors.primary(response.requestId)}`);
console.log(`${colors.highlight('Status:')} ${colors.accent(response.status)}`);
console.log(colors.info('━'.repeat(50)));
console.log(colors.warning('💡 Check the status with: lanonasis api-keys analytics usage'));
}
catch (error) {
console.error(colors.error('✖ Failed to create access request:'), colors.muted(error.message));
process.exit(1);
}
});
// ============================================================================
// ANALYTICS COMMANDS
// ============================================================================
const analyticsCommand = new Command('analytics')
.description('View API key usage analytics and security events');
analyticsCommand
.command('usage')
.description('View usage analytics')
.option('--key-id <id>', 'Filter by specific API key')
.option('--days <days>', 'Number of days to look back', '30')
.option('--json', 'Output as JSON')
.action(async (options) => {
try {
let url = '/api-keys/analytics/usage';
const params = new URLSearchParams();
if (options.keyId)
params.append('keyId', options.keyId);
if (options.days)
params.append('days', options.days);
if (params.toString()) {
url += `?${params.toString()}`;
}
const analytics = await apiClient.get(url);
if (options.json) {
console.log(JSON.stringify(analytics, null, 2));
return;
}
if (analytics.length === 0) {
console.log(chalk.yellow('No usage data found'));
return;
}
const table = new Table({
head: ['Key ID', 'Operation', 'Tool ID', 'Success', 'Timestamp'].map(h => chalk.cyan(h)),
style: { head: [], border: [] }
});
analytics.forEach((entry) => {
const successColor = entry.success ? colors.success('✓') : colors.error('✗');
table.push([
truncateText(entry.keyId || '-', 20),
entry.operation,
truncateText(entry.toolId || '-', 15),
successColor,
formatDate(entry.timestamp)
]);
});
console.log(table.toString());
console.log(colors.info('═'.repeat(80)));
console.log(colors.muted(`📈 Total: ${colors.highlight(analytics.length)} events`));
}
catch (error) {
console.error(colors.error('✖ Failed to get usage analytics:'), colors.muted(error.message));
process.exit(1);
}
});
analyticsCommand
.command('security-events')
.description('View security events')
.option('--severity <level>', 'Filter by severity (low, medium, high, critical)')
.option('--json', 'Output as JSON')
.action(async (options) => {
try {
let url = '/api-keys/analytics/security-events';
if (options.severity) {
url += `?severity=${options.severity}`;
}
const events = await apiClient.get(url);
if (options.json) {
console.log(JSON.stringify(events, null, 2));
return;
}
if (events.length === 0) {
console.log(colors.success('✅ No security events found'));
return;
}
console.log(colors.primary('🛡️ Security Events Monitor'));
console.log(colors.info('═'.repeat(80)));
const table = new Table({
head: ['Event Type', 'Severity', 'Description', 'Resolved', 'Timestamp'].map(h => colors.accent(h)),
style: { head: [], border: [] }
});
events.forEach((event) => {
const severityColor = event.severity === 'critical' ? colors.error :
event.severity === 'high' ? colors.accent :
event.severity === 'medium' ? colors.warning : colors.success;
table.push([
event.eventType,
severityColor(event.severity.toUpperCase()),
truncateText(event.description, 40),
event.resolved ? colors.success('✓') : colors.warning('Pending'),
formatDate(event.timestamp)
]);
});
console.log(table.toString());
console.log(colors.info('═'.repeat(80)));
console.log(colors.muted(`🛡️ Total: ${colors.highlight(events.length)} security events`));
}
catch (error) {
console.error(colors.error('✖ Failed to get security events:'), colors.muted(error.message));
process.exit(1);
}
});
// Add subcommands
apiKeysCommand.addCommand(projectsCommand);
apiKeysCommand.addCommand(mcpCommand);
apiKeysCommand.addCommand(analyticsCommand);
export default apiKeysCommand;