browser-use 0.4.0 → 0.6.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 (58) hide show
  1. package/dist/agent/service.js +2 -0
  2. package/dist/agent/system_prompt.md +269 -0
  3. package/dist/agent/system_prompt_anthropic_flash.md +240 -0
  4. package/dist/agent/system_prompt_browser_use.md +18 -0
  5. package/dist/agent/system_prompt_browser_use_flash.md +15 -0
  6. package/dist/agent/system_prompt_browser_use_no_thinking.md +17 -0
  7. package/dist/agent/system_prompt_flash.md +16 -0
  8. package/dist/agent/system_prompt_flash_anthropic.md +30 -0
  9. package/dist/agent/system_prompt_no_thinking.md +245 -0
  10. package/dist/browser/cloud/index.d.ts +1 -0
  11. package/dist/browser/cloud/index.js +1 -0
  12. package/dist/browser/cloud/management.d.ts +130 -0
  13. package/dist/browser/cloud/management.js +140 -0
  14. package/dist/browser/events.d.ts +61 -3
  15. package/dist/browser/events.js +66 -0
  16. package/dist/browser/profile.d.ts +1 -0
  17. package/dist/browser/profile.js +25 -8
  18. package/dist/browser/session.d.ts +59 -2
  19. package/dist/browser/session.js +943 -131
  20. package/dist/browser/watchdogs/base.js +34 -1
  21. package/dist/browser/watchdogs/captcha-watchdog.d.ts +26 -0
  22. package/dist/browser/watchdogs/captcha-watchdog.js +151 -0
  23. package/dist/browser/watchdogs/index.d.ts +1 -0
  24. package/dist/browser/watchdogs/index.js +1 -0
  25. package/dist/browser/watchdogs/screenshot-watchdog.js +4 -3
  26. package/dist/cli.d.ts +120 -0
  27. package/dist/cli.js +1816 -4
  28. package/dist/controller/service.js +106 -362
  29. package/dist/controller/views.d.ts +9 -6
  30. package/dist/controller/views.js +8 -5
  31. package/dist/dom/dom_tree/index.js +24 -11
  32. package/dist/filesystem/file-system.js +1 -1
  33. package/dist/llm/litellm/chat.d.ts +11 -0
  34. package/dist/llm/litellm/chat.js +16 -0
  35. package/dist/llm/litellm/index.d.ts +1 -0
  36. package/dist/llm/litellm/index.js +1 -0
  37. package/dist/llm/models.js +29 -3
  38. package/dist/llm/oci-raw/chat.d.ts +64 -0
  39. package/dist/llm/oci-raw/chat.js +350 -0
  40. package/dist/llm/oci-raw/index.d.ts +2 -0
  41. package/dist/llm/oci-raw/index.js +2 -0
  42. package/dist/llm/oci-raw/serializer.d.ts +12 -0
  43. package/dist/llm/oci-raw/serializer.js +128 -0
  44. package/dist/mcp/server.d.ts +1 -0
  45. package/dist/mcp/server.js +62 -13
  46. package/dist/skill-cli/direct.d.ts +100 -0
  47. package/dist/skill-cli/direct.js +984 -0
  48. package/dist/skill-cli/index.d.ts +2 -0
  49. package/dist/skill-cli/index.js +2 -0
  50. package/dist/skill-cli/server.d.ts +2 -0
  51. package/dist/skill-cli/server.js +472 -11
  52. package/dist/skill-cli/tunnel.d.ts +61 -0
  53. package/dist/skill-cli/tunnel.js +257 -0
  54. package/dist/sync/auth.d.ts +8 -0
  55. package/dist/sync/auth.js +12 -0
  56. package/dist/utils.d.ts +1 -1
  57. package/dist/utils.js +2 -1
  58. package/package.json +22 -4
@@ -1,3 +1,5 @@
1
1
  export * from './protocol.js';
2
2
  export * from './sessions.js';
3
3
  export * from './server.js';
4
+ export * from './direct.js';
5
+ export * from './tunnel.js';
@@ -1,3 +1,5 @@
1
1
  export * from './protocol.js';
2
2
  export * from './sessions.js';
3
3
  export * from './server.js';
4
+ export * from './direct.js';
5
+ export * from './tunnel.js';
@@ -6,6 +6,8 @@ export interface SkillCliServerOptions {
6
6
  export declare class SkillCliServer {
7
7
  readonly registry: SessionRegistry;
8
8
  constructor(options?: SkillCliServerOptions);
9
+ private _require_node_by_index;
10
+ private _read_node_data;
9
11
  private _handle_browser_action;
10
12
  handle_request(request: Request | string): Promise<Response>;
11
13
  }
@@ -1,51 +1,512 @@
1
+ import { promises as fsp } from 'node:fs';
2
+ import path from 'node:path';
1
3
  import { Request, Response } from './protocol.js';
2
4
  import { SessionRegistry } from './sessions.js';
5
+ const normalizeCookieDomain = (value) => String(value ?? '')
6
+ .trim()
7
+ .replace(/^\./, '')
8
+ .toLowerCase();
9
+ const parseCookieHostname = (url) => {
10
+ const value = String(url ?? '').trim();
11
+ if (!value) {
12
+ return '';
13
+ }
14
+ try {
15
+ return new URL(value).hostname.toLowerCase();
16
+ }
17
+ catch {
18
+ return '';
19
+ }
20
+ };
21
+ const parseCookieUrl = (url) => {
22
+ const value = String(url ?? '').trim();
23
+ if (!value) {
24
+ return null;
25
+ }
26
+ try {
27
+ return new URL(value);
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ };
33
+ const cookiePathMatches = (cookiePath, urlPath) => {
34
+ const normalizedCookiePath = typeof cookiePath === 'string' && cookiePath.length > 0 ? cookiePath : '/';
35
+ if (normalizedCookiePath === '/') {
36
+ return true;
37
+ }
38
+ if (urlPath === normalizedCookiePath) {
39
+ return true;
40
+ }
41
+ return urlPath.startsWith(normalizedCookiePath.endsWith('/')
42
+ ? normalizedCookiePath
43
+ : `${normalizedCookiePath}/`);
44
+ };
45
+ const cookieMatchesUrl = (cookie, url) => {
46
+ const parsedUrl = parseCookieUrl(url);
47
+ const hostname = parsedUrl?.hostname.toLowerCase() ?? '';
48
+ const domain = normalizeCookieDomain(cookie.domain);
49
+ if (!hostname || !domain) {
50
+ return false;
51
+ }
52
+ if (!(hostname === domain ||
53
+ hostname.endsWith(`.${domain}`) ||
54
+ domain.endsWith(`.${hostname}`))) {
55
+ return false;
56
+ }
57
+ if (!cookiePathMatches(cookie.path, parsedUrl?.pathname || '/')) {
58
+ return false;
59
+ }
60
+ if (cookie.secure && parsedUrl?.protocol !== 'https:') {
61
+ return false;
62
+ }
63
+ return true;
64
+ };
65
+ const normalizeSameSite = (value) => {
66
+ const normalized = String(value ?? '')
67
+ .trim()
68
+ .toLowerCase();
69
+ if (normalized === 'strict') {
70
+ return 'Strict';
71
+ }
72
+ if (normalized === 'lax') {
73
+ return 'Lax';
74
+ }
75
+ if (normalized === 'none') {
76
+ return 'None';
77
+ }
78
+ return undefined;
79
+ };
80
+ const parseCookieExpires = (value) => {
81
+ if (value == null || value === '') {
82
+ return undefined;
83
+ }
84
+ const parsed = Number(value);
85
+ return Number.isFinite(parsed) ? parsed : undefined;
86
+ };
3
87
  export class SkillCliServer {
4
88
  registry;
5
89
  constructor(options = {}) {
6
90
  this.registry = options.registry ?? new SessionRegistry();
7
91
  }
92
+ async _require_node_by_index(browser_session, index) {
93
+ const parsedIndex = Number(index);
94
+ if (!Number.isFinite(parsedIndex)) {
95
+ throw new Error('Missing index');
96
+ }
97
+ const node = await browser_session.get_dom_element_by_index(parsedIndex);
98
+ if (!node) {
99
+ return {
100
+ error: `Element index ${parsedIndex} not found - page may have changed`,
101
+ };
102
+ }
103
+ return node;
104
+ }
105
+ async _read_node_data(browser_session, node, kind) {
106
+ if (!node?.xpath) {
107
+ throw new Error('DOM element does not include an XPath selector');
108
+ }
109
+ const page = await browser_session.get_current_page();
110
+ if (!page?.evaluate) {
111
+ throw new Error('No active page available');
112
+ }
113
+ return await page.evaluate(({ xpath, dataKind }) => {
114
+ const element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
115
+ if (!element) {
116
+ return null;
117
+ }
118
+ if (dataKind === 'text') {
119
+ return element.textContent?.trim() ?? '';
120
+ }
121
+ if (dataKind === 'value') {
122
+ return 'value' in element
123
+ ? String(element.value ?? '')
124
+ : null;
125
+ }
126
+ if (dataKind === 'attributes') {
127
+ return Object.fromEntries(Array.from(element.attributes).map((attribute) => [
128
+ attribute.name,
129
+ attribute.value,
130
+ ]));
131
+ }
132
+ if (dataKind === 'bbox') {
133
+ const rect = element.getBoundingClientRect();
134
+ return {
135
+ x: rect.x,
136
+ y: rect.y,
137
+ width: rect.width,
138
+ height: rect.height,
139
+ top: rect.top,
140
+ right: rect.right,
141
+ bottom: rect.bottom,
142
+ left: rect.left,
143
+ };
144
+ }
145
+ return null;
146
+ }, { xpath: node.xpath, dataKind: kind });
147
+ }
8
148
  async _handle_browser_action(action, sessionName, params) {
9
149
  const session = await this.registry.get_or_create_session(sessionName);
10
150
  const browser_session = session.browser_session;
11
151
  if (action === 'open') {
12
- const url = String(params.url ?? '');
152
+ let url = String(params.url ?? '').trim();
13
153
  if (!url) {
14
154
  throw new Error('Missing url');
15
155
  }
156
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)) {
157
+ url = `https://${url}`;
158
+ }
16
159
  await browser_session.navigate_to(url);
17
160
  return { url };
18
161
  }
19
162
  if (action === 'click') {
20
- const index = Number(params.index);
21
- if (!Number.isFinite(index)) {
22
- throw new Error('Missing index');
23
- }
24
- const node = await browser_session.get_dom_element_by_index(index);
25
- if (!node) {
26
- return {
27
- error: `Element index ${index} not found - page may have changed`,
28
- };
163
+ const node = await this._require_node_by_index(browser_session, params.index);
164
+ if ('error' in node) {
165
+ return node;
29
166
  }
30
167
  await browser_session._click_element_node(node);
31
- return { clicked: index };
168
+ return { clicked: Number(params.index) };
169
+ }
170
+ if (action === 'hover') {
171
+ const node = await this._require_node_by_index(browser_session, params.index);
172
+ if ('error' in node) {
173
+ return node;
174
+ }
175
+ const locator = await browser_session.get_locate_element(node);
176
+ if (!locator?.hover) {
177
+ throw new Error('Hover is not available for this element');
178
+ }
179
+ await locator.hover({ timeout: 5000 });
180
+ return { hovered: Number(params.index) };
181
+ }
182
+ if (action === 'dblclick') {
183
+ const node = await this._require_node_by_index(browser_session, params.index);
184
+ if ('error' in node) {
185
+ return node;
186
+ }
187
+ const locator = await browser_session.get_locate_element(node);
188
+ if (!locator?.dblclick) {
189
+ throw new Error('Double-click is not available for this element');
190
+ }
191
+ await locator.dblclick({ timeout: 5000 });
192
+ return { double_clicked: Number(params.index) };
193
+ }
194
+ if (action === 'rightclick') {
195
+ const node = await this._require_node_by_index(browser_session, params.index);
196
+ if ('error' in node) {
197
+ return node;
198
+ }
199
+ const locator = await browser_session.get_locate_element(node);
200
+ if (!locator?.click) {
201
+ throw new Error('Right-click is not available for this element');
202
+ }
203
+ await locator.click({ button: 'right', timeout: 5000 });
204
+ return { right_clicked: Number(params.index) };
32
205
  }
33
206
  if (action === 'type') {
34
207
  const text = String(params.text ?? '');
35
208
  await browser_session.send_keys(text);
36
209
  return { typed: text };
37
210
  }
211
+ if (action === 'input') {
212
+ const node = await this._require_node_by_index(browser_session, params.index);
213
+ if ('error' in node) {
214
+ return node;
215
+ }
216
+ const text = String(params.text ?? '');
217
+ const clear = typeof params.clear === 'boolean' ? params.clear : true;
218
+ await browser_session._input_text_element_node(node, text, { clear });
219
+ return { input: Number(params.index), text, clear };
220
+ }
38
221
  if (action === 'state') {
39
222
  const state = await browser_session.get_browser_state_with_recovery({
40
223
  include_screenshot: false,
41
224
  });
225
+ const page_info = typeof browser_session.get_page_info === 'function'
226
+ ? await browser_session.get_page_info()
227
+ : null;
42
228
  return {
43
229
  url: state.url,
44
230
  title: state.title,
45
231
  tabs: state.tabs,
232
+ page_info,
46
233
  llm_representation: state.llm_representation(),
47
234
  };
48
235
  }
236
+ if (action === 'screenshot') {
237
+ const screenshot = await browser_session.take_screenshot(Boolean(params.full));
238
+ if (!screenshot) {
239
+ throw new Error('Failed to capture screenshot');
240
+ }
241
+ const file = typeof params.file === 'string' ? params.file.trim() : '';
242
+ if (!file) {
243
+ return { screenshot };
244
+ }
245
+ const filePath = path.resolve(file);
246
+ await fsp.writeFile(filePath, Buffer.from(screenshot, 'base64'));
247
+ return { file: filePath };
248
+ }
249
+ if (action === 'wait_selector') {
250
+ const selector = String(params.selector ?? '');
251
+ if (!selector) {
252
+ throw new Error('Missing selector');
253
+ }
254
+ const timeout = Number(params.timeout ?? 5000);
255
+ await browser_session.wait_for_element(selector, timeout);
256
+ return { waited_for: 'selector', selector, timeout };
257
+ }
258
+ if (action === 'wait_text') {
259
+ const text = String(params.text ?? '');
260
+ if (!text) {
261
+ throw new Error('Missing text');
262
+ }
263
+ const timeout = Number(params.timeout ?? 5000);
264
+ const page = await browser_session.get_current_page();
265
+ if (!page?.waitForFunction) {
266
+ throw new Error('No active page available for wait_text');
267
+ }
268
+ await page.waitForFunction((needle) => document.body?.innerText?.includes(needle) ?? false, text, { timeout });
269
+ return { waited_for: 'text', text, timeout };
270
+ }
271
+ if (action === 'scroll') {
272
+ let direction = 'down';
273
+ if (typeof params.direction === 'string' &&
274
+ ['up', 'down', 'left', 'right'].includes(params.direction)) {
275
+ direction = params.direction;
276
+ }
277
+ const amount = Number(params.amount ?? 500);
278
+ await browser_session.scroll(direction, amount);
279
+ return { direction, amount };
280
+ }
281
+ if (action === 'back') {
282
+ await browser_session.go_back();
283
+ return { navigated: 'back' };
284
+ }
285
+ if (action === 'forward') {
286
+ await browser_session.go_forward();
287
+ return { navigated: 'forward' };
288
+ }
289
+ if (action === 'switch') {
290
+ const identifier = params.tab ?? params.target_id;
291
+ if (typeof identifier !== 'string' && typeof identifier !== 'number') {
292
+ throw new Error('Missing tab');
293
+ }
294
+ await browser_session.switch_to_tab(identifier);
295
+ return {
296
+ active_tab: browser_session.active_tab?.target_id ??
297
+ browser_session.active_tab?.tab_id ??
298
+ browser_session.active_tab?.page_id ??
299
+ null,
300
+ };
301
+ }
302
+ if (action === 'close_tab' || action === 'close-tab') {
303
+ const identifier = params.tab ??
304
+ params.target_id ??
305
+ browser_session.active_tab?.target_id ??
306
+ browser_session.active_tab?.page_id ??
307
+ browser_session.active_tab_index;
308
+ if (typeof identifier !== 'string' && typeof identifier !== 'number') {
309
+ throw new Error('Missing tab');
310
+ }
311
+ await browser_session.close_tab(identifier);
312
+ return { closed_tab: identifier };
313
+ }
314
+ if (action === 'keys') {
315
+ const keys = String(params.keys ?? '');
316
+ if (!keys) {
317
+ throw new Error('Missing keys');
318
+ }
319
+ await browser_session.send_keys(keys);
320
+ return { keys };
321
+ }
322
+ if (action === 'select') {
323
+ const node = await this._require_node_by_index(browser_session, params.index);
324
+ if ('error' in node) {
325
+ return node;
326
+ }
327
+ const value = String(params.value ?? '');
328
+ if (!value) {
329
+ throw new Error('Missing value');
330
+ }
331
+ const selected = await browser_session.select_dropdown_option(node, value);
332
+ return {
333
+ index: Number(params.index),
334
+ value,
335
+ selected,
336
+ };
337
+ }
338
+ if (action === 'html') {
339
+ const selector = typeof params.selector === 'string' ? params.selector.trim() : '';
340
+ if (!selector) {
341
+ return { html: await browser_session.get_page_html() };
342
+ }
343
+ const page = await browser_session.get_current_page();
344
+ if (!page?.evaluate) {
345
+ throw new Error('No active page available for html');
346
+ }
347
+ const html = await page.evaluate((targetSelector) => {
348
+ const element = document.querySelector(targetSelector);
349
+ return element ? element.outerHTML : null;
350
+ }, selector);
351
+ if (typeof html !== 'string' || html.length === 0) {
352
+ throw new Error(`No element found for selector: ${selector}`);
353
+ }
354
+ return { selector, html };
355
+ }
356
+ if (action === 'eval') {
357
+ const script = String(params.js ?? params.script ?? '').trim();
358
+ if (!script) {
359
+ throw new Error('Missing js');
360
+ }
361
+ return {
362
+ result: await browser_session.execute_javascript(script),
363
+ };
364
+ }
365
+ if (action === 'extract') {
366
+ const query = String(params.query ?? '').trim();
367
+ if (!query) {
368
+ throw new Error('Missing query');
369
+ }
370
+ return {
371
+ query,
372
+ error: 'extract requires agent mode - use: browser-use run "extract ..."',
373
+ };
374
+ }
375
+ if (action === 'get_title') {
376
+ const page = await browser_session.get_current_page?.();
377
+ if (!page?.title) {
378
+ throw new Error('No active page available for get_title');
379
+ }
380
+ return {
381
+ title: await page.title(),
382
+ };
383
+ }
384
+ if (action === 'get_html') {
385
+ const selector = typeof params.selector === 'string' ? params.selector.trim() : '';
386
+ return selector
387
+ ? await this._handle_browser_action('html', sessionName, { selector })
388
+ : await this._handle_browser_action('html', sessionName, {});
389
+ }
390
+ if (action === 'get_text' ||
391
+ action === 'get_value' ||
392
+ action === 'get_attributes' ||
393
+ action === 'get_bbox') {
394
+ const node = await this._require_node_by_index(browser_session, params.index);
395
+ if ('error' in node) {
396
+ return node;
397
+ }
398
+ const kind = action.replace('get_', '');
399
+ const value = await this._read_node_data(browser_session, node, kind);
400
+ if (value == null) {
401
+ throw new Error(`Unable to retrieve ${kind} for element`);
402
+ }
403
+ return {
404
+ index: Number(params.index),
405
+ [kind]: value,
406
+ };
407
+ }
408
+ if (action === 'cookies_get') {
409
+ const url = typeof params.url === 'string' ? params.url.trim() : '';
410
+ const allCookies = (await browser_session.get_cookies());
411
+ const cookies = url
412
+ ? allCookies.filter((cookie) => cookieMatchesUrl(cookie, url))
413
+ : allCookies;
414
+ return { cookies, count: cookies.length };
415
+ }
416
+ if (action === 'cookies_set') {
417
+ const name = String(params.name ?? '').trim();
418
+ const value = String(params.value ?? '');
419
+ if (!name) {
420
+ throw new Error('Missing cookie name');
421
+ }
422
+ if (!browser_session.browser_context?.addCookies) {
423
+ throw new Error('Browser context does not support setting cookies');
424
+ }
425
+ const currentPage = await browser_session.get_current_page?.();
426
+ const currentUrl = typeof currentPage?.url === 'function' ? currentPage.url() : '';
427
+ const cookie = {
428
+ name,
429
+ value,
430
+ url: typeof params.url === 'string' && params.url.trim().length > 0
431
+ ? params.url.trim()
432
+ : undefined,
433
+ domain: typeof params.domain === 'string' ? params.domain.trim() : undefined,
434
+ path: typeof params.path === 'string' ? params.path : '/',
435
+ secure: Boolean(params.secure),
436
+ httpOnly: Boolean(params.http_only),
437
+ sameSite: normalizeSameSite(params.same_site ?? params.sameSite),
438
+ expires: parseCookieExpires(params.expires),
439
+ };
440
+ if (!cookie.url && !cookie.domain && currentUrl) {
441
+ cookie.url = currentUrl;
442
+ }
443
+ if (!cookie.url && !cookie.domain) {
444
+ throw new Error('Provide cookie url/domain or open a page first');
445
+ }
446
+ await browser_session.browser_context.addCookies([cookie]);
447
+ return { set: name };
448
+ }
449
+ if (action === 'cookies_clear') {
450
+ if (!browser_session.browser_context?.clearCookies) {
451
+ throw new Error('Browser context does not support clearing cookies');
452
+ }
453
+ const url = typeof params.url === 'string' ? params.url.trim() : '';
454
+ if (!url) {
455
+ await browser_session.browser_context.clearCookies();
456
+ return { cleared: true };
457
+ }
458
+ const allCookies = (await browser_session.get_cookies());
459
+ const remainingCookies = allCookies.filter((cookie) => !cookieMatchesUrl(cookie, url));
460
+ const removedCount = allCookies.length - remainingCookies.length;
461
+ await browser_session.browser_context.clearCookies();
462
+ if (remainingCookies.length > 0 &&
463
+ browser_session.browser_context.addCookies) {
464
+ await browser_session.browser_context.addCookies(remainingCookies);
465
+ }
466
+ return { cleared: true, url, count: removedCount };
467
+ }
468
+ if (action === 'cookies_export') {
469
+ const file = String(params.file ?? '').trim();
470
+ if (!file) {
471
+ throw new Error('Missing file');
472
+ }
473
+ const url = typeof params.url === 'string' ? params.url.trim() : '';
474
+ const allCookies = (await browser_session.get_cookies());
475
+ const cookies = url
476
+ ? allCookies.filter((cookie) => cookieMatchesUrl(cookie, url))
477
+ : allCookies;
478
+ const filePath = path.resolve(file);
479
+ await fsp.writeFile(filePath, JSON.stringify(cookies, null, 2));
480
+ return { file: filePath, count: cookies.length };
481
+ }
482
+ if (action === 'cookies_import') {
483
+ const file = String(params.file ?? '').trim();
484
+ if (!file) {
485
+ throw new Error('Missing file');
486
+ }
487
+ if (!browser_session.browser_context?.addCookies) {
488
+ throw new Error('Browser context does not support importing cookies');
489
+ }
490
+ const filePath = path.resolve(file);
491
+ const raw = await fsp.readFile(filePath, 'utf8');
492
+ const cookies = JSON.parse(raw);
493
+ if (!Array.isArray(cookies)) {
494
+ throw new Error('Cookie import file must contain a JSON array');
495
+ }
496
+ const importedCookies = cookies.map((cookie) => {
497
+ if (!cookie || typeof cookie !== 'object') {
498
+ throw new Error('Each imported cookie must be a JSON object');
499
+ }
500
+ const typedCookie = cookie;
501
+ if (typeof typedCookie.name !== 'string' ||
502
+ typeof typedCookie.value !== 'string') {
503
+ throw new Error('Each imported cookie must include string name/value');
504
+ }
505
+ return typedCookie;
506
+ });
507
+ await browser_session.browser_context.addCookies(importedCookies);
508
+ return { file: filePath, imported: importedCookies.length };
509
+ }
49
510
  if (action === 'close') {
50
511
  await this.registry.close_session(sessionName);
51
512
  return { closed: sessionName };
@@ -0,0 +1,61 @@
1
+ import { spawn } from 'node:child_process';
2
+ export type TunnelStatus = {
3
+ available: boolean;
4
+ source: 'system' | null;
5
+ path: string | null;
6
+ note: string;
7
+ };
8
+ export type StartTunnelResult = {
9
+ url: string;
10
+ port: number;
11
+ existing?: boolean;
12
+ } | {
13
+ error: string;
14
+ };
15
+ export type ListTunnelsResult = {
16
+ tunnels: Array<{
17
+ port: number;
18
+ url: string;
19
+ }>;
20
+ count: number;
21
+ };
22
+ export type StopTunnelResult = {
23
+ stopped: number;
24
+ url: string;
25
+ } | {
26
+ error: string;
27
+ };
28
+ export type StopAllTunnelsResult = {
29
+ stopped: number[];
30
+ count: number;
31
+ };
32
+ export interface TunnelManagerOptions {
33
+ tunnel_dir?: string;
34
+ binary_resolver?: (binary: string) => string | null;
35
+ spawn_impl?: typeof spawn;
36
+ sleep_impl?: (ms: number) => Promise<void>;
37
+ is_process_alive?: (pid: number) => boolean;
38
+ kill_process?: (pid: number) => Promise<boolean>;
39
+ }
40
+ export declare class TunnelManager {
41
+ private readonly tunnel_dir;
42
+ private readonly binary_resolver;
43
+ private readonly spawn_impl;
44
+ private readonly sleep_impl;
45
+ private readonly is_process_alive_impl;
46
+ private readonly kill_process_impl;
47
+ private binary_path;
48
+ constructor(options?: TunnelManagerOptions);
49
+ private get_tunnel_file;
50
+ private get_tunnel_log_file;
51
+ private save_tunnel_info;
52
+ private load_tunnel_info;
53
+ get_binary_path(): string;
54
+ is_available(): boolean;
55
+ get_status(): TunnelStatus;
56
+ start_tunnel(port: number): Promise<StartTunnelResult>;
57
+ list_tunnels(): ListTunnelsResult;
58
+ stop_tunnel(port: number): Promise<StopTunnelResult>;
59
+ stop_all_tunnels(): Promise<StopAllTunnelsResult>;
60
+ }
61
+ export declare const get_tunnel_manager: () => TunnelManager;