aico-claude-code
Version:
Claude Code CLI 的网页用户界面
223 lines (190 loc) • 6.29 kB
JavaScript
/**
* CLI entry point for Claude Code UI
*
* Provides command-line interface for running the Claude Code UI server
* with various configuration options.
*/
import { spawn } from 'child_process';
import { readFileSync, existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { parseCliArgs } from './args.js';
import { checkRequirements, buildApplication, initConfig, showSystemInfo, showExtendedHelp } from './commands.js';
// Try to import open, fallback gracefully if not available
let open;
try {
open = (await import('open')).default;
} catch {
open = null;
}
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Parse CLI arguments
const args = parseCliArgs();
// Set environment variables based on CLI arguments
process.env.PORT = args.port.toString();
process.env.VITE_PORT = args.vitePort.toString();
process.env.HOST = args.host;
process.env.DEBUG = args.debug.toString();
if (args.claudePath) {
process.env.CLAUDE_PATH = args.claudePath;
}
if (args.logLevel) {
process.env.LOG_LEVEL = args.logLevel;
}
// Check if we're running from global installation
function isGlobalInstallation() {
const globalPath = process.execPath;
const cliPath = __dirname;
return globalPath.includes('node_modules') || cliPath.includes('.nvm');
}
// Ensure build exists for production mode
async function ensureBuildForProduction() {
const distPath = join(__dirname, '../dist');
const serverPath = join(__dirname, '../server/index.js');
if (!existsSync(distPath)) {
console.log('🔍 Production build not found, building...');
try {
await buildApplication();
} catch (error) {
console.error('❌ Failed to build for production:', error.message);
console.log('💡 Try running: npm run build');
process.exit(1);
}
}
if (!existsSync(serverPath)) {
console.error('❌ Server file not found:', serverPath);
process.exit(1);
}
}
// Determine which server to run
let serverPath;
let serverArgs = [];
if (args.prodMode) {
// Production mode - ensure build exists
ensureBuildForProduction().then(() => {
serverPath = join(__dirname, '../server/index.js');
startServer();
});
} else if (args.devMode) {
// Development mode - check if npm scripts are available
const packageJsonPath = join(__dirname, '../package.json');
if (existsSync(packageJsonPath)) {
console.log('🚀 Starting Claude Code UI in development mode...');
console.log(`📡 Backend: http://localhost:${args.port}`);
console.log(`🎨 Frontend: http://localhost:${args.vitePort}`);
serverPath = 'npm';
serverArgs = ['run', 'dev'];
startServer();
} else {
console.error('❌ Development mode requires package.json in the same directory');
process.exit(1);
}
} else {
// Default mode - run backend server only
serverPath = join(__dirname, '../server/index.js');
startServer();
}
// Start the server
function startServer() {
console.log(`🚀 Starting Claude Code UI...`);
console.log(`📡 Server: http://${args.host}:${args.port}`);
if (!args.prodMode) {
console.log(`🎨 Frontend: http://${args.host}:${args.vitePort}`);
}
console.log(`📝 Log level: ${args.logLevel}`);
if (args.debug) {
console.log(`🐛 Debug mode enabled`);
}
const serverProcess = spawn('node', [serverPath, ...(serverArgs || [])], {
stdio: 'inherit',
env: { ...process.env },
cwd: dirname(__dirname)
});
// Handle process events
serverProcess.on('spawn', () => {
console.log('✅ Claude Code UI server started successfully');
// Open browser if requested
if (args.openBrowser) {
setTimeout(() => {
const url = `http://${args.host}:${args.vitePort || args.port}`;
console.log(`🌐 Opening browser at ${url}`);
if (typeof open === 'function') {
open(url).catch(err => {
console.warn(`Could not open browser: ${err.message}`);
});
} else {
console.warn('Could not open browser: open package not available');
console.warn('Please install the "open" package with: npm install open');
}
}, 2000);
}
});
serverProcess.on('error', (error) => {
console.error('❌ Failed to start server:', error.message);
if (error.code === 'ENOENT') {
console.log('💡 Make sure npm is installed and in your PATH');
}
process.exit(1);
});
serverProcess.on('exit', (code) => {
if (code !== 0) {
console.error(`❌ Server exited with code ${code}`);
} else {
console.log('👋 Claude Code UI server stopped');
}
});
// Handle graceful shutdown
const shutdown = (signal) => {
console.log(`\n📴 Received ${signal}, shutting down gracefully...`);
if (serverProcess && !serverProcess.killed) {
serverProcess.kill('SIGTERM');
}
process.exit(0);
};
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
return serverProcess;
}
// Validate Node.js version
function validateNodeVersion() {
const nodeVersion = process.version;
const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0], 10);
if (majorVersion < 18) {
console.error(`❌ Node.js 18+ is required. Current version: ${nodeVersion}`);
console.error('Please upgrade Node.js to continue.');
process.exit(1);
}
}
// Main execution
function main() {
validateNodeVersion();
// Handle special commands
if (process.argv.includes('build')) {
buildApplication().catch(console.error);
return;
}
if (process.argv.includes('check')) {
checkRequirements();
return;
}
if (process.argv.includes('init')) {
const configPath = process.argv[process.argv.indexOf('init') + 1] || './claude-ui.config.json';
initConfig(configPath);
return;
}
if (process.argv.includes('--info')) {
showSystemInfo();
return;
}
// Start the server for prod/dev modes
if (args.prodMode || args.devMode || (!args.prodMode && !args.devMode)) {
// Server will be started by the async flow above
return;
}
}
// Run CLI
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}