EZO Watcher es un muro de alertas y un buscapersonas instalado en el proyecto del usuario: configuración como código en .watcher/, un proceso local de Node, sin cuenta. Tu trabajo es la instalación completa y probada — no una lista de sugerencias.
El CLI es la fuente de verdad. Cada tema a continuación se imprime desde: watcher skills <topic> (temas: install, discover, sources, channels, hosting, cloudflare, netdata, operate, fix, telemetry), o lee references/<topic>.md junto a este archivo. Ejecuta cada comando como watcher <verbo>, nunca la palabra desnuda (doctor colisiona con el /doctor del agente anfitrión).
Instalar el CLI
CI=1 npm_config_yes=true npm i -g ezo-watcher
watcher --version
Sin instalación global: npx -y ezo-watcher <verbo> ejecuta el mismo CLI. Desde un repositorio clonado: npm ci && npm run build && npm link.
Node 24+. Detalle completo, incluyendo la instalación desde la última versión de GitHub: watcher skills install.
El bucle
Si .watcher/ ya existe, ejecuta primero watcher doctor --json y watcher scan --json, luego agrega solo lo que falte según la clasificación; no reinicies sobre una instalación funcional.
- Iniciar.
watcher initen la raíz del proyecto. Idempotente, nunca pregunta; escribe.watcher/config.json,.watcher/.env(0600),.watcher/data/, agrega los dos últimos al.gitignore, elige un puerto libre a partir del 7717, imprime un primer resumen dedoctor. - Escanear.
watcher scan --json→{signals, suggestedCommands}. En una muestra pequeña etiquetada a mano, su prototipo encontró 15 de 19 puntos HTTP/TLS pero solo 2 de 10 puntos de silencio (watcher skills discover): trata sus comandos como candidatos, luego haz el pase de silencio primero (abajo) leyendo el código. - Elegir según la clasificación (siguiente sección). Una fuente por punto real; nunca inventes una fuente para que se vea verde.
- Agregar. Por cada fuente elegida:
watcher add <tipo> --id <id> ... --found-at <archivo:línea> --read "<lo que prueba>".--found-at/--readson procedencia, ywatcher doctoradvierte si faltan.--dry-runprevisualiza. Luego un canal:watcher add channel <tipo> --id <id> ...(verwatcher skills channels). Si no hay credencial disponible — una máquina sin interfaz gráfica, sin cuenta de proveedor, un usuario que no ha elegido un buscapersonas — agrega la alternativa sin credencial,watcher add channel file --id local: escribe una línea JSON por notificación en.watcher/data/y es un canal real y comprobable, no un stub.watcher add ... --replacereescribe un id existente;watcher rm <id>elimina uno. - Bucle de doctor.
watcher doctor --json; siok:false, arregla el primerfailmediante sunexty vuelve a ejecutar. Sichannel.missingadvierte y el usuario quiere que suene un teléfono, haz el canal del paso 8 ahora y regresa. Salida 0 es éxito, 1 es fallo, 64 es uso incorrecto. - Ejecutar.
watcher run --detach(owatcher service installpara que sobreviva al reinicio). Luegowatcher doctor --live --jsonpara leer el servidor en ejecución. - Probar.
watcher doctor --live --prove --jsonhasta que devuelva{"ok": true, "live": true}.--provemueve la lectura de cada fuente más allá de su umbral y falla a menos que la regla realmente se active (source.assertion-not-red; una aserción vacua de http-probe falla comosource.assertion-vacuous): la respuesta de un http-probe se muta, un latido se evalúa después de una ejecución omitida más su gracia, una lectura de host se mueve más allá de sus umbrales de disco/memoria/carga, una conexión TCP falla, una fecha de expiración cae dentro depageDays. Un tipo cuya alerta es lo que su emisor informa (grafana, vmalert, sentry, netdata, feed, webhook) es un salto con nombresource.prove-unsupported, nunca un fallo. Si el proyecto no tiene servidor ni trabajo programado, agregawatcher add host --id host— la máquina misma, leída localmente sin nada instalado — y pruébalo. Solo un proyecto sin servidor, sin trabajo programado y sin host que monitorear ejecutawatcher doctor --no-runtime-surface --json→{"ok": true, "live": false, "reason": "no-runtime-surface"}, reportado claramente; una fuente configurada siempre gana sobre--no-runtime-surface(el informe entonces adviertedoctor.no-runtime-surface-ignored). No des una instalación no probada por terminada. - Buscar. Agrega un canal y ejecuta
watcher test-alert; confirma que llegó al dispositivo que importa. Una credencial faltante es un salto con nombre, nunca un pase. - Alojar (opcional). La escalera es local → servicio → Docker/VPS → Cloudflare Tunnel (predeterminado para "aloja con mi clave de Cloudflare") → Contenedores beta.
watcher skills hosting,watcher skills cloudflare. - Instalar la habilidad para la próxima vez.
watcher skills install --target all --dir .la escribe en el proyecto. En Claude Code ejecuta/reload-skillsdespués — un directorio de habilidades de nivel superior creado a mitad de sesión no se detecta de otra manera.
Día dos — leer el muro, reconocer, comentar, silenciar, estacionar, volver a verificar — es watcher skills operate.
Cuando algo se rompe
Un incidente es una afirmación con evidencia, no una notificación. El bucle completo es watcher skills fix:
watcher status --briefpara una línea, owatcher incidents --output jsonpara las claves (MCP:watch_state, luegolist_incidents).get_fix_context { key }(CLI:watcher incident <key> --fix-context): el incidente, la configuración de la fuente y del destino con las credenciales como nombres de variables de entorno, las aserciones de la regla y la que falló, dónde se encontró el endpoint, la última observación buena junto a la última mala, el comando exacto que vuelve a ejecutar la verificación, ynext.- Reprodúcelo desde ese comando. Un umbral no es una causa.
- Arregla la causa, redespliega o reinicia para que la corrección esté activa.
verify_fix { key, by }(CLI:watcher recheck <key> --wait): vuelve a verificar, espera la respuesta fresca de la fuente, y registra la confirmación cuando cada alerta está resuelta — o dice qué aserción aún falla, para que se pueda reparar la corrección. Nunca marques un incidente como resuelto antes de que una nueva verificación esté en verde;parkes para una corrección que está fusionada pero aún no activa,confirm_resolutionpara una que la fuente ya cerró.commentlo que encontraste en el camino. Una corrección silenciosa deja a la siguiente persona sin información.
Clasificación: qué monitorear, en orden
- La ruta de readiness más profunda del proceso de producción, afirmando un campo que cambia cuando una dependencia está caída. Nunca un
{"ok": true}/{"status": "ok"}hardcodeado — eso es liveness, y no puede fallar para la falla que importa. - Un sondeo de readiness que la plataforma ya declara:
/readyz,/health/ready, unhealthcheckde compose, unreadinessProbede Kubernetes, una ruta de salud de fly/railway/render. Reflejarlo desde afuera con la misma ruta y puerto. - Sin servidor → un latido por trabajo programado. El silencio es la señal.
- Sin servidor y sin trabajo programado →
watcher add host --id host(carga, memoria, espacio libre, opcionalmente un archivo pid; nada instalado) y pruébalo. Solo sin servidor, sin trabajo programado y sin host que monitorear →watcher doctor --no-runtime-surface --json, reportaNO_RUNTIME_SURFACE, detente. Una fuente configurada siempre gana sobre--no-runtime-surface. Nunca inventes una fuente. - Luego los sistemas que ya existen — Sentry, Grafana, Netdata, un remitente entrante — con credenciales. Una credencial faltante es un salto con nombre con un código y un
next, nunca un pase silencioso.
Silencio primero (el escaneo se los pierde)
Lee el código en busca de estas seis clases; cada una se convierte en un watcher add heartbeat o un watcher add webhook --stale-after:
- Programaciones generadas en tiempo de instalación — heredocs en
install.sh/setup.*que escriben un plist, unit o crontab,launchctl load,systemctl enable, escrituras decrontab. - launchd/systemd/cron creados en tiempo de ejecución — plantillas
.plist,OnCalendar=,StartInterval,KeepAlive,node-cron,schedule(,setIntervalalrededor de un trabajo, celery beat. Un latido por trabajo (--every/--schedule+--grace); N trabajos significa N fuentes. - Trabajadores de colas pull —
worker,queue,bullmq,sqs,claim,lease,dequeue. La falla es profundidad de cola o falta de progreso, no un puerto: envía un latido en la ruta de éxito, o empuja una entradachecks.listconstate: failing. - Obsolescencia en la propia configuración del producto —
staleAfterSec,presenceTtlSec,maxAge,ttl,refreshInterval,lastRunAt. Aplica la misma ventana:watcher add webhook --id <remitente> --token-env <ENV> --stale-after-sec <seg>. - Capacidad de escritura del directorio de estado —
mkdir,state/,data/, sqlite, archivos de bloqueo. Envía latido solo después de una escritura exitosa; un simpletest -wno es una fuente. - Endpoints de dependencia mantenidos como constantes en el código —
process.env.X ?? "https://…", literales host:puerto, bases gRPC/ClickHouse/LLM.watcher add http-probe(HTTPS GET) owatcher add tcp-probe --host --port, con--found-atla constante.
Coloca el latido en la ruta de éxito del trabajo — POST /ingest/heartbeat/<id>, con /fail para una ejecución fallida y /<exit-code> para una específica. watcher add heartbeat acuña el token en .watcher/.env (0600) cuando la variable nombrada aún no tiene valor, o escribe el --secret-value que pases, e imprime las líneas exactas ($NIGHTLY_TOKEN abajo es el nombre que elige para --id nightly; un --token-env que pases es la variable que imprime en su lugar):
set -a; . .watcher/.env
curl -fsS -m 10 "${WATCHER_URL:-http://127.0.0.1:7717}/ingest/heartbeat/<id>?token=$NIGHTLY_TOKEN" # después de una ejecución exitosa
curl -fsS -m 10 "${WATCHER_URL:-http://127.0.0.1:7717}/ingest/heartbeat/<id>/fail?token=$NIGHTLY_TOKEN" # después de una ejecución fallida
curl -fsS -m 10 "${WATCHER_URL:-http://127.0.0.1:7717}/ingest/heartbeat/<id>/<exit-code>?token=$NIGHTLY_TOKEN" # con el código de salida del trabajo
Un recolector que no sea un solo trabajo envía su lista completa en lugar de eso: POST /ingest/<source> con {"checks":{"list":[{"id":"…","service":"…","state":"ok","lastRunAt":"…"}]}} — la forma completa y los estados están en watcher skills sources.
Si una ruta de salud devuelve solo un literal, encuentra una más profunda o agrega una: GET /readyz → 200 {"db":{"ok":true}} cuando la dependencia responde, 503 {"db":{"ok":false}} cuando no. Editar el proyecto del usuario está permitido — eres el agente que escribe el archivo. Luego afirma sobre el campo cambiante, nunca sobre {"ok":true}.
Reglas
- Nunca apuntes una fuente al propio
/healthzdel Watcher —source.self-probelo rechaza. - Una fuente debe afirmar algo que pueda cambiar. Un simple 200 es solo liveness (
source.bare-200);watcher doctor --live --provees la puerta de entrada,--found-ates una advertencia (source.untraced). - Los secretos son nombres de variables de entorno en la configuración y valores solo en
.watcher/.env. Nunca leas valores de.envdurante el escaneo, nunca hagas commit de.watcher/.envo.watcher/data/. - Un canal funcional vale más que cinco configurados. Pruébalo con
watcher test-alert. ¿Sin credencial?watcher add channel file --id localy pruébalo: el archivo es un canal real, y una credencial faltante es un salto con nombre, nunca un pase. - Reporta lo que probaste — fuentes agregadas, salida de comandos, y cada salto con nombre. Si el agente no puede completar un escalón (credencial faltante, sin zona), di cuál y por qué en una línea.
Más detalle, cuando un paso lo necesita: watcher skills discover (catálogo completo de señales y la lista de verificación de silencio), watcher skills sources (cada tipo, banderas, ejemplos), watcher skills channels, watcher skills hosting, watcher skills cloudflare, watcher skills netdata, watcher skills operate (herramientas MCP y triaje de incidentes).