#!/usr/bin/env node /** * T3MP3ST CLI * * Command-line interface for T3MP3ST operations. */ import { Command } from 'commander'; import inquirer from 'inquirer'; import chalk from 'chalk'; import boxen from 'boxen'; import ora from 'ora'; import Table from 'cli-table3'; import gradient from 'gradient-string'; import figlet from 'figlet'; import { createAutoTempest, createTestTempest, createStealthOperation, createAggressiveOperation, getBanner, type Tempest, type OperatorArchetype, } from './index.js'; import { config, hasApiKey, setApiKey, getConfiguredProviders, AVAILABLE_MODELS, } from './config/index.js'; import { LLMBackbone } from './llm/index.js'; // ============================================================================= // DISPLAY HELPERS // ============================================================================= function showBanner(): void { try { const text = figlet.textSync('T3MP3ST', { font: 'ANSI Shadow', horizontalLayout: 'default', }); console.log(gradient.pastel.multiline(text)); } catch { console.log(chalk.cyan(getBanner())); } console.log(chalk.gray(' Tactical Execution Multi-agent Platform for Elite Security Testing\n')); } function showBox(title: string, content: string, borderColor: string = 'cyan'): void { console.log( boxen(content, { title, titleAlignment: 'center', padding: 1, margin: { top: 0, bottom: 1, left: 1, right: 1 }, borderStyle: 'round', borderColor: borderColor as any, }) ); } function showSuccess(message: string): void { console.log(chalk.green('✓ ') + message); } function showError(message: string): void { console.log(chalk.red('✗ ') + message); } function showInfo(message: string): void { console.log(chalk.blue('ℹ ') + message); } function showWarning(message: string): void { console.log(chalk.yellow('⚠ ') + message); } // ============================================================================= // STATUS DISPLAY // ============================================================================= function displayStatus(tempest: Tempest): void { const status = tempest.command.getStatus(); console.log(''); console.log(chalk.bold.cyan('═══ OPERATION STATUS ═══')); console.log(''); // Operation Info console.log(chalk.bold('Operation:'), status.name); console.log(chalk.bold('Status:'), status.running ? (status.paused ? chalk.yellow('PAUSED') : chalk.green('RUNNING')) : chalk.gray('STOPPED')); console.log(chalk.bold('Tick Count:'), status.tickCount); console.log(''); // Operators Table const opTable = new Table({ head: [ chalk.cyan('Status'), chalk.cyan('Count'), ], style: { head: [], border: [] }, }); opTable.push( ['Available', chalk.green(status.operators.available.toString())], ['Busy', chalk.yellow(status.operators.busy.toString())], ['Cooldown', chalk.blue(status.operators.cooldown.toString())], ['Burned', chalk.red(status.operators.burned.toString())], [chalk.bold('Total'), chalk.bold(status.operators.total.toString())], ); console.log(chalk.bold('Operators:')); console.log(opTable.toString()); console.log(''); // Findings Summary const findingsTable = new Table({ head: [ chalk.cyan('Severity'), chalk.cyan('Count'), ], style: { head: [], border: [] }, }); findingsTable.push( [chalk.red('Critical'), status.vault.bySeverity.critical.toString()], [chalk.magenta('High'), status.vault.bySeverity.high.toString()], [chalk.yellow('Medium'), status.vault.bySeverity.medium.toString()], [chalk.blue('Low'), status.vault.bySeverity.low.toString()], [chalk.gray('Info'), status.vault.bySeverity.info.toString()], ); console.log(chalk.bold('Findings:')); console.log(findingsTable.toString()); console.log(''); // OPSEC Status const opsecColor = status.opsec.riskLevel === 'critical' ? chalk.red : status.opsec.riskLevel === 'high' ? chalk.yellow : status.opsec.riskLevel === 'medium' ? chalk.blue : chalk.green; console.log(chalk.bold('OPSEC:'), opsecColor(status.opsec.riskLevel.toUpperCase())); console.log(chalk.bold('Detections:'), `${status.opsec.activeDetections}/${status.opsec.totalDetections}`); if (status.opsec.abortRecommended) { console.log(chalk.red.bold('⚠ ABORT RECOMMENDED')); } console.log(''); } // ============================================================================= // INTERACTIVE MODE // ============================================================================= async function interactiveMode(): Promise { showBanner(); // Check for API keys const providers = getConfiguredProviders().filter(p => p !== 'mock'); if (providers.length === 0) { showBox( 'Setup Required', `No API keys configured. Please run setup first: ${chalk.cyan('npx t3mp3st setup')} Or set environment variables: ${chalk.gray('OPENROUTER_API_KEY=your-key-here')}`, 'yellow' ); const { runSetup } = await inquirer.prompt([ { type: 'confirm', name: 'runSetup', message: 'Would you like to run setup now?', default: true, }, ]); if (runSetup) { // Run setup const { execSync } = await import('child_process'); execSync('npx tsx src/setup.ts', { stdio: 'inherit' }); } return; } // Welcome message showBox( 'Welcome to T3MP3ST', `Configured providers: ${providers.map(p => chalk.cyan(p)).join(', ')} Default: ${chalk.cyan(config.get('defaultProvider'))} / ${chalk.cyan(config.get('defaultModel'))}`, 'cyan' ); // Main menu loop let running = true; let tempest: Tempest | null = null; while (running) { const choices = [ { name: '🚀 Start New Operation', value: 'new' }, ]; if (tempest) { choices.push( { name: '📊 View Status', value: 'status' }, { name: '👤 Spawn Operator', value: 'spawn' }, { name: '🎯 Add Target', value: 'target' }, { name: '📋 Create Mission', value: 'mission' }, { name: '💬 Chat with AI', value: 'chat' }, { name: '📝 Generate Report', value: 'report' }, { name: '⏹️ Stop Operation', value: 'stop' }, ); } choices.push( { name: '⚙️ Settings', value: 'settings' }, { name: '🚪 Exit', value: 'exit' }, ); const { action } = await inquirer.prompt([ { type: 'list', name: 'action', message: tempest ? `[${tempest.command.name}] What would you like to do?` : 'What would you like to do?', choices, }, ]); try { switch (action) { case 'new': tempest = await startNewOperation(); break; case 'status': if (tempest) displayStatus(tempest); break; case 'spawn': if (tempest) await spawnOperator(tempest); break; case 'target': if (tempest) await addTarget(tempest); break; case 'mission': if (tempest) await createMission(tempest); break; case 'chat': if (tempest) await chatWithAI(tempest); break; case 'report': if (tempest) await generateReport(tempest); break; case 'stop': if (tempest) { tempest.command.stop(); showSuccess('Operation stopped'); tempest = null; } break; case 'settings': await openSettings(); break; case 'exit': if (tempest) { const { confirmExit } = await inquirer.prompt([ { type: 'confirm', name: 'confirmExit', message: 'Operation is running. Are you sure you want to exit?', default: false, }, ]); if (!confirmExit) continue; tempest.command.stop(); } running = false; break; } } catch (error) { showError(`Error: ${error instanceof Error ? error.message : String(error)}`); } } console.log(chalk.gray('\nGoodbye!\n')); } async function startNewOperation(): Promise { const { name, type } = await inquirer.prompt([ { type: 'input', name: 'name', message: 'Operation name:', default: `Operation ${Date.now().toString(36).toUpperCase()}`, }, { type: 'list', name: 'type', message: 'Operation type:', choices: [ { name: 'Auto (use configured defaults)', value: 'auto' }, { name: 'Stealth (minimal detection)', value: 'stealth' }, { name: 'Aggressive (maximum speed)', value: 'aggressive' }, { name: 'Test (mock LLM)', value: 'test' }, ], }, ]); const spinner = ora('Initializing operation...').start(); let tempest: Tempest; switch (type) { case 'stealth': tempest = createStealthOperation(name); break; case 'aggressive': tempest = createAggressiveOperation(name); break; case 'test': tempest = createTestTempest(name); break; default: tempest = createAutoTempest(name); } tempest.command.start(); spinner.succeed(`Operation "${name}" started!`); displayStatus(tempest); return tempest; } async function spawnOperator(tempest: Tempest): Promise { const archetypes: OperatorArchetype[] = [ 'recon', 'scanner', 'exploiter', 'infiltrator', 'exfiltrator', 'ghost', 'coordinator', 'analyst', ]; const { archetype, callsign } = await inquirer.prompt([ { type: 'list', name: 'archetype', message: 'Select operator archetype:', choices: archetypes.map(a => ({ name: `${a.charAt(0).toUpperCase() + a.slice(1)}`, value: a, })), }, { type: 'input', name: 'callsign', message: 'Operator callsign:', default: (answers: { archetype: string }) => `${answers.archetype.charAt(0).toUpperCase() + answers.archetype.slice(1)}-${tempest.cell.getAllOperators().length + 1}`, }, ]); const operator = tempest.command.spawnOperator(callsign, archetype); showSuccess(`Operator ${callsign} (${archetype}) spawned!`); showInfo(`ID: ${operator.id}`); } async function addTarget(tempest: Tempest): Promise { const { address, type, zone } = await inquirer.prompt([ { type: 'input', name: 'address', message: 'Target address (URL or IP):', validate: (input: string) => input.length > 0 || 'Address is required', }, { type: 'list', name: 'type', message: 'Target type:', choices: [ 'web_application', 'api', 'network', 'host', 'database', 'cloud', ], }, { type: 'list', name: 'zone', message: 'Target zone:', choices: ['external', 'dmz', 'internal', 'restricted'], }, ]); const target = tempest.targetEnv.addTarget({ name: address, type, zone, address, }); showSuccess(`Target "${address}" added!`); showInfo(`ID: ${target.id}`); } async function createMission(tempest: Tempest): Promise { const { name, objectives } = await inquirer.prompt([ { type: 'input', name: 'name', message: 'Mission name:', default: 'Primary Mission', }, { type: 'input', name: 'objectives', message: 'Objectives (comma-separated):', default: 'Enumerate attack surface, Identify vulnerabilities, Document findings', }, ]); const mission = tempest.mission.createMission({ name, objectives: objectives.split(',').map((o: string) => o.trim()), }); tempest.mission.startMission(mission.id); showSuccess(`Mission "${name}" created and started!`); showInfo(`ID: ${mission.id}`); } async function chatWithAI(tempest: Tempest): Promise { showInfo('Chat mode - type "exit" to return to menu\n'); let chatting = true; while (chatting) { const { message } = await inquirer.prompt([ { type: 'input', name: 'message', message: chalk.cyan('You:'), validate: (input: string) => input.length > 0 || 'Enter a message', }, ]); if (message.toLowerCase() === 'exit') { chatting = false; continue; } const spinner = ora('Thinking...').start(); try { const response = await tempest.llm.prompt(message); spinner.stop(); console.log(chalk.green('AI:'), response); console.log(''); } catch (error) { spinner.fail('Error'); showError(`${error instanceof Error ? error.message : String(error)}`); } } } async function generateReport(tempest: Tempest): Promise { const mission = tempest.mission.getActiveMission(); if (!mission) { // Create a temporary mission for report const tempMission = tempest.mission.createMission({ name: 'Ad-hoc Report', objectives: ['Generate findings report'], }); tempest.mission.startMission(tempMission.id); } try { const report = tempest.command.generateReport(); console.log(''); console.log(chalk.bold.cyan('═══ ENGAGEMENT REPORT ═══')); console.log(''); console.log(report); } catch (error) { showWarning('No findings to report yet.'); } } async function openSettings(): Promise { const { setting } = await inquirer.prompt([ { type: 'list', name: 'setting', message: 'Settings:', choices: [ { name: 'View current configuration', value: 'view' }, { name: 'Change default provider', value: 'provider' }, { name: 'Change default model', value: 'model' }, { name: 'Add/update API key', value: 'apikey' }, { name: 'Back', value: 'back' }, ], }, ]); switch (setting) { case 'view': const settings = config.getAll(); console.log(''); console.log(chalk.bold('Current Configuration:')); console.log(chalk.cyan(' Provider:'), settings.defaultProvider); console.log(chalk.cyan(' Model:'), settings.defaultModel); console.log(chalk.cyan(' OpenRouter:'), hasApiKey('openrouter') ? chalk.green('configured') : chalk.red('not set')); console.log(chalk.cyan(' Venice:'), hasApiKey('venice') ? chalk.green('configured') : chalk.red('not set')); console.log(chalk.cyan(' Anthropic:'), hasApiKey('anthropic') ? chalk.green('configured') : chalk.red('not set')); console.log(chalk.cyan(' OpenAI:'), hasApiKey('openai') ? chalk.green('configured') : chalk.red('not set')); console.log(''); break; case 'provider': const providers = getConfiguredProviders().filter(p => p !== 'mock'); if (providers.length === 0) { showWarning('No API keys configured'); break; } const { provider } = await inquirer.prompt([ { type: 'list', name: 'provider', message: 'Select default provider:', choices: providers, default: config.get('defaultProvider'), }, ]); config.setDefaultProvider(provider); showSuccess(`Default provider set to: ${provider}`); break; case 'model': { const currentProvider = config.get('defaultProvider'); const availableModels = AVAILABLE_MODELS[currentProvider]; if (!availableModels || availableModels.length === 0) { showWarning(`No models available for ${currentProvider}`); break; } const { selectedModel } = await inquirer.prompt([ { type: 'list', name: 'selectedModel', message: 'Select default model:', choices: availableModels.map(m => ({ name: `${m.name} (${m.id}) - ${m.contextWindow.toLocaleString()} ctx`, value: m.id, })), default: config.get('defaultModel'), }, ]); config.setDefaultModel(currentProvider, selectedModel); showSuccess(`Default model set to: ${selectedModel}`); break; } case 'apikey': const { keyProvider } = await inquirer.prompt([ { type: 'list', name: 'keyProvider', message: 'Which provider?', choices: ['openrouter', 'venice', 'anthropic', 'openai'], }, ]); const { apiKey } = await inquirer.prompt([ { type: 'password', name: 'apiKey', message: `Enter ${keyProvider} API key:`, mask: '*', }, ]); if (apiKey) { setApiKey(keyProvider, apiKey); showSuccess(`${keyProvider} API key saved!`); } break; } } // ============================================================================= // CLI COMMANDS // ============================================================================= const program = new Command(); program .name('t3mp3st') .description('T3MP3ST - Tactical Execution Multi-agent Platform for Elite Security Testing') .version('1.0.0'); program .command('interactive', { isDefault: true }) .description('Start interactive mode') .action(interactiveMode); program .command('setup') .description('Run the setup wizard') .action(async () => { const { execSync } = await import('child_process'); try { execSync('npx tsx src/setup.ts', { stdio: 'inherit', cwd: process.cwd() }); } catch { // Setup script handles its own errors } }); program .command('status') .description('Show current configuration status') .action(() => { showBanner(); const settings = config.getAll(); console.log(chalk.bold('Configuration Status:\n')); const table = new Table({ style: { head: [], border: [] }, }); table.push( [chalk.cyan('Default Provider'), settings.defaultProvider], [chalk.cyan('Default Model'), settings.defaultModel], [chalk.cyan('OpenRouter API Key'), hasApiKey('openrouter') ? chalk.green('✓ Configured') : chalk.red('✗ Not set')], [chalk.cyan('Venice API Key'), hasApiKey('venice') ? chalk.green('✓ Configured') : chalk.red('✗ Not set')], [chalk.cyan('Anthropic API Key'), hasApiKey('anthropic') ? chalk.green('✓ Configured') : chalk.red('✗ Not set')], [chalk.cyan('OpenAI API Key'), hasApiKey('openai') ? chalk.green('✓ Configured') : chalk.red('✗ Not set')], [chalk.cyan('Config Path'), config.getConfigPath()], ); console.log(table.toString()); console.log(''); }); program .command('test') .description('Test the LLM connection') .action(async () => { showBanner(); const provider = config.get('defaultProvider'); if (!hasApiKey(provider as any)) { showError(`No API key configured for ${provider}`); showInfo('Run "npx t3mp3st setup" to configure API keys'); return; } const spinner = ora(`Testing connection to ${provider}...`).start(); try { const llm = new LLMBackbone(config.getLLMConfig()); const response = await llm.prompt('Say "Connection successful!" and nothing else.'); spinner.succeed('Connection successful!'); console.log(''); console.log(chalk.cyan('Response:'), response); console.log(''); } catch (error) { spinner.fail('Connection failed'); showError(`${error instanceof Error ? error.message : String(error)}`); } }); program .command('models') .description('List available models') .action(() => { showBanner(); const provider = config.get('defaultProvider'); const models = AVAILABLE_MODELS[provider]; console.log(chalk.bold(`Available models for ${provider}:\n`)); const table = new Table({ head: [chalk.cyan('Model ID'), chalk.cyan('Name'), chalk.cyan('Context')], style: { head: [], border: [] }, }); for (const model of models) { table.push([ model.id, model.name, `${model.contextWindow.toLocaleString()} tokens`, ]); } console.log(table.toString()); console.log(''); }); // Run CLI program.parse();