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.
- package/dist/agent/runner.js +24 -2
- package/dist/agent/session-handler.d.ts +3 -2
- package/dist/agent/session-handler.js +60 -53
- package/dist/agent/tmux-executor.d.ts +19 -33
- package/dist/agent/tmux-executor.js +51 -38
- package/dist/agent/tmux.d.ts +12 -6
- package/dist/agent/tmux.js +16 -9
- package/dist/agent/types.d.ts +0 -10
- package/dist/agent/websocket.d.ts +6 -13
- package/dist/agent/websocket.js +36 -37
- package/dist/commands/agent.js +3 -0
- package/dist/index.js +14 -0
- package/dist/sentry.d.ts +4 -0
- package/dist/sentry.js +87 -0
- package/package.json +2 -1
- package/dist/autopilot/index.d.ts +0 -94
- package/dist/autopilot/index.js +0 -322
- package/dist/autopilot/mission-analyzer.d.ts +0 -27
- package/dist/autopilot/mission-analyzer.js +0 -232
- package/dist/autopilot/project-detector.d.ts +0 -12
- package/dist/autopilot/project-detector.js +0 -326
- package/dist/autopilot/source-scanner.d.ts +0 -26
- package/dist/autopilot/source-scanner.js +0 -285
- package/dist/autopilot/speckit-generator.d.ts +0 -60
- package/dist/autopilot/speckit-generator.js +0 -511
- package/dist/autopilot/types.d.ts +0 -110
- package/dist/autopilot/types.js +0 -6
- package/dist/autopilot/workflow-generator.d.ts +0 -33
- package/dist/autopilot/workflow-generator.js +0 -278
- package/dist/commands/autopilot.d.ts +0 -30
- package/dist/commands/autopilot.js +0 -262
- package/dist/commands/project.d.ts +0 -33
- package/dist/commands/project.js +0 -350
- package/dist/project/executor.d.ts +0 -73
- package/dist/project/executor.js +0 -437
- package/dist/project/index.d.ts +0 -4
- package/dist/project/index.js +0 -20
- package/dist/project/manager.d.ts +0 -66
- package/dist/project/manager.js +0 -290
- package/dist/project/relay-client.d.ts +0 -37
- package/dist/project/relay-client.js +0 -204
- package/dist/project/types.d.ts +0 -48
- package/dist/project/types.js +0 -3
- package/dist/utils/fileUtils.d.ts +0 -28
- package/dist/utils/fileUtils.js +0 -159
package/dist/agent/runner.js
CHANGED
|
@@ -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
|
|
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
|
|
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 =
|
|
43
|
-
const CAPTURE_INTERVAL_IDLE_MS =
|
|
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.
|
|
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,
|
|
96
|
-
this.handleKeys(keys,
|
|
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,
|
|
286
|
-
const target =
|
|
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 (
|
|
318
|
-
// Multi-pane
|
|
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.
|
|
314
|
+
const screen = tmux.capturePaneById(this.tmuxSession, pane.id);
|
|
321
315
|
if (screen !== null) {
|
|
322
|
-
const lastScreen = this.
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
|
324
|
+
const meta = { pane: pane.id };
|
|
331
325
|
if (USE_COMPRESSION && data.length > MIN_COMPRESS_SIZE) {
|
|
332
|
-
this.wsClient.
|
|
326
|
+
this.wsClient.sendScreenCompressedWithMeta(data, meta);
|
|
333
327
|
}
|
|
334
328
|
else {
|
|
335
|
-
this.wsClient.
|
|
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
|
|
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
|
|
346
|
-
const changed = screen !== lastScreen;
|
|
352
|
+
const changed = screen !== this.lastScreen;
|
|
347
353
|
if (changed || forceTime) {
|
|
348
|
-
this.
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
63
|
+
capturePane(session) {
|
|
64
64
|
try {
|
|
65
|
-
const output = (0, child_process_1.execSync)(`tmux
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
76
|
+
capturePaneById(session, paneId) {
|
|
85
77
|
try {
|
|
86
|
-
const
|
|
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
|
-
|
|
255
|
+
capturePane(session) {
|
|
249
256
|
try {
|
|
250
257
|
const escaped = this.escapeSession(session);
|
|
251
|
-
const output = this.executeCommand(`tmux
|
|
258
|
+
const output = this.executeCommand(`tmux capture-pane -t '${escaped}' -p -e -N`);
|
|
252
259
|
if (!output)
|
|
253
|
-
return
|
|
254
|
-
return output.
|
|
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
|
-
|
|
267
|
+
capturePaneById(session, paneId) {
|
|
273
268
|
try {
|
|
274
|
-
const
|
|
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);
|
package/dist/agent/tmux.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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';
|
package/dist/agent/tmux.js
CHANGED
|
@@ -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
|
|
63
|
-
return getExecutor().capturePane(sessionName
|
|
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
|
+
}
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -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
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
}
|
|
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
|