real-browser-mcp-server 1.1.7 → 1.1.9

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 (46) hide show
  1. package/dist/lib/cjs/index.js +384 -0
  2. package/{lib → dist/lib}/cjs/module/pageController.js +27 -29
  3. package/{lib → dist/lib}/cjs/module/turnstile.js +23 -12
  4. package/dist/src/ai/action-parser.js +229 -0
  5. package/dist/src/ai/core.js +367 -0
  6. package/dist/src/ai/element-finder.js +409 -0
  7. package/{src → dist/src}/ai/index.js +35 -50
  8. package/dist/src/ai/page-analyzer.js +264 -0
  9. package/dist/src/ai/selector-healer.js +215 -0
  10. package/dist/src/index.js +116 -0
  11. package/dist/src/mcp/handlers/browser.js +230 -0
  12. package/dist/src/mcp/handlers/dom.js +550 -0
  13. package/dist/src/mcp/handlers/extract.js +451 -0
  14. package/dist/src/mcp/handlers/helpers.js +514 -0
  15. package/dist/src/mcp/handlers/index.js +63 -0
  16. package/dist/src/mcp/handlers/misc.js +1224 -0
  17. package/dist/src/mcp/handlers/network.js +1134 -0
  18. package/dist/src/mcp/handlers/state.js +215 -0
  19. package/dist/src/mcp/handlers/vision.js +484 -0
  20. package/dist/src/mcp/index.js +166 -0
  21. package/dist/src/mcp/server.js +117 -0
  22. package/{src → dist/src}/mcp/tools.js +12 -11
  23. package/dist/src/shared/tools.js +599 -0
  24. package/{test → dist/test}/cjs/test.js +119 -169
  25. package/dist/test/mcp/smoke-test.js +131 -0
  26. package/lib/esm/module/pageController.mjs +21 -18
  27. package/lib/esm/module/turnstile.mjs +7 -0
  28. package/package.json +22 -11
  29. package/.github/ISSUE_TEMPLATE/general_issue.yaml +0 -58
  30. package/.github/SETUP.md +0 -111
  31. package/.github/workflows/publish.yml +0 -162
  32. package/Dockerfile +0 -78
  33. package/lib/cjs/adblocker.bin +0 -0
  34. package/lib/cjs/index.js +0 -396
  35. package/src/ai/action-parser.js +0 -269
  36. package/src/ai/core.js +0 -379
  37. package/src/ai/element-finder.js +0 -466
  38. package/src/ai/page-analyzer.js +0 -295
  39. package/src/ai/selector-healer.js +0 -236
  40. package/src/index.js +0 -128
  41. package/src/mcp/handlers.js +0 -5306
  42. package/src/mcp/index.js +0 -190
  43. package/src/mcp/server.js +0 -141
  44. package/src/shared/tools.js +0 -625
  45. package/test/esm/test.mjs +0 -299
  46. package/test/mcp/smoke-test.js +0 -141
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ // @ts-nocheck
3
+ /**
4
+ * AI Action Parser - Parse natural language commands into actions
5
+ *
6
+ * Converts commands like:
7
+ * - "click the login button" -> { type: 'click', target: 'login button' }
8
+ * - "type hello in the search box" -> { type: 'type', target: 'search box', text: 'hello' }
9
+ * - "scroll down" -> { type: 'scroll', direction: 'down' }
10
+ */
11
+ class ActionParser {
12
+ constructor() {
13
+ // Action patterns
14
+ this.actionPatterns = [
15
+ // Click patterns
16
+ {
17
+ pattern: /^(click|tap|press|hit|select)\s+(?:on\s+)?(?:the\s+)?(.+)/i,
18
+ type: 'click',
19
+ extract: (match) => ({ target: match[2].trim() })
20
+ },
21
+ // Type patterns
22
+ {
23
+ pattern: /^(type|enter|write|input)\s+["']?([^"']+)["']?\s+(?:in|into|in the|into the)\s+(.+)/i,
24
+ type: 'type',
25
+ extract: (match) => ({ text: match[2].trim(), target: match[3].trim() })
26
+ },
27
+ {
28
+ pattern: /^(type|enter|write|input)\s+(.+)\s+(?:in|into)\s+["']?([^"']+)["']?/i,
29
+ type: 'type',
30
+ extract: (match) => ({ target: match[2].trim(), text: match[3].trim() })
31
+ },
32
+ {
33
+ pattern: /^(fill|fill in|complete)\s+(?:the\s+)?(.+)\s+(?:with|as)\s+["']?([^"']+)["']?/i,
34
+ type: 'type',
35
+ extract: (match) => ({ target: match[2].trim(), text: match[3].trim() })
36
+ },
37
+ // Navigate patterns
38
+ {
39
+ pattern: /^(go to|navigate to|open|visit)\s+(.+)/i,
40
+ type: 'navigate',
41
+ extract: (match) => {
42
+ let url = match[2].trim();
43
+ if (!url.startsWith('http')) {
44
+ url = 'https://' + url;
45
+ }
46
+ return { url };
47
+ }
48
+ },
49
+ // Scroll patterns
50
+ {
51
+ pattern: /^scroll\s+(up|down|left|right)(?:\s+(\d+)\s*(?:px|pixels)?)?/i,
52
+ type: 'scroll',
53
+ extract: (match) => ({
54
+ direction: match[1].toLowerCase(),
55
+ amount: parseInt(match[2]) || 300
56
+ })
57
+ },
58
+ {
59
+ pattern: /^scroll\s+to\s+(?:the\s+)?(top|bottom|footer|header)/i,
60
+ type: 'scroll',
61
+ extract: (match) => {
62
+ const target = match[1].toLowerCase();
63
+ return {
64
+ direction: target === 'top' || target === 'header' ? 'up' : 'down',
65
+ amount: 10000,
66
+ scrollTo: target
67
+ };
68
+ }
69
+ },
70
+ // Wait patterns
71
+ {
72
+ pattern: /^wait\s+(?:for\s+)?(\d+)\s*(?:ms|milliseconds?|s|seconds?)?/i,
73
+ type: 'wait',
74
+ extract: (match) => {
75
+ let duration = parseInt(match[1]);
76
+ const unit = match[0].toLowerCase();
77
+ if (unit.includes('s') && !unit.includes('ms')) {
78
+ duration *= 1000;
79
+ }
80
+ return { duration };
81
+ }
82
+ },
83
+ {
84
+ pattern: /^wait\s+(?:for\s+)?(?:the\s+)?(.+?)(?:\s+to\s+(?:appear|load|show))?$/i,
85
+ type: 'waitFor',
86
+ extract: (match) => ({ target: match[1].trim() })
87
+ },
88
+ // Find/Search patterns
89
+ {
90
+ pattern: /^(find|search|look for|locate)\s+(?:the\s+)?(.+)/i,
91
+ type: 'find',
92
+ extract: (match) => ({ query: match[2].trim() })
93
+ },
94
+ // Hover patterns
95
+ {
96
+ pattern: /^(hover|mouse over|move to)\s+(?:the\s+)?(.+)/i,
97
+ type: 'hover',
98
+ extract: (match) => ({ target: match[2].trim() })
99
+ },
100
+ // Clear patterns
101
+ {
102
+ pattern: /^(clear|empty|delete)\s+(?:the\s+)?(.+)/i,
103
+ type: 'clear',
104
+ extract: (match) => ({ target: match[2].trim() })
105
+ },
106
+ // Submit patterns
107
+ {
108
+ pattern: /^submit\s+(?:the\s+)?(?:form)?(.*)$/i,
109
+ type: 'submit',
110
+ extract: (match) => ({ target: match[1].trim() || 'form' })
111
+ },
112
+ // Go back/forward patterns
113
+ {
114
+ pattern: /^go\s+(back|forward)/i,
115
+ type: 'navigation',
116
+ extract: (match) => ({ direction: match[1].toLowerCase() })
117
+ },
118
+ // Refresh patterns
119
+ {
120
+ pattern: /^(refresh|reload)\s*(?:the\s+)?(?:page)?/i,
121
+ type: 'refresh',
122
+ extract: () => ({})
123
+ }
124
+ ];
125
+ // Context variable patterns (for substitution)
126
+ this.variablePattern = /\{(\w+)\}/g;
127
+ }
128
+ /**
129
+ * Parse a natural language command
130
+ */
131
+ async parse(command, context = {}) {
132
+ // Substitute context variables
133
+ let processedCommand = command.replace(this.variablePattern, (match, varName) => {
134
+ return context[varName] !== undefined ? context[varName] : match;
135
+ });
136
+ // Trim and normalize
137
+ processedCommand = processedCommand.trim();
138
+ // Try each pattern
139
+ for (const pattern of this.actionPatterns) {
140
+ const match = processedCommand.match(pattern.pattern);
141
+ if (match) {
142
+ const extracted = pattern.extract(match);
143
+ return {
144
+ type: pattern.type,
145
+ ...extracted,
146
+ originalCommand: command,
147
+ confidence: 0.9
148
+ };
149
+ }
150
+ }
151
+ // Fallback: Try to guess action from keywords
152
+ const fallback = this.guessFallback(processedCommand);
153
+ if (fallback) {
154
+ return {
155
+ ...fallback,
156
+ originalCommand: command,
157
+ confidence: 0.5
158
+ };
159
+ }
160
+ // Could not parse
161
+ return {
162
+ type: 'unknown',
163
+ originalCommand: command,
164
+ confidence: 0,
165
+ error: 'Could not understand command'
166
+ };
167
+ }
168
+ /**
169
+ * Try to guess action from keywords
170
+ */
171
+ guessFallback(command) {
172
+ const lower = command.toLowerCase();
173
+ // Check for action keywords
174
+ if (lower.includes('button') || lower.includes('link') || lower.includes('click')) {
175
+ return { type: 'click', target: command };
176
+ }
177
+ if (lower.includes('type') || lower.includes('enter') || lower.includes('input')) {
178
+ return { type: 'find', query: command, suggestedAction: 'type' };
179
+ }
180
+ if (lower.includes('search') || lower.includes('find') || lower.includes('look')) {
181
+ return { type: 'find', query: command };
182
+ }
183
+ if (lower.includes('scroll')) {
184
+ return { type: 'scroll', direction: 'down', amount: 300 };
185
+ }
186
+ // Default to find
187
+ return { type: 'find', query: command };
188
+ }
189
+ /**
190
+ * Parse multiple commands (separated by 'then', 'and', or newlines)
191
+ */
192
+ async parseMultiple(commands, context = {}) {
193
+ // Split by separators
194
+ const parts = commands.split(/\s+(?:then|and)\s+|\n|;/i).filter(p => p.trim());
195
+ const results = [];
196
+ for (const part of parts) {
197
+ const parsed = await this.parse(part.trim(), context);
198
+ results.push(parsed);
199
+ }
200
+ return results;
201
+ }
202
+ /**
203
+ * Get suggestions for incomplete commands
204
+ */
205
+ getSuggestions(partialCommand) {
206
+ const lower = partialCommand.toLowerCase();
207
+ const suggestions = [];
208
+ if (lower.startsWith('click')) {
209
+ suggestions.push('click the login button', 'click the submit button', 'click the link', 'click the menu');
210
+ }
211
+ else if (lower.startsWith('type')) {
212
+ suggestions.push('type "text" in the search box', 'type "username" in the login field', 'type "hello" in the input');
213
+ }
214
+ else if (lower.startsWith('go')) {
215
+ suggestions.push('go to google.com', 'go back', 'go forward');
216
+ }
217
+ else if (lower.startsWith('scroll')) {
218
+ suggestions.push('scroll down', 'scroll up', 'scroll to the bottom', 'scroll to the top');
219
+ }
220
+ else if (lower.startsWith('wait')) {
221
+ suggestions.push('wait 2 seconds', 'wait for the button to appear', 'wait 500ms');
222
+ }
223
+ else if (lower.startsWith('find')) {
224
+ suggestions.push('find the login button', 'find the search input', 'find all links');
225
+ }
226
+ return suggestions;
227
+ }
228
+ }
229
+ module.exports = ActionParser;
@@ -0,0 +1,367 @@
1
+ "use strict";
2
+ // @ts-nocheck
3
+ /**
4
+ * AI Core Module - Foundation for all AI-powered features
5
+ *
6
+ * This module provides AI capabilities that are automatically available
7
+ * to ALL tools in the project. Any new tool added will automatically
8
+ * benefit from these AI features.
9
+ *
10
+ * Features:
11
+ * - Smart element finding with multiple strategies
12
+ * - Auto-healing selectors when they break
13
+ * - Page understanding and structure analysis
14
+ * - Natural language command parsing
15
+ * - Confidence scoring for element matches
16
+ * - Fallback strategies when primary method fails
17
+ */
18
+ const ElementFinder = require('./element-finder');
19
+ const SelectorHealer = require('./selector-healer');
20
+ const PageAnalyzer = require('./page-analyzer');
21
+ const ActionParser = require('./action-parser');
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+ /**
25
+ * AI Core class - Central AI intelligence for the browser automation
26
+ */
27
+ class AICore {
28
+ constructor() {
29
+ this.elementFinder = new ElementFinder();
30
+ this.selectorHealer = new SelectorHealer();
31
+ this.pageAnalyzer = new PageAnalyzer();
32
+ this.actionParser = new ActionParser();
33
+ // Cache for performance
34
+ this.pageCache = new Map();
35
+ this.selectorCache = new Map();
36
+ // Configuration
37
+ this.config = {
38
+ defaultConfidence: 0.7,
39
+ maxCacheAge: 30000, // 30 seconds
40
+ enableAutoHeal: true,
41
+ enableSmartFind: true,
42
+ logLevel: 'info', // 'debug' | 'info' | 'warn' | 'error'
43
+ cacheFile: path.join(process.cwd(), '.cache', 'ai_cache.json')
44
+ };
45
+ this.loadCache();
46
+ }
47
+ /**
48
+ * Load cache from disk
49
+ */
50
+ loadCache() {
51
+ try {
52
+ if (fs.existsSync(this.config.cacheFile)) {
53
+ const data = JSON.parse(fs.readFileSync(this.config.cacheFile, 'utf8'));
54
+ if (data.pageCache) {
55
+ this.pageCache = new Map(Object.entries(data.pageCache));
56
+ }
57
+ if (data.selectorCache) {
58
+ this.selectorCache = new Map(Object.entries(data.selectorCache));
59
+ }
60
+ this.log('info', 'Loaded AI cache from disk');
61
+ }
62
+ }
63
+ catch (e) {
64
+ this.log('warn', `Failed to load cache: ${e.message}`);
65
+ }
66
+ }
67
+ /**
68
+ * Save cache to disk
69
+ */
70
+ saveCache() {
71
+ try {
72
+ const dir = path.dirname(this.config.cacheFile);
73
+ if (!fs.existsSync(dir)) {
74
+ fs.mkdirSync(dir, { recursive: true });
75
+ }
76
+ const data = {
77
+ pageCache: Object.fromEntries(this.pageCache),
78
+ selectorCache: Object.fromEntries(this.selectorCache)
79
+ };
80
+ fs.writeFileSync(this.config.cacheFile, JSON.stringify(data, null, 2));
81
+ }
82
+ catch (e) {
83
+ this.log('warn', `Failed to save cache: ${e.message}`);
84
+ }
85
+ }
86
+ /**
87
+ * Configure AI Core settings
88
+ */
89
+ configure(options = {}) {
90
+ this.config = { ...this.config, ...options };
91
+ return this;
92
+ }
93
+ /**
94
+ * AI-Enhanced element finding
95
+ * Tries multiple strategies to find an element
96
+ */
97
+ async smartFind(page, query, options = {}) {
98
+ const { strategy = 'auto', context = null, confidence = this.config.defaultConfidence, returnMultiple = false } = options;
99
+ this.log('debug', `SmartFind: "${query}" with strategy: ${strategy}`);
100
+ const results = await this.elementFinder.find(page, query, {
101
+ strategy,
102
+ context,
103
+ confidence,
104
+ returnMultiple
105
+ });
106
+ this.log('info', `SmartFind found ${results.length} elements with confidence >= ${confidence}`);
107
+ return results;
108
+ }
109
+ /**
110
+ * AI-Enhanced click with auto-healing
111
+ * If selector fails, tries to find the element using AI
112
+ */
113
+ async smartClick(page, selector, options = {}) {
114
+ const { humanLike = true, autoHeal = this.config.enableAutoHeal } = options;
115
+ try {
116
+ // Try original selector first
117
+ const element = await page.$(selector);
118
+ if (element) {
119
+ if (humanLike) {
120
+ try {
121
+ const { createCursor } = require('ghost-cursor-patchright');
122
+ const cursor = createCursor(page);
123
+ await cursor.click(selector);
124
+ }
125
+ catch {
126
+ await element.click();
127
+ }
128
+ }
129
+ else {
130
+ await element.click();
131
+ }
132
+ return { success: true, selector, healed: false };
133
+ }
134
+ }
135
+ catch (e) {
136
+ this.log('warn', `Original selector failed: ${selector}`);
137
+ }
138
+ // Auto-heal if enabled
139
+ if (autoHeal) {
140
+ this.log('info', `Attempting to heal selector: ${selector}`);
141
+ const healed = await this.healAndExecute(page, selector, 'click', options);
142
+ if (healed.success) {
143
+ return { ...healed, healed: true };
144
+ }
145
+ }
146
+ return { success: false, error: `Element not found: ${selector}` };
147
+ }
148
+ /**
149
+ * AI-Enhanced type with auto-healing
150
+ */
151
+ async smartType(page, selector, text, options = {}) {
152
+ const { delay = 50, clear = false, autoHeal = this.config.enableAutoHeal } = options;
153
+ try {
154
+ const element = await page.$(selector);
155
+ if (element) {
156
+ if (clear) {
157
+ await element.click({ clickCount: 3 });
158
+ await page.keyboard.press('Backspace');
159
+ }
160
+ await element.type(text, { delay });
161
+ return { success: true, selector, healed: false };
162
+ }
163
+ }
164
+ catch (e) {
165
+ this.log('warn', `Original selector failed: ${selector}`);
166
+ }
167
+ if (autoHeal) {
168
+ const healed = await this.healAndExecute(page, selector, 'type', { text, ...options });
169
+ if (healed.success) {
170
+ return { ...healed, healed: true };
171
+ }
172
+ }
173
+ return { success: false, error: `Element not found: ${selector}` };
174
+ }
175
+ /**
176
+ * Heal a broken selector and execute action
177
+ */
178
+ async healAndExecute(page, brokenSelector, action, options = {}) {
179
+ const alternatives = await this.selectorHealer.heal(page, brokenSelector, {
180
+ maxAlternatives: 5
181
+ });
182
+ for (const alt of alternatives) {
183
+ try {
184
+ const element = await page.$(alt.selector);
185
+ if (element) {
186
+ this.log('info', `Healed selector: ${brokenSelector} -> ${alt.selector} (confidence: ${alt.confidence})`);
187
+ // Cache the healed selector
188
+ this.selectorCache.set(brokenSelector, {
189
+ healed: alt.selector,
190
+ timestamp: Date.now()
191
+ });
192
+ this.saveCache();
193
+ // Execute action
194
+ if (action === 'click') {
195
+ await element.click();
196
+ }
197
+ else if (action === 'type') {
198
+ if (options.clear) {
199
+ await element.click({ clickCount: 3 });
200
+ await page.keyboard.press('Backspace');
201
+ }
202
+ await element.type(options.text, { delay: options.delay || 50 });
203
+ }
204
+ return { success: true, selector: alt.selector, originalSelector: brokenSelector };
205
+ }
206
+ }
207
+ catch (e) {
208
+ continue;
209
+ }
210
+ }
211
+ return { success: false, error: 'Could not heal selector' };
212
+ }
213
+ /**
214
+ * Understand page structure
215
+ */
216
+ async understandPage(page, options = {}) {
217
+ const cacheKey = page.url();
218
+ const cached = this.pageCache.get(cacheKey);
219
+ if (cached && (Date.now() - cached.timestamp) < this.config.maxCacheAge) {
220
+ this.log('debug', 'Using cached page analysis');
221
+ return cached.analysis;
222
+ }
223
+ const analysis = await this.pageAnalyzer.analyze(page, options);
224
+ this.pageCache.set(cacheKey, {
225
+ analysis,
226
+ timestamp: Date.now()
227
+ });
228
+ this.saveCache();
229
+ return analysis;
230
+ }
231
+ /**
232
+ * Parse natural language command and execute
233
+ */
234
+ async executeCommand(page, command, options = {}) {
235
+ const { context = {}, dryRun = false, humanLike = true } = options;
236
+ this.log('info', `Parsing command: "${command}"`);
237
+ const parsed = await this.actionParser.parse(command, context);
238
+ if (dryRun) {
239
+ return { success: true, dryRun: true, parsed };
240
+ }
241
+ // Execute parsed action
242
+ return await this.executeAction(page, parsed, { humanLike });
243
+ }
244
+ /**
245
+ * Execute a parsed action
246
+ */
247
+ async executeAction(page, action, options = {}) {
248
+ const { humanLike = true } = options;
249
+ switch (action.type) {
250
+ case 'click':
251
+ return await this.smartClick(page, action.target, { humanLike });
252
+ case 'type':
253
+ return await this.smartType(page, action.target, action.text, { humanLike });
254
+ case 'navigate':
255
+ // Playwright/Patchright waitUntil: load|domcontentloaded|networkidle|commit
256
+ await page.goto(action.url, { waitUntil: 'networkidle' });
257
+ return { success: true, url: action.url };
258
+ case 'scroll':
259
+ await page.evaluate((direction, amount) => {
260
+ window.scrollBy({ top: direction === 'up' ? -amount : amount, behavior: 'smooth' });
261
+ }, action.direction || 'down', action.amount || 300);
262
+ return { success: true, direction: action.direction };
263
+ case 'wait':
264
+ await new Promise(r => setTimeout(r, action.duration || 1000));
265
+ return { success: true, waited: action.duration };
266
+ case 'find':
267
+ const results = await this.smartFind(page, action.query);
268
+ return { success: true, found: results.length, elements: results };
269
+ default:
270
+ return { success: false, error: `Unknown action type: ${action.type}` };
271
+ }
272
+ }
273
+ /**
274
+ * Wrap any handler with AI capabilities
275
+ * This allows existing handlers to benefit from AI features
276
+ */
277
+ wrapHandler(handler, handlerName) {
278
+ const aiCore = this;
279
+ return async function aiEnhancedHandler(params = {}) {
280
+ const startTime = Date.now();
281
+ // Check if AI features are requested
282
+ const useAI = params._useAI !== false;
283
+ const autoHeal = params._autoHeal !== false && aiCore.config.enableAutoHeal;
284
+ try {
285
+ // Execute original handler
286
+ const result = await handler(params);
287
+ // If success, return result
288
+ if (result.success) {
289
+ return {
290
+ ...result,
291
+ _ai: { used: false, duration: Date.now() - startTime }
292
+ };
293
+ }
294
+ // If failed and autoHeal is enabled, try AI recovery
295
+ if (autoHeal && result.error?.includes('not found')) {
296
+ aiCore.log('info', `AI attempting recovery for ${handlerName}`);
297
+ // Extract selector from params
298
+ const selector = params.selector || params.target;
299
+ if (selector) {
300
+ const healed = await aiCore.selectorHealer.heal(params._page, selector, { maxAlternatives: 3 });
301
+ if (healed.length > 0) {
302
+ // Retry with healed selector
303
+ const retryParams = { ...params, selector: healed[0].selector };
304
+ const retryResult = await handler(retryParams);
305
+ return {
306
+ ...retryResult,
307
+ _ai: {
308
+ used: true,
309
+ healed: true,
310
+ originalSelector: selector,
311
+ healedSelector: healed[0].selector,
312
+ duration: Date.now() - startTime
313
+ }
314
+ };
315
+ }
316
+ }
317
+ }
318
+ return {
319
+ ...result,
320
+ _ai: { used: useAI, duration: Date.now() - startTime }
321
+ };
322
+ }
323
+ catch (error) {
324
+ aiCore.log('error', `Handler ${handlerName} failed: ${error.message}`);
325
+ return {
326
+ success: false,
327
+ error: error.message,
328
+ _ai: { used: useAI, duration: Date.now() - startTime }
329
+ };
330
+ }
331
+ };
332
+ }
333
+ /**
334
+ * Clear caches
335
+ */
336
+ clearCache() {
337
+ this.pageCache.clear();
338
+ this.selectorCache.clear();
339
+ this.saveCache();
340
+ this.log('info', 'AI caches cleared');
341
+ }
342
+ /**
343
+ * Logging utility
344
+ */
345
+ log(level, message) {
346
+ const levels = ['debug', 'info', 'warn', 'error'];
347
+ const configLevel = levels.indexOf(this.config.logLevel);
348
+ const msgLevel = levels.indexOf(level);
349
+ if (msgLevel >= configLevel) {
350
+ const emoji = { debug: '🔍', info: '🤖', warn: '⚠️', error: '❌' }[level];
351
+ console.error(`${emoji} [AI] ${message}`);
352
+ }
353
+ }
354
+ }
355
+ // Singleton instance
356
+ const aiCore = new AICore();
357
+ module.exports = {
358
+ AICore,
359
+ aiCore,
360
+ smartFind: (page, query, options) => aiCore.smartFind(page, query, options),
361
+ smartClick: (page, selector, options) => aiCore.smartClick(page, selector, options),
362
+ smartType: (page, selector, text, options) => aiCore.smartType(page, selector, text, options),
363
+ understandPage: (page, options) => aiCore.understandPage(page, options),
364
+ executeCommand: (page, command, options) => aiCore.executeCommand(page, command, options),
365
+ wrapHandler: (handler, name) => aiCore.wrapHandler(handler, name),
366
+ configure: (options) => aiCore.configure(options)
367
+ };