sessioncast-cli 1.0.0 → 1.1.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.
- package/README.md +65 -40
- package/dist/agent/session-handler.d.ts +1 -0
- package/dist/agent/session-handler.js +42 -0
- package/dist/agent/tmux-executor.d.ts +66 -0
- package/dist/agent/tmux-executor.js +368 -0
- package/dist/agent/tmux.d.ts +9 -1
- package/dist/agent/tmux.js +52 -76
- package/dist/agent/websocket.d.ts +2 -8
- package/dist/agent/websocket.js +78 -14
- package/dist/autopilot/index.d.ts +94 -0
- package/dist/autopilot/index.js +322 -0
- package/dist/autopilot/mission-analyzer.d.ts +27 -0
- package/dist/autopilot/mission-analyzer.js +232 -0
- package/dist/autopilot/project-detector.d.ts +12 -0
- package/dist/autopilot/project-detector.js +326 -0
- package/dist/autopilot/source-scanner.d.ts +26 -0
- package/dist/autopilot/source-scanner.js +285 -0
- package/dist/autopilot/speckit-generator.d.ts +60 -0
- package/dist/autopilot/speckit-generator.js +511 -0
- package/dist/autopilot/types.d.ts +110 -0
- package/dist/autopilot/types.js +6 -0
- package/dist/autopilot/workflow-generator.d.ts +33 -0
- package/dist/autopilot/workflow-generator.js +278 -0
- package/dist/commands/autopilot.d.ts +30 -0
- package/dist/commands/autopilot.js +262 -0
- package/dist/commands/login.d.ts +2 -1
- package/dist/commands/login.js +199 -8
- package/dist/commands/project.d.ts +1 -1
- package/dist/commands/project.js +4 -13
- package/dist/config.d.ts +20 -0
- package/dist/config.js +69 -2
- package/dist/index.js +7 -47
- package/dist/project/executor.d.ts +8 -53
- package/dist/project/executor.js +64 -520
- package/dist/project/manager.d.ts +0 -13
- package/dist/project/manager.js +0 -107
- package/dist/project/relay-client.d.ts +18 -68
- package/dist/project/relay-client.js +134 -130
- package/dist/project/types.d.ts +5 -0
- package/dist/utils/fileUtils.d.ts +28 -0
- package/dist/utils/fileUtils.js +159 -0
- package/dist/utils/oauthServer.d.ts +18 -0
- package/dist/utils/oauthServer.js +244 -0
- package/dist/utils/pkce.d.ts +16 -0
- package/dist/utils/pkce.js +73 -0
- package/package.json +5 -14
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SessionCast CLI
|
|
2
2
|
|
|
3
|
-
Node.js agent and CLI for
|
|
3
|
+
Node.js agent and CLI for SessionCast - a real-time terminal streaming platform.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ Node.js agent and CLI for [SessionCast](https://sessioncast.io) - a real-time te
|
|
|
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
|
-
- **
|
|
12
|
+
- **API integration**: External command execution and LLM service support
|
|
13
13
|
|
|
14
14
|
### CLI Commands
|
|
15
15
|
- `sessioncast login <api-key>` - Authenticate with API key
|
|
@@ -23,36 +23,27 @@ Node.js agent and CLI for [SessionCast](https://sessioncast.io) - a real-time te
|
|
|
23
23
|
## Installation
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
## Quick Start
|
|
26
|
+
# Clone the repository
|
|
27
|
+
git clone git@github.com:your-org/sessioncast-cli.git
|
|
28
|
+
cd sessioncast-cli
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
# Install dependencies
|
|
31
|
+
npm install
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
# Build
|
|
34
|
+
npm run build
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
relay: wss://relay.sessioncast.io/ws
|
|
38
|
-
token: agt_your_agent_token_here
|
|
36
|
+
# Link globally (optional)
|
|
37
|
+
npm link
|
|
39
38
|
```
|
|
40
39
|
|
|
41
|
-
3. **Start the agent**:
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
sessioncast agent
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
4. **View your sessions** at [app.sessioncast.io](https://app.sessioncast.io)
|
|
48
|
-
|
|
49
40
|
## Configuration
|
|
50
41
|
|
|
51
42
|
Create `~/.sessioncast.yml` or `~/.tmux-remote.yml`:
|
|
52
43
|
|
|
53
44
|
```yaml
|
|
54
45
|
machineId: my-machine
|
|
55
|
-
relay: wss://relay.sessioncast.io/ws
|
|
46
|
+
relay: wss://your-relay.sessioncast.io/ws
|
|
56
47
|
token: agt_your_agent_token_here
|
|
57
48
|
|
|
58
49
|
# Optional: API configuration
|
|
@@ -68,6 +59,9 @@ api:
|
|
|
68
59
|
|
|
69
60
|
llm:
|
|
70
61
|
enabled: false
|
|
62
|
+
provider: ollama
|
|
63
|
+
model: codellama
|
|
64
|
+
endpoint: http://localhost:11434
|
|
71
65
|
```
|
|
72
66
|
|
|
73
67
|
### Environment Variables
|
|
@@ -102,15 +96,56 @@ sessioncast send my-machine/dev "Enter"
|
|
|
102
96
|
```
|
|
103
97
|
┌─────────────┐ WebSocket ┌─────────────┐ WebSocket ┌─────────────┐
|
|
104
98
|
│ Agent │ ◄─────────────────► │ Relay │ ◄────────────────► │ Viewer │
|
|
105
|
-
│ (Node.js) │ screen/keys │ (
|
|
99
|
+
│ (Node.js) │ screen/keys │ (Spring) │ screen/keys │ (Web) │
|
|
106
100
|
└─────────────┘ └─────────────┘ └─────────────┘
|
|
107
|
-
│
|
|
108
|
-
│ tmux
|
|
109
|
-
▼
|
|
110
|
-
┌─────────────┐
|
|
111
|
-
│ tmux │
|
|
112
|
-
│ sessions │
|
|
113
|
-
└─────────────┘
|
|
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
|
|
114
149
|
```
|
|
115
150
|
|
|
116
151
|
## Circuit Breaker
|
|
@@ -122,16 +157,6 @@ The agent implements a circuit breaker pattern to prevent reconnection storms:
|
|
|
122
157
|
- **Max delay**: 30 seconds (with exponential backoff + jitter)
|
|
123
158
|
- **Circuit breaker duration**: 2 minutes cooldown after max attempts
|
|
124
159
|
|
|
125
|
-
## Requirements
|
|
126
|
-
|
|
127
|
-
- Node.js >= 18
|
|
128
|
-
- tmux installed on the host machine
|
|
129
|
-
|
|
130
160
|
## License
|
|
131
161
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
## Support
|
|
135
|
-
|
|
136
|
-
- Homepage: https://sessioncast.io
|
|
137
|
-
- Email: devload@sessioncast.io
|
|
162
|
+
Private - All rights reserved.
|
|
@@ -36,6 +36,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.TmuxSessionHandler = void 0;
|
|
37
37
|
const websocket_1 = require("./websocket");
|
|
38
38
|
const tmux = __importStar(require("./tmux"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
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
|
+
}
|
|
39
61
|
// Capture intervals
|
|
40
62
|
const CAPTURE_INTERVAL_ACTIVE_MS = 50;
|
|
41
63
|
const CAPTURE_INTERVAL_IDLE_MS = 200;
|
|
@@ -102,6 +124,10 @@ class TmuxSessionHandler {
|
|
|
102
124
|
tmux.killSession(this.tmuxSession);
|
|
103
125
|
this.stop();
|
|
104
126
|
});
|
|
127
|
+
this.wsClient.on('requestFileView', (filePath) => {
|
|
128
|
+
console.log(`[${this.tmuxSession}] File view request: ${filePath}`);
|
|
129
|
+
this.handleFileViewRequest(filePath);
|
|
130
|
+
});
|
|
105
131
|
this.wsClient.on('error', (error) => {
|
|
106
132
|
console.error(`[${this.tmuxSession}] WebSocket error:`, error.message);
|
|
107
133
|
});
|
|
@@ -110,6 +136,22 @@ class TmuxSessionHandler {
|
|
|
110
136
|
handleKeys(keys) {
|
|
111
137
|
tmux.sendKeys(this.tmuxSession, keys, false);
|
|
112
138
|
}
|
|
139
|
+
handleFileViewRequest(filePath) {
|
|
140
|
+
if (!this.wsClient)
|
|
141
|
+
return;
|
|
142
|
+
const realPath = expandPath(filePath);
|
|
143
|
+
const language = getLanguage(filePath);
|
|
144
|
+
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);
|
|
148
|
+
}
|
|
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);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
113
155
|
startScreenCapture() {
|
|
114
156
|
if (this.captureTimer)
|
|
115
157
|
return;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for tmux operations.
|
|
3
|
+
* Implementations handle platform-specific differences (Unix vs Windows/itmux).
|
|
4
|
+
*/
|
|
5
|
+
export interface TmuxExecutor {
|
|
6
|
+
listSessions(): string[];
|
|
7
|
+
capturePane(session: string): string | null;
|
|
8
|
+
sendKeys(session: string, keys: string): boolean;
|
|
9
|
+
sendSpecialKey(session: string, key: string): boolean;
|
|
10
|
+
resizeWindow(session: string, cols: number, rows: number): boolean;
|
|
11
|
+
killSession(session: string): boolean;
|
|
12
|
+
createSession(session: string, workingDir?: string): boolean;
|
|
13
|
+
isAvailable(): boolean;
|
|
14
|
+
getVersion(): string | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Unix/Linux/macOS implementation of TmuxExecutor.
|
|
18
|
+
*/
|
|
19
|
+
export declare class UnixTmuxExecutor implements TmuxExecutor {
|
|
20
|
+
listSessions(): string[];
|
|
21
|
+
capturePane(session: string): string | null;
|
|
22
|
+
sendKeys(session: string, keys: string): boolean;
|
|
23
|
+
sendSpecialKey(session: string, key: string): boolean;
|
|
24
|
+
resizeWindow(session: string, cols: number, rows: number): boolean;
|
|
25
|
+
killSession(session: string): boolean;
|
|
26
|
+
createSession(session: string, workingDir?: string): boolean;
|
|
27
|
+
isAvailable(): boolean;
|
|
28
|
+
getVersion(): string | null;
|
|
29
|
+
private escapeForShell;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Windows implementation of TmuxExecutor using itmux (Cygwin + tmux bundle).
|
|
33
|
+
*/
|
|
34
|
+
export declare class WindowsTmuxExecutor implements TmuxExecutor {
|
|
35
|
+
private itmuxPath;
|
|
36
|
+
private bashPath;
|
|
37
|
+
constructor(itmuxPath: string);
|
|
38
|
+
private executeCommand;
|
|
39
|
+
listSessions(): string[];
|
|
40
|
+
capturePane(session: string): string | null;
|
|
41
|
+
sendKeys(session: string, keys: string): boolean;
|
|
42
|
+
sendSpecialKey(session: string, key: string): boolean;
|
|
43
|
+
resizeWindow(session: string, cols: number, rows: number): boolean;
|
|
44
|
+
killSession(session: string): boolean;
|
|
45
|
+
createSession(session: string, workingDir?: string): boolean;
|
|
46
|
+
isAvailable(): boolean;
|
|
47
|
+
getVersion(): string | null;
|
|
48
|
+
private escapeSession;
|
|
49
|
+
private windowsToCygwinPath;
|
|
50
|
+
/**
|
|
51
|
+
* Find itmux installation path on Windows.
|
|
52
|
+
*/
|
|
53
|
+
static findItmuxPath(): string | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get the appropriate TmuxExecutor for the current platform.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createTmuxExecutor(): TmuxExecutor;
|
|
59
|
+
/**
|
|
60
|
+
* Check if the current platform is Windows.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isWindows(): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Get platform name for logging.
|
|
65
|
+
*/
|
|
66
|
+
export declare function getPlatformName(): string;
|
|
@@ -0,0 +1,368 @@
|
|
|
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.WindowsTmuxExecutor = exports.UnixTmuxExecutor = void 0;
|
|
37
|
+
exports.createTmuxExecutor = createTmuxExecutor;
|
|
38
|
+
exports.isWindows = isWindows;
|
|
39
|
+
exports.getPlatformName = getPlatformName;
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
/**
|
|
45
|
+
* Unix/Linux/macOS implementation of TmuxExecutor.
|
|
46
|
+
*/
|
|
47
|
+
class UnixTmuxExecutor {
|
|
48
|
+
listSessions() {
|
|
49
|
+
try {
|
|
50
|
+
const output = (0, child_process_1.execSync)('tmux ls -F "#{session_name}"', {
|
|
51
|
+
encoding: 'utf-8',
|
|
52
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
53
|
+
});
|
|
54
|
+
return output
|
|
55
|
+
.trim()
|
|
56
|
+
.split('\n')
|
|
57
|
+
.filter(s => s.length > 0);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
capturePane(session) {
|
|
64
|
+
try {
|
|
65
|
+
const output = (0, child_process_1.execSync)(`tmux capture-pane -t "${session}" -p -e -N`, {
|
|
66
|
+
encoding: 'utf-8',
|
|
67
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
68
|
+
maxBuffer: 10 * 1024 * 1024
|
|
69
|
+
});
|
|
70
|
+
return output.replace(/\n/g, '\r\n');
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
sendKeys(session, keys) {
|
|
77
|
+
try {
|
|
78
|
+
const escaped = this.escapeForShell(keys);
|
|
79
|
+
(0, child_process_1.execSync)(`tmux send-keys -t "${session}" -l "${escaped}"`, { stdio: 'pipe' });
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
sendSpecialKey(session, key) {
|
|
87
|
+
try {
|
|
88
|
+
(0, child_process_1.execSync)(`tmux send-keys -t "${session}" ${key}`, { stdio: 'pipe' });
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
resizeWindow(session, cols, rows) {
|
|
96
|
+
try {
|
|
97
|
+
(0, child_process_1.execSync)(`tmux resize-window -t "${session}" -x ${cols} -y ${rows}`, { stdio: 'pipe' });
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
killSession(session) {
|
|
105
|
+
try {
|
|
106
|
+
(0, child_process_1.execSync)(`tmux kill-session -t "${session}"`, { stdio: 'pipe' });
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
createSession(session, workingDir) {
|
|
114
|
+
try {
|
|
115
|
+
const sanitized = session.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
116
|
+
if (!sanitized)
|
|
117
|
+
return false;
|
|
118
|
+
let cmd = `tmux new-session -d -s "${sanitized}"`;
|
|
119
|
+
if (workingDir) {
|
|
120
|
+
cmd += ` -c "${workingDir}"`;
|
|
121
|
+
}
|
|
122
|
+
(0, child_process_1.execSync)(cmd, { stdio: 'pipe' });
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
isAvailable() {
|
|
130
|
+
try {
|
|
131
|
+
(0, child_process_1.execSync)('which tmux', { stdio: 'pipe' });
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
getVersion() {
|
|
139
|
+
try {
|
|
140
|
+
return (0, child_process_1.execSync)('tmux -V', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
escapeForShell(str) {
|
|
147
|
+
return str.replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/`/g, '\\`');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
exports.UnixTmuxExecutor = UnixTmuxExecutor;
|
|
151
|
+
/**
|
|
152
|
+
* Windows implementation of TmuxExecutor using itmux (Cygwin + tmux bundle).
|
|
153
|
+
*/
|
|
154
|
+
class WindowsTmuxExecutor {
|
|
155
|
+
constructor(itmuxPath) {
|
|
156
|
+
this.itmuxPath = itmuxPath;
|
|
157
|
+
this.bashPath = path.join(itmuxPath, 'bin', 'bash.exe');
|
|
158
|
+
if (!fs.existsSync(this.bashPath)) {
|
|
159
|
+
throw new Error(`itmux bash not found at: ${this.bashPath}`);
|
|
160
|
+
}
|
|
161
|
+
console.log(`[Windows] Using itmux at: ${itmuxPath}`);
|
|
162
|
+
}
|
|
163
|
+
executeCommand(command) {
|
|
164
|
+
try {
|
|
165
|
+
const result = (0, child_process_1.spawnSync)(this.bashPath, ['-l', '-c', command], {
|
|
166
|
+
encoding: 'utf-8',
|
|
167
|
+
cwd: this.itmuxPath,
|
|
168
|
+
env: {
|
|
169
|
+
...process.env,
|
|
170
|
+
CYGWIN: 'nodosfilewarning',
|
|
171
|
+
HOME: `/home/${os.userInfo().username}`,
|
|
172
|
+
TERM: 'xterm-256color'
|
|
173
|
+
},
|
|
174
|
+
maxBuffer: 10 * 1024 * 1024
|
|
175
|
+
});
|
|
176
|
+
if (result.stderr) {
|
|
177
|
+
// Log but don't fail on stderr (tmux often outputs to stderr)
|
|
178
|
+
console.debug(`[itmux stderr] ${result.stderr}`);
|
|
179
|
+
}
|
|
180
|
+
return result.stdout?.trim() || '';
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
console.error(`[itmux] Command failed: ${command}`, error);
|
|
184
|
+
return '';
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
listSessions() {
|
|
188
|
+
try {
|
|
189
|
+
const output = this.executeCommand("tmux ls -F '#{session_name}' 2>/dev/null || true");
|
|
190
|
+
if (!output)
|
|
191
|
+
return [];
|
|
192
|
+
return output
|
|
193
|
+
.split('\n')
|
|
194
|
+
.map(s => s.trim())
|
|
195
|
+
.filter(s => s.length > 0 && !s.startsWith('no server') && !s.includes('error'));
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
capturePane(session) {
|
|
202
|
+
try {
|
|
203
|
+
const escaped = this.escapeSession(session);
|
|
204
|
+
const output = this.executeCommand(`tmux capture-pane -t '${escaped}' -p -e -N`);
|
|
205
|
+
if (!output)
|
|
206
|
+
return null;
|
|
207
|
+
return output.replace(/\n/g, '\r\n');
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
sendKeys(session, keys) {
|
|
214
|
+
try {
|
|
215
|
+
const escapedSession = this.escapeSession(session);
|
|
216
|
+
const escapedKeys = keys.replace(/'/g, "'\\''");
|
|
217
|
+
this.executeCommand(`tmux send-keys -t '${escapedSession}' -l '${escapedKeys}'`);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
sendSpecialKey(session, key) {
|
|
225
|
+
try {
|
|
226
|
+
const escaped = this.escapeSession(session);
|
|
227
|
+
this.executeCommand(`tmux send-keys -t '${escaped}' ${key}`);
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
resizeWindow(session, cols, rows) {
|
|
235
|
+
try {
|
|
236
|
+
const escaped = this.escapeSession(session);
|
|
237
|
+
this.executeCommand(`tmux resize-window -t '${escaped}' -x ${cols} -y ${rows}`);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
killSession(session) {
|
|
245
|
+
try {
|
|
246
|
+
const escaped = this.escapeSession(session);
|
|
247
|
+
this.executeCommand(`tmux kill-session -t '${escaped}'`);
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
createSession(session, workingDir) {
|
|
255
|
+
try {
|
|
256
|
+
const sanitized = session.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
257
|
+
if (!sanitized)
|
|
258
|
+
return false;
|
|
259
|
+
let cmd;
|
|
260
|
+
if (workingDir) {
|
|
261
|
+
const cygwinPath = this.windowsToCygwinPath(workingDir);
|
|
262
|
+
cmd = `tmux new-session -d -s '${sanitized}' -c '${cygwinPath}'`;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
cmd = `tmux new-session -d -s '${sanitized}'`;
|
|
266
|
+
}
|
|
267
|
+
this.executeCommand(cmd);
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
isAvailable() {
|
|
275
|
+
try {
|
|
276
|
+
const version = this.getVersion();
|
|
277
|
+
return version !== null && version.includes('tmux');
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
getVersion() {
|
|
284
|
+
try {
|
|
285
|
+
const output = this.executeCommand('tmux -V');
|
|
286
|
+
return output || null;
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
escapeSession(session) {
|
|
293
|
+
return session.replace(/'/g, "'\\''");
|
|
294
|
+
}
|
|
295
|
+
windowsToCygwinPath(windowsPath) {
|
|
296
|
+
if (!windowsPath)
|
|
297
|
+
return windowsPath;
|
|
298
|
+
// Handle UNC paths
|
|
299
|
+
if (windowsPath.startsWith('\\\\')) {
|
|
300
|
+
return windowsPath;
|
|
301
|
+
}
|
|
302
|
+
// Handle drive letters (C:\...)
|
|
303
|
+
if (windowsPath.length >= 2 && windowsPath[1] === ':') {
|
|
304
|
+
const drive = windowsPath[0].toLowerCase();
|
|
305
|
+
const rest = windowsPath.substring(2).replace(/\\/g, '/');
|
|
306
|
+
return `/cygdrive/${drive}${rest}`;
|
|
307
|
+
}
|
|
308
|
+
return windowsPath.replace(/\\/g, '/');
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Find itmux installation path on Windows.
|
|
312
|
+
*/
|
|
313
|
+
static findItmuxPath() {
|
|
314
|
+
// 1. Check environment variable
|
|
315
|
+
const envPath = process.env.ITMUX_HOME;
|
|
316
|
+
if (envPath && fs.existsSync(path.join(envPath, 'bin', 'bash.exe'))) {
|
|
317
|
+
return envPath;
|
|
318
|
+
}
|
|
319
|
+
// 2. Check common locations
|
|
320
|
+
const locations = [
|
|
321
|
+
path.join(os.homedir(), 'itmux'),
|
|
322
|
+
'C:\\itmux',
|
|
323
|
+
'D:\\itmux',
|
|
324
|
+
path.join(process.cwd(), 'itmux'),
|
|
325
|
+
path.join(process.env.LOCALAPPDATA || '', 'itmux'),
|
|
326
|
+
path.join(process.env.ProgramFiles || '', 'itmux'),
|
|
327
|
+
];
|
|
328
|
+
for (const loc of locations) {
|
|
329
|
+
if (loc && fs.existsSync(path.join(loc, 'bin', 'bash.exe'))) {
|
|
330
|
+
return loc;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
exports.WindowsTmuxExecutor = WindowsTmuxExecutor;
|
|
337
|
+
/**
|
|
338
|
+
* Get the appropriate TmuxExecutor for the current platform.
|
|
339
|
+
*/
|
|
340
|
+
function createTmuxExecutor() {
|
|
341
|
+
const isWindows = os.platform() === 'win32';
|
|
342
|
+
if (isWindows) {
|
|
343
|
+
const itmuxPath = WindowsTmuxExecutor.findItmuxPath();
|
|
344
|
+
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.');
|
|
347
|
+
}
|
|
348
|
+
return new WindowsTmuxExecutor(itmuxPath);
|
|
349
|
+
}
|
|
350
|
+
return new UnixTmuxExecutor();
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Check if the current platform is Windows.
|
|
354
|
+
*/
|
|
355
|
+
function isWindows() {
|
|
356
|
+
return os.platform() === 'win32';
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Get platform name for logging.
|
|
360
|
+
*/
|
|
361
|
+
function getPlatformName() {
|
|
362
|
+
const platform = os.platform();
|
|
363
|
+
if (platform === 'win32')
|
|
364
|
+
return 'Windows';
|
|
365
|
+
if (platform === 'darwin')
|
|
366
|
+
return 'macOS';
|
|
367
|
+
return 'Linux/Unix';
|
|
368
|
+
}
|
package/dist/agent/tmux.d.ts
CHANGED
|
@@ -22,8 +22,16 @@ export declare function resizeWindow(sessionName: string, cols: number, rows: nu
|
|
|
22
22
|
/**
|
|
23
23
|
* Create new tmux session
|
|
24
24
|
*/
|
|
25
|
-
export declare function createSession(sessionName: string): boolean;
|
|
25
|
+
export declare function createSession(sessionName: string, workingDir?: string): boolean;
|
|
26
26
|
/**
|
|
27
27
|
* Kill tmux session
|
|
28
28
|
*/
|
|
29
29
|
export declare function killSession(sessionName: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Check if tmux is available
|
|
32
|
+
*/
|
|
33
|
+
export declare function isAvailable(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Get tmux version
|
|
36
|
+
*/
|
|
37
|
+
export declare function getVersion(): string | null;
|