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
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LlmService = void 0;
4
+ class LlmService {
5
+ constructor(config) {
6
+ this.config = config || {
7
+ enabled: false,
8
+ provider: 'ollama',
9
+ baseUrl: 'http://localhost:11434',
10
+ model: 'llama2'
11
+ };
12
+ }
13
+ async chat(model, messages, temperature, maxTokens, stream) {
14
+ if (!this.config.enabled) {
15
+ return {
16
+ id: '',
17
+ object: 'error',
18
+ created: Date.now(),
19
+ model: '',
20
+ choices: [],
21
+ error: {
22
+ message: 'LLM is disabled on this agent',
23
+ type: 'service_unavailable'
24
+ }
25
+ };
26
+ }
27
+ const provider = this.config.provider || 'ollama';
28
+ const actualModel = model || this.config.model;
29
+ try {
30
+ switch (provider.toLowerCase()) {
31
+ case 'ollama':
32
+ return await this.callOllama(actualModel, messages || [], temperature, maxTokens);
33
+ case 'openai':
34
+ return await this.callOpenAi(actualModel, messages || [], temperature, maxTokens);
35
+ default:
36
+ return {
37
+ id: '',
38
+ object: 'error',
39
+ created: Date.now(),
40
+ model: '',
41
+ choices: [],
42
+ error: {
43
+ message: `Unknown LLM provider: ${provider}`,
44
+ type: 'invalid_request'
45
+ }
46
+ };
47
+ }
48
+ }
49
+ catch (error) {
50
+ return {
51
+ id: '',
52
+ object: 'error',
53
+ created: Date.now(),
54
+ model: '',
55
+ choices: [],
56
+ error: {
57
+ message: error.message,
58
+ type: 'internal_error'
59
+ }
60
+ };
61
+ }
62
+ }
63
+ async callOllama(model, messages, temperature, maxTokens) {
64
+ const baseUrl = this.config.baseUrl || 'http://localhost:11434';
65
+ const requestBody = {
66
+ model,
67
+ messages,
68
+ stream: false
69
+ };
70
+ const options = {};
71
+ if (temperature !== undefined) {
72
+ options.temperature = temperature;
73
+ }
74
+ if (maxTokens !== undefined) {
75
+ options.num_predict = maxTokens;
76
+ }
77
+ if (Object.keys(options).length > 0) {
78
+ requestBody.options = options;
79
+ }
80
+ const response = await fetch(`${baseUrl}/api/chat`, {
81
+ method: 'POST',
82
+ headers: { 'Content-Type': 'application/json' },
83
+ body: JSON.stringify(requestBody)
84
+ });
85
+ if (!response.ok) {
86
+ throw new Error(`Ollama returned status ${response.status}`);
87
+ }
88
+ const ollamaResponse = await response.json();
89
+ return this.convertOllamaToOpenAiFormat(ollamaResponse, model);
90
+ }
91
+ convertOllamaToOpenAiFormat(ollamaResponse, model) {
92
+ const message = ollamaResponse.message || {};
93
+ const content = message.content || '';
94
+ const promptTokens = ollamaResponse.prompt_eval_count || 0;
95
+ const completionTokens = ollamaResponse.eval_count || 0;
96
+ return {
97
+ id: `chatcmpl-${Math.random().toString(36).substring(2, 10)}`,
98
+ object: 'chat.completion',
99
+ created: Math.floor(Date.now() / 1000),
100
+ model,
101
+ choices: [{
102
+ index: 0,
103
+ message: {
104
+ role: 'assistant',
105
+ content
106
+ },
107
+ finish_reason: 'stop'
108
+ }],
109
+ usage: {
110
+ prompt_tokens: promptTokens,
111
+ completion_tokens: completionTokens,
112
+ total_tokens: promptTokens + completionTokens
113
+ }
114
+ };
115
+ }
116
+ async callOpenAi(model, messages, temperature, maxTokens) {
117
+ const baseUrl = this.config.baseUrl || 'https://api.openai.com/v1';
118
+ const apiKey = this.config.apiKey;
119
+ if (!apiKey) {
120
+ return {
121
+ id: '',
122
+ object: 'error',
123
+ created: Date.now(),
124
+ model: '',
125
+ choices: [],
126
+ error: {
127
+ message: 'OpenAI API key not configured',
128
+ type: 'configuration_error'
129
+ }
130
+ };
131
+ }
132
+ const requestBody = {
133
+ model,
134
+ messages
135
+ };
136
+ if (temperature !== undefined) {
137
+ requestBody.temperature = temperature;
138
+ }
139
+ if (maxTokens !== undefined) {
140
+ requestBody.max_tokens = maxTokens;
141
+ }
142
+ const response = await fetch(`${baseUrl}/chat/completions`, {
143
+ method: 'POST',
144
+ headers: {
145
+ 'Content-Type': 'application/json',
146
+ 'Authorization': `Bearer ${apiKey}`
147
+ },
148
+ body: JSON.stringify(requestBody)
149
+ });
150
+ if (!response.ok) {
151
+ throw new Error(`OpenAI returned status ${response.status}`);
152
+ }
153
+ return await response.json();
154
+ }
155
+ }
156
+ exports.LlmService = LlmService;
@@ -0,0 +1,16 @@
1
+ import { AgentConfig } from './types';
2
+ export declare class AgentRunner {
3
+ private config;
4
+ private handlers;
5
+ private apiClient;
6
+ private scanTimer;
7
+ private running;
8
+ constructor(config: AgentConfig);
9
+ static loadConfig(configPath?: string): AgentConfig;
10
+ start(): Promise<void>;
11
+ private scanAndUpdateSessions;
12
+ private startSessionHandler;
13
+ private stopSessionHandler;
14
+ private createTmuxSession;
15
+ stop(): void;
16
+ }
@@ -0,0 +1,187 @@
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.AgentRunner = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const yaml = __importStar(require("js-yaml"));
40
+ const session_handler_1 = require("./session-handler");
41
+ const api_client_1 = require("./api-client");
42
+ const tmux = __importStar(require("./tmux"));
43
+ const SCAN_INTERVAL_MS = 5000;
44
+ class AgentRunner {
45
+ constructor(config) {
46
+ this.handlers = new Map();
47
+ this.apiClient = null;
48
+ this.scanTimer = null;
49
+ this.running = false;
50
+ this.config = config;
51
+ }
52
+ static loadConfig(configPath) {
53
+ // Check environment variable
54
+ const envPath = process.env.SESSIONCAST_CONFIG || process.env.TMUX_REMOTE_CONFIG;
55
+ // Try multiple default paths
56
+ const defaultPaths = [
57
+ path.join(process.env.HOME || '', '.sessioncast.yml'),
58
+ path.join(process.env.HOME || '', '.tmux-remote.yml'),
59
+ ];
60
+ let finalPath = configPath || envPath;
61
+ if (!finalPath) {
62
+ for (const p of defaultPaths) {
63
+ if (fs.existsSync(p)) {
64
+ finalPath = p;
65
+ break;
66
+ }
67
+ }
68
+ }
69
+ if (!finalPath || !fs.existsSync(finalPath)) {
70
+ throw new Error(`Config file not found. Tried: ${configPath || envPath || defaultPaths.join(', ')}`);
71
+ }
72
+ console.log(`Loading config from: ${finalPath}`);
73
+ const content = fs.readFileSync(finalPath, 'utf-8');
74
+ const ext = path.extname(finalPath).toLowerCase();
75
+ if (ext === '.json') {
76
+ return JSON.parse(content);
77
+ }
78
+ else {
79
+ return yaml.load(content);
80
+ }
81
+ }
82
+ async start() {
83
+ if (this.running)
84
+ return;
85
+ this.running = true;
86
+ console.log('Starting SessionCast Agent...');
87
+ console.log(`Machine ID: ${this.config.machineId}`);
88
+ console.log(`Relay: ${this.config.relay}`);
89
+ console.log(`Token: ${this.config.token ? 'present' : 'none'}`);
90
+ // Start API client if configured
91
+ if (this.config.api?.enabled && this.config.api.agentId) {
92
+ this.apiClient = new api_client_1.ApiWebSocketClient(this.config);
93
+ this.apiClient.start();
94
+ }
95
+ // Initial scan
96
+ this.scanAndUpdateSessions();
97
+ // Schedule periodic scan
98
+ this.scanTimer = setInterval(() => {
99
+ this.scanAndUpdateSessions();
100
+ }, SCAN_INTERVAL_MS);
101
+ console.log(`Agent started with auto-discovery (scanning every ${SCAN_INTERVAL_MS / 1000}s)`);
102
+ // Handle shutdown
103
+ process.on('SIGINT', () => this.stop());
104
+ process.on('SIGTERM', () => this.stop());
105
+ }
106
+ scanAndUpdateSessions() {
107
+ try {
108
+ const currentSessions = new Set(tmux.scanSessions());
109
+ const trackedSessions = new Set(this.handlers.keys());
110
+ // Start handlers for new sessions
111
+ for (const session of currentSessions) {
112
+ if (!trackedSessions.has(session)) {
113
+ this.startSessionHandler(session);
114
+ }
115
+ }
116
+ // Stop handlers for removed sessions
117
+ for (const session of trackedSessions) {
118
+ if (!currentSessions.has(session)) {
119
+ this.stopSessionHandler(session);
120
+ }
121
+ }
122
+ }
123
+ catch (error) {
124
+ console.error('Error during session scan:', error);
125
+ }
126
+ }
127
+ startSessionHandler(tmuxSession) {
128
+ console.log(`Discovered new tmux session: ${tmuxSession}`);
129
+ const handler = new session_handler_1.TmuxSessionHandler({
130
+ config: this.config,
131
+ tmuxSession,
132
+ onCreateSession: (name) => this.createTmuxSession(name)
133
+ });
134
+ this.handlers.set(tmuxSession, handler);
135
+ handler.start();
136
+ console.log(`Started handler for session: ${this.config.machineId}/${tmuxSession}`);
137
+ }
138
+ stopSessionHandler(tmuxSession) {
139
+ console.log(`Tmux session removed: ${tmuxSession}`);
140
+ const handler = this.handlers.get(tmuxSession);
141
+ if (handler) {
142
+ handler.stop();
143
+ this.handlers.delete(tmuxSession);
144
+ console.log(`Stopped handler for session: ${this.config.machineId}/${tmuxSession}`);
145
+ }
146
+ }
147
+ createTmuxSession(sessionName) {
148
+ // Sanitize session name
149
+ const sanitized = sessionName.replace(/[^a-zA-Z0-9_-]/g, '_');
150
+ if (!sanitized) {
151
+ console.warn(`Invalid session name: ${sessionName}`);
152
+ return;
153
+ }
154
+ if (this.handlers.has(sanitized)) {
155
+ console.warn(`Session already exists: ${sanitized}`);
156
+ return;
157
+ }
158
+ console.log(`Creating new tmux session: ${sanitized}`);
159
+ if (tmux.createSession(sanitized)) {
160
+ console.log(`Successfully created tmux session: ${sanitized}`);
161
+ // Force immediate scan
162
+ this.scanAndUpdateSessions();
163
+ }
164
+ else {
165
+ console.error(`Failed to create tmux session: ${sanitized}`);
166
+ }
167
+ }
168
+ stop() {
169
+ console.log('Shutting down Agent...');
170
+ this.running = false;
171
+ if (this.scanTimer) {
172
+ clearInterval(this.scanTimer);
173
+ this.scanTimer = null;
174
+ }
175
+ if (this.apiClient) {
176
+ this.apiClient.stop();
177
+ this.apiClient = null;
178
+ }
179
+ for (const handler of this.handlers.values()) {
180
+ handler.stop();
181
+ }
182
+ this.handlers.clear();
183
+ console.log('Agent shutdown complete');
184
+ process.exit(0);
185
+ }
186
+ }
187
+ exports.AgentRunner = AgentRunner;
@@ -0,0 +1,28 @@
1
+ import { AgentConfig } from './types';
2
+ interface SessionHandlerOptions {
3
+ config: AgentConfig;
4
+ tmuxSession: string;
5
+ onCreateSession?: (name: string) => void;
6
+ }
7
+ export declare class TmuxSessionHandler {
8
+ private config;
9
+ private tmuxSession;
10
+ private sessionId;
11
+ private wsClient;
12
+ private onCreateSession?;
13
+ private running;
14
+ private lastScreen;
15
+ private lastForceSendTime;
16
+ private lastChangeTime;
17
+ private captureTimer;
18
+ constructor(options: SessionHandlerOptions);
19
+ start(): void;
20
+ private connectAndRun;
21
+ private handleKeys;
22
+ private startScreenCapture;
23
+ private stopScreenCapture;
24
+ stop(): void;
25
+ getSessionId(): string;
26
+ getTmuxSession(): string;
27
+ }
28
+ export {};
@@ -0,0 +1,184 @@
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.TmuxSessionHandler = void 0;
37
+ const websocket_1 = require("./websocket");
38
+ const tmux = __importStar(require("./tmux"));
39
+ // Capture intervals
40
+ const CAPTURE_INTERVAL_ACTIVE_MS = 50;
41
+ const CAPTURE_INTERVAL_IDLE_MS = 200;
42
+ const ACTIVE_THRESHOLD_MS = 2000;
43
+ const FORCE_SEND_INTERVAL_MS = 10000;
44
+ const USE_COMPRESSION = true;
45
+ const MIN_COMPRESS_SIZE = 512;
46
+ class TmuxSessionHandler {
47
+ constructor(options) {
48
+ this.wsClient = null;
49
+ this.running = false;
50
+ this.lastScreen = '';
51
+ this.lastForceSendTime = 0;
52
+ this.lastChangeTime = 0;
53
+ this.captureTimer = null;
54
+ this.config = options.config;
55
+ this.tmuxSession = options.tmuxSession;
56
+ this.sessionId = `${options.config.machineId}/${options.tmuxSession}`;
57
+ this.onCreateSession = options.onCreateSession;
58
+ }
59
+ start() {
60
+ if (this.running)
61
+ return;
62
+ this.running = true;
63
+ // Add jitter to prevent thundering herd
64
+ const jitter = Math.floor(Math.random() * 5000);
65
+ console.log(`[${this.tmuxSession}] Starting in ${jitter}ms`);
66
+ setTimeout(() => this.connectAndRun(), jitter);
67
+ }
68
+ connectAndRun() {
69
+ if (!this.running)
70
+ return;
71
+ this.wsClient = new websocket_1.RelayWebSocketClient({
72
+ url: this.config.relay,
73
+ sessionId: this.sessionId,
74
+ machineId: this.config.machineId,
75
+ token: this.config.token,
76
+ label: this.tmuxSession,
77
+ autoReconnect: true
78
+ });
79
+ this.wsClient.on('connected', () => {
80
+ console.log(`[${this.tmuxSession}] Connected to relay`);
81
+ this.startScreenCapture();
82
+ });
83
+ this.wsClient.on('disconnected', ({ code, reason }) => {
84
+ console.log(`[${this.tmuxSession}] Disconnected: code=${code}, reason=${reason}`);
85
+ this.stopScreenCapture();
86
+ });
87
+ this.wsClient.on('keys', (keys) => {
88
+ this.handleKeys(keys);
89
+ });
90
+ this.wsClient.on('resize', ({ cols, rows }) => {
91
+ console.log(`[${this.tmuxSession}] Resize: ${cols}x${rows}`);
92
+ tmux.resizeWindow(this.tmuxSession, cols, rows);
93
+ });
94
+ this.wsClient.on('createSession', (name) => {
95
+ console.log(`[${this.tmuxSession}] Create session request: ${name}`);
96
+ if (this.onCreateSession) {
97
+ this.onCreateSession(name);
98
+ }
99
+ });
100
+ this.wsClient.on('killSession', () => {
101
+ console.log(`[${this.tmuxSession}] Kill session request`);
102
+ tmux.killSession(this.tmuxSession);
103
+ this.stop();
104
+ });
105
+ this.wsClient.on('error', (error) => {
106
+ console.error(`[${this.tmuxSession}] WebSocket error:`, error.message);
107
+ });
108
+ this.wsClient.connect();
109
+ }
110
+ handleKeys(keys) {
111
+ tmux.sendKeys(this.tmuxSession, keys, false);
112
+ }
113
+ startScreenCapture() {
114
+ if (this.captureTimer)
115
+ return;
116
+ const capture = () => {
117
+ if (!this.running || !this.wsClient?.getConnected()) {
118
+ // Wait and retry
119
+ this.captureTimer = setTimeout(capture, 500);
120
+ return;
121
+ }
122
+ try {
123
+ const screen = tmux.capturePane(this.tmuxSession);
124
+ if (screen !== null) {
125
+ const now = Date.now();
126
+ const changed = screen !== this.lastScreen;
127
+ const forceTime = (now - this.lastForceSendTime) >= FORCE_SEND_INTERVAL_MS;
128
+ if (changed || forceTime) {
129
+ this.lastScreen = screen;
130
+ this.lastForceSendTime = now;
131
+ if (changed) {
132
+ this.lastChangeTime = now;
133
+ }
134
+ // Send clear screen + content
135
+ const fullOutput = '\x1b[2J\x1b[H' + screen;
136
+ const data = Buffer.from(fullOutput, 'utf-8');
137
+ // Compress if enabled and data is large enough
138
+ if (USE_COMPRESSION && data.length > MIN_COMPRESS_SIZE) {
139
+ this.wsClient.sendScreenCompressed(data);
140
+ }
141
+ else {
142
+ this.wsClient.sendScreen(data);
143
+ }
144
+ }
145
+ // Adaptive sleep: faster when active, slower when idle
146
+ const isActive = (now - this.lastChangeTime) < ACTIVE_THRESHOLD_MS;
147
+ const sleepMs = isActive ? CAPTURE_INTERVAL_ACTIVE_MS : CAPTURE_INTERVAL_IDLE_MS;
148
+ this.captureTimer = setTimeout(capture, sleepMs);
149
+ }
150
+ else {
151
+ this.captureTimer = setTimeout(capture, CAPTURE_INTERVAL_IDLE_MS);
152
+ }
153
+ }
154
+ catch (error) {
155
+ console.error(`[${this.tmuxSession}] Screen capture error:`, error);
156
+ this.captureTimer = setTimeout(capture, 500);
157
+ }
158
+ };
159
+ capture();
160
+ console.log(`[${this.tmuxSession}] Screen capture started`);
161
+ }
162
+ stopScreenCapture() {
163
+ if (this.captureTimer) {
164
+ clearTimeout(this.captureTimer);
165
+ this.captureTimer = null;
166
+ }
167
+ }
168
+ stop() {
169
+ console.log(`[${this.tmuxSession}] Stopping`);
170
+ this.running = false;
171
+ this.stopScreenCapture();
172
+ if (this.wsClient) {
173
+ this.wsClient.destroy();
174
+ this.wsClient = null;
175
+ }
176
+ }
177
+ getSessionId() {
178
+ return this.sessionId;
179
+ }
180
+ getTmuxSession() {
181
+ return this.tmuxSession;
182
+ }
183
+ }
184
+ exports.TmuxSessionHandler = TmuxSessionHandler;
@@ -0,0 +1,29 @@
1
+ import { TmuxSession } from './types';
2
+ /**
3
+ * Scan for all tmux sessions
4
+ */
5
+ export declare function scanSessions(): string[];
6
+ /**
7
+ * Get detailed session info
8
+ */
9
+ export declare function listSessions(): TmuxSession[];
10
+ /**
11
+ * Capture tmux pane content with escape sequences (colors)
12
+ */
13
+ export declare function capturePane(sessionName: string): string | null;
14
+ /**
15
+ * Send keys to tmux session
16
+ */
17
+ export declare function sendKeys(target: string, keys: string, enter?: boolean): boolean;
18
+ /**
19
+ * Resize tmux window
20
+ */
21
+ export declare function resizeWindow(sessionName: string, cols: number, rows: number): boolean;
22
+ /**
23
+ * Create new tmux session
24
+ */
25
+ export declare function createSession(sessionName: string): boolean;
26
+ /**
27
+ * Kill tmux session
28
+ */
29
+ export declare function killSession(sessionName: string): boolean;