UNPKG

vibetunnel

Version:

Terminal sharing server with web interface - supports macOS, Linux, and headless environments

701 lines 106 kB
#!/usr/bin/env pnpm exec tsx --no-deprecation "use strict"; /** * VibeTunnel Forward (fwd.ts) * * A simple command-line tool that spawns a PTY session and forwards it * using the VibeTunnel PTY infrastructure. * * Usage: * pnpm exec tsx src/fwd.ts <command> [args...] * pnpm exec tsx src/fwd.ts claude --resume */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.startVibeTunnelForward = startVibeTunnelForward; const chalk_1 = __importDefault(require("chalk")); const fs = __importStar(require("fs")); const os = __importStar(require("os")); const path = __importStar(require("path")); const util_1 = require("util"); const types_js_1 = require("../shared/types.js"); const index_js_1 = require("./pty/index.js"); const session_manager_js_1 = require("./pty/session-manager.js"); const socket_client_js_1 = require("./pty/socket-client.js"); const activity_detector_js_1 = require("./utils/activity-detector.js"); const claude_patcher_js_1 = require("./utils/claude-patcher.js"); const git_info_js_1 = require("./utils/git-info.js"); const logger_js_1 = require("./utils/logger.js"); const session_naming_js_1 = require("./utils/session-naming.js"); const terminal_title_js_1 = require("./utils/terminal-title.js"); const verbosity_parser_js_1 = require("./utils/verbosity-parser.js"); const version_js_1 = require("./version.js"); const logger = (0, logger_js_1.createLogger)('fwd'); const _execFile = (0, util_1.promisify)(require('child_process').execFile); function showUsage() { console.log(chalk_1.default.blue(`VibeTunnel Forward v${version_js_1.VERSION}`) + chalk_1.default.gray(` (${version_js_1.BUILD_DATE})`)); console.log(''); console.log('Usage:'); console.log(' pnpm exec tsx src/fwd.ts [--session-id <id>] [--title-mode <mode>] [--verbosity <level>] <command> [args...]'); console.log(''); console.log('Options:'); console.log(' --session-id <id> Use a pre-generated session ID'); console.log(' --title-mode <mode> Terminal title mode: none, filter, static, dynamic'); console.log(' (defaults to none for most commands, dynamic for claude)'); console.log(' --update-title <title> Update session title and exit (requires --session-id)'); console.log(' --verbosity <level> Set logging verbosity: silent, error, warn, info, verbose, debug'); console.log(' (defaults to error)'); console.log(' --log-file <path> Override default log file location'); console.log(' (defaults to ~/.vibetunnel/log.txt)'); console.log(''); console.log('Title Modes:'); console.log(' none - No title management (default)'); console.log(' filter - Block all title changes from applications'); console.log(' static - Show working directory and command'); console.log(' dynamic - Show directory, command, and activity (auto-selected for claude)'); console.log(''); console.log('Verbosity Levels:'); console.log(` ${chalk_1.default.gray('silent')} - No output except critical errors`); console.log(` ${chalk_1.default.red('error')} - Only errors ${chalk_1.default.gray('(default)')}`); console.log(` ${chalk_1.default.yellow('warn')} - Errors and warnings`); console.log(` ${chalk_1.default.green('info')} - Errors, warnings, and informational messages`); console.log(` ${chalk_1.default.blue('verbose')} - All messages except debug`); console.log(` ${chalk_1.default.magenta('debug')} - All messages including debug`); console.log(''); console.log(`Quick verbosity: ${chalk_1.default.cyan('-q (quiet), -v (verbose), -vv (extra), -vvv (debug)')}`); console.log(''); console.log('Environment Variables:'); console.log(' VIBETUNNEL_TITLE_MODE=<mode> Set default title mode'); console.log(' VIBETUNNEL_CLAUDE_DYNAMIC_TITLE=1 Force dynamic title for Claude'); console.log(' VIBETUNNEL_LOG_LEVEL=<level> Set default verbosity level'); console.log(' VIBETUNNEL_DEBUG=1 Enable debug mode (legacy)'); console.log(''); console.log('Examples:'); console.log(' pnpm exec tsx src/fwd.ts claude --resume'); console.log(' pnpm exec tsx src/fwd.ts --title-mode static bash -l'); console.log(' pnpm exec tsx src/fwd.ts --title-mode filter vim'); console.log(' pnpm exec tsx src/fwd.ts --session-id abc123 claude'); console.log(' pnpm exec tsx src/fwd.ts --update-title "New Title" --session-id abc123'); console.log(' pnpm exec tsx src/fwd.ts --verbosity silent npm test'); console.log(''); console.log('The command will be spawned in the current working directory'); console.log('and managed through the VibeTunnel PTY infrastructure.'); } async function startVibeTunnelForward(args) { // Parse verbosity from environment variables let verbosityLevel = (0, verbosity_parser_js_1.parseVerbosityFromEnv)(); // Set debug mode on logger for backward compatibility if (verbosityLevel === logger_js_1.VerbosityLevel.DEBUG) { logger.setDebugMode(true); } // Parse command line arguments if (args.length === 0 || args[0] === '--help' || args[0] === '-h') { showUsage(); (0, logger_js_1.closeLogger)(); process.exit(0); } logger.debug(chalk_1.default.blue(`VibeTunnel Forward v${version_js_1.VERSION}`) + chalk_1.default.gray(` (${version_js_1.BUILD_DATE})`)); logger.debug(`Full command: ${args.join(' ')}`); // Parse command line arguments let sessionId; let titleMode = types_js_1.TitleMode.NONE; let updateTitle; let logFilePath; let remainingArgs = args; // Check environment variables for title mode if (process.env.VIBETUNNEL_TITLE_MODE) { const envMode = process.env.VIBETUNNEL_TITLE_MODE.toLowerCase(); if (Object.values(types_js_1.TitleMode).includes(envMode)) { titleMode = envMode; logger.debug(`Title mode set from environment: ${titleMode}`); } } // Force dynamic mode for Claude via environment variable if (process.env.VIBETUNNEL_CLAUDE_DYNAMIC_TITLE === '1' || process.env.VIBETUNNEL_CLAUDE_DYNAMIC_TITLE === 'true') { titleMode = types_js_1.TitleMode.DYNAMIC; logger.debug('Forced dynamic title mode for Claude via environment variable'); } // Parse flags while (remainingArgs.length > 0) { if (remainingArgs[0] === '--session-id' && remainingArgs.length > 1) { sessionId = remainingArgs[1]; remainingArgs = remainingArgs.slice(2); } else if (remainingArgs[0] === '--update-title' && remainingArgs.length > 1) { updateTitle = remainingArgs[1]; remainingArgs = remainingArgs.slice(2); } else if (remainingArgs[0] === '--title-mode' && remainingArgs.length > 1) { const mode = remainingArgs[1].toLowerCase(); if (Object.values(types_js_1.TitleMode).includes(mode)) { titleMode = mode; } else { logger.error(`Invalid title mode: ${remainingArgs[1]}`); logger.error(`Valid modes: ${Object.values(types_js_1.TitleMode).join(', ')}`); (0, logger_js_1.closeLogger)(); process.exit(1); } remainingArgs = remainingArgs.slice(2); } else if (remainingArgs[0] === '--verbosity' && remainingArgs.length > 1) { const parsedLevel = (0, logger_js_1.parseVerbosityLevel)(remainingArgs[1]); if (parsedLevel !== undefined) { verbosityLevel = parsedLevel; } else { logger.error(`Invalid verbosity level: ${remainingArgs[1]}`); logger.error('Valid levels: silent, error, warn, info, verbose, debug'); (0, logger_js_1.closeLogger)(); process.exit(1); } remainingArgs = remainingArgs.slice(2); } else if (remainingArgs[0] === '--log-file' && remainingArgs.length > 1) { logFilePath = remainingArgs[1]; remainingArgs = remainingArgs.slice(2); } else { // Not a flag, must be the start of the command break; } } // Handle -- separator (used by some shells as end-of-options marker) // This allows commands like: fwd -- command-with-dashes if (remainingArgs[0] === '--' && remainingArgs.length > 1) { remainingArgs = remainingArgs.slice(1); } // Apply log file path if set if (logFilePath !== undefined) { (0, logger_js_1.setLogFilePath)(logFilePath); logger.debug(`Log file path set to: ${logFilePath}`); } // Apply verbosity level if set if (verbosityLevel !== undefined) { (0, logger_js_1.setVerbosityLevel)(verbosityLevel); if (verbosityLevel >= logger_js_1.VerbosityLevel.INFO) { logger.log(`Verbosity level set to: ${logger_js_1.VerbosityLevel[verbosityLevel].toLowerCase()}`); } } // Handle special case: --update-title mode if (updateTitle !== undefined) { if (!sessionId) { logger.error('--update-title requires --session-id'); (0, logger_js_1.closeLogger)(); process.exit(1); } // Initialize session manager const controlPath = path.join(os.homedir(), '.vibetunnel', 'control'); const sessionManager = new session_manager_js_1.SessionManager(controlPath); // Validate session ID format for security if (!/^[a-zA-Z0-9_-]+$/.test(sessionId)) { logger.error(`Invalid session ID format: "${sessionId}". Session IDs must only contain letters, numbers, hyphens (-), and underscores (_).`); (0, logger_js_1.closeLogger)(); process.exit(1); } try { // Load existing session info const sessionInfo = sessionManager.loadSessionInfo(sessionId); if (!sessionInfo) { logger.error(`Session ${sessionId} not found`); (0, logger_js_1.closeLogger)(); process.exit(1); } // Sanitize the title - limit length and filter out problematic characters const sanitizedTitle = updateTitle .substring(0, 256) // Limit length .split('') .filter((char) => { const code = char.charCodeAt(0); // Allow printable characters (space to ~) and extended ASCII/Unicode return code >= 32 && code !== 127 && (code < 128 || code > 159); }) .join(''); // Update the title via IPC if session is active const socketPath = path.join(controlPath, sessionId, 'ipc.sock'); // Check if IPC socket exists (session is active) if (fs.existsSync(socketPath)) { logger.debug(`IPC socket found, sending title update via IPC`); // Connect to IPC socket and send update-title command const socketClient = new socket_client_js_1.VibeTunnelSocketClient(socketPath, { autoReconnect: false, // One-shot operation }); try { await socketClient.connect(); // Send update-title command const sent = socketClient.updateTitle(sanitizedTitle); if (sent) { logger.log(`Session title updated to: ${sanitizedTitle}`); // IPC update succeeded, server will handle the file update socketClient.disconnect(); (0, logger_js_1.closeLogger)(); process.exit(0); } else { logger.warn(`Failed to send title update via IPC, falling back to file update`); } // Disconnect after sending socketClient.disconnect(); } catch (ipcError) { logger.warn(`IPC connection failed: ${ipcError}, falling back to file update`); } } else { logger.debug(`No IPC socket found, session might not be active`); } // Only update the file if IPC failed or socket doesn't exist sessionInfo.name = sanitizedTitle; sessionManager.saveSessionInfo(sessionId, sessionInfo); logger.log(`Session title updated to: ${sanitizedTitle}`); (0, logger_js_1.closeLogger)(); process.exit(0); } catch (error) { logger.error(`Failed to update session title: ${error instanceof Error ? error.message : String(error)}`); (0, logger_js_1.closeLogger)(); process.exit(1); } } let command = remainingArgs; if (command.length === 0) { logger.error('No command specified'); showUsage(); (0, logger_js_1.closeLogger)(); process.exit(1); } // Check if this is Claude and patch it if necessary (only in debug mode) if (process.env.VIBETUNNEL_DEBUG === '1' || process.env.VIBETUNNEL_DEBUG === 'true') { const patchedCommand = (0, claude_patcher_js_1.checkAndPatchClaude)(command); if (patchedCommand !== command) { command = patchedCommand; logger.debug(`Command updated after patching`); } } // Auto-select dynamic mode for Claude if no mode was explicitly set if (titleMode === types_js_1.TitleMode.NONE) { // Check all command arguments for Claude const isClaudeCommand = command.some((arg) => arg.toLowerCase().includes('claude')); if (isClaudeCommand) { titleMode = types_js_1.TitleMode.DYNAMIC; logger.log(chalk_1.default.cyan('✓ Auto-selected dynamic title mode for Claude')); logger.debug(`Detected Claude in command: ${command.join(' ')}`); } } const cwd = process.cwd(); // Initialize PTY manager with fallback support const controlPath = path.join(os.homedir(), '.vibetunnel', 'control'); logger.debug(`Control path: ${controlPath}`); // Initialize PtyManager before creating instance await index_js_1.PtyManager.initialize().catch((error) => { logger.error('Failed to initialize PTY manager:', error); (0, logger_js_1.closeLogger)(); process.exit(1); }); const ptyManager = new index_js_1.PtyManager(controlPath); // Store original terminal dimensions // For external spawns, wait a moment for terminal to fully initialize const isExternalSpawn = process.env.VIBETUNNEL_SESSION_ID !== undefined; let originalCols; let originalRows; if (isExternalSpawn) { // Give terminal window time to fully initialize its dimensions await new Promise((resolve) => setTimeout(resolve, 100)); // For external spawns, try to get the actual terminal size // If stdout isn't properly connected, don't use fallback values if (process.stdout.isTTY && process.stdout.columns && process.stdout.rows) { originalCols = process.stdout.columns; originalRows = process.stdout.rows; logger.debug(`External spawn using actual terminal size: ${originalCols}x${originalRows}`); } else { // Don't pass dimensions - let PTY use terminal's natural size logger.debug('External spawn: terminal dimensions not available, using terminal defaults'); } } else { // For non-external spawns, use reasonable defaults originalCols = process.stdout.columns || 120; originalRows = process.stdout.rows || 40; logger.debug(`Regular spawn with dimensions: ${originalCols}x${originalRows}`); } try { // Create a human-readable session name const sessionName = (0, session_naming_js_1.generateSessionName)(command, cwd); // Pre-generate session ID if not provided const finalSessionId = sessionId || `fwd_${Date.now()}`; logger.log(`Creating session for command: ${command.join(' ')}`); logger.debug(`Session ID: ${finalSessionId}, working directory: ${cwd}`); // Log title mode if not default if (titleMode !== types_js_1.TitleMode.NONE) { const modeDescriptions = { [types_js_1.TitleMode.FILTER]: 'Terminal title changes will be blocked', [types_js_1.TitleMode.STATIC]: 'Terminal title will show path and command', [types_js_1.TitleMode.DYNAMIC]: 'Terminal title will show path, command, and activity', }; logger.log(chalk_1.default.cyan(`✓ ${modeDescriptions[titleMode]}`)); } // Detect Git information const gitInfo = await (0, git_info_js_1.detectGitInfo)(cwd); // Variables that need to be accessible in cleanup let sessionFileWatcher; let fileWatchDebounceTimer; let isExitingNormally = false; const sessionOptions = { sessionId: finalSessionId, name: sessionName, workingDir: cwd, titleMode: titleMode, forwardToStdout: true, gitRepoPath: gitInfo.gitRepoPath, gitBranch: gitInfo.gitBranch, gitAheadCount: gitInfo.gitAheadCount, gitBehindCount: gitInfo.gitBehindCount, gitHasChanges: gitInfo.gitHasChanges, gitIsWorktree: gitInfo.gitIsWorktree, gitMainRepoPath: gitInfo.gitMainRepoPath, onExit: async (exitCode) => { // Mark that we're exiting normally isExitingNormally = true; // Show exit message logger.log(chalk_1.default.yellow(`\n✓ VibeTunnel session ended`) + chalk_1.default.gray(` (exit code: ${exitCode})`)); // Remove resize listener process.stdout.removeListener('resize', resizeHandler); // Restore terminal settings and clean up stdin if (process.stdin.isTTY) { logger.debug('Restoring terminal to normal mode'); process.stdin.setRawMode(false); } process.stdin.pause(); process.stdin.removeAllListeners(); // Destroy stdin to ensure it doesn't keep the process alive if (process.stdin.destroy) { process.stdin.destroy(); } // Restore original stdout.write if we hooked it if (cleanupStdout) { cleanupStdout(); } // Clean up file watchers if (sessionFileWatcher) { sessionFileWatcher.close(); sessionFileWatcher = undefined; logger.debug('Closed session file watcher'); } if (fileWatchDebounceTimer) { clearTimeout(fileWatchDebounceTimer); } // Stop watching the file fs.unwatchFile(sessionJsonPath); // Clean up only this session, not all sessions logger.debug(`Cleaning up session ${finalSessionId}`); try { await ptyManager.killSession(finalSessionId); } catch (error) { // Session might already be cleaned up logger.debug(`Session ${finalSessionId} cleanup error (likely already cleaned):`, error); } // Force exit (0, logger_js_1.closeLogger)(); process.exit(exitCode || 0); }, }; // Only add dimensions if they're available (for non-external spawns or when TTY is properly connected) if (originalCols !== undefined && originalRows !== undefined) { sessionOptions.cols = originalCols; sessionOptions.rows = originalRows; } const result = await ptyManager.createSession(command, sessionOptions); // Get session info const session = ptyManager.getSession(result.sessionId); if (!session) { throw new Error('Session not found after creation'); } // Log session info with version logger.log(chalk_1.default.green(`✓ VibeTunnel session started`) + chalk_1.default.gray(` (v${version_js_1.VERSION})`)); logger.log(chalk_1.default.gray('Command:'), command.join(' ')); logger.log(chalk_1.default.gray('Control directory:'), path.join(controlPath, result.sessionId)); logger.log(chalk_1.default.gray('Build:'), `${version_js_1.BUILD_DATE} | Commit: ${version_js_1.GIT_COMMIT}`); // Connect to the session's IPC socket const socketPath = path.join(controlPath, result.sessionId, 'ipc.sock'); const socketClient = new socket_client_js_1.VibeTunnelSocketClient(socketPath, { autoReconnect: true, heartbeatInterval: 30000, // 30 seconds }); // Wait for socket connection try { await socketClient.connect(); logger.debug('Connected to session IPC socket'); } catch (error) { logger.error('Failed to connect to session socket:', error); throw error; } // Set up terminal resize handler const resizeHandler = () => { const cols = process.stdout.columns || 80; const rows = process.stdout.rows || 24; logger.debug(`Terminal resized to ${cols}x${rows}`); // Send resize command through socket if (!socketClient.resize(cols, rows)) { logger.error('Failed to send resize command'); } }; // Listen for terminal resize events process.stdout.on('resize', resizeHandler); // Set up file watcher for session.json changes (for external updates) const sessionJsonPath = path.join(controlPath, result.sessionId, 'session.json'); let lastKnownSessionName = result.sessionInfo.name; // Set up file watcher with retry logic const setupFileWatcher = async (retryCount = 0) => { const maxRetries = 5; const retryDelay = 500 * 2 ** retryCount; // Exponential backoff try { // Check if file exists if (!fs.existsSync(sessionJsonPath)) { if (retryCount < maxRetries) { logger.debug(`Session file not found, retrying in ${retryDelay}ms (attempt ${retryCount + 1}/${maxRetries})`); setTimeout(() => setupFileWatcher(retryCount + 1), retryDelay); return; } else { logger.warn(`Session file not found after ${maxRetries} attempts: ${sessionJsonPath}`); return; } } logger.log(`Setting up file watcher for session name changes`); // Function to check and update title if session name changed const checkSessionNameChange = () => { try { // Check file still exists before reading if (!fs.existsSync(sessionJsonPath)) { return; } const sessionContent = fs.readFileSync(sessionJsonPath, 'utf-8'); const updatedInfo = JSON.parse(sessionContent); // Check if session name changed if (updatedInfo.name !== lastKnownSessionName) { logger.debug(`[File Watch] Session name changed from "${lastKnownSessionName}" to "${updatedInfo.name}"`); lastKnownSessionName = updatedInfo.name; // Always update terminal title when session name changes // Generate new title sequence based on title mode let titleSequence; if (titleMode === types_js_1.TitleMode.NONE || titleMode === types_js_1.TitleMode.FILTER) { // For NONE and FILTER modes, just use the session name titleSequence = `\x1B]2;${updatedInfo.name}\x07`; } else { // For STATIC and DYNAMIC, use the full format with path and command titleSequence = (0, terminal_title_js_1.generateTitleSequence)(cwd, command, updatedInfo.name); } // Write title sequence to terminal process.stdout.write(titleSequence); logger.log(`Updated terminal title to "${updatedInfo.name}" via file watcher`); } } catch (error) { logger.error('Failed to check session.json:', error); } }; // Use fs.watchFile for more reliable file monitoring (polling-based) fs.watchFile(sessionJsonPath, { interval: 500 }, (curr, prev) => { logger.debug(`[File Watch] File stats changed - mtime: ${curr.mtime} vs ${prev.mtime}`); if (curr.mtime !== prev.mtime) { checkSessionNameChange(); } }); // Also use fs.watch as a fallback for immediate notifications try { const sessionDir = path.dirname(sessionJsonPath); sessionFileWatcher = fs.watch(sessionDir, (eventType, filename) => { // Only log in debug mode to avoid noise logger.debug(`[File Watch] Directory event: ${eventType} on ${filename || 'unknown'}`); // Check if it's our file // On macOS, filename might be undefined, so we can't filter properly // In that case, skip fs.watch events and rely on fs.watchFile instead if (filename && (filename === 'session.json' || filename === 'session.json.tmp')) { // Debounce rapid changes if (fileWatchDebounceTimer) { clearTimeout(fileWatchDebounceTimer); } fileWatchDebounceTimer = setTimeout(checkSessionNameChange, 100); } }); } catch (error) { logger.warn('Failed to set up fs.watch, relying on fs.watchFile:', error); } logger.log(`File watcher successfully set up with polling fallback`); // Clean up watcher on error if it was created sessionFileWatcher?.on('error', (error) => { logger.error('File watcher error:', error); sessionFileWatcher?.close(); sessionFileWatcher = undefined; }); } catch (error) { logger.error('Failed to set up file watcher:', error); if (retryCount < maxRetries) { setTimeout(() => setupFileWatcher(retryCount + 1), retryDelay); } } }; // Start setting up the file watcher after a short delay setTimeout(() => setupFileWatcher(), 500); // Set up activity detector for Claude status updates let activityDetector; let cleanupStdout; if (titleMode === types_js_1.TitleMode.DYNAMIC) { activityDetector = new activity_detector_js_1.ActivityDetector(command, sessionId); // Hook into stdout to detect Claude status const originalStdoutWrite = process.stdout.write.bind(process.stdout); let isProcessingActivity = false; // Create a proper override that handles all overloads const _stdoutWriteOverride = function (chunk, encodingOrCallback, callback) { // Handle the overload: write(chunk, callback) if (typeof encodingOrCallback === 'function') { callback = encodingOrCallback; encodingOrCallback = undefined; } if (isProcessingActivity) { if (callback) { return originalStdoutWrite.call(this, chunk, encodingOrCallback, callback); } else if (encodingOrCallback && typeof encodingOrCallback === 'string') { return originalStdoutWrite.call(this, chunk, encodingOrCallback); } else { return originalStdoutWrite.call(this, chunk); } } isProcessingActivity = true; try { // Process output through activity detector if (activityDetector && typeof chunk === 'string') { const { filteredData, activity } = activityDetector.processOutput(chunk); // Send status update if detected if (activity.specificStatus) { socketClient.sendStatus(activity.specificStatus.app, activity.specificStatus.status); } // Call original with correct arguments if (callback) { return originalStdoutWrite.call(this, filteredData, encodingOrCallback, callback); } else if (encodingOrCallback && typeof encodingOrCallback === 'string') { return originalStdoutWrite.call(this, filteredData, encodingOrCallback); } else { return originalStdoutWrite.call(this, filteredData); } } // Pass through as-is if not string or no detector if (callback) { return originalStdoutWrite.call(this, chunk, encodingOrCallback, callback); } else if (encodingOrCallback && typeof encodingOrCallback === 'string') { return originalStdoutWrite.call(this, chunk, encodingOrCallback); } else { return originalStdoutWrite.call(this, chunk); } } finally { isProcessingActivity = false; } }; // Apply the override process.stdout.write = _stdoutWriteOverride; // Store reference for cleanup cleanupStdout = () => { process.stdout.write = originalStdoutWrite; }; // Ensure cleanup happens on process exit process.on('exit', cleanupStdout); process.on('SIGINT', cleanupStdout); process.on('SIGTERM', cleanupStdout); } // Set up raw mode for terminal input if (process.stdin.isTTY) { logger.debug('Setting terminal to raw mode for input forwarding'); process.stdin.setRawMode(true); } process.stdin.resume(); process.stdin.setEncoding('utf8'); // Forward stdin through socket process.stdin.on('data', (data) => { // Send through socket if (!socketClient.sendStdin(data)) { logger.error('Failed to send stdin data'); } }); // Handle socket events socketClient.on('disconnect', (error) => { // Don't log error if we're exiting normally if (isExitingNormally) { logger.debug('Socket disconnected during normal exit'); return; } // Check if this is a common disconnect error during normal operation const errorMessage = error?.message || ''; const isNormalDisconnect = errorMessage.includes('EPIPE') || errorMessage.includes('ECONNRESET') || errorMessage.includes('socket hang up') || errorMessage === 'Unknown error' || // Common during clean exits !error; // No error object means clean disconnect if (isNormalDisconnect) { logger.debug('Socket disconnected (normal termination)'); } else { logger.error('Socket disconnected:', error?.message || 'Unknown error'); } process.exit(1); }); socketClient.on('error', (error) => { logger.error('Socket error:', error); }); // The process will stay alive because stdin is in raw mode and resumed } catch (error) { logger.error('Failed to create or manage session:', error); (0, logger_js_1.closeLogger)(); process.exit(1); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZndkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcnZlci9md2QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFFQTs7Ozs7Ozs7O0dBU0c7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBc0ZILHdEQXlxQkM7QUE3dkJELGtEQUEwQjtBQUMxQix1Q0FBeUI7QUFDekIsdUNBQXlCO0FBQ3pCLDJDQUE2QjtBQUM3QiwrQkFBaUM7QUFDakMsaURBQWlFO0FBQ2pFLDZDQUE0QztBQUM1QyxpRUFBMEQ7QUFDMUQsNkRBQWdFO0FBQ2hFLHVFQUFnRTtBQUNoRSxpRUFBZ0U7QUFDaEUscURBQW9EO0FBQ3BELGlEQU8yQjtBQUMzQixpRUFBZ0U7QUFDaEUsaUVBQWtFO0FBQ2xFLHFFQUFvRTtBQUNwRSw2Q0FBK0Q7QUFFL0QsTUFBTSxNQUFNLEdBQUcsSUFBQSx3QkFBWSxFQUFDLEtBQUssQ0FBQyxDQUFDO0FBQ25DLE1BQU0sU0FBUyxHQUFHLElBQUEsZ0JBQVMsRUFBQyxPQUFPLENBQUMsZUFBZSxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUM7QUFFL0QsU0FBUyxTQUFTO0lBQ2hCLE9BQU8sQ0FBQyxHQUFHLENBQUMsZUFBSyxDQUFDLElBQUksQ0FBQyx1QkFBdUIsb0JBQU8sRUFBRSxDQUFDLEdBQUcsZUFBSyxDQUFDLElBQUksQ0FBQyxLQUFLLHVCQUFVLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDM0YsT0FBTyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUNoQixPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3RCLE9BQU8sQ0FBQyxHQUFHLENBQ1QsZ0hBQWdILENBQ2pILENBQUM7SUFDRixPQUFPLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ2hCLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDeEIsT0FBTyxDQUFDLEdBQUcsQ0FB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