sessioncast-cli 2.2.2 → 2.3.1

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.
@@ -32,12 +32,16 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.AgentRunner = void 0;
37
40
  const fs = __importStar(require("fs"));
38
41
  const path = __importStar(require("path"));
39
42
  const os = __importStar(require("os"));
40
43
  const yaml = __importStar(require("js-yaml"));
44
+ const node_fetch_1 = __importDefault(require("node-fetch"));
41
45
  const session_handler_1 = require("./session-handler");
42
46
  const api_client_1 = require("./api-client");
43
47
  const tmux = __importStar(require("./tmux"));
@@ -52,7 +56,29 @@ class AgentRunner {
52
56
  this.running = false;
53
57
  this.config = config;
54
58
  }
55
- static loadConfig(configPath) {
59
+ /**
60
+ * Fetch optimal relay URL from Platform API using agent token.
61
+ * Returns null on failure (caller should fall back to default).
62
+ */
63
+ static async fetchRelayUrlForAgent(token) {
64
+ try {
65
+ const apiUrl = (0, config_1.getApiUrl)();
66
+ const controller = new AbortController();
67
+ const timeout = setTimeout(() => controller.abort(), 3000);
68
+ const res = await (0, node_fetch_1.default)(`${apiUrl}/public/agent-tokens/${token}/relay`, {
69
+ signal: controller.signal,
70
+ });
71
+ clearTimeout(timeout);
72
+ if (!res.ok)
73
+ return null;
74
+ const data = await res.json();
75
+ return data.relayUrl || null;
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ static async loadConfig(configPath) {
56
82
  // Check if agent token is available (for relay connection)
57
83
  const agentToken = (0, config_1.getAgentToken)();
58
84
  // Check environment variable
@@ -75,9 +101,18 @@ class AgentRunner {
75
101
  if ((!finalPath || !fs.existsSync(finalPath)) && agentToken) {
76
102
  console.log('Using OAuth authentication');
77
103
  const machineId = os.hostname();
104
+ // Try to fetch optimal relay URL from Platform API
105
+ let relayUrl = await AgentRunner.fetchRelayUrlForAgent(agentToken);
106
+ if (relayUrl) {
107
+ console.log(`Fetched relay URL from Platform API: ${relayUrl}`);
108
+ }
109
+ else {
110
+ relayUrl = (0, config_1.getRelayUrl)();
111
+ console.log(`Using default relay URL: ${relayUrl}`);
112
+ }
78
113
  return {
79
114
  machineId,
80
- relay: (0, config_1.getRelayUrl)(),
115
+ relay: relayUrl,
81
116
  token: agentToken,
82
117
  api: {
83
118
  enabled: false
@@ -101,10 +136,23 @@ class AgentRunner {
101
136
  if (this.running)
102
137
  return;
103
138
  this.running = true;
139
+ // Fetch encryption key from Platform API if not already set
140
+ if (!this.config.encKey && this.config.token) {
141
+ try {
142
+ const encKey = await this.fetchEncKey(this.config.token);
143
+ if (encKey) {
144
+ this.config.encKey = encKey;
145
+ }
146
+ }
147
+ catch (e) {
148
+ console.warn('Failed to fetch encryption key (E2E disabled):', e.message);
149
+ }
150
+ }
104
151
  console.log('Starting SessionCast Agent...');
105
152
  console.log(`Machine ID: ${this.config.machineId}`);
106
153
  console.log(`Relay: ${this.config.relay}`);
107
154
  console.log(`Token: ${this.config.token ? 'present' : 'none'}`);
155
+ console.log(`E2E Encryption: ${this.config.encKey ? 'enabled' : 'disabled'}`);
108
156
  // Check tmux/itmux availability before starting
109
157
  if (!tmux.isAvailable()) {
110
158
  const platform = os.platform();
@@ -220,6 +268,16 @@ class AgentRunner {
220
268
  console.error(`Failed to create tmux session: ${sanitized}`);
221
269
  }
222
270
  }
271
+ async fetchEncKey(agentToken) {
272
+ const fetch = (await Promise.resolve().then(() => __importStar(require('node-fetch')))).default;
273
+ const apiUrl = (0, config_1.getApiUrl)();
274
+ const url = `${apiUrl}/internal/agents/enc-key?token=${encodeURIComponent(agentToken)}`;
275
+ const res = await fetch(url, { timeout: 5000 });
276
+ if (!res.ok)
277
+ return null;
278
+ const data = await res.json();
279
+ return data.encKey || null;
280
+ }
223
281
  stop() {
224
282
  console.log('Shutting down Agent...');
225
283
  this.running = false;
@@ -89,7 +89,8 @@ class TmuxSessionHandler {
89
89
  machineId: this.config.machineId,
90
90
  token: this.config.token,
91
91
  label: this.tmuxSession,
92
- autoReconnect: true
92
+ autoReconnect: true,
93
+ encKey: this.config.encKey
93
94
  });
94
95
  this.wsClient.on('connected', () => {
95
96
  console.log(`[${this.tmuxSession}] Connected to relay`);
@@ -0,0 +1,12 @@
1
+ export declare class TunnelHandler {
2
+ private port;
3
+ private host;
4
+ private activeRequests;
5
+ private readonly MAX_CONCURRENT;
6
+ private readonly MAX_RESPONSE_SIZE;
7
+ private readonly CHUNK_SIZE;
8
+ constructor(port: number, host?: string);
9
+ handleRequest(requestId: string, method: string, path: string, queryString: string, headers: Record<string, string>, body: string | undefined, sendResponse: (msg: any) => void): Promise<void>;
10
+ private getBase64ChunkSize;
11
+ private makeHttpRequest;
12
+ }
@@ -0,0 +1,180 @@
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.TunnelHandler = void 0;
37
+ const http = __importStar(require("http"));
38
+ const debug_1 = require("./debug");
39
+ const ALLOWED_HOSTS = ['localhost', '127.0.0.1', '::1'];
40
+ class TunnelHandler {
41
+ constructor(port, host = 'localhost') {
42
+ this.activeRequests = 0;
43
+ this.MAX_CONCURRENT = 10;
44
+ this.MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5MB
45
+ this.CHUNK_SIZE = 200 * 1024; // 200KB before base64
46
+ this.port = port;
47
+ this.host = host;
48
+ }
49
+ async handleRequest(requestId, method, path, queryString, headers, body, sendResponse) {
50
+ // Check concurrent limit
51
+ if (this.activeRequests >= this.MAX_CONCURRENT) {
52
+ sendResponse({
53
+ requestId,
54
+ statusCode: 503,
55
+ headers: { 'content-type': 'text/plain' },
56
+ body: Buffer.from('Too many concurrent requests').toString('base64'),
57
+ });
58
+ return;
59
+ }
60
+ // Validate host (SSRF prevention)
61
+ if (!ALLOWED_HOSTS.includes(this.host)) {
62
+ sendResponse({
63
+ requestId,
64
+ statusCode: 403,
65
+ headers: { 'content-type': 'text/plain' },
66
+ body: Buffer.from('Forbidden: only localhost connections allowed').toString('base64'),
67
+ });
68
+ return;
69
+ }
70
+ this.activeRequests++;
71
+ try {
72
+ const fullPath = queryString ? `${path}?${queryString}` : path;
73
+ const requestBody = body ? Buffer.from(body, 'base64') : undefined;
74
+ (0, debug_1.debugLog)('TUNNEL', `${method} ${fullPath} -> ${this.host}:${this.port}`);
75
+ const { statusCode, responseHeaders, responseBody } = await this.makeHttpRequest(method, fullPath, headers, requestBody);
76
+ const bodyBase64 = responseBody.toString('base64');
77
+ if (responseBody.length > this.CHUNK_SIZE) {
78
+ // Split into chunks
79
+ const totalChunks = Math.ceil(bodyBase64.length / this.getBase64ChunkSize());
80
+ for (let i = 0; i < totalChunks; i++) {
81
+ const start = i * this.getBase64ChunkSize();
82
+ const end = Math.min(start + this.getBase64ChunkSize(), bodyBase64.length);
83
+ const chunk = bodyBase64.slice(start, end);
84
+ const isFinal = i === totalChunks - 1;
85
+ sendResponse({
86
+ requestId,
87
+ statusCode,
88
+ headers: responseHeaders,
89
+ body: chunk,
90
+ chunkIndex: i,
91
+ isFinal,
92
+ chunked: true,
93
+ });
94
+ }
95
+ }
96
+ else {
97
+ sendResponse({
98
+ requestId,
99
+ statusCode,
100
+ headers: responseHeaders,
101
+ body: bodyBase64,
102
+ });
103
+ }
104
+ }
105
+ catch (error) {
106
+ (0, debug_1.debugLog)('TUNNEL', `Error: ${error.message}`);
107
+ sendResponse({
108
+ requestId,
109
+ statusCode: 502,
110
+ headers: { 'content-type': 'text/plain' },
111
+ body: Buffer.from(`Bad Gateway: ${error.message}`).toString('base64'),
112
+ });
113
+ }
114
+ finally {
115
+ this.activeRequests--;
116
+ }
117
+ }
118
+ getBase64ChunkSize() {
119
+ // base64 encoding expands data by ~4/3, so chunk size in base64 chars
120
+ // for 200KB raw data is approximately 200*1024*4/3 ≈ 273KB base64
121
+ return Math.ceil(this.CHUNK_SIZE * 4 / 3);
122
+ }
123
+ makeHttpRequest(method, path, headers, body) {
124
+ return new Promise((resolve, reject) => {
125
+ // Build headers, removing hop-by-hop headers that shouldn't be forwarded
126
+ const forwardHeaders = {
127
+ ...headers,
128
+ host: `${this.host}:${this.port}`,
129
+ };
130
+ delete forwardHeaders['connection'];
131
+ delete forwardHeaders['keep-alive'];
132
+ delete forwardHeaders['transfer-encoding'];
133
+ const options = {
134
+ hostname: this.host,
135
+ port: this.port,
136
+ path,
137
+ method,
138
+ headers: forwardHeaders,
139
+ timeout: 30000,
140
+ };
141
+ const req = http.request(options, (res) => {
142
+ const chunks = [];
143
+ let totalSize = 0;
144
+ res.on('data', (chunk) => {
145
+ totalSize += chunk.length;
146
+ if (totalSize > this.MAX_RESPONSE_SIZE) {
147
+ req.destroy();
148
+ reject(new Error(`Response exceeds maximum size of ${this.MAX_RESPONSE_SIZE} bytes`));
149
+ return;
150
+ }
151
+ chunks.push(chunk);
152
+ });
153
+ res.on('end', () => {
154
+ const responseHeaders = {};
155
+ for (const [key, value] of Object.entries(res.headers)) {
156
+ if (value) {
157
+ responseHeaders[key] = Array.isArray(value) ? value.join(', ') : value;
158
+ }
159
+ }
160
+ resolve({
161
+ statusCode: res.statusCode || 502,
162
+ responseHeaders,
163
+ responseBody: Buffer.concat(chunks),
164
+ });
165
+ });
166
+ res.on('error', reject);
167
+ });
168
+ req.on('error', reject);
169
+ req.on('timeout', () => {
170
+ req.destroy();
171
+ reject(new Error('Request timed out'));
172
+ });
173
+ if (body) {
174
+ req.write(body);
175
+ }
176
+ req.end();
177
+ });
178
+ }
179
+ }
180
+ exports.TunnelHandler = TunnelHandler;
@@ -2,6 +2,7 @@ export interface AgentConfig {
2
2
  machineId: string;
3
3
  relay: string;
4
4
  token: string;
5
+ encKey?: string;
5
6
  api?: ApiConfig;
6
7
  }
7
8
  export interface ApiConfig {
@@ -34,7 +35,7 @@ export interface ExecConfig {
34
35
  }
35
36
  export interface LlmConfig {
36
37
  enabled: boolean;
37
- provider: 'ollama' | 'openai' | 'anthropic' | 'claude-code';
38
+ provider: 'ollama' | 'openai' | 'anthropic' | 'claude-code' | 'codex' | 'gemini' | 'cursor';
38
39
  baseUrl: string;
39
40
  model: string;
40
41
  apiKey?: string;
@@ -64,6 +65,26 @@ export interface ExecResult {
64
65
  stderr: string;
65
66
  duration: number;
66
67
  }
68
+ export interface TunnelConfig {
69
+ chromePath?: string;
70
+ width?: number;
71
+ height?: number;
72
+ cdpPort?: number;
73
+ }
74
+ export interface WebInputEvent {
75
+ inputType?: string;
76
+ type?: string;
77
+ x?: number;
78
+ y?: number;
79
+ button?: string;
80
+ clickCount?: number;
81
+ key?: string;
82
+ code?: string;
83
+ text?: string;
84
+ modifiers?: number;
85
+ deltaX?: number;
86
+ deltaY?: number;
87
+ }
67
88
  export interface LlmMessage {
68
89
  role: 'system' | 'user' | 'assistant';
69
90
  content: string;
@@ -7,6 +7,8 @@ interface WebSocketClientOptions {
7
7
  token: string;
8
8
  label?: string;
9
9
  autoReconnect?: boolean;
10
+ encKey?: string;
11
+ skipAutoRegister?: boolean;
10
12
  }
11
13
  export declare class RelayWebSocketClient extends EventEmitter {
12
14
  private ws;
@@ -16,6 +18,8 @@ export declare class RelayWebSocketClient extends EventEmitter {
16
18
  private token;
17
19
  private label;
18
20
  private autoReconnect;
21
+ private encKeyBuf;
22
+ private skipAutoRegister;
19
23
  private isConnected;
20
24
  private reconnectAttempts;
21
25
  private circuitBreakerOpen;
@@ -25,12 +29,14 @@ export declare class RelayWebSocketClient extends EventEmitter {
25
29
  constructor(options: WebSocketClientOptions);
26
30
  connect(): void;
27
31
  private registerAsHost;
32
+ private decryptPayload;
28
33
  private handleMessage;
29
34
  private handleError;
30
35
  private scheduleReconnect;
31
36
  send(message: Message): boolean;
32
37
  sendScreen(data: Buffer): boolean;
33
38
  sendScreenWithMeta(data: Buffer, meta: Record<string, string>): boolean;
39
+ private compressAndEncrypt;
34
40
  sendScreenCompressed(data: Buffer): boolean;
35
41
  sendScreenCompressedWithMeta(data: Buffer, meta: Record<string, string>): boolean;
36
42
  sendSessionMeta(meta: Record<string, string>): boolean;
@@ -60,6 +66,18 @@ export declare class RelayWebSocketClient extends EventEmitter {
60
66
  * Send upload error notification to web viewer
61
67
  */
62
68
  sendUploadError(filename: string, error: string): boolean;
69
+ /**
70
+ * Send a single rrweb DOM event to the relay.
71
+ */
72
+ sendWebDom(event: any): boolean;
73
+ /**
74
+ * Send a batch of rrweb DOM events.
75
+ */
76
+ sendWebDomBatch(events: any[]): boolean;
77
+ /**
78
+ * Send web page metadata (title, URL, viewport).
79
+ */
80
+ sendWebMeta(meta: Record<string, string>): boolean;
63
81
  getConnected(): boolean;
64
82
  destroy(): void;
65
83
  }
@@ -40,6 +40,8 @@ exports.RelayWebSocketClient = void 0;
40
40
  const ws_1 = __importDefault(require("ws"));
41
41
  const events_1 = require("events");
42
42
  const zlib = __importStar(require("zlib"));
43
+ const zstd = __importStar(require("zstd-napi"));
44
+ const crypto_1 = require("./crypto");
43
45
  const debug_1 = require("./debug");
44
46
  const MAX_RECONNECT_ATTEMPTS = 5;
45
47
  const BASE_RECONNECT_DELAY_MS = 2000;
@@ -49,6 +51,7 @@ class RelayWebSocketClient extends events_1.EventEmitter {
49
51
  constructor(options) {
50
52
  super();
51
53
  this.ws = null;
54
+ this.encKeyBuf = null;
52
55
  this.isConnected = false;
53
56
  this.reconnectAttempts = 0;
54
57
  this.circuitBreakerOpen = false;
@@ -61,6 +64,10 @@ class RelayWebSocketClient extends events_1.EventEmitter {
61
64
  this.token = options.token;
62
65
  this.label = options.label || options.sessionId;
63
66
  this.autoReconnect = options.autoReconnect ?? true;
67
+ if (options.encKey) {
68
+ this.encKeyBuf = Buffer.from(options.encKey, 'base64url');
69
+ }
70
+ this.skipAutoRegister = options.skipAutoRegister ?? false;
64
71
  }
65
72
  connect() {
66
73
  if (this.destroyed)
@@ -72,7 +79,9 @@ class RelayWebSocketClient extends events_1.EventEmitter {
72
79
  this.reconnectAttempts = 0;
73
80
  this.circuitBreakerOpen = false;
74
81
  this.emit('connected');
75
- this.registerAsHost();
82
+ if (!this.skipAutoRegister) {
83
+ this.registerAsHost();
84
+ }
76
85
  });
77
86
  this.ws.on('message', (data) => {
78
87
  try {
@@ -116,9 +125,28 @@ class RelayWebSocketClient extends events_1.EventEmitter {
116
125
  meta
117
126
  });
118
127
  }
128
+ decryptPayload(payload) {
129
+ if (!this.encKeyBuf)
130
+ return payload;
131
+ try {
132
+ const buf = Buffer.from(payload, 'base64');
133
+ const decrypted = (0, crypto_1.decrypt)(buf, this.encKeyBuf);
134
+ return decrypted.toString('utf-8');
135
+ }
136
+ catch {
137
+ // If decryption fails, return as-is (unencrypted message)
138
+ return payload;
139
+ }
140
+ }
119
141
  handleMessage(message) {
120
142
  (0, debug_1.debugLog)('WS:IN', message.type, message.session);
121
143
  switch (message.type) {
144
+ case 'keysEnc':
145
+ if (message.session === this.sessionId && message.payload) {
146
+ const keys = this.decryptPayload(message.payload);
147
+ this.emit('keys', keys, message.meta?.pane);
148
+ }
149
+ break;
122
150
  case 'keys':
123
151
  if (message.session === this.sessionId && message.payload) {
124
152
  this.emit('keys', message.payload, message.meta?.pane);
@@ -148,6 +176,25 @@ class RelayWebSocketClient extends events_1.EventEmitter {
148
176
  this.emit('createWorktree', message.meta.branch);
149
177
  }
150
178
  break;
179
+ case 'webInput':
180
+ if (message.session === this.sessionId && message.payload) {
181
+ try {
182
+ const inputEvent = JSON.parse(message.payload);
183
+ this.emit('webInput', inputEvent);
184
+ }
185
+ catch { /* ignore parse errors */ }
186
+ }
187
+ break;
188
+ case 'webNavigate':
189
+ if (message.session === this.sessionId && message.meta?.url) {
190
+ this.emit('webNavigate', message.meta.url);
191
+ }
192
+ break;
193
+ case 'requestSnapshot':
194
+ if (message.session === this.sessionId) {
195
+ this.emit('requestSnapshot');
196
+ }
197
+ break;
151
198
  case 'requestFileView':
152
199
  if (message.session === this.sessionId && message.meta?.filePath) {
153
200
  this.emit('requestFileView', message.meta.filePath);
@@ -268,17 +315,37 @@ class RelayWebSocketClient extends events_1.EventEmitter {
268
315
  meta
269
316
  });
270
317
  }
318
+ compressAndEncrypt(data) {
319
+ // Step 1: Compress with zstd (fallback to gzip)
320
+ let compressed;
321
+ let compressType = 'zstd';
322
+ try {
323
+ compressed = zstd.compress(data, { compressionLevel: 3 });
324
+ }
325
+ catch {
326
+ compressed = zlib.gzipSync(data);
327
+ compressType = 'gz';
328
+ }
329
+ // Step 2: Encrypt if encKey is available
330
+ if (this.encKeyBuf) {
331
+ const encrypted = (0, crypto_1.encrypt)(compressed, this.encKeyBuf);
332
+ return {
333
+ payload: encrypted.toString('base64'),
334
+ type: 'screenEnc'
335
+ };
336
+ }
337
+ // No encryption — send compressed only
338
+ return {
339
+ payload: compressed.toString('base64'),
340
+ type: compressType === 'zstd' ? 'screenZstd' : 'screenGz'
341
+ };
342
+ }
271
343
  sendScreenCompressed(data) {
272
344
  if (!this.isConnected)
273
345
  return false;
274
346
  try {
275
- const compressed = zlib.gzipSync(data);
276
- const base64Data = compressed.toString('base64');
277
- return this.send({
278
- type: 'screenGz',
279
- session: this.sessionId,
280
- payload: base64Data
281
- });
347
+ const { payload, type } = this.compressAndEncrypt(data);
348
+ return this.send({ type, session: this.sessionId, payload });
282
349
  }
283
350
  catch {
284
351
  return this.sendScreen(data);
@@ -288,13 +355,8 @@ class RelayWebSocketClient extends events_1.EventEmitter {
288
355
  if (!this.isConnected)
289
356
  return false;
290
357
  try {
291
- const compressed = zlib.gzipSync(data);
292
- return this.send({
293
- type: 'screenGz',
294
- session: this.sessionId,
295
- payload: compressed.toString('base64'),
296
- meta
297
- });
358
+ const { payload, type } = this.compressAndEncrypt(data);
359
+ return this.send({ type, session: this.sessionId, payload, meta });
298
360
  }
299
361
  catch {
300
362
  return this.sendScreenWithMeta(data, meta);
@@ -370,6 +432,42 @@ class RelayWebSocketClient extends events_1.EventEmitter {
370
432
  }
371
433
  });
372
434
  }
435
+ /**
436
+ * Send a single rrweb DOM event to the relay.
437
+ */
438
+ sendWebDom(event) {
439
+ if (!this.isConnected)
440
+ return false;
441
+ return this.send({
442
+ type: 'webDom',
443
+ session: this.sessionId,
444
+ payload: JSON.stringify(event),
445
+ });
446
+ }
447
+ /**
448
+ * Send a batch of rrweb DOM events.
449
+ */
450
+ sendWebDomBatch(events) {
451
+ if (!this.isConnected)
452
+ return false;
453
+ return this.send({
454
+ type: 'webDomBatch',
455
+ session: this.sessionId,
456
+ payload: JSON.stringify(events),
457
+ });
458
+ }
459
+ /**
460
+ * Send web page metadata (title, URL, viewport).
461
+ */
462
+ sendWebMeta(meta) {
463
+ if (!this.isConnected)
464
+ return false;
465
+ return this.send({
466
+ type: 'webMeta',
467
+ session: this.sessionId,
468
+ meta,
469
+ });
470
+ }
373
471
  getConnected() {
374
472
  return this.isConnected;
375
473
  }
@@ -14,7 +14,7 @@ async function startAgent(options) {
14
14
  (0, debug_1.setDebug)(true);
15
15
  console.log(chalk_1.default.yellow('[DEBUG] Debug mode enabled'));
16
16
  }
17
- const config = runner_1.AgentRunner.loadConfig(options.config);
17
+ const config = await runner_1.AgentRunner.loadConfig(options.config);
18
18
  const runner = new runner_1.AgentRunner(config);
19
19
  await runner.start();
20
20
  }
@@ -0,0 +1,10 @@
1
+ interface TunnelOptions {
2
+ config?: string;
3
+ debug?: boolean;
4
+ width?: number;
5
+ height?: number;
6
+ chromePath?: string;
7
+ cdpPort?: number;
8
+ }
9
+ export declare function startTunnel(url: string, options: TunnelOptions): Promise<void>;
10
+ export {};