@michaelnkomo/cli
Version:
BroCode CLI - AI coding assistant with @ file tagging and multi-language support
202 lines (201 loc) • 7.45 kB
JavaScript
/**
* BroCode CLI Entry Point - Native Node.js version
*/
import readline from 'readline';
import { Agent, ConfigManager, Logger, AuthService } from '@michaelnkomo/core';
import { printBanner, printError, printSuccess, formatResponse, createLoadingSpinner, stopLoadingSpinner, printCommandHelp, } from './utils/ui.js';
// ============================================
// MAIN CLI
// ============================================
async function main() {
try {
// Show banner
printBanner();
// Initialize backend authentication
const backendUrl = process.env.BROCODE_BACKEND_URL || 'https://brocode-backend.vercel.app';
const auth = new AuthService({ backendUrl });
// Check authentication
if (!await auth.isAuthenticated()) {
console.log(' Authentication required...\n');
console.log('BroCode now requires authentication to use the service.');
console.log('This keeps your API key secure and enables usage tracking.\n');
try {
await auth.login();
}
catch (error) {
printError(`Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
process.exit(1);
}
}
// Get session
const session = await auth.getSession();
if (!session) {
printError('Failed to get authentication session');
process.exit(1);
}
// Show user info
console.log(`\n Logged in as: ${session.user.email}`);
console.log(` Tier: ${session.user.tier.toUpperCase()}`);
if (session.user.apiCallsLimit === -1) {
console.log(` API Calls: Unlimited\n`);
}
else {
console.log(` API Calls: ${session.user.apiCallsUsed}/${session.user.apiCallsLimit}\n`);
}
// Load configuration
const configManager = await ConfigManager.load();
const config = configManager.getAll();
// Create logger
const logger = new Logger({
verbose: config.verbose,
debug: config.debug,
logToFile: true,
component: 'cli',
});
// Create agent config - use backend proxy for secure API access
const agentConfig = {
apiKey: '', // Not needed when using backend proxy
apiUrl: '', // Not needed when using backend proxy
model: config.model,
temperature: config.temperature,
top_p: config.top_p,
max_tokens: config.max_tokens,
// Backend proxy configuration
useBackendProxy: true,
backendUrl: backendUrl,
authToken: session.token,
};
// Initialize agent
const agent = new Agent(agentConfig, logger);
printSuccess('BroCode is ready! Type your request or /help for commands.');
// Create readline interface
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: '\n\x1b[32mYou:\x1b[0m ',
});
rl.prompt();
rl.on('line', async (input) => {
const userInput = input.trim();
// Handle empty input
if (!userInput) {
rl.prompt();
return;
}
// Handle commands
if (userInput.startsWith('/')) {
await handleCommand(userInput, agent, rl);
return;
}
// Process with AI
const spinner = createLoadingSpinner('BroCode is thinking');
try {
const response = await agent.chat(userInput);
stopLoadingSpinner(spinner);
// Format and print response
console.log('\n\x1b[36mBroCode:\x1b[0m');
console.log(formatResponse(response));
}
catch (error) {
stopLoadingSpinner(spinner);
printError(`Error: ${error.message}`);
}
rl.prompt();
});
rl.on('close', () => {
console.log('\n\nGoodbye! 👋\n');
process.exit(0);
});
// Handle Ctrl+C gracefully
process.on('SIGINT', () => {
rl.close();
});
}
catch (error) {
printError(`Failed to initialize BroCode: ${error.message}`);
process.exit(1);
}
}
// ============================================
// COMMAND HANDLER
// ============================================
async function handleCommand(command, agent, rl) {
const cmd = command.toLowerCase();
switch (cmd) {
case '/help':
case '/h':
printCommandHelp([
{
command: '/help',
description: 'Show this help message',
aliases: ['/h'],
},
{
command: '/clear',
description: 'Clear conversation history',
aliases: ['/c'],
},
{
command: '/history',
description: 'Show conversation history',
aliases: ['/hist'],
},
{
command: '/exit',
description: 'Exit BroCode',
aliases: ['/quit', '/q'],
},
{
command: '/stats',
description: 'Show conversation statistics',
},
]);
break;
case '/clear':
case '/c':
agent.clearHistory();
printSuccess('Conversation history cleared!');
break;
case '/history':
case '/hist':
const history = agent.getHistory();
if (history.length === 0) {
console.log('\n No conversation history yet.\n');
}
else {
console.log(`\n Conversation History (${history.length} messages):\n`);
history.forEach((msg, idx) => {
const role = msg.role === 'user' ? '\x1b[32mYou\x1b[0m' : '\x1b[36mBroCode\x1b[0m';
const preview = msg.content.substring(0, 80) + (msg.content.length > 80 ? '...' : '');
console.log(` ${idx + 1}. ${role}: ${preview}`);
});
console.log();
}
break;
case '/stats':
const messageCount = agent.getMessageCount();
console.log(`\n Conversation Statistics:\n`);
console.log(` • Total messages: ${messageCount}`);
console.log(` • User messages: ${Math.ceil(messageCount / 2)}`);
console.log(` • AI responses: ${Math.floor(messageCount / 2)}`);
console.log();
break;
case '/exit':
case '/quit':
case '/q':
rl.close();
break;
default:
printError(`Unknown command: ${command}\n\nType /help to see available commands.`);
break;
}
rl.prompt();
}
// ============================================
// RUN
// ============================================
main().catch((error) => {
printError(`Fatal error: ${error.message}`);
process.exit(1);
});