unified-cli.tsā¢11.7 kB
#!/usr/bin/env node
/**
* Unified Vibe CLI - Combines MCP Server and Natural Language CLI
*
* Usage:
* vibe - Start interactive REPL mode
* vibe "request" - Process natural language request
* vibe --server - Start MCP server
* vibe --help - Show help
* vibe --setup - Run setup wizard
*/
import { fileURLToPath } from 'url';
import path from 'path';
import chalk from 'chalk';
import boxen from 'boxen';
import ora from 'ora';
import dotenv from 'dotenv';
import { createSetupWizard } from './setup-wizard.js';
import { TransportContext } from './index-with-setup.js';
import logger from './logger.js';
// Get directory paths
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.resolve(__dirname, '..');
const envPath = path.join(projectRoot, '.env');
// Parse command line arguments
const args = process.argv.slice(2);
// Detect mode based on arguments
function detectMode(): 'server' | 'cli' | 'help' | 'setup' | 'interactive' | 'version' {
// Check for special flags first
if (args.includes('--help') || args.includes('-h')) {
return 'help';
}
if (args.includes('--version') || args.includes('-v')) {
return 'version';
}
if (args.includes('--setup') || args.includes('--reconfigure')) {
return 'setup';
}
// Check for explicit server mode
if (args.includes('--server') || args.includes('--stdio')) {
return 'server';
}
// Check for interactive mode
if (args.includes('--interactive') || args.includes('-i')) {
return 'interactive';
}
// If no arguments, default to interactive mode for better UX
if (args.length === 0) {
return 'interactive';
}
// If only server-related flags (SSE, port), start server
if (args.every(arg => ['--sse', '--port'].includes(arg.split('=')[0]))) {
return 'server';
}
// If there's a non-flag argument, it's a CLI request
if (args.some(arg => !arg.startsWith('-'))) {
return 'cli';
}
return 'server';
}
// Display version information
async function displayVersion(): Promise<void> {
try {
// Read package.json using fs for ESM compatibility
const fs = await import('fs/promises');
const packageJsonPath = path.join(projectRoot, 'package.json');
const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8');
const packageJson = JSON.parse(packageJsonContent);
console.log(`vibe-coder-mcp v${packageJson.version}`);
} catch {
console.log('vibe-coder-mcp (version unknown)');
}
}
// Display unified help
function displayHelp(): void {
console.log(boxen(
chalk.cyan.bold('š¤ Vibe - Unified AI Development Assistant') + '\n\n' +
chalk.white('Your one-stop command for AI-powered development'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'cyan',
textAlignment: 'center'
}
));
console.log(chalk.yellow('\nš Usage:\n'));
console.log(chalk.green(' vibe ') + chalk.gray('Start interactive REPL mode (default)'));
console.log(chalk.green(' vibe --server ') + chalk.gray('Start MCP server'));
console.log(chalk.green(' vibe --stdio ') + chalk.gray('Start MCP server in stdio mode'));
console.log(chalk.green(' vibe "your request" ') + chalk.gray('Process natural language request'));
console.log(chalk.green(' vibe --setup ') + chalk.gray('Run setup wizard'));
console.log(chalk.green(' vibe --version ') + chalk.gray('Show version information'));
console.log(chalk.green(' vibe --help ') + chalk.gray('Show this help message'));
console.log(chalk.yellow('\nš Server Options:\n'));
console.log(chalk.green(' vibe --sse ') + chalk.gray('Start with Server-Sent Events transport'));
console.log(chalk.green(' vibe --port <number> ') + chalk.gray('Specify port for SSE mode (default: 3000)'));
console.log(chalk.yellow('\nš¬ CLI Options:\n'));
console.log(chalk.green(' vibe "request" --verbose') + chalk.gray('Show detailed output'));
console.log(chalk.green(' vibe "request" --json ') + chalk.gray('Output in JSON format'));
console.log(chalk.green(' vibe "request" --yaml ') + chalk.gray('Output in YAML format'));
console.log(chalk.green(' vibe "request" --quiet ') + chalk.gray('Suppress non-error output'));
console.log(chalk.yellow('\nš Examples:\n'));
console.log(chalk.cyan(' Start MCP Server:'));
console.log(chalk.gray(' vibe'));
console.log(chalk.gray(' vibe --sse'));
console.log(chalk.cyan('\n Process Requests:'));
console.log(chalk.gray(' vibe "research React best practices"'));
console.log(chalk.gray(' vibe "create a PRD for e-commerce platform"'));
console.log(chalk.gray(' vibe "map the codebase structure" --json'));
console.log(chalk.gray(' vibe "generate user stories for auth"'));
console.log(chalk.yellow('\nš ļø Available Tools:\n'));
const tools = [
'⢠Research Manager - Technical research',
'⢠PRD Generator - Product requirements',
'⢠User Stories - Agile stories',
'⢠Task List - Development tasks',
'⢠Code Map - Codebase analysis',
'⢠Context Curator - AI context packages',
'⢠Starter Kit - Project templates',
'⢠Rules Generator - Coding standards',
'⢠Vibe Task Manager - Task management',
'⢠Workflow Runner - Multi-step workflows',
'⢠Agent Coordination - Multi-agent tasks'
];
tools.forEach(tool => console.log(chalk.cyan(tool)));
console.log(chalk.gray('\nš Documentation: https://github.com/freshtechbro/Vibe-Coder-MCP\n'));
}
// Main entry point
async function main() {
try {
const mode = detectMode();
// Enable auto-detection for CLI mode to use working directory
process.env.VIBE_USE_PROJECT_ROOT_AUTO_DETECTION = 'true';
// Create CLI transport context for context-aware configuration
const cliTransportContext: TransportContext = {
sessionId: `cli-${Date.now()}`,
transportType: 'cli',
timestamp: Date.now(),
workingDirectory: process.cwd(), // User's current working directory
mcpClientConfig: undefined // Will be loaded by context-aware config manager
};
// Handle version and help immediately without any setup
if (mode === 'version') {
await displayVersion();
return;
}
if (mode === 'help') {
displayHelp();
return;
}
// Load environment variables BEFORE checking first run
// This ensures .env file is loaded so isFirstRun() can properly detect existing config
dotenv.config({ path: envPath });
// Create context-aware setup wizard instance
const contextAwareSetupWizard = createSetupWizard(cliTransportContext);
// Always check for first run (except in help/version modes)
if (await contextAwareSetupWizard.isFirstRun()) {
console.log(chalk.cyan.bold('\nš Welcome to Vibe!\n'));
console.log(chalk.yellow('First-time setup required...\n'));
const success = await contextAwareSetupWizard.run();
if (!success) {
console.log(chalk.red('\nā Setup cancelled.'));
console.log(chalk.gray('Run ') + chalk.cyan('vibe --setup') + chalk.gray(' to configure later.'));
process.exit(1);
}
// After successful setup, show what to do next
console.log(boxen(
chalk.green.bold('ā
Setup Complete!') + '\n\n' +
chalk.white('Vibe is now configured and ready to use.') + '\n\n' +
chalk.cyan('Starting in ' + mode + ' mode...'),
{
padding: 1,
margin: 1,
borderStyle: 'double',
borderColor: 'green'
}
));
// Reload environment variables after setup to get the new configuration
dotenv.config({ path: envPath });
// Continue with the originally requested mode
console.log();
}
switch (mode) {
case 'setup':
await runSetup();
break;
case 'server':
await startServer();
break;
case 'cli':
await runCLI();
break;
case 'interactive':
await runInteractive();
break;
}
} catch (error) {
console.error(chalk.red('\nā Error:'), error);
logger.error({ err: error }, 'Unified CLI error');
process.exit(1);
}
}
// Run setup wizard
async function runSetup() {
console.log(chalk.cyan.bold('\nš§ Vibe Configuration\n'));
// Create CLI transport context for setup reconfiguration
const cliTransportContext: TransportContext = {
sessionId: `cli-setup-${Date.now()}`,
transportType: 'cli',
timestamp: Date.now(),
workingDirectory: process.cwd(), // User's current working directory
mcpClientConfig: undefined // Will be loaded by context-aware config manager
};
// Create context-aware setup wizard for reconfiguration
const contextAwareSetupWizard = createSetupWizard(cliTransportContext);
const success = await contextAwareSetupWizard.run();
if (success) {
console.log(chalk.green('\nā
Configuration updated successfully!'));
console.log(chalk.gray('Run ') + chalk.cyan('vibe') + chalk.gray(' to start the server.'));
} else {
console.log(chalk.red('\nā Configuration failed.'));
}
}
// Start MCP server
async function startServer() {
const spinner = ora({
text: 'Starting Vibe MCP Server...',
color: 'cyan'
}).start();
try {
// Delegate to compliant server implementation which properly validates via central config
await import('./index-with-setup.js');
// The server will handle its own initialization and configuration validation
spinner.stop(); // Stop spinner as server will show its own messages
} catch (error) {
spinner.fail('Failed to start server');
throw error;
}
}
// Run interactive CLI mode
async function runInteractive() {
try {
// Start the interactive REPL
process.argv = [process.argv[0], process.argv[1], '--interactive'];
await import('./cli/index.js');
} catch (error) {
console.error(chalk.red('ā Interactive CLI Error:'), error);
logger.error({ err: error }, 'Interactive CLI execution error');
process.exit(1);
}
}
// Run CLI for natural language processing
async function runCLI() {
// Import the CLI module dynamically
try {
// Remove any CLI-specific flags to get just the request
const cliArgs = args.filter(arg => !arg.startsWith('-') ||
['--verbose', '--quiet', '--json', '--yaml', '--format', '--no-color', '--force', '-v', '-q', '-f'].includes(arg));
// Set process.argv for the CLI module
process.argv = [process.argv[0], process.argv[1], ...cliArgs];
// Delegate to compliant CLI implementation which properly uses central config
await import('./cli/index.js');
} catch (error) {
console.error(chalk.red('ā CLI Error:'), error);
logger.error({ err: error }, 'CLI execution error');
process.exit(1);
}
}
// Handle uncaught errors
process.on('uncaughtException', (error) => {
logger.error({ err: error }, 'Uncaught exception');
console.error(chalk.red('\nā Uncaught exception:'), error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
logger.error({ err: reason, promise }, 'Unhandled rejection');
console.error(chalk.red('\nā Unhandled rejection:'), reason);
process.exit(1);
});
// Run the main function
main().catch((error) => {
console.error(chalk.red('\nā Fatal error:'), error);
process.exit(1);
});