Aviso: Este contenido fue traducido automáticamente. Enviar feedback

Harbor 1.0: Cada servidor de desarrollo de tu Mac, a un atajo de distancia

7 min de lectura

macos, swift, dev-tools, harbor, agents, shipping

Construí y lancé una utilidad nativa de la barra de menús de macOS que muestra cada servidor de desarrollo escuchando en localhost — con métricas en vivo, acciones de un clic y un release notarizado de verdad. Esto es lo que hace y lo que costó lanzarlo.


Todo desarrollador conoce ese momento: Error: listen EADDRINUSE: address already in use :::3000.

Algo está ocupando el puerto. Quizás es el servidor de Vite de un proyecto que cerraste hace dos días. Quizás es un proceso de Next.js que sobrevivió a su terminal. Pegas el mismo conjuro de siempre — lsof -ti :3000 | xargs kill — y esperas que mate lo correcto.

Harbor 1.0 existe para que nunca vuelva a hacer eso.

Sesenta segundos de Harbor — una demo en movimiento hecha con Remotion, que coreografía imágenes reales del panel a partir del release candidate.

Qué es Harbor

Harbor es una utilidad nativa de la barra de menús de macOS que muestra cada servidor de desarrollo escuchando en localhost — y te deja actuar sobre él. Haz clic en el bolardo de tu barra de menús (o pulsa ⌘⌥P desde donde estés) y obtienes un panel en vivo: cada puerto en escucha, de qué framework se trata, cuánto tiempo lleva corriendo, cuánto te está costando en CPU y memoria, y una insignia de energía cuando algo consume tu batería en silencio.

El panel de la barra de menús de Harbor mostrando servidores de desarrollo locales con insignias de framework, puertos, tiempo de actividad y métricas en modo claro

Desde ese panel, todo está a una acción de distancia:

  • Kill de un servidor — SIGTERM primero, escalando a SIGKILL solo si hace falta, con revalidación de identidad para que un PID reutilizado nunca tumbe a un proceso inocente.
  • Kill All — con una confirmación enumerada que nombra exactamente lo que va a terminar. Sin barridos a ciegas.
  • Abrir en el navegador — esquema autodetectado, loopback IPv6 gestionado.
  • Saltar a la terminal — Harbor resuelve qué terminal es dueña del proceso y la activa. Funciona en Terminal, iTerm2, Warp, Ghostty, WezTerm, kitty, Alacritty y las terminales de VS Code/Cursor.

Todo está pensado primero para el teclado: flechas, filtrar al escribir, Return para actuar, Esc para descartar. Se ve y se comporta como la utilidad de sistema que es — claro y oscuro, VoiceOver, Dynamic Type, movimiento reducido.

El mismo panel de Harbor en modo oscuro

Ver más que un número de puerto

Un número de puerto por sí solo no responde a la pregunta real, que es: ¿qué es esto y puedo matarlo?

Harbor detecta el framework detrás de cada listener — Vite, Next.js, Rails, Flask, Express y más — y cuando un servidor se esconde tras un worker hijo (el servidor de desarrollo de Next hace esto en algunas configuraciones, como en las capturas de arriba), recurre honestamente a la insignia de runtime en lugar de adivinar. Los daemons de sistema tampoco se hacen pasar por servidores de desarrollo: un resolver de puertos bien conocidos nombra cosas como AirPlay y Screen Sharing, así que la vista "Show all" se lee como un inventario en lugar de un misterio.

Cada fila se expande en un inspector en línea: la línea de comandos completa, el directorio de trabajo, la app dueña, el alcance de bind (¿esto escucha en loopback o está expuesto a tu red?) y sparklines rodantes de CPU/memoria dibujadas como barras discretas ancladas en cero — para que un proceso inactivo se vea inactivo y no como un sismógrafo.

El inspector de detalle de Harbor expandido en una fila, mostrando la línea de comandos, el directorio de trabajo, el alcance de bind y las sparklines de barras de CPU/memoria

La exposición es una señal de primera clase. Cualquier cosa vinculada más allá de loopback queda marcada en el encabezado del panel — esa insignia de conteo de expuestos es la diferencia entre un servidor de desarrollo y un servicio de red accidental, mostrada antes de que vayas a buscarla.

Para esas sesiones de debugging en las que el puerto está tomado pero nada está escuchando, una vista opcional saca a la luz sockets UDP y estados TCP no-LISTEN atascados (CLOSE_WAIT, FIN_WAIT) en una sección atenuada. Desactivada por defecto, porque la vista predeterminada debe mantenerse limpia.

Local-first, y verificable

Harbor nunca llama a casa. No hay telemetría, ni cuenta, ni llamadas de red salvo las que tú pides (abrir tu servidor en un navegador, buscar actualizaciones). La app está firmada con Developer ID, tiene hardened-runtime, está notarizada por Apple y se distribuye como un DMG universal con un SHA256 publicado. Las actualizaciones llegan a través de Sparkle con appcasts firmados con EdDSA.

Nada de eso es lenguaje de marketing — es la checklist que costó trabajo real, lo que me lleva a la parte de este post que de verdad quería escribir.

La historia detrás del build

Harbor es Swift y SwiftUI nativos — cinco módulos de SwiftPM con un grafo de dependencias acíclico y estricto: modelos puros en la base, enumeración e inteligencia de procesos en una capa de servicios, design system y UI por encima, y un composition root en la cima. Los protocolos viven en el core; las implementaciones viven en los servicios; la UI depende de abstracciones. Es la arquitectura aburrida, y es aburrida a propósito: cada capa es unit-testable, y la test suite corre con más de 650 tests.

La mayor parte de la implementación fue asistida por agentes — un equipo de coding agents trabajando en paralelo en git worktrees, con cada cambio aterrizando por el mismo circuito: build completo, test suite completa, lint, format, una revisión adversarial por un agente aparte y una decisión de merge humana. La lección interesante no fue que los agentes escriben código. Es que la calidad vive en la revisión.

Piensa en una función: la vista opcional para sockets UDP y no escuchando. El agente de implementación produjo una rama limpia — tests en verde, resolución cuidadosa de conflictos a lo largo de catorce commits de drift, compatibilidad byte a byte cuando el toggle estaba apagado. Parecía terminada. Entonces la pasada de revisión, hecha por un agente aparte sin nada en juego en el trabajo, encontró cuatro cosas: el toggle de ajustes estaba conectado a una ruta de código que la app publicada nunca llamaba, así que toda la función era inalcanzable en runtime; Kill All había ampliado en silencio su radio de impacto para incluir las nuevas filas subordinadas; el parser de fallback de lsof leía mal los sockets conectados y los habría marcado como expuestos a la red; y la rotación normal de TCP habría fijado el bucle de refresh adaptativo en la frecuencia máxima para siempre. Cuatro bugs reales, uno de ellos destructivo, en una rama que todas las compuertas automáticas habían bendecido.

Ese patrón se repitió hasta el final. Quien escribe no es quien encuentra el problema — el escéptico sí. Estructurar el trabajo para que un par de ojos frescos ataque cada cambio antes de que un humano decida es lo de mayor palanca en todo el workflow, y es exactamente igual de cierto para los agentes que lo ha sido siempre para las personas.

Dos bugs merecen sus propias frases, porque son de esos que solo conoces cuando lanzas de verdad.

Primero: el pipeline de release firmaba la app dentro del DMG pero nunca firmaba el propio contenedor DMG. Cada compuerta automática estaba en verde hasta que la primera corrida plenamente credencializada — certificado real, notarización real — chocó con la verificación de firma externa de Gatekeeper y falló. Y no lo puedes arreglar después: re-firmar un DMG con el ticket stapled invalida la notarización. El pipeline se reconstruyó para firmar antes de enviarlo. Ningún dry run habría encontrado esto; solo lo hizo la corrida real.

Segundo: durante el QA del release candidate — yo, haciendo clics por todos lados como un usuario — el panel se congeló en seco al 97% de CPU. El culpable fue un livelock de layout de SwiftUI: lazy stacks remidiéndose contra el rect visible de un scroll view dentro de un popover autodimensionado, un bucle de feedback que nunca convergía una vez que el inspector de una fila se expandía bajo el cursor. El arreglo fue casi vergonzoso: el panel muestra decenas de filas, así que la laziness no compraba nada. Stacks planos, geometría estable, bug fuera.

Lanzar es una disciplina distinta a construir. El último cinco por ciento — notarización, stapling, Gatekeeper, firma de actualizaciones, checksums, evidencia de release — es donde los proyectos paralelos suelen quedarse dormidos. Atravesarlo es casi todo lo que significa "1.0".

El bolardo

La marca de Harbor es un bolardo de amarre con una cuerda — lo que un puerto de verdad usa para sostener embarcaciones con seguridad en su sitio, del mismo modo en que Harbor mantiene tus servidores de desarrollo a la vista. Dos arcos de vidrio de mar sugieren un puerto vivo, en escucha. Deliberadamente no es un ancla y deliberadamente no es un contenedor.

El ícono de la app Harbor: un bolardo de amarre blanco con una cuerda dorada sobre un cuadrado redondeado en azul de mar profundo

Consigue Harbor

Harbor 1.0 es gratis, corre en macOS 14+ y es universal (Apple silicon + Intel).

Si hay un puerto ocupado ahora mismo — y lo hay, siempre lo hay — Harbor te dirá qué es y lo terminará en un solo atajo.