UNPKG

@graphteon/juricode

Version:

We are forging the future with lines of digital steel

265 lines 9.89 kB
#!/usr/bin/env node import { Command } from 'commander'; import prompts from 'prompts'; import chalk from 'chalk'; import figlet from 'figlet'; import gradient from 'gradient-string'; import { Client } from 'ssh2'; import { readFileSync, existsSync } from 'fs'; import { join } from 'path'; import { createServer } from 'net'; import { createNewTask, listTasks } from './commands/task'; import { listRepositories } from './commands/repository'; import { listSuggestedTasks } from './commands/suggested-tasks'; import { startConversationTUI } from './commands/conversation-tui'; export { setupVSCodeTunnel, setupVSCodeTunnelFromAPI }; const getPackageVersion = () => { try { const possiblePaths = [ join(__dirname, '..', 'package.json'), join(__dirname, '..', '..', 'package.json'), join(process.cwd(), 'package.json'), ]; for (const packageJsonPath of possiblePaths) { if (existsSync(packageJsonPath)) { const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); return packageJson.version; } } return '0.3.1'; } catch (error) { return '0.3.1'; } }; let globalTunnelInfo = null; const setupVSCodeTunnel = async (vscodePort) => { if (!globalTunnelInfo) { throw new Error('Main tunnel not established'); } const { ssh, tunnels } = globalTunnelInfo; return new Promise((resolve, reject) => { const localServer = createServer((socket) => { ssh.forwardOut('127.0.0.1', socket.localPort || 0, '127.0.0.1', vscodePort, (err, stream) => { if (err) { console.error(chalk.red(`VSCode tunnel error: ${err.message}`)); socket.end(); return; } socket.pipe(stream).pipe(socket); socket.on('close', () => { stream.end(); }); stream.on('close', () => { socket.end(); }); }); }); localServer.listen(vscodePort, '127.0.0.1', () => { tunnels.set(vscodePort, localServer); resolve(); }); localServer.on('error', (err) => { reject(err); }); }); }; const setupVSCodeTunnelFromAPI = async (taskId) => { if (!globalTunnelInfo) { throw new Error('Main tunnel not established'); } try { const response = await fetch(`http://localhost:13000/api/conversations/${taskId}/vscode-url`, { headers: { 'Accept': 'application/json, text/plain, */*', 'User-Agent': 'JuriCode CLI' } }); if (!response.ok) { throw new Error(`API request failed: ${response.status} ${response.statusText}`); } const data = await response.json(); const vscodeUrl = data.vscode_url; if (!vscodeUrl) { throw new Error('No VSCode URL received from API'); } const urlMatch = vscodeUrl.match(/localhost:(\d+)/); if (!urlMatch) { throw new Error('Could not parse port from VSCode URL'); } const vscodePort = parseInt(urlMatch[1]); await setupVSCodeTunnel(vscodePort); const tunneledUrl = vscodeUrl.replace(`localhost:${vscodePort}`, `localhost:${vscodePort}`); return tunneledUrl; } catch (error) { console.error(chalk.red(`Failed to setup VSCode tunnel: ${error instanceof Error ? error.message : 'Unknown error'}`)); throw error; } }; const setupTunnel = async (target) => { let user, host, port = '3000'; if (target.includes(':')) { [target, port] = target.split(':'); } [user, host] = target.split('@'); if (!user || !host) { throw new Error('Invalid target format. Use <user>@<host> or <user>@<host>:<port>'); } console.log(chalk.yellow(`Setting up SSH connection to ${user}@${host}...`)); const ssh = new Client(); return new Promise((resolve, reject) => { const keyPath = `${process.env.HOME}/.ssh/id_rsa`; if (!existsSync(keyPath)) { reject(new Error(`SSH key not found: ${keyPath}`)); return; } try { const privateKey = readFileSync(keyPath, 'utf8'); console.log(chalk.yellow(`Using SSH key: ${keyPath}`)); ssh.on('ready', () => { console.log(chalk.green('SSH connection established!')); const localServer = createServer((socket) => { ssh.forwardOut('127.0.0.1', socket.localPort || 0, '127.0.0.1', parseInt(port), (err, stream) => { if (err) { console.error(chalk.red(`Tunnel error: ${err.message}`)); socket.end(); return; } socket.pipe(stream).pipe(socket); socket.on('close', () => { stream.end(); }); stream.on('close', () => { socket.end(); }); }); }); localServer.listen(13000, '127.0.0.1', () => { console.log(chalk.green('Main SSH tunnel established!')); globalTunnelInfo = { user, host, apiPort: port, ssh, tunnels: new Map([[13000, localServer]]) }; resolve(); }); localServer.on('error', (err) => { console.error(chalk.red(`Local server error: ${err.message}`)); reject(err); }); }).on('error', (err) => { console.error(chalk.red(`SSH connection error: ${err.message}`)); reject(err); }); ssh.connect({ host: host, username: user, privateKey: privateKey, readyTimeout: 20000, algorithms: { kex: ['diffie-hellman-group14-sha256', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512'], cipher: ['aes128-ctr', 'aes192-ctr', 'aes256-ctr'], hmac: ['hmac-sha2-256', 'hmac-sha2-512'], compress: ['none'] } }); } catch (error) { reject(new Error(`Failed to read SSH key: ${error instanceof Error ? error.message : 'Unknown error'}`)); } process.on('SIGINT', () => { console.log(chalk.yellow('\nClosing SSH connection...')); ssh.end(); process.exit(); }); process.on('SIGTERM', () => { console.log(chalk.yellow('\nClosing SSH connection...')); ssh.end(); process.exit(); }); }); }; const version = getPackageVersion(); console.log(gradient.pastel.multiline(figlet.textSync('JuriCode', { font: 'Big', horizontalLayout: 'default', verticalLayout: 'default', }))); console.log(chalk.dim(`v${version}\n`)); const startTUIInterface = async () => { console.log(chalk.green('🚀 Starting JuriCode TUI Interface...\n')); await startConversationTUI(); }; const showLegacyMenu = async () => { const { action } = await prompts({ type: 'select', name: 'action', message: 'What would you like to do?', choices: [ { title: '📋 View All Tasks (CLI)', value: 'view-tasks' }, { title: '💡 Suggested Tasks', value: 'suggested-tasks' }, { title: '📚 Browse Repositories', value: 'view-repos' }, { title: '📝 Create New Task', value: 'new-task' }, { title: '💬 Start TUI Conversation', value: 'tui-conversation' }, { title: '🚪 Exit', value: 'exit' } ] }); switch (action) { case 'new-task': await createNewTask(); break; case 'view-tasks': await listTasks(); break; case 'suggested-tasks': await listSuggestedTasks(); break; case 'view-repos': await listRepositories(); break; case 'tui-conversation': const { taskId } = await prompts({ type: 'text', name: 'taskId', message: 'Enter Task ID for conversation:', validate: (input) => input.length === 0 ? 'Task ID is required' : true }); if (taskId) { await startConversationTUI(taskId); } break; case 'exit': console.log(chalk.yellow('👋 Goodbye!')); process.exit(0); } await showLegacyMenu(); }; const program = new Command(); program .name('juricode') .description('JuriCode CLI') .version(getPackageVersion()) .option('-t, --tunnel <target>', 'Setup SSH tunnel (format: <user>@<host> or <user>@<host>:<port>, default port: 13000)') .option('--legacy', 'Use legacy CLI menu instead of TUI interface') .action(async (options) => { try { if (options.tunnel) { await setupTunnel(options.tunnel); } if (options.legacy) { await showLegacyMenu(); } else { await startTUIInterface(); } } catch (error) { console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error')); process.exit(1); } }); program.parse(process.argv); //# sourceMappingURL=index.js.map