brave-real-browser-mcp-server 2.34.0 → 2.35.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/dist/index.js +8 -0
- package/package.json +7 -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/lsp/capabilities/autocomplete.js +102 -0
- package/src/lsp/capabilities/diagnostics.js +42 -0
- package/src/lsp/capabilities/hover.js +65 -0
- package/src/lsp/capabilities/refactoring.js +23 -0
- package/src/lsp/capabilities/simulation.js +31 -0
- package/src/lsp/capabilities/snippets.js +13 -0
- package/src/lsp/index.js +24 -0
- package/src/lsp/languages/en.js +66 -0
- package/src/lsp/languages/hi.js +50 -0
- package/src/lsp/server.js +78 -0
- package/src/lsp/utils/i18n.js +29 -0
package/dist/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.35.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",
|
|
@@ -22,12 +22,14 @@
|
|
|
22
22
|
"node": ">=18.0.0"
|
|
23
23
|
},
|
|
24
24
|
"bin": {
|
|
25
|
-
"brave-mcp": "./
|
|
25
|
+
"brave-mcp": "./dist/index.js",
|
|
26
|
+
"brave-lsp": "./src/lsp/index.js"
|
|
26
27
|
},
|
|
27
28
|
"scripts": {
|
|
28
29
|
"dev": "node src/mcp/index.js",
|
|
29
30
|
"mcp": "node src/mcp/index.js",
|
|
30
31
|
"mcp:verbose": "node src/mcp/index.js --verbose",
|
|
32
|
+
"lsp": "node src/lsp/index.js",
|
|
31
33
|
"build": "echo 'Root package uses pre-built lib/ - no build needed'",
|
|
32
34
|
"build:self": "echo 'Root package uses pre-built lib/ - no build needed'",
|
|
33
35
|
"test": "npm run cjs_test && npm run esm_test",
|
|
@@ -65,11 +67,13 @@
|
|
|
65
67
|
"license": "ISC",
|
|
66
68
|
"dependencies": {
|
|
67
69
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
68
|
-
"brave-real-puppeteer-core": "^24.
|
|
70
|
+
"brave-real-puppeteer-core": "^24.50.0",
|
|
69
71
|
"ghost-cursor": "^1.4.2",
|
|
70
72
|
"puppeteer-extra": "^3.3.6",
|
|
71
73
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
72
74
|
"tree-kill": "^1.2.2",
|
|
75
|
+
"vscode-languageserver": "^9.0.1",
|
|
76
|
+
"vscode-languageserver-textdocument": "^1.0.12",
|
|
73
77
|
"xvfb": "^0.4.0"
|
|
74
78
|
},
|
|
75
79
|
"devDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-blocker",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.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.50.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.17.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.11.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.50.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.17.0",
|
|
136
136
|
"get-east-asian-width": "^1.4.0",
|
|
137
137
|
"yargs": "^18.0.0"
|
|
138
138
|
},
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autocomplete Capability
|
|
3
|
+
*/
|
|
4
|
+
const { CompletionItemKind, InsertTextFormat } = require('vscode-languageserver/node');
|
|
5
|
+
|
|
6
|
+
function getCompletions(document, position, tools, lang, settings) {
|
|
7
|
+
const text = document.getText();
|
|
8
|
+
const offset = document.offsetAt(position);
|
|
9
|
+
const lineText = text.split('\n')[position.line] || '';
|
|
10
|
+
const prefix = getPrefix(lineText, position.character);
|
|
11
|
+
const completions = [];
|
|
12
|
+
const context = detectContext(text, offset);
|
|
13
|
+
|
|
14
|
+
if (context.type === 'tool_name' || context.type === 'start') {
|
|
15
|
+
for (const tool of tools) {
|
|
16
|
+
const toolLang = lang.tools[tool.name] || {};
|
|
17
|
+
if (tool.name.toLowerCase().startsWith(prefix) || prefix === '') {
|
|
18
|
+
completions.push({
|
|
19
|
+
label: tool.name,
|
|
20
|
+
kind: CompletionItemKind.Function,
|
|
21
|
+
detail: `${tool.emoji} ${toolLang.detail || tool.description}`,
|
|
22
|
+
documentation: toolLang.documentation || tool.description,
|
|
23
|
+
insertText: generateToolSnippet(tool),
|
|
24
|
+
insertTextFormat: InsertTextFormat.Snippet,
|
|
25
|
+
data: { toolName: tool.name },
|
|
26
|
+
sortText: `0_${tool.name}`,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (context.type === 'parameter_name') {
|
|
33
|
+
const tool = tools.find(t => t.name === context.toolName);
|
|
34
|
+
if (tool?.inputSchema?.properties) {
|
|
35
|
+
const toolLang = lang.tools[tool.name] || {};
|
|
36
|
+
for (const [name, schema] of Object.entries(tool.inputSchema.properties)) {
|
|
37
|
+
if (name.toLowerCase().startsWith(prefix) || prefix === '') {
|
|
38
|
+
completions.push({
|
|
39
|
+
label: name,
|
|
40
|
+
kind: CompletionItemKind.Property,
|
|
41
|
+
detail: schema.type,
|
|
42
|
+
documentation: toolLang.parameters?.[name] || schema.description,
|
|
43
|
+
insertText: `${name}: `,
|
|
44
|
+
sortText: tool.inputSchema.required?.includes(name) ? `0_${name}` : `1_${name}`,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (context.type === 'parameter_value') {
|
|
52
|
+
const tool = tools.find(t => t.name === context.toolName);
|
|
53
|
+
const schema = tool?.inputSchema?.properties?.[context.paramName];
|
|
54
|
+
if (schema?.enum) {
|
|
55
|
+
for (const value of schema.enum) {
|
|
56
|
+
completions.push({ label: value, kind: CompletionItemKind.EnumMember, insertText: `'${value}'` });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (schema?.type === 'boolean') {
|
|
60
|
+
completions.push({ label: 'true', kind: CompletionItemKind.Keyword }, { label: 'false', kind: CompletionItemKind.Keyword });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return completions;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function generateToolSnippet(tool) {
|
|
68
|
+
const required = tool.inputSchema.required || [];
|
|
69
|
+
const props = tool.inputSchema.properties || {};
|
|
70
|
+
if (required.length === 0) return `${tool.name}()`;
|
|
71
|
+
let i = 1;
|
|
72
|
+
const params = required.map(p => {
|
|
73
|
+
const prop = props[p];
|
|
74
|
+
if (prop?.type === 'string') return `${p}: '\${${i++}}'`;
|
|
75
|
+
if (prop?.type === 'boolean') return `${p}: \${${i++}:false}`;
|
|
76
|
+
if (prop?.type === 'number') return `${p}: \${${i++}:0}`;
|
|
77
|
+
return `${p}: \${${i++}}`;
|
|
78
|
+
});
|
|
79
|
+
return `${tool.name}({ ${params.join(', ')} })`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getPrefix(line, char) {
|
|
83
|
+
let start = char;
|
|
84
|
+
while (start > 0 && /\w/.test(line[start - 1])) start--;
|
|
85
|
+
return line.substring(start, char).toLowerCase();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function detectContext(text, offset) {
|
|
89
|
+
const before = text.substring(0, offset);
|
|
90
|
+
if (/(\w+)\s*\(\s*\{[^}]*(\w+)\s*:\s*['"]?$/.test(before)) {
|
|
91
|
+
const match = before.match(/(\w+)\s*\(\s*\{[^}]*(\w+)\s*:\s*['"]?$/);
|
|
92
|
+
return { type: 'parameter_value', toolName: match[1], paramName: match[2] };
|
|
93
|
+
}
|
|
94
|
+
if (/(\w+)\s*\(\s*\{[^}]*$/.test(before)) {
|
|
95
|
+
const match = before.match(/(\w+)\s*\(\s*\{[^}]*$/);
|
|
96
|
+
return { type: 'parameter_name', toolName: match[1] };
|
|
97
|
+
}
|
|
98
|
+
if (/(?:await\s+|^\s*)$/.test(before)) return { type: 'tool_name' };
|
|
99
|
+
return { type: 'start' };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { getCompletions };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostics Capability
|
|
3
|
+
*/
|
|
4
|
+
const { DiagnosticSeverity } = require('vscode-languageserver/node');
|
|
5
|
+
|
|
6
|
+
function getDiagnostics(document, tools, lang, maxDiagnostics = 100) {
|
|
7
|
+
const text = document.getText();
|
|
8
|
+
const diagnostics = [];
|
|
9
|
+
let browserInit = false, browserClosed = false;
|
|
10
|
+
const browserRequired = ['navigate','get_content','wait','click','type','solve_captcha','random_scroll','find_element','save_content_as_markdown','search_regex','extract_json','scrape_meta_tags','press_key','deep_analysis','network_recorder','link_harvester','cookie_manager','iframe_handler','stream_extractor','js_scrape','execute_js','player_api_hook','form_automator'];
|
|
11
|
+
const toolNames = tools.map(t => t.name);
|
|
12
|
+
const regex = new RegExp(`(${toolNames.join('|')})\\s*\\(`, 'g');
|
|
13
|
+
const lines = text.split('\n');
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < lines.length && diagnostics.length < maxDiagnostics; i++) {
|
|
16
|
+
const line = lines[i];
|
|
17
|
+
let match;
|
|
18
|
+
while ((match = regex.exec(line)) !== null) {
|
|
19
|
+
const name = match[1];
|
|
20
|
+
const range = { start: { line: i, character: match.index }, end: { line: i, character: match.index + name.length } };
|
|
21
|
+
|
|
22
|
+
if (name === 'browser_init') { browserInit = true; browserClosed = false; }
|
|
23
|
+
if (name === 'browser_close') browserClosed = true;
|
|
24
|
+
|
|
25
|
+
if (browserRequired.includes(name) && !browserInit) {
|
|
26
|
+
diagnostics.push({ severity: DiagnosticSeverity.Error, range, message: lang.diagnostics?.browserNotInit || 'Browser not initialized', source: 'brave-real-browser', code: 'browser-not-init' });
|
|
27
|
+
}
|
|
28
|
+
if (browserRequired.includes(name) && browserClosed) {
|
|
29
|
+
diagnostics.push({ severity: DiagnosticSeverity.Error, range, message: 'Browser already closed', source: 'brave-real-browser', code: 'browser-closed' });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
regex.lastIndex = 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (browserInit && !browserClosed) {
|
|
36
|
+
diagnostics.push({ severity: DiagnosticSeverity.Information, range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, message: 'Consider adding browser_close() at the end', source: 'brave-real-browser', code: 'missing-cleanup' });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return diagnostics;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { getDiagnostics };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hover Capability
|
|
3
|
+
*/
|
|
4
|
+
const { MarkupKind } = require('vscode-languageserver/node');
|
|
5
|
+
|
|
6
|
+
function getHoverInfo(document, position, tools, lang) {
|
|
7
|
+
const text = document.getText();
|
|
8
|
+
const lineText = text.split('\n')[position.line] || '';
|
|
9
|
+
const word = getWordAtPosition(lineText, position.character);
|
|
10
|
+
if (!word) return null;
|
|
11
|
+
|
|
12
|
+
const tool = tools.find(t => t.name === word);
|
|
13
|
+
if (tool) {
|
|
14
|
+
const toolLang = lang.tools[tool.name] || {};
|
|
15
|
+
const props = tool.inputSchema.properties || {};
|
|
16
|
+
const required = tool.inputSchema.required || [];
|
|
17
|
+
const paramDocs = Object.entries(props).map(([name, schema]) => {
|
|
18
|
+
const isReq = required.includes(name);
|
|
19
|
+
const desc = toolLang.parameters?.[name] || schema.description || '';
|
|
20
|
+
return `| \`${name}\` | ${schema.type} | ${isReq ? '✅' : '❌'} | ${desc} |`;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
contents: {
|
|
25
|
+
kind: MarkupKind.Markdown,
|
|
26
|
+
value: [
|
|
27
|
+
`## ${tool.emoji} ${toolLang.label || tool.name}`,
|
|
28
|
+
'', toolLang.documentation || tool.description, '',
|
|
29
|
+
'### Parameters', '', '| Name | Type | Required | Description |', '|------|------|----------|-------------|',
|
|
30
|
+
...paramDocs, '',
|
|
31
|
+
'### Example', '', '```javascript', generateExample(tool), '```'
|
|
32
|
+
].join('\n'),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function generateExample(tool) {
|
|
40
|
+
const props = tool.inputSchema.properties || {};
|
|
41
|
+
const required = tool.inputSchema.required || [];
|
|
42
|
+
if (required.length === 0) return `await ${tool.name}();`;
|
|
43
|
+
const params = required.map(p => {
|
|
44
|
+
const prop = props[p];
|
|
45
|
+
let val = prop?.default;
|
|
46
|
+
if (val === undefined) {
|
|
47
|
+
if (prop?.type === 'string') val = "'value'";
|
|
48
|
+
else if (prop?.type === 'boolean') val = 'true';
|
|
49
|
+
else if (prop?.type === 'number') val = '1000';
|
|
50
|
+
else if (prop?.enum) val = `'${prop.enum[0]}'`;
|
|
51
|
+
else val = '{}';
|
|
52
|
+
} else if (typeof val === 'string') val = `'${val}'`;
|
|
53
|
+
return ` ${p}: ${val}`;
|
|
54
|
+
});
|
|
55
|
+
return `await ${tool.name}({\n${params.join(',\n')}\n});`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getWordAtPosition(line, char) {
|
|
59
|
+
let start = char, end = char;
|
|
60
|
+
while (start > 0 && /\w/.test(line[start - 1])) start--;
|
|
61
|
+
while (end < line.length && /\w/.test(line[end])) end++;
|
|
62
|
+
return start === end ? null : line.substring(start, end);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { getHoverInfo };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refactoring Capability
|
|
3
|
+
*/
|
|
4
|
+
const { CodeActionKind } = require('vscode-languageserver/node');
|
|
5
|
+
|
|
6
|
+
function getRefactorings(document, range, context, tools, lang) {
|
|
7
|
+
const actions = [];
|
|
8
|
+
for (const diag of context.diagnostics || []) {
|
|
9
|
+
if (diag.code === 'browser-not-init') {
|
|
10
|
+
actions.push({ title: 'Add browser_init() at start', kind: CodeActionKind.QuickFix, diagnostics: [diag], edit: { changes: { [document.uri]: [{ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, newText: "await browser_init({ headless: false });\n\n" }] } } });
|
|
11
|
+
}
|
|
12
|
+
if (diag.code === 'missing-cleanup') {
|
|
13
|
+
const lastLine = document.lineCount - 1;
|
|
14
|
+
actions.push({ title: 'Add browser_close() at end', kind: CodeActionKind.QuickFix, diagnostics: [diag], edit: { changes: { [document.uri]: [{ range: { start: { line: lastLine, character: 9999 }, end: { line: lastLine, character: 9999 } }, newText: "\n\nawait browser_close();" }] } } });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const selectedText = document.getText(range);
|
|
18
|
+
if (selectedText.includes('await')) {
|
|
19
|
+
actions.push({ title: 'Wrap in try-catch', kind: CodeActionKind.RefactorRewrite, edit: { changes: { [document.uri]: [{ range, newText: `try {\n ${selectedText.split('\n').join('\n ')}\n} catch (error) {\n console.error(error);\n}` }] } } });
|
|
20
|
+
}
|
|
21
|
+
return actions;
|
|
22
|
+
}
|
|
23
|
+
module.exports = { getRefactorings };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Simulation Capability
|
|
3
|
+
*/
|
|
4
|
+
function simulateWorkflow(document, tools, lang, execute = false) {
|
|
5
|
+
const text = document.getText();
|
|
6
|
+
const codeLenses = [];
|
|
7
|
+
const toolNames = tools.map(t => t.name);
|
|
8
|
+
const regex = new RegExp(`(${toolNames.join('|')})\\s*\\(`, 'g');
|
|
9
|
+
const lines = text.split('\n');
|
|
10
|
+
let stepCount = 0;
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13
|
+
let match;
|
|
14
|
+
while ((match = regex.exec(lines[i])) !== null) {
|
|
15
|
+
stepCount++;
|
|
16
|
+
const tool = tools.find(t => t.name === match[1]);
|
|
17
|
+
codeLenses.push({
|
|
18
|
+
range: { start: { line: i, character: match.index }, end: { line: i, character: match.index + match[1].length } },
|
|
19
|
+
command: { title: `${tool?.emoji || '▶️'} Step ${stepCount}`, command: 'braveRealBrowser.stepDetails', arguments: [match[1], i] }
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
regex.lastIndex = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (stepCount > 0) {
|
|
26
|
+
codeLenses.unshift({ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, command: { title: `▶️ Run Simulation (${stepCount} steps)`, command: 'braveRealBrowser.runSimulation', arguments: [document.uri] } });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return codeLenses;
|
|
30
|
+
}
|
|
31
|
+
module.exports = { simulateWorkflow };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snippets Capability
|
|
3
|
+
*/
|
|
4
|
+
function getSnippets(category, tools, lang) {
|
|
5
|
+
const snippets = {
|
|
6
|
+
basic: [{ name: 'basic-workflow', prefix: 'brave-basic', description: lang.snippets?.basicFlow || 'Basic flow', body: ["await browser_init({ headless: ${1:false} });","await navigate({ url: '${2:https://example.com}' });","${0}","await browser_close();"] }],
|
|
7
|
+
login: [{ name: 'login-flow', prefix: 'brave-login', description: lang.snippets?.loginFlow || 'Login flow', body: ["await browser_init({ headless: false, turnstile: true });","await navigate({ url: '${1:https://example.com/login}' });","await type({ selector: '${2:input[name=\"email\"]}', text: '${3:user@example.com}' });","await type({ selector: '${4:input[name=\"password\"]}', text: '${5:password}' });","await click({ selector: '${6:button[type=\"submit\"]}', humanLike: true });","${0}"] }],
|
|
8
|
+
scraping: [{ name: 'scrape-flow', prefix: 'brave-scrape', description: lang.snippets?.scrapeFlow || 'Scraping', body: ["await browser_init({ headless: true });","await navigate({ url: '${1:https://example.com}' });","const content = await get_content({ format: '${2:text}' });","const links = await link_harvester({ types: ['all'] });","${0}","await browser_close();"] }],
|
|
9
|
+
captcha: [{ name: 'captcha-flow', prefix: 'brave-captcha', description: lang.snippets?.captchaFlow || 'CAPTCHA', body: ["await browser_init({ headless: false, turnstile: true });","await navigate({ url: '${1:https://site-with-captcha.com}' });","await solve_captcha({ type: 'auto', timeout: 30000 });","${0}"] }],
|
|
10
|
+
};
|
|
11
|
+
return category && snippets[category] ? snippets[category] : Object.values(snippets).flat();
|
|
12
|
+
}
|
|
13
|
+
module.exports = { getSnippets };
|
package/src/lsp/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Brave Real Browser LSP Server - Entry Point
|
|
4
|
+
*/
|
|
5
|
+
const colors = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m' };
|
|
6
|
+
|
|
7
|
+
function displayBanner() {
|
|
8
|
+
console.error('');
|
|
9
|
+
console.error(`${colors.bright}${colors.cyan}╔════════════════════════════════════════════════════════════╗${colors.reset}`);
|
|
10
|
+
console.error(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.bright}${colors.magenta}🦁 Brave Real Browser LSP Server${colors.reset} ${colors.cyan}║${colors.reset}`);
|
|
11
|
+
console.error(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.dim}Language Server Protocol for IDE Intelligence${colors.reset} ${colors.cyan}║${colors.reset}`);
|
|
12
|
+
console.error(`${colors.bright}${colors.cyan}╚════════════════════════════════════════════════════════════╝${colors.reset}`);
|
|
13
|
+
console.error('');
|
|
14
|
+
console.error(`${colors.bright}${colors.green}🎯 Capabilities:${colors.reset}`);
|
|
15
|
+
const caps = [['✨ Autocomplete', 'Tool names, parameters, values'],['📖 Hover', 'Documentation on hover'],['⚠️ Diagnostics', 'Error & warning detection'],['📝 Snippets', 'Code templates & workflows'],['🔧 Refactoring', 'Quick fixes & code actions'],['🎬 Simulation', 'Workflow simulation in IDE'],['🌍 Multi-language', 'English & Hindi support']];
|
|
16
|
+
for (const [n, d] of caps) console.error(` ${colors.yellow}${n.padEnd(20)}${colors.reset}${colors.dim}${d}${colors.reset}`);
|
|
17
|
+
console.error('');
|
|
18
|
+
console.error(`${colors.bright}${colors.blue}🔗 Transport:${colors.reset} STDIO`);
|
|
19
|
+
console.error(`${colors.bright}${colors.green}Starting LSP Server...${colors.reset}`);
|
|
20
|
+
console.error('');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!process.stdin.isTTY) { require('./server.js'); }
|
|
24
|
+
else { displayBanner(); require('./server.js'); }
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* English Language Pack for LSP
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
tools: {
|
|
7
|
+
browser_init: {
|
|
8
|
+
label: 'Initialize Browser',
|
|
9
|
+
detail: 'Start Brave browser with stealth mode',
|
|
10
|
+
documentation: 'Launches Brave browser with anti-detection features, ad blocker, and optional proxy support.',
|
|
11
|
+
parameters: {
|
|
12
|
+
headless: 'Run browser without visible window',
|
|
13
|
+
proxy: 'Proxy server configuration',
|
|
14
|
+
turnstile: 'Enable Cloudflare Turnstile auto-solver',
|
|
15
|
+
enableBlocker: 'Enable ad/tracker blocking'
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
navigate: { label: 'Navigate to URL', detail: 'Go to a specific webpage', documentation: 'Navigates the browser to the specified URL.', parameters: { url: 'The URL to navigate to', waitUntil: 'When to consider navigation complete', timeout: 'Maximum wait time' } },
|
|
19
|
+
get_content: { label: 'Get Page Content', detail: 'Extract content from page', documentation: 'Gets the page content in specified format.', parameters: { format: 'Output format', selector: 'CSS selector' } },
|
|
20
|
+
wait: { label: 'Wait', detail: 'Wait for element or timeout', documentation: 'Pauses execution until condition is met.', parameters: { type: 'Wait type', value: 'Selector or timeout', timeout: 'Maximum wait' } },
|
|
21
|
+
click: { label: 'Click Element', detail: 'Click with human-like behavior', documentation: 'Clicks on an element using ghost-cursor.', parameters: { selector: 'Element selector', humanLike: 'Use realistic movement', clickCount: 'Number of clicks', delay: 'Click delay' } },
|
|
22
|
+
type: { label: 'Type Text', detail: 'Type into input field', documentation: 'Types text with realistic keystroke delays.', parameters: { selector: 'Input selector', text: 'Text to type', delay: 'Keystroke delay', clear: 'Clear first' } },
|
|
23
|
+
browser_close: { label: 'Close Browser', detail: 'Close and cleanup', documentation: 'Closes the browser.', parameters: { force: 'Force close' } },
|
|
24
|
+
solve_captcha: { label: 'Solve CAPTCHA', detail: 'Auto-solve CAPTCHAs', documentation: 'Automatically solves Turnstile, reCAPTCHA, hCaptcha.', parameters: { type: 'CAPTCHA type', timeout: 'Max solve time' } },
|
|
25
|
+
random_scroll: { label: 'Random Scroll', detail: 'Human-like scrolling', documentation: 'Scrolls with human-like behavior.', parameters: { direction: 'Scroll direction', amount: 'Scroll amount', smooth: 'Smooth scrolling' } },
|
|
26
|
+
find_element: { label: 'Find Element', detail: 'Locate elements', documentation: 'Finds elements by selector/xpath/text.', parameters: { selector: 'CSS selector', xpath: 'XPath', text: 'Text content', multiple: 'Return all' } },
|
|
27
|
+
save_content_as_markdown: { label: 'Save as Markdown', detail: 'Export to MD', documentation: 'Saves page as Markdown file.', parameters: { filename: 'Output filename', selector: 'Content selector', includeImages: 'Include images', includeMeta: 'Include meta' } },
|
|
28
|
+
redirect_tracer: { label: 'Trace Redirects', detail: 'Follow redirects', documentation: 'Traces redirects to final destination.', parameters: { url: 'URL to trace', maxRedirects: 'Max redirects', includeHeaders: 'Include headers' } },
|
|
29
|
+
search_regex: { label: 'Regex Search', detail: 'Search with patterns', documentation: 'Searches content using regex.', parameters: { pattern: 'Regex pattern', flags: 'Regex flags', source: 'Search source' } },
|
|
30
|
+
extract_json: { label: 'Extract JSON', detail: 'Get JSON data', documentation: 'Extracts JSON from page/scripts.', parameters: { source: 'Data source', selector: 'CSS selector', jsonPath: 'JSONPath' } },
|
|
31
|
+
scrape_meta_tags: { label: 'Scrape Meta Tags', detail: 'Get meta data', documentation: 'Extracts meta, OG, Twitter data.', parameters: { types: 'Tag types' } },
|
|
32
|
+
press_key: { label: 'Press Key', detail: 'Keyboard input', documentation: 'Presses keyboard keys.', parameters: { key: 'Key to press', modifiers: 'Modifier keys', count: 'Press count' } },
|
|
33
|
+
progress_tracker: { label: 'Track Progress', detail: 'Monitor automation', documentation: 'Tracks automation progress.', parameters: { action: 'Action type', taskName: 'Task name', progress: 'Progress %' } },
|
|
34
|
+
deep_analysis: { label: 'Deep Analysis', detail: 'Page analysis', documentation: 'Analyzes SEO, performance, accessibility.', parameters: { types: 'Analysis types', detailed: 'Detailed info' } },
|
|
35
|
+
network_recorder: { label: 'Record Network', detail: 'Capture requests', documentation: 'Records network requests.', parameters: { action: 'Action', filter: 'Request filter' } },
|
|
36
|
+
link_harvester: { label: 'Harvest Links', detail: 'Extract links', documentation: 'Extracts all links from page.', parameters: { types: 'Link types', selector: 'Container', includeText: 'Include text' } },
|
|
37
|
+
cookie_manager: { label: 'Manage Cookies', detail: 'Cookie operations', documentation: 'Manages browser cookies.', parameters: { action: 'Action', name: 'Cookie name', value: 'Cookie value', domain: 'Domain' } },
|
|
38
|
+
file_downloader: { label: 'Download File', detail: 'Download from URL', documentation: 'Downloads files.', parameters: { url: 'File URL', filename: 'Output name', directory: 'Output dir' } },
|
|
39
|
+
iframe_handler: { label: 'Handle iFrame', detail: 'iFrame operations', documentation: 'Handles iFrame content.', parameters: { action: 'Action', selector: 'iFrame selector', index: 'iFrame index' } },
|
|
40
|
+
stream_extractor: { label: 'Extract Streams', detail: 'Get video/audio', documentation: 'Extracts stream URLs.', parameters: { types: 'Stream types', quality: 'Quality' } },
|
|
41
|
+
js_scrape: { label: 'JS Scrape', detail: 'Scrape JS content', documentation: 'Scrapes JS-rendered content.', parameters: { selector: 'CSS selector', waitForJS: 'Wait for JS', timeout: 'Max wait' } },
|
|
42
|
+
execute_js: { label: 'Execute JS', detail: 'Run JavaScript', documentation: 'Executes custom JavaScript.', parameters: { code: 'JS code', returnValue: 'Return result' } },
|
|
43
|
+
player_api_hook: { label: 'Player API Hook', detail: 'Video player control', documentation: 'Hooks into video players.', parameters: { playerType: 'Player type', action: 'Action' } },
|
|
44
|
+
form_automator: { label: 'Automate Form', detail: 'Fill and submit', documentation: 'Auto-fills and submits forms.', parameters: { selector: 'Form selector', data: 'Form data', submit: 'Submit', humanLike: 'Human delays' } }
|
|
45
|
+
},
|
|
46
|
+
diagnostics: {
|
|
47
|
+
browserNotInit: 'Browser not initialized. Call browser_init first.',
|
|
48
|
+
invalidSelector: 'Invalid CSS selector syntax.',
|
|
49
|
+
missingUrl: 'URL is required.',
|
|
50
|
+
invalidUrl: 'Invalid URL format.',
|
|
51
|
+
timeoutTooLong: 'Timeout exceeds maximum.',
|
|
52
|
+
missingRequired: 'Required parameter is missing.',
|
|
53
|
+
invalidJson: 'Invalid JSON syntax.',
|
|
54
|
+
selectorNotFound: 'Element not found.',
|
|
55
|
+
unknownTool: 'Unknown tool name.',
|
|
56
|
+
deprecatedTool: 'This tool is deprecated.'
|
|
57
|
+
},
|
|
58
|
+
snippets: {
|
|
59
|
+
basicFlow: 'Basic browser automation flow',
|
|
60
|
+
loginFlow: 'Login automation with form fill',
|
|
61
|
+
scrapeFlow: 'Web scraping workflow',
|
|
62
|
+
captchaFlow: 'CAPTCHA solving workflow',
|
|
63
|
+
downloadFlow: 'File download automation'
|
|
64
|
+
},
|
|
65
|
+
ui: { autocompletePlaceholder: 'Type tool name...', noSuggestions: 'No suggestions', loading: 'Loading...', error: 'Error', warning: 'Warning', info: 'Info' }
|
|
66
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hindi Language Pack for LSP - हिंदी भाषा पैक
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
tools: {
|
|
7
|
+
browser_init: { label: 'ब्राउज़र शुरू करें', detail: 'Brave ब्राउज़र स्टेल्थ मोड में', documentation: 'Brave ब्राउज़र को एंटी-डिटेक्शन के साथ लॉन्च करता है।', parameters: { headless: 'बिना विंडो चलाएं', proxy: 'प्रॉक्सी कॉन्फ़िगरेशन', turnstile: 'Turnstile ऑटो-सॉल्वर', enableBlocker: 'एड ब्लॉकिंग' } },
|
|
8
|
+
navigate: { label: 'URL पर जाएं', detail: 'वेबपेज पर जाएं', documentation: 'ब्राउज़र को URL पर ले जाता है।', parameters: { url: 'जाने का URL', waitUntil: 'नेविगेशन पूरा कब', timeout: 'अधिकतम समय' } },
|
|
9
|
+
get_content: { label: 'कंटेंट लें', detail: 'पेज से कंटेंट', documentation: 'पेज कंटेंट प्राप्त करता है।', parameters: { format: 'फॉर्मेट', selector: 'CSS selector' } },
|
|
10
|
+
wait: { label: 'इंतजार करें', detail: 'एलीमेंट का इंतजार', documentation: 'शर्त पूरी होने तक रुकता है।', parameters: { type: 'इंतजार प्रकार', value: 'Selector या समय', timeout: 'अधिकतम' } },
|
|
11
|
+
click: { label: 'क्लिक करें', detail: 'मानव-जैसा क्लिक', documentation: 'ghost-cursor से क्लिक करता है।', parameters: { selector: 'एलीमेंट selector', humanLike: 'वास्तविक मूवमेंट', clickCount: 'क्लिक संख्या', delay: 'देरी' } },
|
|
12
|
+
type: { label: 'टाइप करें', detail: 'इनपुट में टाइप', documentation: 'वास्तविक कीस्ट्रोक के साथ टाइप करता है।', parameters: { selector: 'इनपुट selector', text: 'टेक्स्ट', delay: 'कीस्ट्रोक देरी', clear: 'पहले साफ करें' } },
|
|
13
|
+
browser_close: { label: 'ब्राउज़र बंद करें', detail: 'बंद और क्लीनअप', documentation: 'ब्राउज़र बंद करता है।', parameters: { force: 'फोर्स क्लोज़' } },
|
|
14
|
+
solve_captcha: { label: 'CAPTCHA हल करें', detail: 'ऑटो-सॉल्व', documentation: 'Turnstile, reCAPTCHA हल करता है।', parameters: { type: 'CAPTCHA प्रकार', timeout: 'अधिकतम समय' } },
|
|
15
|
+
random_scroll: { label: 'रैंडम स्क्रॉल', detail: 'मानव-जैसा स्क्रॉल', documentation: 'रैंडम स्क्रॉल करता है।', parameters: { direction: 'दिशा', amount: 'मात्रा', smooth: 'स्मूथ' } },
|
|
16
|
+
find_element: { label: 'एलीमेंट खोजें', detail: 'पेज पर खोजें', documentation: 'selector/xpath से खोजता है।', parameters: { selector: 'CSS selector', xpath: 'XPath', text: 'टेक्स्ट', multiple: 'सभी लौटाएं' } },
|
|
17
|
+
save_content_as_markdown: { label: 'MD में सेव', detail: 'Markdown एक्सपोर्ट', documentation: 'Markdown फाइल में सेव करता है।', parameters: { filename: 'फाइलनेम', selector: 'कंटेंट selector', includeImages: 'इमेज शामिल', includeMeta: 'मेटा शामिल' } },
|
|
18
|
+
redirect_tracer: { label: 'रीडायरेक्ट ट्रेस', detail: 'रीडायरेक्ट फॉलो', documentation: 'सभी रीडायरेक्ट ट्रेस करता है।', parameters: { url: 'ट्रेस URL', maxRedirects: 'अधिकतम', includeHeaders: 'हेडर शामिल' } },
|
|
19
|
+
search_regex: { label: 'Regex सर्च', detail: 'पैटर्न से खोज', documentation: 'regex से खोजता है।', parameters: { pattern: 'पैटर्न', flags: 'फ्लैग्स', source: 'सोर्स' } },
|
|
20
|
+
extract_json: { label: 'JSON निकालें', detail: 'JSON डेटा', documentation: 'JSON निकालता है।', parameters: { source: 'सोर्स', selector: 'selector', jsonPath: 'JSONPath' } },
|
|
21
|
+
scrape_meta_tags: { label: 'Meta Tags', detail: 'मेटा डेटा', documentation: 'Meta, OG, Twitter डेटा।', parameters: { types: 'प्रकार' } },
|
|
22
|
+
press_key: { label: 'की प्रेस', detail: 'कीबोर्ड इनपुट', documentation: 'की प्रेस करता है।', parameters: { key: 'की', modifiers: 'मॉडिफायर', count: 'संख्या' } },
|
|
23
|
+
progress_tracker: { label: 'प्रोग्रेस ट्रैक', detail: 'ऑटोमेशन मॉनिटर', documentation: 'प्रोग्रेस ट्रैक करता है।', parameters: { action: 'एक्शन', taskName: 'टास्क नाम', progress: 'प्रतिशत' } },
|
|
24
|
+
deep_analysis: { label: 'गहरा विश्लेषण', detail: 'पेज विश्लेषण', documentation: 'SEO, परफॉर्मेंस विश्लेषण।', parameters: { types: 'प्रकार', detailed: 'विस्तृत' } },
|
|
25
|
+
network_recorder: { label: 'नेटवर्क रिकॉर्ड', detail: 'रिक्वेस्ट कैप्चर', documentation: 'नेटवर्क रिकॉर्ड करता है।', parameters: { action: 'एक्शन', filter: 'फ़िल्टर' } },
|
|
26
|
+
link_harvester: { label: 'लिंक्स निकालें', detail: 'सभी लिंक्स', documentation: 'सभी लिंक्स निकालता है।', parameters: { types: 'प्रकार', selector: 'कंटेनर', includeText: 'टेक्स्ट शामिल' } },
|
|
27
|
+
cookie_manager: { label: 'कुकीज़ मैनेज', detail: 'कुकी ऑपरेशन', documentation: 'कुकीज़ मैनेज करता है।', parameters: { action: 'एक्शन', name: 'नाम', value: 'वैल्यू', domain: 'डोमेन' } },
|
|
28
|
+
file_downloader: { label: 'फाइल डाउनलोड', detail: 'URL से डाउनलोड', documentation: 'फाइल्स डाउनलोड करता है।', parameters: { url: 'फाइल URL', filename: 'नाम', directory: 'डायरेक्टरी' } },
|
|
29
|
+
iframe_handler: { label: 'iFrame हैंडल', detail: 'iFrame ऑपरेशन', documentation: 'iFrame हैंडल करता है।', parameters: { action: 'एक्शन', selector: 'selector', index: 'इंडेक्स' } },
|
|
30
|
+
stream_extractor: { label: 'स्ट्रीम निकालें', detail: 'वीडियो/ऑडियो URL', documentation: 'स्ट्रीम URLs निकालता है।', parameters: { types: 'प्रकार', quality: 'क्वालिटी' } },
|
|
31
|
+
js_scrape: { label: 'JS स्क्रैप', detail: 'JS कंटेंट', documentation: 'JS-रेंडर्ड कंटेंट।', parameters: { selector: 'selector', waitForJS: 'JS इंतजार', timeout: 'अधिकतम' } },
|
|
32
|
+
execute_js: { label: 'JS चलाएं', detail: 'JavaScript रन', documentation: 'कस्टम JS चलाता है।', parameters: { code: 'कोड', returnValue: 'रिज़ल्ट लौटाएं' } },
|
|
33
|
+
player_api_hook: { label: 'Player Hook', detail: 'वीडियो प्लेयर', documentation: 'प्लेयर में हुक करता है।', parameters: { playerType: 'प्लेयर', action: 'एक्शन' } },
|
|
34
|
+
form_automator: { label: 'फॉर्म ऑटोमेट', detail: 'फॉर्म भरें', documentation: 'फॉर्म भरता और सबमिट करता है।', parameters: { selector: 'फॉर्म selector', data: 'डेटा', submit: 'सबमिट', humanLike: 'मानव देरी' } }
|
|
35
|
+
},
|
|
36
|
+
diagnostics: {
|
|
37
|
+
browserNotInit: 'ब्राउज़र इनिशियलाइज़ नहीं। पहले browser_init करें।',
|
|
38
|
+
invalidSelector: 'अमान्य CSS selector।',
|
|
39
|
+
missingUrl: 'URL आवश्यक है।',
|
|
40
|
+
invalidUrl: 'अमान्य URL फॉर्मेट।',
|
|
41
|
+
timeoutTooLong: 'टाइमआउट बहुत लंबा।',
|
|
42
|
+
missingRequired: 'आवश्यक पैरामीटर गायब।',
|
|
43
|
+
invalidJson: 'अमान्य JSON।',
|
|
44
|
+
selectorNotFound: 'एलीमेंट नहीं मिला।',
|
|
45
|
+
unknownTool: 'अज्ञात टूल।',
|
|
46
|
+
deprecatedTool: 'यह टूल deprecated है।'
|
|
47
|
+
},
|
|
48
|
+
snippets: { basicFlow: 'बेसिक ऑटोमेशन', loginFlow: 'लॉगिन ऑटोमेशन', scrapeFlow: 'स्क्रैपिंग वर्कफ्लो', captchaFlow: 'CAPTCHA वर्कफ्लो', downloadFlow: 'डाउनलोड ऑटोमेशन' },
|
|
49
|
+
ui: { autocompletePlaceholder: 'टूल टाइप करें...', noSuggestions: 'कोई सुझाव नहीं', loading: 'लोड हो रहा...', error: 'त्रुटि', warning: 'चेतावनी', info: 'जानकारी' }
|
|
50
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brave Real Browser LSP Server
|
|
3
|
+
*/
|
|
4
|
+
const { createConnection, TextDocuments, ProposedFeatures, TextDocumentSyncKind, MarkupKind } = require('vscode-languageserver/node');
|
|
5
|
+
const { TextDocument } = require('vscode-languageserver-textdocument');
|
|
6
|
+
const { TOOLS } = require('../mcp/tools.js');
|
|
7
|
+
const { getCompletions } = require('./capabilities/autocomplete.js');
|
|
8
|
+
const { getHoverInfo } = require('./capabilities/hover.js');
|
|
9
|
+
const { getDiagnostics } = require('./capabilities/diagnostics.js');
|
|
10
|
+
const { getSnippets } = require('./capabilities/snippets.js');
|
|
11
|
+
const { getRefactorings } = require('./capabilities/refactoring.js');
|
|
12
|
+
const { simulateWorkflow } = require('./capabilities/simulation.js');
|
|
13
|
+
const { getLanguagePack } = require('./utils/i18n.js');
|
|
14
|
+
|
|
15
|
+
const connection = createConnection(ProposedFeatures.all, process.stdin, process.stdout);
|
|
16
|
+
const documents = new TextDocuments(TextDocument);
|
|
17
|
+
let settings = { language: 'en', maxDiagnostics: 100, enableSnippets: true, enableSimulation: true, enableRefactoring: true };
|
|
18
|
+
|
|
19
|
+
connection.onInitialize(() => ({
|
|
20
|
+
capabilities: {
|
|
21
|
+
textDocumentSync: TextDocumentSyncKind.Incremental,
|
|
22
|
+
completionProvider: { resolveProvider: true, triggerCharacters: ['.', "'", '"', '(', '{', ' '] },
|
|
23
|
+
hoverProvider: true,
|
|
24
|
+
codeActionProvider: true,
|
|
25
|
+
codeLensProvider: { resolveProvider: true },
|
|
26
|
+
signatureHelpProvider: { triggerCharacters: ['(', ','] },
|
|
27
|
+
documentSymbolProvider: true,
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
connection.onDidChangeConfiguration((change) => {
|
|
32
|
+
if (change.settings?.braveRealBrowser) settings = { ...settings, ...change.settings.braveRealBrowser };
|
|
33
|
+
documents.all().forEach(validateDocument);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
connection.onCompletion((params) => {
|
|
37
|
+
const doc = documents.get(params.textDocument.uri);
|
|
38
|
+
return doc ? getCompletions(doc, params.position, TOOLS, getLanguagePack(settings.language), settings) : [];
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
connection.onCompletionResolve((item) => {
|
|
42
|
+
const lang = getLanguagePack(settings.language);
|
|
43
|
+
const tool = TOOLS.find(t => t.name === item.data?.toolName);
|
|
44
|
+
if (tool && lang.tools[tool.name]) {
|
|
45
|
+
const tl = lang.tools[tool.name];
|
|
46
|
+
item.documentation = { kind: MarkupKind.Markdown, value: `### ${tool.emoji} ${tl.label}\n\n${tl.documentation}\n\n**Parameters:**\n${Object.entries(tl.parameters || {}).map(([k, v]) => `- \`${k}\`: ${v}`).join('\n')}` };
|
|
47
|
+
}
|
|
48
|
+
return item;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
connection.onHover((params) => {
|
|
52
|
+
const doc = documents.get(params.textDocument.uri);
|
|
53
|
+
return doc ? getHoverInfo(doc, params.position, TOOLS, getLanguagePack(settings.language)) : null;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function validateDocument(doc) {
|
|
57
|
+
connection.sendDiagnostics({ uri: doc.uri, diagnostics: getDiagnostics(doc, TOOLS, getLanguagePack(settings.language), settings.maxDiagnostics) });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
documents.onDidChangeContent((change) => validateDocument(change.document));
|
|
61
|
+
|
|
62
|
+
connection.onCodeAction((params) => {
|
|
63
|
+
const doc = documents.get(params.textDocument.uri);
|
|
64
|
+
return doc ? getRefactorings(doc, params.range, params.context, TOOLS, getLanguagePack(settings.language)) : [];
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
connection.onCodeLens((params) => {
|
|
68
|
+
const doc = documents.get(params.textDocument.uri);
|
|
69
|
+
return doc ? simulateWorkflow(doc, TOOLS, getLanguagePack(settings.language)) : [];
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
connection.onCodeLensResolve((lens) => lens);
|
|
73
|
+
|
|
74
|
+
connection.onRequest('braveRealBrowser/snippets', (params) => getSnippets(params?.category, TOOLS, getLanguagePack(settings.language)));
|
|
75
|
+
|
|
76
|
+
documents.listen(connection);
|
|
77
|
+
connection.listen();
|
|
78
|
+
console.error('🦁 Brave Real Browser LSP Server started');
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internationalization Utility
|
|
3
|
+
*/
|
|
4
|
+
const en = require('../languages/en.js');
|
|
5
|
+
const hi = require('../languages/hi.js');
|
|
6
|
+
const languagePacks = { en, hi, english: en, hindi: hi };
|
|
7
|
+
|
|
8
|
+
function getLanguagePack(langCode) {
|
|
9
|
+
return languagePacks[(langCode || 'en').toLowerCase()] || languagePacks.en;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getAvailableLanguages() {
|
|
13
|
+
return [
|
|
14
|
+
{ code: 'en', name: 'English', nativeName: 'English' },
|
|
15
|
+
{ code: 'hi', name: 'Hindi', nativeName: 'हिंदी' },
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function translate(langCode, key, fallback = '') {
|
|
20
|
+
const lang = getLanguagePack(langCode);
|
|
21
|
+
let value = lang;
|
|
22
|
+
for (const k of key.split('.')) {
|
|
23
|
+
value = value?.[k];
|
|
24
|
+
if (value === undefined) break;
|
|
25
|
+
}
|
|
26
|
+
return value || fallback;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { getLanguagePack, getAvailableLanguages, translate };
|