/** * Attach Command * * 连接到远程 Server */ import type { Command } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; import { createClient } from '../client/index.js'; export function registerAttachCommand(program: Command): void { program .command('attach') .description('Connect to a remote AI Assistant server') .argument('', 'Server URL (e.g., http://192.168.1.100:3000)') .option('-t, --token ', 'Authentication token') .option('-s, --session ', 'Session ID to connect to') .option('--tui', 'Use TUI mode (default)', true) .option('--no-tui', 'Use simple CLI mode') .action(async (url: string, options) => { const { token, session: sessionId, tui } = options; const spinner = ora('Connecting to server...').start(); try { // 创建客户端 const client = createClient({ baseUrl: url, token, }); // 检查服务器健康状态 const health = await client.health(); spinner.succeed(`Connected to server at ${url}`); if (tui) { // TUI 模式 const { TUIApp } = await import('../tui/index.js'); const app = new TUIApp({ client, sessionId, }); await app.start(); } else { // 简单 CLI 模式 console.log(chalk.gray('─'.repeat(50))); console.log(chalk.bold('Server Status:')); console.log(` Status: ${chalk.green(health.status)}`); console.log(` Agent: ${health.agent.coreAvailable ? chalk.green('Available') : chalk.yellow('Not available')}`); console.log(` Auth: ${health.auth.enabled ? chalk.yellow('Enabled') : chalk.gray('Disabled')}`); console.log(` Sessions: ${health.stats.sessions}`); console.log(` WebSocket: ${health.stats.websocket.connections} connections`); console.log(chalk.gray('─'.repeat(50))); // 如果指定了 session,连接到该 session if (sessionId) { await connectToSession(client, sessionId); } else { // 显示可用 sessions 或创建新的 await showSessionMenu(client); } } } catch (error) { spinner.fail('Failed to connect to server'); if (error instanceof Error) { console.error(chalk.red(`Error: ${error.message}`)); } process.exit(1); } }); } async function connectToSession(client: ReturnType, sessionId: string): Promise { const spinner = ora(`Connecting to session ${sessionId}...`).start(); try { // 获取 session 信息 const { data: session } = await client.getSession(sessionId); spinner.succeed(`Connected to session: ${session.name || session.id}`); // 获取历史消息 const { data: messages } = await client.getMessages(sessionId); if (messages.length > 0) { console.log(chalk.gray('\n─── Recent Messages ───')); const recentMessages = messages.slice(-5); for (const msg of recentMessages) { const prefix = msg.role === 'user' ? chalk.blue('You: ') : chalk.green('AI: '); const content = msg.content.length > 100 ? msg.content.slice(0, 100) + '...' : msg.content; console.log(prefix + content); } console.log(chalk.gray('───────────────────────\n')); } // 启动交互式会话 await startInteractiveSession(client, sessionId); } catch (error) { spinner.fail('Failed to connect to session'); throw error; } } async function showSessionMenu(client: ReturnType): Promise { const { default: inquirer } = await import('inquirer'); // 获取可用 sessions const { data: sessions } = await client.listSessions(); const choices = [ { name: chalk.green('+ Create new session'), value: 'new' }, ...sessions.map((s) => ({ name: `${s.name || s.id} (${s.messageCount} messages)`, value: s.id, })), ]; const { selection } = await inquirer.prompt([ { type: 'list', name: 'selection', message: 'Select a session:', choices, }, ]); if (selection === 'new') { const { name } = await inquirer.prompt([ { type: 'input', name: 'name', message: 'Session name (optional):', }, ]); const { data: newSession } = await client.createSession(name || undefined); console.log(chalk.green(`Created new session: ${newSession.id}`)); await startInteractiveSession(client, newSession.id); } else { await connectToSession(client, selection); } } async function startInteractiveSession( client: ReturnType, sessionId: string ): Promise { const { createInterface } = await import('readline'); console.log(chalk.gray('Type your message and press Enter. Type /quit to exit.\n')); // 连接 WebSocket const ws = client.connectWebSocket(sessionId); let currentResponse = ''; ws.onopen = () => { console.log(chalk.gray('[Connected to WebSocket]')); }; ws.onmessage = (event) => { try { const message = JSON.parse(event.data as string); switch (message.type) { case 'chunk': process.stdout.write(message.payload?.content || ''); currentResponse += message.payload?.content || ''; break; case 'done': if (currentResponse) { console.log('\n'); currentResponse = ''; } break; case 'error': console.error(chalk.red(`\nError: ${message.payload?.message}`)); break; } } catch { // 忽略解析错误 } }; ws.onerror = (error) => { console.error(chalk.red('WebSocket error:', error)); }; ws.onclose = () => { console.log(chalk.gray('[Disconnected from WebSocket]')); process.exit(0); }; // 创建交互式 readline const rl = createInterface({ input: process.stdin, output: process.stdout, }); const prompt = () => { rl.question(chalk.blue('You: '), async (input) => { const trimmed = input.trim(); if (trimmed === '/quit' || trimmed === '/exit') { console.log(chalk.gray('Goodbye!')); ws.close(); rl.close(); return; } if (!trimmed) { prompt(); return; } // 通过 WebSocket 发送消息 ws.send(JSON.stringify({ type: 'message', sessionId, payload: { content: trimmed }, })); process.stdout.write(chalk.green('AI: ')); // 等待响应完成后再提示 const waitForDone = () => { if (currentResponse === '') { prompt(); } else { setTimeout(waitForDone, 100); } }; setTimeout(waitForDone, 500); }); }; // 等待 WebSocket 连接 await new Promise((resolve) => { if (ws.readyState === WebSocket.OPEN) { resolve(); } else { ws.onopen = () => resolve(); } }); prompt(); }