build-in-public-bot
Version:
AI-powered CLI bot for automating build-in-public tweets with code screenshots
414 lines • 15.4 kB
JavaScript
"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