docs(browser): guía de control de navegador para LLMs (CDP anti-colapso)

Añade LLM_BROWSER_GUIDE.md: guía operativa para que un agente LLM (Claude u
otro) controle los navegadores del equipo via Chrome DevTools Protocol sin
saturar su contexto. Cubre la conexión (puertos 9222 diario / 9333 dedicado),
las capacidades por eje (ventanas, pestañas, network, DOM, ejecución de JS) con
recetas lanzables, y sobre todo la lectura de páginas mediante accessibility
tree recortado (cdp_get_ax_tree + trim_ax_tree, render a outline) en lugar de
volcar el HTML crudo, que es la causa principal de colapso de contexto.

Incluye una tabla de presupuesto de tokens por acción, recetas de tarea
end-to-end (entender página, scraping, login+SPA, mapear API oculta), los
gotchas heredados de CHROMIUM_SYSTEM.md y la hoja de ruta del futuro servidor
MCP de navegador (cada capacidad como tool que devuelve representaciones
compactas en el borde). project.md referencia la nueva guía.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 09:52:40 +02:00
parent 467e27fc2f
commit 2527fd306a
2 changed files with 463 additions and 0 deletions
+457
View File
@@ -0,0 +1,457 @@
# Guía de control de navegador para LLMs (CDP anti-colapso)
Guía operativa para que un agente LLM (Claude u otro) controle los navegadores del equipo via
Chrome DevTools Protocol (CDP) **sin saturar su propio contexto**. El problema central que esta
guía resuelve: una página web cruda (HTML completo, `outerHTML`, screenshots como datos) puede
ocupar decenas de miles de tokens y colapsar la ventana de contexto del modelo. La solución es
operar siempre sobre representaciones compactas — sobre todo el **accessibility tree (AX tree)
recortado** — y devolver únicamente lo extraído, nunca la página entera.
Complementa, no sustituye:
- `project.md` — qué es el proyecto y para qué sirve.
- `CONVENTIONS.md` — las 9 reglas operativas (ventana fija, perfil dedicado, esperas inteligentes,
ratón humano, perfiles solo-uBlock, CDP global).
- `CHROMIUM_SYSTEM.md` — mapa físico de la configuración de Chromium en este equipo (puertos,
`/etc/chromium.d/`, perfiles, proxy mitm, gotchas de sistema).
Todas las rutas de esta guía son relativas a la raíz del repo `fn_registry`.
---
## TL;DR — la regla de oro
1. **Nunca vuelques la página entera al contexto.** Prohibido `script-navegador html` (devuelve el
`outerHTML` completo) salvo que vayas a procesarlo fuera del contexto (a archivo, a un pipe).
Prohibido pegar screenshots como "datos".
2. **Para *entender* una página, usa un AX snapshot recortado** (`cdp_get_ax_tree` + `trim_ax_tree`,
render a outline de texto). Es la vista semántica de la página (roles + nombres accesibles),
ya sin ruido de divs/spans vacíos. Típicamente 10-50x más pequeña que el HTML.
3. **Para *extraer* datos, ejecuta JavaScript que devuelva solo el resultado** (`script-navegador
eval`), no el DOM. Devuelve un JSON pequeño con los campos que necesitas.
4. **Para páginas enormes**, trocea el AX tree con `chunk_ax_tree` y procesa chunk a chunk.
5. **Espera condiciones reales** (carga, red en reposo, selector presente), nunca `sleep` ciegos.
---
## 0. Prerrequisitos y conexión
### Atajos de ruta usados en esta guía
```bash
# Desde la raíz del repo fn_registry:
SN=projects/web_scraping/apps/script_navegador/script-navegador # binario Go ya compilado
PY=python/.venv/bin/python3 # venv del registry (tiene websocket-client)
```
Si el binario `script-navegador` no existiera, recompílalo:
`cd projects/web_scraping/apps/script_navegador && CGO_ENABLED=1 go build -o script-navegador .`
### Mapa de puertos CDP (ver `CHROMIUM_SYSTEM.md` para el detalle)
| Puerto | Quién | Cuándo usarlo |
|---|---|---|
| `9222` | Chromium **diario** del usuario (CDP global loopback, siempre activo) | Tareas sobre sitios donde el usuario ya tiene sesión/cookies/login. Opera sobre su navegación real. |
| `9333` (o el que pidas) | Navegador de **automatización** dedicado, perfil aislado | Scraping limpio que no debe tocar la sesión personal. Empieza solo con uBlock. |
Dos procesos Chromium no pueden compartir el mismo `--remote-debugging-port`.
### Comprobar que hay un Chrome al que conectarse
```bash
curl -s --max-time 2 http://127.0.0.1:9222/json/version | head -c 300 # vacío => no hay CDP en 9222
```
Si no responde, lanza uno (perfil dedicado de automatización recomendado para no tocar lo del usuario):
```bash
# Visible (recomendado al desarrollar el flujo), perfil dedicado del proyecto, ventana fija 1366x768:
$SN launch --port 9333 --profile-directory Automation
# Conectarse a un Chrome ya vivo no requiere launch: los comandos rápidos usan --port.
```
> Gotcha de sistema: lanzar chromium directamente desde una tool de shell del agente puede dar
> exit-144 (el harness mata el cgroup). `script-navegador launch` ya esquiva esto. Si lanzas
> chromium a mano, usa `systemd-run --user --unit=<x> --collect chromium ...`. Ver
> `CHROMIUM_SYSTEM.md` y la memoria `harness-exit-144-chromium`.
### Elegir la pestaña (tab) sobre la que operar
Los comandos rápidos del binario (`open`, `click`, `eval`...) actúan sobre la **primera tab de tipo
`page`**. Las funciones AX en cambio reciben un `tab_id` explícito. Para listarlos:
```bash
$SN tabs # legible: título | url de cada page
curl -s http://127.0.0.1:9222/json | $PY -c 'import sys,json; [print(t["id"], t["url"]) for t in json.load(sys.stdin) if t["type"]=="page"]'
```
---
## 1. Presupuesto de contexto (el corazón anti-colapso)
| Acción | Coste típico en tokens | Veredicto |
|---|---|---|
| `script-navegador html` (outerHTML completo) | 5.000 100.000+ | ❌ nunca al contexto |
| Screenshot pegado como imagen/base64 | enorme | ❌ solo a archivo (`shot /tmp/x.png`), descríbelo, no lo pegues |
| AX tree completo (`cdp_get_ax_tree` sin recortar) | 5.000 50.000 | ⚠️ recórtalo siempre antes de mirarlo |
| **AX tree recortado** (`trim_ax_tree`, render outline) | 200 3.000 | ✅ vista por defecto para entender |
| `eval` que devuelve un JSON de campos extraídos | 50 1.000 | ✅ vista por defecto para extraer |
**Reglas duras:**
- Si necesitas el HTML para procesarlo (regex, parser), mándalo a archivo y trabájalo con
herramientas, no lo leas entero: `$SN html > /tmp/page.html` y luego `grep`/funciones del registry.
- Antes de mirar un AX tree, pásalo por `trim_ax_tree`. Si sigue siendo grande, `chunk_ax_tree` y
procesa por partes.
- Para extraer datos estructurados, prefiere un `eval` con un snippet JS que ya devuelva el array de
objetos final. El DOM se queda en el navegador; al contexto solo llega el resultado.
---
## 2. Capacidades — tabla maestra
| Eje | Qué hay hoy | Cómo se invoca | Gap (hoy a mano via CDP crudo) |
|---|---|---|---|
| **Ejecutar JS** | `cdp_evaluate` | `$SN eval '<js>'` | — |
| **AX tree (semántico)** | `cdp_get_ax_tree` + `trim_ax_tree` + `chunk_ax_tree` + `llm_propose_scraping_schema` | heredoc Python (§3.6) | render a outline texto (no existe función; snippet en §3.6) |
| **DOM leer/interactuar** | `cdp_get_html` `cdp_find_by_text` `cdp_click` `cdp_click_text` `cdp_type_text` `cdp_wait_element` | `$SN open/click/type/wait/html` | set-attribute / remove-node dedicados (se hacen via `eval`) |
| **Pestañas** | crear `cdp_new_tab` `cdp_open_url_and_wait`; listar `cdp_list_tabs` | `$SN tabs`; heredoc | **cerrar** y **activar/focus** tab (CDP crudo, §3.2) |
| **Network** | capturar `cdp_har_record`; cookies `cdp_set_cookie`; HLS `extract_hls_from_cdp_tab` | heredoc / `fn run` | **interceptar / bloquear / modificar** (Fetch domain, §3.3) |
| **Ventanas** | — | — | **todo** (get/set bounds, min/max/fullscreen) via CDP crudo (§3.1) |
Los gaps están listados como candidatos a función / tool MCP en §6.
---
## 3. Recetas por capacidad
### 3.1 Ventanas (CRUD) — hoy via CDP crudo
No hay función del registry todavía. El control de ventana es **browser-level** (no de página), así
que se habla por el WebSocket de `/json/version` (`webSocketDebuggerUrl`), no por el de una tab.
Secuencia: `Browser.getWindowForTarget` → `windowId` → `Browser.setWindowBounds`.
```bash
# Mover/redimensionar la ventana de un target (tab). Requiere el targetId de la tab.
PORT=9222 PY=$PY $PY - <<'PYEOF'
import os, json, urllib.request, websocket
port = int(os.environ["PORT"])
# 1) targetId de la primera page
tabs = json.load(urllib.request.urlopen(f"http://127.0.0.1:{port}/json"))
page = next(t for t in tabs if t["type"] == "page")
target_id = page["id"]
# 2) ws browser-level
ver = json.load(urllib.request.urlopen(f"http://127.0.0.1:{port}/json/version"))
ws = websocket.create_connection(ver["webSocketDebuggerUrl"])
def call(mid, method, params):
ws.send(json.dumps({"id": mid, "method": method, "params": params}))
while True:
m = json.loads(ws.recv())
if m.get("id") == mid:
return m
win = call(1, "Browser.getWindowForTarget", {"targetId": target_id})["result"]
wid = win["windowId"]
# 3) set bounds (normal). Para maximizar: {"windowState":"maximized"} solo.
call(2, "Browser.setWindowBounds", {"windowId": wid,
"bounds": {"left": 0, "top": 0, "width": 1366, "height": 768, "windowState": "normal"}})
print(json.dumps(call(3, "Browser.getWindowForTarget", {"targetId": target_id})["result"]))
ws.close()
PYEOF
```
Estados válidos de `windowState`: `normal`, `minimized`, `maximized`, `fullscreen`. Para
maximizar/minimizar, manda solo `{"windowState": "maximized"}` sin width/height.
### 3.2 Pestañas (CRUD)
```bash
# CREATE — abrir URL en tab nuevo y esperar a que cargue (devuelve tab_id):
TAB_ID="$(PORT=9222 URL='https://example.com' $PY - <<'PYEOF'
import os, sys; sys.path.insert(0, "python/functions")
from pipelines.cdp_open_url_and_wait import cdp_open_url_and_wait
print(cdp_open_url_and_wait(int(os.environ["PORT"]), os.environ["URL"], timeout_s=20))
PYEOF
)"; echo "tab=$TAB_ID"
# READ — listar tabs:
$SN tabs
curl -s http://127.0.0.1:9222/json | $PY -c 'import sys,json; [print(t["id"], t["title"][:40], t["url"]) for t in json.load(sys.stdin) if t["type"]=="page"]'
# DELETE — cerrar un tab por id (endpoint HTTP, sin WebSocket):
curl -s "http://127.0.0.1:9222/json/close/$TAB_ID" >/dev/null && echo "cerrado $TAB_ID"
# UPDATE — activar / traer al frente un tab por id:
curl -s "http://127.0.0.1:9222/json/activate/$TAB_ID" >/dev/null && echo "activado $TAB_ID"
```
`cerrar` y `activar` son gaps de función (se hacen con `curl` al endpoint `/json/`). Candidatos a
`cdp_close_tab` / `cdp_activate_tab` en §6.
### 3.3 Network (capturar / interceptar)
**Capturar tráfico** (mapear las APIs reales que usa una página — recon/pentesting). La función
`cdp_har_record` registra todas las peticiones HTTP/WS que ocurren mientras corre una acción y
devuelve un HAR 1.2:
```bash
# Capturar el tráfico de una recarga de página y guardarlo como HAR:
PORT=9222 OUT=/tmp/capture.har $PY - <<'PYEOF'
import os, sys, json; sys.path.insert(0, "functions") # las cdp_*_go_browser son Go; ver nota
# NOTA: cdp_har_record es Go. Para HAR desde Python usa el flujo de web_proxy (mitm) o
# llama al binario que la compone. Aquí el patrón equivalente en CDP crudo:
PYEOF
echo "Para HAR: usa la app web_proxy (proxy mitm 8889) o cdp_har_record vía un binario Go."
```
> Nota de stack: `cdp_har_record`, `cdp_set_cookie`, etc. son funciones **Go** (`*_go_browser`).
> Desde el agente se consumen compiladas dentro de un binario (como `script-navegador`) o, para
> captura de tráfico de largo recorrido, via la app **`web_proxy`** (mitmproxy, proxy `127.0.0.1:8889`,
> UI `http://127.0.0.1:8081`). Ver `CHROMIUM_SYSTEM.md` §"Proxy / captura mitm".
**Interceptar / bloquear / modificar** peticiones (ej. bloquear imágenes y trackers para acelerar y
limpiar el scraping) — gap de función, hoy via CDP crudo con el dominio `Fetch`:
```bash
# Bloquear patrones de URL en la tab actual mientras dure la conexión (Ctrl-C para soltar):
PORT=9222 TAB_ID="$TAB_ID" BLOCK='*.png,*.jpg,*.gif,*doubleclick*,*googlesyndication*' $PY - <<'PYEOF'
import os, json, fnmatch, urllib.request, websocket
port=int(os.environ["PORT"]); tab=os.environ["TAB_ID"]
patterns=[p.strip() for p in os.environ["BLOCK"].split(",") if p.strip()]
tabs=json.load(urllib.request.urlopen(f"http://127.0.0.1:{port}/json"))
ws_url=next(t["webSocketDebuggerUrl"] for t in tabs if t["id"]==tab)
ws=websocket.create_connection(ws_url)
ws.send(json.dumps({"id":1,"method":"Fetch.enable","params":{"patterns":[{"urlPattern":"*"}]}}))
print("interceptando; Ctrl-C para parar")
while True:
m=json.loads(ws.recv())
if m.get("method")=="Fetch.requestPaused":
rid=m["params"]["requestId"]; url=m["params"]["request"]["url"]
if any(fnmatch.fnmatch(url, p) for p in patterns):
ws.send(json.dumps({"id":99,"method":"Fetch.failRequest","params":{"requestId":rid,"errorReason":"BlockedByClient"}}))
else:
ws.send(json.dumps({"id":99,"method":"Fetch.continueRequest","params":{"requestId":rid}}))
PYEOF
```
Candidato a `cdp_intercept_requests` / `cdp_block_urls` en §6.
### 3.4 DOM — leer e interactuar
```bash
$SN open https://example.com # navega la tab activa, espera carga + red en reposo (SPAs)
$SN wait '#resultados' # espera a que un selector exista (timeout-ms configurable)
$SN click '.boton-aceptar' # click con trayectoria de ratón Bézier (anti-detección)
$SN click '.x' --instant # click directo (si el elemento no tiene bounding box)
$SN type 'input[name=q]' 'mi búsqueda' # enfoca con click y escribe carácter a carácter
$SN shot /tmp/page.png # screenshot a ARCHIVO (no al contexto). --full = página entera
```
**Modificar el DOM** (set attribute, quitar nodos, rellenar valores) — vía `eval`:
```bash
$SN eval 'document.querySelector("#banner")?.remove(); "removed"'
$SN eval 'document.querySelector("input#token").value="abc"; "set"'
```
Click robusto por texto visible (cuando el selector CSS es frágil) — usa `cdp_find_by_text` /
`cdp_click_text` (Go, dentro del binario) o, equivalente en `eval`:
```bash
$SN eval '[...document.querySelectorAll("button")].find(b=>b.innerText.trim()==="Siguiente")?.click(); "clicked"'
```
### 3.5 Ejecutar JavaScript — la vía de extracción compacta
`eval` devuelve el resultado serializado. **Haz que el JS devuelva solo lo extraído**, no el DOM:
```bash
# Título y nº de resultados — devuelve 2 valores, no la página:
$SN eval 'JSON.stringify({title: document.title, n: document.querySelectorAll(".item").length})'
# Extraer una tabla a array de objetos (esto es lo que llega al contexto, ~compacto):
$SN eval '
JSON.stringify([...document.querySelectorAll("table.precios tbody tr")].map(tr => {
const td = tr.querySelectorAll("td");
return { producto: td[0]?.innerText.trim(), precio: td[1]?.innerText.trim() };
}))'
```
### 3.6 AX tree — la vista semántica anti-colapso (receta estrella)
El accessibility tree es cómo los lectores de pantalla "ven" la página: una jerarquía de roles
(`button`, `link`, `heading`, `textbox`, `list`...) con su nombre accesible. `trim_ax_tree` ya
descarta nodos ignorados, `generic`/`none` vacíos y `StaticText` vacíos, y colapsa cadenas de un
solo hijo. El resultado es una vista compacta y navegable de la página.
**Receta canónica — AX snapshot a outline de texto** (compón `cdp_get_ax_tree` + `trim_ax_tree` y
renderiza a líneas indentadas `role "nombre"`):
```bash
# Requiere TAB_ID (ver §3.2). Devuelve un outline compacto, NO el HTML.
PORT=9222 TAB_ID="$TAB_ID" MAXLINES=400 $PY - <<'PYEOF'
import os, sys; sys.path.insert(0, "python/functions")
from pipelines.cdp_get_ax_tree import cdp_get_ax_tree
from core.trim_ax_tree import trim_ax_tree
port=int(os.environ["PORT"]); tab=os.environ["TAB_ID"]; maxlines=int(os.environ.get("MAXLINES","400"))
nodes = trim_ax_tree(cdp_get_ax_tree(port, tab))
by_id = {n["nodeId"]: n for n in nodes}
children = set(c for n in nodes for c in n.get("childIds", []))
roots = [n for n in nodes if n["nodeId"] not in children] or nodes[:1]
def field(n, k):
v = n.get(k, {})
return v.get("value", "") if isinstance(v, dict) else (v or "")
out, count = [], 0
def walk(n, depth):
global count
if count >= maxlines: return
role = field(n, "role"); name = field(n, "name")
if role: # omite nodos sin role
line = " "*depth + role + (f' "{name}"' if name else "")
out.append(line[:200]); count += 1
for cid in n.get("childIds", []):
ch = by_id.get(cid)
if ch: walk(ch, depth+1)
for r in roots: walk(r, 0)
print("\n".join(out))
if count >= maxlines:
print(f"... [truncado a {maxlines} líneas; sube MAXLINES o usa chunk_ax_tree]")
PYEOF
```
Salida típica (compacta, lista para razonar):
```
WebArea "Example Domain"
heading "Example Domain"
paragraph "This domain is for use in illustrative examples..."
link "More information..."
```
**Página enorme** — trocea con `chunk_ax_tree` y procesa chunk a chunk (cada chunk ≤ max_chars y
arranca con un nodo `context` que da el path desde la raíz):
```bash
PORT=9222 TAB_ID="$TAB_ID" $PY - <<'PYEOF'
import os, sys, json; sys.path.insert(0, "python/functions")
from pipelines.cdp_get_ax_tree import cdp_get_ax_tree
from core.trim_ax_tree import trim_ax_tree
from core.chunk_ax_tree import chunk_ax_tree
nodes = trim_ax_tree(cdp_get_ax_tree(int(os.environ["PORT"]), os.environ["TAB_ID"]))
chunks = chunk_ax_tree(nodes, max_chars=25000)
print(f"{len(nodes)} nodos -> {len(chunks)} chunks") # procesa chunks[i] de uno en uno
PYEOF
```
**Proponer un schema de scraping automáticamente** desde el AX tree (orquesta trim → chunk → LLM):
función `llm_propose_scraping_schema(url, ax_tree, ...)` (`*_py_infra`).
---
## 4. Recetas de tarea end-to-end
### 4.1 Entender una página desconocida sin colapsar
1. `$SN open <url>` (o crea tab con `cdp_open_url_and_wait`, §3.2) y captura el `TAB_ID`.
2. AX snapshot a outline (§3.6). **Esto** es lo que lees, no el HTML.
3. Si necesitas un detalle puntual, `$SN eval '<js que devuelve solo ese dato>'`.
### 4.2 Extraer datos estructurados (scraping)
1. Abre y espera (`$SN open` ya espera carga + red en reposo; para SPAs añade `$SN wait '<selector>'`).
2. AX snapshot para localizar los contenedores/roles relevantes.
3. Un único `$SN eval` que devuelva el array de objetos final (§3.5). Solo el resultado llega al
contexto. Persiste a `vaults/` o a archivo si es grande.
### 4.3 Login + scrape de un SPA
1. `$SN open <login_url>`; `$SN type 'input[name=user]' '<u>'`; `$SN type 'input[name=pass]' '<p>'`;
`$SN click 'button[type=submit]'`.
2. `$SN wait '<selector que solo existe logueado>'` (espera real, no sleep).
3. Navega y extrae con `eval` (§4.2). Sobre el puerto **9222** reaprovechas la sesión ya logueada del
usuario; sobre un perfil dedicado, el login persiste en su `user-data-dir` entre ejecuciones.
### 4.4 Mapear la API oculta de un sitio (recon)
1. Arranca la captura: app `web_proxy` (proxy mitm `127.0.0.1:8889`) o `cdp_har_record` envolviendo
la acción. Para acelerar, bloquea imágenes/trackers con el interceptor `Fetch` (§3.3).
2. Navega/interactúa para disparar las llamadas (`$SN open`, `$SN click`).
3. Consulta el tráfico: `./web_proxy query "~d dominio.com" --last`, `./web_proxy har salida.har`.
Los endpoints XHR/fetch que aparecen son las APIs reales del sitio.
---
## 5. Gotchas (además de los de `CHROMIUM_SYSTEM.md`)
- **No leas el HTML al contexto.** Es el error de colapso nº 1. AX outline o `eval` con resultado.
- **`tab_id` ≠ `targetId` de ventana.** Las funciones AX usan el `id` de `/json` (la tab). El control
de ventana (§3.1) usa el `webSocketDebuggerUrl` browser-level de `/json/version`.
- **`Page.loadEventFired` no dispara en SPAs** con routing sin recarga. Usa `$SN wait '<selector>'`
o `cdp_wait_idle` (red en reposo) en lugar de fiarte del load event.
- **Selector de perfil:** si lanzas chromium con varios perfiles sin `--profile-directory`, Chrome se
atasca en el picker y CDP opera sobre una ventana vacía. Siempre pasa el perfil. Ver
`CHROMIUM_SYSTEM.md`.
- **`--remote-allow-origins=*` entre comillas en zsh** (el `*` se expande como glob).
- **Lock por user-data-dir:** dos chromium no comparten el mismo `--user-data-dir`. Para automatizar
en paralelo a la sesión del usuario, usa un `--user-data-dir` dedicado.
- **El proxy mitm (8889) puede ralentizar sitios externos.** Si una tab se cuelga durante captura,
sube timeouts o revisa el upstream del proxy. Ver `CHROMIUM_SYSTEM.md`.
- **AX tree obsoleto tras cambios JS:** el snapshot es del momento. Tras un click que muta el DOM,
vuelve a pedir el AX tree.
---
## 6. Gaps → hoja de ruta del MCP
Cuando montemos el servidor MCP de navegador (para que cualquier LLM use estas capacidades como
tools nativas, sin recordar comandos), cada fila se vuelve una tool. Las marcadas "función pendiente"
hoy se hacen con el CDP crudo de §3 y son candidatas a `fn-constructor` antes/junto al MCP.
| Tool MCP propuesta | Estado de la capacidad subyacente | Origen |
|---|---|---|
| `browser_tabs_list` / `_new` / `_open_wait` | ✅ función existe | `cdp_list_tabs`, `cdp_new_tab`, `cdp_open_url_and_wait` |
| `browser_tab_close` / `_activate` | ⏳ función pendiente (`/json/close`, `/json/activate`) | §3.2 |
| `browser_ax_snapshot` (outline compacto) | ⏳ función pendiente (compón get+trim+render) | §3.6 |
| `browser_ax_chunks` | ✅ `chunk_ax_tree` | §3.6 |
| `browser_eval` | ✅ `cdp_evaluate` | §3.5 |
| `browser_click` / `_type` / `_wait` | ✅ funciones Go | §3.4 |
| `browser_screenshot` (a archivo, devuelve ruta) | ✅ `cdp_screenshot` | §3.4 |
| `browser_window_get` / `_set_bounds` | ⏳ función pendiente (`Browser.*WindowBounds`) | §3.1 |
| `browser_net_capture_har` | ✅ `cdp_har_record` (Go) | §3.3 |
| `browser_net_block` / `_intercept` | ⏳ función pendiente (`Fetch` domain) | §3.3 |
| `browser_set_cookie` | ✅ `cdp_set_cookie` (Go) | — |
**Principio de diseño del MCP:** las tools devuelven **siempre representaciones compactas** —
AX outline, JSON de campos, rutas de archivo para screenshots/HAR — nunca el HTML o la imagen cruda.
El anti-colapso se hace cumplir en el borde de la tool, no en la disciplina del LLM.
---
## 7. Cheatsheet
```bash
SN=projects/web_scraping/apps/script_navegador/script-navegador
PY=python/.venv/bin/python3
$SN launch --port 9333 --profile-directory Automation # chrome dedicado aislado
$SN tabs # listar pestañas
$SN open https://sitio.com # navegar + esperar
$SN wait '#listo' # esperar selector
$SN click '.btn' # click humano (Bézier)
$SN type 'input[name=q]' 'texto' # escribir
$SN eval 'JSON.stringify({t:document.title})' # extraer compacto
$SN shot /tmp/x.png # screenshot a archivo
# AX snapshot compacto (NO html) -> ver §3.6 para el heredoc completo
# tab nuevo: cdp_open_url_and_wait(port, url) (§3.2)
# cerrar tab: curl .../json/close/<id> (§3.2)
# bloquear net: Fetch.enable + failRequest (§3.3)
# ventana: Browser.getWindowForTarget/setWindowBounds (§3.1)
```
**Norma única que lo resume todo:** para *mirar* una página → AX outline; para *sacar* datos →
`eval` que devuelve solo el resultado; el HTML crudo y los screenshots van a archivo, jamás al
contexto.
+6
View File
@@ -47,3 +47,9 @@ crudo a través de las funciones del dominio `browser` del registry, no con Play
Las reglas operativas (tamaño de ventana, perfil del proyecto, headless, jitter, captura,
movimientos realistas, proxys rotativos, CDP en el navegador del usuario) están en
`CONVENTIONS.md`.
Para **controlar el navegador desde un agente LLM** (Claude u otro) sin saturar su contexto, la
guía operativa es `LLM_BROWSER_GUIDE.md`: cómo conectar via CDP, qué comandos usar por capacidad
(ventanas, pestañas, network, DOM, JS) y, sobre todo, cómo leer páginas via accessibility tree
recortado en lugar de volcar el HTML crudo (anti-colapso). Incluye la hoja de ruta del futuro
servidor MCP de navegador.