UNPKG

sushil-gitmate

Version:

Professional Git workflow automation powered by AI. Streamline your development process with natural language commands and intelligent automation.

435 lines (377 loc) 13.7 kB
#!/usr/bin/env node import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import { handleNlpCommand, handleRepoCommand, handleGitCommand, handleGenerateGitignore, handleGenerateCommitMessage, handleAuthLogout, handleSwitchAIProvider, handleUserInfo, handleGitConfig } from '../commands/commandHandler.js'; import logger from '../src/utils/logger.js'; import UI from '../src/utils/ui.js'; import { storeToken } from '../src/utils/tokenManager.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Set the working directory to the project root for global installations process.chdir(process.cwd()); const serviceName = 'CLI'; async function main() { try { const args = process.argv.slice(2); if (args.length === 0) { showHelp(); return; } const command = args[0]; switch (command) { case '--version': case '-v': await showVersion(); break; case '--help': case '-h': showHelp(); break; case 'init': await handleInit(); break; case 'config': await handleConfig(args.slice(1)); break; case 'auth': await handleAuth(args.slice(1)); break; case 'repo': await handleRepoCommand(args.slice(1)); break; case 'git': await handleGitCommand(args.slice(1)); break; case 'generate-commit-message': await handleGenerateCommitMessage(); break; case 'generate-gitignore': { const description = args.slice(1).join(' '); await handleGenerateGitignore(description); } break; case 'switch-ai-provider': { const provider = args[1]; if (!provider) { UI.error('Provider Required', 'Please specify an AI provider: openai or anthropic'); process.exitCode = 1; return; } await handleSwitchAIProvider(provider); } break; case 'logout': await handleAuthLogout(); break; case 'whoami': case 'who': await handleUserInfo(); break; case 'list': // Handle list command - route to repo list functionality await handleRepoCommand(['list', ...args.slice(1)]); break; case 'status': await handleGitCommand(['status'], '.'); break; default: // For all other commands, use AI to parse intent and route await handleAiIntent(command + ' ' + args.slice(1).join(' ')); break; } } catch (error) { logger.error('CLI error:', { message: error.message, stack: error.stack, service: serviceName }); UI.error('Unexpected Error', error.message); process.exitCode = 1; } } async function showVersion() { const packageJson = JSON.parse( await import('fs/promises').then(fs => fs.readFile(join(__dirname, '..', 'package.json'), 'utf8')) ); console.log(`GitBot Assistant v${packageJson.version}`); } function showHelp() { console.log(` GitBot Assistant - Professional Git workflow automation powered by AI USAGE: gitmate <command> [options] gitmate "natural language command" COMMANDS: init Initialize GitBot Assistant configuration config [options] Manage configuration settings auth <provider> Authenticate with external services list List your GitHub repositories repo <subcommand> Manage GitHub repositories git <subcommand> Execute Git operations generate-commit-message Generate commit message from changes generate-gitignore Generate .gitignore file switch-ai-provider Switch between AI providers logout Clear stored authentication tokens whoami Show current user information NATURAL LANGUAGE COMMANDS: gitmate "push my changes to main" gitmate "create a new branch called feature-x" gitmate "commit with message 'fix bug'" gitmate "create merge request from feature to main" gitmate "list all of my repos" gitmate "who am i" OPTIONS: -h, --help Show this help message -v, --version Show version information EXAMPLES: gitmate init gitmate config --show gitmate config git set gitmate whoami gitmate list gitmate "list all of my repos" gitmate "push code please" gitmate repo create my-project --private For more information, visit: https://github.com/yourusername/gitbot-assistant `); } async function handleInit() { try { UI.section('GitBot Assistant Initialization', 'Setting up your configuration...'); // Check if config already exists const configPath = join(process.env.HOME || process.env.USERPROFILE, '.gitmate', 'config.json'); const fs = await import('fs/promises'); try { await fs.access(configPath); const overwrite = await UI.confirm('Configuration already exists. Overwrite?', false); if (!overwrite) { console.log('Initialization cancelled.'); return; } } catch (error) { // Config doesn't exist, continue } // Create config directory const configDir = join(process.env.HOME || process.env.USERPROFILE, '.gitmate'); await fs.mkdir(configDir, { recursive: true }); // Initialize configuration const inquirer = (await import('inquirer')).default; const answers = await inquirer.prompt([ { type: 'list', name: 'aiProvider', message: 'Select your preferred AI provider:', choices: [ { name: 'Mistral (Recommended)', value: 'mistral' }, { name: 'OpenAI (GPT-4)', value: 'openai' }, { name: 'Anthropic (Claude)', value: 'anthropic' } ], default: 'mistral' }, { type: 'password', name: 'apiKey', message: 'Enter your API key:', validate: input => input.trim() !== '' || 'API key is required' }, { type: 'confirm', name: 'setupGitHub', message: 'Would you like to set up GitHub authentication now?', default: true } ]); // Save configuration const config = { aiProvider: answers.aiProvider, apiKey: answers.apiKey, version: '1.0.0', createdAt: new Date().toISOString() }; await fs.writeFile(configPath, JSON.stringify(config, null, 2)); UI.success('Configuration Saved', 'GitBot Assistant has been initialized successfully!'); if (answers.setupGitHub) { console.log('\nTo complete GitHub setup, run: gitmate auth github'); } } catch (error) { logger.error('Initialization error:', { message: error.message, stack: error.stack, service: serviceName }); UI.error('Initialization Failed', error.message); process.exitCode = 1; } } async function handleConfig(args) { try { const configPath = join(process.env.HOME || process.env.USERPROFILE, '.gitmate', 'config.json'); const fs = await import('fs/promises'); if (args.length === 0 || args.includes('--show')) { try { const config = JSON.parse(await fs.readFile(configPath, 'utf8')); console.log('\nCurrent Configuration:'); console.log(JSON.stringify(config, null, 2)); } catch (error) { UI.error('Configuration Not Found', 'Run "gitmate init" to create configuration.'); process.exitCode = 1; } return; } if (args.includes('--reset')) { const confirm = await UI.confirm('Are you sure you want to reset your configuration?', false); if (confirm) { await fs.unlink(configPath); UI.success('Configuration Reset', 'Configuration has been reset. Run "gitmate init" to reconfigure.'); } return; } // Handle git config subcommand if (args[0] === 'git') { await handleGitConfig(args.slice(1)); return; } // Handle other config options UI.warning('Unknown Config Option', `Unknown config option: ${args.join(' ')}`); console.log('Available options: --show, --reset, git <subcommand>'); console.log('Git subcommands: show, set, reset'); } catch (error) { logger.error('Config error:', { message: error.message, stack: error.stack, service: serviceName }); UI.error('Configuration Error', error.message); process.exitCode = 1; } } async function handleAuth(args) { if (args.length === 0) { UI.error('Provider Required', 'Please specify an authentication provider (e.g., github)'); process.exitCode = 1; return; } const provider = args[0]; switch (provider.toLowerCase()) { case 'github': { // Start the auth server const { startAuthServer } = await import('../src/server/authServer.js'); await startAuthServer(); // Prompt for token after browser is opened const inquirer = (await import('inquirer')).default; const { token } = await inquirer.prompt([ { type: 'password', name: 'token', message: 'Paste the token you received from the browser:', mask: '*', validate: input => input.trim() !== '' || 'Token is required', }, ]); await storeToken('github_access_token', token.trim()); console.log('Authentication Complete: Your GitHub token has been saved. You are now authenticated!'); break; } default: UI.error('Unknown Provider', `Unknown authentication provider: ${provider}`); process.exitCode = 1; break; } } // Handle process termination gracefully process.on('SIGINT', () => { console.log('\n\nGoodbye! 👋'); process.exit(0); }); process.on('SIGTERM', () => { console.log('\n\nGoodbye! 👋'); process.exit(0); }); // Run the CLI main().catch(error => { logger.error('Unhandled CLI error:', { message: error.message, stack: error.stack, service: serviceName }); UI.error('Fatal Error', error.message); process.exit(1); }); async function handleAiIntent(userInput) { try { const { aiService } = await import('../src/services/aiServiceFactory.js'); // Check AI service status first const aiReady = await aiService.checkStatus(); if (!aiReady) { console.log('\nAI service is temporarily unavailable. Please try again in some time. This is a temporary issue and will be resolved soon.'); return; } const { intent, entities, confidence } = await aiService.parseIntent(userInput); // If confidence is low or intent is unknown, show help or ask for clarification if (!intent || intent === 'unknown' || (confidence !== undefined && confidence < 0.5)) { console.log('\nSorry, I could not confidently understand your request. Here are some things you can try:'); showHelp(); return; } // Route to the correct handler based on intent switch (intent) { case 'git_status': await handleGitCommand(['status'], '.'); break; case 'list_branches': await handleGitCommand(['branch'], '.'); break; case 'list_repos': await handleRepoCommand(['list']); break; case 'get_remotes': case 'list_remotes': case 'git_remote': await handleGitCommand(['remote'], '.'); break; case 'git_log': await handleGitCommand(['log'], '.'); break; case 'git_diff': await handleGitCommand(['diff'], '.'); break; case 'push_changes': // Use executeGitOperation for interactive commit logic { const { executeGitOperation } = await import('../commands/commandHandler.js'); await executeGitOperation({ intent, entities }, 'User'); } break; case 'pull_changes': await handleGitCommand(['pull'], '.'); break; case 'create_branch': await handleGitCommand(['branch', entities?.branch || 'new-branch'], '.'); break; case 'checkout_branch': await handleGitCommand(['checkout', entities?.branch || 'main'], '.'); break; case 'git_commit': await handleGitCommand(['commit', entities?.commit_message || 'Update'], '.'); break; case 'add_remote': await handleGitCommand(['remote', 'add', entities?.name, entities?.url], '.'); break; case 'clone_repo': await handleGitCommand(['clone', entities?.repo_url], '.'); break; case 'user_info': case 'who_am_i': case 'login_info': await handleUserInfo(); break; case 'git_config': case 'configure_git': await handleGitConfig(['set']); break; case 'show_git_config': await handleGitConfig(['show']); break; case 'create_pr': { const { executeGitOperation } = await import('../commands/commandHandler.js'); await executeGitOperation({ intent, entities }, 'User'); } break; // Add more intent handlers as needed default: // If intent is not mapped, fallback to NLP handler for conversational response await handleNlpCommand(userInput); } } catch (error) { logger.error('AI intent routing error:', { message: error.message, stack: error.stack, userInput, service: serviceName }); UI.error('AI Routing Error', error.message + (error.stack ? '\n' + error.stack : '')); console.error('Full AI Routing Error:', error); } }