UNPKG

@kaifronsdal/transcript-viewer

Version:

A web-based viewer for AI conversation transcripts with rollback support

198 lines (172 loc) • 6.48 kB
#!/usr/bin/env node import { exec } from 'child_process'; import { fileURLToPath } from 'url'; import { dirname, join, resolve } from 'path'; import { existsSync } from 'fs'; import express from 'express'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const packageRoot = join(__dirname, '..'); const buildDir = join(packageRoot, 'build'); // Parse command line arguments using yargs const argv = yargs(hideBin(process.argv)) .option('dir', { alias: 'd', type: 'string', description: 'Specify the transcript directory to load', default: './transcripts' }) .option('host', { type: 'string', description: 'Specify the host to bind to', default: 'localhost' }) .option('port', { alias: 'p', type: 'number', description: 'Specify the port to bind to', default: 3000 }) .option('force-rebuild', { alias: 'f', type: 'boolean', description: 'Force rebuild the application even if build exists', default: false }) .option('open', { alias: 'o', type: 'boolean', description: 'Open browser automatically', default: false }) .help() .example('transcript-viewer', 'Start with default directory (./transcripts)') .example('transcript-viewer --dir ./my-transcripts', 'Start with custom directory') .example('transcript-viewer -d /path/to/transcripts', 'Start with custom directory (short form)') .example('transcript-viewer --host 0.0.0.0 --port 8080', 'Start accessible from all interfaces on port 8080') .example('transcript-viewer --force-rebuild', 'Force rebuild and start') .epilog('šŸš€ A web-based viewer for AI conversation transcripts') .argv; const transcriptDir = argv.dir ? resolve(argv.dir) : null; const host = argv.host || process.env.HOST || 'localhost'; const port = argv.port || parseInt(process.env.PORT || '3000'); const forceRebuild = argv['force-rebuild']; const openBrowser = argv.open; console.log('šŸš€ Starting Transcript Viewer...'); if (transcriptDir) { if (!existsSync(transcriptDir)) { console.error(`āŒ Error: Transcript directory does not exist: ${transcriptDir}`); process.exit(1); } console.log(`šŸ“ Using transcript directory: ${transcriptDir}`); } else { const defaultPath = resolve(process.cwd(), 'transcripts'); console.log(`šŸ“ Using default transcript directory: ${defaultPath}`); console.log(`šŸ’” Tip: Use --dir <path> to specify a different directory`); } console.log(`🌐 Server will bind to: ${host}:${port}`); if (host === '0.0.0.0') { console.log(`🌐 Server will be accessible from all interfaces`); } // Check if build directory exists and if we should force rebuild if (!existsSync(buildDir) || forceRebuild) { if (forceRebuild) { console.log('šŸ”„ Force rebuilding application...'); } else { console.log('šŸ“¦ Building application...'); } exec('npm run build', { cwd: packageRoot }, (error, stdout, stderr) => { if (error) { console.error('āŒ Build failed:', error); process.exit(1); } // console.log(stdout); // console.log(stderr); console.log('šŸ—ļø Build completed'); startServer(); }); } else { startServer(); } async function startServer() { console.log('🌐 Starting server...'); // Set up environment variables const env = { ...process.env }; if (transcriptDir) { env.TRANSCRIPT_DIR = transcriptDir; } else { // If no directory specified, use ./transcripts relative to where the user ran the command env.TRANSCRIPT_DIR = resolve(process.cwd(), 'transcripts'); } // Set environment variables for the SvelteKit app Object.assign(process.env, env); try { // Import the SvelteKit handler using dynamic import const { handler } = await import(join(buildDir, 'handler.js')); // Create Express app const app = express(); // Serve static files from the build/client directory const clientDir = join(buildDir, 'client'); if (existsSync(clientDir)) { app.use(express.static(clientDir)); } // Use SvelteKit handler for all routes app.use(handler); // Start the server const server = app.listen(port, host, () => { const url = `http://${host}:${port}`; console.log('\nāœ… Transcript Viewer is running!'); console.log(`šŸ”— Open your browser to: ${url}`); // Show additional info if binding to all interfaces if (host === '0.0.0.0') { console.log(`šŸ“ Make sure port ${port} is accessible if running remotely`); } console.log('\nšŸ’” To stop the server, press Ctrl+C'); // Open browser if requested if (openBrowser) { try { const open = require('open'); open(url); } catch (error) { console.log('šŸ’” Could not open browser automatically. Please install "open" package or open manually.'); } } }); // Handle graceful shutdown const gracefulShutdown = (signal) => { console.log(`\nšŸ›‘ Received ${signal}. Stopping server gracefully...`); server.close(() => { console.log('šŸ‘‹ Server stopped'); process.exit(0); }); // Force exit after 10 seconds setTimeout(() => { console.log('ā° Force exit after timeout'); process.exit(1); }, 10000); }; process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); process.on('SIGINT', () => gracefulShutdown('SIGINT')); // Handle server errors server.on('error', (error) => { if (error.code === 'EADDRINUSE') { console.error(`āŒ Error: Port ${port} is already in use`); console.error('šŸ’” Try using a different port with --port <number>'); } else if (error.code === 'EACCES') { console.error(`āŒ Error: Permission denied to bind to ${host}:${port}`); console.error('šŸ’” Try using a port above 1024 or run with elevated privileges'); } else { console.error('āŒ Server error:', error.message); } process.exit(1); }); } catch (error) { console.error('āŒ Failed to load SvelteKit handler:', error.message); console.error('šŸ’” Make sure the application has been built with "npm run build"'); process.exit(1); } }