sessioncast-cli 1.1.5 → 2.0.0

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 (39) hide show
  1. package/README.md +27 -1
  2. package/dist/agent/runner.js +26 -23
  3. package/dist/agent/session-handler.d.ts +30 -1
  4. package/dist/agent/session-handler.js +166 -36
  5. package/dist/agent/tmux-executor.d.ts +6 -0
  6. package/dist/agent/tmux-executor.js +61 -2
  7. package/dist/agent/tmux.d.ts +8 -0
  8. package/dist/agent/tmux.js +14 -0
  9. package/dist/agent/websocket.d.ts +16 -2
  10. package/dist/agent/websocket.js +54 -70
  11. package/dist/api.js +2 -5
  12. package/dist/commands/login.js +7 -29
  13. package/dist/index.js +3 -3
  14. package/package.json +3 -3
  15. package/LICENSE +0 -21
  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/project/executor.d.ts +0 -73
  31. package/dist/project/executor.js +0 -437
  32. package/dist/project/index.d.ts +0 -4
  33. package/dist/project/index.js +0 -20
  34. package/dist/project/manager.d.ts +0 -66
  35. package/dist/project/manager.js +0 -290
  36. package/dist/project/relay-client.d.ts +0 -37
  37. package/dist/project/relay-client.js +0 -204
  38. package/dist/project/types.d.ts +0 -48
  39. package/dist/project/types.js +0 -3
package/README.md CHANGED
@@ -12,7 +12,8 @@ Node.js agent and CLI for [SessionCast](https://sessioncast.io) - a real-time te
12
12
  - **File viewer**: Cmd+Click on file paths to view files in browser
13
13
 
14
14
  ### CLI Commands
15
- - `sessioncast login <api-key>` - Authenticate with API key
15
+ - `sessioncast login` - Browser-based OAuth login (recommended)
16
+ - `sessioncast login <api-key>` - Authenticate with API key or agent token
16
17
  - `sessioncast logout` - Clear stored credentials
17
18
  - `sessioncast status` - Check authentication status
18
19
  - `sessioncast agents` - List registered agents
@@ -26,6 +27,31 @@ Node.js agent and CLI for [SessionCast](https://sessioncast.io) - a real-time te
26
27
  npm install -g sessioncast-cli
27
28
  ```
28
29
 
30
+ ### Requirements
31
+ - Node.js 18+
32
+ - tmux (Linux/macOS) or [itmux](https://github.com/itefixnet/itmux) (Windows)
33
+
34
+ ### Windows Setup
35
+
36
+ **Quick Install (PowerShell - Recommended)**:
37
+ ```powershell
38
+ # Download and extract itmux to C:\itmux
39
+ Invoke-WebRequest -Uri "https://github.com/itefixnet/itmux/releases/download/v1.1.0/itmux_1.1.0_x64_free.zip" -OutFile "$env:TEMP\itmux.zip"
40
+ Expand-Archive -Path "$env:TEMP\itmux.zip" -DestinationPath "C:\itmux" -Force
41
+
42
+ # Install SessionCast CLI
43
+ npm install -g sessioncast-cli
44
+ ```
45
+
46
+ **Manual Installation**:
47
+ 1. Download [itmux v1.1.0](https://github.com/itefixnet/itmux/releases/download/v1.1.0/itmux_1.1.0_x64_free.zip)
48
+ 2. Extract to one of these locations:
49
+ - `C:\itmux` (recommended)
50
+ - `%USERPROFILE%\itmux`
51
+ - Or set `ITMUX_HOME` environment variable
52
+ 3. Verify: `C:\itmux\bin\bash.exe` should exist
53
+ 4. Install CLI: `npm install -g sessioncast-cli`
54
+
29
55
  ## Quick Start
30
56
 
31
57
  1. **Get your agent token** from [app.sessioncast.io](https://app.sessioncast.io)
@@ -36,10 +36,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.AgentRunner = void 0;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
+ const os = __importStar(require("os"));
39
40
  const yaml = __importStar(require("js-yaml"));
40
41
  const session_handler_1 = require("./session-handler");
41
42
  const api_client_1 = require("./api-client");
42
43
  const tmux = __importStar(require("./tmux"));
44
+ const config_1 = require("../config");
43
45
  const SCAN_INTERVAL_MS = 5000;
44
46
  class AgentRunner {
45
47
  constructor(config) {
@@ -50,6 +52,8 @@ class AgentRunner {
50
52
  this.config = config;
51
53
  }
52
54
  static loadConfig(configPath) {
55
+ // Check if agent token is available (for relay connection)
56
+ const agentToken = (0, config_1.getAgentToken)();
53
57
  // Check environment variable
54
58
  const envPath = process.env.SESSIONCAST_CONFIG || process.env.TMUX_REMOTE_CONFIG;
55
59
  // Try multiple default paths
@@ -66,33 +70,32 @@ class AgentRunner {
66
70
  }
67
71
  }
68
72
  }
69
- // If yml file found, load from file
70
- if (finalPath && fs.existsSync(finalPath)) {
71
- console.log(`Loading config from: ${finalPath}`);
72
- const content = fs.readFileSync(finalPath, 'utf-8');
73
- const ext = path.extname(finalPath).toLowerCase();
74
- if (ext === '.json') {
75
- return JSON.parse(content);
76
- }
77
- else {
78
- return yaml.load(content);
79
- }
80
- }
81
- // SaaS mode: load from conf store (after sessioncast login)
82
- const config_1 = require("../config");
83
- const agentToken = (0, config_1.getAgentToken)();
84
- const relayUrl = (0, config_1.getRelayUrl)();
85
- const machineId = (0, config_1.getMachineId)();
86
- if (agentToken) {
87
- const os = require("os");
88
- console.log('Loading config from login credentials (SaaS mode)');
73
+ // If no config file found but agent token exists, create default config
74
+ if ((!finalPath || !fs.existsSync(finalPath)) && agentToken) {
75
+ console.log('Using OAuth authentication');
76
+ const hostname = os.hostname();
77
+ const machineId = `${hostname}-${Date.now()}`;
89
78
  return {
90
- machineId: machineId || os.hostname(),
91
- relay: relayUrl || 'wss://relay.sessioncast.io/ws',
79
+ machineId,
80
+ relay: (0, config_1.getRelayUrl)(),
92
81
  token: agentToken,
82
+ api: {
83
+ enabled: false
84
+ }
93
85
  };
94
86
  }
95
- throw new Error('No config found. Run "sessioncast login" first, or create ~/.sessioncast.yml');
87
+ if (!finalPath || !fs.existsSync(finalPath)) {
88
+ throw new Error(`Config file not found. Tried: ${configPath || envPath || defaultPaths.join(', ')}`);
89
+ }
90
+ console.log(`Loading config from: ${finalPath}`);
91
+ const content = fs.readFileSync(finalPath, 'utf-8');
92
+ const ext = path.extname(finalPath).toLowerCase();
93
+ if (ext === '.json') {
94
+ return JSON.parse(content);
95
+ }
96
+ else {
97
+ return yaml.load(content);
98
+ }
96
99
  }
97
100
  async start() {
98
101
  if (this.running)
@@ -15,11 +15,40 @@ export declare class TmuxSessionHandler {
15
15
  private lastForceSendTime;
16
16
  private lastChangeTime;
17
17
  private captureTimer;
18
+ private pendingUploads;
19
+ private uploadDir;
18
20
  constructor(options: SessionHandlerOptions);
19
21
  start(): void;
20
22
  private connectAndRun;
23
+ /**
24
+ * Handle file view request from web client
25
+ */
26
+ private handleRequestFileView;
27
+ /**
28
+ * Handle incoming file upload chunk
29
+ */
30
+ private handleUploadChunk;
31
+ /**
32
+ * Complete file upload by assembling chunks and writing to disk
33
+ */
34
+ private completeUpload;
35
+ /**
36
+ * Get upload directory (try to get tmux pane's current working directory)
37
+ */
38
+ private getUploadDirectory;
39
+ /**
40
+ * Sanitize filename to prevent path traversal attacks
41
+ */
42
+ private sanitizeFilename;
43
+ /**
44
+ * Get unique file path if file already exists
45
+ */
46
+ private getUniqueFilePath;
47
+ /**
48
+ * Get content type from file extension
49
+ */
50
+ private getContentType;
21
51
  private handleKeys;
22
- private handleFileViewRequest;
23
52
  private startScreenCapture;
24
53
  private stopScreenCapture;
25
54
  stop(): void;
@@ -38,26 +38,6 @@ 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 os = __importStar(require("os"));
42
- function expandPath(filePath) {
43
- if (filePath.startsWith('~/')) {
44
- return path.join(os.homedir(), filePath.slice(2));
45
- }
46
- return filePath;
47
- }
48
- function getLanguage(filePath) {
49
- const ext = path.extname(filePath).toLowerCase();
50
- const langMap = {
51
- '.js': 'javascript', '.ts': 'typescript', '.tsx': 'typescript',
52
- '.jsx': 'javascript', '.json': 'json', '.md': 'markdown',
53
- '.py': 'python', '.java': 'java', '.sh': 'shell',
54
- '.css': 'css', '.html': 'html', '.yaml': 'yaml', '.yml': 'yaml',
55
- '.kt': 'kotlin', '.swift': 'swift', '.go': 'go', '.rs': 'rust',
56
- '.c': 'c', '.cpp': 'cpp', '.h': 'c', '.hpp': 'cpp',
57
- '.rb': 'ruby', '.php': 'php', '.sql': 'sql', '.xml': 'xml',
58
- };
59
- return langMap[ext] || 'text';
60
- }
61
41
  // Capture intervals
62
42
  const CAPTURE_INTERVAL_ACTIVE_MS = 50;
63
43
  const CAPTURE_INTERVAL_IDLE_MS = 200;
@@ -73,6 +53,9 @@ class TmuxSessionHandler {
73
53
  this.lastForceSendTime = 0;
74
54
  this.lastChangeTime = 0;
75
55
  this.captureTimer = null;
56
+ // File upload handling
57
+ this.pendingUploads = new Map();
58
+ this.uploadDir = process.cwd(); // Default to current working directory
76
59
  this.config = options.config;
77
60
  this.tmuxSession = options.tmuxSession;
78
61
  this.sessionId = `${options.config.machineId}/${options.tmuxSession}`;
@@ -125,32 +108,179 @@ class TmuxSessionHandler {
125
108
  this.stop();
126
109
  });
127
110
  this.wsClient.on('requestFileView', (filePath) => {
128
- console.log(`[${this.tmuxSession}] File view request: ${filePath}`);
129
- this.handleFileViewRequest(filePath);
111
+ this.handleRequestFileView(filePath);
112
+ });
113
+ this.wsClient.on('uploadFile', (chunk) => {
114
+ this.handleUploadChunk(chunk);
130
115
  });
131
116
  this.wsClient.on('error', (error) => {
132
117
  console.error(`[${this.tmuxSession}] WebSocket error:`, error.message);
133
118
  });
134
119
  this.wsClient.connect();
135
120
  }
136
- handleKeys(keys) {
137
- tmux.sendKeys(this.tmuxSession, keys, false);
121
+ /**
122
+ * Handle file view request from web client
123
+ */
124
+ handleRequestFileView(filePath) {
125
+ console.log(`[${this.tmuxSession}] File view request: ${filePath}`);
126
+ try {
127
+ // Resolve file path (could be relative or absolute)
128
+ const resolvedPath = path.isAbsolute(filePath)
129
+ ? filePath
130
+ : path.resolve(this.uploadDir, filePath);
131
+ if (!fs.existsSync(resolvedPath)) {
132
+ console.log(`[${this.tmuxSession}] File not found: ${resolvedPath}`);
133
+ return;
134
+ }
135
+ const stat = fs.statSync(resolvedPath);
136
+ if (!stat.isFile()) {
137
+ console.log(`[${this.tmuxSession}] Not a file: ${resolvedPath}`);
138
+ return;
139
+ }
140
+ // Read file and send to viewer
141
+ const content = fs.readFileSync(resolvedPath);
142
+ const base64Content = content.toString('base64');
143
+ const filename = path.basename(resolvedPath);
144
+ const ext = path.extname(filename).toLowerCase();
145
+ // Determine content type
146
+ const contentType = this.getContentType(ext);
147
+ this.wsClient?.sendFileView(filename, base64Content, contentType, resolvedPath);
148
+ console.log(`[${this.tmuxSession}] Sent file view: ${filename}`);
149
+ }
150
+ catch (error) {
151
+ console.error(`[${this.tmuxSession}] Error reading file:`, error);
152
+ }
138
153
  }
139
- handleFileViewRequest(filePath) {
140
- if (!this.wsClient)
141
- return;
142
- const realPath = expandPath(filePath);
143
- const language = getLanguage(filePath);
154
+ /**
155
+ * Handle incoming file upload chunk
156
+ */
157
+ handleUploadChunk(chunk) {
158
+ const { filename, size, chunkIndex, totalChunks, payload } = chunk;
159
+ console.log(`[${this.tmuxSession}] Upload chunk: ${filename} (${chunkIndex + 1}/${totalChunks})`);
160
+ // Sanitize filename to prevent path traversal
161
+ const safeFilename = this.sanitizeFilename(filename);
162
+ const uploadKey = `${safeFilename}_${size}`;
163
+ // Get or create pending upload
164
+ let pending = this.pendingUploads.get(uploadKey);
165
+ if (!pending) {
166
+ pending = {
167
+ filename: safeFilename,
168
+ size,
169
+ chunks: new Map(),
170
+ totalChunks,
171
+ receivedAt: Date.now()
172
+ };
173
+ this.pendingUploads.set(uploadKey, pending);
174
+ }
175
+ // Store chunk
176
+ pending.chunks.set(chunkIndex, payload);
177
+ // Check if all chunks received
178
+ if (pending.chunks.size === totalChunks) {
179
+ this.completeUpload(uploadKey, pending);
180
+ }
181
+ }
182
+ /**
183
+ * Complete file upload by assembling chunks and writing to disk
184
+ */
185
+ completeUpload(uploadKey, pending) {
144
186
  try {
145
- const content = fs.readFileSync(realPath, 'utf-8');
146
- console.log(`[${this.tmuxSession}] File read: ${content.length} bytes`);
147
- this.wsClient.sendFileView(filePath, content, language);
187
+ // Assemble chunks in order
188
+ const chunks = [];
189
+ for (let i = 0; i < pending.totalChunks; i++) {
190
+ const chunkData = pending.chunks.get(i);
191
+ if (!chunkData) {
192
+ throw new Error(`Missing chunk ${i}`);
193
+ }
194
+ chunks.push(Buffer.from(chunkData, 'base64'));
195
+ }
196
+ const fileBuffer = Buffer.concat(chunks);
197
+ // Determine upload directory (try to get tmux pane's cwd)
198
+ const uploadPath = this.getUploadDirectory();
199
+ const filePath = path.join(uploadPath, pending.filename);
200
+ // Handle filename conflicts
201
+ const finalPath = this.getUniqueFilePath(filePath);
202
+ // Write file
203
+ fs.writeFileSync(finalPath, fileBuffer);
204
+ console.log(`[${this.tmuxSession}] File uploaded: ${finalPath}`);
205
+ // Send success notification
206
+ this.wsClient?.sendUploadComplete(pending.filename, finalPath);
207
+ // Cleanup
208
+ this.pendingUploads.delete(uploadKey);
209
+ }
210
+ catch (error) {
211
+ console.error(`[${this.tmuxSession}] Upload failed:`, error);
212
+ this.wsClient?.sendUploadError(pending.filename, error.message);
213
+ this.pendingUploads.delete(uploadKey);
214
+ }
215
+ }
216
+ /**
217
+ * Get upload directory (try to get tmux pane's current working directory)
218
+ */
219
+ getUploadDirectory() {
220
+ try {
221
+ // Try to get pane's current directory from tmux
222
+ const paneId = tmux.getActivePane(this.tmuxSession);
223
+ if (paneId) {
224
+ const cwd = tmux.getPaneCwd(this.tmuxSession, paneId);
225
+ if (cwd && fs.existsSync(cwd)) {
226
+ return cwd;
227
+ }
228
+ }
148
229
  }
149
- catch (err) {
150
- const errorMessage = err instanceof Error ? err.message : 'Unknown error';
151
- console.error(`[${this.tmuxSession}] File read error: ${errorMessage}`);
152
- this.wsClient.sendFileView(filePath, `Error: ${errorMessage}`, 'text', errorMessage);
230
+ catch {
231
+ // Ignore errors, fall back to default
153
232
  }
233
+ return this.uploadDir;
234
+ }
235
+ /**
236
+ * Sanitize filename to prevent path traversal attacks
237
+ */
238
+ sanitizeFilename(filename) {
239
+ // Remove path separators and dangerous characters
240
+ return path.basename(filename).replace(/[<>:"|?*\x00-\x1f]/g, '_');
241
+ }
242
+ /**
243
+ * Get unique file path if file already exists
244
+ */
245
+ getUniqueFilePath(filePath) {
246
+ if (!fs.existsSync(filePath)) {
247
+ return filePath;
248
+ }
249
+ const dir = path.dirname(filePath);
250
+ const ext = path.extname(filePath);
251
+ const name = path.basename(filePath, ext);
252
+ let counter = 1;
253
+ let newPath;
254
+ do {
255
+ newPath = path.join(dir, `${name} (${counter})${ext}`);
256
+ counter++;
257
+ } while (fs.existsSync(newPath) && counter < 1000);
258
+ return newPath;
259
+ }
260
+ /**
261
+ * Get content type from file extension
262
+ */
263
+ getContentType(ext) {
264
+ const types = {
265
+ '.txt': 'text/plain',
266
+ '.md': 'text/markdown',
267
+ '.html': 'text/html',
268
+ '.css': 'text/css',
269
+ '.js': 'text/javascript',
270
+ '.ts': 'text/typescript',
271
+ '.json': 'application/json',
272
+ '.xml': 'application/xml',
273
+ '.png': 'image/png',
274
+ '.jpg': 'image/jpeg',
275
+ '.jpeg': 'image/jpeg',
276
+ '.gif': 'image/gif',
277
+ '.svg': 'image/svg+xml',
278
+ '.pdf': 'application/pdf',
279
+ };
280
+ return types[ext] || 'application/octet-stream';
281
+ }
282
+ handleKeys(keys) {
283
+ tmux.sendKeys(this.tmuxSession, keys, false);
154
284
  }
155
285
  startScreenCapture() {
156
286
  if (this.captureTimer)
@@ -12,6 +12,8 @@ export interface TmuxExecutor {
12
12
  createSession(session: string, workingDir?: string): boolean;
13
13
  isAvailable(): boolean;
14
14
  getVersion(): string | null;
15
+ getActivePane(session: string): string | null;
16
+ getPaneCwd(session: string, paneId?: string): string | null;
15
17
  }
16
18
  /**
17
19
  * Unix/Linux/macOS implementation of TmuxExecutor.
@@ -26,6 +28,8 @@ export declare class UnixTmuxExecutor implements TmuxExecutor {
26
28
  createSession(session: string, workingDir?: string): boolean;
27
29
  isAvailable(): boolean;
28
30
  getVersion(): string | null;
31
+ getActivePane(session: string): string | null;
32
+ getPaneCwd(session: string, paneId?: string): string | null;
29
33
  private escapeForShell;
30
34
  }
31
35
  /**
@@ -45,6 +49,8 @@ export declare class WindowsTmuxExecutor implements TmuxExecutor {
45
49
  createSession(session: string, workingDir?: string): boolean;
46
50
  isAvailable(): boolean;
47
51
  getVersion(): string | null;
52
+ getActivePane(session: string): string | null;
53
+ getPaneCwd(session: string, paneId?: string): string | null;
48
54
  private escapeSession;
49
55
  private windowsToCygwinPath;
50
56
  /**
@@ -143,6 +143,31 @@ class UnixTmuxExecutor {
143
143
  return null;
144
144
  }
145
145
  }
146
+ getActivePane(session) {
147
+ try {
148
+ const output = (0, child_process_1.execSync)(`tmux display-message -t "${session}" -p "#{pane_id}"`, {
149
+ encoding: 'utf-8',
150
+ stdio: ['pipe', 'pipe', 'pipe']
151
+ });
152
+ return output.trim() || null;
153
+ }
154
+ catch {
155
+ return null;
156
+ }
157
+ }
158
+ getPaneCwd(session, paneId) {
159
+ try {
160
+ const target = paneId ? `${session}:${paneId}` : session;
161
+ const output = (0, child_process_1.execSync)(`tmux display-message -t "${target}" -p "#{pane_current_path}"`, {
162
+ encoding: 'utf-8',
163
+ stdio: ['pipe', 'pipe', 'pipe']
164
+ });
165
+ return output.trim() || null;
166
+ }
167
+ catch {
168
+ return null;
169
+ }
170
+ }
146
171
  escapeForShell(str) {
147
172
  return str.replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/`/g, '\\`');
148
173
  }
@@ -289,6 +314,27 @@ class WindowsTmuxExecutor {
289
314
  return null;
290
315
  }
291
316
  }
317
+ getActivePane(session) {
318
+ try {
319
+ const escaped = this.escapeSession(session);
320
+ const output = this.executeCommand(`tmux display-message -t '${escaped}' -p "#{pane_id}"`);
321
+ return output.trim() || null;
322
+ }
323
+ catch {
324
+ return null;
325
+ }
326
+ }
327
+ getPaneCwd(session, paneId) {
328
+ try {
329
+ const escapedSession = this.escapeSession(session);
330
+ const target = paneId ? `${escapedSession}:${paneId}` : escapedSession;
331
+ const output = this.executeCommand(`tmux display-message -t '${target}' -p "#{pane_current_path}"`);
332
+ return output.trim() || null;
333
+ }
334
+ catch {
335
+ return null;
336
+ }
337
+ }
292
338
  escapeSession(session) {
293
339
  return session.replace(/'/g, "'\\''");
294
340
  }
@@ -342,8 +388,21 @@ function createTmuxExecutor() {
342
388
  if (isWindows) {
343
389
  const itmuxPath = WindowsTmuxExecutor.findItmuxPath();
344
390
  if (!itmuxPath) {
345
- throw new Error('itmux not found. Please install itmux from https://github.com/itefixnet/itmux\n' +
346
- 'Set ITMUX_HOME environment variable or place itmux in a standard location.');
391
+ throw new Error('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
392
+ ' itmux not found - Windows tmux package required\n' +
393
+ '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n' +
394
+ ' Download: https://github.com/itefixnet/itmux/releases/latest\n' +
395
+ ' Latest: https://github.com/itefixnet/itmux/releases/download/v1.1.0/itmux_1.1.0_x64_free.zip\n\n' +
396
+ ' Installation:\n' +
397
+ ' 1. Download and extract itmux_1.1.0_x64_free.zip\n' +
398
+ ' 2. Place in one of these locations:\n' +
399
+ ' • C:\\itmux\n' +
400
+ ' • %USERPROFILE%\\itmux\n' +
401
+ ' • Or set ITMUX_HOME environment variable\n\n' +
402
+ ' Quick install (PowerShell):\n' +
403
+ ' Invoke-WebRequest -Uri "https://github.com/itefixnet/itmux/releases/download/v1.1.0/itmux_1.1.0_x64_free.zip" -OutFile "$env:TEMP\\itmux.zip"\n' +
404
+ ' Expand-Archive -Path "$env:TEMP\\itmux.zip" -DestinationPath "C:\\itmux" -Force\n\n' +
405
+ '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
347
406
  }
348
407
  return new WindowsTmuxExecutor(itmuxPath);
349
408
  }
@@ -35,3 +35,11 @@ export declare function isAvailable(): boolean;
35
35
  * Get tmux version
36
36
  */
37
37
  export declare function getVersion(): string | null;
38
+ /**
39
+ * Get the active pane ID in a session
40
+ */
41
+ export declare function getActivePane(sessionName: string): string | null;
42
+ /**
43
+ * Get the current working directory of a pane
44
+ */
45
+ export declare function getPaneCwd(sessionName: string, paneId?: string): string | null;
@@ -9,6 +9,8 @@ exports.createSession = createSession;
9
9
  exports.killSession = killSession;
10
10
  exports.isAvailable = isAvailable;
11
11
  exports.getVersion = getVersion;
12
+ exports.getActivePane = getActivePane;
13
+ exports.getPaneCwd = getPaneCwd;
12
14
  const tmux_executor_1 = require("./tmux-executor");
13
15
  // Lazy-initialized executor (created on first use)
14
16
  let executor = null;
@@ -131,3 +133,15 @@ function getVersion() {
131
133
  return null;
132
134
  }
133
135
  }
136
+ /**
137
+ * Get the active pane ID in a session
138
+ */
139
+ function getActivePane(sessionName) {
140
+ return getExecutor().getActivePane(sessionName);
141
+ }
142
+ /**
143
+ * Get the current working directory of a pane
144
+ */
145
+ function getPaneCwd(sessionName, paneId) {
146
+ return getExecutor().getPaneCwd(sessionName, paneId);
147
+ }
@@ -26,13 +26,27 @@ export declare class RelayWebSocketClient extends EventEmitter {
26
26
  connect(): void;
27
27
  private registerAsHost;
28
28
  private handleMessage;
29
- private handleFileUpload;
30
29
  private handleError;
31
30
  private scheduleReconnect;
32
31
  send(message: Message): boolean;
33
32
  sendScreen(data: Buffer): boolean;
34
33
  sendScreenCompressed(data: Buffer): boolean;
35
- sendFileView(filePath: string, content: string, language: string, error?: string): boolean;
34
+ /**
35
+ * Send file content to be displayed in the web FileViewer
36
+ * @param filename - The name of the file
37
+ * @param content - The file content (UTF-8 for text, base64 for images)
38
+ * @param contentType - MIME type (e.g., 'text/markdown', 'text/html', 'image/png')
39
+ * @param path - Optional file path
40
+ */
41
+ sendFileView(filename: string, content: string, contentType: string, path?: string): boolean;
42
+ /**
43
+ * Send upload complete notification to web viewer
44
+ */
45
+ sendUploadComplete(filename: string, path: string): boolean;
46
+ /**
47
+ * Send upload error notification to web viewer
48
+ */
49
+ sendUploadError(filename: string, error: string): boolean;
36
50
  getConnected(): boolean;
37
51
  destroy(): void;
38
52
  }