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