browser-use 0.6.1 → 0.7.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.
Files changed (84) hide show
  1. package/README.md +24 -18
  2. package/dist/actor/element.js +24 -3
  3. package/dist/actor/mouse.js +21 -3
  4. package/dist/actor/page.js +33 -11
  5. package/dist/agent/gif.js +28 -3
  6. package/dist/agent/message-manager/service.js +2 -22
  7. package/dist/agent/message-manager/utils.js +15 -2
  8. package/dist/agent/message-manager/views.d.ts +7 -7
  9. package/dist/agent/message-manager/views.js +1 -0
  10. package/dist/agent/prompts.d.ts +3 -0
  11. package/dist/agent/prompts.js +22 -12
  12. package/dist/agent/service.d.ts +9 -1
  13. package/dist/agent/service.js +204 -79
  14. package/dist/agent/system_prompt.md +12 -11
  15. package/dist/agent/system_prompt_anthropic_flash.md +6 -5
  16. package/dist/agent/system_prompt_no_thinking.md +12 -11
  17. package/dist/agent/views.d.ts +2 -0
  18. package/dist/agent/views.js +48 -36
  19. package/dist/browser/extensions.js +20 -10
  20. package/dist/browser/profile.d.ts +4 -0
  21. package/dist/browser/profile.js +107 -4
  22. package/dist/browser/session.d.ts +28 -1
  23. package/dist/browser/session.js +1436 -528
  24. package/dist/browser/watchdogs/default-action-watchdog.js +32 -3
  25. package/dist/browser/watchdogs/downloads-watchdog.d.ts +4 -0
  26. package/dist/browser/watchdogs/downloads-watchdog.js +105 -9
  27. package/dist/browser/watchdogs/har-recording-watchdog.d.ts +1 -0
  28. package/dist/browser/watchdogs/har-recording-watchdog.js +54 -2
  29. package/dist/browser/watchdogs/permissions-watchdog.d.ts +5 -0
  30. package/dist/browser/watchdogs/permissions-watchdog.js +106 -3
  31. package/dist/browser/watchdogs/recording-watchdog.d.ts +2 -0
  32. package/dist/browser/watchdogs/recording-watchdog.js +54 -2
  33. package/dist/browser/watchdogs/security-watchdog.d.ts +1 -0
  34. package/dist/browser/watchdogs/security-watchdog.js +47 -7
  35. package/dist/browser/watchdogs/storage-state-watchdog.d.ts +6 -0
  36. package/dist/browser/watchdogs/storage-state-watchdog.js +206 -14
  37. package/dist/cli.d.ts +13 -2
  38. package/dist/cli.js +190 -9
  39. package/dist/code-use/namespace.js +52 -7
  40. package/dist/code-use/notebook-export.js +18 -2
  41. package/dist/code-use/service.js +1 -0
  42. package/dist/config.js +26 -4
  43. package/dist/controller/action-timeout.d.ts +9 -0
  44. package/dist/controller/action-timeout.js +95 -0
  45. package/dist/controller/registry/service.d.ts +1 -0
  46. package/dist/controller/registry/service.js +28 -1
  47. package/dist/controller/service.d.ts +2 -1
  48. package/dist/controller/service.js +494 -329
  49. package/dist/entrypoint.d.ts +1 -0
  50. package/dist/entrypoint.js +27 -0
  51. package/dist/filesystem/file-system.js +38 -8
  52. package/dist/integrations/gmail/service.js +30 -6
  53. package/dist/llm/browser-use/chat.js +2 -2
  54. package/dist/llm/codex/auth.d.ts +118 -0
  55. package/dist/llm/codex/auth.js +599 -0
  56. package/dist/llm/codex/chat.d.ts +70 -0
  57. package/dist/llm/codex/chat.js +392 -0
  58. package/dist/llm/codex/index.d.ts +2 -0
  59. package/dist/llm/codex/index.js +2 -0
  60. package/dist/llm/google/chat.js +18 -1
  61. package/dist/logging-config.js +22 -11
  62. package/dist/mcp/client.d.ts +1 -0
  63. package/dist/mcp/client.js +12 -10
  64. package/dist/mcp/redaction.d.ts +3 -0
  65. package/dist/mcp/redaction.js +132 -0
  66. package/dist/mcp/server.d.ts +2 -0
  67. package/dist/mcp/server.js +64 -22
  68. package/dist/screenshots/service.js +25 -2
  69. package/dist/skill-cli/direct.d.ts +4 -1
  70. package/dist/skill-cli/direct.js +263 -66
  71. package/dist/skill-cli/server.d.ts +1 -0
  72. package/dist/skill-cli/server.js +115 -25
  73. package/dist/skill-cli/tunnel.d.ts +1 -0
  74. package/dist/skill-cli/tunnel.js +16 -4
  75. package/dist/sync/auth.js +22 -9
  76. package/dist/telemetry/service.js +21 -2
  77. package/dist/telemetry/views.js +31 -8
  78. package/dist/tokens/custom-pricing.js +2 -2
  79. package/dist/tokens/openrouter-pricing.d.ts +11 -0
  80. package/dist/tokens/openrouter-pricing.js +102 -0
  81. package/dist/tokens/service.js +20 -16
  82. package/dist/utils.d.ts +3 -1
  83. package/dist/utils.js +3 -1
  84. package/package.json +68 -27
@@ -6,7 +6,45 @@ import net from 'node:net';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { BrowserSession, systemChrome } from '../browser/session.js';
8
8
  import { CloudBrowserClient } from '../browser/cloud/cloud.js';
9
- export const DIRECT_STATE_FILE = path.join(os.tmpdir(), 'browser-use-direct.json');
9
+ import { isMainModule } from '../entrypoint.js';
10
+ const getSafeUserSegment = () => {
11
+ if (typeof process.getuid === 'function') {
12
+ return String(process.getuid());
13
+ }
14
+ try {
15
+ const username = os.userInfo().username;
16
+ return username.replace(/[^a-zA-Z0-9_.-]/g, '_') || 'user';
17
+ }
18
+ catch {
19
+ return 'user';
20
+ }
21
+ };
22
+ const getDefaultDirectStateDir = () => {
23
+ const runtimeDir = process.env.XDG_RUNTIME_DIR?.trim();
24
+ if (runtimeDir && path.isAbsolute(runtimeDir)) {
25
+ return path.join(runtimeDir, 'browser-use');
26
+ }
27
+ const homeDir = os.homedir();
28
+ if (homeDir) {
29
+ if (process.platform === 'darwin') {
30
+ return path.join(homeDir, 'Library', 'Application Support', 'browser-use');
31
+ }
32
+ if (process.platform === 'win32') {
33
+ const localAppData = process.env.LOCALAPPDATA?.trim();
34
+ const baseDir = localAppData && path.isAbsolute(localAppData)
35
+ ? localAppData
36
+ : path.join(homeDir, 'AppData', 'Local');
37
+ return path.join(baseDir, 'browser-use');
38
+ }
39
+ const stateHome = process.env.XDG_STATE_HOME?.trim();
40
+ const baseDir = stateHome && path.isAbsolute(stateHome)
41
+ ? stateHome
42
+ : path.join(homeDir, '.local', 'state');
43
+ return path.join(baseDir, 'browser-use');
44
+ }
45
+ return path.join(os.tmpdir(), `browser-use-${getSafeUserSegment()}`);
46
+ };
47
+ export const DIRECT_STATE_FILE = path.join(getDefaultDirectStateDir(), 'direct-state.json');
10
48
  const normalizeCookieDomain = (value) => String(value ?? '')
11
49
  .trim()
12
50
  .replace(/^\./, '')
@@ -23,6 +61,9 @@ const parseCookieHostname = (url) => {
23
61
  return '';
24
62
  }
25
63
  };
64
+ const validateDirectPageAfterAction = async (session, page) => {
65
+ await session.validate_page_after_action?.(page);
66
+ };
26
67
  const parseCookieUrl = (url) => {
27
68
  const value = String(url ?? '').trim();
28
69
  if (!value) {
@@ -54,9 +95,7 @@ const cookieMatchesUrl = (cookie, url) => {
54
95
  if (!hostname || !domain) {
55
96
  return false;
56
97
  }
57
- if (!(hostname === domain ||
58
- hostname.endsWith(`.${domain}`) ||
59
- domain.endsWith(`.${hostname}`))) {
98
+ if (!(hostname === domain || hostname.endsWith(`.${domain}`))) {
60
99
  return false;
61
100
  }
62
101
  if (!cookiePathMatches(cookie.path, parsedUrl?.pathname || '/')) {
@@ -82,6 +121,33 @@ const normalizeSameSite = (value) => {
82
121
  }
83
122
  return undefined;
84
123
  };
124
+ const getDirectCookieDenialReason = (session, cookie) => {
125
+ const checker = session._get_cookie_access_denial_reason;
126
+ if (typeof checker !== 'function') {
127
+ return null;
128
+ }
129
+ return checker.call(session, cookie);
130
+ };
131
+ const filterDirectAllowedCookies = (session, cookies) => cookies.filter((cookie) => !getDirectCookieDenialReason(session, cookie));
132
+ const partitionDirectAllowedCookies = (session, cookies) => {
133
+ const allowedCookies = [];
134
+ const blockedCookies = [];
135
+ for (const cookie of cookies) {
136
+ if (getDirectCookieDenialReason(session, cookie)) {
137
+ blockedCookies.push(cookie);
138
+ }
139
+ else {
140
+ allowedCookies.push(cookie);
141
+ }
142
+ }
143
+ return { allowedCookies, blockedCookies };
144
+ };
145
+ const assertDirectCookieUrlAllowed = (session, url) => {
146
+ const denialReason = getDirectCookieDenialReason(session, { url });
147
+ if (denialReason) {
148
+ throw new Error(`Cookie URL blocked by domain policy: ${denialReason}`);
149
+ }
150
+ };
85
151
  const DEFAULT_STDOUT = process.stdout;
86
152
  const DEFAULT_STDERR = process.stderr;
87
153
  const writeLine = (stream, message) => {
@@ -99,15 +165,55 @@ export const load_direct_state = (state_file = DIRECT_STATE_FILE) => {
99
165
  }
100
166
  };
101
167
  export const save_direct_state = (state, state_file = DIRECT_STATE_FILE) => {
102
- fs.writeFileSync(state_file, JSON.stringify(state, null, 2));
168
+ fs.mkdirSync(path.dirname(state_file), { recursive: true, mode: 0o700 });
169
+ if (process.platform !== 'win32' &&
170
+ path.resolve(state_file) === path.resolve(DIRECT_STATE_FILE)) {
171
+ fs.chmodSync(path.dirname(state_file), 0o700);
172
+ }
173
+ fs.writeFileSync(state_file, JSON.stringify(state, null, 2), {
174
+ encoding: 'utf8',
175
+ mode: 0o600,
176
+ });
177
+ if (process.platform !== 'win32') {
178
+ fs.chmodSync(state_file, 0o600);
179
+ }
103
180
  };
104
181
  export const clear_direct_state = (state_file = DIRECT_STATE_FILE) => {
105
182
  fs.rmSync(state_file, { force: true });
106
183
  };
184
+ const writePrivateFile = (filePath, contents) => {
185
+ fs.writeFileSync(filePath, contents, { encoding: 'utf8', mode: 0o600 });
186
+ if (process.platform !== 'win32') {
187
+ fs.chmodSync(filePath, 0o600);
188
+ }
189
+ };
190
+ const writePrivateBufferFile = (filePath, contents) => {
191
+ fs.writeFileSync(filePath, contents, { mode: 0o600 });
192
+ if (process.platform !== 'win32') {
193
+ fs.chmodSync(filePath, 0o600);
194
+ }
195
+ };
196
+ const isOwnedDirectUserDataDir = (userDataDir) => {
197
+ if (!userDataDir || !fs.existsSync(userDataDir)) {
198
+ return false;
199
+ }
200
+ try {
201
+ const resolvedDir = fs.realpathSync(userDataDir);
202
+ const resolvedTmp = fs.realpathSync(os.tmpdir());
203
+ return (path.dirname(resolvedDir) === resolvedTmp &&
204
+ path.basename(resolvedDir).startsWith('browser-use-direct-'));
205
+ }
206
+ catch {
207
+ return false;
208
+ }
209
+ };
107
210
  const cleanupOwnedDirectUserDataDir = (state) => {
108
211
  if (!state.owns_user_data_dir || !state.user_data_dir) {
109
212
  return;
110
213
  }
214
+ if (!isOwnedDirectUserDataDir(state.user_data_dir)) {
215
+ return;
216
+ }
111
217
  try {
112
218
  fs.rmSync(state.user_data_dir, { recursive: true, force: true });
113
219
  }
@@ -301,40 +407,46 @@ const readDirectNodeData = async (session, node, kind) => {
301
407
  if (!page?.evaluate) {
302
408
  throw new Error('No active page available');
303
409
  }
304
- return await page.evaluate(({ xpath, dataKind }) => {
305
- const element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
306
- if (!element) {
410
+ await validateDirectPageAfterAction(session, page);
411
+ try {
412
+ return await page.evaluate(({ xpath, dataKind }) => {
413
+ const element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
414
+ if (!element) {
415
+ return null;
416
+ }
417
+ if (dataKind === 'text') {
418
+ return element.textContent?.trim() ?? '';
419
+ }
420
+ if (dataKind === 'value') {
421
+ return 'value' in element
422
+ ? String(element.value ?? '')
423
+ : null;
424
+ }
425
+ if (dataKind === 'attributes') {
426
+ return Object.fromEntries(Array.from(element.attributes).map((attribute) => [
427
+ attribute.name,
428
+ attribute.value,
429
+ ]));
430
+ }
431
+ if (dataKind === 'bbox') {
432
+ const rect = element.getBoundingClientRect();
433
+ return {
434
+ x: rect.x,
435
+ y: rect.y,
436
+ width: rect.width,
437
+ height: rect.height,
438
+ top: rect.top,
439
+ right: rect.right,
440
+ bottom: rect.bottom,
441
+ left: rect.left,
442
+ };
443
+ }
307
444
  return null;
308
- }
309
- if (dataKind === 'text') {
310
- return element.textContent?.trim() ?? '';
311
- }
312
- if (dataKind === 'value') {
313
- return 'value' in element
314
- ? String(element.value ?? '')
315
- : null;
316
- }
317
- if (dataKind === 'attributes') {
318
- return Object.fromEntries(Array.from(element.attributes).map((attribute) => [
319
- attribute.name,
320
- attribute.value,
321
- ]));
322
- }
323
- if (dataKind === 'bbox') {
324
- const rect = element.getBoundingClientRect();
325
- return {
326
- x: rect.x,
327
- y: rect.y,
328
- width: rect.width,
329
- height: rect.height,
330
- top: rect.top,
331
- right: rect.right,
332
- bottom: rect.bottom,
333
- left: rect.left,
334
- };
335
- }
336
- return null;
337
- }, { xpath: node.xpath, dataKind: kind });
445
+ }, { xpath: node.xpath, dataKind: kind });
446
+ }
447
+ finally {
448
+ await validateDirectPageAfterAction(session, page);
449
+ }
338
450
  };
339
451
  const takeDirectOptionValue = (args, index, option) => {
340
452
  const next = args[index + 1]?.trim();
@@ -648,7 +760,7 @@ export const run_direct_command = async (argv, options = {}) => {
648
760
  }
649
761
  const bytes = Buffer.from(screenshot, 'base64');
650
762
  if (outputPath) {
651
- fs.writeFileSync(outputPath, bytes);
763
+ writePrivateBufferFile(outputPath, bytes);
652
764
  writeLine(environment.stdout, `Screenshot saved to ${outputPath} (${bytes.length} bytes)`);
653
765
  }
654
766
  else {
@@ -737,7 +849,12 @@ export const run_direct_command = async (argv, options = {}) => {
737
849
  if (!page?.waitForFunction) {
738
850
  throw new Error('No active page available for wait text');
739
851
  }
740
- await page.waitForFunction((needle) => document.body?.innerText?.includes(needle) ?? false, text, { timeout: 5000 });
852
+ try {
853
+ await page.waitForFunction((needle) => document.body?.innerText?.includes(needle) ?? false, text, { timeout: 5000 });
854
+ }
855
+ finally {
856
+ await validateDirectPageAfterAction(session, page);
857
+ }
741
858
  writeLine(environment.stdout, `Waited for text "${text}"`);
742
859
  }
743
860
  else {
@@ -750,7 +867,13 @@ export const run_direct_command = async (argv, options = {}) => {
750
867
  if (!locator?.hover) {
751
868
  throw new Error('Hover is not available for this element');
752
869
  }
753
- await locator.hover({ timeout: 5000 });
870
+ const page = await session.get_current_page?.();
871
+ try {
872
+ await locator.hover({ timeout: 5000 });
873
+ }
874
+ finally {
875
+ await validateDirectPageAfterAction(session, page);
876
+ }
754
877
  writeLine(environment.stdout, `Hovered element [${index}]`);
755
878
  }
756
879
  else if (command === 'dblclick') {
@@ -759,7 +882,13 @@ export const run_direct_command = async (argv, options = {}) => {
759
882
  if (!locator?.dblclick) {
760
883
  throw new Error('Double-click is not available for this element');
761
884
  }
762
- await locator.dblclick({ timeout: 5000 });
885
+ const page = await session.get_current_page?.();
886
+ try {
887
+ await locator.dblclick({ timeout: 5000 });
888
+ }
889
+ finally {
890
+ await validateDirectPageAfterAction(session, page);
891
+ }
763
892
  writeLine(environment.stdout, `Double-clicked element [${index}]`);
764
893
  }
765
894
  else if (command === 'rightclick') {
@@ -768,7 +897,13 @@ export const run_direct_command = async (argv, options = {}) => {
768
897
  if (!locator?.click) {
769
898
  throw new Error('Right-click is not available for this element');
770
899
  }
771
- await locator.click({ button: 'right', timeout: 5000 });
900
+ const page = await session.get_current_page?.();
901
+ try {
902
+ await locator.click({ button: 'right', timeout: 5000 });
903
+ }
904
+ finally {
905
+ await validateDirectPageAfterAction(session, page);
906
+ }
772
907
  writeLine(environment.stdout, `Right-clicked element [${index}]`);
773
908
  }
774
909
  else if (command === 'cookies') {
@@ -776,10 +911,14 @@ export const run_direct_command = async (argv, options = {}) => {
776
911
  if (cookieCommand === 'get') {
777
912
  const parsed = parseDirectCookieOptions(args.slice(2));
778
913
  const url = parsed.url ?? parsed.positional[0] ?? null;
914
+ if (url) {
915
+ assertDirectCookieUrlAllowed(session, url);
916
+ }
779
917
  const allCookies = (await session.get_cookies?.()) ?? [];
918
+ const allowedCookies = filterDirectAllowedCookies(session, allCookies);
780
919
  const cookies = url
781
- ? allCookies.filter((cookie) => cookieMatchesUrl(cookie, url))
782
- : allCookies;
920
+ ? allowedCookies.filter((cookie) => cookieMatchesUrl(cookie, url))
921
+ : allowedCookies;
783
922
  writeLine(environment.stdout, JSON.stringify({ cookies, count: cookies.length }, null, 2));
784
923
  }
785
924
  else if (cookieCommand === 'set') {
@@ -815,6 +954,10 @@ export const run_direct_command = async (argv, options = {}) => {
815
954
  else {
816
955
  throw new Error('Provide cookie url/domain or open a page first');
817
956
  }
957
+ const denialReason = getDirectCookieDenialReason(session, cookie);
958
+ if (denialReason) {
959
+ throw new Error(`Cookie target blocked by domain policy: ${denialReason}`);
960
+ }
818
961
  await session.browser_context.addCookies([cookie]);
819
962
  writeLine(environment.stdout, `Set cookie ${name}`);
820
963
  }
@@ -825,16 +968,41 @@ export const run_direct_command = async (argv, options = {}) => {
825
968
  const parsed = parseDirectCookieOptions(args.slice(2));
826
969
  const url = parsed.url ?? parsed.positional[0] ?? null;
827
970
  if (!url) {
828
- await session.browser_context.clearCookies();
829
- writeLine(environment.stdout, 'Cleared cookies');
971
+ if (typeof session.get_cookies !== 'function') {
972
+ await session.browser_context.clearCookies();
973
+ writeLine(environment.stdout, 'Cleared cookies');
974
+ }
975
+ else {
976
+ const allCookies = (await session.get_cookies?.({ include_blocked: true })) ?? [];
977
+ const { allowedCookies, blockedCookies } = partitionDirectAllowedCookies(session, allCookies);
978
+ if (allowedCookies.length === 0 && blockedCookies.length > 0) {
979
+ writeLine(environment.stdout, 'Cleared 0 cookies');
980
+ }
981
+ else {
982
+ const addCookies = session.browser_context.addCookies;
983
+ if (blockedCookies.length > 0 && !addCookies) {
984
+ throw new Error('Browser context does not support preserving blocked cookies');
985
+ }
986
+ await session.browser_context.clearCookies();
987
+ if (blockedCookies.length > 0) {
988
+ await addCookies(blockedCookies);
989
+ }
990
+ writeLine(environment.stdout, `Cleared ${allowedCookies.length} cookies`);
991
+ }
992
+ }
830
993
  }
831
994
  else {
832
- const allCookies = (await session.get_cookies?.()) ?? [];
995
+ assertDirectCookieUrlAllowed(session, url);
996
+ const allCookies = (await session.get_cookies?.({ include_blocked: true })) ?? [];
833
997
  const remaining = allCookies.filter((cookie) => !cookieMatchesUrl(cookie, url));
834
998
  const removedCount = allCookies.length - remaining.length;
999
+ const addCookies = session.browser_context.addCookies;
1000
+ if (remaining.length > 0 && !addCookies) {
1001
+ throw new Error('Browser context does not support preserving non-matching cookies');
1002
+ }
835
1003
  await session.browser_context.clearCookies();
836
- if (remaining.length > 0 && session.browser_context.addCookies) {
837
- await session.browser_context.addCookies(remaining);
1004
+ if (remaining.length > 0) {
1005
+ await addCookies(remaining);
838
1006
  }
839
1007
  writeLine(environment.stdout, `Cleared ${removedCount} cookies matching ${url}`);
840
1008
  }
@@ -846,12 +1014,16 @@ export const run_direct_command = async (argv, options = {}) => {
846
1014
  }
847
1015
  const parsed = parseDirectCookieOptions(args.slice(3));
848
1016
  const url = parsed.url ?? parsed.positional[0] ?? null;
1017
+ if (url) {
1018
+ assertDirectCookieUrlAllowed(session, url);
1019
+ }
849
1020
  const allCookies = (await session.get_cookies?.()) ?? [];
1021
+ const allowedCookies = filterDirectAllowedCookies(session, allCookies);
850
1022
  const cookies = url
851
- ? allCookies.filter((cookie) => cookieMatchesUrl(cookie, url))
852
- : allCookies;
1023
+ ? allowedCookies.filter((cookie) => cookieMatchesUrl(cookie, url))
1024
+ : allowedCookies;
853
1025
  const outputPath = path.resolve(file);
854
- fs.writeFileSync(outputPath, JSON.stringify(cookies, null, 2), 'utf8');
1026
+ writePrivateFile(outputPath, JSON.stringify(cookies, null, 2));
855
1027
  writeLine(environment.stdout, `Exported ${cookies.length} cookies to ${outputPath}`);
856
1028
  }
857
1029
  else if (cookieCommand === 'import') {
@@ -868,8 +1040,11 @@ export const run_direct_command = async (argv, options = {}) => {
868
1040
  if (!Array.isArray(cookies)) {
869
1041
  throw new Error('Cookie import file must contain a JSON array');
870
1042
  }
871
- await session.browser_context.addCookies(cookies);
872
- writeLine(environment.stdout, `Imported ${cookies.length} cookies from ${inputPath}`);
1043
+ const allowedCookies = filterDirectAllowedCookies(session, cookies);
1044
+ if (allowedCookies.length > 0) {
1045
+ await session.browser_context.addCookies(allowedCookies);
1046
+ }
1047
+ writeLine(environment.stdout, `Imported ${allowedCookies.length} cookies from ${inputPath}`);
873
1048
  }
874
1049
  else {
875
1050
  throw new Error('Usage: cookies get [url|--url <url>] | cookies set <name> <value> | cookies clear [--url <url>] | cookies export <file> [--url <url>] | cookies import <file>');
@@ -882,7 +1057,13 @@ export const run_direct_command = async (argv, options = {}) => {
882
1057
  if (!page?.title) {
883
1058
  throw new Error('No active page available for get title');
884
1059
  }
885
- writeLine(environment.stdout, await page.title());
1060
+ await validateDirectPageAfterAction(session, page);
1061
+ try {
1062
+ writeLine(environment.stdout, await page.title());
1063
+ }
1064
+ finally {
1065
+ await validateDirectPageAfterAction(session, page);
1066
+ }
886
1067
  }
887
1068
  else if (subcommand === 'html') {
888
1069
  const selector = args.slice(2).join(' ').trim();
@@ -894,10 +1075,18 @@ export const run_direct_command = async (argv, options = {}) => {
894
1075
  if (!page?.evaluate) {
895
1076
  throw new Error('No active page available for get html');
896
1077
  }
897
- const html = await page.evaluate((targetSelector) => {
898
- const element = document.querySelector(targetSelector);
899
- return element ? element.outerHTML : null;
900
- }, selector);
1078
+ await validateDirectPageAfterAction(session, page);
1079
+ const html = await (async () => {
1080
+ try {
1081
+ return await page.evaluate((targetSelector) => {
1082
+ const element = document.querySelector(targetSelector);
1083
+ return element ? element.outerHTML : null;
1084
+ }, selector);
1085
+ }
1086
+ finally {
1087
+ await validateDirectPageAfterAction(session, page);
1088
+ }
1089
+ })();
901
1090
  if (typeof html !== 'string' || html.length === 0) {
902
1091
  throw new Error(`No element found for selector: ${selector}`);
903
1092
  }
@@ -939,10 +1128,18 @@ export const run_direct_command = async (argv, options = {}) => {
939
1128
  if (!page?.evaluate) {
940
1129
  throw new Error('No active page available for html');
941
1130
  }
942
- const html = await page.evaluate((targetSelector) => {
943
- const element = document.querySelector(targetSelector);
944
- return element ? element.outerHTML : null;
945
- }, selector);
1131
+ await validateDirectPageAfterAction(session, page);
1132
+ const html = await (async () => {
1133
+ try {
1134
+ return await page.evaluate((targetSelector) => {
1135
+ const element = document.querySelector(targetSelector);
1136
+ return element ? element.outerHTML : null;
1137
+ }, selector);
1138
+ }
1139
+ finally {
1140
+ await validateDirectPageAfterAction(session, page);
1141
+ }
1142
+ })();
946
1143
  if (typeof html !== 'string' || html.length === 0) {
947
1144
  throw new Error(`No element found for selector: ${selector}`);
948
1145
  }
@@ -974,11 +1171,11 @@ export const run_direct_command = async (argv, options = {}) => {
974
1171
  };
975
1172
  export const main = async (argv = process.argv.slice(2)) => {
976
1173
  const exitCode = await run_direct_command(argv);
977
- if (import.meta.url === `file://${process.argv[1]}`) {
1174
+ if (isMainModule(import.meta.url)) {
978
1175
  process.exit(exitCode);
979
1176
  }
980
1177
  return exitCode;
981
1178
  };
982
- if (import.meta.url === `file://${process.argv[1]}`) {
1179
+ if (isMainModule(import.meta.url)) {
983
1180
  void main();
984
1181
  }
@@ -7,6 +7,7 @@ export declare class SkillCliServer {
7
7
  readonly registry: SessionRegistry;
8
8
  constructor(options?: SkillCliServerOptions);
9
9
  private _require_node_by_index;
10
+ private _run_with_page_validation;
10
11
  private _read_node_data;
11
12
  private _handle_browser_action;
12
13
  handle_request(request: Request | string): Promise<Response>;