| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- #!/usr/bin/env node
- import fs from 'fs';
- import net from 'net';
- import os from 'os';
- import path from 'path';
- import { spawnSync } from 'child_process';
- import { fileURLToPath } from 'url';
- import {
- getPilotDeckConfigPath,
- readPilotDeckConfigFile,
- validatePilotDeckConfig,
- } from './services/pilotdeckConfig.js';
- const __filename = fileURLToPath(import.meta.url);
- const __dirname = path.dirname(__filename);
- const packageJsonPath = path.join(__dirname, '../package.json');
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
- const colors = {
- reset: '\x1b[0m',
- bright: '\x1b[1m',
- dim: '\x1b[2m',
- cyan: '\x1b[36m',
- green: '\x1b[32m',
- yellow: '\x1b[33m',
- red: '\x1b[31m',
- blue: '\x1b[34m',
- };
- const c = {
- info: (text) => `${colors.cyan}${text}${colors.reset}`,
- ok: (text) => `${colors.green}${text}${colors.reset}`,
- warn: (text) => `${colors.yellow}${text}${colors.reset}`,
- error: (text) => `${colors.red}${text}${colors.reset}`,
- tip: (text) => `${colors.blue}${text}${colors.reset}`,
- bright: (text) => `${colors.bright}${text}${colors.reset}`,
- dim: (text) => `${colors.dim}${text}${colors.reset}`,
- };
- function defaultDatabasePath() {
- return path.join(process.env.PILOT_HOME || path.join(os.homedir(), '.pilotdeck'), 'auth.db');
- }
- function getInstallDir() {
- return path.join(__dirname, '..');
- }
- function parseArgs(args) {
- const parsed = { command: 'start', options: {} };
- for (let index = 0; index < args.length; index += 1) {
- const arg = args[index];
- if (arg === '--port' || arg === '-p') {
- parsed.options.serverPort = args[++index];
- } else if (arg.startsWith('--port=')) {
- parsed.options.serverPort = arg.split('=')[1];
- } else if (arg === '--database-path') {
- parsed.options.databasePath = args[++index];
- } else if (arg.startsWith('--database-path=')) {
- parsed.options.databasePath = arg.split('=')[1];
- } else if (arg === '--config') {
- parsed.options.configPath = args[++index];
- } else if (arg.startsWith('--config=')) {
- parsed.options.configPath = arg.split('=')[1];
- } else if (arg === '--help' || arg === '-h') {
- parsed.command = 'help';
- } else if (arg === '--version' || arg === '-v') {
- parsed.command = 'version';
- } else if (!arg.startsWith('-')) {
- parsed.command = arg;
- }
- }
- return parsed;
- }
- function applyOptions(options) {
- if (options.serverPort) process.env.SERVER_PORT = options.serverPort;
- else if (!process.env.SERVER_PORT && process.env.PORT) process.env.SERVER_PORT = process.env.PORT;
- if (options.databasePath) process.env.DATABASE_PATH = options.databasePath;
- if (options.configPath) process.env.PILOTDECK_CONFIG_PATH = options.configPath;
- if (!process.env.DATABASE_PATH) process.env.DATABASE_PATH = defaultDatabasePath();
- }
- function showHelp() {
- console.log(`
- ${c.bright('pilotdeck - Command Line Tool')}
- Usage:
- pilotdeck [command] [options]
- Commands:
- start Start the PilotDeck web UI (default)
- status Show configuration and data locations
- help Show this help information
- version Show version information
- Options:
- -p, --port <port> Set server port (default: 3001)
- --database-path <path> Set database location
- --config <path> Set pilotdeck.yaml location
- -h, --help Show this help information
- -v, --version Show version information
- Examples:
- pilotdeck
- pilotdeck --port 8080
- pilotdeck status
- Configuration:
- PilotDeck reads ~/.pilotdeck/pilotdeck.yaml by default.
- First run opens the onboarding UI if no usable config exists.
- `);
- }
- function showVersion() {
- console.log(packageJson.version);
- }
- function hasUsableConfig(record) {
- const validation = validatePilotDeckConfig(record.config);
- if (!record.exists || !validation.valid) return false;
- const mainModel = record.config?.agents?.main?.model;
- const entry = mainModel ? record.config?.models?.entries?.[mainModel] : null;
- const provider = entry?.provider ? record.config?.models?.providers?.[entry.provider] : null;
- return Boolean(mainModel && entry?.name && provider?.baseUrl && provider?.apiKey);
- }
- function showStatus() {
- const configPath = getPilotDeckConfigPath();
- const record = readPilotDeckConfigFile();
- const dbPath = process.env.DATABASE_PATH || defaultDatabasePath();
- console.log(`\n${c.bright('pilotdeck - Status')}\n`);
- console.log(c.dim('═'.repeat(60)));
- console.log(`\n${c.info('[INFO]')} Version: ${c.bright(packageJson.version)}`);
- console.log(`${c.info('[INFO]')} Installation Directory: ${c.dim(getInstallDir())}`);
- console.log(`${c.info('[INFO]')} Server Port: ${c.bright(process.env.SERVER_PORT || '3001')}`);
- console.log(`${c.info('[INFO]')} Config File: ${c.dim(configPath)}`);
- console.log(` Status: ${record.exists ? c.ok('[OK] Exists') : c.warn('[WARN] Not found')}`);
- console.log(` Onboarding: ${hasUsableConfig(record) ? c.ok('[OK] Complete') : c.warn('[WARN] Required')}`);
- console.log(`${c.info('[INFO]')} Database: ${c.dim(dbPath)}`);
- console.log(` Status: ${fs.existsSync(dbPath) ? c.ok('[OK] Exists') : c.warn('[WARN] Not created yet')}`);
- console.log('\n' + c.dim('═'.repeat(60)));
- console.log(`\n${c.tip('[TIP]')} Start with ${c.bright('pilotdeck')} and open http://localhost:${process.env.SERVER_PORT || '3001'}\n`);
- }
- function assertPortAvailable(port, host) {
- return new Promise((resolve, reject) => {
- const server = net.createServer();
- server.once('error', (error) => {
- if (error.code === 'EADDRINUSE') {
- reject(new Error(`Port ${port} is already in use. Try: pilotdeck --port ${Number(port) + 1}`));
- } else {
- reject(error);
- }
- });
- server.once('listening', () => {
- server.close(() => resolve());
- });
- server.listen(Number(port), host);
- });
- }
- function ensureFrontendBuild() {
- const installDir = getInstallDir();
- const distIndexPath = path.join(installDir, 'dist', 'index.html');
- if (fs.existsSync(distIndexPath)) return;
- console.log(`${c.warn('[WARN]')} Frontend build not found at ${c.dim(distIndexPath)}`);
- console.log(`${c.info('[INFO]')} Building frontend before starting production server...`);
- const result = spawnSync('npm', ['run', 'build'], {
- cwd: installDir,
- stdio: 'inherit',
- env: { ...process.env, HUSKY: '0' },
- });
- if (result.status !== 0) {
- throw new Error('Frontend build failed. Run "cd ui && npm install && npm run build" manually, then retry pilotdeck.');
- }
- if (!fs.existsSync(distIndexPath)) {
- throw new Error(`Frontend build completed but ${distIndexPath} was not created.`);
- }
- }
- async function startServer() {
- const host = process.env.HOST || '0.0.0.0';
- const port = process.env.SERVER_PORT || '3001';
- await assertPortAvailable(port, host);
- ensureFrontendBuild();
- console.log(`\n${c.bright('pilotdeck')} starting...\n`);
- console.log(`${c.info('[INFO]')} Config: ${c.dim(getPilotDeckConfigPath())}`);
- console.log(`${c.info('[INFO]')} Database: ${c.dim(process.env.DATABASE_PATH || defaultDatabasePath())}`);
- console.log(`${c.info('[INFO]')} Server: http://localhost:${port}\n`);
- await import('./index.js');
- }
- async function main() {
- const { command, options } = parseArgs(process.argv.slice(2));
- applyOptions(options);
- switch (command) {
- case 'start':
- await startServer();
- break;
- case 'status':
- case 'info':
- showStatus();
- break;
- case 'help':
- showHelp();
- break;
- case 'version':
- showVersion();
- break;
- default:
- console.error(`${c.error('[ERROR]')} Unknown command: ${command}`);
- console.error(`Run ${c.bright('pilotdeck help')} for usage information.`);
- process.exit(1);
- }
- }
- main().catch((error) => {
- console.error(`${c.error('[ERROR]')} ${error.message}`);
- process.exit(1);
- });
|