vibetunnel
Version:
Terminal sharing server with web interface - supports macOS, Linux, and headless environments
701 lines • 106 kB
JavaScript
#!/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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