sessioncast-cli 1.1.4 → 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 (38) hide show
  1. package/README.md +67 -66
  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 +4 -4
  14. package/package.json +2 -2
  15. package/dist/autopilot/index.d.ts +0 -94
  16. package/dist/autopilot/index.js +0 -322
  17. package/dist/autopilot/mission-analyzer.d.ts +0 -27
  18. package/dist/autopilot/mission-analyzer.js +0 -232
  19. package/dist/autopilot/project-detector.d.ts +0 -12
  20. package/dist/autopilot/project-detector.js +0 -326
  21. package/dist/autopilot/source-scanner.d.ts +0 -26
  22. package/dist/autopilot/source-scanner.js +0 -285
  23. package/dist/autopilot/speckit-generator.d.ts +0 -60
  24. package/dist/autopilot/speckit-generator.js +0 -511
  25. package/dist/autopilot/types.d.ts +0 -110
  26. package/dist/autopilot/types.js +0 -6
  27. package/dist/autopilot/workflow-generator.d.ts +0 -33
  28. package/dist/autopilot/workflow-generator.js +0 -278
  29. package/dist/project/executor.d.ts +0 -73
  30. package/dist/project/executor.js +0 -437
  31. package/dist/project/index.d.ts +0 -4
  32. package/dist/project/index.js +0 -20
  33. package/dist/project/manager.d.ts +0 -66
  34. package/dist/project/manager.js +0 -290
  35. package/dist/project/relay-client.d.ts +0 -37
  36. package/dist/project/relay-client.js +0 -204
  37. package/dist/project/types.d.ts +0 -48
  38. package/dist/project/types.js +0 -3
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SessionCast CLI
2
2
 
3
- Node.js agent and CLI for SessionCast - a real-time terminal streaming platform.
3
+ Node.js agent and CLI for [SessionCast](https://sessioncast.io) - a real-time terminal sharing platform.
4
4
 
5
5
  ## Features
6
6
 
@@ -9,10 +9,11 @@ Node.js agent and CLI for SessionCast - a real-time terminal streaming platform.
9
9
  - **Real-time screen capture**: Streams terminal output with gzip compression
10
10
  - **Circuit breaker**: Prevents reconnection storms with exponential backoff
11
11
  - **Interactive control**: Supports keyboard input, resize, and session management
12
- - **API integration**: External command execution and LLM service support
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
@@ -23,27 +24,61 @@ Node.js agent and CLI for SessionCast - a real-time terminal streaming platform.
23
24
  ## Installation
24
25
 
25
26
  ```bash
26
- # Clone the repository
27
- git clone git@github.com:your-org/sessioncast-cli.git
28
- cd sessioncast-cli
27
+ npm install -g sessioncast-cli
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
+
55
+ ## Quick Start
29
56
 
30
- # Install dependencies
31
- npm install
57
+ 1. **Get your agent token** from [app.sessioncast.io](https://app.sessioncast.io)
32
58
 
33
- # Build
34
- npm run build
59
+ 2. **Create config file** `~/.sessioncast.yml`:
35
60
 
36
- # Link globally (optional)
37
- npm link
61
+ ```yaml
62
+ machineId: my-machine
63
+ relay: wss://relay.sessioncast.io/ws
64
+ token: agt_your_agent_token_here
65
+ ```
66
+
67
+ 3. **Start the agent**:
68
+
69
+ ```bash
70
+ sessioncast agent
38
71
  ```
39
72
 
73
+ 4. **View your sessions** at [app.sessioncast.io](https://app.sessioncast.io)
74
+
40
75
  ## Configuration
41
76
 
42
77
  Create `~/.sessioncast.yml` or `~/.tmux-remote.yml`:
43
78
 
44
79
  ```yaml
45
80
  machineId: my-machine
46
- relay: wss://your-relay.sessioncast.io/ws
81
+ relay: wss://relay.sessioncast.io/ws
47
82
  token: agt_your_agent_token_here
48
83
 
49
84
  # Optional: API configuration
@@ -59,9 +94,6 @@ api:
59
94
 
60
95
  llm:
61
96
  enabled: false
62
- provider: ollama
63
- model: codellama
64
- endpoint: http://localhost:11434
65
97
  ```
66
98
 
67
99
  ### Environment Variables
@@ -96,56 +128,15 @@ sessioncast send my-machine/dev "Enter"
96
128
  ```
97
129
  ┌─────────────┐ WebSocket ┌─────────────┐ WebSocket ┌─────────────┐
98
130
  │ Agent │ ◄─────────────────► │ Relay │ ◄────────────────► │ Viewer │
99
- │ (Node.js) │ screen/keys │ (Spring) │ screen/keys │ (Web) │
131
+ │ (Node.js) │ screen/keys │ (Server) │ screen/keys │ (Web) │
100
132
  └─────────────┘ └─────────────┘ └─────────────┘
101
-
102
- │ tmux │ DynamoDB
103
-
104
- ┌─────────────┐ ┌─────────────┐
105
- │ tmux │ │ Sessions │
106
- │ sessions │ │ metadata │
107
- └─────────────┘ └─────────────┘
108
- ```
109
-
110
- ## Development
111
-
112
- ```bash
113
- # Install dependencies
114
- npm install
115
-
116
- # Build TypeScript
117
- npm run build
118
-
119
- # Watch mode (if configured)
120
- npm run dev
121
- ```
122
-
123
- ## Project Structure
124
-
125
- ```
126
- sessioncast-cli/
127
- ├── src/
128
- │ ├── agent/
129
- │ │ ├── api-client.ts # API WebSocket client
130
- │ │ ├── exec-service.ts # Command execution service
131
- │ │ ├── llm-service.ts # LLM integration (Ollama/OpenAI)
132
- │ │ ├── runner.ts # Agent runner with auto-discovery
133
- │ │ ├── session-handler.ts # tmux session handler
134
- │ │ ├── tmux.ts # tmux utilities
135
- │ │ ├── types.ts # Type definitions
136
- │ │ └── websocket.ts # WebSocket client with circuit breaker
137
- │ ├── commands/
138
- │ │ ├── agent.ts # Agent command
139
- │ │ ├── agents.ts # List agents command
140
- │ │ ├── login.ts # Login command
141
- │ │ ├── sendkeys.ts # Send keys command
142
- │ │ └── sessions.ts # List sessions command
143
- │ ├── api.ts # API client
144
- │ ├── config.ts # Configuration management
145
- │ └── index.ts # CLI entry point
146
- ├── package.json
147
- ├── tsconfig.json
148
- └── README.md
133
+
134
+ │ tmux
135
+
136
+ ┌─────────────┐
137
+ │ tmux │
138
+ │ sessions │
139
+ └─────────────┘
149
140
  ```
150
141
 
151
142
  ## Circuit Breaker
@@ -157,6 +148,16 @@ The agent implements a circuit breaker pattern to prevent reconnection storms:
157
148
  - **Max delay**: 30 seconds (with exponential backoff + jitter)
158
149
  - **Circuit breaker duration**: 2 minutes cooldown after max attempts
159
150
 
151
+ ## Requirements
152
+
153
+ - Node.js >= 18
154
+ - tmux installed on the host machine
155
+
160
156
  ## License
161
157
 
162
- Private - All rights reserved.
158
+ MIT License - see [LICENSE](LICENSE) for details.
159
+
160
+ ## Support
161
+
162
+ - Homepage: https://sessioncast.io
163
+ - Email: devload@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;