sessioncast-cli 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/agent/runner.js +24 -2
  2. package/dist/agent/session-handler.d.ts +3 -2
  3. package/dist/agent/session-handler.js +60 -53
  4. package/dist/agent/tmux-executor.d.ts +19 -33
  5. package/dist/agent/tmux-executor.js +51 -38
  6. package/dist/agent/tmux.d.ts +12 -6
  7. package/dist/agent/tmux.js +16 -9
  8. package/dist/agent/types.d.ts +0 -10
  9. package/dist/agent/websocket.d.ts +6 -13
  10. package/dist/agent/websocket.js +36 -37
  11. package/dist/commands/agent.js +3 -0
  12. package/dist/index.js +14 -0
  13. package/dist/sentry.d.ts +4 -0
  14. package/dist/sentry.js +87 -0
  15. package/package.json +2 -1
  16. package/dist/autopilot/index.d.ts +0 -94
  17. package/dist/autopilot/index.js +0 -322
  18. package/dist/autopilot/mission-analyzer.d.ts +0 -27
  19. package/dist/autopilot/mission-analyzer.js +0 -232
  20. package/dist/autopilot/project-detector.d.ts +0 -12
  21. package/dist/autopilot/project-detector.js +0 -326
  22. package/dist/autopilot/source-scanner.d.ts +0 -26
  23. package/dist/autopilot/source-scanner.js +0 -285
  24. package/dist/autopilot/speckit-generator.d.ts +0 -60
  25. package/dist/autopilot/speckit-generator.js +0 -511
  26. package/dist/autopilot/types.d.ts +0 -110
  27. package/dist/autopilot/types.js +0 -6
  28. package/dist/autopilot/workflow-generator.d.ts +0 -33
  29. package/dist/autopilot/workflow-generator.js +0 -278
  30. package/dist/commands/autopilot.d.ts +0 -30
  31. package/dist/commands/autopilot.js +0 -262
  32. package/dist/commands/project.d.ts +0 -33
  33. package/dist/commands/project.js +0 -350
  34. package/dist/project/executor.d.ts +0 -73
  35. package/dist/project/executor.js +0 -437
  36. package/dist/project/index.d.ts +0 -4
  37. package/dist/project/index.js +0 -20
  38. package/dist/project/manager.d.ts +0 -66
  39. package/dist/project/manager.js +0 -290
  40. package/dist/project/relay-client.d.ts +0 -37
  41. package/dist/project/relay-client.js +0 -204
  42. package/dist/project/types.d.ts +0 -48
  43. package/dist/project/types.js +0 -3
  44. package/dist/utils/fileUtils.d.ts +0 -28
  45. package/dist/utils/fileUtils.js +0 -159
@@ -42,6 +42,7 @@ const session_handler_1 = require("./session-handler");
42
42
  const api_client_1 = require("./api-client");
43
43
  const tmux = __importStar(require("./tmux"));
44
44
  const config_1 = require("../config");
45
+ const sentry_1 = require("../sentry");
45
46
  const SCAN_INTERVAL_MS = 5000;
46
47
  class AgentRunner {
47
48
  constructor(config) {
@@ -73,8 +74,7 @@ class AgentRunner {
73
74
  // If no config file found but agent token exists, create default config
74
75
  if ((!finalPath || !fs.existsSync(finalPath)) && agentToken) {
75
76
  console.log('Using OAuth authentication');
76
- const hostname = os.hostname();
77
- const machineId = `${hostname}-${Date.now()}`;
77
+ const machineId = os.hostname();
78
78
  return {
79
79
  machineId,
80
80
  relay: (0, config_1.getRelayUrl)(),
@@ -105,6 +105,27 @@ class AgentRunner {
105
105
  console.log(`Machine ID: ${this.config.machineId}`);
106
106
  console.log(`Relay: ${this.config.relay}`);
107
107
  console.log(`Token: ${this.config.token ? 'present' : 'none'}`);
108
+ // Check tmux availability before starting
109
+ if (!tmux.isAvailable()) {
110
+ const platform = os.platform();
111
+ let installHint;
112
+ if (platform === 'darwin') {
113
+ installHint = ' Install: brew install tmux';
114
+ }
115
+ else {
116
+ installHint = ' Install: sudo apt install tmux (Debian/Ubuntu)\n' +
117
+ ' sudo yum install tmux (RHEL/CentOS)';
118
+ }
119
+ throw new Error('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
120
+ ' tmux not found - required for SessionCast Agent\n' +
121
+ '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n' +
122
+ `${installHint}\n\n` +
123
+ ' After installing, start a tmux session:\n' +
124
+ ' tmux new -s main\n\n' +
125
+ ' Then run the agent:\n' +
126
+ ' sessioncast agent\n\n' +
127
+ '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
128
+ }
108
129
  // Start API client if configured
109
130
  if (this.config.api?.enabled && this.config.api.agentId) {
110
131
  this.apiClient = new api_client_1.ApiWebSocketClient(this.config);
@@ -139,6 +160,7 @@ class AgentRunner {
139
160
  }
140
161
  }
141
162
  catch (error) {
163
+ (0, sentry_1.captureException)(error);
142
164
  console.error('Error during session scan:', error);
143
165
  }
144
166
  }
@@ -11,11 +11,12 @@ export declare class TmuxSessionHandler {
11
11
  private wsClient;
12
12
  private onCreateSession?;
13
13
  private running;
14
- private lastScreens;
15
- private lastPaneLayoutJson;
14
+ private lastScreen;
16
15
  private lastForceSendTime;
17
16
  private lastChangeTime;
18
17
  private captureTimer;
18
+ private lastPaneIds;
19
+ private lastPaneScreens;
19
20
  private pendingUploads;
20
21
  private uploadDir;
21
22
  constructor(options: SessionHandlerOptions);
@@ -38,9 +38,10 @@ const websocket_1 = require("./websocket");
38
38
  const tmux = __importStar(require("./tmux"));
39
39
  const fs = __importStar(require("fs"));
40
40
  const path = __importStar(require("path"));
41
+ const sentry_1 = require("../sentry");
41
42
  // Capture intervals
42
- const CAPTURE_INTERVAL_ACTIVE_MS = 16;
43
- const CAPTURE_INTERVAL_IDLE_MS = 100;
43
+ const CAPTURE_INTERVAL_ACTIVE_MS = 50;
44
+ const CAPTURE_INTERVAL_IDLE_MS = 200;
44
45
  const ACTIVE_THRESHOLD_MS = 2000;
45
46
  const FORCE_SEND_INTERVAL_MS = 10000;
46
47
  const USE_COMPRESSION = true;
@@ -49,11 +50,13 @@ class TmuxSessionHandler {
49
50
  constructor(options) {
50
51
  this.wsClient = null;
51
52
  this.running = false;
52
- this.lastScreens = new Map();
53
- this.lastPaneLayoutJson = '';
53
+ this.lastScreen = '';
54
54
  this.lastForceSendTime = 0;
55
55
  this.lastChangeTime = 0;
56
56
  this.captureTimer = null;
57
+ // Multi-pane tracking
58
+ this.lastPaneIds = '';
59
+ this.lastPaneScreens = new Map();
57
60
  // File upload handling
58
61
  this.pendingUploads = new Map();
59
62
  this.uploadDir = process.cwd(); // Default to current working directory
@@ -84,16 +87,14 @@ class TmuxSessionHandler {
84
87
  });
85
88
  this.wsClient.on('connected', () => {
86
89
  console.log(`[${this.tmuxSession}] Connected to relay`);
87
- // Reset pane layout cache so it gets re-sent on reconnection
88
- this.lastPaneLayoutJson = '';
89
90
  this.startScreenCapture();
90
91
  });
91
92
  this.wsClient.on('disconnected', ({ code, reason }) => {
92
93
  console.log(`[${this.tmuxSession}] Disconnected: code=${code}, reason=${reason}`);
93
94
  this.stopScreenCapture();
94
95
  });
95
- this.wsClient.on('keys', (keys, meta) => {
96
- this.handleKeys(keys, meta);
96
+ this.wsClient.on('keys', (keys, paneId) => {
97
+ this.handleKeys(keys, paneId);
97
98
  });
98
99
  this.wsClient.on('resize', ({ cols, rows }) => {
99
100
  console.log(`[${this.tmuxSession}] Resize: ${cols}x${rows}`);
@@ -117,6 +118,7 @@ class TmuxSessionHandler {
117
118
  this.handleUploadChunk(chunk);
118
119
  });
119
120
  this.wsClient.on('error', (error) => {
121
+ (0, sentry_1.captureException)(error);
120
122
  console.error(`[${this.tmuxSession}] WebSocket error:`, error.message);
121
123
  });
122
124
  this.wsClient.connect();
@@ -282,8 +284,8 @@ class TmuxSessionHandler {
282
284
  };
283
285
  return types[ext] || 'application/octet-stream';
284
286
  }
285
- handleKeys(keys, meta) {
286
- const target = meta?.pane ? `${this.tmuxSession}.${meta.pane}` : this.tmuxSession;
287
+ handleKeys(keys, paneId) {
288
+ const target = paneId || this.tmuxSession;
287
289
  tmux.sendKeys(target, keys, false);
288
290
  }
289
291
  startScreenCapture() {
@@ -298,58 +300,66 @@ class TmuxSessionHandler {
298
300
  try {
299
301
  const panes = tmux.listPanes(this.tmuxSession);
300
302
  const now = Date.now();
301
- const isMultiPane = panes.length > 1;
302
- // Check if pane layout changed
303
- const paneLayoutJson = JSON.stringify(panes);
304
- if (paneLayoutJson !== this.lastPaneLayoutJson) {
305
- this.lastPaneLayoutJson = paneLayoutJson;
306
- this.wsClient.sendPaneLayout(panes);
307
- // Clean up screens for removed panes
308
- const currentPaneIds = new Set(panes.map(p => p.id));
309
- for (const key of this.lastScreens.keys()) {
310
- if (!currentPaneIds.has(key)) {
311
- this.lastScreens.delete(key);
312
- }
313
- }
314
- }
315
- let anyChanged = false;
316
303
  const forceTime = (now - this.lastForceSendTime) >= FORCE_SEND_INTERVAL_MS;
317
- if (isMultiPane) {
318
- // Multi-pane: capture each pane individually
304
+ if (panes && panes.length > 1) {
305
+ // Multi-pane mode
306
+ const currentPaneIds = panes.map(p => p.id).join(',');
307
+ if (currentPaneIds !== this.lastPaneIds || forceTime) {
308
+ // Layout changed or periodic resend for late-joining viewers
309
+ this.lastPaneIds = currentPaneIds;
310
+ this.wsClient.sendPaneLayout(panes);
311
+ }
312
+ // Capture each pane individually
319
313
  for (const pane of panes) {
320
- const screen = tmux.capturePane(this.tmuxSession, pane.id);
314
+ const screen = tmux.capturePaneById(this.tmuxSession, pane.id);
321
315
  if (screen !== null) {
322
- const lastScreen = this.lastScreens.get(pane.id) || '';
323
- const changed = screen !== lastScreen;
324
- if (changed || forceTime) {
325
- this.lastScreens.set(pane.id, screen);
326
- if (changed)
327
- anyChanged = true;
316
+ const lastScreen = this.lastPaneScreens.get(pane.id);
317
+ if (screen !== lastScreen || forceTime) {
318
+ this.lastPaneScreens.set(pane.id, screen);
319
+ if (screen !== lastScreen) {
320
+ this.lastChangeTime = now;
321
+ }
328
322
  const fullOutput = '\x1b[2J\x1b[H' + screen;
329
323
  const data = Buffer.from(fullOutput, 'utf-8');
330
- const paneMeta = { pane: pane.id, index: pane.index };
324
+ const meta = { pane: pane.id };
331
325
  if (USE_COMPRESSION && data.length > MIN_COMPRESS_SIZE) {
332
- this.wsClient.sendScreenCompressed(data, paneMeta);
326
+ this.wsClient.sendScreenCompressedWithMeta(data, meta);
333
327
  }
334
328
  else {
335
- this.wsClient.sendScreen(data, paneMeta);
329
+ this.wsClient.sendScreenWithMeta(data, meta);
336
330
  }
337
331
  }
338
332
  }
339
333
  }
334
+ if (forceTime) {
335
+ this.lastForceSendTime = now;
336
+ }
337
+ // Adaptive sleep
338
+ const isActive = (now - this.lastChangeTime) < ACTIVE_THRESHOLD_MS;
339
+ const sleepMs = isActive ? CAPTURE_INTERVAL_ACTIVE_MS : CAPTURE_INTERVAL_IDLE_MS;
340
+ this.captureTimer = setTimeout(capture, sleepMs);
340
341
  }
341
342
  else {
342
- // Single pane: backward compatible (no meta)
343
+ // Single pane mode (existing logic)
344
+ if (this.lastPaneIds !== '') {
345
+ // Was multi-pane, now single — notify viewers
346
+ this.lastPaneIds = '';
347
+ this.lastPaneScreens.clear();
348
+ this.wsClient.sendPaneLayout([]);
349
+ }
343
350
  const screen = tmux.capturePane(this.tmuxSession);
344
351
  if (screen !== null) {
345
- const lastScreen = this.lastScreens.get('_single') || '';
346
- const changed = screen !== lastScreen;
352
+ const changed = screen !== this.lastScreen;
347
353
  if (changed || forceTime) {
348
- this.lastScreens.set('_single', screen);
349
- if (changed)
350
- anyChanged = true;
354
+ this.lastScreen = screen;
355
+ this.lastForceSendTime = now;
356
+ if (changed) {
357
+ this.lastChangeTime = now;
358
+ }
359
+ // Send clear screen + content
351
360
  const fullOutput = '\x1b[2J\x1b[H' + screen;
352
361
  const data = Buffer.from(fullOutput, 'utf-8');
362
+ // Compress if enabled and data is large enough
353
363
  if (USE_COMPRESSION && data.length > MIN_COMPRESS_SIZE) {
354
364
  this.wsClient.sendScreenCompressed(data);
355
365
  }
@@ -357,25 +367,22 @@ class TmuxSessionHandler {
357
367
  this.wsClient.sendScreen(data);
358
368
  }
359
369
  }
370
+ // Adaptive sleep: faster when active, slower when idle
371
+ const isActive = (now - this.lastChangeTime) < ACTIVE_THRESHOLD_MS;
372
+ const sleepMs = isActive ? CAPTURE_INTERVAL_ACTIVE_MS : CAPTURE_INTERVAL_IDLE_MS;
373
+ this.captureTimer = setTimeout(capture, sleepMs);
360
374
  }
361
- }
362
- if (anyChanged || forceTime) {
363
- this.lastForceSendTime = now;
364
- if (anyChanged) {
365
- this.lastChangeTime = now;
375
+ else {
376
+ this.captureTimer = setTimeout(capture, CAPTURE_INTERVAL_IDLE_MS);
366
377
  }
367
378
  }
368
- const isActive = (now - this.lastChangeTime) < ACTIVE_THRESHOLD_MS;
369
- const sleepMs = isActive ? CAPTURE_INTERVAL_ACTIVE_MS : CAPTURE_INTERVAL_IDLE_MS;
370
- this.captureTimer = setTimeout(capture, sleepMs);
371
379
  }
372
380
  catch (error) {
373
381
  console.error(`[${this.tmuxSession}] Screen capture error:`, error);
374
382
  this.captureTimer = setTimeout(capture, 500);
375
383
  }
376
384
  };
377
- // Small delay to ensure register message is processed by server first
378
- setTimeout(capture, 300);
385
+ capture();
379
386
  console.log(`[${this.tmuxSession}] Screen capture started`);
380
387
  }
381
388
  stopScreenCapture() {
@@ -2,19 +2,21 @@
2
2
  * Interface for tmux operations.
3
3
  * Implementations handle platform-specific differences (Unix vs Windows/itmux).
4
4
  */
5
+ export interface PaneData {
6
+ id: string;
7
+ index: number;
8
+ width: number;
9
+ height: number;
10
+ top: number;
11
+ left: number;
12
+ active: boolean;
13
+ title: string;
14
+ }
5
15
  export interface TmuxExecutor {
6
16
  listSessions(): string[];
7
- listPanes(session: string): {
8
- id: string;
9
- index: number;
10
- width: number;
11
- height: number;
12
- top: number;
13
- left: number;
14
- active: boolean;
15
- title: string;
16
- }[];
17
- capturePane(session: string, paneId?: string): string | null;
17
+ capturePane(session: string): string | null;
18
+ capturePaneById(session: string, paneId: string): string | null;
19
+ listPanes(session: string): PaneData[] | null;
18
20
  sendKeys(session: string, keys: string): boolean;
19
21
  sendSpecialKey(session: string, key: string): boolean;
20
22
  resizeWindow(session: string, cols: number, rows: number): boolean;
@@ -30,17 +32,9 @@ export interface TmuxExecutor {
30
32
  */
31
33
  export declare class UnixTmuxExecutor implements TmuxExecutor {
32
34
  listSessions(): string[];
33
- listPanes(session: string): {
34
- id: string;
35
- index: number;
36
- width: number;
37
- height: number;
38
- top: number;
39
- left: number;
40
- active: boolean;
41
- title: string;
42
- }[];
43
- capturePane(session: string, paneId?: string): string | null;
35
+ capturePane(session: string): string | null;
36
+ capturePaneById(session: string, paneId: string): string | null;
37
+ listPanes(session: string): PaneData[] | null;
44
38
  sendKeys(session: string, keys: string): boolean;
45
39
  sendSpecialKey(session: string, key: string): boolean;
46
40
  resizeWindow(session: string, cols: number, rows: number): boolean;
@@ -61,17 +55,9 @@ export declare class WindowsTmuxExecutor implements TmuxExecutor {
61
55
  constructor(itmuxPath: string);
62
56
  private executeCommand;
63
57
  listSessions(): string[];
64
- listPanes(session: string): {
65
- id: string;
66
- index: number;
67
- width: number;
68
- height: number;
69
- top: number;
70
- left: number;
71
- active: boolean;
72
- title: string;
73
- }[];
74
- capturePane(session: string, paneId?: string): string | null;
58
+ capturePane(session: string): string | null;
59
+ capturePaneById(session: string, paneId: string): string | null;
60
+ listPanes(session: string): PaneData[] | null;
75
61
  sendKeys(session: string, keys: string): boolean;
76
62
  sendSpecialKey(session: string, key: string): boolean;
77
63
  resizeWindow(session: string, cols: number, rows: number): boolean;
@@ -60,31 +60,22 @@ class UnixTmuxExecutor {
60
60
  return [];
61
61
  }
62
62
  }
63
- listPanes(session) {
63
+ capturePane(session) {
64
64
  try {
65
- const output = (0, child_process_1.execSync)(`tmux list-panes -t "${session}" -F "#{pane_id}:#{pane_index}:#{pane_width}:#{pane_height}:#{pane_top}:#{pane_left}:#{pane_active}:#{pane_title}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
66
- return output.trim().split('\n').filter(l => l.length > 0).map(line => {
67
- const parts = line.split(':');
68
- return {
69
- id: parts[0],
70
- index: parseInt(parts[1], 10),
71
- width: parseInt(parts[2], 10),
72
- height: parseInt(parts[3], 10),
73
- top: parseInt(parts[4], 10),
74
- left: parseInt(parts[5], 10),
75
- active: parts[6] === '1',
76
- title: parts.slice(7).join(':') // title may contain colons
77
- };
65
+ const output = (0, child_process_1.execSync)(`tmux capture-pane -t "${session}" -p -e -N`, {
66
+ encoding: 'utf-8',
67
+ stdio: ['pipe', 'pipe', 'pipe'],
68
+ maxBuffer: 10 * 1024 * 1024
78
69
  });
70
+ return output.replace(/\n/g, '\r\n');
79
71
  }
80
72
  catch {
81
- return [];
73
+ return null;
82
74
  }
83
75
  }
84
- capturePane(session, paneId) {
76
+ capturePaneById(session, paneId) {
85
77
  try {
86
- const target = paneId ? `${session}.${paneId}` : session;
87
- const output = (0, child_process_1.execSync)(`tmux capture-pane -t "${target}" -p -e -N`, {
78
+ const output = (0, child_process_1.execSync)(`tmux capture-pane -t "${paneId}" -p -e -N`, {
88
79
  encoding: 'utf-8',
89
80
  stdio: ['pipe', 'pipe', 'pipe'],
90
81
  maxBuffer: 10 * 1024 * 1024
@@ -95,6 +86,22 @@ class UnixTmuxExecutor {
95
86
  return null;
96
87
  }
97
88
  }
89
+ listPanes(session) {
90
+ try {
91
+ const output = (0, child_process_1.execSync)(`tmux list-panes -t "${session}" -F "#{pane_id}:#{pane_index}:#{pane_width}:#{pane_height}:#{pane_top}:#{pane_left}:#{?pane_active,1,0}:#{pane_title}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
92
+ return output.trim().split('\n').filter(Boolean).map(line => {
93
+ const [id, index, width, height, top, left, active, ...titleParts] = line.split(':');
94
+ return {
95
+ id, index: parseInt(index), width: parseInt(width), height: parseInt(height),
96
+ top: parseInt(top), left: parseInt(left),
97
+ active: active === '1', title: titleParts.join(':') || ''
98
+ };
99
+ });
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
98
105
  sendKeys(session, keys) {
99
106
  try {
100
107
  const escaped = this.escapeForShell(keys);
@@ -245,34 +252,21 @@ class WindowsTmuxExecutor {
245
252
  return [];
246
253
  }
247
254
  }
248
- listPanes(session) {
255
+ capturePane(session) {
249
256
  try {
250
257
  const escaped = this.escapeSession(session);
251
- const output = this.executeCommand(`tmux list-panes -t '${escaped}' -F '#{pane_id}:#{pane_index}:#{pane_width}:#{pane_height}:#{pane_top}:#{pane_left}:#{pane_active}:#{pane_title}'`);
258
+ const output = this.executeCommand(`tmux capture-pane -t '${escaped}' -p -e -N`);
252
259
  if (!output)
253
- return [];
254
- return output.split('\n').map(s => s.trim()).filter(l => l.length > 0).map(line => {
255
- const parts = line.split(':');
256
- return {
257
- id: parts[0],
258
- index: parseInt(parts[1], 10),
259
- width: parseInt(parts[2], 10),
260
- height: parseInt(parts[3], 10),
261
- top: parseInt(parts[4], 10),
262
- left: parseInt(parts[5], 10),
263
- active: parts[6] === '1',
264
- title: parts.slice(7).join(':')
265
- };
266
- });
260
+ return null;
261
+ return output.replace(/\n/g, '\r\n');
267
262
  }
268
263
  catch {
269
- return [];
264
+ return null;
270
265
  }
271
266
  }
272
- capturePane(session, paneId) {
267
+ capturePaneById(session, paneId) {
273
268
  try {
274
- const target = paneId ? `${session}.${paneId}` : session;
275
- const escaped = this.escapeSession(target);
269
+ const escaped = this.escapeSession(paneId);
276
270
  const output = this.executeCommand(`tmux capture-pane -t '${escaped}' -p -e -N`);
277
271
  if (!output)
278
272
  return null;
@@ -282,6 +276,25 @@ class WindowsTmuxExecutor {
282
276
  return null;
283
277
  }
284
278
  }
279
+ listPanes(session) {
280
+ try {
281
+ const escaped = this.escapeSession(session);
282
+ const output = this.executeCommand(`tmux list-panes -t '${escaped}' -F "#{pane_id}:#{pane_index}:#{pane_width}:#{pane_height}:#{pane_top}:#{pane_left}:#{?pane_active,1,0}:#{pane_title}"`);
283
+ if (!output)
284
+ return null;
285
+ return output.trim().split('\n').filter(Boolean).map(line => {
286
+ const [id, index, width, height, top, left, active, ...titleParts] = line.split(':');
287
+ return {
288
+ id, index: parseInt(index), width: parseInt(width), height: parseInt(height),
289
+ top: parseInt(top), left: parseInt(left),
290
+ active: active === '1', title: titleParts.join(':') || ''
291
+ };
292
+ });
293
+ }
294
+ catch {
295
+ return null;
296
+ }
297
+ }
285
298
  sendKeys(session, keys) {
286
299
  try {
287
300
  const escapedSession = this.escapeSession(session);
@@ -1,4 +1,5 @@
1
- import { TmuxSession, PaneInfo } from './types';
1
+ import { PaneData } from './tmux-executor';
2
+ import { TmuxSession } from './types';
2
3
  /**
3
4
  * Scan for all tmux sessions
4
5
  */
@@ -7,14 +8,10 @@ export declare function scanSessions(): string[];
7
8
  * Get detailed session info
8
9
  */
9
10
  export declare function listSessions(): TmuxSession[];
10
- /**
11
- * List all panes in a tmux session
12
- */
13
- export declare function listPanes(sessionName: string): PaneInfo[];
14
11
  /**
15
12
  * Capture tmux pane content with escape sequences (colors)
16
13
  */
17
- export declare function capturePane(sessionName: string, paneId?: string): string | null;
14
+ export declare function capturePane(sessionName: string): string | null;
18
15
  /**
19
16
  * Send keys to tmux session
20
17
  */
@@ -47,3 +44,12 @@ export declare function getActivePane(sessionName: string): string | null;
47
44
  * Get the current working directory of a pane
48
45
  */
49
46
  export declare function getPaneCwd(sessionName: string, paneId?: string): string | null;
47
+ /**
48
+ * List all panes in a tmux session
49
+ */
50
+ export declare function listPanes(sessionName: string): PaneData[] | null;
51
+ /**
52
+ * Capture a specific pane by its pane ID (e.g., %0, %1)
53
+ */
54
+ export declare function capturePaneById(sessionName: string, paneId: string): string | null;
55
+ export type { PaneData } from './tmux-executor';
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.scanSessions = scanSessions;
4
4
  exports.listSessions = listSessions;
5
- exports.listPanes = listPanes;
6
5
  exports.capturePane = capturePane;
7
6
  exports.sendKeys = sendKeys;
8
7
  exports.resizeWindow = resizeWindow;
@@ -12,6 +11,8 @@ exports.isAvailable = isAvailable;
12
11
  exports.getVersion = getVersion;
13
12
  exports.getActivePane = getActivePane;
14
13
  exports.getPaneCwd = getPaneCwd;
14
+ exports.listPanes = listPanes;
15
+ exports.capturePaneById = capturePaneById;
15
16
  const tmux_executor_1 = require("./tmux-executor");
16
17
  // Lazy-initialized executor (created on first use)
17
18
  let executor = null;
@@ -50,17 +51,11 @@ function listSessions() {
50
51
  return [];
51
52
  }
52
53
  }
53
- /**
54
- * List all panes in a tmux session
55
- */
56
- function listPanes(sessionName) {
57
- return getExecutor().listPanes(sessionName);
58
- }
59
54
  /**
60
55
  * Capture tmux pane content with escape sequences (colors)
61
56
  */
62
- function capturePane(sessionName, paneId) {
63
- return getExecutor().capturePane(sessionName, paneId);
57
+ function capturePane(sessionName) {
58
+ return getExecutor().capturePane(sessionName);
64
59
  }
65
60
  /**
66
61
  * Send keys to tmux session
@@ -152,3 +147,15 @@ function getActivePane(sessionName) {
152
147
  function getPaneCwd(sessionName, paneId) {
153
148
  return getExecutor().getPaneCwd(sessionName, paneId);
154
149
  }
150
+ /**
151
+ * List all panes in a tmux session
152
+ */
153
+ function listPanes(sessionName) {
154
+ return getExecutor().listPanes(sessionName);
155
+ }
156
+ /**
157
+ * Capture a specific pane by its pane ID (e.g., %0, %1)
158
+ */
159
+ function capturePaneById(sessionName, paneId) {
160
+ return getExecutor().capturePaneById(sessionName, paneId);
161
+ }
@@ -37,16 +37,6 @@ export interface TmuxSession {
37
37
  created?: string;
38
38
  attached: boolean;
39
39
  }
40
- export interface PaneInfo {
41
- id: string;
42
- index: number;
43
- width: number;
44
- height: number;
45
- top: number;
46
- left: number;
47
- active: boolean;
48
- title: string;
49
- }
50
40
  export interface ExecResult {
51
41
  exitCode: number;
52
42
  stdout: string;
@@ -21,26 +21,19 @@ export declare class RelayWebSocketClient extends EventEmitter {
21
21
  private circuitBreakerOpen;
22
22
  private circuitBreakerResetTime;
23
23
  private reconnectTimer;
24
- private pingTimer;
25
24
  private destroyed;
26
25
  constructor(options: WebSocketClientOptions);
27
26
  connect(): void;
28
27
  private registerAsHost;
29
28
  private handleMessage;
30
29
  private handleError;
31
- private startPing;
32
- private stopPing;
33
30
  private scheduleReconnect;
34
31
  send(message: Message): boolean;
35
- sendScreen(data: Buffer, paneMeta?: {
36
- pane: string;
37
- index: number;
38
- }): boolean;
39
- sendScreenCompressed(data: Buffer, paneMeta?: {
40
- pane: string;
41
- index: number;
42
- }): boolean;
43
- sendPaneLayout(panes: {
32
+ sendScreen(data: Buffer): boolean;
33
+ sendScreenWithMeta(data: Buffer, meta: Record<string, string>): boolean;
34
+ sendScreenCompressed(data: Buffer): boolean;
35
+ sendScreenCompressedWithMeta(data: Buffer, meta: Record<string, string>): boolean;
36
+ sendPaneLayout(panes: Array<{
44
37
  id: string;
45
38
  index: number;
46
39
  width: number;
@@ -49,7 +42,7 @@ export declare class RelayWebSocketClient extends EventEmitter {
49
42
  left: number;
50
43
  active: boolean;
51
44
  title: string;
52
- }[]): boolean;
45
+ }>): boolean;
53
46
  /**
54
47
  * Send file content to be displayed in the web FileViewer
55
48
  * @param filename - The name of the file