@jobshimo/browser-link 0.0.1 → 0.2.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/LICENSE +21 -21
- package/README.md +102 -83
- package/dist/cli.js +50 -36
- package/dist/cli.js.map +1 -1
- package/dist/commands/about.d.ts +17 -18
- package/dist/commands/about.js +149 -190
- package/dist/commands/about.js.map +1 -1
- package/dist/commands/updates.d.ts +19 -0
- package/dist/commands/updates.js +83 -0
- package/dist/commands/updates.js.map +1 -0
- package/dist/commands/welcome.d.ts +10 -8
- package/dist/commands/welcome.js +49 -128
- package/dist/commands/welcome.js.map +1 -1
- package/dist/extension/background.js +90 -90
- package/dist/extension/icons/icon.svg +14 -14
- package/dist/extension/manifest.json +28 -28
- package/dist/extension/popup.html +88 -88
- package/dist/installers/copilot.d.ts +2 -0
- package/dist/installers/copilot.js +72 -0
- package/dist/installers/copilot.js.map +1 -0
- package/dist/installers/index.d.ts +2 -3
- package/dist/installers/index.js +5 -4
- package/dist/installers/index.js.map +1 -1
- package/dist/installers/opencode.js +49 -21
- package/dist/installers/opencode.js.map +1 -1
- package/dist/installers/types.d.ts +1 -1
- package/dist/map/db.js +28 -28
- package/dist/map/queries.js +4 -4
- package/dist/tools/server-instructions.js +46 -46
- package/dist/ui/app.d.ts +7 -0
- package/dist/ui/app.js +62 -0
- package/dist/ui/app.js.map +1 -0
- package/dist/ui/components.d.ts +18 -0
- package/dist/ui/components.js +27 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/screens.d.ts +48 -0
- package/dist/ui/screens.js +291 -0
- package/dist/ui/screens.js.map +1 -0
- package/dist/ui/start.d.ts +6 -0
- package/dist/ui/start.js +19 -0
- package/dist/ui/start.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +15 -0
- package/dist/version.js.map +1 -0
- package/package.json +69 -61
- package/dist/commands/menu.d.ts +0 -26
- package/dist/commands/menu.js +0 -187
- package/dist/commands/menu.js.map +0 -1
- package/dist/commands/tty.d.ts +0 -51
- package/dist/commands/tty.js +0 -148
- package/dist/commands/tty.js.map +0 -1
package/dist/commands/welcome.js
CHANGED
|
@@ -1,32 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/* Welcome screen strings (English / Spanish) shared by the interactive Ink
|
|
2
|
+
* Welcome view and any future non-interactive caller. No runtime logic here
|
|
3
|
+
* — every screen consumes this as a pure data module. */
|
|
4
4
|
export const I18N_WELCOME = {
|
|
5
5
|
en: {
|
|
6
6
|
title: 'browser-link',
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
|
|
7
|
+
aboutTitle: 'What this is',
|
|
8
|
+
about: [
|
|
9
|
+
'An MCP server that opens a small WebSocket bridge between an MCP',
|
|
10
|
+
'client (Claude Code, OpenCode, GitHub Copilot CLI…) and the Google',
|
|
11
|
+
'Chrome tabs you explicitly grant access to through a custom companion',
|
|
12
|
+
'extension you load locally.',
|
|
13
|
+
].join('\n'),
|
|
14
|
+
capabilitiesTitle: 'What the agent can do on a connected tab',
|
|
12
15
|
capabilities: [
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
' • Click and type into its elements',
|
|
18
|
-
' • Execute arbitrary JavaScript in the page context',
|
|
16
|
+
'• Navigate that tab to any URL',
|
|
17
|
+
'• Read its DOM, console and network traffic',
|
|
18
|
+
'• Click and type into its elements',
|
|
19
|
+
'• Execute arbitrary JavaScript in the page context',
|
|
19
20
|
'',
|
|
20
|
-
'Tabs you do not connect remain invisible to the agent.
|
|
21
|
-
'
|
|
22
|
-
'',
|
|
23
|
-
'
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
],
|
|
21
|
+
'Tabs you do not connect remain invisible to the agent. Each one is',
|
|
22
|
+
'enabled one by one, by hand. The server also remembers what it learns',
|
|
23
|
+
'about each app across sessions in a local SQLite map (selectors,',
|
|
24
|
+
'flows, gotchas — never uploaded anywhere).',
|
|
25
|
+
].join('\n'),
|
|
26
|
+
warningTitle: 'Read this before you continue',
|
|
27
27
|
warning: [
|
|
28
|
-
`${ansi.bold}${ansi.yellow}⚠ Read this before you continue${ansi.reset}`,
|
|
29
|
-
'',
|
|
30
28
|
'Connecting a tab gives the agent access to whatever is on it — any',
|
|
31
29
|
'logged-in session, saved card, wallet, banking page, work console or',
|
|
32
30
|
'admin panel the browser is currently showing on that tab.',
|
|
@@ -38,48 +36,40 @@ export const I18N_WELCOME = {
|
|
|
38
36
|
'Only connect tabs where you would be comfortable letting an automated',
|
|
39
37
|
'process act on your behalf. Disconnect a tab from the extension popup',
|
|
40
38
|
'when you are done with it.',
|
|
41
|
-
],
|
|
42
|
-
responsibility: 'You are responsible for every action the agent performs on the tabs '
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
'The setup menu after this screen will tell you where it lives so ' +
|
|
47
|
-
'you can load it via chrome://extensions → Load unpacked.',
|
|
48
|
-
actions: {
|
|
39
|
+
].join('\n'),
|
|
40
|
+
responsibility: 'You are responsible for every action the agent performs on the tabs you explicitly enable. The extension stays inert on any tab where you have not pressed "Conectar" yourself.',
|
|
41
|
+
extensionNote: 'The Chrome extension is custom and ships inside this package. The setup menu after this screen will tell you where it lives so you can load it via chrome://extensions → Load unpacked.',
|
|
42
|
+
prompt: 'How do you want to proceed?',
|
|
43
|
+
options: {
|
|
49
44
|
accept: 'I understand, continue',
|
|
50
45
|
dismiss: "Accept and don't show again",
|
|
51
46
|
swap: 'Switch to español',
|
|
52
47
|
quit: 'Quit',
|
|
53
48
|
},
|
|
54
|
-
prompt: 'Press [A] to accept, [D] to accept & hide next time, [L] for español, [Q] to quit.',
|
|
55
|
-
promptNoDismiss: 'Press [A] to continue, [L] for español, [Q] to quit.',
|
|
56
49
|
},
|
|
57
50
|
es: {
|
|
58
51
|
title: 'browser-link',
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
|
|
52
|
+
aboutTitle: 'Qué es esto',
|
|
53
|
+
about: [
|
|
54
|
+
'Un servidor MCP que abre un puente WebSocket entre un cliente MCP',
|
|
55
|
+
'(Claude Code, OpenCode, GitHub Copilot CLI…) y las pestañas de Google',
|
|
56
|
+
'Chrome a las que vos le des acceso explícito a través de una extensión',
|
|
57
|
+
'que cargás vos manualmente.',
|
|
58
|
+
].join('\n'),
|
|
59
|
+
capabilitiesTitle: 'Qué puede hacer el agente en una pestaña conectada',
|
|
64
60
|
capabilities: [
|
|
65
|
-
'
|
|
66
|
-
'
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
' • Hacer click y escribir en sus elementos',
|
|
70
|
-
' • Ejecutar JavaScript arbitrario en el contexto de la página',
|
|
61
|
+
'• Navegar esa pestaña a cualquier URL',
|
|
62
|
+
'• Leer su DOM, su consola y su tráfico de red',
|
|
63
|
+
'• Hacer click y escribir en sus elementos',
|
|
64
|
+
'• Ejecutar JavaScript arbitrario en el contexto de la página',
|
|
71
65
|
'',
|
|
72
|
-
'Las pestañas que NO conectes siguen invisibles para el agente.
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'',
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
'ningún lado).',
|
|
79
|
-
],
|
|
66
|
+
'Las pestañas que NO conectes siguen invisibles para el agente. Cada',
|
|
67
|
+
'una se habilita una por una, a mano. El servidor además guarda lo que',
|
|
68
|
+
'aprende de cada app entre sesiones en un mapa SQLite local (selectores,',
|
|
69
|
+
'flujos, gotchas — nunca se sube a ningún lado).',
|
|
70
|
+
].join('\n'),
|
|
71
|
+
warningTitle: 'Leelo antes de continuar',
|
|
80
72
|
warning: [
|
|
81
|
-
`${ansi.bold}${ansi.yellow}⚠ Leelo antes de continuar${ansi.reset}`,
|
|
82
|
-
'',
|
|
83
73
|
'Conectar una pestaña le da al agente acceso a todo lo que esté en esa',
|
|
84
74
|
'pestaña: sesiones iniciadas, tarjetas guardadas, wallets, banca,',
|
|
85
75
|
'consolas de trabajo, paneles de administración… lo que el navegador',
|
|
@@ -93,85 +83,16 @@ export const I18N_WELCOME = {
|
|
|
93
83
|
'Solo conectá pestañas donde estarías cómodo dejando que un proceso',
|
|
94
84
|
'automatizado actúe en tu nombre. Desconectá la pestaña desde el popup',
|
|
95
85
|
'cuando termines de usarla.',
|
|
96
|
-
],
|
|
97
|
-
responsibility: 'Sos responsable de cada acción que el agente haga en las pestañas que '
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
'El menú que aparece después de esta pantalla te dice exactamente dónde ' +
|
|
102
|
-
'está para que la cargues vía chrome://extensions → Cargar sin empaquetar.',
|
|
103
|
-
actions: {
|
|
86
|
+
].join('\n'),
|
|
87
|
+
responsibility: 'Sos responsable de cada acción que el agente haga en las pestañas que habilitás explícitamente. La extensión se mantiene inerte en cualquier pestaña donde no hayas apretado "Conectar" vos mismo.',
|
|
88
|
+
extensionNote: 'La extensión de Chrome es custom y viene incluida en este paquete. El menú que aparece después de esta pantalla te dice exactamente dónde está para que la cargues vía chrome://extensions → Cargar sin empaquetar.',
|
|
89
|
+
prompt: '¿Cómo querés seguir?',
|
|
90
|
+
options: {
|
|
104
91
|
accept: 'Entendido, continuar',
|
|
105
92
|
dismiss: 'Aceptar y no volver a mostrar',
|
|
106
93
|
swap: 'Cambiar a English',
|
|
107
94
|
quit: 'Salir',
|
|
108
95
|
},
|
|
109
|
-
prompt: 'Pulsá [A] para aceptar, [D] para aceptar y ocultar la próxima vez, [L] para English, [Q] para salir.',
|
|
110
|
-
promptNoDismiss: 'Pulsá [A] para continuar, [L] para English, [Q] para salir.',
|
|
111
96
|
},
|
|
112
97
|
};
|
|
113
|
-
export function buildWelcomeScreen(t, hideDismiss) {
|
|
114
|
-
const lines = [];
|
|
115
|
-
lines.push(`${ansi.bold}${ansi.cyan}${t.title}${ansi.reset}`);
|
|
116
|
-
lines.push('');
|
|
117
|
-
for (const l of t.intro)
|
|
118
|
-
lines.push(l);
|
|
119
|
-
lines.push('');
|
|
120
|
-
for (const l of t.capabilities)
|
|
121
|
-
lines.push(l);
|
|
122
|
-
lines.push('');
|
|
123
|
-
for (const l of t.warning)
|
|
124
|
-
lines.push(l);
|
|
125
|
-
lines.push('');
|
|
126
|
-
lines.push(`${ansi.dim}${t.responsibility}${ansi.reset}`);
|
|
127
|
-
lines.push('');
|
|
128
|
-
lines.push(`${ansi.dim}${t.extensionNote}${ansi.reset}`);
|
|
129
|
-
lines.push('');
|
|
130
|
-
const acceptLine = ` ${ansi.green}[A]${ansi.reset} ${t.actions.accept}`;
|
|
131
|
-
const dismissLine = hideDismiss ? '' : ` ${ansi.green}[D]${ansi.reset} ${t.actions.dismiss}`;
|
|
132
|
-
const langLine = ` ${ansi.cyan}[L]${ansi.reset} ${t.actions.swap}`;
|
|
133
|
-
const quitLine = ` ${ansi.red}[Q]${ansi.reset} ${t.actions.quit}`;
|
|
134
|
-
if (hideDismiss) {
|
|
135
|
-
lines.push(`${acceptLine} ${langLine} ${quitLine}`);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
lines.push(acceptLine);
|
|
139
|
-
lines.push(dismissLine);
|
|
140
|
-
lines.push(`${langLine} ${quitLine}`);
|
|
141
|
-
}
|
|
142
|
-
return renderBox(lines, { borderColor: ansi.gray });
|
|
143
|
-
}
|
|
144
|
-
export async function runWelcome(opts = {}) {
|
|
145
|
-
let lang = opts.initial ?? 'en';
|
|
146
|
-
const hideDismiss = opts.hideDismiss === true;
|
|
147
|
-
hideCursor();
|
|
148
|
-
try {
|
|
149
|
-
while (true) {
|
|
150
|
-
const t = I18N_WELCOME[lang];
|
|
151
|
-
clearScreen();
|
|
152
|
-
stdout.write(buildWelcomeScreen(t, hideDismiss));
|
|
153
|
-
stdout.write('\n\n');
|
|
154
|
-
stdout.write(`${ansi.dim}${hideDismiss ? t.promptNoDismiss : t.prompt}${ansi.reset} `);
|
|
155
|
-
const key = classifyKey(await readKey());
|
|
156
|
-
if (key === 'a')
|
|
157
|
-
return { action: 'continue', language: lang, persisted: false };
|
|
158
|
-
if (key === 'd' && !hideDismiss) {
|
|
159
|
-
saveConfig({ skipWelcome: true, language: lang });
|
|
160
|
-
return { action: 'continue', language: lang, persisted: true };
|
|
161
|
-
}
|
|
162
|
-
if (key === 'l') {
|
|
163
|
-
lang = lang === 'en' ? 'es' : 'en';
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
if (key === 'q' || key === 'esc' || key === 'ctrl-c') {
|
|
167
|
-
return { action: 'quit', language: lang, persisted: false };
|
|
168
|
-
}
|
|
169
|
-
// ignore other keys
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
finally {
|
|
173
|
-
showCursor();
|
|
174
|
-
stdout.write('\n');
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
98
|
//# sourceMappingURL=welcome.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"welcome.js","sourceRoot":"","sources":["../../src/commands/welcome.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"welcome.js","sourceRoot":"","sources":["../../src/commands/welcome.ts"],"names":[],"mappings":"AAAA;;yDAEyD;AAkCzD,MAAM,CAAC,MAAM,YAAY,GAA2B;IAClD,EAAE,EAAE;QACF,KAAK,EAAE,cAAc;QACrB,UAAU,EAAE,cAAc;QAC1B,KAAK,EAAE;YACL,kEAAkE;YAClE,oEAAoE;YACpE,uEAAuE;YACvE,6BAA6B;SAC9B,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,iBAAiB,EAAE,0CAA0C;QAC7D,YAAY,EAAE;YACZ,gCAAgC;YAChC,6CAA6C;YAC7C,oCAAoC;YACpC,oDAAoD;YACpD,EAAE;YACF,oEAAoE;YACpE,uEAAuE;YACvE,kEAAkE;YAClE,4CAA4C;SAC7C,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,YAAY,EAAE,+BAA+B;QAC7C,OAAO,EAAE;YACP,oEAAoE;YACpE,sEAAsE;YACtE,2DAA2D;YAC3D,EAAE;YACF,uEAAuE;YACvE,wEAAwE;YACxE,qEAAqE;YACrE,EAAE;YACF,uEAAuE;YACvE,uEAAuE;YACvE,4BAA4B;SAC7B,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,cAAc,EACZ,iLAAiL;QACnL,aAAa,EACX,yLAAyL;QAC3L,MAAM,EAAE,6BAA6B;QACrC,OAAO,EAAE;YACP,MAAM,EAAE,wBAAwB;YAChC,OAAO,EAAE,6BAA6B;YACtC,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE,MAAM;SACb;KACF;IACD,EAAE,EAAE;QACF,KAAK,EAAE,cAAc;QACrB,UAAU,EAAE,aAAa;QACzB,KAAK,EAAE;YACL,mEAAmE;YACnE,uEAAuE;YACvE,wEAAwE;YACxE,6BAA6B;SAC9B,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,iBAAiB,EAAE,oDAAoD;QACvE,YAAY,EAAE;YACZ,uCAAuC;YACvC,+CAA+C;YAC/C,2CAA2C;YAC3C,8DAA8D;YAC9D,EAAE;YACF,qEAAqE;YACrE,uEAAuE;YACvE,yEAAyE;YACzE,iDAAiD;SAClD,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,YAAY,EAAE,0BAA0B;QACxC,OAAO,EAAE;YACP,uEAAuE;YACvE,kEAAkE;YAClE,qEAAqE;YACrE,wCAAwC;YACxC,EAAE;YACF,iEAAiE;YACjE,4DAA4D;YAC5D,qEAAqE;YACrE,oCAAoC;YACpC,EAAE;YACF,oEAAoE;YACpE,uEAAuE;YACvE,4BAA4B;SAC7B,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,cAAc,EACZ,oMAAoM;QACtM,aAAa,EACX,qNAAqN;QACvN,MAAM,EAAE,sBAAsB;QAC9B,OAAO,EAAE;YACP,MAAM,EAAE,sBAAsB;YAC9B,OAAO,EAAE,+BAA+B;YACxC,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE,OAAO;SACd;KACF;CACF,CAAC"}
|
|
@@ -96,82 +96,82 @@ function attachDebuggerListener(state) {
|
|
|
96
96
|
};
|
|
97
97
|
state.debuggerListener = listener;
|
|
98
98
|
}
|
|
99
|
-
const SNAPSHOT_JS = `
|
|
100
|
-
(() => {
|
|
101
|
-
function isVisible(el) {
|
|
102
|
-
if (!(el instanceof HTMLElement)) return true;
|
|
103
|
-
const style = getComputedStyle(el);
|
|
104
|
-
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
|
|
105
|
-
if (el.offsetParent === null && style.position !== 'fixed') return false;
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
function shortText(el) {
|
|
109
|
-
const t = (el.innerText || el.textContent || '').trim();
|
|
110
|
-
return t.length > 120 ? t.slice(0, 120) + '...' : t;
|
|
111
|
-
}
|
|
112
|
-
function safeCss(s) {
|
|
113
|
-
return s.replace(/"/g, '\\\\"');
|
|
114
|
-
}
|
|
115
|
-
function genSelector(el) {
|
|
116
|
-
if (el.id && !/^[\\d]/.test(el.id) && !/\\s/.test(el.id)) {
|
|
117
|
-
try { if (document.querySelectorAll('#' + CSS.escape(el.id)).length === 1) return '#' + CSS.escape(el.id); } catch (_) {}
|
|
118
|
-
}
|
|
119
|
-
const tid = el.getAttribute('data-testid');
|
|
120
|
-
if (tid) return '[data-testid="' + safeCss(tid) + '"]';
|
|
121
|
-
const al = el.getAttribute('aria-label');
|
|
122
|
-
if (al && al.length < 60) return el.tagName.toLowerCase() + '[aria-label="' + safeCss(al) + '"]';
|
|
123
|
-
const name = el.getAttribute('name');
|
|
124
|
-
if (name && (el.tagName === 'INPUT' || el.tagName === 'SELECT' || el.tagName === 'TEXTAREA')) {
|
|
125
|
-
return el.tagName.toLowerCase() + '[name="' + safeCss(name) + '"]';
|
|
126
|
-
}
|
|
127
|
-
const parts = [];
|
|
128
|
-
let cur = el;
|
|
129
|
-
while (cur && cur.nodeType === 1 && cur !== document.body && parts.length < 6) {
|
|
130
|
-
let part = cur.tagName.toLowerCase();
|
|
131
|
-
const parent = cur.parentElement;
|
|
132
|
-
if (parent) {
|
|
133
|
-
const sib = Array.from(parent.children).filter(s => s.tagName === cur.tagName);
|
|
134
|
-
if (sib.length > 1) {
|
|
135
|
-
part += ':nth-of-type(' + (sib.indexOf(cur) + 1) + ')';
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
parts.unshift(part);
|
|
139
|
-
cur = parent;
|
|
140
|
-
}
|
|
141
|
-
return parts.join(' > ');
|
|
142
|
-
}
|
|
143
|
-
const sel = 'a[href], button, input, select, textarea, [role=button], [role=link], [role=checkbox], [role=tab], [role=menuitem], [contenteditable=true]';
|
|
144
|
-
const interactive = [];
|
|
145
|
-
document.querySelectorAll(sel).forEach((el) => {
|
|
146
|
-
if (!isVisible(el)) return;
|
|
147
|
-
interactive.push({
|
|
148
|
-
tag: el.tagName.toLowerCase(),
|
|
149
|
-
role: el.getAttribute('role') || el.tagName.toLowerCase(),
|
|
150
|
-
text: shortText(el),
|
|
151
|
-
value: 'value' in el ? (el.value || '') : '',
|
|
152
|
-
placeholder: el.getAttribute('placeholder') || '',
|
|
153
|
-
aria_label: el.getAttribute('aria-label') || '',
|
|
154
|
-
name: el.getAttribute('name') || '',
|
|
155
|
-
type: el.getAttribute('type') || '',
|
|
156
|
-
href: el.getAttribute('href') || '',
|
|
157
|
-
disabled: 'disabled' in el ? !!el.disabled : false,
|
|
158
|
-
selector: genSelector(el),
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
const headings = [];
|
|
162
|
-
document.querySelectorAll('h1, h2, h3').forEach((h) => {
|
|
163
|
-
if (!isVisible(h)) return;
|
|
164
|
-
headings.push({ level: h.tagName, text: shortText(h) });
|
|
165
|
-
});
|
|
166
|
-
const visibleText = (document.body && document.body.innerText) ? document.body.innerText.slice(0, 4000) : '';
|
|
167
|
-
return {
|
|
168
|
-
title: document.title,
|
|
169
|
-
url: location.href,
|
|
170
|
-
headings: headings.slice(0, 30),
|
|
171
|
-
text: visibleText,
|
|
172
|
-
interactive: interactive.slice(0, 120),
|
|
173
|
-
};
|
|
174
|
-
})()
|
|
99
|
+
const SNAPSHOT_JS = `
|
|
100
|
+
(() => {
|
|
101
|
+
function isVisible(el) {
|
|
102
|
+
if (!(el instanceof HTMLElement)) return true;
|
|
103
|
+
const style = getComputedStyle(el);
|
|
104
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
|
|
105
|
+
if (el.offsetParent === null && style.position !== 'fixed') return false;
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
function shortText(el) {
|
|
109
|
+
const t = (el.innerText || el.textContent || '').trim();
|
|
110
|
+
return t.length > 120 ? t.slice(0, 120) + '...' : t;
|
|
111
|
+
}
|
|
112
|
+
function safeCss(s) {
|
|
113
|
+
return s.replace(/"/g, '\\\\"');
|
|
114
|
+
}
|
|
115
|
+
function genSelector(el) {
|
|
116
|
+
if (el.id && !/^[\\d]/.test(el.id) && !/\\s/.test(el.id)) {
|
|
117
|
+
try { if (document.querySelectorAll('#' + CSS.escape(el.id)).length === 1) return '#' + CSS.escape(el.id); } catch (_) {}
|
|
118
|
+
}
|
|
119
|
+
const tid = el.getAttribute('data-testid');
|
|
120
|
+
if (tid) return '[data-testid="' + safeCss(tid) + '"]';
|
|
121
|
+
const al = el.getAttribute('aria-label');
|
|
122
|
+
if (al && al.length < 60) return el.tagName.toLowerCase() + '[aria-label="' + safeCss(al) + '"]';
|
|
123
|
+
const name = el.getAttribute('name');
|
|
124
|
+
if (name && (el.tagName === 'INPUT' || el.tagName === 'SELECT' || el.tagName === 'TEXTAREA')) {
|
|
125
|
+
return el.tagName.toLowerCase() + '[name="' + safeCss(name) + '"]';
|
|
126
|
+
}
|
|
127
|
+
const parts = [];
|
|
128
|
+
let cur = el;
|
|
129
|
+
while (cur && cur.nodeType === 1 && cur !== document.body && parts.length < 6) {
|
|
130
|
+
let part = cur.tagName.toLowerCase();
|
|
131
|
+
const parent = cur.parentElement;
|
|
132
|
+
if (parent) {
|
|
133
|
+
const sib = Array.from(parent.children).filter(s => s.tagName === cur.tagName);
|
|
134
|
+
if (sib.length > 1) {
|
|
135
|
+
part += ':nth-of-type(' + (sib.indexOf(cur) + 1) + ')';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
parts.unshift(part);
|
|
139
|
+
cur = parent;
|
|
140
|
+
}
|
|
141
|
+
return parts.join(' > ');
|
|
142
|
+
}
|
|
143
|
+
const sel = 'a[href], button, input, select, textarea, [role=button], [role=link], [role=checkbox], [role=tab], [role=menuitem], [contenteditable=true]';
|
|
144
|
+
const interactive = [];
|
|
145
|
+
document.querySelectorAll(sel).forEach((el) => {
|
|
146
|
+
if (!isVisible(el)) return;
|
|
147
|
+
interactive.push({
|
|
148
|
+
tag: el.tagName.toLowerCase(),
|
|
149
|
+
role: el.getAttribute('role') || el.tagName.toLowerCase(),
|
|
150
|
+
text: shortText(el),
|
|
151
|
+
value: 'value' in el ? (el.value || '') : '',
|
|
152
|
+
placeholder: el.getAttribute('placeholder') || '',
|
|
153
|
+
aria_label: el.getAttribute('aria-label') || '',
|
|
154
|
+
name: el.getAttribute('name') || '',
|
|
155
|
+
type: el.getAttribute('type') || '',
|
|
156
|
+
href: el.getAttribute('href') || '',
|
|
157
|
+
disabled: 'disabled' in el ? !!el.disabled : false,
|
|
158
|
+
selector: genSelector(el),
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
const headings = [];
|
|
162
|
+
document.querySelectorAll('h1, h2, h3').forEach((h) => {
|
|
163
|
+
if (!isVisible(h)) return;
|
|
164
|
+
headings.push({ level: h.tagName, text: shortText(h) });
|
|
165
|
+
});
|
|
166
|
+
const visibleText = (document.body && document.body.innerText) ? document.body.innerText.slice(0, 4000) : '';
|
|
167
|
+
return {
|
|
168
|
+
title: document.title,
|
|
169
|
+
url: location.href,
|
|
170
|
+
headings: headings.slice(0, 30),
|
|
171
|
+
text: visibleText,
|
|
172
|
+
interactive: interactive.slice(0, 120),
|
|
173
|
+
};
|
|
174
|
+
})()
|
|
175
175
|
`;
|
|
176
176
|
async function evaluateInTab(tabId, expression) {
|
|
177
177
|
const result = (await cdp(tabId, 'Runtime.evaluate', {
|
|
@@ -255,13 +255,13 @@ async function handleTool(state, msg) {
|
|
|
255
255
|
}
|
|
256
256
|
case 'click': {
|
|
257
257
|
const selector = String(p.selector);
|
|
258
|
-
const expr = `
|
|
259
|
-
(() => {
|
|
260
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
261
|
-
if (!el) return null;
|
|
262
|
-
el.scrollIntoView({ block: 'center', inline: 'center' });
|
|
263
|
-
const r = el.getBoundingClientRect();
|
|
264
|
-
return { x: r.left + r.width / 2, y: r.top + r.height / 2, tag: el.tagName.toLowerCase() };
|
|
258
|
+
const expr = `
|
|
259
|
+
(() => {
|
|
260
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
261
|
+
if (!el) return null;
|
|
262
|
+
el.scrollIntoView({ block: 'center', inline: 'center' });
|
|
263
|
+
const r = el.getBoundingClientRect();
|
|
264
|
+
return { x: r.left + r.width / 2, y: r.top + r.height / 2, tag: el.tagName.toLowerCase() };
|
|
265
265
|
})()`;
|
|
266
266
|
const coords = (await evaluateInTab(tabId, expr));
|
|
267
267
|
if (!coords) {
|
|
@@ -302,13 +302,13 @@ async function handleTool(state, msg) {
|
|
|
302
302
|
const selector = String(p.selector);
|
|
303
303
|
const text = String(p.text);
|
|
304
304
|
const clear = !!p.clear;
|
|
305
|
-
const focusExpr = `
|
|
306
|
-
(() => {
|
|
307
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
308
|
-
if (!el) return false;
|
|
309
|
-
el.focus();
|
|
310
|
-
${clear ? "if ('value' in el) { el.value = ''; el.dispatchEvent(new Event('input', { bubbles: true })); }" : ''}
|
|
311
|
-
return true;
|
|
305
|
+
const focusExpr = `
|
|
306
|
+
(() => {
|
|
307
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
308
|
+
if (!el) return false;
|
|
309
|
+
el.focus();
|
|
310
|
+
${clear ? "if ('value' in el) { el.value = ''; el.dispatchEvent(new Event('input', { bubbles: true })); }" : ''}
|
|
311
|
+
return true;
|
|
312
312
|
})()`;
|
|
313
313
|
const focused = await evaluateInTab(tabId, focusExpr);
|
|
314
314
|
if (!focused) {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
<svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
<defs>
|
|
3
|
-
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
-
<stop offset="0%" stop-color="#3b82f6" />
|
|
5
|
-
<stop offset="100%" stop-color="#1d4ed8" />
|
|
6
|
-
</linearGradient>
|
|
7
|
-
</defs>
|
|
8
|
-
<rect width="128" height="128" rx="26" fill="url(#bg)" />
|
|
9
|
-
<g fill="none" stroke="#ffffff" stroke-width="11" stroke-linecap="round" stroke-linejoin="round">
|
|
10
|
-
<path d="M52 78 L36 78 A22 22 0 0 1 36 34 L52 34" />
|
|
11
|
-
<path d="M76 34 L92 34 A22 22 0 0 1 92 78 L76 78" />
|
|
12
|
-
<line x1="44" y1="56" x2="84" y2="56" />
|
|
13
|
-
</g>
|
|
14
|
-
</svg>
|
|
1
|
+
<svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0%" stop-color="#3b82f6" />
|
|
5
|
+
<stop offset="100%" stop-color="#1d4ed8" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="128" height="128" rx="26" fill="url(#bg)" />
|
|
9
|
+
<g fill="none" stroke="#ffffff" stroke-width="11" stroke-linecap="round" stroke-linejoin="round">
|
|
10
|
+
<path d="M52 78 L36 78 A22 22 0 0 1 36 34 L52 34" />
|
|
11
|
+
<path d="M76 34 L92 34 A22 22 0 0 1 92 78 L76 78" />
|
|
12
|
+
<line x1="44" y1="56" x2="84" y2="56" />
|
|
13
|
+
</g>
|
|
14
|
+
</svg>
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
{
|
|
2
|
-
"manifest_version": 3,
|
|
3
|
-
"name": "browser-link",
|
|
4
|
-
"version": "0.0
|
|
5
|
-
"description": "Bridge between Chrome and an MCP client (Claude Code). Per-tab manual activation.",
|
|
6
|
-
"permissions": ["debugger", "activeTab", "storage", "tabs"],
|
|
7
|
-
"host_permissions": [],
|
|
8
|
-
"icons": {
|
|
9
|
-
"16": "icons/icon-16.png",
|
|
10
|
-
"32": "icons/icon-32.png",
|
|
11
|
-
"48": "icons/icon-48.png",
|
|
12
|
-
"128": "icons/icon-128.png"
|
|
13
|
-
},
|
|
14
|
-
"background": {
|
|
15
|
-
"service_worker": "background.js",
|
|
16
|
-
"type": "module"
|
|
17
|
-
},
|
|
18
|
-
"action": {
|
|
19
|
-
"default_popup": "popup.html",
|
|
20
|
-
"default_title": "browser-link",
|
|
21
|
-
"default_icon": {
|
|
22
|
-
"16": "icons/icon-16.png",
|
|
23
|
-
"32": "icons/icon-32.png",
|
|
24
|
-
"48": "icons/icon-48.png",
|
|
25
|
-
"128": "icons/icon-128.png"
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "browser-link",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Bridge between Chrome and an MCP client (Claude Code, OpenCode, …). Per-tab manual activation.",
|
|
6
|
+
"permissions": ["debugger", "activeTab", "storage", "tabs"],
|
|
7
|
+
"host_permissions": [],
|
|
8
|
+
"icons": {
|
|
9
|
+
"16": "icons/icon-16.png",
|
|
10
|
+
"32": "icons/icon-32.png",
|
|
11
|
+
"48": "icons/icon-48.png",
|
|
12
|
+
"128": "icons/icon-128.png"
|
|
13
|
+
},
|
|
14
|
+
"background": {
|
|
15
|
+
"service_worker": "background.js",
|
|
16
|
+
"type": "module"
|
|
17
|
+
},
|
|
18
|
+
"action": {
|
|
19
|
+
"default_popup": "popup.html",
|
|
20
|
+
"default_title": "browser-link",
|
|
21
|
+
"default_icon": {
|
|
22
|
+
"16": "icons/icon-16.png",
|
|
23
|
+
"32": "icons/icon-32.png",
|
|
24
|
+
"48": "icons/icon-48.png",
|
|
25
|
+
"128": "icons/icon-128.png"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|