UNPKG

tinyagent

Version:

Connect your local shell to any device - access your dev environment from anywhere

231 lines 9.67 kB
#!/usr/bin/env node "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const chalk_1 = __importDefault(require("chalk")); const dotenv_1 = require("dotenv"); const child_process_1 = require("child_process"); const shell_client_v2_1 = require("./shell-client-v2"); const firebase_auth_simple_1 = require("./firebase-auth-simple"); (0, dotenv_1.config)(); /** * Check if Claude Code CLI is installed */ function isClaudeInstalled() { try { (0, child_process_1.execSync)('which claude', { stdio: 'ignore' }); return true; } catch { return false; } } /** * Install Claude Code CLI via npm */ async function installClaude() { console.log(chalk_1.default.yellow('\nClaude Code CLI not found.')); console.log(chalk_1.default.cyan('Installing Claude Code (@anthropic-ai/claude-code)...\n')); try { // Use npm to install globally const result = (0, child_process_1.spawnSync)('npm', ['install', '-g', '@anthropic-ai/claude-code'], { stdio: 'inherit', shell: true }); if (result.status === 0) { console.log(chalk_1.default.green('\n✓ Claude Code installed successfully!\n')); return true; } else { console.log(chalk_1.default.red('\n✗ Failed to install Claude Code.')); console.log(chalk_1.default.yellow('Try installing manually: npm install -g @anthropic-ai/claude-code\n')); return false; } } catch (error) { console.log(chalk_1.default.red(`\n✗ Installation error: ${error}`)); console.log(chalk_1.default.yellow('Try installing manually: npm install -g @anthropic-ai/claude-code\n')); return false; } } /** * Ensure Claude Code is available, installing if necessary */ async function ensureClaudeInstalled() { if (isClaudeInstalled()) { return true; } return await installClaude(); } const program = new commander_1.Command(); // Authentication commands program .command('login') .description('Authenticate with Tinyagent') .action(async () => { const authClient = new firebase_auth_simple_1.FirebaseAuthClient(); const success = await authClient.authenticate(); process.exit(success ? 0 : 1); }); program .command('logout') .description('Log out from Tinyagent') .action(() => { const authClient = new firebase_auth_simple_1.FirebaseAuthClient(); authClient.logout(); process.exit(0); }); program .command('whoami') .description('Display current user information') .action(() => { const authClient = new firebase_auth_simple_1.FirebaseAuthClient(); authClient.whoami(); process.exit(0); }); // Sessions command - list active sessions program .command('sessions') .description('List your active sessions') .option('-r, --relay <url>', 'Relay server URL', process.env.RELAY_URL || 'https://relay.tinyagent.app') .action(async (options) => { const authClient = new firebase_auth_simple_1.FirebaseAuthClient(); if (!authClient.isAuthenticated()) { console.log(chalk_1.default.yellow('You need to log in to see your sessions.')); console.log(chalk_1.default.cyan('Run: tinyagent login')); process.exit(1); } const token = authClient.getToken(); if (!token) { console.log(chalk_1.default.red('Failed to get auth token')); process.exit(1); } try { const httpUrl = options.relay.replace('wss://', 'https://').replace('ws://', 'http://'); const response = await fetch(`${httpUrl}/api/user/sessions`, { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) { const error = await response.text(); console.log(chalk_1.default.red(`Failed to fetch sessions: ${error}`)); process.exit(1); } const data = await response.json(); if (data.sessions.length === 0) { console.log(chalk_1.default.yellow('No active sessions')); } else { console.log(chalk_1.default.bold('\nYour active sessions:\n')); for (const session of data.sessions) { const tierNames = { 1: chalk_1.default.green('authenticated'), 2: chalk_1.default.yellow('password-protected'), 3: chalk_1.default.red('public') }; const status = session.mobileConnected ? chalk_1.default.green('mobile connected') : chalk_1.default.gray('no mobile client'); console.log(` ${chalk_1.default.bold(session.sessionId)}`); console.log(` Security: ${tierNames[session.securityTier] || 'unknown'}`); console.log(` Status: ${status}`); if (session.currentCommand) { console.log(` Running: ${chalk_1.default.cyan(session.currentCommand)}`); } console.log(); } } } catch (error) { console.log(chalk_1.default.red(`Error: ${error.message}`)); process.exit(1); } process.exit(0); }); // Main shell command program .name('tinyagent') .description('Connect your local shell to any device') .version('1.3.0') .argument('[sessionId]', 'Session ID to connect to (optional, auto-generated if not provided)') .option('--session-id <id>', 'Session ID (alternative to positional argument)') .option('-r, --relay <url>', 'Relay server URL', process.env.RELAY_URL || 'wss://relay.tinyagent.app') .option('-s, --shell <shell>', 'Shell to use', process.env.SHELL || '/bin/bash') .option('-c, --command <cmd>', 'Server command to run (e.g., "npm run dev")') .option('-p, --port <port>', 'Local server port', '3000') .option('--no-tunnel', 'Disable tunnel creation') .option('--no-claude', 'Do not auto-start Claude') .option('-v, --verbose', 'Show detailed debug output') .option('--password <password>', 'Password required for mobile clients to connect') .option('--public', 'Create a public session (anyone with QR can connect)') .option('--resume', 'Resume the last Claude session') .option('--continue', 'Continue the last Claude session (alias for --resume)') .action(async (sessionIdArg, options) => { // Determine session ID from argument or option, or generate one let sessionId = sessionIdArg || options.sessionId; if (!sessionId) { // Generate a random session ID sessionId = Math.random().toString(36).substring(2, 15); console.log(chalk_1.default.cyan(`Generated session ID: ${sessionId}`)); } // Check authentication status const authClient = new firebase_auth_simple_1.FirebaseAuthClient(); const isAuthenticated = authClient.isAuthenticated(); // Require --public flag for unauthenticated sessions without password if (!isAuthenticated && !options.password && !options.public) { console.log(chalk_1.default.yellow('\n⚠️ Security Notice')); console.log(chalk_1.default.white('You are not logged in and no password was set.')); console.log(chalk_1.default.white('Please choose one of these options:\n')); console.log(chalk_1.default.cyan(' 1. tinyagent login')); console.log(chalk_1.default.gray(' Most secure - only you can connect to your sessions\n')); console.log(chalk_1.default.cyan(` 2. tinyagent ${sessionId} --password <secret>`)); console.log(chalk_1.default.gray(' Medium security - requires password to connect\n')); console.log(chalk_1.default.cyan(` 3. tinyagent ${sessionId} --public`)); console.log(chalk_1.default.gray(' Low security - anyone with your session ID can connect & execute code on your computer\n')); process.exit(1); } try { // Ensure Claude Code is installed (unless --no-claude is set) if (options.claude !== false) { const claudeReady = await ensureClaudeInstalled(); if (!claudeReady) { console.log(chalk_1.default.yellow('Continuing without Claude Code auto-start feature.\n')); } } console.log(chalk_1.default.blue(`Creating session: ${sessionId}`)); // QR code will be shown after connection (needs sessionToken) const client = new shell_client_v2_1.ShellClient({ sessionId, relayUrl: options.relay, shell: options.shell, serverCommand: options.command, serverPort: parseInt(options.port), createTunnel: options.tunnel, autoStartClaude: !options.noClaude, password: options.password, isPublic: options.public, resumeClaude: options.resume || options.continue, verbose: options.verbose }); await client.connect(); process.on('SIGINT', () => { console.log(chalk_1.default.yellow('\nDisconnecting...')); client.disconnect(); process.exit(0); }); process.on('SIGTERM', () => { client.disconnect(); process.exit(0); }); } catch (error) { console.error(chalk_1.default.red(`Error: ${error}`)); process.exit(1); } }); program.parse(); //# sourceMappingURL=cli.js.map