brave-real-browser-mcp-server 2.36.0 → 2.37.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 +113 -1
- package/package.json +4 -3
- package/packages/brave-real-blocker/package.json +2 -2
- package/packages/brave-real-launcher/package.json +2 -2
- package/packages/brave-real-puppeteer-core/package.json +2 -2
- package/src/index.js +66 -28
- package/src/lsp/server.js +160 -54
- package/src/mcp/handlers.js +222 -16
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ A production-ready MCP (Model Context Protocol) server that combines Puppeteer w
|
|
|
14
14
|
|
|
15
15
|
| Feature | Description |
|
|
16
16
|
|---------|-------------|
|
|
17
|
-
| MCP Server | Model Context Protocol compatible server |
|
|
17
|
+
| **MCP Server** | Model Context Protocol compatible server with 28 tools |
|
|
18
|
+
| **LSP Server** | Language Server Protocol for IDE code intelligence |
|
|
18
19
|
| Brave Browser | Uses Brave instead of Chromium for better privacy |
|
|
19
20
|
| 50+ Stealth Features | Passes all major bot detectors |
|
|
20
21
|
| Built-in Ad Blocker | uBlock Origin filters with auto-update |
|
|
@@ -25,6 +26,7 @@ A production-ready MCP (Model Context Protocol) server that combines Puppeteer w
|
|
|
25
26
|
| Auto-Install | Brave auto-installs if missing |
|
|
26
27
|
| TypeScript Support | Full type definitions included |
|
|
27
28
|
| ESM + CJS | Dual module support |
|
|
29
|
+
| **Multi-language** | English & Hindi language support |
|
|
28
30
|
| **Auto-Update** | Daily automatic dependency updates |
|
|
29
31
|
| **Auto-Publish** | Automatic NPM publishing on updates |
|
|
30
32
|
| **Monorepo** | npm workspaces with linked packages |
|
|
@@ -127,6 +129,113 @@ AI: [Calls file_downloader with url="..."] -> Downloaded to ./downloads/image.pn
|
|
|
127
129
|
|
|
128
130
|
---
|
|
129
131
|
|
|
132
|
+
## LSP Server (Language Server Protocol)
|
|
133
|
+
|
|
134
|
+
This package also includes a full-featured **LSP Server** for IDE code intelligence when writing browser automation scripts.
|
|
135
|
+
|
|
136
|
+
### Quick Start LSP Server
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Start the LSP server
|
|
140
|
+
npm run lsp
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### LSP Capabilities
|
|
144
|
+
|
|
145
|
+
| Feature | Description |
|
|
146
|
+
|---------|-------------|
|
|
147
|
+
| **Autocomplete** | Tool names, parameters, and enum values |
|
|
148
|
+
| **Hover** | Full documentation on hover |
|
|
149
|
+
| **Diagnostics** | Error & warning detection (missing browser_init, etc.) |
|
|
150
|
+
| **Snippets** | Code templates for common workflows |
|
|
151
|
+
| **Refactoring** | Quick fixes (add browser_init, try-catch, etc.) |
|
|
152
|
+
| **Simulation** | Workflow simulation in IDE |
|
|
153
|
+
| **Multi-language** | English & Hindi support |
|
|
154
|
+
|
|
155
|
+
### VS Code Setup
|
|
156
|
+
|
|
157
|
+
Create `.vscode/settings.json` in your project:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"braveRealBrowser.language": "en",
|
|
162
|
+
"braveRealBrowser.maxDiagnostics": 100,
|
|
163
|
+
"braveRealBrowser.enableSnippets": true,
|
|
164
|
+
"braveRealBrowser.enableSimulation": true,
|
|
165
|
+
"braveRealBrowser.enableRefactoring": true
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Diagnostic Features
|
|
170
|
+
|
|
171
|
+
The LSP detects common issues:
|
|
172
|
+
|
|
173
|
+
- Missing `browser_init()` before page operations
|
|
174
|
+
- Missing `navigate()` before content extraction
|
|
175
|
+
- Invalid selectors and URLs
|
|
176
|
+
- Missing required parameters
|
|
177
|
+
- Unclosed browser sessions
|
|
178
|
+
- Security issues (eval usage, etc.)
|
|
179
|
+
|
|
180
|
+
### Quick Fixes
|
|
181
|
+
|
|
182
|
+
When diagnostics are detected, quick fixes are offered:
|
|
183
|
+
|
|
184
|
+
- Add `browser_init()` at start
|
|
185
|
+
- Add `navigate()` before page tools
|
|
186
|
+
- Add `browser_close()` at end
|
|
187
|
+
- Wrap in try-catch
|
|
188
|
+
- Extract to function
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Unified Architecture
|
|
193
|
+
|
|
194
|
+
Both MCP and LSP servers share the same tool definitions:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
src/
|
|
198
|
+
├── shared/
|
|
199
|
+
│ └── tools.js # Single source of truth (28 tools)
|
|
200
|
+
├── mcp/
|
|
201
|
+
│ ├── server.js # MCP server for AI agents
|
|
202
|
+
│ └── handlers.js # Tool implementations
|
|
203
|
+
├── lsp/
|
|
204
|
+
│ ├── server.js # LSP server for IDEs
|
|
205
|
+
│ └── capabilities/ # Autocomplete, hover, diagnostics, etc.
|
|
206
|
+
└── index.js # Unified entry point
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Unified CLI
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# List all tools
|
|
213
|
+
node src/index.js --list
|
|
214
|
+
|
|
215
|
+
# Start MCP server (default)
|
|
216
|
+
node src/index.js mcp
|
|
217
|
+
|
|
218
|
+
# Start LSP server
|
|
219
|
+
node src/index.js lsp
|
|
220
|
+
|
|
221
|
+
# Show help
|
|
222
|
+
node src/index.js --help
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Tool Categories
|
|
226
|
+
|
|
227
|
+
| Category | Tools | Description |
|
|
228
|
+
|----------|-------|-------------|
|
|
229
|
+
| **Browser** | 3 | Browser lifecycle (init, close, cookies) |
|
|
230
|
+
| **Navigation** | 1 | Page navigation |
|
|
231
|
+
| **Interaction** | 8 | User actions (click, type, scroll, etc.) |
|
|
232
|
+
| **Extraction** | 10 | Content scraping (HTML, JSON, links, etc.) |
|
|
233
|
+
| **Network** | 3 | Network operations (recorder, download, trace) |
|
|
234
|
+
| **Analysis** | 1 | Page analysis (SEO, performance, etc.) |
|
|
235
|
+
| **Utility** | 2 | Helpers (wait, progress tracker) |
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
130
239
|
## Monorepo Ecosystem
|
|
131
240
|
|
|
132
241
|
```
|
|
@@ -398,9 +507,12 @@ console.log(blocker === sameBlocker); // true
|
|
|
398
507
|
|
|
399
508
|
| Command | Description |
|
|
400
509
|
|---------|-------------|
|
|
510
|
+
| `npm start` | Start unified server (MCP by default) |
|
|
401
511
|
| `npm run dev` | Start MCP server |
|
|
402
512
|
| `npm run mcp` | Start MCP server (alias) |
|
|
403
513
|
| `npm run mcp:verbose` | Start MCP server with tool details |
|
|
514
|
+
| `npm run lsp` | Start LSP server for IDE intelligence |
|
|
515
|
+
| `npm run list` | List all 28 tools with categories |
|
|
404
516
|
| `npm install` | Install all dependencies with workspace linking |
|
|
405
517
|
| `npm test` | Run all tests (CJS + ESM) |
|
|
406
518
|
| `npm run cjs_test` | Run CommonJS tests only |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.37.0",
|
|
4
4
|
"description": "MCP Server for Brave Real Browser - Puppeteer with Brave Browser, Stealth Mode, Ad Blocker, and Turnstile Auto-Solver for undetectable web automation.",
|
|
5
5
|
"main": "lib/cjs/index.js",
|
|
6
6
|
"module": "lib/esm/index.mjs",
|
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"start": "node src/index.js",
|
|
31
|
-
"dev": "node src/index.js
|
|
31
|
+
"dev": "node src/index.js",
|
|
32
32
|
"mcp": "node src/index.js mcp",
|
|
33
33
|
"mcp:verbose": "node src/index.js mcp --verbose",
|
|
34
34
|
"lsp": "node src/index.js lsp",
|
|
35
|
+
"lsp:tcp": "node src/lsp/server.js --tcp",
|
|
35
36
|
"list": "node src/index.js --list",
|
|
36
37
|
"build": "echo 'Root package uses pre-built lib/ - no build needed'",
|
|
37
38
|
"build:self": "echo 'Root package uses pre-built lib/ - no build needed'",
|
|
@@ -70,7 +71,7 @@
|
|
|
70
71
|
"license": "ISC",
|
|
71
72
|
"dependencies": {
|
|
72
73
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
73
|
-
"brave-real-puppeteer-core": "^24.
|
|
74
|
+
"brave-real-puppeteer-core": "^24.52.0",
|
|
74
75
|
"ghost-cursor": "^1.4.2",
|
|
75
76
|
"puppeteer-extra": "^3.3.6",
|
|
76
77
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-blocker",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Advanced uBlock Origin management and stealth features for Brave Real Browser",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@types/adm-zip": "^0.5.5",
|
|
64
64
|
"@types/fs-extra": "^11.0.4",
|
|
65
65
|
"@types/node": "^20.0.0",
|
|
66
|
-
"brave-real-puppeteer-core": "^24.
|
|
66
|
+
"brave-real-puppeteer-core": "^24.52.0",
|
|
67
67
|
"mocha": "^10.4.0",
|
|
68
68
|
"puppeteer-core": "^24.35.0",
|
|
69
69
|
"sinon": "^17.0.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-launcher",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "Launch Brave Browser with ease from node. Based on chrome-launcher with Brave-specific support.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"typescript": "^5.0.0"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"brave-real-blocker": "^1.
|
|
57
|
+
"brave-real-blocker": "^1.13.0",
|
|
58
58
|
"escape-string-regexp": "^4.0.0",
|
|
59
59
|
"is-wsl": "^2.2.0",
|
|
60
60
|
"which": "^4.0.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-puppeteer-core",
|
|
3
|
-
"version": "24.
|
|
3
|
+
"version": "24.52.0",
|
|
4
4
|
"description": "🦁 Brave Real-World Optimized Puppeteer & Playwright Core with 1-5ms ultra-fast timing, 50+ professional stealth features, intelligent browser auto-detection, and 100% bot detection bypass. Features cross-platform Brave browser integration, comprehensive anti-detection, and breakthrough performance improvements.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"automation",
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
"test-version": "node ./scripts/test-version-management.js"
|
|
133
133
|
},
|
|
134
134
|
"dependencies": {
|
|
135
|
-
"brave-real-launcher": "^1.
|
|
135
|
+
"brave-real-launcher": "^1.19.0",
|
|
136
136
|
"get-east-asian-width": "^1.4.0",
|
|
137
137
|
"yargs": "^18.0.0"
|
|
138
138
|
},
|
package/src/index.js
CHANGED
|
@@ -3,20 +3,19 @@
|
|
|
3
3
|
* Brave Real Browser - Unified Entry Point
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* node src/index.js
|
|
7
|
-
* node src/index.js
|
|
8
|
-
* node src/index.js
|
|
6
|
+
* node src/index.js - Start both MCP + LSP together (default)
|
|
7
|
+
* node src/index.js mcp - Start MCP Server only
|
|
8
|
+
* node src/index.js lsp - Start LSP Server only (STDIO)
|
|
9
|
+
* node src/index.js --help - Show help
|
|
9
10
|
*
|
|
10
11
|
* npm scripts:
|
|
11
|
-
* npm run dev
|
|
12
|
-
* npm run mcp
|
|
13
|
-
* npm run lsp
|
|
12
|
+
* npm run dev - Start MCP + LSP together
|
|
13
|
+
* npm run mcp - Start MCP server only
|
|
14
|
+
* npm run lsp - Start LSP server only
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
* Claude
|
|
17
|
-
*
|
|
18
|
-
* For IDEs (LSP):
|
|
19
|
-
* VS Code, Neovim, Sublime Text, etc.
|
|
16
|
+
* Architecture:
|
|
17
|
+
* MCP Server → STDIO transport (for AI agents: Claude, Cursor, Copilot)
|
|
18
|
+
* LSP Server → TCP :7777 (for IDEs: VS Code, Neovim)
|
|
20
19
|
*/
|
|
21
20
|
|
|
22
21
|
const { TOOLS, TOOL_DISPLAY, CATEGORIES } = require('./shared/tools.js');
|
|
@@ -45,8 +44,9 @@ ${colors.bright}USAGE:${colors.reset}
|
|
|
45
44
|
node src/index.js [mode] [options]
|
|
46
45
|
|
|
47
46
|
${colors.bright}MODES:${colors.reset}
|
|
48
|
-
${colors.green}
|
|
49
|
-
${colors.green}
|
|
47
|
+
${colors.green}(default)${colors.reset} Start both MCP + LSP servers together
|
|
48
|
+
${colors.green}mcp${colors.reset} Start MCP Server only (for AI agents)
|
|
49
|
+
${colors.green}lsp${colors.reset} Start LSP Server only (for IDEs)
|
|
50
50
|
|
|
51
51
|
${colors.bright}OPTIONS:${colors.reset}
|
|
52
52
|
${colors.yellow}--help, -h${colors.reset} Show this help message
|
|
@@ -54,23 +54,25 @@ ${colors.bright}OPTIONS:${colors.reset}
|
|
|
54
54
|
${colors.yellow}--list${colors.reset} List all available tools
|
|
55
55
|
|
|
56
56
|
${colors.bright}EXAMPLES:${colors.reset}
|
|
57
|
-
node src/index.js
|
|
58
|
-
node src/index.js
|
|
57
|
+
node src/index.js # Start MCP + LSP together
|
|
58
|
+
node src/index.js mcp # Start MCP server only
|
|
59
|
+
node src/index.js lsp # Start LSP server only
|
|
59
60
|
node src/index.js --list # List all tools
|
|
60
61
|
|
|
61
62
|
${colors.bright}NPM SCRIPTS:${colors.reset}
|
|
62
|
-
npm run dev # Start MCP
|
|
63
|
-
npm run mcp # Start MCP server
|
|
64
|
-
npm run lsp # Start LSP server
|
|
63
|
+
npm run dev # Start MCP + LSP together
|
|
64
|
+
npm run mcp # Start MCP server only
|
|
65
|
+
npm run lsp # Start LSP server only (STDIO)
|
|
66
|
+
|
|
67
|
+
${colors.bright}ARCHITECTURE:${colors.reset}
|
|
68
|
+
${colors.cyan}MCP Server${colors.reset} → STDIO transport → AI Agents (Claude, Cursor, Copilot)
|
|
69
|
+
${colors.cyan}LSP Server${colors.reset} → TCP :7777 → IDEs (VS Code, Neovim)
|
|
65
70
|
|
|
66
71
|
${colors.bright}TOOL CATEGORIES (${TOOLS.length} tools):${colors.reset}
|
|
67
72
|
${Object.entries(CATEGORIES).map(([key, cat]) => {
|
|
68
73
|
const count = TOOLS.filter(t => t.category === key).length;
|
|
69
74
|
return ` ${cat.emoji} ${colors.yellow}${cat.name.padEnd(15)}${colors.reset} ${colors.dim}(${count} tools)${colors.reset}`;
|
|
70
75
|
}).join('\n')}
|
|
71
|
-
|
|
72
|
-
${colors.dim}MCP: For AI assistants (Claude, Cursor, Copilot)
|
|
73
|
-
LSP: For IDE code intelligence (VS Code, Neovim)${colors.reset}
|
|
74
76
|
`);
|
|
75
77
|
}
|
|
76
78
|
|
|
@@ -88,13 +90,45 @@ function listTools() {
|
|
|
88
90
|
console.log(`${colors.dim}${'─'.repeat(50)}${colors.reset}`);
|
|
89
91
|
|
|
90
92
|
for (const tool of tools) {
|
|
91
|
-
const required = tool.inputSchema?.required || [];
|
|
92
93
|
console.log(` ${tool.emoji} ${colors.yellow}${tool.name.padEnd(25)}${colors.reset} ${colors.dim}${tool.description.substring(0, 40)}${colors.reset}`);
|
|
93
94
|
}
|
|
94
95
|
console.log('');
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Start both MCP and LSP servers together
|
|
101
|
+
*/
|
|
102
|
+
async function startBothServers() {
|
|
103
|
+
console.error('');
|
|
104
|
+
console.error(`${colors.bright}${colors.cyan}╔════════════════════════════════════════════════════════════╗${colors.reset}`);
|
|
105
|
+
console.error(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.bright}${colors.magenta}🦁 Brave Real Browser - Unified Server${colors.reset} ${colors.cyan}║${colors.reset}`);
|
|
106
|
+
console.error(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.dim}MCP (AI Agents) + LSP (IDE Intelligence)${colors.reset} ${colors.cyan}║${colors.reset}`);
|
|
107
|
+
console.error(`${colors.bright}${colors.cyan}╚════════════════════════════════════════════════════════════╝${colors.reset}`);
|
|
108
|
+
console.error('');
|
|
109
|
+
|
|
110
|
+
// Start LSP Server on TCP first (background)
|
|
111
|
+
console.error(`${colors.bright}${colors.blue}📡 Starting LSP Server...${colors.reset}`);
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const { startTcpServer, LSP_PORT } = require('./lsp/server.js');
|
|
115
|
+
const { port } = await startTcpServer();
|
|
116
|
+
console.error(`${colors.bright}${colors.green}✅ LSP Server running on TCP :${port}${colors.reset}`);
|
|
117
|
+
console.error(`${colors.dim} IDEs can connect to: 127.0.0.1:${port}${colors.reset}`);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.error(`${colors.yellow}⚠️ LSP Server failed: ${err.message}${colors.reset}`);
|
|
120
|
+
console.error(`${colors.dim} Continuing with MCP only...${colors.reset}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.error('');
|
|
124
|
+
|
|
125
|
+
// Start MCP Server on STDIO (foreground)
|
|
126
|
+
console.error(`${colors.bright}${colors.blue}🚀 Starting MCP Server...${colors.reset}`);
|
|
127
|
+
|
|
128
|
+
// Import and run MCP server
|
|
129
|
+
require('./mcp/index.js');
|
|
130
|
+
}
|
|
131
|
+
|
|
98
132
|
/**
|
|
99
133
|
* Main entry point
|
|
100
134
|
*/
|
|
@@ -104,7 +138,7 @@ async function main() {
|
|
|
104
138
|
// Parse arguments
|
|
105
139
|
const hasHelp = args.includes('--help') || args.includes('-h');
|
|
106
140
|
const hasList = args.includes('--list');
|
|
107
|
-
const mode = args.find(a => ['mcp', 'lsp'].includes(a.toLowerCase()))
|
|
141
|
+
const mode = args.find(a => ['mcp', 'lsp'].includes(a.toLowerCase()));
|
|
108
142
|
|
|
109
143
|
if (hasHelp) {
|
|
110
144
|
showHelp();
|
|
@@ -116,13 +150,16 @@ async function main() {
|
|
|
116
150
|
process.exit(0);
|
|
117
151
|
}
|
|
118
152
|
|
|
119
|
-
// Start appropriate server
|
|
120
|
-
if (mode
|
|
121
|
-
// Start LSP Server
|
|
153
|
+
// Start appropriate server(s)
|
|
154
|
+
if (mode?.toLowerCase() === 'lsp') {
|
|
155
|
+
// Start LSP Server only (STDIO mode)
|
|
122
156
|
require('./lsp/index.js');
|
|
123
|
-
} else {
|
|
124
|
-
// Start MCP Server
|
|
157
|
+
} else if (mode?.toLowerCase() === 'mcp') {
|
|
158
|
+
// Start MCP Server only
|
|
125
159
|
require('./mcp/index.js');
|
|
160
|
+
} else {
|
|
161
|
+
// Default: Start both servers together
|
|
162
|
+
await startBothServers();
|
|
126
163
|
}
|
|
127
164
|
}
|
|
128
165
|
|
|
@@ -133,6 +170,7 @@ module.exports = {
|
|
|
133
170
|
CATEGORIES,
|
|
134
171
|
startMCP: () => require('./mcp/index.js'),
|
|
135
172
|
startLSP: () => require('./lsp/index.js'),
|
|
173
|
+
startBoth: startBothServers,
|
|
136
174
|
};
|
|
137
175
|
|
|
138
176
|
// Run if called directly
|
package/src/lsp/server.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Brave Real Browser LSP Server
|
|
3
|
+
*
|
|
4
|
+
* Supports both STDIO and TCP transports:
|
|
5
|
+
* - STDIO: For direct IDE connections (npm run lsp)
|
|
6
|
+
* - TCP: For running alongside MCP server (npm run dev)
|
|
3
7
|
*/
|
|
8
|
+
const net = require('net');
|
|
4
9
|
const { createConnection, TextDocuments, ProposedFeatures, TextDocumentSyncKind, MarkupKind } = require('vscode-languageserver/node');
|
|
5
10
|
const { TextDocument } = require('vscode-languageserver-textdocument');
|
|
6
11
|
const { TOOLS } = require('../mcp/tools.js');
|
|
@@ -12,67 +17,168 @@ const { getRefactorings } = require('./capabilities/refactoring.js');
|
|
|
12
17
|
const { simulateWorkflow } = require('./capabilities/simulation.js');
|
|
13
18
|
const { getLanguagePack } = require('./utils/i18n.js');
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
20
|
+
// Default settings
|
|
21
|
+
let settings = {
|
|
22
|
+
language: 'en',
|
|
23
|
+
maxDiagnostics: 100,
|
|
24
|
+
enableSnippets: true,
|
|
25
|
+
enableSimulation: true,
|
|
26
|
+
enableRefactoring: true
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// LSP port for TCP mode
|
|
30
|
+
const LSP_PORT = process.env.LSP_PORT || 7777;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Setup LSP connection handlers
|
|
34
|
+
*/
|
|
35
|
+
function setupConnection(connection) {
|
|
36
|
+
const documents = new TextDocuments(TextDocument);
|
|
37
|
+
|
|
38
|
+
connection.onInitialize(() => ({
|
|
39
|
+
capabilities: {
|
|
40
|
+
textDocumentSync: TextDocumentSyncKind.Incremental,
|
|
41
|
+
completionProvider: { resolveProvider: true, triggerCharacters: ['.', "'", '"', '(', '{', ' '] },
|
|
42
|
+
hoverProvider: true,
|
|
43
|
+
codeActionProvider: true,
|
|
44
|
+
codeLensProvider: { resolveProvider: true },
|
|
45
|
+
signatureHelpProvider: { triggerCharacters: ['(', ','] },
|
|
46
|
+
documentSymbolProvider: true,
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
connection.onDidChangeConfiguration((change) => {
|
|
51
|
+
if (change.settings?.braveRealBrowser) {
|
|
52
|
+
settings = { ...settings, ...change.settings.braveRealBrowser };
|
|
53
|
+
}
|
|
54
|
+
documents.all().forEach((doc) => validateDocument(connection, doc));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
connection.onCompletion((params) => {
|
|
58
|
+
const doc = documents.get(params.textDocument.uri);
|
|
59
|
+
return doc ? getCompletions(doc, params.position, TOOLS, getLanguagePack(settings.language), settings) : [];
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
connection.onCompletionResolve((item) => {
|
|
63
|
+
const lang = getLanguagePack(settings.language);
|
|
64
|
+
const tool = TOOLS.find(t => t.name === item.data?.toolName);
|
|
65
|
+
if (tool && lang.tools[tool.name]) {
|
|
66
|
+
const tl = lang.tools[tool.name];
|
|
67
|
+
item.documentation = {
|
|
68
|
+
kind: MarkupKind.Markdown,
|
|
69
|
+
value: `### ${tool.emoji} ${tl.label}\n\n${tl.documentation}\n\n**Parameters:**\n${Object.entries(tl.parameters || {}).map(([k, v]) => `- \`${k}\`: ${v}`).join('\n')}`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return item;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
connection.onHover((params) => {
|
|
76
|
+
const doc = documents.get(params.textDocument.uri);
|
|
77
|
+
return doc ? getHoverInfo(doc, params.position, TOOLS, getLanguagePack(settings.language)) : null;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
function validateDocument(conn, doc) {
|
|
81
|
+
conn.sendDiagnostics({
|
|
82
|
+
uri: doc.uri,
|
|
83
|
+
diagnostics: getDiagnostics(doc, TOOLS, getLanguagePack(settings.language), settings.maxDiagnostics)
|
|
84
|
+
});
|
|
47
85
|
}
|
|
48
|
-
return item;
|
|
49
|
-
});
|
|
50
86
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
87
|
+
documents.onDidChangeContent((change) => validateDocument(connection, change.document));
|
|
88
|
+
|
|
89
|
+
connection.onCodeAction((params) => {
|
|
90
|
+
const doc = documents.get(params.textDocument.uri);
|
|
91
|
+
return doc ? getRefactorings(doc, params.range, params.context, TOOLS, getLanguagePack(settings.language)) : [];
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
connection.onCodeLens((params) => {
|
|
95
|
+
const doc = documents.get(params.textDocument.uri);
|
|
96
|
+
return doc ? simulateWorkflow(doc, TOOLS, getLanguagePack(settings.language)) : [];
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
connection.onCodeLensResolve((lens) => lens);
|
|
55
100
|
|
|
56
|
-
|
|
57
|
-
|
|
101
|
+
connection.onRequest('braveRealBrowser/snippets', (params) =>
|
|
102
|
+
getSnippets(params?.category, TOOLS, getLanguagePack(settings.language))
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
documents.listen(connection);
|
|
106
|
+
connection.listen();
|
|
107
|
+
|
|
108
|
+
return connection;
|
|
58
109
|
}
|
|
59
110
|
|
|
60
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Start LSP server in STDIO mode
|
|
113
|
+
*/
|
|
114
|
+
function startStdioServer() {
|
|
115
|
+
const connection = createConnection(ProposedFeatures.all, process.stdin, process.stdout);
|
|
116
|
+
setupConnection(connection);
|
|
117
|
+
console.error('🦁 Brave Real Browser LSP Server started (STDIO)');
|
|
118
|
+
return connection;
|
|
119
|
+
}
|
|
61
120
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Start LSP server in TCP mode
|
|
123
|
+
*/
|
|
124
|
+
function startTcpServer(port = LSP_PORT) {
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
const server = net.createServer((socket) => {
|
|
127
|
+
console.error(`🔌 LSP client connected from ${socket.remoteAddress}`);
|
|
128
|
+
|
|
129
|
+
const connection = createConnection(ProposedFeatures.all, socket, socket);
|
|
130
|
+
setupConnection(connection);
|
|
131
|
+
|
|
132
|
+
socket.on('close', () => {
|
|
133
|
+
console.error('🔌 LSP client disconnected');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
66
136
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
137
|
+
server.on('error', (err) => {
|
|
138
|
+
if (err.code === 'EADDRINUSE') {
|
|
139
|
+
console.error(`⚠️ LSP port ${port} already in use, trying ${port + 1}...`);
|
|
140
|
+
server.close();
|
|
141
|
+
startTcpServer(port + 1).then(resolve).catch(reject);
|
|
142
|
+
} else {
|
|
143
|
+
reject(err);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
71
146
|
|
|
72
|
-
|
|
147
|
+
server.listen(port, '127.0.0.1', () => {
|
|
148
|
+
console.error(`🦁 Brave Real Browser LSP Server started (TCP: 127.0.0.1:${port})`);
|
|
149
|
+
resolve({ server, port });
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
73
153
|
|
|
74
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Get transport mode from args or environment
|
|
156
|
+
*/
|
|
157
|
+
function getTransportMode() {
|
|
158
|
+
if (process.argv.includes('--tcp')) return 'tcp';
|
|
159
|
+
if (process.argv.includes('--stdio')) return 'stdio';
|
|
160
|
+
if (process.env.LSP_TRANSPORT === 'tcp') return 'tcp';
|
|
161
|
+
// Default: STDIO for direct invocation, TCP when embedded
|
|
162
|
+
return process.env.LSP_EMBEDDED === 'true' ? 'tcp' : 'stdio';
|
|
163
|
+
}
|
|
75
164
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
165
|
+
// Export for programmatic use
|
|
166
|
+
module.exports = {
|
|
167
|
+
startStdioServer,
|
|
168
|
+
startTcpServer,
|
|
169
|
+
setupConnection,
|
|
170
|
+
LSP_PORT
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Auto-start if run directly
|
|
174
|
+
if (require.main === module) {
|
|
175
|
+
const mode = getTransportMode();
|
|
176
|
+
if (mode === 'tcp') {
|
|
177
|
+
startTcpServer().catch(err => {
|
|
178
|
+
console.error('❌ Failed to start LSP TCP server:', err.message);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
});
|
|
181
|
+
} else {
|
|
182
|
+
startStdioServer();
|
|
183
|
+
}
|
|
184
|
+
}
|
package/src/mcp/handlers.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Brave Real Browser MCP Server - Tool Handlers
|
|
3
3
|
*
|
|
4
4
|
* Implementation of all 28 browser automation tools
|
|
5
|
+
*
|
|
6
|
+
* Environment Variables:
|
|
7
|
+
* HEADLESS=true - Run browser in headless mode
|
|
8
|
+
* HEADLESS=false - Run browser in GUI mode (visible)
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
const path = require('path');
|
|
@@ -15,6 +19,61 @@ let networkRecords = [];
|
|
|
15
19
|
let isRecordingNetwork = false;
|
|
16
20
|
let progressTasks = {};
|
|
17
21
|
|
|
22
|
+
// Progress notification callback (set by server)
|
|
23
|
+
let progressCallback = null;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Set progress callback for real-time notifications
|
|
27
|
+
*/
|
|
28
|
+
function setProgressCallback(callback) {
|
|
29
|
+
progressCallback = callback;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Send real-time progress notification
|
|
34
|
+
*/
|
|
35
|
+
function notifyProgress(toolName, status, message, data = {}) {
|
|
36
|
+
const notification = {
|
|
37
|
+
tool: toolName,
|
|
38
|
+
status, // 'started' | 'progress' | 'completed' | 'error'
|
|
39
|
+
message,
|
|
40
|
+
timestamp: new Date().toISOString(),
|
|
41
|
+
...data
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Log to stderr for visibility
|
|
45
|
+
const emoji = {
|
|
46
|
+
started: '🚀',
|
|
47
|
+
progress: '⏳',
|
|
48
|
+
completed: '✅',
|
|
49
|
+
error: '❌'
|
|
50
|
+
}[status] || '📌';
|
|
51
|
+
|
|
52
|
+
console.error(`${emoji} [${toolName}] ${message}`);
|
|
53
|
+
|
|
54
|
+
// Call progress callback if set
|
|
55
|
+
if (progressCallback) {
|
|
56
|
+
progressCallback(notification);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return notification;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get headless setting from environment
|
|
64
|
+
*/
|
|
65
|
+
function getHeadlessFromEnv() {
|
|
66
|
+
const envHeadless = process.env.HEADLESS;
|
|
67
|
+
|
|
68
|
+
if (envHeadless === undefined || envHeadless === null || envHeadless === '') {
|
|
69
|
+
return false; // Default: GUI mode
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Parse string to boolean
|
|
73
|
+
const value = envHeadless.toLowerCase().trim();
|
|
74
|
+
return value === 'true' || value === '1' || value === 'yes';
|
|
75
|
+
}
|
|
76
|
+
|
|
18
77
|
/**
|
|
19
78
|
* Get browser and page instances
|
|
20
79
|
*/
|
|
@@ -38,9 +97,17 @@ function requireBrowser() {
|
|
|
38
97
|
const handlers = {
|
|
39
98
|
// 1. Browser Init
|
|
40
99
|
async browser_init(params = {}) {
|
|
100
|
+
notifyProgress('browser_init', 'started', 'Initializing browser...');
|
|
101
|
+
|
|
41
102
|
const { connect } = require('../../lib/cjs/index.js');
|
|
42
103
|
|
|
43
|
-
|
|
104
|
+
// Get headless from params OR environment variable
|
|
105
|
+
const envHeadless = getHeadlessFromEnv();
|
|
106
|
+
const headless = params.headless !== undefined ? params.headless : envHeadless;
|
|
107
|
+
|
|
108
|
+
const { proxy = {}, turnstile = false, enableBlocker = true } = params;
|
|
109
|
+
|
|
110
|
+
notifyProgress('browser_init', 'progress', `Mode: ${headless ? 'Headless' : 'GUI (Visible)'}`, { headless });
|
|
44
111
|
|
|
45
112
|
const result = await connect({
|
|
46
113
|
headless,
|
|
@@ -53,10 +120,18 @@ const handlers = {
|
|
|
53
120
|
pageInstance = result.page;
|
|
54
121
|
blockerInstance = result.blocker;
|
|
55
122
|
|
|
123
|
+
const pid = browserInstance.process()?.pid;
|
|
124
|
+
|
|
125
|
+
notifyProgress('browser_init', 'completed', `Browser started (PID: ${pid})`, {
|
|
126
|
+
headless,
|
|
127
|
+
pid,
|
|
128
|
+
blockerEnabled: enableBlocker
|
|
129
|
+
});
|
|
130
|
+
|
|
56
131
|
return {
|
|
57
132
|
success: true,
|
|
58
|
-
message:
|
|
59
|
-
pid
|
|
133
|
+
message: `Browser initialized in ${headless ? 'headless' : 'GUI'} mode`,
|
|
134
|
+
pid,
|
|
60
135
|
headless,
|
|
61
136
|
blockerEnabled: enableBlocker
|
|
62
137
|
};
|
|
@@ -67,12 +142,18 @@ const handlers = {
|
|
|
67
142
|
const { page } = requireBrowser();
|
|
68
143
|
const { url, waitUntil = 'networkidle2', timeout = 30000 } = params;
|
|
69
144
|
|
|
145
|
+
notifyProgress('navigate', 'started', `Navigating to: ${url}`);
|
|
146
|
+
|
|
70
147
|
await page.goto(url, { waitUntil, timeout });
|
|
71
148
|
|
|
149
|
+
const title = await page.title();
|
|
150
|
+
|
|
151
|
+
notifyProgress('navigate', 'completed', `Loaded: ${title}`, { url: page.url(), title });
|
|
152
|
+
|
|
72
153
|
return {
|
|
73
154
|
success: true,
|
|
74
155
|
url: page.url(),
|
|
75
|
-
title
|
|
156
|
+
title
|
|
76
157
|
};
|
|
77
158
|
},
|
|
78
159
|
|
|
@@ -81,11 +162,14 @@ const handlers = {
|
|
|
81
162
|
const { page } = requireBrowser();
|
|
82
163
|
const { format = 'text', selector } = params;
|
|
83
164
|
|
|
165
|
+
notifyProgress('get_content', 'started', `Extracting ${format} content${selector ? ` from ${selector}` : ''}`);
|
|
166
|
+
|
|
84
167
|
let content;
|
|
85
168
|
|
|
86
169
|
if (selector) {
|
|
87
170
|
const element = await page.$(selector);
|
|
88
171
|
if (!element) {
|
|
172
|
+
notifyProgress('get_content', 'error', `Element not found: ${selector}`);
|
|
89
173
|
return { success: false, error: `Element not found: ${selector}` };
|
|
90
174
|
}
|
|
91
175
|
|
|
@@ -99,7 +183,6 @@ const handlers = {
|
|
|
99
183
|
content = await page.content();
|
|
100
184
|
} else if (format === 'markdown') {
|
|
101
185
|
content = await page.evaluate(() => {
|
|
102
|
-
// Simple HTML to Markdown conversion
|
|
103
186
|
const body = document.body.innerText;
|
|
104
187
|
return body;
|
|
105
188
|
});
|
|
@@ -108,6 +191,8 @@ const handlers = {
|
|
|
108
191
|
}
|
|
109
192
|
}
|
|
110
193
|
|
|
194
|
+
notifyProgress('get_content', 'completed', `Extracted ${content.length} characters`, { format, length: content.length });
|
|
195
|
+
|
|
111
196
|
return {
|
|
112
197
|
success: true,
|
|
113
198
|
content,
|
|
@@ -121,6 +206,8 @@ const handlers = {
|
|
|
121
206
|
const { page } = requireBrowser();
|
|
122
207
|
const { type = 'timeout', value, timeout = 30000 } = params;
|
|
123
208
|
|
|
209
|
+
notifyProgress('wait', 'started', `Waiting for ${type}: ${value}`);
|
|
210
|
+
|
|
124
211
|
switch (type) {
|
|
125
212
|
case 'selector':
|
|
126
213
|
await page.waitForSelector(value, { timeout });
|
|
@@ -136,6 +223,8 @@ const handlers = {
|
|
|
136
223
|
await new Promise(r => setTimeout(r, parseInt(value) || 1000));
|
|
137
224
|
}
|
|
138
225
|
|
|
226
|
+
notifyProgress('wait', 'completed', `Wait completed: ${type}`, { type, value });
|
|
227
|
+
|
|
139
228
|
return { success: true, type, value };
|
|
140
229
|
},
|
|
141
230
|
|
|
@@ -144,19 +233,23 @@ const handlers = {
|
|
|
144
233
|
const { page } = requireBrowser();
|
|
145
234
|
const { selector, humanLike = true, clickCount = 1, delay = 0 } = params;
|
|
146
235
|
|
|
236
|
+
notifyProgress('click', 'started', `Clicking: ${selector}`);
|
|
237
|
+
|
|
147
238
|
if (humanLike) {
|
|
148
239
|
try {
|
|
149
240
|
const { createCursor } = require('ghost-cursor');
|
|
150
241
|
const cursor = createCursor(page);
|
|
151
242
|
await cursor.click(selector);
|
|
243
|
+
notifyProgress('click', 'progress', 'Used human-like cursor movement');
|
|
152
244
|
} catch (e) {
|
|
153
|
-
// Fallback to regular click
|
|
154
245
|
await page.click(selector, { clickCount, delay });
|
|
155
246
|
}
|
|
156
247
|
} else {
|
|
157
248
|
await page.click(selector, { clickCount, delay });
|
|
158
249
|
}
|
|
159
250
|
|
|
251
|
+
notifyProgress('click', 'completed', `Clicked: ${selector}`, { selector, humanLike });
|
|
252
|
+
|
|
160
253
|
return { success: true, selector, clicked: true };
|
|
161
254
|
},
|
|
162
255
|
|
|
@@ -165,13 +258,18 @@ const handlers = {
|
|
|
165
258
|
const { page } = requireBrowser();
|
|
166
259
|
const { selector, text, delay = 50, clear = false } = params;
|
|
167
260
|
|
|
261
|
+
notifyProgress('type', 'started', `Typing ${text.length} characters into ${selector}`);
|
|
262
|
+
|
|
168
263
|
if (clear) {
|
|
169
264
|
await page.click(selector, { clickCount: 3 });
|
|
170
265
|
await page.keyboard.press('Backspace');
|
|
266
|
+
notifyProgress('type', 'progress', 'Cleared existing text');
|
|
171
267
|
}
|
|
172
268
|
|
|
173
269
|
await page.type(selector, text, { delay });
|
|
174
270
|
|
|
271
|
+
notifyProgress('type', 'completed', `Typed ${text.length} characters`, { selector, textLength: text.length });
|
|
272
|
+
|
|
175
273
|
return { success: true, selector, textLength: text.length };
|
|
176
274
|
},
|
|
177
275
|
|
|
@@ -179,12 +277,16 @@ const handlers = {
|
|
|
179
277
|
async browser_close(params = {}) {
|
|
180
278
|
const { force = false } = params;
|
|
181
279
|
|
|
280
|
+
notifyProgress('browser_close', 'started', 'Closing browser...');
|
|
281
|
+
|
|
182
282
|
if (browserInstance) {
|
|
183
283
|
try {
|
|
184
284
|
await browserInstance.close();
|
|
285
|
+
notifyProgress('browser_close', 'progress', 'Browser closed gracefully');
|
|
185
286
|
} catch (e) {
|
|
186
287
|
if (force) {
|
|
187
288
|
browserInstance.process()?.kill('SIGKILL');
|
|
289
|
+
notifyProgress('browser_close', 'progress', 'Browser force killed');
|
|
188
290
|
}
|
|
189
291
|
}
|
|
190
292
|
browserInstance = null;
|
|
@@ -192,6 +294,8 @@ const handlers = {
|
|
|
192
294
|
blockerInstance = null;
|
|
193
295
|
}
|
|
194
296
|
|
|
297
|
+
notifyProgress('browser_close', 'completed', 'Browser closed');
|
|
298
|
+
|
|
195
299
|
return { success: true, message: 'Browser closed' };
|
|
196
300
|
},
|
|
197
301
|
|
|
@@ -200,22 +304,32 @@ const handlers = {
|
|
|
200
304
|
const { page } = requireBrowser();
|
|
201
305
|
const { type = 'auto', timeout = 30000 } = params;
|
|
202
306
|
|
|
203
|
-
|
|
307
|
+
notifyProgress('solve_captcha', 'started', `Solving ${type} captcha...`);
|
|
308
|
+
|
|
204
309
|
const start = Date.now();
|
|
310
|
+
let attempts = 0;
|
|
205
311
|
|
|
206
312
|
while (Date.now() - start < timeout) {
|
|
313
|
+
attempts++;
|
|
314
|
+
|
|
207
315
|
const turnstileToken = await page.evaluate(() => {
|
|
208
316
|
const input = document.querySelector('input[name="cf-turnstile-response"]');
|
|
209
317
|
return input ? input.value : null;
|
|
210
318
|
});
|
|
211
319
|
|
|
212
320
|
if (turnstileToken) {
|
|
321
|
+
notifyProgress('solve_captcha', 'completed', `Captcha solved after ${attempts} checks`, { type: 'turnstile', attempts });
|
|
213
322
|
return { success: true, type: 'turnstile', solved: true };
|
|
214
323
|
}
|
|
215
324
|
|
|
325
|
+
if (attempts % 10 === 0) {
|
|
326
|
+
notifyProgress('solve_captcha', 'progress', `Still solving... (${attempts} checks)`, { attempts });
|
|
327
|
+
}
|
|
328
|
+
|
|
216
329
|
await new Promise(r => setTimeout(r, 500));
|
|
217
330
|
}
|
|
218
331
|
|
|
332
|
+
notifyProgress('solve_captcha', 'error', 'Captcha solving timeout');
|
|
219
333
|
return { success: false, error: 'Captcha solving timeout' };
|
|
220
334
|
},
|
|
221
335
|
|
|
@@ -229,11 +343,15 @@ const handlers = {
|
|
|
229
343
|
? (Math.random() > 0.5 ? 'down' : 'up')
|
|
230
344
|
: direction;
|
|
231
345
|
|
|
346
|
+
notifyProgress('random_scroll', 'started', `Scrolling ${scrollDirection} ${scrollAmount}px`);
|
|
347
|
+
|
|
232
348
|
await page.evaluate(({ scrollAmount, scrollDirection, smooth }) => {
|
|
233
349
|
const y = scrollDirection === 'down' ? scrollAmount : -scrollAmount;
|
|
234
350
|
window.scrollBy({ top: y, behavior: smooth ? 'smooth' : 'auto' });
|
|
235
351
|
}, { scrollAmount, scrollDirection, smooth });
|
|
236
352
|
|
|
353
|
+
notifyProgress('random_scroll', 'completed', `Scrolled ${scrollDirection} ${scrollAmount}px`, { direction: scrollDirection, amount: scrollAmount });
|
|
354
|
+
|
|
237
355
|
return { success: true, direction: scrollDirection, amount: scrollAmount };
|
|
238
356
|
},
|
|
239
357
|
|
|
@@ -242,6 +360,8 @@ const handlers = {
|
|
|
242
360
|
const { page } = requireBrowser();
|
|
243
361
|
const { selector, xpath, text, multiple = false } = params;
|
|
244
362
|
|
|
363
|
+
notifyProgress('find_element', 'started', `Finding element: ${selector || xpath || text}`);
|
|
364
|
+
|
|
245
365
|
let elements = [];
|
|
246
366
|
|
|
247
367
|
if (selector) {
|
|
@@ -278,6 +398,8 @@ const handlers = {
|
|
|
278
398
|
);
|
|
279
399
|
}
|
|
280
400
|
|
|
401
|
+
notifyProgress('find_element', 'completed', `Found ${elements.length} element(s)`, { found: elements.length });
|
|
402
|
+
|
|
281
403
|
return { success: true, found: elements.length, elements };
|
|
282
404
|
},
|
|
283
405
|
|
|
@@ -286,6 +408,8 @@ const handlers = {
|
|
|
286
408
|
const { page } = requireBrowser();
|
|
287
409
|
const { filename, selector, includeImages = true, includeMeta = true } = params;
|
|
288
410
|
|
|
411
|
+
notifyProgress('save_content_as_markdown', 'started', `Saving to: ${filename}`);
|
|
412
|
+
|
|
289
413
|
let markdown = '';
|
|
290
414
|
|
|
291
415
|
if (includeMeta) {
|
|
@@ -304,6 +428,8 @@ const handlers = {
|
|
|
304
428
|
const outputPath = path.resolve(filename);
|
|
305
429
|
fs.writeFileSync(outputPath, markdown);
|
|
306
430
|
|
|
431
|
+
notifyProgress('save_content_as_markdown', 'completed', `Saved ${markdown.length} bytes to ${filename}`, { filename: outputPath, size: markdown.length });
|
|
432
|
+
|
|
307
433
|
return { success: true, filename: outputPath, size: markdown.length };
|
|
308
434
|
},
|
|
309
435
|
|
|
@@ -312,6 +438,8 @@ const handlers = {
|
|
|
312
438
|
const { page } = requireBrowser();
|
|
313
439
|
const { url, maxRedirects = 10, includeHeaders = false } = params;
|
|
314
440
|
|
|
441
|
+
notifyProgress('redirect_tracer', 'started', `Tracing redirects for: ${url}`);
|
|
442
|
+
|
|
315
443
|
const redirects = [];
|
|
316
444
|
|
|
317
445
|
const responseHandler = response => {
|
|
@@ -321,6 +449,7 @@ const handlers = {
|
|
|
321
449
|
status: response.status(),
|
|
322
450
|
headers: includeHeaders ? response.headers() : undefined
|
|
323
451
|
});
|
|
452
|
+
notifyProgress('redirect_tracer', 'progress', `Redirect ${redirects.length}: ${response.status()}`, { status: response.status() });
|
|
324
453
|
}
|
|
325
454
|
};
|
|
326
455
|
|
|
@@ -330,6 +459,8 @@ const handlers = {
|
|
|
330
459
|
|
|
331
460
|
page.off('response', responseHandler);
|
|
332
461
|
|
|
462
|
+
notifyProgress('redirect_tracer', 'completed', `Found ${redirects.length} redirects`, { redirectCount: redirects.length, finalUrl: page.url() });
|
|
463
|
+
|
|
333
464
|
return {
|
|
334
465
|
success: true,
|
|
335
466
|
originalUrl: url,
|
|
@@ -344,6 +475,8 @@ const handlers = {
|
|
|
344
475
|
const { page } = requireBrowser();
|
|
345
476
|
const { pattern, flags = 'gi', source = 'html' } = params;
|
|
346
477
|
|
|
478
|
+
notifyProgress('search_regex', 'started', `Searching pattern: ${pattern}`);
|
|
479
|
+
|
|
347
480
|
let content;
|
|
348
481
|
if (source === 'html') {
|
|
349
482
|
content = await page.content();
|
|
@@ -356,6 +489,8 @@ const handlers = {
|
|
|
356
489
|
const regex = new RegExp(pattern, flags);
|
|
357
490
|
const matches = content.match(regex) || [];
|
|
358
491
|
|
|
492
|
+
notifyProgress('search_regex', 'completed', `Found ${matches.length} matches`, { matchCount: matches.length });
|
|
493
|
+
|
|
359
494
|
return { success: true, pattern, matchCount: matches.length, matches: matches.slice(0, 100) };
|
|
360
495
|
},
|
|
361
496
|
|
|
@@ -364,6 +499,8 @@ const handlers = {
|
|
|
364
499
|
const { page } = requireBrowser();
|
|
365
500
|
const { source = 'page', selector, jsonPath } = params;
|
|
366
501
|
|
|
502
|
+
notifyProgress('extract_json', 'started', `Extracting JSON from: ${source}`);
|
|
503
|
+
|
|
367
504
|
let jsonData = [];
|
|
368
505
|
|
|
369
506
|
if (source === 'ld+json') {
|
|
@@ -384,6 +521,8 @@ const handlers = {
|
|
|
384
521
|
try { jsonData = [JSON.parse(text)]; } catch {}
|
|
385
522
|
}
|
|
386
523
|
|
|
524
|
+
notifyProgress('extract_json', 'completed', `Extracted ${jsonData.length} JSON objects`, { count: jsonData.length });
|
|
525
|
+
|
|
387
526
|
return { success: true, source, count: jsonData.length, data: jsonData };
|
|
388
527
|
},
|
|
389
528
|
|
|
@@ -392,6 +531,8 @@ const handlers = {
|
|
|
392
531
|
const { page } = requireBrowser();
|
|
393
532
|
const { types = ['all'] } = params;
|
|
394
533
|
|
|
534
|
+
notifyProgress('scrape_meta_tags', 'started', 'Extracting meta tags...');
|
|
535
|
+
|
|
395
536
|
const meta = await page.evaluate(() => {
|
|
396
537
|
const result = { meta: {}, og: {}, twitter: {} };
|
|
397
538
|
|
|
@@ -415,6 +556,9 @@ const handlers = {
|
|
|
415
556
|
return result;
|
|
416
557
|
});
|
|
417
558
|
|
|
559
|
+
const tagCount = Object.keys(meta.meta).length + Object.keys(meta.og).length + Object.keys(meta.twitter).length;
|
|
560
|
+
notifyProgress('scrape_meta_tags', 'completed', `Extracted ${tagCount} meta tags`, { tagCount });
|
|
561
|
+
|
|
418
562
|
return { success: true, ...meta };
|
|
419
563
|
},
|
|
420
564
|
|
|
@@ -423,6 +567,8 @@ const handlers = {
|
|
|
423
567
|
const { page } = requireBrowser();
|
|
424
568
|
const { key, modifiers = [], count = 1 } = params;
|
|
425
569
|
|
|
570
|
+
notifyProgress('press_key', 'started', `Pressing: ${modifiers.length ? modifiers.join('+') + '+' : ''}${key} x${count}`);
|
|
571
|
+
|
|
426
572
|
for (let i = 0; i < count; i++) {
|
|
427
573
|
if (modifiers.length > 0) {
|
|
428
574
|
const keyCombo = [...modifiers, key].join('+');
|
|
@@ -432,6 +578,8 @@ const handlers = {
|
|
|
432
578
|
}
|
|
433
579
|
}
|
|
434
580
|
|
|
581
|
+
notifyProgress('press_key', 'completed', `Pressed ${key} ${count} time(s)`, { key, modifiers, count });
|
|
582
|
+
|
|
435
583
|
return { success: true, key, modifiers, count };
|
|
436
584
|
},
|
|
437
585
|
|
|
@@ -442,16 +590,20 @@ const handlers = {
|
|
|
442
590
|
switch (action) {
|
|
443
591
|
case 'start':
|
|
444
592
|
progressTasks[taskName] = { progress: 0, startTime: Date.now() };
|
|
593
|
+
notifyProgress('progress_tracker', 'started', `Task started: ${taskName}`);
|
|
445
594
|
break;
|
|
446
595
|
case 'update':
|
|
447
596
|
if (progressTasks[taskName]) {
|
|
448
597
|
progressTasks[taskName].progress = progress;
|
|
598
|
+
notifyProgress('progress_tracker', 'progress', `${taskName}: ${progress}%`, { taskName, progress });
|
|
449
599
|
}
|
|
450
600
|
break;
|
|
451
601
|
case 'complete':
|
|
452
602
|
if (progressTasks[taskName]) {
|
|
453
603
|
progressTasks[taskName].progress = 100;
|
|
454
604
|
progressTasks[taskName].endTime = Date.now();
|
|
605
|
+
const duration = progressTasks[taskName].endTime - progressTasks[taskName].startTime;
|
|
606
|
+
notifyProgress('progress_tracker', 'completed', `${taskName} completed in ${duration}ms`, { taskName, duration });
|
|
455
607
|
}
|
|
456
608
|
break;
|
|
457
609
|
}
|
|
@@ -464,6 +616,8 @@ const handlers = {
|
|
|
464
616
|
const { page } = requireBrowser();
|
|
465
617
|
const { types = ['all'], detailed = true } = params;
|
|
466
618
|
|
|
619
|
+
notifyProgress('deep_analysis', 'started', 'Analyzing page...');
|
|
620
|
+
|
|
467
621
|
const analysis = await page.evaluate(() => {
|
|
468
622
|
const result = {
|
|
469
623
|
seo: {
|
|
@@ -495,6 +649,8 @@ const handlers = {
|
|
|
495
649
|
return result;
|
|
496
650
|
});
|
|
497
651
|
|
|
652
|
+
notifyProgress('deep_analysis', 'completed', `Analysis complete: ${analysis.performance.domElements} DOM elements`, { domElements: analysis.performance.domElements });
|
|
653
|
+
|
|
498
654
|
return { success: true, url: page.url(), analysis };
|
|
499
655
|
},
|
|
500
656
|
|
|
@@ -517,12 +673,15 @@ const handlers = {
|
|
|
517
673
|
});
|
|
518
674
|
}
|
|
519
675
|
});
|
|
676
|
+
notifyProgress('network_recorder', 'started', 'Network recording started');
|
|
520
677
|
break;
|
|
521
678
|
case 'stop':
|
|
522
679
|
isRecordingNetwork = false;
|
|
680
|
+
notifyProgress('network_recorder', 'completed', `Recording stopped: ${networkRecords.length} requests captured`);
|
|
523
681
|
break;
|
|
524
682
|
case 'clear':
|
|
525
683
|
networkRecords = [];
|
|
684
|
+
notifyProgress('network_recorder', 'completed', 'Network records cleared');
|
|
526
685
|
break;
|
|
527
686
|
}
|
|
528
687
|
|
|
@@ -543,6 +702,8 @@ const handlers = {
|
|
|
543
702
|
const { page } = requireBrowser();
|
|
544
703
|
const { types = ['all'], selector, includeText = true } = params;
|
|
545
704
|
|
|
705
|
+
notifyProgress('link_harvester', 'started', 'Harvesting links...');
|
|
706
|
+
|
|
546
707
|
const currentHost = new URL(page.url()).hostname;
|
|
547
708
|
|
|
548
709
|
let links = await page.$$eval(selector ? `${selector} a` : 'a', (anchors, includeText) =>
|
|
@@ -566,6 +727,8 @@ const handlers = {
|
|
|
566
727
|
});
|
|
567
728
|
}
|
|
568
729
|
|
|
730
|
+
notifyProgress('link_harvester', 'completed', `Found ${links.length} links`, { count: links.length });
|
|
731
|
+
|
|
569
732
|
return { success: true, count: links.length, links };
|
|
570
733
|
},
|
|
571
734
|
|
|
@@ -574,24 +737,30 @@ const handlers = {
|
|
|
574
737
|
const { page } = requireBrowser();
|
|
575
738
|
const { action = 'get', name, value, domain, expires } = params;
|
|
576
739
|
|
|
740
|
+
notifyProgress('cookie_manager', 'started', `Cookie action: ${action}`);
|
|
741
|
+
|
|
577
742
|
switch (action) {
|
|
578
743
|
case 'get':
|
|
579
744
|
const cookies = await page.cookies();
|
|
745
|
+
notifyProgress('cookie_manager', 'completed', `Retrieved ${cookies.length} cookies`);
|
|
580
746
|
return { success: true, cookies: name ? cookies.filter(c => c.name === name) : cookies };
|
|
581
747
|
|
|
582
748
|
case 'set':
|
|
583
749
|
await page.setCookie({ name, value, domain: domain || new URL(page.url()).hostname, expires });
|
|
750
|
+
notifyProgress('cookie_manager', 'completed', `Cookie set: ${name}`);
|
|
584
751
|
return { success: true, message: `Cookie ${name} set` };
|
|
585
752
|
|
|
586
753
|
case 'delete':
|
|
587
754
|
const toDelete = await page.cookies();
|
|
588
755
|
const filtered = name ? toDelete.filter(c => c.name === name) : toDelete;
|
|
589
756
|
await page.deleteCookie(...filtered);
|
|
757
|
+
notifyProgress('cookie_manager', 'completed', `Deleted ${filtered.length} cookie(s)`);
|
|
590
758
|
return { success: true, message: `Deleted ${filtered.length} cookie(s)` };
|
|
591
759
|
|
|
592
760
|
case 'clear':
|
|
593
761
|
const allCookies = await page.cookies();
|
|
594
762
|
await page.deleteCookie(...allCookies);
|
|
763
|
+
notifyProgress('cookie_manager', 'completed', `Cleared ${allCookies.length} cookies`);
|
|
595
764
|
return { success: true, message: `Cleared ${allCookies.length} cookies` };
|
|
596
765
|
}
|
|
597
766
|
|
|
@@ -603,12 +772,12 @@ const handlers = {
|
|
|
603
772
|
const { page } = requireBrowser();
|
|
604
773
|
const { url, filename, directory = './downloads' } = params;
|
|
605
774
|
|
|
606
|
-
|
|
775
|
+
notifyProgress('file_downloader', 'started', `Downloading: ${url}`);
|
|
776
|
+
|
|
607
777
|
if (!fs.existsSync(directory)) {
|
|
608
778
|
fs.mkdirSync(directory, { recursive: true });
|
|
609
779
|
}
|
|
610
780
|
|
|
611
|
-
// Use page to download
|
|
612
781
|
const response = await page.goto(url, { waitUntil: 'networkidle2' });
|
|
613
782
|
const buffer = await response.buffer();
|
|
614
783
|
|
|
@@ -617,6 +786,8 @@ const handlers = {
|
|
|
617
786
|
|
|
618
787
|
fs.writeFileSync(outputPath, buffer);
|
|
619
788
|
|
|
789
|
+
notifyProgress('file_downloader', 'completed', `Downloaded: ${outputFilename} (${buffer.length} bytes)`, { filename: outputPath, size: buffer.length });
|
|
790
|
+
|
|
620
791
|
return { success: true, filename: outputPath, size: buffer.length };
|
|
621
792
|
},
|
|
622
793
|
|
|
@@ -625,10 +796,13 @@ const handlers = {
|
|
|
625
796
|
const { page } = requireBrowser();
|
|
626
797
|
const { action = 'list', selector, index } = params;
|
|
627
798
|
|
|
799
|
+
notifyProgress('iframe_handler', 'started', `iFrame action: ${action}`);
|
|
800
|
+
|
|
628
801
|
const frames = page.frames();
|
|
629
802
|
|
|
630
803
|
switch (action) {
|
|
631
804
|
case 'list':
|
|
805
|
+
notifyProgress('iframe_handler', 'completed', `Found ${frames.length} frames`);
|
|
632
806
|
return {
|
|
633
807
|
success: true,
|
|
634
808
|
count: frames.length,
|
|
@@ -641,8 +815,10 @@ const handlers = {
|
|
|
641
815
|
: frames[index];
|
|
642
816
|
|
|
643
817
|
if (targetFrame) {
|
|
818
|
+
notifyProgress('iframe_handler', 'completed', `Switched to frame: ${targetFrame.url()}`);
|
|
644
819
|
return { success: true, switched: true, url: targetFrame.url() };
|
|
645
820
|
}
|
|
821
|
+
notifyProgress('iframe_handler', 'error', 'Frame not found');
|
|
646
822
|
return { success: false, error: 'Frame not found' };
|
|
647
823
|
|
|
648
824
|
case 'content':
|
|
@@ -652,11 +828,13 @@ const handlers = {
|
|
|
652
828
|
|
|
653
829
|
if (frame) {
|
|
654
830
|
const content = await frame.content();
|
|
831
|
+
notifyProgress('iframe_handler', 'completed', `Got frame content: ${content.length} chars`);
|
|
655
832
|
return { success: true, content };
|
|
656
833
|
}
|
|
657
834
|
return { success: false, error: 'Frame not found' };
|
|
658
835
|
|
|
659
836
|
case 'exit':
|
|
837
|
+
notifyProgress('iframe_handler', 'completed', 'Returned to main frame');
|
|
660
838
|
return { success: true, message: 'Returned to main frame' };
|
|
661
839
|
}
|
|
662
840
|
|
|
@@ -668,22 +846,21 @@ const handlers = {
|
|
|
668
846
|
const { page } = requireBrowser();
|
|
669
847
|
const { types = ['all'], quality = 'best' } = params;
|
|
670
848
|
|
|
849
|
+
notifyProgress('stream_extractor', 'started', 'Extracting streams...');
|
|
850
|
+
|
|
671
851
|
const streams = await page.evaluate(() => {
|
|
672
852
|
const result = { video: [], audio: [], hls: [], dash: [] };
|
|
673
853
|
|
|
674
|
-
// Video elements
|
|
675
854
|
document.querySelectorAll('video source, video').forEach(el => {
|
|
676
855
|
const src = el.src || el.getAttribute('src');
|
|
677
856
|
if (src) result.video.push({ src, type: el.type || 'video' });
|
|
678
857
|
});
|
|
679
858
|
|
|
680
|
-
// Audio elements
|
|
681
859
|
document.querySelectorAll('audio source, audio').forEach(el => {
|
|
682
860
|
const src = el.src || el.getAttribute('src');
|
|
683
861
|
if (src) result.audio.push({ src, type: el.type || 'audio' });
|
|
684
862
|
});
|
|
685
863
|
|
|
686
|
-
// Find HLS/DASH in scripts
|
|
687
864
|
const scripts = [...document.querySelectorAll('script')].map(s => s.textContent).join('\n');
|
|
688
865
|
const hlsMatches = scripts.match(/https?:\/\/[^\s"']+\.m3u8[^\s"']*/gi) || [];
|
|
689
866
|
const dashMatches = scripts.match(/https?:\/\/[^\s"']+\.mpd[^\s"']*/gi) || [];
|
|
@@ -694,6 +871,9 @@ const handlers = {
|
|
|
694
871
|
return result;
|
|
695
872
|
});
|
|
696
873
|
|
|
874
|
+
const totalStreams = streams.video.length + streams.audio.length + streams.hls.length + streams.dash.length;
|
|
875
|
+
notifyProgress('stream_extractor', 'completed', `Found ${totalStreams} streams`, { totalStreams });
|
|
876
|
+
|
|
697
877
|
return { success: true, streams };
|
|
698
878
|
},
|
|
699
879
|
|
|
@@ -702,8 +882,11 @@ const handlers = {
|
|
|
702
882
|
const { page } = requireBrowser();
|
|
703
883
|
const { selector, waitForJS = true, timeout = 10000 } = params;
|
|
704
884
|
|
|
885
|
+
notifyProgress('js_scrape', 'started', `Scraping: ${selector}`);
|
|
886
|
+
|
|
705
887
|
if (waitForJS) {
|
|
706
888
|
await page.waitForSelector(selector, { timeout });
|
|
889
|
+
notifyProgress('js_scrape', 'progress', 'Element found, extracting content...');
|
|
707
890
|
}
|
|
708
891
|
|
|
709
892
|
const content = await page.$eval(selector, el => ({
|
|
@@ -712,6 +895,8 @@ const handlers = {
|
|
|
712
895
|
attributes: Object.fromEntries([...el.attributes].map(a => [a.name, a.value]))
|
|
713
896
|
}));
|
|
714
897
|
|
|
898
|
+
notifyProgress('js_scrape', 'completed', `Scraped ${content.text.length} characters`, { selector });
|
|
899
|
+
|
|
715
900
|
return { success: true, selector, content };
|
|
716
901
|
},
|
|
717
902
|
|
|
@@ -720,8 +905,12 @@ const handlers = {
|
|
|
720
905
|
const { page } = requireBrowser();
|
|
721
906
|
const { code, returnValue = true } = params;
|
|
722
907
|
|
|
908
|
+
notifyProgress('execute_js', 'started', 'Executing JavaScript...');
|
|
909
|
+
|
|
723
910
|
const result = await page.evaluate(code);
|
|
724
911
|
|
|
912
|
+
notifyProgress('execute_js', 'completed', 'JavaScript executed', { hasResult: result !== undefined });
|
|
913
|
+
|
|
725
914
|
return { success: true, result: returnValue ? result : undefined };
|
|
726
915
|
},
|
|
727
916
|
|
|
@@ -730,8 +919,9 @@ const handlers = {
|
|
|
730
919
|
const { page } = requireBrowser();
|
|
731
920
|
const { playerType = 'auto', action = 'info' } = params;
|
|
732
921
|
|
|
922
|
+
notifyProgress('player_api_hook', 'started', `Player ${action}: ${playerType}`);
|
|
923
|
+
|
|
733
924
|
const playerInfo = await page.evaluate(({ playerType, action }) => {
|
|
734
|
-
// Try to detect player
|
|
735
925
|
if (window.player || window.videoPlayer || window.jwplayer) {
|
|
736
926
|
const player = window.player || window.videoPlayer || (window.jwplayer && window.jwplayer());
|
|
737
927
|
|
|
@@ -763,7 +953,6 @@ const handlers = {
|
|
|
763
953
|
}
|
|
764
954
|
}
|
|
765
955
|
|
|
766
|
-
// Fallback to HTML5 video
|
|
767
956
|
const video = document.querySelector('video');
|
|
768
957
|
if (video) {
|
|
769
958
|
if (action === 'play') video.play();
|
|
@@ -782,6 +971,8 @@ const handlers = {
|
|
|
782
971
|
return { detected: false };
|
|
783
972
|
}, { playerType, action });
|
|
784
973
|
|
|
974
|
+
notifyProgress('player_api_hook', 'completed', playerInfo.detected ? `Player detected: ${playerInfo.type}` : 'No player found', { detected: playerInfo.detected });
|
|
975
|
+
|
|
785
976
|
return { success: true, ...playerInfo };
|
|
786
977
|
},
|
|
787
978
|
|
|
@@ -791,6 +982,11 @@ const handlers = {
|
|
|
791
982
|
const { selector, data, submit = false, humanLike = true } = params;
|
|
792
983
|
|
|
793
984
|
const formSelector = selector || 'form';
|
|
985
|
+
const fields = Object.keys(data || {});
|
|
986
|
+
|
|
987
|
+
notifyProgress('form_automator', 'started', `Filling form with ${fields.length} fields`);
|
|
988
|
+
|
|
989
|
+
let filledCount = 0;
|
|
794
990
|
|
|
795
991
|
for (const [field, value] of Object.entries(data || {})) {
|
|
796
992
|
const inputSelector = `${formSelector} [name="${field}"], ${formSelector} #${field}, ${formSelector} [placeholder*="${field}" i]`;
|
|
@@ -813,6 +1009,8 @@ const handlers = {
|
|
|
813
1009
|
await page.type(inputSelector, String(value));
|
|
814
1010
|
}
|
|
815
1011
|
}
|
|
1012
|
+
filledCount++;
|
|
1013
|
+
notifyProgress('form_automator', 'progress', `Filled: ${field}`, { field, filledCount });
|
|
816
1014
|
}
|
|
817
1015
|
} catch (e) {
|
|
818
1016
|
// Field not found, continue
|
|
@@ -821,9 +1019,12 @@ const handlers = {
|
|
|
821
1019
|
|
|
822
1020
|
if (submit) {
|
|
823
1021
|
await page.click(`${formSelector} [type="submit"], ${formSelector} button`);
|
|
1022
|
+
notifyProgress('form_automator', 'progress', 'Form submitted');
|
|
824
1023
|
}
|
|
825
1024
|
|
|
826
|
-
|
|
1025
|
+
notifyProgress('form_automator', 'completed', `Filled ${filledCount}/${fields.length} fields`, { filledCount, submitted: submit });
|
|
1026
|
+
|
|
1027
|
+
return { success: true, formSelector, fieldsProcessed: filledCount, submitted: submit };
|
|
827
1028
|
}
|
|
828
1029
|
};
|
|
829
1030
|
|
|
@@ -834,12 +1035,14 @@ async function executeTool(name, params = {}) {
|
|
|
834
1035
|
const handler = handlers[name];
|
|
835
1036
|
|
|
836
1037
|
if (!handler) {
|
|
1038
|
+
notifyProgress(name, 'error', `Unknown tool: ${name}`);
|
|
837
1039
|
return { success: false, error: `Unknown tool: ${name}` };
|
|
838
1040
|
}
|
|
839
1041
|
|
|
840
1042
|
try {
|
|
841
1043
|
return await handler(params);
|
|
842
1044
|
} catch (error) {
|
|
1045
|
+
notifyProgress(name, 'error', error.message);
|
|
843
1046
|
return { success: false, error: error.message };
|
|
844
1047
|
}
|
|
845
1048
|
}
|
|
@@ -865,5 +1068,8 @@ module.exports = {
|
|
|
865
1068
|
executeTool,
|
|
866
1069
|
cleanup,
|
|
867
1070
|
getState,
|
|
868
|
-
requireBrowser
|
|
1071
|
+
requireBrowser,
|
|
1072
|
+
setProgressCallback,
|
|
1073
|
+
notifyProgress,
|
|
1074
|
+
getHeadlessFromEnv
|
|
869
1075
|
};
|