aiwf
Version:
AI Workflow Framework for Claude Code with multi-language support (Korean/English)
312 lines (260 loc) • 10.7 kB
JavaScript
/**
* AIWF 토큰 추적 명령어
* AI 토큰 사용량 모니터링 및 분석
*/
import { ResourceLoader } from '../lib/resource-loader.js';
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
class TokenCommand {
constructor() {
this.resourceLoader = new ResourceLoader();
this.tokenDataPath = path.join(process.cwd(), '.aiwf', 'token-usage');
}
/**
* Token 명령어 실행
*/
async execute(args) {
const [subcommand, ...restArgs] = args;
switch (subcommand) {
case 'status':
await this.showStatus();
break;
case 'report':
await this.generateReport(restArgs);
break;
case 'track':
await this.trackUsage(restArgs);
break;
case 'reset':
await this.resetTracking();
break;
case 'limit':
await this.setLimit(restArgs);
break;
default:
this.showHelp();
break;
}
}
/**
* 토큰 사용 상태 표시
*/
async showStatus() {
try {
// 토큰 추적 모듈 로드
const tokenTrackerModule = await this.resourceLoader.loadUtil('token-tracker.js');
const TokenTracker = tokenTrackerModule.TokenTracker || tokenTrackerModule.default;
if (!TokenTracker) {
throw new Error('TokenTracker 모듈을 로드할 수 없습니다.');
}
const tracker = new TokenTracker({
storageDir: this.tokenDataPath
});
await tracker.init();
const stats = await tracker.getStatistics();
const limits = await this.getLimits();
console.log(chalk.cyan('💰 토큰 사용 현황'));
console.log(chalk.gray('-'.repeat(50)));
// 오늘 사용량
console.log(chalk.bold('오늘 사용량:'));
console.log(` 입력 토큰: ${chalk.yellow(stats.today.input.toLocaleString())}`);
console.log(` 출력 토큰: ${chalk.yellow(stats.today.output.toLocaleString())}`);
console.log(` 총 토큰: ${chalk.yellow(stats.today.total.toLocaleString())}`);
if (limits.daily > 0) {
const dailyUsagePercent = (stats.today.total / limits.daily * 100).toFixed(1);
const dailyColor = dailyUsagePercent > 90 ? 'red' : dailyUsagePercent > 70 ? 'yellow' : 'green';
console.log(` 일일 한도: ${chalk[dailyColor](`${dailyUsagePercent}% 사용`)}`);
}
// 이번 주 사용량
console.log(chalk.bold('\n이번 주 사용량:'));
console.log(` 입력 토큰: ${chalk.yellow(stats.week.input.toLocaleString())}`);
console.log(` 출력 토큰: ${chalk.yellow(stats.week.output.toLocaleString())}`);
console.log(` 총 토큰: ${chalk.yellow(stats.week.total.toLocaleString())}`);
// 이번 달 사용량
console.log(chalk.bold('\n이번 달 사용량:'));
console.log(` 입력 토큰: ${chalk.yellow(stats.month.input.toLocaleString())}`);
console.log(` 출력 토큰: ${chalk.yellow(stats.month.output.toLocaleString())}`);
console.log(` 총 토큰: ${chalk.yellow(stats.month.total.toLocaleString())}`);
if (limits.monthly > 0) {
const monthlyUsagePercent = (stats.month.total / limits.monthly * 100).toFixed(1);
const monthlyColor = monthlyUsagePercent > 90 ? 'red' : monthlyUsagePercent > 70 ? 'yellow' : 'green';
console.log(` 월간 한도: ${chalk[monthlyColor](`${monthlyUsagePercent}% 사용`)}`);
}
// 전체 통계
console.log(chalk.bold('\n전체 통계:'));
console.log(` 총 세션: ${stats.overall.sessions}`);
console.log(` 평균 세션당 토큰: ${Math.round(stats.overall.total / stats.overall.sessions).toLocaleString()}`);
console.log(` 최대 단일 세션: ${stats.overall.maxSession.toLocaleString()} 토큰`);
console.log(chalk.gray('\n' + '-'.repeat(50)));
console.log(chalk.gray('상세 리포트: aiwf token report'));
} catch (error) {
console.error(chalk.red('❌ 토큰 상태 조회 중 오류가 발생했습니다:'), error.message);
}
}
/**
* 토큰 사용 리포트 생성
*/
async generateReport(args) {
const [period = 'week'] = args;
try {
// 토큰 리포터 모듈 로드
const reporterModule = await this.resourceLoader.loadUtil('token-reporter.js');
const TokenReporter = reporterModule.TokenReporter || reporterModule.default;
if (!TokenReporter) {
throw new Error('TokenReporter 모듈을 로드할 수 없습니다.');
}
const reporter = new TokenReporter({
storageDir: this.tokenDataPath
});
console.log(chalk.cyan(`📊 토큰 사용 리포트 (${period})`));
console.log(chalk.gray('생성 중...'));
const report = await reporter.generateReport(period);
const reportPath = path.join(this.tokenDataPath, 'reports', `token-report-${period}-${Date.now()}.md`);
await fs.ensureDir(path.dirname(reportPath));
await fs.writeFile(reportPath, report);
console.log(chalk.green(`✅ 리포트가 생성되었습니다: ${reportPath}`));
// 리포트 요약 표시
console.log(chalk.cyan('\n📄 리포트 요약:'));
const lines = report.split('\n').slice(0, 20);
console.log(lines.join('\n'));
console.log(chalk.gray('\n... (전체 리포트는 파일을 확인하세요)'));
} catch (error) {
console.error(chalk.red('❌ 리포트 생성 중 오류가 발생했습니다:'), error.message);
}
}
/**
* 토큰 사용량 추적
*/
async trackUsage(args) {
const [inputTokens, outputTokens] = args.map(Number);
if (!inputTokens || !outputTokens) {
console.error(chalk.red('❌ 입력 토큰과 출력 토큰 수를 지정해주세요.'));
console.log(chalk.gray('사용법: aiwf token track <입력토큰> <출력토큰>'));
return;
}
try {
const trackerModule = await this.resourceLoader.loadUtil('token-tracker.js');
const TokenTracker = trackerModule.TokenTracker || trackerModule.default;
const tracker = new TokenTracker({
storageDir: this.tokenDataPath
});
await tracker.init();
await tracker.trackUsage({
input: inputTokens,
output: outputTokens,
timestamp: new Date().toISOString(),
source: 'manual'
});
console.log(chalk.green('✅ 토큰 사용량이 기록되었습니다.'));
console.log(`입력: ${chalk.yellow(inputTokens.toLocaleString())} 토큰`);
console.log(`출력: ${chalk.yellow(outputTokens.toLocaleString())} 토큰`);
console.log(`총합: ${chalk.yellow((inputTokens + outputTokens).toLocaleString())} 토큰`);
} catch (error) {
console.error(chalk.red('❌ 토큰 추적 중 오류가 발생했습니다:'), error.message);
}
}
/**
* 토큰 추적 초기화
*/
async resetTracking() {
try {
const confirm = await this.confirmReset();
if (!confirm) {
console.log(chalk.yellow('초기화가 취소되었습니다.'));
return;
}
await fs.remove(this.tokenDataPath);
console.log(chalk.green('✅ 토큰 추적 데이터가 초기화되었습니다.'));
} catch (error) {
console.error(chalk.red('❌ 초기화 중 오류가 발생했습니다:'), error.message);
}
}
/**
* 토큰 사용 한도 설정
*/
async setLimit(args) {
const [limitType, limitValue] = args;
if (!limitType || !limitValue) {
console.error(chalk.red('❌ 한도 유형과 값을 지정해주세요.'));
console.log(chalk.gray('사용법: aiwf token limit <daily|monthly> <값>'));
return;
}
const validTypes = ['daily', 'monthly'];
if (!validTypes.includes(limitType)) {
console.error(chalk.red(`❌ 유효하지 않은 한도 유형: ${limitType}`));
console.log(chalk.gray('유효한 유형: daily, monthly'));
return;
}
const limit = parseInt(limitValue);
if (isNaN(limit) || limit < 0) {
console.error(chalk.red('❌ 한도 값은 0 이상의 숫자여야 합니다.'));
return;
}
try {
const configPath = path.join(this.tokenDataPath, 'limits.json');
await fs.ensureDir(this.tokenDataPath);
let limits = {};
if (fs.existsSync(configPath)) {
limits = await fs.readJson(configPath);
}
limits[limitType] = limit;
await fs.writeJson(configPath, limits, { spaces: 2 });
console.log(chalk.green(`✅ ${limitType === 'daily' ? '일일' : '월간'} 한도가 ${limit.toLocaleString()} 토큰으로 설정되었습니다.`));
} catch (error) {
console.error(chalk.red('❌ 한도 설정 중 오류가 발생했습니다:'), error.message);
}
}
/**
* 한도 설정 가져오기
*/
async getLimits() {
try {
const configPath = path.join(this.tokenDataPath, 'limits.json');
if (fs.existsSync(configPath)) {
return await fs.readJson(configPath);
}
} catch (error) {
// 에러 무시
}
return { daily: 0, monthly: 0 };
}
/**
* 초기화 확인
*/
async confirmReset() {
// 간단한 확인 (실제로는 prompts 패키지 사용 권장)
console.log(chalk.yellow('⚠️ 모든 토큰 추적 데이터가 삭제됩니다.'));
console.log(chalk.gray('계속하려면 5초 내에 Ctrl+C로 취소하세요...'));
await new Promise(resolve => setTimeout(resolve, 5000));
return true;
}
/**
* 도움말 표시
*/
showHelp() {
console.log(chalk.cyan('💰 AIWF 토큰 추적'));
console.log(chalk.gray('-'.repeat(50)));
console.log('사용법: aiwf token <명령어> [옵션]');
console.log('');
console.log('명령어:');
console.log(' status 토큰 사용 현황 표시');
console.log(' report [기간] 사용 리포트 생성');
console.log(' track <입력> <출력> 토큰 사용량 수동 기록');
console.log(' limit <유형> <값> 사용 한도 설정');
console.log(' reset 추적 데이터 초기화');
console.log('');
console.log('리포트 기간:');
console.log(' day 일간 리포트');
console.log(' week 주간 리포트 (기본값)');
console.log(' month 월간 리포트');
console.log('');
console.log('예시:');
console.log(' aiwf token status');
console.log(' aiwf token report week');
console.log(' aiwf token track 1500 500');
console.log(' aiwf token limit daily 100000');
}
}
export default TokenCommand;