mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-25 14:57:37 +00:00
handle session id passing + better error messages
This commit is contained in:
parent
34fc38d13f
commit
d820a4b147
5 changed files with 112 additions and 24 deletions
|
|
@ -615,7 +615,7 @@ final class TerminalLauncher {
|
|||
// For Bun server, use fwd to create sessions
|
||||
logger.info("Using Bun server session creation via fwd")
|
||||
let bunPath = findBunExecutable()
|
||||
let bunCommand = buildBunCommand(bunPath: bunPath, userCommand: command, workingDir: escapedWorkingDir)
|
||||
let bunCommand = buildBunCommand(bunPath: bunPath, userCommand: command, workingDir: escapedWorkingDir, sessionId: sessionId)
|
||||
let fullCommand = "cd \"\(escapedWorkingDir)\" && \(bunCommand) && exit"
|
||||
|
||||
// Get the preferred terminal or fallback
|
||||
|
|
@ -674,17 +674,18 @@ final class TerminalLauncher {
|
|||
let bunCommand = buildBunCommand(
|
||||
bunPath: bunPath,
|
||||
userCommand: actualCommand,
|
||||
workingDir: escapedDir
|
||||
workingDir: escapedDir,
|
||||
sessionId: sessionId
|
||||
)
|
||||
fullCommand = "cd \"\(escapedDir)\" && \(bunCommand) && exit"
|
||||
} else {
|
||||
// Fallback if format is different
|
||||
let bunCommand = buildBunCommand(bunPath: bunPath, userCommand: command, workingDir: escapedDir)
|
||||
let bunCommand = buildBunCommand(bunPath: bunPath, userCommand: command, workingDir: escapedDir, sessionId: sessionId)
|
||||
fullCommand = "cd \"\(escapedDir)\" && \(bunCommand) && exit"
|
||||
}
|
||||
} else {
|
||||
// Command is just the user command
|
||||
let bunCommand = buildBunCommand(bunPath: bunPath, userCommand: command, workingDir: escapedDir)
|
||||
let bunCommand = buildBunCommand(bunPath: bunPath, userCommand: command, workingDir: escapedDir, sessionId: sessionId)
|
||||
fullCommand = "cd \"\(escapedDir)\" && \(bunCommand) && exit"
|
||||
}
|
||||
} else {
|
||||
|
|
@ -751,9 +752,14 @@ final class TerminalLauncher {
|
|||
return "echo 'VibeTunnel: Bun executable not found in app bundle'; false"
|
||||
}
|
||||
|
||||
private func buildBunCommand(bunPath: String, userCommand: String, workingDir: String) -> String {
|
||||
private func buildBunCommand(bunPath: String, userCommand: String, workingDir: String, sessionId: String? = nil) -> String {
|
||||
// Bun executable has fwd command built-in
|
||||
logger.info("Using Bun executable for session creation")
|
||||
return "\"\(bunPath)\" fwd \(userCommand)"
|
||||
if let sessionId = sessionId {
|
||||
// Pass the pre-generated session ID to fwd
|
||||
return "\"\(bunPath)\" fwd --session-id \(sessionId) \(userCommand)"
|
||||
} else {
|
||||
return "\"\(bunPath)\" fwd \(userCommand)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -492,6 +492,7 @@ export class VibeTunnelApp extends LitElement {
|
|||
<file-browser-enhanced
|
||||
.visible=${this.showFileBrowser}
|
||||
.mode=${'browse'}
|
||||
.session=${null}
|
||||
@browser-cancel=${() => (this.showFileBrowser = false)}
|
||||
></file-browser-enhanced>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -19,16 +19,18 @@ function showUsage() {
|
|||
console.log('VibeTunnel Forward (fwd.ts)');
|
||||
console.log('');
|
||||
console.log('Usage:');
|
||||
console.log(' npx tsx src/fwd.ts <command> [args...]');
|
||||
console.log(' npx tsx src/fwd.ts --monitor-only <command> [args...]');
|
||||
console.log(' npx tsx src/fwd.ts [--session-id <id>] <command> [args...]');
|
||||
console.log(' npx tsx src/fwd.ts [--session-id <id>] --monitor-only <command> [args...]');
|
||||
console.log('');
|
||||
console.log('Options:');
|
||||
console.log(' --monitor-only Just create session and monitor, no interactive I/O');
|
||||
console.log(' --session-id <id> Use a pre-generated session ID');
|
||||
console.log(' --monitor-only Just create session and monitor, no interactive I/O');
|
||||
console.log('');
|
||||
console.log('Examples:');
|
||||
console.log(' npx tsx src/fwd.ts claude --resume');
|
||||
console.log(' npx tsx src/fwd.ts bash -l');
|
||||
console.log(' npx tsx src/fwd.ts python3 -i');
|
||||
console.log(' npx tsx src/fwd.ts --session-id abc123 claude');
|
||||
console.log(' npx tsx src/fwd.ts --monitor-only long-running-command');
|
||||
console.log('');
|
||||
console.log('The command will be spawned in the current working directory');
|
||||
|
|
@ -42,8 +44,17 @@ export async function startVibeTunnelForward(args: string[]) {
|
|||
process.exit(0);
|
||||
}
|
||||
|
||||
const monitorOnly = args[0] === '--monitor-only';
|
||||
const command = monitorOnly ? args.slice(1) : args;
|
||||
// Check for --session-id parameter
|
||||
let sessionId: string | undefined;
|
||||
let remainingArgs = args;
|
||||
|
||||
if (args[0] === '--session-id' && args.length > 1) {
|
||||
sessionId = args[1];
|
||||
remainingArgs = args.slice(2);
|
||||
}
|
||||
|
||||
const monitorOnly = remainingArgs[0] === '--monitor-only';
|
||||
const command = monitorOnly ? remainingArgs.slice(1) : remainingArgs;
|
||||
|
||||
if (command.length === 0) {
|
||||
console.error('Error: No command specified');
|
||||
|
|
@ -61,11 +72,41 @@ export async function startVibeTunnelForward(args: string[]) {
|
|||
const ptyManager = new PtyManager(controlPath);
|
||||
|
||||
try {
|
||||
// Handle shell expansion for single commands that aren't common shells
|
||||
let finalCommand = command;
|
||||
const isShell =
|
||||
command.length === 1 &&
|
||||
(command[0].endsWith('bash') || command[0].endsWith('zsh') || command[0].endsWith('sh'));
|
||||
|
||||
// Match Linux implementation - add -i for shells
|
||||
if (isShell && command.length === 1) {
|
||||
// For shells, add -i flag to ensure interactive mode
|
||||
console.log(`Adding -i flag for interactive shell: ${command[0]}`);
|
||||
finalCommand = [...command, '-i'];
|
||||
} else if (command.length === 1 && command[0] === 'claude') {
|
||||
// Special handling for claude - it needs to run in a shell environment
|
||||
const userShell = process.env.SHELL || '/bin/zsh';
|
||||
console.log(`Running claude through shell for proper terminal setup: ${userShell}`);
|
||||
// Run claude through an interactive shell
|
||||
finalCommand = [userShell, '-i', '-c', 'claude'];
|
||||
} else {
|
||||
// All other commands - spawn as-is (like Linux does)
|
||||
console.log(`Spawning command as-is: ${command.join(' ')}`);
|
||||
finalCommand = command;
|
||||
}
|
||||
|
||||
// Create the session
|
||||
const sessionName = `fwd_${command[0]}_${Date.now()}`;
|
||||
console.log(`Creating session: ${sessionName}`);
|
||||
if (sessionId) {
|
||||
console.log(`Using pre-generated session ID: ${sessionId}`);
|
||||
}
|
||||
|
||||
const result = await ptyManager.createSession(command, {
|
||||
console.log(`Final command to spawn: ${JSON.stringify(finalCommand)}`);
|
||||
console.log(`Environment TERM: ${process.env.TERM}`);
|
||||
|
||||
const result = await ptyManager.createSession(finalCommand, {
|
||||
sessionId, // Use the pre-generated session ID if provided
|
||||
sessionName,
|
||||
workingDir: cwd,
|
||||
term: process.env.TERM || 'xterm-256color',
|
||||
|
|
@ -149,10 +190,19 @@ export async function startVibeTunnelForward(args: string[]) {
|
|||
|
||||
if (!fs.existsSync(controlPath)) {
|
||||
if (!isWindows) {
|
||||
const { spawnSync } = require('child_process');
|
||||
const result = spawnSync('mkfifo', [controlPath], { stdio: 'ignore' });
|
||||
if (result.status === 0) {
|
||||
useFifo = true;
|
||||
try {
|
||||
const { spawnSync } = require('child_process');
|
||||
const result = spawnSync('mkfifo', [controlPath], { stdio: 'pipe' });
|
||||
if (result.status === 0) {
|
||||
useFifo = true;
|
||||
console.log(`Created control FIFO at: ${controlPath}`);
|
||||
} else {
|
||||
console.warn(
|
||||
`Failed to create FIFO: ${result.stderr?.toString() || 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`Error creating FIFO: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,16 +109,46 @@ export class PtyManager {
|
|||
);
|
||||
|
||||
// Create PTY process
|
||||
const ptyProcess = pty.spawn(command[0], command.slice(1), {
|
||||
name: term,
|
||||
cols,
|
||||
rows,
|
||||
cwd: workingDir,
|
||||
env: {
|
||||
let ptyProcess;
|
||||
try {
|
||||
// Log the command we're about to spawn for debugging
|
||||
console.log(
|
||||
`[PTY] Spawning command: ${command[0]} with args: ${JSON.stringify(command.slice(1))}`
|
||||
);
|
||||
console.log(`[PTY] Working directory: ${workingDir}`);
|
||||
console.log(`[PTY] Terminal: ${term}, Size: ${cols}x${rows}`);
|
||||
|
||||
// Set up environment like Linux implementation
|
||||
const ptyEnv = {
|
||||
...process.env,
|
||||
TERM: term,
|
||||
},
|
||||
});
|
||||
SHELL: command[0], // Set SHELL to the command being run (like Linux does)
|
||||
};
|
||||
|
||||
ptyProcess = pty.spawn(command[0], command.slice(1), {
|
||||
name: term,
|
||||
cols,
|
||||
rows,
|
||||
cwd: workingDir,
|
||||
env: ptyEnv,
|
||||
});
|
||||
} catch (spawnError: any) {
|
||||
// Provide better error messages for common issues
|
||||
let errorMessage = spawnError.message;
|
||||
|
||||
if (spawnError.code === 'ENOENT' || errorMessage.includes('ENOENT')) {
|
||||
errorMessage = `Command not found: '${command[0]}'. Please ensure the command exists and is in your PATH.`;
|
||||
} else if (spawnError.code === 'EACCES' || errorMessage.includes('EACCES')) {
|
||||
errorMessage = `Permission denied: '${command[0]}'. The command exists but is not executable.`;
|
||||
} else if (spawnError.code === 'ENXIO' || errorMessage.includes('ENXIO')) {
|
||||
errorMessage = `Failed to allocate terminal for '${command[0]}'. This may occur if the command doesn't exist or the system cannot create a pseudo-terminal.`;
|
||||
} else if (errorMessage.includes('cwd') || errorMessage.includes('working directory')) {
|
||||
errorMessage = `Working directory does not exist: '${workingDir}'`;
|
||||
}
|
||||
|
||||
console.error(`PTY spawn error for command '${command.join(' ')}':`, spawnError);
|
||||
throw new PtyError(errorMessage, 'SPAWN_FAILED');
|
||||
}
|
||||
|
||||
// Create session object
|
||||
const session: PtySession = {
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ export function createSessionRoutes(config: SessionRoutesConfig): Router {
|
|||
const sessionName = name || `session_${Date.now()}`;
|
||||
|
||||
// Request Mac app to spawn terminal
|
||||
console.log(`Requesting terminal spawn with command: ${JSON.stringify(command)}`);
|
||||
const spawnResult = await requestTerminalSpawn({
|
||||
sessionId,
|
||||
sessionName,
|
||||
|
|
|
|||
Loading…
Reference in a new issue