/** * Setup command for VC-SYS CLI * Initializes project with Claude Code integration */ import { Command } from 'commander'; import fs from 'fs/promises'; import path from 'path'; import chalk from 'chalk'; import ora from 'ora'; import { ConfigManager } from '../lib/core/config-manager'; import { Logger } from '../lib/core/logger'; const logger = new Logger('SetupCommand'); /** * Create setup command */ export function createSetupCommand(): Command { const setupCommand = new Command('init-project'); setupCommand .description('Initialize VC-SYS project with Supabase integration') .option('--force', 'Force re-initialization even if already setup') .action(async (options) => { await handleSetup(options); }); return setupCommand; } /** * Handle setup command with structured output for Claude Code */ export async function handleSetup(options: { force?: boolean }): Promise { const configManager = new ConfigManager(); try { // Check if already initialized const isInitialized = await configManager.isInitialized(); if (isInitialized && !options.force) { outputStructured({ status: 'success', action: 'already_initialized', message: 'Project is already initialized', data: { paths: configManager.getProjectPaths() } }); logger.info('Project already initialized'); return; } if (options.force && isInitialized) { outputStructured({ status: 'info', action: 'force_reinitialize', message: 'Force re-initialization requested' }); } const spinner = ora('Setting up VC-SYS CLI in current project...').start(); try { // Step 1: Create configuration directories spinner.text = 'Creating configuration directories...'; await configManager.ensureProjectDir(); await configManager.ensureConfigDir(); // Global config // Step 2: Initialize project configuration spinner.text = 'Initializing project configuration...'; const projectConfig = await configManager.getProjectConfig(); await configManager.saveProjectConfig(projectConfig); // Step 3: Create Claude Code integration spinner.text = 'Setting up Claude Code integration...'; await configManager.updateClaudeConfig(); // Step 4: Update .gitignore spinner.text = 'Updating .gitignore...'; await updateGitignore(); // Step 5: Create auth directory spinner.text = 'Setting up authentication directory...'; const authDir = path.join(configManager.getProjectPaths().vcsysDir, 'auth'); await fs.mkdir(authDir, { recursive: true }); spinner.succeed('VC-SYS CLI setup completed successfully!'); // Output structured success data outputStructured({ status: 'success', action: 'setup_complete', message: 'VC-SYS CLI initialized successfully', data: { projectName: projectConfig.name, paths: configManager.getProjectPaths(), created: [ '.vcsys/', '.vcsys/config.json', '.vcsys/auth/', '.claude/', '.claude/vcsys-commands.md' ], updated: [ '.gitignore' ], nextSteps: [ 'vcsys auth login - Connect to Supabase', 'vcsys provision --list-orgs - See available organizations', 'vcsys provision --org "My Org" --name "my-project" - Create project' ] } }); // Human-readable output console.log(chalk.green('\nāœ… Setup Complete!\n')); console.log('šŸ“ Created configuration:'); console.log(` ${chalk.dim('.vcsys/')} - CLI configuration directory`); console.log(` ${chalk.dim('.vcsys/config.json')} - Project settings`); console.log(` ${chalk.dim('.vcsys/auth/')} - Authentication storage`); console.log(` ${chalk.dim('.claude/vcsys-commands.md')} - Claude Code integration`); console.log('\nšŸ”§ Updated files:'); console.log(` ${chalk.dim('.gitignore')} - Added VC-SYS exclusions`); console.log('\nšŸš€ Next steps:'); console.log(` ${chalk.cyan('vcsys auth login')} - Connect to Supabase`); console.log(` ${chalk.cyan('vcsys provision --list-orgs')} - See your organizations`); console.log(` ${chalk.cyan('vcsys provision --org "My Org" --name "project"')} - Create project`); console.log('\nšŸ’” Claude Code Integration:'); console.log(` All commands output structured JSON for AI integration`); console.log(` Check ${chalk.dim('.claude/vcsys-commands.md')} for usage examples`); logger.info('Setup completed successfully', { projectName: projectConfig.name }); } catch (error) { spinner.fail('Setup failed'); throw error; } } catch (error) { logger.error('Setup command failed', error); outputStructured({ status: 'error', action: 'setup_failed', error: 'SETUP_ERROR', message: error instanceof Error ? error.message : 'Unknown setup error', suggestion: 'Check file permissions and try again' }); console.error(chalk.red(`āŒ Setup failed: ${error instanceof Error ? error.message : 'Unknown error'}`)); process.exit(1); } } /** * Update .gitignore with VC-SYS exclusions */ async function updateGitignore(): Promise { const gitignorePath = path.join(process.cwd(), '.gitignore'); let gitignoreContent = ''; try { // Read existing .gitignore if it exists try { gitignoreContent = await fs.readFile(gitignorePath, 'utf8'); } catch { // File doesn't exist, start with empty content } // VC-SYS ignore patterns const vcsysIgnores = [ '', '# VC-SYS CLI', '.vcsys/auth/', '.vcsys/tokens/', '*.vcsys-temp', '' ].join('\n'); // Only add if not already present if (!gitignoreContent.includes('.vcsys/auth/')) { await fs.writeFile(gitignorePath, gitignoreContent + vcsysIgnores); } } catch (error) { logger.warn('Failed to update .gitignore', error); // Don't fail setup for this } } /** * Output structured data for Claude Code integration */ function outputStructured(data: any): void { console.log('--- CLAUDE CODE INTEGRATION ---'); console.log(JSON.stringify({ ...data, timestamp: new Date().toISOString() }, null, 2)); console.log('--- END INTEGRATION DATA ---'); } /** * Check if current directory is suitable for VC-SYS setup */ export async function validateSetupDirectory(): Promise<{ valid: boolean; issues: string[] }> { const issues: string[] = []; try { // Check if we can write to the current directory const testFile = path.join(process.cwd(), '.vcsys-test'); await fs.writeFile(testFile, 'test'); await fs.unlink(testFile); } catch (error) { issues.push('Cannot write to current directory'); } // Check if it looks like a development project const commonFiles = ['package.json', 'composer.json', 'requirements.txt', 'Cargo.toml', 'go.mod']; let hasProjectFiles = false; for (const file of commonFiles) { try { await fs.access(path.join(process.cwd(), file)); hasProjectFiles = true; break; } catch { // File doesn't exist } } if (!hasProjectFiles) { issues.push('Directory does not appear to contain a development project'); } return { valid: issues.length === 0, issues }; }