browser-use 0.0.1 → 0.1.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 (200) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +761 -0
  3. package/dist/agent/cloud-events.d.ts +264 -0
  4. package/dist/agent/cloud-events.js +318 -0
  5. package/dist/agent/gif.d.ts +15 -0
  6. package/dist/agent/gif.js +215 -0
  7. package/dist/agent/index.d.ts +8 -0
  8. package/dist/agent/index.js +8 -0
  9. package/dist/agent/message-manager/service.d.ts +30 -0
  10. package/dist/agent/message-manager/service.js +208 -0
  11. package/dist/agent/message-manager/utils.d.ts +2 -0
  12. package/dist/agent/message-manager/utils.js +41 -0
  13. package/dist/agent/message-manager/views.d.ts +26 -0
  14. package/dist/agent/message-manager/views.js +73 -0
  15. package/dist/agent/prompts.d.ts +52 -0
  16. package/dist/agent/prompts.js +259 -0
  17. package/dist/agent/service.d.ts +290 -0
  18. package/dist/agent/service.js +2200 -0
  19. package/dist/agent/views.d.ts +741 -0
  20. package/dist/agent/views.js +537 -0
  21. package/dist/browser/browser.d.ts +7 -0
  22. package/dist/browser/browser.js +5 -0
  23. package/dist/browser/context.d.ts +8 -0
  24. package/dist/browser/context.js +4 -0
  25. package/dist/browser/dvd-screensaver.d.ts +101 -0
  26. package/dist/browser/dvd-screensaver.js +270 -0
  27. package/dist/browser/extensions.d.ts +63 -0
  28. package/dist/browser/extensions.js +359 -0
  29. package/dist/browser/index.d.ts +10 -0
  30. package/dist/browser/index.js +9 -0
  31. package/dist/browser/playwright-manager.d.ts +47 -0
  32. package/dist/browser/playwright-manager.js +146 -0
  33. package/dist/browser/profile.d.ts +196 -0
  34. package/dist/browser/profile.js +815 -0
  35. package/dist/browser/session.d.ts +505 -0
  36. package/dist/browser/session.js +3409 -0
  37. package/dist/browser/types.d.ts +1184 -0
  38. package/dist/browser/types.js +1 -0
  39. package/dist/browser/utils.d.ts +1 -0
  40. package/dist/browser/utils.js +19 -0
  41. package/dist/browser/views.d.ts +78 -0
  42. package/dist/browser/views.js +72 -0
  43. package/dist/cli.d.ts +2 -0
  44. package/dist/cli.js +44 -0
  45. package/dist/config.d.ts +108 -0
  46. package/dist/config.js +430 -0
  47. package/dist/controller/index.d.ts +3 -0
  48. package/dist/controller/index.js +3 -0
  49. package/dist/controller/registry/index.d.ts +2 -0
  50. package/dist/controller/registry/index.js +2 -0
  51. package/dist/controller/registry/service.d.ts +45 -0
  52. package/dist/controller/registry/service.js +184 -0
  53. package/dist/controller/registry/views.d.ts +55 -0
  54. package/dist/controller/registry/views.js +174 -0
  55. package/dist/controller/service.d.ts +49 -0
  56. package/dist/controller/service.js +1176 -0
  57. package/dist/controller/views.d.ts +241 -0
  58. package/dist/controller/views.js +88 -0
  59. package/dist/dom/clickable-element-processor/service.d.ts +11 -0
  60. package/dist/dom/clickable-element-processor/service.js +60 -0
  61. package/dist/dom/dom_tree/index.js +1400 -0
  62. package/dist/dom/history-tree-processor/service.d.ts +14 -0
  63. package/dist/dom/history-tree-processor/service.js +75 -0
  64. package/dist/dom/history-tree-processor/view.d.ts +54 -0
  65. package/dist/dom/history-tree-processor/view.js +56 -0
  66. package/dist/dom/playground/extraction.d.ts +19 -0
  67. package/dist/dom/playground/extraction.js +187 -0
  68. package/dist/dom/playground/process-dom.d.ts +1 -0
  69. package/dist/dom/playground/process-dom.js +5 -0
  70. package/dist/dom/playground/test-accessibility.d.ts +44 -0
  71. package/dist/dom/playground/test-accessibility.js +111 -0
  72. package/dist/dom/service.d.ts +19 -0
  73. package/dist/dom/service.js +227 -0
  74. package/dist/dom/utils.d.ts +1 -0
  75. package/dist/dom/utils.js +6 -0
  76. package/dist/dom/views.d.ts +61 -0
  77. package/dist/dom/views.js +247 -0
  78. package/dist/event-bus.d.ts +11 -0
  79. package/dist/event-bus.js +19 -0
  80. package/dist/exceptions.d.ts +10 -0
  81. package/dist/exceptions.js +22 -0
  82. package/dist/filesystem/file-system.d.ts +68 -0
  83. package/dist/filesystem/file-system.js +412 -0
  84. package/dist/filesystem/index.d.ts +1 -0
  85. package/dist/filesystem/index.js +1 -0
  86. package/dist/index.d.ts +31 -0
  87. package/dist/index.js +33 -0
  88. package/dist/integrations/gmail/actions.d.ts +12 -0
  89. package/dist/integrations/gmail/actions.js +113 -0
  90. package/dist/integrations/gmail/index.d.ts +2 -0
  91. package/dist/integrations/gmail/index.js +2 -0
  92. package/dist/integrations/gmail/service.d.ts +61 -0
  93. package/dist/integrations/gmail/service.js +260 -0
  94. package/dist/llm/anthropic/chat.d.ts +28 -0
  95. package/dist/llm/anthropic/chat.js +126 -0
  96. package/dist/llm/anthropic/index.d.ts +2 -0
  97. package/dist/llm/anthropic/index.js +2 -0
  98. package/dist/llm/anthropic/serializer.d.ts +68 -0
  99. package/dist/llm/anthropic/serializer.js +285 -0
  100. package/dist/llm/aws/chat-anthropic.d.ts +61 -0
  101. package/dist/llm/aws/chat-anthropic.js +176 -0
  102. package/dist/llm/aws/chat-bedrock.d.ts +15 -0
  103. package/dist/llm/aws/chat-bedrock.js +80 -0
  104. package/dist/llm/aws/index.d.ts +3 -0
  105. package/dist/llm/aws/index.js +3 -0
  106. package/dist/llm/aws/serializer.d.ts +5 -0
  107. package/dist/llm/aws/serializer.js +68 -0
  108. package/dist/llm/azure/chat.d.ts +15 -0
  109. package/dist/llm/azure/chat.js +83 -0
  110. package/dist/llm/azure/index.d.ts +1 -0
  111. package/dist/llm/azure/index.js +1 -0
  112. package/dist/llm/base.d.ts +16 -0
  113. package/dist/llm/base.js +1 -0
  114. package/dist/llm/deepseek/chat.d.ts +15 -0
  115. package/dist/llm/deepseek/chat.js +51 -0
  116. package/dist/llm/deepseek/index.d.ts +2 -0
  117. package/dist/llm/deepseek/index.js +2 -0
  118. package/dist/llm/deepseek/serializer.d.ts +6 -0
  119. package/dist/llm/deepseek/serializer.js +57 -0
  120. package/dist/llm/exceptions.d.ts +10 -0
  121. package/dist/llm/exceptions.js +18 -0
  122. package/dist/llm/google/chat.d.ts +20 -0
  123. package/dist/llm/google/chat.js +144 -0
  124. package/dist/llm/google/index.d.ts +2 -0
  125. package/dist/llm/google/index.js +2 -0
  126. package/dist/llm/google/serializer.d.ts +6 -0
  127. package/dist/llm/google/serializer.js +64 -0
  128. package/dist/llm/groq/chat.d.ts +15 -0
  129. package/dist/llm/groq/chat.js +52 -0
  130. package/dist/llm/groq/index.d.ts +3 -0
  131. package/dist/llm/groq/index.js +3 -0
  132. package/dist/llm/groq/parser.d.ts +32 -0
  133. package/dist/llm/groq/parser.js +189 -0
  134. package/dist/llm/groq/serializer.d.ts +6 -0
  135. package/dist/llm/groq/serializer.js +56 -0
  136. package/dist/llm/messages.d.ts +77 -0
  137. package/dist/llm/messages.js +157 -0
  138. package/dist/llm/ollama/chat.d.ts +15 -0
  139. package/dist/llm/ollama/chat.js +77 -0
  140. package/dist/llm/ollama/index.d.ts +2 -0
  141. package/dist/llm/ollama/index.js +2 -0
  142. package/dist/llm/ollama/serializer.d.ts +6 -0
  143. package/dist/llm/ollama/serializer.js +53 -0
  144. package/dist/llm/openai/chat.d.ts +38 -0
  145. package/dist/llm/openai/chat.js +174 -0
  146. package/dist/llm/openai/index.d.ts +3 -0
  147. package/dist/llm/openai/index.js +3 -0
  148. package/dist/llm/openai/like.d.ts +17 -0
  149. package/dist/llm/openai/like.js +19 -0
  150. package/dist/llm/openai/serializer.d.ts +6 -0
  151. package/dist/llm/openai/serializer.js +57 -0
  152. package/dist/llm/openrouter/chat.d.ts +15 -0
  153. package/dist/llm/openrouter/chat.js +74 -0
  154. package/dist/llm/openrouter/index.d.ts +2 -0
  155. package/dist/llm/openrouter/index.js +2 -0
  156. package/dist/llm/openrouter/serializer.d.ts +3 -0
  157. package/dist/llm/openrouter/serializer.js +3 -0
  158. package/dist/llm/schema.d.ts +6 -0
  159. package/dist/llm/schema.js +77 -0
  160. package/dist/llm/views.d.ts +15 -0
  161. package/dist/llm/views.js +12 -0
  162. package/dist/logging-config.d.ts +25 -0
  163. package/dist/logging-config.js +89 -0
  164. package/dist/mcp/client.d.ts +142 -0
  165. package/dist/mcp/client.js +638 -0
  166. package/dist/mcp/controller.d.ts +6 -0
  167. package/dist/mcp/controller.js +38 -0
  168. package/dist/mcp/index.d.ts +3 -0
  169. package/dist/mcp/index.js +3 -0
  170. package/dist/mcp/server.d.ts +134 -0
  171. package/dist/mcp/server.js +759 -0
  172. package/dist/observability-decorators.d.ts +158 -0
  173. package/dist/observability-decorators.js +286 -0
  174. package/dist/observability.d.ts +23 -0
  175. package/dist/observability.js +58 -0
  176. package/dist/screenshots/index.d.ts +1 -0
  177. package/dist/screenshots/index.js +1 -0
  178. package/dist/screenshots/service.d.ts +6 -0
  179. package/dist/screenshots/service.js +28 -0
  180. package/dist/sync/auth.d.ts +27 -0
  181. package/dist/sync/auth.js +205 -0
  182. package/dist/sync/index.d.ts +2 -0
  183. package/dist/sync/index.js +2 -0
  184. package/dist/sync/service.d.ts +21 -0
  185. package/dist/sync/service.js +146 -0
  186. package/dist/telemetry/index.d.ts +2 -0
  187. package/dist/telemetry/index.js +2 -0
  188. package/dist/telemetry/service.d.ts +12 -0
  189. package/dist/telemetry/service.js +85 -0
  190. package/dist/telemetry/views.d.ts +112 -0
  191. package/dist/telemetry/views.js +112 -0
  192. package/dist/tokens/index.d.ts +2 -0
  193. package/dist/tokens/index.js +2 -0
  194. package/dist/tokens/service.d.ts +35 -0
  195. package/dist/tokens/service.js +423 -0
  196. package/dist/tokens/views.d.ts +58 -0
  197. package/dist/tokens/views.js +1 -0
  198. package/dist/utils.d.ts +128 -0
  199. package/dist/utils.js +529 -0
  200. package/package.json +94 -5
@@ -0,0 +1,270 @@
1
+ /**
2
+ * DVD Screensaver Loading Animation
3
+ *
4
+ * Displays a fun DVD logo bouncing animation while waiting for browser operations.
5
+ * Inspired by the classic DVD screensaver.
6
+ */
7
+ import { createLogger } from '../logging-config.js';
8
+ const logger = createLogger('browser_use.dvd_screensaver');
9
+ /**
10
+ * DVD Screensaver Animation Controller
11
+ */
12
+ export class DVDScreensaver {
13
+ isRunning = false;
14
+ intervalId = null;
15
+ width = 80;
16
+ height = 20;
17
+ x = 0;
18
+ y = 0;
19
+ dx = 1;
20
+ dy = 1;
21
+ logoWidth = 10;
22
+ logoHeight = 3;
23
+ colors = [
24
+ '\x1b[31m',
25
+ '\x1b[32m',
26
+ '\x1b[33m',
27
+ '\x1b[34m',
28
+ '\x1b[35m',
29
+ '\x1b[36m',
30
+ ];
31
+ currentColorIndex = 0;
32
+ cornerHits = 0;
33
+ frameCount = 0;
34
+ message;
35
+ constructor(message = 'Loading...') {
36
+ this.message = message;
37
+ this.x = Math.floor(Math.random() * (this.width - this.logoWidth));
38
+ this.y = Math.floor(Math.random() * (this.height - this.logoHeight));
39
+ }
40
+ /**
41
+ * Start the animation
42
+ */
43
+ start(fps = 10) {
44
+ if (this.isRunning) {
45
+ return;
46
+ }
47
+ this.isRunning = true;
48
+ // Hide cursor
49
+ process.stderr.write('\x1b[?25l');
50
+ // Clear screen
51
+ this.clear();
52
+ this.intervalId = setInterval(() => {
53
+ this.update();
54
+ this.render();
55
+ }, 1000 / fps);
56
+ }
57
+ /**
58
+ * Stop the animation
59
+ */
60
+ stop() {
61
+ if (!this.isRunning) {
62
+ return;
63
+ }
64
+ this.isRunning = false;
65
+ if (this.intervalId) {
66
+ clearInterval(this.intervalId);
67
+ this.intervalId = null;
68
+ }
69
+ // Clear screen one last time
70
+ this.clear();
71
+ // Show cursor
72
+ process.stderr.write('\x1b[?25h');
73
+ // Log corner hits if any
74
+ if (this.cornerHits > 0) {
75
+ logger.debug(`DVD logo hit corner ${this.cornerHits} time(s)! 🎯`);
76
+ }
77
+ }
78
+ /**
79
+ * Update logo position
80
+ */
81
+ update() {
82
+ this.frameCount++;
83
+ // Update position
84
+ this.x += this.dx;
85
+ this.y += this.dy;
86
+ let hitCorner = false;
87
+ // Check horizontal bounds
88
+ if (this.x <= 0 || this.x >= this.width - this.logoWidth) {
89
+ this.dx = -this.dx;
90
+ this.x = Math.max(0, Math.min(this.x, this.width - this.logoWidth));
91
+ this.changeColor();
92
+ // Check if hit corner
93
+ if (this.y <= 0 || this.y >= this.height - this.logoHeight) {
94
+ hitCorner = true;
95
+ }
96
+ }
97
+ // Check vertical bounds
98
+ if (this.y <= 0 || this.y >= this.height - this.logoHeight) {
99
+ this.dy = -this.dy;
100
+ this.y = Math.max(0, Math.min(this.y, this.height - this.logoHeight));
101
+ this.changeColor();
102
+ // Check if hit corner
103
+ if (this.x <= 0 || this.x >= this.width - this.logoWidth) {
104
+ hitCorner = true;
105
+ }
106
+ }
107
+ if (hitCorner) {
108
+ this.cornerHits++;
109
+ }
110
+ }
111
+ /**
112
+ * Change logo color
113
+ */
114
+ changeColor() {
115
+ this.currentColorIndex = (this.currentColorIndex + 1) % this.colors.length;
116
+ }
117
+ /**
118
+ * Render the current frame
119
+ */
120
+ render() {
121
+ // Move cursor to top-left
122
+ process.stderr.write('\x1b[H');
123
+ const color = this.colors[this.currentColorIndex];
124
+ const reset = '\x1b[0m';
125
+ // Build frame
126
+ const frame = [];
127
+ // Draw border and content
128
+ for (let row = 0; row < this.height; row++) {
129
+ let line = '';
130
+ for (let col = 0; col < this.width; col++) {
131
+ // Check if this position is part of the logo
132
+ if (row >= this.y &&
133
+ row < this.y + this.logoHeight &&
134
+ col >= this.x &&
135
+ col < this.x + this.logoWidth) {
136
+ // Draw logo
137
+ const logoRow = row - this.y;
138
+ const logoCol = col - this.x;
139
+ line += color + this.getLogoChar(logoRow, logoCol) + reset;
140
+ }
141
+ else if (row === 0 ||
142
+ row === this.height - 1 ||
143
+ col === 0 ||
144
+ col === this.width - 1) {
145
+ // Draw border
146
+ line += '·';
147
+ }
148
+ else {
149
+ line += ' ';
150
+ }
151
+ }
152
+ frame.push(line);
153
+ }
154
+ // Add status message
155
+ const statusLine = `\n${this.message} (Frame: ${this.frameCount}, Corner hits: ${this.cornerHits})`;
156
+ // Write frame to stderr
157
+ process.stderr.write(frame.join('\n') + statusLine);
158
+ }
159
+ /**
160
+ * Get character for logo at specific position
161
+ */
162
+ getLogoChar(row, col) {
163
+ // Simple "DVD" text logo
164
+ if (row === 1) {
165
+ const text = ' DVD ';
166
+ return col < text.length ? text[col] : ' ';
167
+ }
168
+ return '▓';
169
+ }
170
+ /**
171
+ * Clear screen
172
+ */
173
+ clear() {
174
+ process.stderr.write('\x1b[2J\x1b[H');
175
+ }
176
+ }
177
+ /**
178
+ * Show DVD screensaver loading animation
179
+ * Returns a function to stop the animation
180
+ *
181
+ * @param message - Message to display
182
+ * @param fps - Frames per second (default: 10)
183
+ * @returns Function to stop the animation
184
+ *
185
+ * @example
186
+ * const stopAnimation = showDVDScreensaver('Loading browser...');
187
+ * await someAsyncOperation();
188
+ * stopAnimation();
189
+ */
190
+ export function showDVDScreensaver(message = 'Loading...', fps = 10) {
191
+ const screensaver = new DVDScreensaver(message);
192
+ screensaver.start(fps);
193
+ return () => {
194
+ screensaver.stop();
195
+ };
196
+ }
197
+ /**
198
+ * Run an async operation with DVD screensaver animation
199
+ *
200
+ * @param operation - Async operation to run
201
+ * @param message - Message to display
202
+ * @returns Result of the operation
203
+ *
204
+ * @example
205
+ * const result = await withDVDScreensaver(
206
+ * async () => await longRunningOperation(),
207
+ * 'Processing...'
208
+ * );
209
+ */
210
+ export async function withDVDScreensaver(operation, message = 'Loading...') {
211
+ const stopAnimation = showDVDScreensaver(message);
212
+ try {
213
+ const result = await operation();
214
+ return result;
215
+ }
216
+ finally {
217
+ stopAnimation();
218
+ }
219
+ }
220
+ /**
221
+ * Simple spinner animation (alternative to DVD screensaver)
222
+ */
223
+ export class SpinnerAnimation {
224
+ isRunning = false;
225
+ intervalId = null;
226
+ frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
227
+ currentFrame = 0;
228
+ message;
229
+ constructor(message = 'Loading...') {
230
+ this.message = message;
231
+ }
232
+ start(fps = 10) {
233
+ if (this.isRunning) {
234
+ return;
235
+ }
236
+ this.isRunning = true;
237
+ process.stderr.write('\x1b[?25l'); // Hide cursor
238
+ this.intervalId = setInterval(() => {
239
+ this.render();
240
+ this.currentFrame = (this.currentFrame + 1) % this.frames.length;
241
+ }, 1000 / fps);
242
+ }
243
+ stop() {
244
+ if (!this.isRunning) {
245
+ return;
246
+ }
247
+ this.isRunning = false;
248
+ if (this.intervalId) {
249
+ clearInterval(this.intervalId);
250
+ this.intervalId = null;
251
+ }
252
+ // Clear line and show cursor
253
+ process.stderr.write('\r\x1b[K');
254
+ process.stderr.write('\x1b[?25h');
255
+ }
256
+ render() {
257
+ const frame = this.frames[this.currentFrame];
258
+ process.stderr.write(`\r${frame} ${this.message}`);
259
+ }
260
+ }
261
+ /**
262
+ * Show simple spinner animation
263
+ */
264
+ export function showSpinner(message = 'Loading...', fps = 10) {
265
+ const spinner = new SpinnerAnimation(message);
266
+ spinner.start(fps);
267
+ return () => {
268
+ spinner.stop();
269
+ };
270
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Browser Extension Management
3
+ * Handles Chrome extension download, installation, and runtime integration.
4
+ * Supports both Manifest V2 and V3 extensions.
5
+ */
6
+ export interface BrowserExtensionDescriptor {
7
+ name: string;
8
+ webstore_id?: string;
9
+ id?: string;
10
+ webstore_url?: string;
11
+ crx_url?: string;
12
+ crx_path?: string;
13
+ unpacked_path?: string;
14
+ version?: string;
15
+ manifest?: any;
16
+ manifest_version?: string;
17
+ homepage_url?: string;
18
+ options_url?: string;
19
+ target?: any;
20
+ target_ctx?: any;
21
+ target_type?: string;
22
+ target_url?: string;
23
+ read_manifest?: () => any;
24
+ read_version?: () => string | null;
25
+ dispatch_eval?: (...args: any[]) => Promise<any>;
26
+ dispatch_popup?: () => Promise<any>;
27
+ dispatch_action?: (tab?: any) => Promise<any>;
28
+ dispatch_message?: (message: any, options?: any) => Promise<any>;
29
+ dispatch_command?: (command: string, tab?: any) => Promise<any>;
30
+ }
31
+ /**
32
+ * Generate extension ID from unpacked path
33
+ * Chrome uses SHA256 hash of the directory path
34
+ */
35
+ export declare function getExtensionId(unpackedPath: string): string | null;
36
+ /**
37
+ * Load or install extension
38
+ */
39
+ export declare function loadOrInstallExtension(ext: BrowserExtensionDescriptor, extensionsDir?: string): Promise<BrowserExtensionDescriptor>;
40
+ /**
41
+ * Check if a browser target is an extension
42
+ */
43
+ export declare function isTargetExtension(target: any): Promise<{
44
+ target_type: string | null;
45
+ target_ctx: any;
46
+ target_url: string | null;
47
+ target_is_bg: boolean;
48
+ target_is_extension: boolean;
49
+ extension_id: string | null;
50
+ manifest_version: string;
51
+ }>;
52
+ /**
53
+ * Load extension from browser target (runtime connection)
54
+ */
55
+ export declare function loadExtensionFromTarget(extensions: BrowserExtensionDescriptor[], target: any): Promise<BrowserExtensionDescriptor | null>;
56
+ /**
57
+ * Load all Chrome extensions from browser targets
58
+ */
59
+ export declare function getChromeExtensionsFromBrowser(browser: any, extensions: BrowserExtensionDescriptor[]): Promise<BrowserExtensionDescriptor[]>;
60
+ /**
61
+ * Install extensions from persona configuration
62
+ */
63
+ export declare function installExtensionsFromConfig(extensionConfigs: BrowserExtensionDescriptor[], extensionsDir: string): Promise<BrowserExtensionDescriptor[]>;
@@ -0,0 +1,359 @@
1
+ /**
2
+ * Browser Extension Management
3
+ * Handles Chrome extension download, installation, and runtime integration.
4
+ * Supports both Manifest V2 and V3 extensions.
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { createHash } from 'node:crypto';
9
+ import { pipeline } from 'node:stream/promises';
10
+ import { createWriteStream } from 'node:fs';
11
+ import { Readable } from 'node:stream';
12
+ // @ts-ignore - extract-zip types may not be available
13
+ import extract from 'extract-zip';
14
+ import { createLogger } from '../logging-config.js';
15
+ const logger = createLogger('browser_use.extensions');
16
+ /**
17
+ * Generate extension ID from unpacked path
18
+ * Chrome uses SHA256 hash of the directory path
19
+ */
20
+ export function getExtensionId(unpackedPath) {
21
+ const manifestPath = path.join(unpackedPath, 'manifest.json');
22
+ if (!fs.existsSync(manifestPath)) {
23
+ return null;
24
+ }
25
+ // Chrome uses SHA256 hash and converts to letter format
26
+ const hash = createHash('sha256').update(unpackedPath).digest('hex');
27
+ const extensionId = hash
28
+ .slice(0, 32)
29
+ .split('')
30
+ .map((char) => String.fromCharCode(parseInt(char, 16) + 'a'.charCodeAt(0)))
31
+ .join('');
32
+ return extensionId;
33
+ }
34
+ /**
35
+ * Download CRX file from Chrome Web Store
36
+ */
37
+ async function downloadCrx(crxUrl, crxPath) {
38
+ try {
39
+ logger.info(`[🛠️] Downloading extension from ${crxUrl}...`);
40
+ const response = await fetch(crxUrl);
41
+ if (!response.ok || !response.body) {
42
+ logger.warning(`[⚠️] Failed to download extension: ${response.statusText}`);
43
+ return false;
44
+ }
45
+ // Ensure directory exists
46
+ const dir = path.dirname(crxPath);
47
+ if (!fs.existsSync(dir)) {
48
+ fs.mkdirSync(dir, { recursive: true });
49
+ }
50
+ // Download file
51
+ const fileStream = createWriteStream(crxPath);
52
+ await pipeline(Readable.fromWeb(response.body), fileStream);
53
+ logger.info(`[✅] Downloaded to ${crxPath}`);
54
+ return true;
55
+ }
56
+ catch (error) {
57
+ logger.error(`[❌] Download failed: ${error.message}`);
58
+ return false;
59
+ }
60
+ }
61
+ /**
62
+ * Extract CRX file to unpacked directory
63
+ */
64
+ async function unpackCrx(crxPath, unpackedPath) {
65
+ try {
66
+ // Ensure unpacked directory exists
67
+ if (!fs.existsSync(unpackedPath)) {
68
+ fs.mkdirSync(unpackedPath, { recursive: true });
69
+ }
70
+ // Extract zip file (CRX is essentially a ZIP with extra header)
71
+ await extract(crxPath, { dir: path.resolve(unpackedPath) });
72
+ // Verify manifest exists
73
+ const manifestPath = path.join(unpackedPath, 'manifest.json');
74
+ if (!fs.existsSync(manifestPath)) {
75
+ logger.error(`[❌] No manifest.json found in ${unpackedPath}`);
76
+ return false;
77
+ }
78
+ logger.info(`[✅] Extracted to ${unpackedPath}`);
79
+ return true;
80
+ }
81
+ catch (error) {
82
+ logger.error(`[❌] Extraction failed: ${error.message}`);
83
+ return false;
84
+ }
85
+ }
86
+ /**
87
+ * Install extension (download and unpack if needed)
88
+ */
89
+ async function installExtension(extension) {
90
+ const manifestPath = path.join(extension.unpacked_path, 'manifest.json');
91
+ const crxPath = extension.crx_path;
92
+ // Download CRX if neither manifest nor CRX exists
93
+ if (!fs.existsSync(manifestPath) && !fs.existsSync(crxPath)) {
94
+ logger.info(`[🛠️] Downloading missing extension ${extension.name} ${extension.webstore_id} -> ${crxPath}`);
95
+ const downloaded = await downloadCrx(extension.crx_url, crxPath);
96
+ if (!downloaded) {
97
+ return false;
98
+ }
99
+ }
100
+ // Unpack CRX if manifest doesn't exist
101
+ if (!fs.existsSync(manifestPath)) {
102
+ const unpacked = await unpackCrx(crxPath, extension.unpacked_path);
103
+ if (!unpacked) {
104
+ return false;
105
+ }
106
+ }
107
+ return true;
108
+ }
109
+ /**
110
+ * Load or install extension
111
+ */
112
+ export async function loadOrInstallExtension(ext, extensionsDir = path.join(process.cwd(), '.browser-use', 'extensions')) {
113
+ if (!ext.webstore_id && !ext.unpacked_path) {
114
+ throw new Error('Extension must have either webstore_id or unpacked_path');
115
+ }
116
+ // Set statically computable extension metadata
117
+ ext.webstore_id = ext.webstore_id || ext.id;
118
+ ext.name = ext.name || ext.webstore_id;
119
+ ext.webstore_url =
120
+ ext.webstore_url ||
121
+ `https://chromewebstore.google.com/detail/${ext.webstore_id}`;
122
+ ext.crx_url =
123
+ ext.crx_url ||
124
+ `https://clients2.google.com/service/update2/crx?response=redirect&prodversion=130.0&acceptformat=crx3&x=id%3D${ext.webstore_id}%26uc`;
125
+ ext.crx_path =
126
+ ext.crx_path ||
127
+ path.join(extensionsDir, `${ext.webstore_id}__${ext.name}.crx`);
128
+ ext.unpacked_path =
129
+ ext.unpacked_path ||
130
+ path.join(extensionsDir, `${ext.webstore_id}__${ext.name}`);
131
+ const manifestPath = path.join(ext.unpacked_path, 'manifest.json');
132
+ // Helper functions
133
+ const readManifest = () => {
134
+ if (fs.existsSync(manifestPath)) {
135
+ return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
136
+ }
137
+ return null;
138
+ };
139
+ const readVersion = () => {
140
+ const manifest = readManifest();
141
+ return manifest?.version || null;
142
+ };
143
+ ext.read_manifest = readManifest;
144
+ ext.read_version = readVersion;
145
+ // Install extension if not already installed
146
+ if (!readVersion()) {
147
+ await installExtension(ext);
148
+ }
149
+ // Auto-detect ID and version
150
+ ext.id = getExtensionId(ext.unpacked_path) ?? undefined;
151
+ ext.version = readVersion() ?? undefined;
152
+ if (!ext.version) {
153
+ logger.warning(`[❌] Unable to detect ID and version of installed extension ${ext.unpacked_path}`);
154
+ }
155
+ else {
156
+ logger.info(`[➕] Installed extension ${ext.name} (${ext.version})... ${ext.unpacked_path}`);
157
+ }
158
+ return ext;
159
+ }
160
+ /**
161
+ * Check if a browser target is an extension
162
+ */
163
+ export async function isTargetExtension(target) {
164
+ let target_type = null;
165
+ let target_ctx = null;
166
+ let target_url = null;
167
+ try {
168
+ target_type = await target.type();
169
+ target_ctx = (await target.worker?.()) || (await target.page?.()) || null;
170
+ target_url =
171
+ (await target.url?.()) || (target_ctx ? await target_ctx.url() : null);
172
+ }
173
+ catch (error) {
174
+ if (error.message?.includes('No target with given id found')) {
175
+ // Target already closed
176
+ target_type = 'closed';
177
+ target_ctx = null;
178
+ target_url = 'about:closed';
179
+ }
180
+ else {
181
+ throw error;
182
+ }
183
+ }
184
+ const target_is_bg = ['service_worker', 'background_page'].includes(target_type || '');
185
+ const target_is_extension = target_url?.startsWith('chrome-extension://') || false;
186
+ const extension_id = target_is_extension
187
+ ? target_url.split('://')[1].split('/')[0]
188
+ : null;
189
+ const manifest_version = target_type === 'service_worker' ? '3' : '2';
190
+ return {
191
+ target_type,
192
+ target_ctx,
193
+ target_url,
194
+ target_is_bg,
195
+ target_is_extension,
196
+ extension_id,
197
+ manifest_version,
198
+ };
199
+ }
200
+ /**
201
+ * Load extension from browser target (runtime connection)
202
+ */
203
+ export async function loadExtensionFromTarget(extensions, target) {
204
+ const extensionInfo = await isTargetExtension(target);
205
+ const { target_is_bg, target_is_extension, target_type, target_ctx, target_url, extension_id, manifest_version, } = extensionInfo;
206
+ if (!target_is_bg || !extension_id || !target_ctx) {
207
+ return null;
208
+ }
209
+ // Get manifest from extension
210
+ const manifest = await target_ctx.evaluate('() => chrome.runtime.getManifest()');
211
+ const name = manifest.name;
212
+ const version = manifest.version;
213
+ const homepage_url = manifest.homepage_url;
214
+ const options_page = manifest.options_page;
215
+ const options_ui = manifest.options_ui || {};
216
+ if (!version || !extension_id) {
217
+ return null;
218
+ }
219
+ // Get options URL
220
+ const options_url = await target_ctx.evaluate('(options_page) => chrome.runtime.getURL(options_page)', options_page || options_ui.page || 'options.html');
221
+ // Get keyboard commands
222
+ const commands = await target_ctx.evaluate(`
223
+ async () => {
224
+ return await new Promise((resolve) => {
225
+ if (chrome.commands) {
226
+ chrome.commands.getAll(resolve);
227
+ } else {
228
+ resolve({});
229
+ }
230
+ });
231
+ }
232
+ `);
233
+ // Dispatch helpers
234
+ const dispatch_eval = async (...args) => {
235
+ return await target_ctx.evaluate(...args);
236
+ };
237
+ const dispatch_popup = async () => {
238
+ return await target_ctx.evaluate("() => chrome.action?.openPopup() || chrome.tabs.create({url: chrome.runtime.getURL('popup.html')})");
239
+ };
240
+ let dispatch_action;
241
+ let dispatch_message;
242
+ let dispatch_command;
243
+ if (manifest_version === '3') {
244
+ // Manifest V3 APIs
245
+ dispatch_action = async (tab) => {
246
+ return await target_ctx.evaluate(`
247
+ async (tab) => {
248
+ tab = tab || (await new Promise((resolve) =>
249
+ chrome.tabs.query({currentWindow: true, active: true}, ([tab]) => resolve(tab))
250
+ ));
251
+ return await chrome.action.onClicked.dispatch(tab);
252
+ }
253
+ `, tab);
254
+ };
255
+ dispatch_message = async (message, options) => {
256
+ return await target_ctx.evaluate(`
257
+ async (extension_id, message, options) => {
258
+ return await chrome.runtime.sendMessage(extension_id, message, options);
259
+ }
260
+ `, extension_id, message, options);
261
+ };
262
+ dispatch_command = async (command, tab) => {
263
+ return await target_ctx.evaluate(`
264
+ async (command, tab) => {
265
+ return await chrome.commands.onCommand.dispatch(command, tab);
266
+ }
267
+ `, command, tab);
268
+ };
269
+ }
270
+ else {
271
+ // Manifest V2 APIs
272
+ dispatch_action = async (tab) => {
273
+ return await target_ctx.evaluate(`
274
+ async (tab) => {
275
+ tab = tab || (await new Promise((resolve) =>
276
+ chrome.tabs.query({currentWindow: true, active: true}, ([tab]) => resolve(tab))
277
+ ));
278
+ return await chrome.browserAction.onClicked.dispatch(tab);
279
+ }
280
+ `, tab);
281
+ };
282
+ dispatch_message = async (message, options) => {
283
+ return await target_ctx.evaluate(`
284
+ async (extension_id, message, options) => {
285
+ return await new Promise((resolve) =>
286
+ chrome.runtime.sendMessage(extension_id, message, options, resolve)
287
+ );
288
+ }
289
+ `, extension_id, message, options);
290
+ };
291
+ dispatch_command = async (command, tab) => {
292
+ return await target_ctx.evaluate(`
293
+ async (command, tab) => {
294
+ return await new Promise((resolve) =>
295
+ chrome.commands.onCommand.dispatch(command, tab, resolve)
296
+ );
297
+ }
298
+ `, command, tab);
299
+ };
300
+ }
301
+ // Find existing extension or create new one
302
+ const existing_extension = extensions.find((ext) => ext.id === extension_id) ||
303
+ {};
304
+ const new_extension = {
305
+ ...existing_extension,
306
+ id: extension_id,
307
+ name,
308
+ target,
309
+ target_ctx,
310
+ target_type: target_type,
311
+ target_url: target_url,
312
+ manifest_version,
313
+ manifest,
314
+ version,
315
+ homepage_url,
316
+ options_url,
317
+ dispatch_eval,
318
+ dispatch_popup,
319
+ dispatch_action,
320
+ dispatch_message,
321
+ dispatch_command,
322
+ };
323
+ logger.info(`[➕] Loaded extension ${name.slice(0, 32)} (${version}) ${target_type}... ${target_url}`);
324
+ // Update existing extension in-place
325
+ Object.assign(existing_extension, new_extension);
326
+ return new_extension;
327
+ }
328
+ /**
329
+ * Load all Chrome extensions from browser targets
330
+ */
331
+ export async function getChromeExtensionsFromBrowser(browser, extensions) {
332
+ logger.info(`[⚙️] Loading ${extensions.length} chrome extensions from browser...`);
333
+ // Find loaded extensions at runtime by checking all browser targets
334
+ const targets = await browser.targets();
335
+ for (const target of targets) {
336
+ await loadExtensionFromTarget(extensions, target);
337
+ }
338
+ return extensions;
339
+ }
340
+ /**
341
+ * Install extensions from persona configuration
342
+ */
343
+ export async function installExtensionsFromConfig(extensionConfigs, extensionsDir) {
344
+ logger.info('*************************************************************************');
345
+ logger.info(`[⚙️] Installing ${extensionConfigs.length} chrome extensions...`);
346
+ const extensions = [];
347
+ try {
348
+ // Install each extension
349
+ for (const config of extensionConfigs) {
350
+ const extension = await loadOrInstallExtension(config, extensionsDir);
351
+ extensions.push(extension);
352
+ }
353
+ }
354
+ catch (error) {
355
+ logger.error(`[❌] Extension installation failed: ${error.message}`);
356
+ }
357
+ logger.info('*************************************************************************');
358
+ return extensions;
359
+ }
@@ -0,0 +1,10 @@
1
+ export * from './profile.js';
2
+ export * from './views.js';
3
+ export * from './utils.js';
4
+ export * from './session.js';
5
+ export * from './extensions.js';
6
+ export * from './dvd-screensaver.js';
7
+ export * from './playwright-manager.js';
8
+ export type { Browser, BrowserConfig, BrowserContext, BrowserContextConfig, } from './context.js';
9
+ export type { Browser as PlaywrightBrowser, BrowserContext as PlaywrightBrowserContext, Page, Locator, FrameLocator, ElementHandle, Playwright, PlaywrightOrPatchright, ClientCertificate, Geolocation, HttpCredentials, ProxySettings, StorageState, ViewportSize, } from './types.js';
10
+ export { async_playwright } from './types.js';