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.
Files changed (45) hide show
  1. package/dist/agent/runner.js +23 -0
  2. package/dist/agent/session-handler.d.ts +1 -2
  3. package/dist/agent/session-handler.js +32 -77
  4. package/dist/agent/tmux-executor.d.ts +3 -33
  5. package/dist/agent/tmux-executor.js +3 -50
  6. package/dist/agent/tmux.d.ts +2 -6
  7. package/dist/agent/tmux.js +2 -9
  8. package/dist/agent/types.d.ts +0 -10
  9. package/dist/agent/websocket.d.ts +2 -18
  10. package/dist/agent/websocket.js +9 -26
  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) {
@@ -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 lastScreens;
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.lastScreens = new Map();
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, meta) => {
96
- this.handleKeys(keys, meta);
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, meta) {
286
- const target = meta?.pane ? `${this.tmuxSession}.${meta.pane}` : this.tmuxSession;
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 panes = tmux.listPanes(this.tmuxSession);
300
- 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);
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
- let anyChanged = false;
316
- const forceTime = (now - this.lastForceSendTime) >= FORCE_SEND_INTERVAL_MS;
317
- if (isMultiPane) {
318
- // Multi-pane: capture each pane individually
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
- if (anyChanged || forceTime) {
363
- this.lastForceSendTime = now;
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
- // Small delay to ensure register message is processed by server first
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
- 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;
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
- 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;
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
- 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;
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
- 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
- };
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
- listPanes(session) {
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;
@@ -1,4 +1,4 @@
1
- import { TmuxSession, PaneInfo } from './types';
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, paneId?: string): string | null;
13
+ export declare function capturePane(sessionName: string): string | null;
18
14
  /**
19
15
  * Send keys to tmux session
20
16
  */
@@ -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, paneId) {
63
- return getExecutor().capturePane(sessionName, paneId);
55
+ function capturePane(sessionName) {
56
+ return getExecutor().capturePane(sessionName);
64
57
  }
65
58
  /**
66
59
  * Send keys to tmux session
@@ -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, paneMeta?: {
33
- pane: string;
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
@@ -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, message.meta);
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, paneMeta) {
242
+ sendScreen(data) {
243
243
  if (!this.isConnected)
244
244
  return false;
245
245
  const base64Data = data.toString('base64');
246
- const msg = {
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, paneMeta) {
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
- const msg = {
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, paneMeta);
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
@@ -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';
@@ -0,0 +1,4 @@
1
+ export declare function initSentry(): void;
2
+ export declare function captureException(error: unknown): void;
3
+ export declare function setUser(email: string): void;
4
+ export declare function flush(): Promise<void>;
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.1",
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",