Skip to main content
Glama

Curupira

by drzln
cli.ts8.51 kB
/** * @fileoverview Main Curupira CLI class */ import { Command } from 'commander' import chalk from 'chalk' import updateNotifier from 'update-notifier' import { readFileSync, existsSync } from 'fs' import { join, resolve } from 'path' import { createLogger } from '@curupira/shared' import { ProjectConfigLoader } from '@curupira/shared' import type { CliConfig, CliContext, CommandResult, UpdateInfo, ProjectDetection } from './types.js' import { detectProject, checkForUpdates } from './utils/index.js' import { InitCommand, ValidateCommand, DevCommand, StartCommand, DebugCommand } from './commands/index.js' const logger = createLogger({ level: 'info', name: 'curupira-cli' }) /** * Main Curupira CLI class */ export class CurupiraCLI { private program: Command private cliConfig: CliConfig private packageJson: any constructor() { this.program = new Command() this.cliConfig = this.getDefaultConfig() this.packageJson = this.loadPackageJson() this.setupProgram() } /** * Run the CLI with provided arguments */ async run(argv: string[]): Promise<CommandResult> { try { // Check for updates (non-blocking) this.checkUpdates() // Parse arguments await this.program.parseAsync(argv) return { success: true, message: 'Command executed successfully', exitCode: 0 } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) if (!this.cliConfig.silent) { console.error(chalk.red('✗ Error:'), errorMessage) } return { success: false, message: errorMessage, error: error instanceof Error ? error : new Error(String(error)), exitCode: 1 } } } /** * Setup commander program */ private setupProgram(): void { this.program .name('curupira') .description('AI-assisted React debugging tool') .version(this.packageJson.version, '-v, --version', 'Display version number') .option('-V, --verbose', 'Enable verbose output') .option('-s, --silent', 'Suppress output') .option('--log-level <level>', 'Set log level (error|warn|info|debug)', 'info') .option('--config <path>', 'Path to curupira config file') .hook('preAction', (thisCommand) => { // Update CLI config with parsed options const opts = thisCommand.opts() this.cliConfig = { ...this.cliConfig, verbose: opts.verbose || false, silent: opts.silent || false, logLevel: opts.logLevel || 'info', configPath: opts.config } }) // Add commands this.addInitCommand() this.addValidateCommand() this.addDevCommand() this.addStartCommand() this.addDebugCommand() // Handle unknown commands this.program .action(() => { console.log(this.program.helpInformation()) }) } /** * Add 'curupira init' command */ private addInitCommand(): void { this.program .command('init') .description('Initialize Curupira in a React project') .option('-f, --force', 'Overwrite existing configuration') .option('-t, --template <name>', 'Use specific template') .option('--skip-install', 'Skip dependency installation') .action(async (options) => { const context = await this.createContext() const command = new InitCommand() const result = await command.execute(context, options) if (!result.success) { process.exit(result.exitCode) } }) } /** * Add 'curupira validate' command */ private addValidateCommand(): void { this.program .command('validate') .description('Validate curupira.yml configuration') .option('-f, --fix', 'Auto-fix common issues') .action(async (options) => { const context = await this.createContext() const command = new ValidateCommand() const result = await command.execute(context, options) if (!result.success) { process.exit(result.exitCode) } }) } /** * Add 'curupira dev' command */ private addDevCommand(): void { this.program .command('dev') .description('Start Curupira MCP server in development mode') .option('-p, --port <port>', 'Server port', '8080') .option('--host <host>', 'Server host', 'localhost') .option('--open', 'Open browser extension DevTools') .action(async (options) => { const context = await this.createContext() const command = new DevCommand() const result = await command.execute(context, options) if (!result.success) { process.exit(result.exitCode) } }) } /** * Add 'curupira start' command */ private addStartCommand(): void { this.program .command('start') .description('Start Curupira MCP server in production mode') .option('-p, --port <port>', 'Server port', '8080') .option('--host <host>', 'Server host', '0.0.0.0') .option('-d, --daemon', 'Run as daemon process') .action(async (options) => { const context = await this.createContext() const command = new StartCommand() const result = await command.execute(context, options) if (!result.success) { process.exit(result.exitCode) } }) } /** * Add 'curupira debug' command */ private addDebugCommand(): void { this.program .command('debug') .description('Run targeted debugging session') .option('-c, --component <name>', 'Target specific component') .option('-u, --url <url>', 'Target specific page URL') .option('--profile', 'Enable performance profiling') .option('--snapshot', 'Create state snapshot') .action(async (options) => { const context = await this.createContext() const command = new DebugCommand() const result = await command.execute(context, options) if (!result.success) { process.exit(result.exitCode) } }) } /** * Create CLI context for command execution */ private async createContext(): Promise<CliContext> { const cwd = process.cwd() const projectRoot = this.cliConfig.configPath ? resolve(cwd, this.cliConfig.configPath, '..') : cwd // Load project configuration let projectConfig try { projectConfig = await ProjectConfigLoader.loadConfig(projectRoot) } catch (error) { if (this.cliConfig.verbose) { logger.warn({ error }, 'Failed to load project config') } } return { config: this.cliConfig, projectConfig: projectConfig || undefined, cwd, packageJson: this.packageJson } } /** * Get default CLI configuration */ private getDefaultConfig(): CliConfig { return { version: this.packageJson?.version || '1.0.0', verbose: false, silent: false, logLevel: 'info', projectRoot: process.cwd() } } /** * Load package.json */ private loadPackageJson(): any { try { const packagePath = join(import.meta.dirname, '..', 'package.json') if (existsSync(packagePath)) { return JSON.parse(readFileSync(packagePath, 'utf-8')) } } catch (error) { logger.debug({ error }, 'Failed to load package.json') } return { name: 'curupira', version: '1.0.0' } } /** * Check for updates (non-blocking) */ private checkUpdates(): void { if (this.cliConfig.silent) return try { const notifier = updateNotifier({ pkg: this.packageJson, updateCheckInterval: 1000 * 60 * 60 * 24 // 24 hours }) if (notifier.update) { console.log(chalk.yellow('\n💡 Update available:')) console.log(chalk.gray(` Current: ${notifier.update.current}`)) console.log(chalk.green(` Latest: ${notifier.update.latest}`)) console.log(chalk.cyan(` Run: npm install -g curupira@latest\n`)) } } catch (error) { // Silently ignore update check errors logger.debug({ error }, 'Update check failed') } } /** * Get program instance (for testing) */ getProgram(): Command { return this.program } /** * Get CLI config (for testing) */ getConfig(): CliConfig { return this.cliConfig } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/drzln/curupira'

If you have feedback or need assistance with the MCP directory API, please join our Discord server