chrome-bridge-mcp 1.32.2 → 1.32.3
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.
|
@@ -23,7 +23,41 @@ export function elementOutcome(results, selector) {
|
|
|
23
23
|
if (first?.result != null) return first.result;
|
|
24
24
|
if (first?.error?.message) throw new Error(first.error.message);
|
|
25
25
|
if (!selector) throw new Error('The page returned no result for the command');
|
|
26
|
-
throw
|
|
26
|
+
throw notFoundError(selector);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Sintassi di Playwright, jQuery o XPath dentro un selettore CSS: querySelector
|
|
31
|
+
* la rifiuta con un SyntaxError, che executeScript riduce a un risultato vuoto.
|
|
32
|
+
* «no element matches» faceva credere a un selettore valido senza riscontri
|
|
33
|
+
* (note observe chrome-bridg-a1255c1b, chrome-bridg-5a9d399e, 1.32.1). Le
|
|
34
|
+
* stringhe tra virgolette si tolgono prima: `[title=":visible"]` è CSS valido.
|
|
35
|
+
*/
|
|
36
|
+
export function nonCssToken(selector) {
|
|
37
|
+
const s = String(selector).replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, '""');
|
|
38
|
+
if (/^\s*\(?\/\//.test(s) || /(^|[\s,])xpath=/.test(s)) return 'XPath';
|
|
39
|
+
const engine = /(^|[\s,])(text|css|id|role|data-testid|internal:[\w-]+)=/.exec(s);
|
|
40
|
+
if (engine) return `${engine[2]}=`;
|
|
41
|
+
if (/(^|[^>])>>(?!>)/.test(s)) return '>>';
|
|
42
|
+
const pseudo = /:(has-text|text-is|text-matches|text|visible|hidden|nth-match|left-of|right-of|above|below|near|contains|eq|gt|lt|first|last|even|odd)(?![\w-])/.exec(s);
|
|
43
|
+
return pseudo ? `:${pseudo[1]}` : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function notFoundError(selector) {
|
|
47
|
+
const token = nonCssToken(selector);
|
|
48
|
+
if (!token && String(selector).includes('>>>')) {
|
|
49
|
+
// ">>>" entra nello shadowRoot del primo elemento che la parte prima trova:
|
|
50
|
+
// un antenato senza shadowRoot («main >>> span») non trova niente anche
|
|
51
|
+
// se lo span c'è (note observe chrome-bridg-6379570f, chrome-bridg-f262acc4).
|
|
52
|
+
const host = String(selector).split('>>>')[0].trim();
|
|
53
|
+
return new Error(`Element not found: ${selector} (no element matches; ">>>" enters the shadowRoot of the first element "${host}" matches, `
|
|
54
|
+
+ 'so that part must be the shadow host itself, e.g. the custom element\'s tag, not an ancestor of it. '
|
|
55
|
+
+ 'Check it with query_dom, or locate the target by its visible text with find_text, which searches inside shadow roots)');
|
|
56
|
+
}
|
|
57
|
+
if (!token) return new Error(`Element not found: ${selector} (no element matches; check it with query_dom)`);
|
|
58
|
+
const what = token === 'XPath' ? 'XPath is not CSS' : `"${token}" is Playwright/jQuery syntax, not CSS`;
|
|
59
|
+
return new Error(`Invalid selector: ${selector} — ${what}: the page rejects the whole selector, every item of a comma list included. `
|
|
60
|
+
+ 'Use plain CSS (">>>" pierces shadow DOM), a ref from get_interactives, or find_text to locate by visible text.');
|
|
27
61
|
}
|
|
28
62
|
|
|
29
63
|
/**
|
|
@@ -100,10 +100,33 @@ async function attach(tabId, purpose) {
|
|
|
100
100
|
} catch (e) {
|
|
101
101
|
const msg = String(e?.message ?? e);
|
|
102
102
|
if (/already attached/i.test(msg)) throw new Error(`${purpose} unavailable: another debugger (DevTools?) is attached to this tab — close it, or retry without it`);
|
|
103
|
+
if (/chrome-extension:\/\/ URL of different extension/i.test(msg)) throw new Error(`${purpose} unavailable: ${await foreignExtensionCause(tabId, purpose)}`);
|
|
103
104
|
throw new Error(`${purpose} unavailable: ${msg}`);
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
// Chrome rifiuta chrome.debugger.attach su una scheda che contiene anche un
|
|
109
|
+
// solo frame di un'altra estensione (password manager, traduttore, correttore
|
|
110
|
+
// che iniettano un iframe chrome-extension://). Il messaggio grezzo non dice
|
|
111
|
+
// quale frame né che riprovare è inutile: un agente ha ripetuto lo stesso
|
|
112
|
+
// click 7 volte (nota observe chrome-bridg-8f18b944, 1.32.1).
|
|
113
|
+
async function foreignExtensionCause(tabId, purpose) {
|
|
114
|
+
let ids = [];
|
|
115
|
+
try {
|
|
116
|
+
const frames = await chrome.webNavigation.getAllFrames({ tabId });
|
|
117
|
+
const self = chrome.runtime?.id;
|
|
118
|
+
ids = [...new Set((frames || [])
|
|
119
|
+
.map((f) => /^chrome-extension:\/\/([^/]+)/.exec(f.url || '')?.[1])
|
|
120
|
+
.filter((id) => id && id !== self))];
|
|
121
|
+
} catch { /* scheda chiusa o frame illeggibili: resta la causa senza id */ }
|
|
122
|
+
const which = ids.length ? ` (chrome-extension://${ids.join(', chrome-extension://')})` : '';
|
|
123
|
+
const remedy = purpose === 'trusted input'
|
|
124
|
+
? 'Retry without trusted (synthetic events)'
|
|
125
|
+
: 'Use the options that work without the debugger';
|
|
126
|
+
return `another extension has a frame in this page${which}, and Chrome does not let chrome.debugger attach to a tab that holds another extension's frame: retrying the same call fails the same way. `
|
|
127
|
+
+ `${remedy}, or turn that extension off for this site (chrome://extensions → Details → Site access) and reload the tab.`;
|
|
128
|
+
}
|
|
129
|
+
|
|
107
130
|
/** Tiene il debugger agganciato alla scheda per un motivo, finché releaseDebugger. */
|
|
108
131
|
export async function holdDebugger(tabId, why, purpose = why) {
|
|
109
132
|
listenDetach();
|
package/extension/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Chrome Bridge for Claude Code",
|
|
4
|
-
"version": "1.32.
|
|
4
|
+
"version": "1.32.3",
|
|
5
5
|
"description": "Bridge your browser to Claude Code: 69 token-efficient web-dev automation tools over a local WebSocket. ChromeOS included.",
|
|
6
6
|
"homepage_url": "https://github.com/frsorrentino/chrome-bridge",
|
|
7
7
|
"minimum_chrome_version": "135",
|
|
@@ -13,7 +13,7 @@ import { computeTiles } from './lib/tile-layout.js';
|
|
|
13
13
|
import { classifyDownload } from './lib/download-state.js';
|
|
14
14
|
import { downloadName } from './lib/capture-names.js';
|
|
15
15
|
import { findTextInPage } from './lib/find-text.js';
|
|
16
|
-
import { elementOutcome, watchNavErrors, navErrorMessage } from './lib/command-outcome.js';
|
|
16
|
+
import { elementOutcome, notFoundError, watchNavErrors, navErrorMessage } from './lib/command-outcome.js';
|
|
17
17
|
import { withDebugger, keyEvents, mouseClickEvents, holdDebugger, releaseDebugger, debuggerHeld } from './lib/trusted-input.js';
|
|
18
18
|
import { emulationCommands } from './lib/emulation.js';
|
|
19
19
|
const { pushError } = globalThis.__cbTelemetry;
|
|
@@ -1021,7 +1021,7 @@ async function trustedTarget(tabId, frame_id, selector, what) {
|
|
|
1021
1021
|
world: 'MAIN',
|
|
1022
1022
|
});
|
|
1023
1023
|
const out = results?.[0]?.result;
|
|
1024
|
-
if (!out || out.missing) throw
|
|
1024
|
+
if (!out || out.missing) throw notFoundError(selector);
|
|
1025
1025
|
return out;
|
|
1026
1026
|
}
|
|
1027
1027
|
|
|
@@ -3458,7 +3458,7 @@ async function cmdWatchDom({ selector = 'body', attributes = true, childList = t
|
|
|
3458
3458
|
args: [selector, { attributes, childList, characterData, subtree }],
|
|
3459
3459
|
world: 'MAIN',
|
|
3460
3460
|
});
|
|
3461
|
-
if (install?.[0]?.result?.missing) throw
|
|
3461
|
+
if (install?.[0]?.result?.missing) throw notFoundError(selector);
|
|
3462
3462
|
|
|
3463
3463
|
// Read mutations
|
|
3464
3464
|
const results = await chrome.scripting.executeScript({
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-bridge-mcp",
|
|
3
|
-
"version": "1.32.
|
|
3
|
+
"version": "1.32.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "chrome-bridge-mcp",
|
|
9
|
-
"version": "1.32.
|
|
9
|
+
"version": "1.32.3",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@jridgewell/trace-mapping": "^0.3.31",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-bridge-mcp",
|
|
3
3
|
"mcpName": "io.github.frsorrentino/chrome-bridge",
|
|
4
|
-
"version": "1.32.
|
|
4
|
+
"version": "1.32.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Chrome Bridge by frsorrentino: MCP server that lets Claude Code borrow a tab from your own logged-in Chrome and hand it back — logins, 2FA and CAPTCHAs stay with you (handoff). 69 tools; 2.8-4.3× cheaper and up to 9.3× fewer turns than Claude in Chrome, measured; ChromeOS included.",
|
|
7
7
|
"main": "server/index.js",
|