UNPKG

build-in-public-bot

Version:

AI-powered CLI bot for automating build-in-public tweets with code screenshots

414 lines 15.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.doctorCommand = void 0; const commander_1 = require("commander"); const chalk_1 = __importDefault(require("chalk")); const ora_1 = __importDefault(require("ora")); const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const config_1 = require("../services/config"); const screenshot_1 = require("../services/screenshot"); const storage_1 = require("../services/storage"); const errors_1 = require("../utils/errors"); const child_process_1 = require("child_process"); const util_1 = require("util"); const execAsync = (0, util_1.promisify)(child_process_1.exec); exports.doctorCommand = new commander_1.Command('doctor') .description('Run health checks on your build-in-public bot setup') .option('--fix', 'Attempt to fix issues automatically') .option('--json', 'Output results in JSON format') .action(async (options) => { try { const checks = []; if (!options.json) { console.log(chalk_1.default.cyan.bold('\n🩺 Running Build-in-Public Bot Health Checks...\n')); } await runConfigChecks(checks, options); await runDependencyChecks(checks, options); await runAuthChecks(checks, options); await runFileSystemChecks(checks, options); await runServiceChecks(checks, options); if (options.json) { console.log(JSON.stringify({ checks, summary: generateSummary(checks) }, null, 2)); } else { displayResults(checks); } const failedCritical = checks.filter(c => c.status === 'fail'); if (failedCritical.length > 0) { process.exit(1); } } catch (error) { (0, errors_1.handleError)(error); } }); async function runConfigChecks(checks, options) { const spinner = options.json ? null : (0, ora_1.default)('Checking configuration...').start(); try { const configService = config_1.ConfigService.getInstance(); try { await fs.access(configService.getConfigPath()); const validation = await configService.validate(); if (validation.valid) { checks.push({ name: 'Configuration', status: 'pass', message: 'Configuration is valid' }); } else { checks.push({ name: 'Configuration', status: 'fail', message: 'Configuration validation failed', details: validation.errors }); if (options.fix) { if (spinner) { spinner.text = 'Attempting to fix configuration...'; } checks.push({ name: 'Configuration Fix', status: 'warn', message: 'Run "bip init" to fix configuration issues' }); } } } catch (error) { checks.push({ name: 'Configuration', status: 'fail', message: 'Configuration file not found', details: ['Run "bip init" to create configuration'] }); } spinner?.succeed(); } catch (error) { spinner?.fail(); checks.push({ name: 'Configuration', status: 'fail', message: 'Failed to check configuration', details: [error instanceof Error ? error.message : 'Unknown error'] }); } } async function runDependencyChecks(checks, options) { const spinner = options.json ? null : (0, ora_1.default)('Checking dependencies...').start(); try { const nodeVersion = process.version; const majorVersion = parseInt(nodeVersion.split('.')[0].substring(1)); if (majorVersion >= 16) { checks.push({ name: 'Node.js Version', status: 'pass', message: `Node.js ${nodeVersion} is supported` }); } else { checks.push({ name: 'Node.js Version', status: 'fail', message: `Node.js ${nodeVersion} is too old`, details: ['Requires Node.js 16.0.0 or higher'] }); } try { require('canvas'); checks.push({ name: 'Canvas Dependencies', status: 'pass', message: 'Canvas module is properly installed' }); } catch (error) { checks.push({ name: 'Canvas Dependencies', status: 'fail', message: 'Canvas module not found or improperly installed', details: [ 'Canvas requires system dependencies.', 'On macOS: brew install pkg-config cairo pango libpng jpeg giflib librsvg', 'On Ubuntu: sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev' ] }); } try { await execAsync('npx tsc --version'); checks.push({ name: 'TypeScript', status: 'pass', message: 'TypeScript compiler is available' }); } catch (error) { checks.push({ name: 'TypeScript', status: 'warn', message: 'TypeScript compiler not found', details: ['Run "npm install" to install dependencies'] }); } spinner?.succeed(); } catch (error) { spinner?.fail(); checks.push({ name: 'Dependencies', status: 'fail', message: 'Failed to check dependencies', details: [error instanceof Error ? error.message : 'Unknown error'] }); } } async function runAuthChecks(checks, options) { const spinner = options.json ? null : (0, ora_1.default)('Checking authentication...').start(); try { const configService = config_1.ConfigService.getInstance(); let config; try { config = await configService.load(); } catch { checks.push({ name: 'Authentication', status: 'skip', message: 'Skipped - configuration not available' }); spinner?.succeed(); return; } if (config.ai?.apiKey) { checks.push({ name: 'AI API Key', status: 'pass', message: 'AI API key is configured' }); } else { checks.push({ name: 'AI API Key', status: 'fail', message: 'AI API key is missing', details: ['Set OPENAI_API_KEY or ANTHROPIC_API_KEY environment variable'] }); } if (config.twitter?.sessionData) { checks.push({ name: 'Twitter Authentication', status: 'pass', message: 'Twitter session data is present' }); } else { checks.push({ name: 'Twitter Authentication', status: 'warn', message: 'Twitter not authenticated', details: ['Run "bip setup browser" to authenticate with Twitter'] }); } spinner?.succeed(); } catch (error) { spinner?.fail(); checks.push({ name: 'Authentication', status: 'fail', message: 'Failed to check authentication', details: [error instanceof Error ? error.message : 'Unknown error'] }); } } async function runFileSystemChecks(checks, options) { const spinner = options.json ? null : (0, ora_1.default)('Checking file system...').start(); try { const configService = config_1.ConfigService.getInstance(); try { await fs.access(configService.getConfigDir()); checks.push({ name: 'Config Directory', status: 'pass', message: `Config directory exists: ${configService.getConfigDir()}` }); } catch { checks.push({ name: 'Config Directory', status: 'fail', message: 'Config directory not found', details: [`Expected at: ${configService.getConfigDir()}`] }); if (options.fix) { await fs.mkdir(configService.getConfigDir(), { recursive: true }); checks.push({ name: 'Config Directory Fix', status: 'pass', message: 'Created config directory' }); } } const tempDir = path.join(process.cwd(), '.bip-temp'); try { await fs.access(tempDir); const stats = await fs.stat(tempDir); if (stats.isDirectory()) { checks.push({ name: 'Temp Directory', status: 'pass', message: 'Temp directory exists' }); } } catch { checks.push({ name: 'Temp Directory', status: 'pass', message: 'Temp directory will be created when needed' }); } spinner?.succeed(); } catch (error) { spinner?.fail(); checks.push({ name: 'File System', status: 'fail', message: 'Failed to check file system', details: [error instanceof Error ? error.message : 'Unknown error'] }); } } async function runServiceChecks(checks, options) { const spinner = options.json ? null : (0, ora_1.default)('Checking services...').start(); try { const services = [ { name: 'Screenshot Service', getInstance: () => screenshot_1.ScreenshotService.getInstance() }, { name: 'Storage Service', getInstance: () => storage_1.StorageService.getInstance() }, { name: 'Config Service', getInstance: () => config_1.ConfigService.getInstance() } ]; for (const service of services) { try { service.getInstance(); checks.push({ name: service.name, status: 'pass', message: `${service.name} is functional` }); } catch (error) { checks.push({ name: service.name, status: 'fail', message: `${service.name} initialization failed`, details: [error instanceof Error ? error.message : 'Unknown error'] }); } } spinner?.succeed(); } catch (error) { spinner?.fail(); checks.push({ name: 'Services', status: 'fail', message: 'Failed to check services', details: [error instanceof Error ? error.message : 'Unknown error'] }); } } function displayResults(checks) { console.log(chalk_1.default.bold('\nHealth Check Results:\n')); for (const check of checks) { const icon = getStatusIcon(check.status); const color = getStatusColor(check.status); console.log(`${icon} ${color(check.name)}: ${check.message}`); if (check.details && check.details.length > 0) { for (const detail of check.details) { console.log(chalk_1.default.gray(` → ${detail}`)); } } } const summary = generateSummary(checks); console.log(chalk_1.default.bold('\nSummary:')); console.log(` ${chalk_1.default.green(`✓ Passed: ${summary.passed}`)}`); console.log(` ${chalk_1.default.yellow(`⚠ Warnings: ${summary.warnings}`)}`); console.log(` ${chalk_1.default.red(`✗ Failed: ${summary.failed}`)}`); console.log(` ${chalk_1.default.gray(`- Skipped: ${summary.skipped}`)}`); if (summary.failed > 0) { console.log(chalk_1.default.red('\n⚠️ Some checks failed. Please fix the issues above.')); } else if (summary.warnings > 0) { console.log(chalk_1.default.yellow('\n⚠️ Some warnings were found. Consider addressing them.')); } else { console.log(chalk_1.default.green('\n✅ All checks passed! Your bot is ready to use.')); } } function getStatusIcon(status) { switch (status) { case 'pass': return chalk_1.default.green('✓'); case 'fail': return chalk_1.default.red('✗'); case 'warn': return chalk_1.default.yellow('⚠'); case 'skip': return chalk_1.default.gray('-'); default: return ' '; } } function getStatusColor(status) { switch (status) { case 'pass': return chalk_1.default.green; case 'fail': return chalk_1.default.red; case 'warn': return chalk_1.default.yellow; case 'skip': return chalk_1.default.gray; default: return chalk_1.default.white; } } function generateSummary(checks) { return { passed: checks.filter(c => c.status === 'pass').length, failed: checks.filter(c => c.status === 'fail').length, warnings: checks.filter(c => c.status === 'warn').length, skipped: checks.filter(c => c.status === 'skip').length }; } exports.default = exports.doctorCommand; //# sourceMappingURL=doctor.js.map