mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-04 11:05:53 +00:00
230 lines
6.9 KiB
TypeScript
230 lines
6.9 KiB
TypeScript
import type {
|
|
MultiplexerSession,
|
|
MultiplexerStatus,
|
|
MultiplexerType,
|
|
TmuxPane,
|
|
TmuxWindow,
|
|
} from '../../shared/multiplexer-types.js';
|
|
import type { SessionCreateOptions } from '../../shared/types.js';
|
|
import { TitleMode } from '../../shared/types.js';
|
|
import type { PtyManager } from '../pty/pty-manager.js';
|
|
import { createLogger } from '../utils/logger.js';
|
|
import { ScreenManager } from './screen-manager.js';
|
|
import { TmuxManager } from './tmux-manager.js';
|
|
import { ZellijManager } from './zellij-manager.js';
|
|
|
|
const logger = createLogger('MultiplexerManager');
|
|
|
|
export class MultiplexerManager {
|
|
private static instance: MultiplexerManager;
|
|
private tmuxManager: TmuxManager;
|
|
private zellijManager: ZellijManager;
|
|
private screenManager: ScreenManager;
|
|
private ptyManager: PtyManager;
|
|
|
|
private constructor(ptyManager: PtyManager) {
|
|
this.ptyManager = ptyManager;
|
|
this.tmuxManager = TmuxManager.getInstance(ptyManager);
|
|
this.zellijManager = ZellijManager.getInstance(ptyManager);
|
|
this.screenManager = ScreenManager.getInstance();
|
|
}
|
|
|
|
static getInstance(ptyManager: PtyManager): MultiplexerManager {
|
|
if (!MultiplexerManager.instance) {
|
|
MultiplexerManager.instance = new MultiplexerManager(ptyManager);
|
|
}
|
|
return MultiplexerManager.instance;
|
|
}
|
|
|
|
/**
|
|
* Get available multiplexers and their sessions
|
|
*/
|
|
async getAvailableMultiplexers(): Promise<MultiplexerStatus> {
|
|
const [tmuxAvailable, zellijAvailable, screenAvailable] = await Promise.all([
|
|
this.tmuxManager.isAvailable(),
|
|
this.zellijManager.isAvailable(),
|
|
this.screenManager.isAvailable(),
|
|
]);
|
|
|
|
const result: MultiplexerStatus = {
|
|
tmux: {
|
|
available: tmuxAvailable,
|
|
type: 'tmux' as MultiplexerType,
|
|
sessions: [] as MultiplexerSession[],
|
|
},
|
|
zellij: {
|
|
available: zellijAvailable,
|
|
type: 'zellij' as MultiplexerType,
|
|
sessions: [] as MultiplexerSession[],
|
|
},
|
|
screen: {
|
|
available: screenAvailable,
|
|
type: 'screen' as MultiplexerType,
|
|
sessions: [] as MultiplexerSession[],
|
|
},
|
|
};
|
|
|
|
// Load sessions for available multiplexers
|
|
if (tmuxAvailable) {
|
|
try {
|
|
const tmuxSessions = await this.tmuxManager.listSessions();
|
|
result.tmux.sessions = tmuxSessions.map((session) => ({
|
|
...session,
|
|
type: 'tmux' as MultiplexerType,
|
|
}));
|
|
} catch (error) {
|
|
logger.error('Failed to list tmux sessions', { error });
|
|
}
|
|
}
|
|
|
|
if (zellijAvailable) {
|
|
try {
|
|
const zellijSessions = await this.zellijManager.listSessions();
|
|
result.zellij.sessions = zellijSessions.map((session) => ({
|
|
...session,
|
|
type: 'zellij' as MultiplexerType,
|
|
}));
|
|
} catch (error) {
|
|
logger.error('Failed to list zellij sessions', { error });
|
|
}
|
|
}
|
|
|
|
if (screenAvailable) {
|
|
try {
|
|
const screenSessions = await this.screenManager.listSessions();
|
|
result.screen.sessions = screenSessions.map((session) => ({
|
|
...session,
|
|
type: 'screen' as MultiplexerType,
|
|
}));
|
|
} catch (error) {
|
|
logger.error('Failed to list screen sessions', { error });
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get windows for a tmux session
|
|
*/
|
|
async getTmuxWindows(sessionName: string): Promise<TmuxWindow[]> {
|
|
return this.tmuxManager.listWindows(sessionName);
|
|
}
|
|
|
|
/**
|
|
* Get panes for a tmux window
|
|
*/
|
|
async getTmuxPanes(sessionName: string, windowIndex?: number): Promise<TmuxPane[]> {
|
|
return this.tmuxManager.listPanes(sessionName, windowIndex);
|
|
}
|
|
|
|
/**
|
|
* Create a new session
|
|
*/
|
|
async createSession(
|
|
type: MultiplexerType,
|
|
name: string,
|
|
options?: { command?: string[]; layout?: string }
|
|
): Promise<void> {
|
|
if (type === 'tmux') {
|
|
await this.tmuxManager.createSession(name, options?.command);
|
|
} else if (type === 'zellij') {
|
|
await this.zellijManager.createSession(name, options?.layout);
|
|
} else if (type === 'screen') {
|
|
// Screen expects a single command string, not an array
|
|
const command = options?.command ? options.command.join(' ') : undefined;
|
|
await this.screenManager.createSession(name, command);
|
|
} else {
|
|
throw new Error(`Unknown multiplexer type: ${type}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attach to a session
|
|
*/
|
|
async attachToSession(
|
|
type: MultiplexerType,
|
|
sessionName: string,
|
|
options?: Partial<SessionCreateOptions> & { windowIndex?: number; paneIndex?: number }
|
|
): Promise<string> {
|
|
if (type === 'tmux') {
|
|
return this.tmuxManager.attachToTmux(
|
|
sessionName,
|
|
options?.windowIndex,
|
|
options?.paneIndex,
|
|
options
|
|
);
|
|
} else if (type === 'zellij') {
|
|
return this.zellijManager.attachToZellij(sessionName, options);
|
|
} else if (type === 'screen') {
|
|
// Screen doesn't support programmatic attach like tmux/zellij
|
|
// We need to create a new session that runs the attach command
|
|
const attachCmd = await this.screenManager.attachToSession(sessionName);
|
|
// Create a new PTY session that will run the screen attach command
|
|
const result = await this.ptyManager.createSession(attachCmd, {
|
|
...options,
|
|
titleMode: TitleMode.DYNAMIC,
|
|
});
|
|
return result.sessionId;
|
|
} else {
|
|
throw new Error(`Unknown multiplexer type: ${type}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Kill/delete a session
|
|
*/
|
|
async killSession(type: MultiplexerType, sessionName: string): Promise<void> {
|
|
if (type === 'tmux') {
|
|
await this.tmuxManager.killSession(sessionName);
|
|
} else if (type === 'zellij') {
|
|
await this.zellijManager.killSession(sessionName);
|
|
} else if (type === 'screen') {
|
|
await this.screenManager.killSession(sessionName);
|
|
} else {
|
|
throw new Error(`Unknown multiplexer type: ${type}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Kill a tmux window
|
|
*/
|
|
async killTmuxWindow(sessionName: string, windowIndex: number): Promise<void> {
|
|
await this.tmuxManager.killWindow(sessionName, windowIndex);
|
|
}
|
|
|
|
/**
|
|
* Kill a tmux pane
|
|
*/
|
|
async killTmuxPane(sessionName: string, paneId: string): Promise<void> {
|
|
await this.tmuxManager.killPane(sessionName, paneId);
|
|
}
|
|
|
|
/**
|
|
* Check which multiplexer we're currently inside
|
|
*/
|
|
getCurrentMultiplexer(): { type: MultiplexerType; session: string } | null {
|
|
if (this.tmuxManager.isInsideTmux()) {
|
|
const session = this.tmuxManager.getCurrentSession();
|
|
if (session) {
|
|
return { type: 'tmux', session };
|
|
}
|
|
}
|
|
|
|
if (this.zellijManager.isInsideZellij()) {
|
|
const session = this.zellijManager.getCurrentSession();
|
|
if (session) {
|
|
return { type: 'zellij', session };
|
|
}
|
|
}
|
|
|
|
if (this.screenManager.isInsideScreen()) {
|
|
const session = this.screenManager.getCurrentSession();
|
|
if (session) {
|
|
return { type: 'screen', session };
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|