sessioncast-cli 2.0.1 → 2.0.3
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 +23 -0
- package/dist/agent/session-handler.d.ts +1 -2
- package/dist/agent/session-handler.js +32 -77
- package/dist/agent/tmux-executor.d.ts +3 -33
- package/dist/agent/tmux-executor.js +3 -50
- package/dist/agent/tmux.d.ts +2 -6
- package/dist/agent/tmux.js +2 -9
- package/dist/agent/types.d.ts +0 -10
- package/dist/agent/websocket.d.ts +2 -18
- package/dist/agent/websocket.js +9 -26
- 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) {
|
|
@@ -105,6 +106,27 @@ class AgentRunner {
|
|
|
105
106
|
console.log(`Machine ID: ${this.config.machineId}`);
|
|
106
107
|
console.log(`Relay: ${this.config.relay}`);
|
|
107
108
|
console.log(`Token: ${this.config.token ? 'present' : 'none'}`);
|
|
109
|
+
// Check tmux availability before starting
|
|
110
|
+
if (!tmux.isAvailable()) {
|
|
111
|
+
const platform = os.platform();
|
|
112
|
+
let installHint;
|
|
113
|
+
if (platform === 'darwin') {
|
|
114
|
+
installHint = ' Install: brew install tmux';
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
installHint = ' Install: sudo apt install tmux (Debian/Ubuntu)\n' +
|
|
118
|
+
' sudo yum install tmux (RHEL/CentOS)';
|
|
119
|
+
}
|
|
120
|
+
throw new Error('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
|
|
121
|
+
' tmux not found - required for SessionCast Agent\n' +
|
|
122
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n' +
|
|
123
|
+
`${installHint}\n\n` +
|
|
124
|
+
' After installing, start a tmux session:\n' +
|
|
125
|
+
' tmux new -s main\n\n' +
|
|
126
|
+
' Then run the agent:\n' +
|
|
127
|
+
' sessioncast agent\n\n' +
|
|
128
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
129
|
+
}
|
|
108
130
|
// Start API client if configured
|
|
109
131
|
if (this.config.api?.enabled && this.config.api.agentId) {
|
|
110
132
|
this.apiClient = new api_client_1.ApiWebSocketClient(this.config);
|
|
@@ -139,6 +161,7 @@ class AgentRunner {
|
|
|
139
161
|
}
|
|
140
162
|
}
|
|
141
163
|
catch (error) {
|
|
164
|
+
(0, sentry_1.captureException)(error);
|
|
142
165
|
console.error('Error during session scan:', error);
|
|
143
166
|
}
|
|
144
167
|
}
|
|
@@ -11,8 +11,7 @@ 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;
|
|
@@ -38,6 +38,7 @@ 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
43
|
const CAPTURE_INTERVAL_ACTIVE_MS = 50;
|
|
43
44
|
const CAPTURE_INTERVAL_IDLE_MS = 200;
|
|
@@ -49,8 +50,7 @@ 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;
|
|
@@ -84,16 +84,14 @@ class TmuxSessionHandler {
|
|
|
84
84
|
});
|
|
85
85
|
this.wsClient.on('connected', () => {
|
|
86
86
|
console.log(`[${this.tmuxSession}] Connected to relay`);
|
|
87
|
-
// Reset pane layout cache so it gets re-sent on reconnection
|
|
88
|
-
this.lastPaneLayoutJson = '';
|
|
89
87
|
this.startScreenCapture();
|
|
90
88
|
});
|
|
91
89
|
this.wsClient.on('disconnected', ({ code, reason }) => {
|
|
92
90
|
console.log(`[${this.tmuxSession}] Disconnected: code=${code}, reason=${reason}`);
|
|
93
91
|
this.stopScreenCapture();
|
|
94
92
|
});
|
|
95
|
-
this.wsClient.on('keys', (keys
|
|
96
|
-
this.handleKeys(keys
|
|
93
|
+
this.wsClient.on('keys', (keys) => {
|
|
94
|
+
this.handleKeys(keys);
|
|
97
95
|
});
|
|
98
96
|
this.wsClient.on('resize', ({ cols, rows }) => {
|
|
99
97
|
console.log(`[${this.tmuxSession}] Resize: ${cols}x${rows}`);
|
|
@@ -117,6 +115,7 @@ class TmuxSessionHandler {
|
|
|
117
115
|
this.handleUploadChunk(chunk);
|
|
118
116
|
});
|
|
119
117
|
this.wsClient.on('error', (error) => {
|
|
118
|
+
(0, sentry_1.captureException)(error);
|
|
120
119
|
console.error(`[${this.tmuxSession}] WebSocket error:`, error.message);
|
|
121
120
|
});
|
|
122
121
|
this.wsClient.connect();
|
|
@@ -282,9 +281,8 @@ class TmuxSessionHandler {
|
|
|
282
281
|
};
|
|
283
282
|
return types[ext] || 'application/octet-stream';
|
|
284
283
|
}
|
|
285
|
-
handleKeys(keys
|
|
286
|
-
|
|
287
|
-
tmux.sendKeys(target, keys, false);
|
|
284
|
+
handleKeys(keys) {
|
|
285
|
+
tmux.sendKeys(this.tmuxSession, keys, false);
|
|
288
286
|
}
|
|
289
287
|
startScreenCapture() {
|
|
290
288
|
if (this.captureTimer)
|
|
@@ -296,86 +294,43 @@ class TmuxSessionHandler {
|
|
|
296
294
|
return;
|
|
297
295
|
}
|
|
298
296
|
try {
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
for (const key of this.lastScreens.keys()) {
|
|
310
|
-
if (!currentPaneIds.has(key)) {
|
|
311
|
-
this.lastScreens.delete(key);
|
|
297
|
+
const screen = tmux.capturePane(this.tmuxSession);
|
|
298
|
+
if (screen !== null) {
|
|
299
|
+
const now = Date.now();
|
|
300
|
+
const changed = screen !== this.lastScreen;
|
|
301
|
+
const forceTime = (now - this.lastForceSendTime) >= FORCE_SEND_INTERVAL_MS;
|
|
302
|
+
if (changed || forceTime) {
|
|
303
|
+
this.lastScreen = screen;
|
|
304
|
+
this.lastForceSendTime = now;
|
|
305
|
+
if (changed) {
|
|
306
|
+
this.lastChangeTime = now;
|
|
312
307
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
for (const pane of panes) {
|
|
320
|
-
const screen = tmux.capturePane(this.tmuxSession, pane.id);
|
|
321
|
-
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;
|
|
328
|
-
const fullOutput = '\x1b[2J\x1b[H' + screen;
|
|
329
|
-
const data = Buffer.from(fullOutput, 'utf-8');
|
|
330
|
-
const paneMeta = { pane: pane.id, index: pane.index };
|
|
331
|
-
if (USE_COMPRESSION && data.length > MIN_COMPRESS_SIZE) {
|
|
332
|
-
this.wsClient.sendScreenCompressed(data, paneMeta);
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
this.wsClient.sendScreen(data, paneMeta);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
308
|
+
// Send clear screen + content
|
|
309
|
+
const fullOutput = '\x1b[2J\x1b[H' + screen;
|
|
310
|
+
const data = Buffer.from(fullOutput, 'utf-8');
|
|
311
|
+
// Compress if enabled and data is large enough
|
|
312
|
+
if (USE_COMPRESSION && data.length > MIN_COMPRESS_SIZE) {
|
|
313
|
+
this.wsClient.sendScreenCompressed(data);
|
|
338
314
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
else {
|
|
342
|
-
// Single pane: backward compatible (no meta)
|
|
343
|
-
const screen = tmux.capturePane(this.tmuxSession);
|
|
344
|
-
if (screen !== null) {
|
|
345
|
-
const lastScreen = this.lastScreens.get('_single') || '';
|
|
346
|
-
const changed = screen !== lastScreen;
|
|
347
|
-
if (changed || forceTime) {
|
|
348
|
-
this.lastScreens.set('_single', screen);
|
|
349
|
-
if (changed)
|
|
350
|
-
anyChanged = true;
|
|
351
|
-
const fullOutput = '\x1b[2J\x1b[H' + screen;
|
|
352
|
-
const data = Buffer.from(fullOutput, 'utf-8');
|
|
353
|
-
if (USE_COMPRESSION && data.length > MIN_COMPRESS_SIZE) {
|
|
354
|
-
this.wsClient.sendScreenCompressed(data);
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
this.wsClient.sendScreen(data);
|
|
358
|
-
}
|
|
315
|
+
else {
|
|
316
|
+
this.wsClient.sendScreen(data);
|
|
359
317
|
}
|
|
360
318
|
}
|
|
319
|
+
// Adaptive sleep: faster when active, slower when idle
|
|
320
|
+
const isActive = (now - this.lastChangeTime) < ACTIVE_THRESHOLD_MS;
|
|
321
|
+
const sleepMs = isActive ? CAPTURE_INTERVAL_ACTIVE_MS : CAPTURE_INTERVAL_IDLE_MS;
|
|
322
|
+
this.captureTimer = setTimeout(capture, sleepMs);
|
|
361
323
|
}
|
|
362
|
-
|
|
363
|
-
this.
|
|
364
|
-
if (anyChanged) {
|
|
365
|
-
this.lastChangeTime = now;
|
|
366
|
-
}
|
|
324
|
+
else {
|
|
325
|
+
this.captureTimer = setTimeout(capture, CAPTURE_INTERVAL_IDLE_MS);
|
|
367
326
|
}
|
|
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
327
|
}
|
|
372
328
|
catch (error) {
|
|
373
329
|
console.error(`[${this.tmuxSession}] Screen capture error:`, error);
|
|
374
330
|
this.captureTimer = setTimeout(capture, 500);
|
|
375
331
|
}
|
|
376
332
|
};
|
|
377
|
-
|
|
378
|
-
setTimeout(capture, 300);
|
|
333
|
+
capture();
|
|
379
334
|
console.log(`[${this.tmuxSession}] Screen capture started`);
|
|
380
335
|
}
|
|
381
336
|
stopScreenCapture() {
|
|
@@ -4,17 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export interface TmuxExecutor {
|
|
6
6
|
listSessions(): string[];
|
|
7
|
-
|
|
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;
|
|
7
|
+
capturePane(session: string): string | null;
|
|
18
8
|
sendKeys(session: string, keys: string): boolean;
|
|
19
9
|
sendSpecialKey(session: string, key: string): boolean;
|
|
20
10
|
resizeWindow(session: string, cols: number, rows: number): boolean;
|
|
@@ -30,17 +20,7 @@ export interface TmuxExecutor {
|
|
|
30
20
|
*/
|
|
31
21
|
export declare class UnixTmuxExecutor implements TmuxExecutor {
|
|
32
22
|
listSessions(): string[];
|
|
33
|
-
|
|
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;
|
|
23
|
+
capturePane(session: string): string | null;
|
|
44
24
|
sendKeys(session: string, keys: string): boolean;
|
|
45
25
|
sendSpecialKey(session: string, key: string): boolean;
|
|
46
26
|
resizeWindow(session: string, cols: number, rows: number): boolean;
|
|
@@ -61,17 +41,7 @@ export declare class WindowsTmuxExecutor implements TmuxExecutor {
|
|
|
61
41
|
constructor(itmuxPath: string);
|
|
62
42
|
private executeCommand;
|
|
63
43
|
listSessions(): string[];
|
|
64
|
-
|
|
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;
|
|
44
|
+
capturePane(session: string): string | null;
|
|
75
45
|
sendKeys(session: string, keys: string): boolean;
|
|
76
46
|
sendSpecialKey(session: string, key: string): boolean;
|
|
77
47
|
resizeWindow(session: string, cols: number, rows: number): boolean;
|
|
@@ -60,31 +60,9 @@ 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
|
-
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
|
-
};
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
return [];
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
capturePane(session, paneId) {
|
|
85
|
-
try {
|
|
86
|
-
const target = paneId ? `${session}.${paneId}` : session;
|
|
87
|
-
const output = (0, child_process_1.execSync)(`tmux capture-pane -t "${target}" -p -e -N`, {
|
|
65
|
+
const output = (0, child_process_1.execSync)(`tmux capture-pane -t "${session}" -p -e -N`, {
|
|
88
66
|
encoding: 'utf-8',
|
|
89
67
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
90
68
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -245,34 +223,9 @@ class WindowsTmuxExecutor {
|
|
|
245
223
|
return [];
|
|
246
224
|
}
|
|
247
225
|
}
|
|
248
|
-
|
|
226
|
+
capturePane(session) {
|
|
249
227
|
try {
|
|
250
228
|
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}'`);
|
|
252
|
-
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
|
-
});
|
|
267
|
-
}
|
|
268
|
-
catch {
|
|
269
|
-
return [];
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
capturePane(session, paneId) {
|
|
273
|
-
try {
|
|
274
|
-
const target = paneId ? `${session}.${paneId}` : session;
|
|
275
|
-
const escaped = this.escapeSession(target);
|
|
276
229
|
const output = this.executeCommand(`tmux capture-pane -t '${escaped}' -p -e -N`);
|
|
277
230
|
if (!output)
|
|
278
231
|
return null;
|
package/dist/agent/tmux.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TmuxSession
|
|
1
|
+
import { TmuxSession } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Scan for all tmux sessions
|
|
4
4
|
*/
|
|
@@ -7,14 +7,10 @@ export declare function scanSessions(): string[];
|
|
|
7
7
|
* Get detailed session info
|
|
8
8
|
*/
|
|
9
9
|
export declare function listSessions(): TmuxSession[];
|
|
10
|
-
/**
|
|
11
|
-
* List all panes in a tmux session
|
|
12
|
-
*/
|
|
13
|
-
export declare function listPanes(sessionName: string): PaneInfo[];
|
|
14
10
|
/**
|
|
15
11
|
* Capture tmux pane content with escape sequences (colors)
|
|
16
12
|
*/
|
|
17
|
-
export declare function capturePane(sessionName: string
|
|
13
|
+
export declare function capturePane(sessionName: string): string | null;
|
|
18
14
|
/**
|
|
19
15
|
* Send keys to tmux session
|
|
20
16
|
*/
|
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;
|
|
@@ -50,17 +49,11 @@ function listSessions() {
|
|
|
50
49
|
return [];
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
|
-
/**
|
|
54
|
-
* List all panes in a tmux session
|
|
55
|
-
*/
|
|
56
|
-
function listPanes(sessionName) {
|
|
57
|
-
return getExecutor().listPanes(sessionName);
|
|
58
|
-
}
|
|
59
52
|
/**
|
|
60
53
|
* Capture tmux pane content with escape sequences (colors)
|
|
61
54
|
*/
|
|
62
|
-
function capturePane(sessionName
|
|
63
|
-
return getExecutor().capturePane(sessionName
|
|
55
|
+
function capturePane(sessionName) {
|
|
56
|
+
return getExecutor().capturePane(sessionName);
|
|
64
57
|
}
|
|
65
58
|
/**
|
|
66
59
|
* Send keys to tmux session
|
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;
|
|
@@ -29,24 +29,8 @@ export declare class RelayWebSocketClient extends EventEmitter {
|
|
|
29
29
|
private handleError;
|
|
30
30
|
private scheduleReconnect;
|
|
31
31
|
send(message: Message): boolean;
|
|
32
|
-
sendScreen(data: Buffer
|
|
33
|
-
|
|
34
|
-
index: number;
|
|
35
|
-
}): boolean;
|
|
36
|
-
sendScreenCompressed(data: Buffer, paneMeta?: {
|
|
37
|
-
pane: string;
|
|
38
|
-
index: number;
|
|
39
|
-
}): boolean;
|
|
40
|
-
sendPaneLayout(panes: {
|
|
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
|
-
}[]): boolean;
|
|
32
|
+
sendScreen(data: Buffer): boolean;
|
|
33
|
+
sendScreenCompressed(data: Buffer): boolean;
|
|
50
34
|
/**
|
|
51
35
|
* Send file content to be displayed in the web FileViewer
|
|
52
36
|
* @param filename - The name of the file
|
package/dist/agent/websocket.js
CHANGED
|
@@ -70,8 +70,8 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
70
70
|
this.isConnected = true;
|
|
71
71
|
this.reconnectAttempts = 0;
|
|
72
72
|
this.circuitBreakerOpen = false;
|
|
73
|
-
this.registerAsHost();
|
|
74
73
|
this.emit('connected');
|
|
74
|
+
this.registerAsHost();
|
|
75
75
|
});
|
|
76
76
|
this.ws.on('message', (data) => {
|
|
77
77
|
try {
|
|
@@ -119,7 +119,7 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
119
119
|
switch (message.type) {
|
|
120
120
|
case 'keys':
|
|
121
121
|
if (message.session === this.sessionId && message.payload) {
|
|
122
|
-
this.emit('keys', message.payload
|
|
122
|
+
this.emit('keys', message.payload);
|
|
123
123
|
}
|
|
124
124
|
break;
|
|
125
125
|
case 'resize':
|
|
@@ -239,49 +239,32 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
239
239
|
return false;
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
-
sendScreen(data
|
|
242
|
+
sendScreen(data) {
|
|
243
243
|
if (!this.isConnected)
|
|
244
244
|
return false;
|
|
245
245
|
const base64Data = data.toString('base64');
|
|
246
|
-
|
|
246
|
+
return this.send({
|
|
247
247
|
type: 'screen',
|
|
248
248
|
session: this.sessionId,
|
|
249
249
|
payload: base64Data
|
|
250
|
-
};
|
|
251
|
-
if (paneMeta) {
|
|
252
|
-
msg.meta = { pane: paneMeta.pane, index: String(paneMeta.index) };
|
|
253
|
-
}
|
|
254
|
-
return this.send(msg);
|
|
250
|
+
});
|
|
255
251
|
}
|
|
256
|
-
sendScreenCompressed(data
|
|
252
|
+
sendScreenCompressed(data) {
|
|
257
253
|
if (!this.isConnected)
|
|
258
254
|
return false;
|
|
259
255
|
try {
|
|
260
256
|
const compressed = zlib.gzipSync(data);
|
|
261
257
|
const base64Data = compressed.toString('base64');
|
|
262
|
-
|
|
258
|
+
return this.send({
|
|
263
259
|
type: 'screenGz',
|
|
264
260
|
session: this.sessionId,
|
|
265
261
|
payload: base64Data
|
|
266
|
-
};
|
|
267
|
-
if (paneMeta) {
|
|
268
|
-
msg.meta = { pane: paneMeta.pane, index: String(paneMeta.index) };
|
|
269
|
-
}
|
|
270
|
-
return this.send(msg);
|
|
262
|
+
});
|
|
271
263
|
}
|
|
272
264
|
catch {
|
|
273
|
-
return this.sendScreen(data
|
|
265
|
+
return this.sendScreen(data);
|
|
274
266
|
}
|
|
275
267
|
}
|
|
276
|
-
sendPaneLayout(panes) {
|
|
277
|
-
if (!this.isConnected)
|
|
278
|
-
return false;
|
|
279
|
-
return this.send({
|
|
280
|
-
type: 'paneLayout',
|
|
281
|
-
session: this.sessionId,
|
|
282
|
-
payload: JSON.stringify(panes)
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
268
|
/**
|
|
286
269
|
* Send file content to be displayed in the web FileViewer
|
|
287
270
|
* @param filename - The name of the file
|
package/dist/commands/agent.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.startAgent = startAgent;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const runner_1 = require("../agent/runner");
|
|
9
|
+
const sentry_1 = require("../sentry");
|
|
9
10
|
async function startAgent(options) {
|
|
10
11
|
try {
|
|
11
12
|
const config = runner_1.AgentRunner.loadConfig(options.config);
|
|
@@ -13,6 +14,8 @@ async function startAgent(options) {
|
|
|
13
14
|
await runner.start();
|
|
14
15
|
}
|
|
15
16
|
catch (error) {
|
|
17
|
+
(0, sentry_1.captureException)(error);
|
|
18
|
+
await (0, sentry_1.flush)();
|
|
16
19
|
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
17
20
|
process.exit(1);
|
|
18
21
|
}
|
package/dist/index.js
CHANGED
|
@@ -47,6 +47,20 @@ const sessions_1 = require("./commands/sessions");
|
|
|
47
47
|
const sendkeys_1 = require("./commands/sendkeys");
|
|
48
48
|
const agent_1 = require("./commands/agent");
|
|
49
49
|
const config_1 = require("./config");
|
|
50
|
+
const sentry_1 = require("./sentry");
|
|
51
|
+
// Initialize Sentry as early as possible
|
|
52
|
+
(0, sentry_1.initSentry)();
|
|
53
|
+
// Catch unhandled errors globally
|
|
54
|
+
process.on('uncaughtException', async (error) => {
|
|
55
|
+
(0, sentry_1.captureException)(error);
|
|
56
|
+
await (0, sentry_1.flush)();
|
|
57
|
+
console.error(chalk_1.default.red(`Fatal error: ${error.message}`));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|
|
60
|
+
process.on('unhandledRejection', async (reason) => {
|
|
61
|
+
(0, sentry_1.captureException)(reason);
|
|
62
|
+
await (0, sentry_1.flush)();
|
|
63
|
+
});
|
|
50
64
|
// Check if tmux/itmux is available
|
|
51
65
|
function checkTmux() {
|
|
52
66
|
const isWindows = os.platform() === 'win32';
|
package/dist/sentry.d.ts
ADDED
package/dist/sentry.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.initSentry = initSentry;
|
|
37
|
+
exports.captureException = captureException;
|
|
38
|
+
exports.setUser = setUser;
|
|
39
|
+
exports.flush = flush;
|
|
40
|
+
const Sentry = __importStar(require("@sentry/node"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const SENTRY_DSN = process.env.SESSIONCAST_SENTRY_DSN || 'https://ee8894c0f54e651031b49f21a5bf8dc4@o4510832077701120.ingest.us.sentry.io/4510861067681792';
|
|
43
|
+
let initialized = false;
|
|
44
|
+
function initSentry() {
|
|
45
|
+
if (initialized || !SENTRY_DSN)
|
|
46
|
+
return;
|
|
47
|
+
Sentry.init({
|
|
48
|
+
dsn: SENTRY_DSN,
|
|
49
|
+
environment: process.env.NODE_ENV || 'production',
|
|
50
|
+
release: `sessioncast-cli@${getVersion()}`,
|
|
51
|
+
beforeSend(event) {
|
|
52
|
+
// Strip sensitive data: tokens, paths with usernames
|
|
53
|
+
if (event.extra) {
|
|
54
|
+
delete event.extra['token'];
|
|
55
|
+
delete event.extra['apiKey'];
|
|
56
|
+
}
|
|
57
|
+
return event;
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
Sentry.setTag('platform', os.platform());
|
|
61
|
+
Sentry.setTag('arch', os.arch());
|
|
62
|
+
Sentry.setTag('node', process.version);
|
|
63
|
+
initialized = true;
|
|
64
|
+
}
|
|
65
|
+
function captureException(error) {
|
|
66
|
+
if (!initialized)
|
|
67
|
+
return;
|
|
68
|
+
Sentry.captureException(error);
|
|
69
|
+
}
|
|
70
|
+
function setUser(email) {
|
|
71
|
+
if (!initialized)
|
|
72
|
+
return;
|
|
73
|
+
Sentry.setUser({ email });
|
|
74
|
+
}
|
|
75
|
+
async function flush() {
|
|
76
|
+
if (!initialized)
|
|
77
|
+
return;
|
|
78
|
+
await Sentry.flush(2000);
|
|
79
|
+
}
|
|
80
|
+
function getVersion() {
|
|
81
|
+
try {
|
|
82
|
+
return require('../package.json').version;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return 'unknown';
|
|
86
|
+
}
|
|
87
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sessioncast-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "SessionCast CLI - Control your agents from anywhere",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"dist"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@sentry/node": "^10.38.0",
|
|
29
30
|
"chalk": "^4.1.2",
|
|
30
31
|
"commander": "^12.1.0",
|
|
31
32
|
"conf": "^10.2.0",
|