real-browser-mcp-server 1.3.4 → 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 +229 -184
- 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 -352
- 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 -267
- package/test/esm/package.json +0 -13
- package/test/esm/test.js +0 -235
package/lib/cjs/index.js
DELETED
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
const { PlaywrightBlocker } = require("@ghostery/adblocker-playwright");
|
|
2
|
-
const { pageController } = require("./module/pageController.js");
|
|
3
|
-
|
|
4
|
-
const { chromium } = require("patchright");
|
|
5
|
-
const { createCursor } = require("ghost-cursor-patchright");
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
|
|
9
|
-
let adBlockerInstance = null;
|
|
10
|
-
let adBlockerPromise = null;
|
|
11
|
-
function getAdBlocker() {
|
|
12
|
-
if (!adBlockerPromise) {
|
|
13
|
-
const cachePath = path.join(__dirname, 'adblocker.bin');
|
|
14
|
-
adBlockerPromise = PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch, {
|
|
15
|
-
path: cachePath,
|
|
16
|
-
read: fs.promises.readFile,
|
|
17
|
-
write: fs.promises.writeFile,
|
|
18
|
-
}).then(blocker => {
|
|
19
|
-
adBlockerInstance = blocker;
|
|
20
|
-
return blocker;
|
|
21
|
-
}).catch(err => {
|
|
22
|
-
console.error('[adblocker] Failed to initialize adblocker:', err.message);
|
|
23
|
-
return null;
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
return adBlockerPromise;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function loadEnvFile() {
|
|
30
|
-
const envPaths = [
|
|
31
|
-
path.join(process.cwd(), '.env'),
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
let currentDir = process.cwd();
|
|
35
|
-
for (let i = 0; i < 5; i++) {
|
|
36
|
-
const envPath = path.join(currentDir, '.env');
|
|
37
|
-
if (fs.existsSync(envPath) && !envPaths.includes(envPath)) {
|
|
38
|
-
envPaths.push(envPath);
|
|
39
|
-
}
|
|
40
|
-
const parentDir = path.dirname(currentDir);
|
|
41
|
-
if (parentDir === currentDir) break;
|
|
42
|
-
currentDir = parentDir;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
for (const envPath of envPaths) {
|
|
46
|
-
try {
|
|
47
|
-
if (fs.existsSync(envPath)) {
|
|
48
|
-
const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
49
|
-
envContent.split('\n').forEach(line => {
|
|
50
|
-
const trimmed = line.trim();
|
|
51
|
-
if (trimmed && !trimmed.startsWith('#')) {
|
|
52
|
-
const [key, ...valueParts] = trimmed.split('=');
|
|
53
|
-
const value = valueParts.join('=').replace(/^["']|["']$/g, '');
|
|
54
|
-
if (key && !process.env[key]) {
|
|
55
|
-
process.env[key] = value;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
} catch (error) {
|
|
62
|
-
// Silently ignore .env loading errors
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
loadEnvFile();
|
|
68
|
-
|
|
69
|
-
function getDefaultHeadless() {
|
|
70
|
-
const envHeadless = (process.env.HEADLESS || '').toLowerCase();
|
|
71
|
-
return envHeadless === 'true';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function applyBrowserShims(browser, page) {
|
|
75
|
-
if (page._shimsApplied) return page;
|
|
76
|
-
page._shimsApplied = true;
|
|
77
|
-
|
|
78
|
-
// Enable ad blocker
|
|
79
|
-
if (adBlockerInstance) {
|
|
80
|
-
adBlockerInstance.enableBlockingInPage(page).catch(() => {});
|
|
81
|
-
} else {
|
|
82
|
-
getAdBlocker().then(blocker => {
|
|
83
|
-
if (blocker) {
|
|
84
|
-
blocker.enableBlockingInPage(page).catch(() => {});
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Cookie shims (Compatibility adapter for Playwright context)
|
|
90
|
-
if (!page.cookies) {
|
|
91
|
-
page.cookies = async (urls) => {
|
|
92
|
-
return await page.context().cookies(urls ? (Array.isArray(urls) ? urls : [urls]) : []);
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (!page.setCookie) {
|
|
97
|
-
page.setCookie = async (...cookies) => {
|
|
98
|
-
const formatted = cookies.map(c => ({
|
|
99
|
-
name: c.name,
|
|
100
|
-
value: c.value,
|
|
101
|
-
domain: c.domain || new URL(page.url()).hostname,
|
|
102
|
-
path: c.path || '/',
|
|
103
|
-
expires: c.expires || undefined,
|
|
104
|
-
httpOnly: c.httpOnly,
|
|
105
|
-
secure: c.secure,
|
|
106
|
-
sameSite: c.sameSite
|
|
107
|
-
}));
|
|
108
|
-
await page.context().addCookies(formatted);
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (!page.deleteCookie) {
|
|
113
|
-
page.deleteCookie = async (...cookies) => {
|
|
114
|
-
await page.context().clearCookies();
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Navigation shims
|
|
119
|
-
if (!page.waitForNetworkIdle) {
|
|
120
|
-
page.waitForNetworkIdle = async (options = {}) => {
|
|
121
|
-
try {
|
|
122
|
-
await page.waitForLoadState('networkidle', { timeout: options.timeout });
|
|
123
|
-
} catch (e) {
|
|
124
|
-
// Ignore networkidle timeouts if they are non-fatal
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (!page.evaluateOnNewDocument) {
|
|
130
|
-
page.evaluateOnNewDocument = async (fn, ...args) => {
|
|
131
|
-
return await page.addInitScript(fn, ...args);
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Ghost Cursor integration via ghost-cursor-patchright
|
|
136
|
-
const cursorPromise = createCursor(page).catch(err => {
|
|
137
|
-
console.error("Failed to create cursor:", err);
|
|
138
|
-
return null;
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
page.realCursor = {
|
|
142
|
-
move: async (selector, options = {}) => {
|
|
143
|
-
try {
|
|
144
|
-
const cursor = await cursorPromise;
|
|
145
|
-
if (cursor) {
|
|
146
|
-
await cursor.move(selector, options);
|
|
147
|
-
} else {
|
|
148
|
-
await page.hover(selector, options);
|
|
149
|
-
}
|
|
150
|
-
} catch (e) {
|
|
151
|
-
// Silently fallback if element is not found
|
|
152
|
-
try {
|
|
153
|
-
await page.hover(selector, options);
|
|
154
|
-
} catch (err) {}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
page.realClick = async (selector, options = {}) => {
|
|
159
|
-
try {
|
|
160
|
-
const cursor = await cursorPromise;
|
|
161
|
-
if (cursor) {
|
|
162
|
-
await cursor.click(selector, options);
|
|
163
|
-
} else {
|
|
164
|
-
await page.click(selector, options);
|
|
165
|
-
}
|
|
166
|
-
} catch (e) {
|
|
167
|
-
await page.click(selector, options);
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
// Mouse wheel shim (Adapter for Playwright positional args)
|
|
172
|
-
if (page.mouse && page.mouse.wheel) {
|
|
173
|
-
const originalWheel = page.mouse.wheel.bind(page.mouse);
|
|
174
|
-
page.mouse.wheel = async (x, y) => {
|
|
175
|
-
if (typeof x === 'object' && x !== null) {
|
|
176
|
-
const deltaX = x.deltaX || 0;
|
|
177
|
-
const deltaY = x.deltaY || 0;
|
|
178
|
-
return await originalWheel(deltaX, deltaY);
|
|
179
|
-
}
|
|
180
|
-
return await originalWheel(x, y);
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Browser shims
|
|
185
|
-
if (!browser.process) {
|
|
186
|
-
browser.process = () => ({
|
|
187
|
-
pid: browser._childProcess?.pid || 1337
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (!browser.pages) {
|
|
192
|
-
browser.pages = async () => {
|
|
193
|
-
return browser.contexts().flatMap(c => c.pages());
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return page;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async function connect({
|
|
201
|
-
args = [],
|
|
202
|
-
headless = getDefaultHeadless(),
|
|
203
|
-
proxy = {},
|
|
204
|
-
turnstile = false,
|
|
205
|
-
disableXvfb = false,
|
|
206
|
-
} = {}) {
|
|
207
|
-
let xvfbInstance = null;
|
|
208
|
-
if (process.platform === 'linux' && !process.env.DISPLAY && !disableXvfb) {
|
|
209
|
-
try {
|
|
210
|
-
const Xvfb = require('xvfb');
|
|
211
|
-
xvfbInstance = new Xvfb({
|
|
212
|
-
silent: true,
|
|
213
|
-
xvfb_args: ['-screen', '0', '1280x1024x24', '-ac']
|
|
214
|
-
});
|
|
215
|
-
xvfbInstance.startSync();
|
|
216
|
-
} catch (err) {
|
|
217
|
-
console.error('[xvfb] Failed to start Xvfb server automatically:', err.message);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
let playwrightProxy = undefined;
|
|
222
|
-
if (proxy && proxy.host && proxy.port) {
|
|
223
|
-
playwrightProxy = {
|
|
224
|
-
server: `${proxy.host}:${proxy.port}`
|
|
225
|
-
};
|
|
226
|
-
if (proxy.username && proxy.password) {
|
|
227
|
-
playwrightProxy.username = proxy.username;
|
|
228
|
-
playwrightProxy.password = proxy.password;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const chromiumArgs = [
|
|
233
|
-
'--disable-blink-features=AutomationControlled',
|
|
234
|
-
'--no-sandbox',
|
|
235
|
-
'--disable-setuid-sandbox',
|
|
236
|
-
'--window-size=1920,1080',
|
|
237
|
-
...args
|
|
238
|
-
];
|
|
239
|
-
|
|
240
|
-
if (headless) {
|
|
241
|
-
chromiumArgs.push(
|
|
242
|
-
'--headless=new',
|
|
243
|
-
'--disable-gpu',
|
|
244
|
-
'--hide-scrollbars',
|
|
245
|
-
'--mute-audio'
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function getBravePath() {
|
|
250
|
-
if (process.platform === 'win32') {
|
|
251
|
-
const paths = [
|
|
252
|
-
"C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
|
|
253
|
-
"C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
|
|
254
|
-
path.join(process.env.LOCALAPPDATA || '', 'BraveSoftware\\Brave-Browser\\Application\\brave.exe'),
|
|
255
|
-
path.join(process.env.PROGRAMFILES || '', 'BraveSoftware\\Brave-Browser\\Application\\brave.exe'),
|
|
256
|
-
path.join(process.env['PROGRAMFILES(X86)'] || '', 'BraveSoftware\\Brave-Browser\\Application\\brave.exe')
|
|
257
|
-
];
|
|
258
|
-
for (const p of paths) {
|
|
259
|
-
if (p && fs.existsSync(p)) return p;
|
|
260
|
-
}
|
|
261
|
-
} else if (process.platform === 'darwin') {
|
|
262
|
-
const paths = [
|
|
263
|
-
'/Applications/Brave Browser.app/Contents/MacOS/Brave Browser'
|
|
264
|
-
];
|
|
265
|
-
for (const p of paths) {
|
|
266
|
-
if (fs.existsSync(p)) return p;
|
|
267
|
-
}
|
|
268
|
-
} else if (process.platform === 'linux') {
|
|
269
|
-
const paths = [
|
|
270
|
-
'/usr/bin/brave-browser',
|
|
271
|
-
'/usr/bin/brave'
|
|
272
|
-
];
|
|
273
|
-
for (const p of paths) {
|
|
274
|
-
if (fs.existsSync(p)) return p;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const bravePath = getBravePath();
|
|
281
|
-
if (!bravePath) {
|
|
282
|
-
throw new Error("Brave Browser was not found on this system. Real Browser MCP Server is configured to exclusively use Brave Browser.");
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
let browser;
|
|
286
|
-
try {
|
|
287
|
-
browser = await chromium.launch({
|
|
288
|
-
headless: false, // Prevent Playwright from adding leaky headless shims
|
|
289
|
-
args: chromiumArgs,
|
|
290
|
-
proxy: playwrightProxy,
|
|
291
|
-
executablePath: bravePath
|
|
292
|
-
});
|
|
293
|
-
console.error('[Launch] Successfully launched system Brave Browser');
|
|
294
|
-
} catch (err) {
|
|
295
|
-
console.error('[Launch] Failed to launch system Brave Browser:', err.message);
|
|
296
|
-
throw err;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (xvfbInstance) {
|
|
300
|
-
browser.on('disconnected', () => {
|
|
301
|
-
try {
|
|
302
|
-
xvfbInstance.stopSync();
|
|
303
|
-
} catch (e) {
|
|
304
|
-
console.error('[xvfb] Failed to stop Xvfb server:', e.message);
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
const originalClose = browser.close.bind(browser);
|
|
308
|
-
browser.close = async () => {
|
|
309
|
-
await originalClose();
|
|
310
|
-
try {
|
|
311
|
-
xvfbInstance.stopSync();
|
|
312
|
-
} catch (e) {
|
|
313
|
-
console.error('[xvfb] Failed to stop Xvfb server during close:', e.message);
|
|
314
|
-
}
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Ensure ad blocker is ready
|
|
319
|
-
await getAdBlocker();
|
|
320
|
-
|
|
321
|
-
const context = await browser.newContext({
|
|
322
|
-
viewport: null,
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
let page = await context.newPage();
|
|
326
|
-
|
|
327
|
-
applyBrowserShims(browser, page);
|
|
328
|
-
|
|
329
|
-
page = await pageController({
|
|
330
|
-
browser,
|
|
331
|
-
page,
|
|
332
|
-
proxy,
|
|
333
|
-
turnstile,
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
context.on('page', async (newPage) => {
|
|
337
|
-
applyBrowserShims(browser, newPage);
|
|
338
|
-
await pageController({
|
|
339
|
-
browser,
|
|
340
|
-
page: newPage,
|
|
341
|
-
proxy,
|
|
342
|
-
turnstile,
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
return {
|
|
347
|
-
browser,
|
|
348
|
-
page,
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
module.exports = { connect };
|
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;
|