sessioncast-cli 1.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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +137 -0
  3. package/dist/agent/api-client.d.ts +27 -0
  4. package/dist/agent/api-client.js +295 -0
  5. package/dist/agent/exec-service.d.ts +6 -0
  6. package/dist/agent/exec-service.js +126 -0
  7. package/dist/agent/index.d.ts +8 -0
  8. package/dist/agent/index.js +24 -0
  9. package/dist/agent/llm-service.d.ts +9 -0
  10. package/dist/agent/llm-service.js +156 -0
  11. package/dist/agent/runner.d.ts +16 -0
  12. package/dist/agent/runner.js +187 -0
  13. package/dist/agent/session-handler.d.ts +28 -0
  14. package/dist/agent/session-handler.js +184 -0
  15. package/dist/agent/tmux.d.ts +29 -0
  16. package/dist/agent/tmux.js +157 -0
  17. package/dist/agent/types.d.ts +72 -0
  18. package/dist/agent/types.js +2 -0
  19. package/dist/agent/websocket.d.ts +45 -0
  20. package/dist/agent/websocket.js +288 -0
  21. package/dist/api.d.ts +31 -0
  22. package/dist/api.js +78 -0
  23. package/dist/commands/agent.d.ts +5 -0
  24. package/dist/commands/agent.js +19 -0
  25. package/dist/commands/agents.d.ts +1 -0
  26. package/dist/commands/agents.js +77 -0
  27. package/dist/commands/login.d.ts +5 -0
  28. package/dist/commands/login.js +41 -0
  29. package/dist/commands/project.d.ts +33 -0
  30. package/dist/commands/project.js +359 -0
  31. package/dist/commands/sendkeys.d.ts +3 -0
  32. package/dist/commands/sendkeys.js +66 -0
  33. package/dist/commands/sessions.d.ts +1 -0
  34. package/dist/commands/sessions.js +89 -0
  35. package/dist/config.d.ts +13 -0
  36. package/dist/config.js +37 -0
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.js +125 -0
  39. package/dist/project/executor.d.ts +118 -0
  40. package/dist/project/executor.js +893 -0
  41. package/dist/project/index.d.ts +4 -0
  42. package/dist/project/index.js +20 -0
  43. package/dist/project/manager.d.ts +79 -0
  44. package/dist/project/manager.js +397 -0
  45. package/dist/project/relay-client.d.ts +87 -0
  46. package/dist/project/relay-client.js +200 -0
  47. package/dist/project/types.d.ts +43 -0
  48. package/dist/project/types.js +3 -0
  49. package/package.json +59 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 SessionCast
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # SessionCast CLI
2
+
3
+ Node.js agent and CLI for [SessionCast](https://sessioncast.io) - a real-time terminal sharing platform.
4
+
5
+ ## Features
6
+
7
+ ### Agent
8
+ - **Auto-discovery**: Automatically detects and connects tmux sessions
9
+ - **Real-time screen capture**: Streams terminal output with gzip compression
10
+ - **Circuit breaker**: Prevents reconnection storms with exponential backoff
11
+ - **Interactive control**: Supports keyboard input, resize, and session management
12
+ - **File viewer**: Cmd+Click on file paths to view files in browser
13
+
14
+ ### CLI Commands
15
+ - `sessioncast login <api-key>` - Authenticate with API key
16
+ - `sessioncast logout` - Clear stored credentials
17
+ - `sessioncast status` - Check authentication status
18
+ - `sessioncast agents` - List registered agents
19
+ - `sessioncast list [agent]` - List tmux sessions
20
+ - `sessioncast send <target> <keys>` - Send keys to a session
21
+ - `sessioncast agent` - Start the agent
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install -g sessioncast-cli
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ 1. **Get your agent token** from [app.sessioncast.io](https://app.sessioncast.io)
32
+
33
+ 2. **Create config file** `~/.sessioncast.yml`:
34
+
35
+ ```yaml
36
+ machineId: my-machine
37
+ relay: wss://relay.sessioncast.io/ws
38
+ token: agt_your_agent_token_here
39
+ ```
40
+
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
+ ## Configuration
50
+
51
+ Create `~/.sessioncast.yml` or `~/.tmux-remote.yml`:
52
+
53
+ ```yaml
54
+ machineId: my-machine
55
+ relay: wss://relay.sessioncast.io/ws
56
+ token: agt_your_agent_token_here
57
+
58
+ # Optional: API configuration
59
+ api:
60
+ enabled: true
61
+ agentId: "your-agent-uuid"
62
+
63
+ exec:
64
+ enabled: true
65
+ shell: /bin/bash
66
+ workingDir: /home/user
67
+ defaultTimeout: 30000
68
+
69
+ llm:
70
+ enabled: false
71
+ ```
72
+
73
+ ### Environment Variables
74
+
75
+ - `SESSIONCAST_CONFIG` - Custom config file path
76
+ - `TMUX_REMOTE_CONFIG` - Alternative config file path
77
+
78
+ ## Usage
79
+
80
+ ### Start the Agent
81
+
82
+ ```bash
83
+ # Run agent (foreground)
84
+ sessioncast agent
85
+
86
+ # Run agent (background)
87
+ nohup sessioncast agent > /tmp/sessioncast-agent.log 2>&1 &
88
+ ```
89
+
90
+ ### Send Keys to Session
91
+
92
+ ```bash
93
+ # Send text to a session
94
+ sessioncast send my-machine/dev "ls -la"
95
+
96
+ # Send special keys
97
+ sessioncast send my-machine/dev "Enter"
98
+ ```
99
+
100
+ ## Architecture
101
+
102
+ ```
103
+ ┌─────────────┐ WebSocket ┌─────────────┐ WebSocket ┌─────────────┐
104
+ │ Agent │ ◄─────────────────► │ Relay │ ◄────────────────► │ Viewer │
105
+ │ (Node.js) │ screen/keys │ (Server) │ screen/keys │ (Web) │
106
+ └─────────────┘ └─────────────┘ └─────────────┘
107
+
108
+ │ tmux
109
+
110
+ ┌─────────────┐
111
+ │ tmux │
112
+ │ sessions │
113
+ └─────────────┘
114
+ ```
115
+
116
+ ## Circuit Breaker
117
+
118
+ The agent implements a circuit breaker pattern to prevent reconnection storms:
119
+
120
+ - **Max reconnect attempts**: 5
121
+ - **Base delay**: 1 second
122
+ - **Max delay**: 30 seconds (with exponential backoff + jitter)
123
+ - **Circuit breaker duration**: 2 minutes cooldown after max attempts
124
+
125
+ ## Requirements
126
+
127
+ - Node.js >= 18
128
+ - tmux installed on the host machine
129
+
130
+ ## License
131
+
132
+ MIT License - see [LICENSE](LICENSE) for details.
133
+
134
+ ## Support
135
+
136
+ - Homepage: https://sessioncast.io
137
+ - Email: devload@sessioncast.io
@@ -0,0 +1,27 @@
1
+ import { AgentConfig } from './types';
2
+ export declare class ApiWebSocketClient {
3
+ private ws;
4
+ private config;
5
+ private apiConfig;
6
+ private commandService;
7
+ private llmService;
8
+ private isConnected;
9
+ private reconnectAttempts;
10
+ private circuitBreakerOpen;
11
+ private circuitBreakerResetTime;
12
+ private reconnectTimer;
13
+ private destroyed;
14
+ constructor(config: AgentConfig);
15
+ start(): void;
16
+ private connect;
17
+ private registerAsApiAgent;
18
+ private handleMessage;
19
+ private handleExec;
20
+ private handleLlmChat;
21
+ private handleSendKeys;
22
+ private handleListSessions;
23
+ private sendApiResponse;
24
+ private send;
25
+ private scheduleReconnect;
26
+ stop(): void;
27
+ }
@@ -0,0 +1,295 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.ApiWebSocketClient = void 0;
40
+ const ws_1 = __importDefault(require("ws"));
41
+ const exec_service_1 = require("./exec-service");
42
+ const llm_service_1 = require("./llm-service");
43
+ const tmux = __importStar(require("./tmux"));
44
+ const MAX_RECONNECT_ATTEMPTS = 5;
45
+ const BASE_RECONNECT_DELAY_MS = 2000;
46
+ const MAX_RECONNECT_DELAY_MS = 60000;
47
+ const CIRCUIT_BREAKER_DURATION_MS = 120000;
48
+ class ApiWebSocketClient {
49
+ constructor(config) {
50
+ this.ws = null;
51
+ this.isConnected = false;
52
+ this.reconnectAttempts = 0;
53
+ this.circuitBreakerOpen = false;
54
+ this.circuitBreakerResetTime = 0;
55
+ this.reconnectTimer = null;
56
+ this.destroyed = false;
57
+ this.config = config;
58
+ this.apiConfig = config.api || { enabled: false };
59
+ this.commandService = new exec_service_1.CommandExecutionService(this.apiConfig.exec);
60
+ this.llmService = new llm_service_1.LlmService(this.apiConfig.llm);
61
+ }
62
+ start() {
63
+ if (!this.apiConfig.enabled || !this.apiConfig.agentId) {
64
+ console.log('[API] API client disabled or no agentId configured');
65
+ return;
66
+ }
67
+ // Add jitter to prevent thundering herd
68
+ const jitter = Math.floor(Math.random() * 2000);
69
+ console.log(`[API] Starting in ${jitter}ms`);
70
+ setTimeout(() => this.connect(), jitter);
71
+ }
72
+ connect() {
73
+ if (this.destroyed)
74
+ return;
75
+ try {
76
+ this.ws = new ws_1.default(this.config.relay);
77
+ this.ws.on('open', () => {
78
+ this.isConnected = true;
79
+ this.reconnectAttempts = 0;
80
+ this.circuitBreakerOpen = false;
81
+ console.log('[API] Connected to relay');
82
+ this.registerAsApiAgent();
83
+ });
84
+ this.ws.on('message', (data) => {
85
+ try {
86
+ const message = JSON.parse(data.toString());
87
+ this.handleMessage(message);
88
+ }
89
+ catch (e) {
90
+ console.error('[API] Failed to parse message:', e);
91
+ }
92
+ });
93
+ this.ws.on('close', (code, reason) => {
94
+ this.isConnected = false;
95
+ console.log(`[API] Disconnected: code=${code}, reason=${reason.toString()}`);
96
+ if (!this.destroyed) {
97
+ this.scheduleReconnect();
98
+ }
99
+ });
100
+ this.ws.on('error', (error) => {
101
+ console.error('[API] WebSocket error:', error.message);
102
+ });
103
+ }
104
+ catch (error) {
105
+ console.error('[API] Connection error:', error.message);
106
+ if (!this.destroyed) {
107
+ this.scheduleReconnect();
108
+ }
109
+ }
110
+ }
111
+ registerAsApiAgent() {
112
+ const meta = {
113
+ machineId: this.config.machineId,
114
+ agentId: this.apiConfig.agentId
115
+ };
116
+ if (this.config.token) {
117
+ meta.token = this.config.token;
118
+ }
119
+ this.send({
120
+ type: 'register',
121
+ role: 'host',
122
+ session: `api-${this.apiConfig.agentId}`,
123
+ meta
124
+ });
125
+ console.log(`[API] Registered as API agent: ${this.apiConfig.agentId}`);
126
+ }
127
+ async handleMessage(message) {
128
+ switch (message.type) {
129
+ case 'exec':
130
+ await this.handleExec(message);
131
+ break;
132
+ case 'llm_chat':
133
+ await this.handleLlmChat(message);
134
+ break;
135
+ case 'send_keys':
136
+ await this.handleSendKeys(message);
137
+ break;
138
+ case 'list_sessions':
139
+ await this.handleListSessions(message);
140
+ break;
141
+ }
142
+ }
143
+ async handleExec(message) {
144
+ const meta = message.meta;
145
+ if (!meta?.requestId)
146
+ return;
147
+ try {
148
+ const payload = meta.payload ? JSON.parse(meta.payload) : {};
149
+ const { command, cwd, timeout, sessionId } = payload;
150
+ console.log(`[API] exec: command=${command}, cwd=${cwd}, timeout=${timeout}`);
151
+ const result = await this.commandService.executeCommand(command, cwd, timeout, sessionId);
152
+ this.sendApiResponse(meta.requestId, result);
153
+ }
154
+ catch (error) {
155
+ this.sendApiResponse(meta.requestId, {
156
+ exitCode: -1,
157
+ stdout: '',
158
+ stderr: `Error: ${error.message}`,
159
+ duration: 0
160
+ });
161
+ }
162
+ }
163
+ async handleLlmChat(message) {
164
+ const meta = message.meta;
165
+ if (!meta?.requestId)
166
+ return;
167
+ try {
168
+ const payload = meta.payload ? JSON.parse(meta.payload) : {};
169
+ const { model, messages, temperature, max_tokens, stream } = payload;
170
+ console.log(`[API] llm_chat: model=${model}, messages=${messages?.length || 0}`);
171
+ const result = await this.llmService.chat(model, messages, temperature, max_tokens, stream);
172
+ this.sendApiResponse(meta.requestId, result);
173
+ }
174
+ catch (error) {
175
+ this.sendApiResponse(meta.requestId, {
176
+ error: {
177
+ message: error.message,
178
+ type: 'internal_error'
179
+ }
180
+ });
181
+ }
182
+ }
183
+ async handleSendKeys(message) {
184
+ const meta = message.meta;
185
+ if (!meta?.requestId)
186
+ return;
187
+ try {
188
+ const payload = meta.payload ? JSON.parse(meta.payload) : {};
189
+ const { target, keys, enter = true } = payload;
190
+ if (!target || !keys) {
191
+ this.sendApiResponse(meta.requestId, {
192
+ success: false,
193
+ error: 'target and keys are required'
194
+ });
195
+ return;
196
+ }
197
+ console.log(`[API] send_keys: target=${target}, keys=${keys}, enter=${enter}`);
198
+ const success = tmux.sendKeys(target, keys, enter);
199
+ this.sendApiResponse(meta.requestId, { success, target });
200
+ }
201
+ catch (error) {
202
+ this.sendApiResponse(meta.requestId, {
203
+ success: false,
204
+ error: error.message
205
+ });
206
+ }
207
+ }
208
+ async handleListSessions(message) {
209
+ const meta = message.meta;
210
+ if (!meta?.requestId)
211
+ return;
212
+ try {
213
+ console.log('[API] list_sessions');
214
+ const sessions = tmux.listSessions();
215
+ this.sendApiResponse(meta.requestId, { sessions });
216
+ }
217
+ catch (error) {
218
+ this.sendApiResponse(meta.requestId, {
219
+ sessions: [],
220
+ error: error.message
221
+ });
222
+ }
223
+ }
224
+ sendApiResponse(requestId, response) {
225
+ this.send({
226
+ type: 'api_response',
227
+ meta: {
228
+ requestId,
229
+ payload: JSON.stringify(response)
230
+ }
231
+ });
232
+ }
233
+ send(message) {
234
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN) {
235
+ return false;
236
+ }
237
+ try {
238
+ this.ws.send(JSON.stringify(message));
239
+ return true;
240
+ }
241
+ catch {
242
+ return false;
243
+ }
244
+ }
245
+ scheduleReconnect() {
246
+ if (this.destroyed)
247
+ return;
248
+ // Check circuit breaker
249
+ if (this.circuitBreakerOpen) {
250
+ const now = Date.now();
251
+ if (now < this.circuitBreakerResetTime) {
252
+ const remainingSeconds = Math.ceil((this.circuitBreakerResetTime - now) / 1000);
253
+ console.log(`[API] Circuit breaker open. Retry in ${remainingSeconds} seconds`);
254
+ this.reconnectTimer = setTimeout(() => this.scheduleReconnect(), this.circuitBreakerResetTime - now);
255
+ return;
256
+ }
257
+ else {
258
+ console.log('[API] Circuit breaker reset');
259
+ this.circuitBreakerOpen = false;
260
+ this.reconnectAttempts = 0;
261
+ }
262
+ }
263
+ this.reconnectAttempts++;
264
+ if (this.reconnectAttempts > MAX_RECONNECT_ATTEMPTS) {
265
+ console.error(`[API] Max reconnect attempts reached. Circuit breaker active for ${CIRCUIT_BREAKER_DURATION_MS / 1000}s`);
266
+ this.circuitBreakerOpen = true;
267
+ this.circuitBreakerResetTime = Date.now() + CIRCUIT_BREAKER_DURATION_MS;
268
+ this.reconnectAttempts = 0;
269
+ this.reconnectTimer = setTimeout(() => this.scheduleReconnect(), CIRCUIT_BREAKER_DURATION_MS);
270
+ return;
271
+ }
272
+ const delay = Math.min(BASE_RECONNECT_DELAY_MS * Math.pow(2, this.reconnectAttempts - 1), MAX_RECONNECT_DELAY_MS);
273
+ const jitter = Math.random() * delay * 0.5;
274
+ const reconnectDelay = Math.floor(delay + jitter);
275
+ console.log(`[API] Reconnecting in ${reconnectDelay}ms (attempt ${this.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
276
+ this.reconnectTimer = setTimeout(() => {
277
+ if (!this.isConnected && !this.destroyed) {
278
+ this.connect();
279
+ }
280
+ }, reconnectDelay);
281
+ }
282
+ stop() {
283
+ console.log('[API] Stopping');
284
+ this.destroyed = true;
285
+ if (this.reconnectTimer) {
286
+ clearTimeout(this.reconnectTimer);
287
+ this.reconnectTimer = null;
288
+ }
289
+ if (this.ws) {
290
+ this.ws.close();
291
+ this.ws = null;
292
+ }
293
+ }
294
+ }
295
+ exports.ApiWebSocketClient = ApiWebSocketClient;
@@ -0,0 +1,6 @@
1
+ import { ExecConfig, ExecResult } from './types';
2
+ export declare class CommandExecutionService {
3
+ private config;
4
+ constructor(config?: ExecConfig);
5
+ executeCommand(command: string, cwd?: string, timeout?: number, sessionId?: string): Promise<ExecResult>;
6
+ }
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CommandExecutionService = void 0;
4
+ const child_process_1 = require("child_process");
5
+ class CommandExecutionService {
6
+ constructor(config) {
7
+ this.config = config || {
8
+ enabled: false,
9
+ shell: '/bin/bash',
10
+ defaultTimeout: 30000
11
+ };
12
+ }
13
+ async executeCommand(command, cwd, timeout, sessionId) {
14
+ const startTime = Date.now();
15
+ if (!this.config.enabled) {
16
+ return {
17
+ exitCode: -1,
18
+ stdout: '',
19
+ stderr: 'Command execution is disabled on this agent',
20
+ duration: 0
21
+ };
22
+ }
23
+ // Check allowed commands if configured
24
+ if (this.config.allowedCommands && this.config.allowedCommands.length > 0) {
25
+ const allowed = this.config.allowedCommands.some(pattern => command.startsWith(pattern) || new RegExp(pattern).test(command));
26
+ if (!allowed) {
27
+ return {
28
+ exitCode: -1,
29
+ stdout: '',
30
+ stderr: 'Command not in allowed list',
31
+ duration: 0
32
+ };
33
+ }
34
+ }
35
+ const timeoutMs = timeout ?? this.config.defaultTimeout;
36
+ const workingDir = cwd ?? this.config.workingDir;
37
+ const shell = this.config.shell || '/bin/bash';
38
+ return new Promise((resolve) => {
39
+ try {
40
+ // If sessionId is provided, run in tmux session
41
+ if (sessionId) {
42
+ try {
43
+ (0, child_process_1.execSync)(`tmux send-keys -t "${sessionId}" "${escapeForShell(command)}" Enter`, {
44
+ stdio: 'pipe',
45
+ timeout: timeoutMs
46
+ });
47
+ resolve({
48
+ exitCode: 0,
49
+ stdout: 'Command sent to tmux session',
50
+ stderr: '',
51
+ duration: Date.now() - startTime
52
+ });
53
+ }
54
+ catch (error) {
55
+ resolve({
56
+ exitCode: -1,
57
+ stdout: '',
58
+ stderr: error.message || 'Failed to send to tmux',
59
+ duration: Date.now() - startTime
60
+ });
61
+ }
62
+ return;
63
+ }
64
+ // Direct shell execution
65
+ const child = (0, child_process_1.spawn)(shell, ['-c', command], {
66
+ cwd: workingDir || undefined,
67
+ stdio: ['pipe', 'pipe', 'pipe']
68
+ });
69
+ let stdout = '';
70
+ let stderr = '';
71
+ let killed = false;
72
+ const timer = setTimeout(() => {
73
+ killed = true;
74
+ child.kill('SIGKILL');
75
+ }, timeoutMs);
76
+ child.stdout.on('data', (data) => {
77
+ stdout += data.toString();
78
+ });
79
+ child.stderr.on('data', (data) => {
80
+ stderr += data.toString();
81
+ });
82
+ child.on('close', (code) => {
83
+ clearTimeout(timer);
84
+ const duration = Date.now() - startTime;
85
+ if (killed) {
86
+ resolve({
87
+ exitCode: -1,
88
+ stdout: '',
89
+ stderr: `Command timed out after ${timeoutMs}ms`,
90
+ duration
91
+ });
92
+ }
93
+ else {
94
+ resolve({
95
+ exitCode: code ?? -1,
96
+ stdout,
97
+ stderr,
98
+ duration
99
+ });
100
+ }
101
+ });
102
+ child.on('error', (error) => {
103
+ clearTimeout(timer);
104
+ resolve({
105
+ exitCode: -1,
106
+ stdout: '',
107
+ stderr: `Execution error: ${error.message}`,
108
+ duration: Date.now() - startTime
109
+ });
110
+ });
111
+ }
112
+ catch (error) {
113
+ resolve({
114
+ exitCode: -1,
115
+ stdout: '',
116
+ stderr: `Execution error: ${error.message}`,
117
+ duration: Date.now() - startTime
118
+ });
119
+ }
120
+ });
121
+ }
122
+ }
123
+ exports.CommandExecutionService = CommandExecutionService;
124
+ function escapeForShell(str) {
125
+ return str.replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/`/g, '\\`');
126
+ }
@@ -0,0 +1,8 @@
1
+ export * from './types';
2
+ export * from './tmux';
3
+ export * from './websocket';
4
+ export * from './session-handler';
5
+ export * from './exec-service';
6
+ export * from './llm-service';
7
+ export * from './api-client';
8
+ export * from './runner';
@@ -0,0 +1,24 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./tmux"), exports);
19
+ __exportStar(require("./websocket"), exports);
20
+ __exportStar(require("./session-handler"), exports);
21
+ __exportStar(require("./exec-service"), exports);
22
+ __exportStar(require("./llm-service"), exports);
23
+ __exportStar(require("./api-client"), exports);
24
+ __exportStar(require("./runner"), exports);
@@ -0,0 +1,9 @@
1
+ import { LlmConfig, LlmMessage, LlmResponse } from './types';
2
+ export declare class LlmService {
3
+ private config;
4
+ constructor(config?: LlmConfig);
5
+ chat(model?: string, messages?: LlmMessage[], temperature?: number, maxTokens?: number, stream?: boolean): Promise<LlmResponse>;
6
+ private callOllama;
7
+ private convertOllamaToOpenAiFormat;
8
+ private callOpenAi;
9
+ }