@jobshimo/browser-link 0.0.1

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 (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/auth/allowlist.d.ts +2 -0
  4. package/dist/auth/allowlist.js +53 -0
  5. package/dist/auth/allowlist.js.map +1 -0
  6. package/dist/auth/process-identity.d.ts +19 -0
  7. package/dist/auth/process-identity.js +106 -0
  8. package/dist/auth/process-identity.js.map +1 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +116 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/commands/about.d.ts +23 -0
  13. package/dist/commands/about.js +248 -0
  14. package/dist/commands/about.js.map +1 -0
  15. package/dist/commands/doctor.d.ts +29 -0
  16. package/dist/commands/doctor.js +110 -0
  17. package/dist/commands/doctor.js.map +1 -0
  18. package/dist/commands/extension.d.ts +12 -0
  19. package/dist/commands/extension.js +76 -0
  20. package/dist/commands/extension.js.map +1 -0
  21. package/dist/commands/install.d.ts +19 -0
  22. package/dist/commands/install.js +52 -0
  23. package/dist/commands/install.js.map +1 -0
  24. package/dist/commands/menu.d.ts +26 -0
  25. package/dist/commands/menu.js +187 -0
  26. package/dist/commands/menu.js.map +1 -0
  27. package/dist/commands/tty.d.ts +51 -0
  28. package/dist/commands/tty.js +148 -0
  29. package/dist/commands/tty.js.map +1 -0
  30. package/dist/commands/uninstall.d.ts +8 -0
  31. package/dist/commands/uninstall.js +10 -0
  32. package/dist/commands/uninstall.js.map +1 -0
  33. package/dist/commands/welcome.d.ts +33 -0
  34. package/dist/commands/welcome.js +177 -0
  35. package/dist/commands/welcome.js.map +1 -0
  36. package/dist/config.d.ts +11 -0
  37. package/dist/config.js +35 -0
  38. package/dist/config.js.map +1 -0
  39. package/dist/entry-info.d.ts +12 -0
  40. package/dist/entry-info.js +15 -0
  41. package/dist/entry-info.js.map +1 -0
  42. package/dist/extension/background.d.ts +1 -0
  43. package/dist/extension/background.js +490 -0
  44. package/dist/extension/background.js.map +1 -0
  45. package/dist/extension/icons/icon-128.png +0 -0
  46. package/dist/extension/icons/icon-16.png +0 -0
  47. package/dist/extension/icons/icon-32.png +0 -0
  48. package/dist/extension/icons/icon-48.png +0 -0
  49. package/dist/extension/icons/icon.svg +14 -0
  50. package/dist/extension/manifest.json +28 -0
  51. package/dist/extension/popup.d.ts +13 -0
  52. package/dist/extension/popup.html +88 -0
  53. package/dist/extension/popup.js +78 -0
  54. package/dist/extension/popup.js.map +1 -0
  55. package/dist/index.d.ts +1 -0
  56. package/dist/index.js +6 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/installers/claude.d.ts +2 -0
  59. package/dist/installers/claude.js +77 -0
  60. package/dist/installers/claude.js.map +1 -0
  61. package/dist/installers/index.d.ts +9 -0
  62. package/dist/installers/index.js +14 -0
  63. package/dist/installers/index.js.map +1 -0
  64. package/dist/installers/opencode.d.ts +2 -0
  65. package/dist/installers/opencode.js +39 -0
  66. package/dist/installers/opencode.js.map +1 -0
  67. package/dist/installers/types.d.ts +30 -0
  68. package/dist/installers/types.js +2 -0
  69. package/dist/installers/types.js.map +1 -0
  70. package/dist/map/db.d.ts +3 -0
  71. package/dist/map/db.js +81 -0
  72. package/dist/map/db.js.map +1 -0
  73. package/dist/map/paths.d.ts +12 -0
  74. package/dist/map/paths.js +22 -0
  75. package/dist/map/paths.js.map +1 -0
  76. package/dist/map/queries.d.ts +72 -0
  77. package/dist/map/queries.js +162 -0
  78. package/dist/map/queries.js.map +1 -0
  79. package/dist/map/tools.d.ts +8 -0
  80. package/dist/map/tools.js +143 -0
  81. package/dist/map/tools.js.map +1 -0
  82. package/dist/messages.d.ts +42 -0
  83. package/dist/messages.js +9 -0
  84. package/dist/messages.js.map +1 -0
  85. package/dist/server.d.ts +6 -0
  86. package/dist/server.js +217 -0
  87. package/dist/server.js.map +1 -0
  88. package/dist/tools/browser-definitions.d.ts +7 -0
  89. package/dist/tools/browser-definitions.js +127 -0
  90. package/dist/tools/browser-definitions.js.map +1 -0
  91. package/dist/tools/browser-dispatch.d.ts +20 -0
  92. package/dist/tools/browser-dispatch.js +75 -0
  93. package/dist/tools/browser-dispatch.js.map +1 -0
  94. package/dist/tools/responses.d.ts +19 -0
  95. package/dist/tools/responses.js +27 -0
  96. package/dist/tools/responses.js.map +1 -0
  97. package/dist/tools/server-instructions.d.ts +3 -0
  98. package/dist/tools/server-instructions.js +50 -0
  99. package/dist/tools/server-instructions.js.map +1 -0
  100. package/dist/tools/types.d.ts +6 -0
  101. package/dist/tools/types.js +2 -0
  102. package/dist/tools/types.js.map +1 -0
  103. package/dist/utils/open-url.d.ts +4 -0
  104. package/dist/utils/open-url.js +37 -0
  105. package/dist/utils/open-url.js.map +1 -0
  106. package/package.json +61 -0
@@ -0,0 +1,248 @@
1
+ import { stdout } from 'node:process';
2
+ import { ansi, clearScreen, hideCursor, readKey, renderBox, showCursor, } from './tty.js';
3
+ export const I18N_ABOUT = {
4
+ en: {
5
+ title: 'browser-link — about',
6
+ whatItIs: [
7
+ `${ansi.bold}What is this?${ansi.reset}`,
8
+ '',
9
+ 'browser-link is an MCP (Model Context Protocol) server. It lets',
10
+ 'Claude Code observe and act on the Google Chrome tabs you explicitly',
11
+ 'enable through a companion extension. It is split in two pieces:',
12
+ ' 1) This Node binary, the MCP server Claude Code spawns over stdio.',
13
+ ' 2) A small Chrome extension that you load manually. The extension',
14
+ ' stays inert on every tab until you press "Conectar" on it. You',
15
+ ' can enable as many tabs as you want, one by one. Tabs you do',
16
+ ' not connect remain invisible to the agent.',
17
+ ],
18
+ howItWorks: [
19
+ `${ansi.bold}How does it work?${ansi.reset}`,
20
+ '',
21
+ 'The Node binary listens on 127.0.0.1:17529 (loopback only — never on a',
22
+ 'public interface). Each tab where you press "Conectar" opens its own',
23
+ 'WebSocket to that port and registers a tab_id. From that moment on,',
24
+ 'the agent can call tools targeting that tab_id, and the server',
25
+ 'forwards each request to the extension over the WebSocket.',
26
+ '',
27
+ `${ansi.bold}Process binding (zero-config defense)${ansi.reset}`,
28
+ '',
29
+ 'Before accepting any WebSocket handshake, the server asks the OS',
30
+ 'kernel which process opened the incoming TCP connection. Connections',
31
+ 'whose owner is not a known Chromium-based browser binary (Chrome,',
32
+ 'Edge, Brave, Vivaldi, Chromium) are rejected before any application',
33
+ 'bytes are exchanged. This closes the "any local process can talk to',
34
+ 'the bridge" vector without asking you to copy or paste tokens.',
35
+ '',
36
+ 'It does NOT defend against malware that has already injected itself',
37
+ 'inside Chrome (chrome.debugger from another extension, etc.). That',
38
+ 'attacker already controls the browser directly and the bridge adds',
39
+ 'no surface they did not already have.',
40
+ ],
41
+ bridgeTools: {
42
+ title: `${ansi.bold}Tools the agent gets over the bridge${ansi.reset}`,
43
+ items: [
44
+ ' browser.list_tabs list connected tabs',
45
+ ' browser.ping check the bridge to a tab',
46
+ ' browser.snapshot dump title, url, text and interactive elements',
47
+ ' browser.navigate change the tab URL',
48
+ ' browser.click click an element by CSS selector',
49
+ ' browser.type type into an input',
50
+ ' browser.evaluate run JavaScript in the page context',
51
+ ' browser.console recent console messages (rolling 200)',
52
+ ' browser.network recent network requests (rolling 200)',
53
+ ' browser.network_body fetch the body of a specific request',
54
+ ],
55
+ },
56
+ mapTools: {
57
+ title: `${ansi.bold}Persistent UI map (private, on your machine)${ansi.reset}`,
58
+ items: [
59
+ 'The server keeps a small SQLite database with what the agent has',
60
+ 'learned about each app it operates: stable selectors, multi-step',
61
+ 'flows, and gotchas. Three kinds of entries (selector, flow, gotcha)',
62
+ 'indexed by app + pathname. Tools the agent uses:',
63
+ '',
64
+ ' browser.map.recall what do we know about this app/route?',
65
+ ' browser.map.save persist a selector, flow, or gotcha',
66
+ ' browser.map.record_use verified / failed timestamp per entry',
67
+ ' browser.map.forget delete an entry or an entire app',
68
+ ' browser.map.rename_app fix a bad app_key from the first save',
69
+ ' browser.map.apps list known apps',
70
+ ],
71
+ },
72
+ privacy: [
73
+ `${ansi.bold}Where your data lives${ansi.reset}`,
74
+ '',
75
+ ' • The UI map is at:',
76
+ ' Linux $XDG_DATA_HOME/browser-link/map.db',
77
+ ' macOS ~/Library/Application Support/browser-link/map.db',
78
+ ' Windows %APPDATA%/browser-link/map.db',
79
+ ' Override with BROWSER_LINK_DATA_DIR.',
80
+ ' • Nothing is uploaded anywhere by this package. The bridge only',
81
+ ' talks loopback to your local Chrome extension.',
82
+ ' • What you save in the map is your responsibility. Never save',
83
+ ' domain data (IDs, names, dates) — the map is for UI structure.',
84
+ ],
85
+ whereToGetHelp: [
86
+ `${ansi.bold}Get help / report issues${ansi.reset}`,
87
+ '',
88
+ ' GitHub https://github.com/jobshimo/browser-link',
89
+ ' Issues https://github.com/jobshimo/browser-link/issues',
90
+ '',
91
+ 'Useful subcommands:',
92
+ ' browser-link doctor diagnose what is and is not set up',
93
+ ' browser-link extension show where the Chrome extension assets are',
94
+ ' browser-link install register browser-link in your MCP client',
95
+ ' browser-link about show this page',
96
+ ' browser-link help list every subcommand',
97
+ ],
98
+ prompt: 'Press any key to return.',
99
+ },
100
+ es: {
101
+ title: 'browser-link — información',
102
+ whatItIs: [
103
+ `${ansi.bold}¿Qué es esto?${ansi.reset}`,
104
+ '',
105
+ 'browser-link es un servidor MCP (Model Context Protocol). Permite que',
106
+ 'Claude Code observe y actúe sobre las pestañas de Google Chrome a las',
107
+ 'que vos le des acceso explícito mediante una extensión que acompaña',
108
+ 'al paquete. Tiene dos piezas:',
109
+ ' 1) Este binario de Node, el servidor MCP que Claude Code arranca',
110
+ ' por stdio.',
111
+ ' 2) Una pequeña extensión de Chrome que cargás vos a mano. La',
112
+ ' extensión queda inerte en cada pestaña hasta que pulsás',
113
+ ' "Conectar" sobre ella. Podés habilitar todas las pestañas que',
114
+ ' quieras, una por una. Las pestañas que no conectes siguen',
115
+ ' invisibles al agente.',
116
+ ],
117
+ howItWorks: [
118
+ `${ansi.bold}¿Cómo funciona?${ansi.reset}`,
119
+ '',
120
+ 'El binario de Node escucha en 127.0.0.1:17529 (solo loopback — nunca',
121
+ 'en una interfaz pública). Cada pestaña donde pulsás "Conectar" abre',
122
+ 'su propio WebSocket contra ese puerto y se registra con un tab_id.',
123
+ 'Desde ahí, el agente puede llamar herramientas que apuntan a ese',
124
+ 'tab_id y el servidor reenvía cada petición a la extensión.',
125
+ '',
126
+ `${ansi.bold}Validación por proceso (sin configuración)${ansi.reset}`,
127
+ '',
128
+ 'Antes de aceptar cualquier handshake WebSocket, el servidor le',
129
+ 'pregunta al kernel del SO qué proceso abrió la conexión TCP entrante.',
130
+ 'Conexiones cuyo dueño no es un binario conocido de un navegador',
131
+ 'Chromium-based (Chrome, Edge, Brave, Vivaldi, Chromium) se rechazan',
132
+ 'antes de intercambiar bytes de aplicación. Eso cierra el vector "cualquier',
133
+ 'proceso local puede hablar con el puente" sin pedirte copiar tokens.',
134
+ '',
135
+ 'NO te protege contra malware que ya se inyectó dentro de Chrome',
136
+ '(chrome.debugger desde otra extensión maliciosa, etc.). Ese atacante',
137
+ 'ya controla el navegador directamente y el puente no le suma',
138
+ 'superficie nueva.',
139
+ ],
140
+ bridgeTools: {
141
+ title: `${ansi.bold}Herramientas que el agente expone vía el puente${ansi.reset}`,
142
+ items: [
143
+ ' browser.list_tabs listar pestañas conectadas',
144
+ ' browser.ping comprobar el puente a una pestaña',
145
+ ' browser.snapshot volcar título, url, texto y elementos interactivos',
146
+ ' browser.navigate cambiar la URL de la pestaña',
147
+ ' browser.click hacer click en un elemento por CSS selector',
148
+ ' browser.type escribir en un input',
149
+ ' browser.evaluate ejecutar JavaScript en el contexto de la página',
150
+ ' browser.console mensajes recientes de consola (200 últimos)',
151
+ ' browser.network peticiones de red recientes (200 últimas)',
152
+ ' browser.network_body traer el body de una petición concreta',
153
+ ],
154
+ },
155
+ mapTools: {
156
+ title: `${ansi.bold}Mapa de UI persistente (privado, en tu máquina)${ansi.reset}`,
157
+ items: [
158
+ 'El servidor mantiene una pequeña base SQLite con lo que el agente',
159
+ 'va aprendiendo de cada app: selectores estables, flujos de pasos y',
160
+ 'gotchas. Tres tipos de entradas (selector, flow, gotcha) indexadas',
161
+ 'por app + path. Herramientas que el agente usa:',
162
+ '',
163
+ ' browser.map.recall ¿qué sabemos de esta app/ruta?',
164
+ ' browser.map.save guardar un selector, flujo o gotcha',
165
+ ' browser.map.record_use marcar verified / failed por entrada',
166
+ ' browser.map.forget borrar una entrada o una app entera',
167
+ ' browser.map.rename_app corregir un app_key mal derivado',
168
+ ' browser.map.apps listar apps conocidas',
169
+ ],
170
+ },
171
+ privacy: [
172
+ `${ansi.bold}Dónde vive tu información${ansi.reset}`,
173
+ '',
174
+ ' • El mapa de UI está en:',
175
+ ' Linux $XDG_DATA_HOME/browser-link/map.db',
176
+ ' macOS ~/Library/Application Support/browser-link/map.db',
177
+ ' Windows %APPDATA%/browser-link/map.db',
178
+ ' Sobreescribilo con BROWSER_LINK_DATA_DIR.',
179
+ ' • Este paquete no sube nada a ningún lado. El puente solo habla',
180
+ ' loopback con la extensión local de Chrome.',
181
+ ' • Lo que guardes en el mapa es responsabilidad tuya. No guardes',
182
+ ' datos de dominio (IDs, nombres, fechas) — el mapa es para',
183
+ ' estructura de UI, no para data.',
184
+ ],
185
+ whereToGetHelp: [
186
+ `${ansi.bold}Ayuda / reportar bugs${ansi.reset}`,
187
+ '',
188
+ ' GitHub https://github.com/jobshimo/browser-link',
189
+ ' Issues https://github.com/jobshimo/browser-link/issues',
190
+ '',
191
+ 'Subcomandos útiles:',
192
+ ' browser-link doctor diagnóstico de qué está y qué falta',
193
+ ' browser-link extension ver dónde están los assets de la extensión',
194
+ ' browser-link install registrar browser-link en tu cliente MCP',
195
+ ' browser-link about mostrar esta página',
196
+ ' browser-link help listar todos los subcomandos',
197
+ ],
198
+ prompt: 'Pulsá cualquier tecla para volver.',
199
+ },
200
+ };
201
+ export function buildAboutScreen(t) {
202
+ const lines = [];
203
+ lines.push(`${ansi.bold}${ansi.cyan}${t.title}${ansi.reset}`);
204
+ lines.push('');
205
+ for (const l of t.whatItIs)
206
+ lines.push(l);
207
+ lines.push('');
208
+ for (const l of t.howItWorks)
209
+ lines.push(l);
210
+ lines.push('');
211
+ lines.push(t.bridgeTools.title);
212
+ lines.push('');
213
+ for (const l of t.bridgeTools.items)
214
+ lines.push(l);
215
+ lines.push('');
216
+ lines.push(t.mapTools.title);
217
+ lines.push('');
218
+ for (const l of t.mapTools.items)
219
+ lines.push(l);
220
+ lines.push('');
221
+ for (const l of t.privacy)
222
+ lines.push(l);
223
+ lines.push('');
224
+ for (const l of t.whereToGetHelp)
225
+ lines.push(l);
226
+ return renderBox(lines, { borderColor: ansi.gray });
227
+ }
228
+ export async function runAbout(language = 'en') {
229
+ const t = I18N_ABOUT[language];
230
+ hideCursor();
231
+ try {
232
+ clearScreen();
233
+ stdout.write(buildAboutScreen(t));
234
+ stdout.write('\n\n');
235
+ stdout.write(`${ansi.dim}${t.prompt}${ansi.reset} `);
236
+ await readKey();
237
+ }
238
+ finally {
239
+ showCursor();
240
+ stdout.write('\n');
241
+ }
242
+ }
243
+ /** Plain-text render of About for non-interactive output (browser-link about). */
244
+ export function printAbout(language = 'en') {
245
+ const t = I18N_ABOUT[language];
246
+ console.log(buildAboutScreen(t));
247
+ }
248
+ //# sourceMappingURL=about.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"about.js","sourceRoot":"","sources":["../../src/commands/about.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ export interface DoctorReport {
2
+ ws: {
3
+ listening: boolean;
4
+ detail: string;
5
+ host: string;
6
+ port: number;
7
+ };
8
+ clients: {
9
+ id: string;
10
+ displayName: string;
11
+ installed: boolean;
12
+ registered: boolean;
13
+ configPath: string;
14
+ }[];
15
+ extension: {
16
+ path: string | null;
17
+ };
18
+ map: {
19
+ dbPath: string;
20
+ exists: boolean;
21
+ sizeBytes: number;
22
+ apps: number;
23
+ };
24
+ security: {
25
+ allowedBrowsers: readonly string[];
26
+ };
27
+ }
28
+ export declare function runDoctor(): Promise<DoctorReport>;
29
+ export declare function formatDoctor(r: DoctorReport): string;
@@ -0,0 +1,110 @@
1
+ import { existsSync, statSync } from 'node:fs';
2
+ import { createConnection } from 'node:net';
3
+ import { INSTALLERS } from '../installers/index.js';
4
+ import { getAllowedBrowsers } from '../auth/allowlist.js';
5
+ import { getDbPath } from '../map/paths.js';
6
+ import { listApps } from '../map/queries.js';
7
+ import { resolveExtensionPath } from './extension.js';
8
+ const WS_HOST = '127.0.0.1';
9
+ const WS_PORT = 17529;
10
+ function checkPort(host, port, timeoutMs = 500) {
11
+ return new Promise((resolve) => {
12
+ const socket = createConnection({ host, port });
13
+ const settle = (listening, detail) => {
14
+ socket.removeAllListeners();
15
+ socket.destroy();
16
+ resolve({ listening, detail });
17
+ };
18
+ const timer = setTimeout(() => settle(false, `no response within ${timeoutMs}ms`), timeoutMs);
19
+ socket.once('connect', () => {
20
+ clearTimeout(timer);
21
+ settle(true, `something is listening on ${host}:${port}`);
22
+ });
23
+ socket.once('error', (err) => {
24
+ clearTimeout(timer);
25
+ if (err.code === 'ECONNREFUSED')
26
+ settle(false, 'no MCP server running');
27
+ else
28
+ settle(false, err.message);
29
+ });
30
+ });
31
+ }
32
+ export async function runDoctor() {
33
+ const ws = await checkPort(WS_HOST, WS_PORT);
34
+ const clients = INSTALLERS.map((i) => {
35
+ const d = i.detect();
36
+ return { id: i.id, displayName: i.displayName, ...d };
37
+ });
38
+ const extPath = resolveExtensionPath();
39
+ const dbPath = getDbPath();
40
+ const dbExists = existsSync(dbPath);
41
+ const sizeBytes = dbExists ? statSync(dbPath).size : 0;
42
+ let apps = 0;
43
+ if (dbExists) {
44
+ try {
45
+ apps = listApps().length;
46
+ }
47
+ catch {
48
+ apps = -1;
49
+ }
50
+ }
51
+ return {
52
+ ws: { ...ws, host: WS_HOST, port: WS_PORT },
53
+ clients,
54
+ extension: { path: extPath },
55
+ map: { dbPath, exists: dbExists, sizeBytes, apps },
56
+ security: { allowedBrowsers: getAllowedBrowsers() },
57
+ };
58
+ }
59
+ function symbol(ok) {
60
+ return ok ? '✓' : '✗';
61
+ }
62
+ export function formatDoctor(r) {
63
+ const lines = [];
64
+ lines.push('browser-link doctor');
65
+ lines.push('');
66
+ lines.push(`WebSocket bridge ${symbol(r.ws.listening)} ${r.ws.host}:${r.ws.port} — ${r.ws.detail}`);
67
+ if (!r.ws.listening) {
68
+ lines.push(' (the server is launched by your MCP client; open Claude Code / OpenCode to start it)');
69
+ }
70
+ lines.push('');
71
+ lines.push('MCP clients:');
72
+ for (const c of r.clients) {
73
+ const status = !c.installed
74
+ ? '✗ not installed'
75
+ : c.registered
76
+ ? '✓ registered'
77
+ : '⚠ installed but not registered';
78
+ lines.push(` ${c.displayName.padEnd(14)} ${status}`);
79
+ lines.push(` ${' '.repeat(14)} config: ${c.configPath}`);
80
+ }
81
+ lines.push('');
82
+ lines.push('Chrome extension assets:');
83
+ if (r.extension.path) {
84
+ lines.push(` ${symbol(true)} ${r.extension.path}`);
85
+ }
86
+ else {
87
+ lines.push(` ${symbol(false)} not found (run \`browser-link extension\` for guidance)`);
88
+ }
89
+ lines.push('');
90
+ lines.push('Map DB:');
91
+ lines.push(` path: ${r.map.dbPath}`);
92
+ if (!r.map.exists) {
93
+ lines.push(' (not created yet — will be initialized on first run)');
94
+ }
95
+ else {
96
+ lines.push(` size: ${r.map.sizeBytes} bytes`);
97
+ lines.push(` apps tracked: ${r.map.apps}`);
98
+ }
99
+ lines.push('');
100
+ lines.push('Process binding:');
101
+ if (r.security.allowedBrowsers.length === 0) {
102
+ lines.push(` ✗ no allowlist for this OS — incoming WS connections will be rejected.`);
103
+ }
104
+ else {
105
+ lines.push(` ✓ accepts WS connections from ${r.security.allowedBrowsers.length} known browser binaries.`);
106
+ lines.push(` (${r.security.allowedBrowsers.slice(0, 4).join(', ')}${r.security.allowedBrowsers.length > 4 ? ', …' : ''})`);
107
+ }
108
+ return lines.join('\n');
109
+ }
110
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Resolve the absolute path to the Chrome extension assets.
3
+ * Tries the npm-install layout first, then the monorepo dev layout.
4
+ * Returns null if neither has a manifest.json.
5
+ */
6
+ export declare function resolveExtensionPath(): string | null;
7
+ export interface ExtensionInfo {
8
+ path: string | null;
9
+ hints: string;
10
+ }
11
+ export declare function getExtensionInfo(): ExtensionInfo;
12
+ export declare function printExtensionInstructions(): void;
@@ -0,0 +1,76 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { platform } from 'node:os';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ /**
6
+ * Resolve the absolute path to the Chrome extension assets.
7
+ * Tries the npm-install layout first, then the monorepo dev layout.
8
+ * Returns null if neither has a manifest.json.
9
+ */
10
+ export function resolveExtensionPath() {
11
+ const here = dirname(fileURLToPath(import.meta.url));
12
+ const candidates = [
13
+ // npm package layout (extension bundled alongside server dist)
14
+ join(here, '..', 'extension'),
15
+ // monorepo dev layout (packages/server/dist/commands → packages/extension/dist)
16
+ resolve(here, '..', '..', '..', 'extension', 'dist'),
17
+ // monorepo dev layout when cli is at dist root (packages/server/dist → packages/extension/dist)
18
+ resolve(here, '..', '..', 'extension', 'dist'),
19
+ ];
20
+ for (const c of candidates) {
21
+ if (existsSync(join(c, 'manifest.json')))
22
+ return c;
23
+ }
24
+ return null;
25
+ }
26
+ function osHints(extPath) {
27
+ const p = platform();
28
+ if (p === 'win32') {
29
+ return [
30
+ 'Open Chrome and go to: chrome://extensions',
31
+ 'Toggle "Developer mode" (top-right).',
32
+ 'Click "Load unpacked".',
33
+ `Browse to: ${extPath}`,
34
+ 'Click "Select Folder".',
35
+ ].join('\n ');
36
+ }
37
+ if (p === 'darwin') {
38
+ return [
39
+ 'Open Chrome and go to: chrome://extensions',
40
+ 'Toggle "Developer mode" (top-right).',
41
+ 'Click "Load unpacked".',
42
+ `In the file picker, press ⌘⇧G and paste: ${extPath}`,
43
+ 'Press Return, then "Select".',
44
+ ].join('\n ');
45
+ }
46
+ return [
47
+ 'Open Chrome and go to: chrome://extensions',
48
+ 'Toggle "Developer mode" (top-right).',
49
+ 'Click "Load unpacked".',
50
+ `Select the folder: ${extPath}`,
51
+ ].join('\n ');
52
+ }
53
+ export function getExtensionInfo() {
54
+ const path = resolveExtensionPath();
55
+ return {
56
+ path,
57
+ hints: path
58
+ ? osHints(path)
59
+ : 'Extension assets not found. Run `npm run build:extension` (dev) or reinstall the package.',
60
+ };
61
+ }
62
+ export function printExtensionInstructions() {
63
+ const info = getExtensionInfo();
64
+ if (!info.path) {
65
+ console.log(info.hints);
66
+ return;
67
+ }
68
+ console.log('Chrome extension assets are at:');
69
+ console.log(` ${info.path}`);
70
+ console.log('');
71
+ console.log('Install steps:');
72
+ console.log(` ${info.hints}`);
73
+ console.log('');
74
+ console.log('After loading, open the extension popup on any tab and click "Conectar" to bridge it.');
75
+ }
76
+ //# sourceMappingURL=extension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.js","sourceRoot":"","sources":["../../src/commands/extension.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;;GAIG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,MAAM,UAAU,GAAG;QACjB,+DAA+D;QAC/D,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC;QAC7B,gFAAgF;QAChF,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC;QACpD,gGAAgG;QAChG,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC;KAC/C,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,OAAO,CAAC,OAAe;IAC9B,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC;IACrB,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC;QAClB,OAAO;YACL,4CAA4C;YAC5C,sCAAsC;YACtC,wBAAwB;YACxB,cAAc,OAAO,EAAE;YACvB,wBAAwB;SACzB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjB,CAAC;IACD,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;QACnB,OAAO;YACL,4CAA4C;YAC5C,sCAAsC;YACtC,wBAAwB;YACxB,4CAA4C,OAAO,EAAE;YACrD,8BAA8B;SAC/B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjB,CAAC;IACD,OAAO;QACL,4CAA4C;QAC5C,sCAAsC;QACtC,wBAAwB;QACxB,sBAAsB,OAAO,EAAE;KAChC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACjB,CAAC;AAOD,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;IACpC,OAAO;QACL,IAAI;QACJ,KAAK,EAAE,IAAI;YACT,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;YACf,CAAC,CAAC,2FAA2F;KAChG,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,0BAA0B;IACxC,MAAM,IAAI,GAAG,gBAAgB,EAAE,CAAC;IAChC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,OAAO;IACT,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CACT,uFAAuF,CACxF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { type ClientId } from '../installers/index.js';
2
+ /**
3
+ * Same shape but using the absolute path to the compiled MCP entry — useful
4
+ * as a fallback when the bin is not yet on PATH (fresh clone, no `npm link`).
5
+ * The entry path is resolved at runtime by `entry-info.js` so this stays
6
+ * correct even if the dist layout changes.
7
+ */
8
+ export declare function resolveAbsoluteServerCommand(): {
9
+ command: string;
10
+ args: string[];
11
+ };
12
+ export interface InstallReport {
13
+ client: ClientId;
14
+ displayName: string;
15
+ installedClient: boolean;
16
+ message: string;
17
+ }
18
+ export declare function installFor(client: ClientId, mode?: 'bin' | 'absolute'): InstallReport;
19
+ export declare function installAll(mode?: 'bin' | 'absolute'): InstallReport[];
@@ -0,0 +1,52 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { SERVER_ENTRY_PATH } from '../entry-info.js';
3
+ import { INSTALLERS, getInstaller } from '../installers/index.js';
4
+ /**
5
+ * Where this CLI lives on disk. Used to build the MCP command that clients
6
+ * must spawn. We prefer the bin name "browser-link" on PATH; if the user
7
+ * invoked us via an absolute path (e.g. unpublished dev install), we fall
8
+ * back to that absolute node command.
9
+ */
10
+ function resolveServerCommand() {
11
+ // npm installs put `browser-link` on PATH. That's the most portable across OSes.
12
+ // For dev runs (no global install) the user can override via env var.
13
+ const override = process.env.BROWSER_LINK_BIN;
14
+ if (override) {
15
+ const parts = override.split(' ').filter(Boolean);
16
+ return { command: parts[0], args: parts.slice(1) };
17
+ }
18
+ // Default: rely on the PATH lookup. This works on Windows because npm
19
+ // creates a `browser-link.cmd` shim alongside the global node binary.
20
+ return { command: 'browser-link', args: [] };
21
+ }
22
+ /**
23
+ * Same shape but using the absolute path to the compiled MCP entry — useful
24
+ * as a fallback when the bin is not yet on PATH (fresh clone, no `npm link`).
25
+ * The entry path is resolved at runtime by `entry-info.js` so this stays
26
+ * correct even if the dist layout changes.
27
+ */
28
+ export function resolveAbsoluteServerCommand() {
29
+ if (existsSync(SERVER_ENTRY_PATH)) {
30
+ return { command: process.execPath, args: [SERVER_ENTRY_PATH] };
31
+ }
32
+ return resolveServerCommand();
33
+ }
34
+ export function installFor(client, mode = 'bin') {
35
+ const inst = getInstaller(client);
36
+ const detect = inst.detect();
37
+ if (!detect.installed) {
38
+ return {
39
+ client,
40
+ displayName: inst.displayName,
41
+ installedClient: false,
42
+ message: `${inst.displayName} config not found at ${detect.configPath}. Install ${inst.displayName} first.`,
43
+ };
44
+ }
45
+ const cmd = mode === 'absolute' ? resolveAbsoluteServerCommand() : resolveServerCommand();
46
+ const message = inst.install(cmd.command, cmd.args);
47
+ return { client, displayName: inst.displayName, installedClient: true, message };
48
+ }
49
+ export function installAll(mode = 'bin') {
50
+ return INSTALLERS.map((i) => installFor(i.id, mode));
51
+ }
52
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAiB,MAAM,wBAAwB,CAAC;AAEjF;;;;;GAKG;AACH,SAAS,oBAAoB;IAC3B,iFAAiF;IACjF,sEAAsE;IACtE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC9C,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAClD,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,CAAC;IACD,sEAAsE;IACtE,sEAAsE;IACtE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,4BAA4B;IAC1C,IAAI,UAAU,CAAC,iBAAiB,CAAC,EAAE,CAAC;QAClC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,iBAAiB,CAAC,EAAE,CAAC;IAClE,CAAC;IACD,OAAO,oBAAoB,EAAE,CAAC;AAChC,CAAC;AASD,MAAM,UAAU,UAAU,CAAC,MAAgB,EAAE,OAA2B,KAAK;IAC3E,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAC7B,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO;YACL,MAAM;YACN,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,eAAe,EAAE,KAAK;YACtB,OAAO,EAAE,GAAG,IAAI,CAAC,WAAW,wBAAwB,MAAM,CAAC,UAAU,aAAa,IAAI,CAAC,WAAW,SAAS;SAC5G,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,4BAA4B,EAAE,CAAC,CAAC,CAAC,oBAAoB,EAAE,CAAC;IAC1F,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IACpD,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,eAAe,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAA2B,KAAK;IACzD,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { type Language } from './welcome.js';
2
+ interface MenuI18n {
3
+ title: string;
4
+ prompt: string;
5
+ pressEnter: string;
6
+ options: {
7
+ register: string;
8
+ extension: string;
9
+ doctor: string;
10
+ welcome: string;
11
+ about: string;
12
+ repo: string;
13
+ quit: string;
14
+ };
15
+ statusRegistered: string;
16
+ statusNotRegistered: string;
17
+ statusClientMissing: string;
18
+ registerSuccessHint: string;
19
+ repoOpened: string;
20
+ repoFallback: string;
21
+ }
22
+ export declare const I18N_MENU: Record<Language, MenuI18n>;
23
+ export declare function claudeStatus(t: MenuI18n): string;
24
+ export declare function renderMenuScreen(t: MenuI18n, selectedIndex: number, options: string[]): string;
25
+ export declare function runMenu(initialLanguage?: Language): Promise<void>;
26
+ export {};