@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.
Files changed (51) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +102 -83
  3. package/dist/cli.js +50 -36
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/about.d.ts +17 -18
  6. package/dist/commands/about.js +149 -190
  7. package/dist/commands/about.js.map +1 -1
  8. package/dist/commands/updates.d.ts +19 -0
  9. package/dist/commands/updates.js +83 -0
  10. package/dist/commands/updates.js.map +1 -0
  11. package/dist/commands/welcome.d.ts +10 -8
  12. package/dist/commands/welcome.js +49 -128
  13. package/dist/commands/welcome.js.map +1 -1
  14. package/dist/extension/background.js +90 -90
  15. package/dist/extension/icons/icon.svg +14 -14
  16. package/dist/extension/manifest.json +28 -28
  17. package/dist/extension/popup.html +88 -88
  18. package/dist/installers/copilot.d.ts +2 -0
  19. package/dist/installers/copilot.js +72 -0
  20. package/dist/installers/copilot.js.map +1 -0
  21. package/dist/installers/index.d.ts +2 -3
  22. package/dist/installers/index.js +5 -4
  23. package/dist/installers/index.js.map +1 -1
  24. package/dist/installers/opencode.js +49 -21
  25. package/dist/installers/opencode.js.map +1 -1
  26. package/dist/installers/types.d.ts +1 -1
  27. package/dist/map/db.js +28 -28
  28. package/dist/map/queries.js +4 -4
  29. package/dist/tools/server-instructions.js +46 -46
  30. package/dist/ui/app.d.ts +7 -0
  31. package/dist/ui/app.js +62 -0
  32. package/dist/ui/app.js.map +1 -0
  33. package/dist/ui/components.d.ts +18 -0
  34. package/dist/ui/components.js +27 -0
  35. package/dist/ui/components.js.map +1 -0
  36. package/dist/ui/screens.d.ts +48 -0
  37. package/dist/ui/screens.js +291 -0
  38. package/dist/ui/screens.js.map +1 -0
  39. package/dist/ui/start.d.ts +6 -0
  40. package/dist/ui/start.js +19 -0
  41. package/dist/ui/start.js.map +1 -0
  42. package/dist/version.d.ts +2 -0
  43. package/dist/version.js +15 -0
  44. package/dist/version.js.map +1 -0
  45. package/package.json +69 -61
  46. package/dist/commands/menu.d.ts +0 -26
  47. package/dist/commands/menu.js +0 -187
  48. package/dist/commands/menu.js.map +0 -1
  49. package/dist/commands/tty.d.ts +0 -51
  50. package/dist/commands/tty.js +0 -148
  51. package/dist/commands/tty.js.map +0 -1
@@ -1,32 +1,30 @@
1
- import { stdout } from 'node:process';
2
- import { saveConfig } from '../config.js';
3
- import { ansi, classifyKey, clearScreen, hideCursor, readKey, renderBox, showCursor, } from './tty.js';
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
- intro: [
8
- 'An MCP server that opens a small WebSocket bridge between Claude Code',
9
- 'and the Google Chrome tabs you explicitly grant access to through a',
10
- 'custom companion extension you load locally.',
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
- 'On every tab where you press "Conectar" in the extension popup, the',
14
- 'agent can:',
15
- ' Navigate that tab to any URL',
16
- ' Read its DOM, console and network traffic',
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. You can connect',
21
- 'as many tabs as you want — each one is enabled one by one, by hand.',
22
- '',
23
- 'And the server remembers what it learns about each app across sessions',
24
- 'in a local SQLite map (selectors, flows, gotchas — never uploaded',
25
- 'anywhere).',
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
- 'you explicitly enable. The extension stays inert on any tab where ' +
44
- 'you have not pressed "Conectar" yourself.',
45
- extensionNote: 'The Chrome extension is custom and ships inside this package. ' +
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
- intro: [
60
- 'Un servidor MCP que abre un puente WebSocket entre Claude Code y las',
61
- 'pestañas de Google Chrome a las que vos le des acceso explícito a',
62
- 'través de una extensión que cargás vos manualmente.',
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
- 'En cada pestaña donde pulsás "Conectar" en el popup de la extensión,',
66
- 'el agente puede:',
67
- ' Navegar esa pestaña a cualquier URL',
68
- ' Leer su DOM, su consola y su tráfico de red',
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. Podés',
73
- 'conectar todas las pestañas que quieras cada una se habilita una por',
74
- 'una, a mano.',
75
- '',
76
- 'Además el servidor guarda lo que aprende de cada app entre sesiones en',
77
- 'un mapa SQLite local (selectores, flujos, gotchas — nunca se sube a',
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
- 'habilitás explícitamente. La extensión se mantiene inerte en cualquier ' +
99
- 'pestaña donde no hayas apretado "Conectar" vos mismo.',
100
- extensionNote: 'La extensión de Chrome es custom y viene incluida en este paquete. ' +
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":"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"}
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.1",
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
+ }