@ace-sdk/cli
Version:
ACE CLI - Command-line tool for intelligent pattern learning and playbook management
146 lines ⢠6.38 kB
JavaScript
/**
* Usage history command - time-windowed API usage analytics
*
* @package @ace-sdk/cli
* @since v2.10.0
*/
import { globalOptions } from '../cli.js';
import { createContext } from '../types/config.js';
import { ACEServerClient } from '../services/server-client.js';
import { Logger } from '../services/logger.js';
import chalk from 'chalk';
import { isSubscriptionError, QuotaExceededError, PaymentRequiredError, AccountBlockedError, TokenExpiredError } from '@ace-sdk/core';
import { formatNumber } from '../utils/usage-display.js';
const VALID_WINDOWS = ['1h', '6h', '12h', '1d', '7d', '14d', '30d'];
/**
* Show usage history with time-windowed buckets
*/
export async function usageCommand(options) {
const logger = new Logger(globalOptions);
// Validate window parameter
const window = (options.window || '1d');
if (!VALID_WINDOWS.includes(window)) {
logger.error(chalk.red(`Invalid window: ${options.window}. Valid: ${VALID_WINDOWS.join(', ')}`));
process.exit(1);
}
let spinner = logger.spinner(`Fetching usage history (${window})...`);
try {
const context = await createContext({ org: globalOptions.org, project: globalOptions.project });
const client = new ACEServerClient(context, logger);
const result = await client.getUsageHistory({
window,
projectId: options.project || undefined
});
spinner?.succeed('Usage history retrieved');
if (logger.isJson()) {
logger.output(result);
}
else {
logger.info(chalk.bold(`\nUsage History (${result.window}, ${result.granularity})\n`));
if (result.project_id) {
logger.info(` ${chalk.cyan('Project:')} ${result.project_id}`);
}
logger.info(` ${chalk.cyan('Org:')} ${result.org_id}\n`);
if (result.buckets.length === 0) {
logger.info(chalk.dim(' No activity in this time window.\n'));
}
else {
// Table header
const isHourly = result.granularity === 'hourly';
logger.info(chalk.bold(` ${'Period'.padEnd(isHourly ? 18 : 14)} ` +
`${'API'.padStart(6)} ` +
`${'Patterns'.padStart(10)} ` +
`${'Traces'.padStart(8)} ` +
`${'Bootstrap'.padStart(10)}`));
logger.info(chalk.dim(' ' + '-'.repeat(isHourly ? 56 : 52)));
for (const bucket of result.buckets) {
const period = formatPeriod(bucket.period, isHourly);
logger.info(` ${chalk.cyan(period.padEnd(isHourly ? 18 : 14))} ` +
`${formatNumber(bucket.api_calls_total).padStart(6)} ` +
`${formatPatternSummary(bucket).padStart(10)} ` +
`${formatNumber(bucket.traces_submitted).padStart(8)} ` +
`${formatNumber(bucket.bootstrap_runs).padStart(10)}`);
}
// Totals
logger.info(chalk.dim(' ' + '-'.repeat(isHourly ? 56 : 52)));
logger.info(chalk.bold(` ${'Totals'.padEnd(isHourly ? 18 : 14)} ` +
`${formatNumber(result.totals.api_calls_total).padStart(6)} ` +
`${formatNumber(result.totals.patterns_created).padStart(10)} ` +
`${formatNumber(result.totals.traces_submitted).padStart(8)}`));
}
logger.info('');
}
}
catch (error) {
spinner?.fail('Failed to fetch usage history');
// Handle token expiry errors (v2.11.0+)
if (error instanceof TokenExpiredError) {
if (logger.isJson()) {
logger.error(JSON.stringify({ error: 'token_expired', message: error.message }));
}
else {
logger.error(chalk.red('\nš Session expired (7-day hard cap reached)'));
logger.error(chalk.dim(' Your session has reached its maximum lifetime.'));
logger.error(chalk.cyan(' Run: ace-cli login\n'));
}
process.exit(1);
}
if (isSubscriptionError(error)) {
if (logger.isJson()) {
logger.error(JSON.stringify({
error: error.name,
message: error.message,
upgrade_url: 'upgradeUrl' in error ? error.upgradeUrl : 'https://ace-ai.app/pricing'
}));
}
else {
if (error instanceof QuotaExceededError) {
logger.error(chalk.yellow(`\n Quota exceeded: ${error.message}`));
}
else if (error instanceof PaymentRequiredError) {
logger.error(chalk.yellow(`\n Account is in read-only mode: ${error.message}`));
}
else if (error instanceof AccountBlockedError) {
logger.error(chalk.red(`\n Account is blocked: ${error.message}`));
}
else {
logger.error(chalk.red(`\n ${error.message}`));
}
}
process.exit(1);
}
if (logger.isJson()) {
logger.error(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
}
else {
logger.error(chalk.red(`\n Error: ${error instanceof Error ? error.message : String(error)}\n`));
}
process.exit(1);
}
}
function formatPeriod(period, isHourly) {
const date = new Date(period);
if (isHourly) {
return date.toLocaleString('en-US', {
month: 'short', day: 'numeric',
hour: '2-digit', minute: '2-digit', hour12: false
});
}
return date.toLocaleDateString('en-US', {
month: 'short', day: 'numeric', year: 'numeric'
});
}
function formatPatternSummary(bucket) {
const created = bucket.patterns_created;
const updated = bucket.patterns_updated;
const deleted = bucket.patterns_deleted;
const parts = [];
if (created > 0)
parts.push(`+${created}`);
if (updated > 0)
parts.push(`~${updated}`);
if (deleted > 0)
parts.push(`-${deleted}`);
return parts.length > 0 ? parts.join('/') : '0';
}
//# sourceMappingURL=usage.js.map