#!/usr/bin/env node /** CLI entry point for @geepers/chat server mode */ import { createServer } from './server.js'; import { detectProviders, checkOllama } from './detect.js'; import type { DetectedProvider, ServerConfig } from './types.js'; import { exec } from 'node:child_process'; import { platform } from 'node:os'; import net from 'node:net'; // ---- Arg parsing (no deps) ---- function parseArgs(): { port: number; gateway: string | null; noOpen: boolean; host: string } { const args = process.argv.slice(2); let port = 3456; let gateway: string | null = null; let noOpen = false; let host = '127.0.0.1'; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === '--port' && args[i + 1]) { port = parseInt(args[++i], 10); } else if (arg === '--gateway') { gateway = (args[i + 1] && !args[i + 1].startsWith('--')) ? args[++i] : 'https://api.dr.eamer.dev/v1'; } else if (arg === '--no-open') { noOpen = true; } else if (arg === '--host' && args[i + 1]) { host = args[++i]; } else if (arg === '--help' || arg === '-h') { console.log(` @geepers/chat — Multi-provider LLM chat Usage: npx @geepers/chat [options] Options: --port Port to listen on (default: 3456) --host Bind address (default: 127.0.0.1) --gateway [url] Use gateway mode (default: https://api.dr.eamer.dev/v1) --no-open Don't open browser automatically --help Show this help Environment: Set provider API keys to enable direct mode: OPENAI_API_KEY, ANTHROPIC_API_KEY, XAI_API_KEY, MISTRAL_API_KEY, COHERE_API_KEY, GEMINI_API_KEY, PERPLEXITY_API_KEY, GROQ_API_KEY, HF_API_KEY Or use gateway mode: GATEWAY_API_KEY Key for the gateway API `); process.exit(0); } } return { port, gateway, noOpen, host }; } // ---- Port finding ---- function isPortAvailable(port: number, host: string): Promise { return new Promise(resolve => { const server = net.createServer(); server.once('error', () => resolve(false)); server.once('listening', () => { server.close(); resolve(true); }); server.listen(port, host); }); } async function findPort(preferred: number, host: string): Promise { for (let p = preferred; p < preferred + 20; p++) { if (await isPortAvailable(p, host)) return p; } return 0; // Let OS pick } // ---- Browser open ---- function openBrowser(url: string): void { const os = platform(); const cmd = os === 'darwin' ? 'open' : os === 'win32' ? 'start' : 'xdg-open'; exec(`${cmd} ${url}`, () => {}); // Ignore errors } // ---- Main ---- async function main(): Promise { const opts = parseArgs(); // Detect providers const providers: DetectedProvider[] = detectProviders(); const ollama = await checkOllama(process.env.OLLAMA_HOST); if (ollama) providers.push(ollama); // Determine mode const mode = opts.gateway || providers.length === 0 ? 'gateway' : 'direct'; const gatewayUrl = opts.gateway || 'https://api.dr.eamer.dev/v1'; const port = await findPort(opts.port, opts.host); const config: ServerConfig = { port, host: opts.host, mode, providers, gatewayUrl, openBrowser: !opts.noOpen, }; const app = createServer(config); app.listen(port, opts.host, () => { const url = `http://${opts.host === '0.0.0.0' ? 'localhost' : opts.host}:${port}`; console.log(` \x1b[1m@geepers/chat\x1b[0m Mode: ${mode === 'direct' ? `Direct (${providers.length} provider${providers.length !== 1 ? 's' : ''})` : `Gateway (${gatewayUrl})`} Providers: ${providers.length > 0 ? providers.map(p => p.name).join(', ') : 'none (gateway handles routing)'} URL: ${url} Press Ctrl+C to stop `); if (config.openBrowser) openBrowser(url); }); } main().catch(err => { console.error('Failed to start:', err); process.exit(1); });