Harbor 1.0 : tous les serveurs de développement de votre Mac, à un raccourci près
J'ai conçu et livré un utilitaire natif macOS, logé dans la barre de menus, qui affiche chaque serveur de développement à l'écoute sur localhost — avec vitals en temps réel, actions en un clic et une vraie release notarisée. Voici ce qu'il fait et ce qu'il a fallu pour le livrer.
Tout développeur connaît ce moment : Error: listen EADDRINUSE: address already in use :::3000.
Quelque chose squatte le port. Peut-être le serveur Vite d'un projet fermé il y a deux jours. Peut-être un process Next.js qui a survécu à son terminal. Vous collez la même incantation que d'habitude — lsof -ti :3000 | xargs kill — en espérant qu'elle tue la bonne chose.
Harbor 1.0 existe pour que je ne fasse plus jamais ça.
Soixante secondes de Harbor — une démo animée réalisée avec Remotion, chorégraphiant de véritables captures du panneau depuis la release candidate.
Ce qu'est Harbor
Harbor est un utilitaire natif macOS, logé dans la barre de menus, qui affiche chaque serveur de développement à l'écoute sur localhost — et vous laisse agir dessus. Cliquez sur la bitte d'amarrage dans votre barre de menus (ou faites ⌘⌥P depuis n'importe où) et vous obtenez un panneau en temps réel : chaque port à l'écoute, de quel framework il s'agit, depuis combien de temps il tourne, ce qu'il vous coûte en CPU et en mémoire, et un badge d'énergie quand quelque chose grignote discrètement votre batterie.

Depuis ce panneau, tout est à une action près :
- Kill un serveur — SIGTERM d'abord, escalade vers SIGKILL seulement si nécessaire, avec revalidation d'identité pour qu'un PID réutilisé ne fasse jamais tomber un process innocent.
- Kill All — avec une confirmation détaillée qui nomme exactement ce qui va être terminé. Pas de balayage à l'aveugle.
- Open in browser — schéma auto-détecté, loopback IPv6 géré.
- Jump to terminal — Harbor détermine quel terminal possède le process et l'active. Fonctionne avec Terminal, iTerm2, Warp, Ghostty, WezTerm, kitty, Alacritty et les terminaux de VS Code/Cursor.
L'ensemble est pensé clavier d'abord : flèches, filtrage au fil de la frappe, Return pour agir, Esc pour fermer. Il a l'apparence et le comportement de l'utilitaire système qu'il est — clair et sombre, VoiceOver, Dynamic Type, animations réduites.

Voir plus qu'un numéro de port
Un numéro de port seul ne répond pas à la vraie question, qui est : c'est quoi, ça, et est-ce que je peux le killer ?
Harbor détecte le framework derrière chaque listener — Vite, Next.js, Rails, Flask, Express, et d'autres — et quand un serveur se cache derrière un worker enfant (le serveur de développement de Next fait ça dans certaines configurations, comme sur les captures ci-dessus), il retombe honnêtement sur le badge du runtime au lieu de deviner. Les daemons système ne se font pas passer pour des serveurs de développement non plus : un resolver de ports bien connus nomme des choses comme AirPlay ou le Partage d'écran, si bien que la vue « Show all » se lit comme un inventaire plutôt que comme un mystère.
Chaque ligne se déploie en un inspecteur inline : ligne de commande complète, répertoire de travail, l'application propriétaire, la portée de binding (est-ce à l'écoute sur loopback, ou exposé à votre réseau ?), et des sparklines CPU/mémoire glissantes dessinées comme des barres discrètes ancrées à zéro — pour qu'un process au repos ait l'air au repos, et pas d'un sismographe.

L'exposition est un signal de premier rang. Tout ce qui est bindé au-delà de loopback est signalé dans l'en-tête du panneau — ce badge de compteur d'exposition, c'est la différence entre un serveur de développement et un service réseau accidentel, mis en évidence avant même que vous alliez le chercher.
Pour les sessions de debugging où le port est pris mais où rien n'écoute, une vue optionnelle fait remonter les sockets UDP et les états TCP non-LISTEN bloqués (CLOSE_WAIT, FIN_WAIT) dans une section volontairement discrète. Désactivée par défaut, parce que la vue par défaut doit rester propre.
Local-first, et vérifiable
Harbor ne téléphone jamais à la maison. Pas de télémétrie, pas de compte, aucun appel réseau à part ceux que vous demandez (ouvrir votre serveur dans un navigateur, vérifier les mises à jour). L'app est signée avec un Developer ID, en hardened runtime, notarisée par Apple, et livrée en DMG universel avec un SHA256 publié. Les mises à jour arrivent via Sparkle avec des appcasts signés en EdDSA.
Rien de tout ça n'est du langage marketing — c'est la checklist qui a demandé du vrai travail, ce qui m'amène à la partie de ce post que je voulais réellement écrire.
L'histoire de la construction
Harbor est en Swift et SwiftUI natifs — cinq modules SwiftPM avec un graphe de dépendances strictement acyclique : modèles purs tout en bas, énumération et process intelligence dans une couche de services, design system et UI au-dessus, une seule composition root au sommet. Les protocoles vivent dans le core ; les implémentations vivent dans les services ; l'UI dépend d'abstractions. C'est l'architecture ennuyeuse, et elle est ennuyeuse à dessein : chaque couche est unit-testable, et la suite de tests tourne à plus de 650 tests.
L'essentiel de l'implémentation a été assisté par agents — une équipe d'agents de code travaillant en parallèle dans des git worktrees séparés, chaque changement passant par la même épreuve : build complet, suite de tests complète, lint, format, une review adversariale par un agent distinct, et une décision de merge humaine. La leçon intéressante n'a pas été que les agents écrivent du code. C'est que la qualité vit dans la review.
Prenez une fonctionnalité : la vue optionnelle pour les sockets UDP et non-listening. L'agent d'implémentation a produit une branche propre — tests au vert, résolution de conflits soignée sur quatorze commits de dérive, compatibilité octet par octet quand le toggle était désactivé. Ça avait l'air terminé. Puis la passe de review, menée par un agent distinct sans aucun intérêt dans le travail, a trouvé quatre choses : le toggle des réglages était câblé à un chemin de code que l'app livrée n'appelait jamais, donc toute la fonctionnalité était inatteignable à l'exécution ; Kill All avait silencieusement élargi son rayon d'action pour inclure les nouvelles lignes subordonnées ; le parser de fallback lsof lisait mal les sockets connectés et les aurait signalés comme exposés au réseau ; et le churn TCP ordinaire aurait bloqué la boucle de refresh adaptative à sa fréquence maximale pour toujours. Quatre vrais bugs, dont un destructeur, dans une branche que chaque garde-fou automatisé avait bénie.
Ce schéma s'est répété jusqu'en bas. Celui qui écrit n'est pas celui qui trouve le problème — c'est le sceptique. Structurer le travail pour qu'un regard neuf attaque chaque changement avant qu'un humain ne décide, c'est la chose à plus fort levier de tout le workflow, et c'est exactement aussi vrai pour les agents que ça l'a toujours été pour les humains.
Deux bugs méritent leur propre paragraphe, parce qu'ils sont du genre qu'on ne rencontre que lorsqu'on livre pour de vrai.
Premièrement : le pipeline de release signait l'app à l'intérieur du DMG mais ne signait jamais le conteneur DMG lui-même. Chaque garde-fou automatisé était au vert jusqu'à ce que le premier run entièrement authentifié — vrai certificat, vraie notarisation — se heurte au contrôle de signature externe de Gatekeeper et échoue. Impossible de corriger après coup, en plus : re-signer un DMG stapled invalide le ticket de notarisation. Le pipeline a été reconstruit pour signer avant soumission. Aucun dry run ne l'aurait trouvé ; seul le vrai l'a fait.
Deuxièmement : pendant la QA de la release candidate — moi, cliquant partout comme un utilisateur — le panneau a gelé net à 97 % de CPU. Le coupable était un livelock de layout SwiftUI : des lazy stacks qui se re-mesuraient contre le rect visible d'une scroll view à l'intérieur d'un popover auto-dimensionné, une boucle de feedback qui ne convergeait jamais dès qu'un inspecteur de ligne se déployait sous le curseur. Le fix était presque gênant : le panneau affiche quelques dizaines de lignes, donc la laziness n'apportait rien. Stacks simples, géométrie stable, bug disparu.
Livrer est une discipline différente de celle de construire. Les cinq derniers pour cent — notarisation, stapling, Gatekeeper, signature des mises à jour, checksums, preuves de release — c'est là que les side projects s'endorment généralement. Les traverser, c'est l'essentiel de ce que « 1.0 » veut dire.
La bitte d'amarrage
La marque de Harbor est une bitte d'amarrage avec une corde — l'objet qu'un port utilise réellement pour maintenir les navires en place en toute sécurité, comme Harbor maintient vos serveurs de développement à vue. Deux arcs de verre marin suggèrent un port vivant, à l'écoute. Ce n'est délibérément pas une ancre, et délibérément pas un conteneur.
![]()
Récupérer Harbor
Harbor 1.0 est gratuit, tourne sur macOS 14+, et est universel (Apple silicon + Intel).
- Télécharger : Harbor.dmg depuis la dernière release GitHub — checksum SHA256 publié à côté.
- Landing page : ftchvs.github.io/harbor
- Changelog : notes de version complètes
Si un port est squatté en ce moment — et il y en a un, il y en a toujours un — Harbor vous dira ce que c'est, et y mettra fin en un raccourci.