browser-ipc-cdp 1.8.1 → 1.8.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.
@@ -17,10 +17,11 @@
17
17
  *
18
18
  * Flujo:
19
19
  * 1. Lee cdp_info.json -> puerto candidato
20
- * 2. Verifica CDP (curl /json/version) en hostIP:puerto
20
+ * 2. Verifica CDP (/json/version) en hostIP:puerto
21
21
  * 3. Si responde -> lanza chrome-devtools-mcp con ese URL
22
- * 4. Si NO responde -> ejecuta brave_ipc.py --no-kill (auto-launch)
23
- * 5. Releer cdp_info.json + reverificar -> conectar
22
+ * 4. Si stale -> auto-discovery via tasklist+netstat de procesos Chromium
23
+ * 5. Si encuentra port vivo -> reescribe cdp_info.json y conecta
24
+ * 6. Ultimo recurso -> brave_ipc.py --no-kill (auto-launch)
24
25
  */
25
26
  const { execSync, spawn, spawnSync } = require('child_process');
26
27
  const fs = require('fs');
@@ -112,6 +113,90 @@ function testCdp(url, timeoutMs = 3000) {
112
113
  });
113
114
  }
114
115
 
116
+ /**
117
+ * Descubre el puerto CDP real de cualquier proceso Chromium activo.
118
+ * Usa tasklist (PIDs por imagen) + netstat (LISTENING por PID) y
119
+ * prueba /json/version en cada candidato.
120
+ *
121
+ * Retorna { port, version } si encuentra uno vivo, o null.
122
+ */
123
+ async function discoverBrowserCdp() {
124
+ if (!IS_WIN) return null;
125
+
126
+ const images = ['brave.exe', 'chrome.exe', 'msedge.exe', 'chromium.exe'];
127
+ const pids = new Set();
128
+
129
+ for (const img of images) {
130
+ try {
131
+ const out = execSync(
132
+ `tasklist /FI "IMAGENAME eq ${img}" /FO CSV /NH`,
133
+ { encoding: 'utf-8', timeout: 8000, maxBuffer: 16 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] }
134
+ );
135
+ for (const line of out.split('\n')) {
136
+ const parts = line.trim().split('","');
137
+ if (parts.length >= 2) {
138
+ const pid = parseInt(parts[1].replace(/"/g, ''), 10);
139
+ if (Number.isFinite(pid) && pid > 0) pids.add(pid);
140
+ }
141
+ }
142
+ } catch (e) {}
143
+ }
144
+
145
+ if (pids.size === 0) {
146
+ logErr('Auto-discovery: ningun proceso Chromium corriendo');
147
+ return null;
148
+ }
149
+
150
+ let netstatOut = '';
151
+ try {
152
+ netstatOut = execSync('netstat -ano', {
153
+ encoding: 'utf-8', timeout: 20000, maxBuffer: 64 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'],
154
+ });
155
+ } catch (e) {
156
+ logErr(`Auto-discovery netstat fallo: ${e.message}`);
157
+ return null;
158
+ }
159
+
160
+ const ports = new Set();
161
+ for (const line of netstatOut.split('\n')) {
162
+ const tokens = line.trim().split(/\s+/);
163
+ if (tokens.length < 5 || tokens[0] !== 'TCP' || !tokens[3].includes('LISTENING')) continue;
164
+ const pid = parseInt(tokens[4], 10);
165
+ if (!pids.has(pid)) continue;
166
+ const m = tokens[1].match(/:(\d+)$/);
167
+ if (!m) continue;
168
+ const port = parseInt(m[1], 10);
169
+ if (port > 1024 && port < 65536) ports.add(port);
170
+ }
171
+
172
+ logErr(`Auto-discovery: ${ports.size} ports candidatos de ${pids.size} procesos`);
173
+
174
+ for (const port of ports) {
175
+ const version = await testCdp(`http://127.0.0.1:${port}`, 1500);
176
+ if (version) {
177
+ logErr(`Auto-discovery: CDP vivo en puerto ${port}`);
178
+ return { port, version };
179
+ }
180
+ }
181
+
182
+ return null;
183
+ }
184
+
185
+ function rewriteCdpInfo(port, version) {
186
+ const ws = (version && version.webSocketDebuggerUrl) || '';
187
+ const browser = (version && version.Browser) || 'Unknown';
188
+ const data = {
189
+ DEBUG_PORT: port,
190
+ DEBUG_WS: ws,
191
+ BROWSER: browser,
192
+ CDP_URL: `http://127.0.0.1:${port}`,
193
+ MODE: 'AUTO_DISCOVERED',
194
+ };
195
+ for (const p of [HOME_CDP_INFO, CDP_INFO]) {
196
+ try { fs.writeFileSync(p, JSON.stringify(data, null, 2), 'utf-8'); } catch (e) {}
197
+ }
198
+ }
199
+
115
200
  async function ensureCdpReady() {
116
201
  const candidates = getHostCandidates();
117
202
  logErr(`Candidatos host: ${candidates.join(', ')}`);
@@ -125,9 +210,21 @@ async function ensureCdpReady() {
125
210
  logErr(`CDP activo en ${url}`);
126
211
  return url;
127
212
  }
213
+ logErr(`Puerto ${port} de cdp_info.json stale. Auto-discovery...`);
214
+ }
215
+
216
+ // Intento 2: auto-discovery via tasklist+netstat (no reinicia browser)
217
+ const discovered = await discoverBrowserCdp();
218
+ if (discovered) {
219
+ rewriteCdpInfo(discovered.port, discovered.version);
220
+ const url = await tryCandidates(discovered.port, candidates, 3000);
221
+ if (url) {
222
+ logErr(`CDP descubierto y cdp_info.json actualizado: ${url}`);
223
+ return url;
224
+ }
128
225
  }
129
226
 
130
- // Intento 2: auto-launch via brave_ipc.py
227
+ // Intento 3: auto-launch via brave_ipc.py
131
228
  logErr('CDP no responde. Auto-lanzando Brave via brave_ipc.py...');
132
229
  if (!fs.existsSync(BRAVE_IPC_PY)) {
133
230
  logErr(`brave_ipc.py no encontrado en ${BRAVE_IPC_PY}`);
package/lib/mcp.js CHANGED
@@ -46,6 +46,53 @@ function getWslHostIp() {
46
46
  return '127.0.0.1';
47
47
  }
48
48
 
49
+ function updateClaudeUserConfig(wrapperPath) {
50
+ // Registra el MCP "brave" en scope user (~/.claude.json top-level mcpServers)
51
+ // para que aparezca en /mcp desde cualquier directorio, sin depender del cwd.
52
+ const braveEntry = {
53
+ type: 'stdio',
54
+ command: 'node',
55
+ args: [wrapperPath],
56
+ env: {},
57
+ };
58
+
59
+ const candidates = new Set();
60
+ if (process.env.HOME) candidates.add(path.join(process.env.HOME, '.claude.json'));
61
+ if (process.env.USERPROFILE) candidates.add(path.join(process.env.USERPROFILE, '.claude.json'));
62
+
63
+ // Desde WSL: buscar tambien en /mnt/c/Users/*/.claude.json
64
+ try {
65
+ if (fs.existsSync('/mnt/c/Users')) {
66
+ for (const user of fs.readdirSync('/mnt/c/Users')) {
67
+ const candidate = `/mnt/c/Users/${user}/.claude.json`;
68
+ if (fs.existsSync(candidate)) candidates.add(candidate);
69
+ }
70
+ }
71
+ } catch (e) {}
72
+
73
+ let updated = 0;
74
+ for (const p of candidates) {
75
+ try {
76
+ // Solo modificar si Claude Code ya creo el archivo.
77
+ // No bootstrap-eamos .claude.json para no chocar con first-run setup.
78
+ if (!fs.existsSync(p)) continue;
79
+ const data = JSON.parse(fs.readFileSync(p, 'utf-8'));
80
+ if (!data.mcpServers) data.mcpServers = {};
81
+ data.mcpServers.brave = braveEntry;
82
+
83
+ // Write atomico: tmp + rename (evita corromper si Claude Code lee a medias)
84
+ const tmp = p + '.tmp';
85
+ fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
86
+ fs.renameSync(tmp, p);
87
+ updated++;
88
+ log(` -> ${p} (user scope)`);
89
+ } catch (e) {
90
+ warn(`No se pudo actualizar ${p}: ${e.message}`);
91
+ }
92
+ }
93
+ return updated;
94
+ }
95
+
49
96
  function updateMcpJson(port, wslIp) {
50
97
  // Estrategia: apuntar al wrapper Node dinamico.
51
98
  // El wrapper lee cdp_info.json en cada invocacion -> puerto siempre fresco.
@@ -115,7 +162,16 @@ function updateMcpJson(port, wslIp) {
115
162
  }
116
163
 
117
164
  success(`.mcp.json actualizado (${updated} archivos): brave -> ${wslIp}:${port}`);
118
- return updated > 0;
165
+
166
+ // Tambien registrar en scope user (~/.claude.json) para que /mcp lo vea desde cualquier cwd
167
+ const userUpdated = updateClaudeUserConfig(wrapperPath);
168
+ if (userUpdated > 0) {
169
+ success(`.claude.json actualizado (${userUpdated} archivos, scope user): brave registrado`);
170
+ } else {
171
+ warn('No se encontro .claude.json para registrar en scope user. Reinicia Claude Code una vez para que se cree, luego re-ejecuta este comando.');
172
+ }
173
+
174
+ return (updated + userUpdated) > 0;
119
175
  }
120
176
 
121
- module.exports = { getWslHostIp, updateMcpJson };
177
+ module.exports = { getWslHostIp, updateMcpJson, updateClaudeUserConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-ipc-cdp",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "Control remoto de navegadores Chromium (Brave, Chrome, Edge) via IPC + CDP dinamico. Un comando para conectar Claude Code a tu navegador real.",
5
5
  "bin": {
6
6
  "browser-ipc-cdp": "bin/cli.js"