Blog
30 Sekunden bis zur Verbindung: Wie wir den `npx tacticremote`-Distributionspfad gebaut haben
Die technische Geschichte hinter `npx tacticremote`, von Bun-kompilierten Binaries und einem schlanken npm-Launcher bis zu QR-Auto-Save und schnellerem Erststart.
Weitere Produkt- und Engineering-Updates aus dieser Kategorie.
Kategorie öffnenDas Ziel war leicht zu formulieren und überraschend schwer umzusetzen: Ein neuer Nutzer soll einen Befehl ausführen, einen QR-Code scannen und innerhalb von 30 Sekunden ein Live-Terminal auf dem Telefon sehen.
In diesem Beitrag geht es darum, wie wir genau das erreicht haben.
Das Problem
Bevor npx tacticremote existierte, war der Serverstart je nach Plattform ein mehrstufiger Prozess:
- macOS: DMG herunterladen, einhängen, die App nach Applications verschieben, starten und mit Gatekeeper umgehen.
- Linux / CI: Das Repository klonen,
npm installausführen und dann herausfinden, wie man den Server in dieser Umgebung zuverlässig startet und am Laufen hält.
Für Entwickler auf entfernten Linux-VMs oder in Docker-Containern war das wirklich mühsam. Der Repo-Clone-Pfad setzte git, Node.js und eine funktionierende npm-Umgebung mit Zugriff auf das Registry voraus. Es gab keinen einzelnen Befehl, den man jemandem einfach hinschreiben konnte mit "führ das aus".
Wir wollten: npx tacticremote → scannen → fertig.
Warum nicht einfach npm install?
Das Serverpaket hat zwar eine kleine Runtime-Abhängigkeitsmenge, aber npm install erzeugt trotzdem Reibung. Aktuell gehören dazu ws, bonjour-service und qrcode-terminal.
Die npm-Auflösung kostet Zeit, bevor der Server überhaupt etwas tut. In eingeschränkten Umgebungen wie Unternehmensnetzen oder containerisiertem CI kann sie außerdem an Proxy-Einstellungen, Registry-Mirrors oder Berechtigungen scheitern.
Wichtiger noch: Der CLI-Pfad sollte sich leicht anfühlen. Würde das veröffentlichte Paket die komplette Runtime-Baumstruktur mitbringen, wäre der erste Eindruck weiterhin: erst JavaScript herunterladen, dann den Server starten. Genau das wollten wir vermeiden.
Also musste die npm-Schicht so dünn wie möglich bleiben.
Der Bun-Build
Wir kompilieren den Server mit Bun zu einem einzigen, selbst enthaltenen Binary. Das Ergebnis ist eine ausführbare Datei mit rund 20 MB vor der gzip-Komprimierung, die enthält:
- die Bun-Runtime
- alle Runtime-Abhängigkeiten
- den kompletten Server-Quellcode
Kein node_modules. Keine separate Bun-Installation. Keine Auflösung von Abhängigkeiten beim Start. Binary holen, ausführen, fertig.
GitHub Actions (.github/workflows/build-cli.yml) baut bei jedem Release vier Ziele:
bun-darwin-arm64- Apple Silicon Macbun-darwin-x64- Intel Macbun-linux-x64- x86_64 Linuxbun-linux-arm64- ARM Linux (Raspberry Pi, AWS Graviton usw.)
Jedes Binary wird als gzip-Asset in einen GitHub Release hochgeladen.
Der dünne Wrapper
Das auf npmjs.com veröffentlichte Paket ist nicht der Server selbst. Es ist ein 124-Zeilen-Launcher-Skript unter bin/tacticremote.js. Es macht im Wesentlichen Folgendes:
- Ermittelt das aktuelle Betriebssystem und die CPU-Architektur über
process.platformundprocess.arch - Löst den passenden Binary-Namen für diese Plattform auf
- Prüft
~/.tacticremote/binauf ein gecachtes Binary der aktuellen Version - Falls kein Cache vorhanden ist: lädt ein gzip-komprimiertes Binary aus GitHub Releases, entpackt es und schreibt es mit Ausführungsrechten auf die Platte
- Startet das Binary, übernimmt
stdiound reicht Signale wieSIGINTundSIGTERMdurch
Der erste Start lädt das passende Release-Asset und speichert es lokal im Cache. Danach wird bei derselben npm-Paketversion das Binary wiederverwendet.
Dieses Muster ist nicht neu. esbuild hat es populär gemacht, turbo nutzt es, prisma macht Ähnliches. Die Idee ist simpel: npm ist ein Distributionskanal, kein Build-System. Man liefert einen Downloader aus, nicht einen Dependency-Baum.
Die package.json-Aufteilung
Wir pflegen zwei Paketmanifeste nebeneinander:
package.json- die Entwicklungsvariante. Enthält alle devDependencies (Jest, ESLint, Prettier), Test- und Lint-Skripte sowie die vollständige Dependency-Liste für lokale Entwicklung.package.npm.json- das schlanke Publish-Manifest. Es zeigt dasbin-Feld auftacticremote.jsund lässt den Runtime-Dependency-Baum weg.
Diese Aufteilung hält Entwicklungs- und Veröffentlichungsworkflow sauber voneinander getrennt. Sie bringt aber auch eine klare Wartungslast mit sich: Metadaten, die sich überschneiden, müssen synchron bleiben.
Dafür fehlt im Repo derzeit noch ein Publish-Check, der diese Übergabe garantiert. CI-Automatisierung ist deshalb der naheliegende nächste Schritt.
Der 30-Sekunden-Flow
Nachdem das Distributionsproblem gelöst war, haben wir jeden Schritt des Verbindungsflusses auf Reibung geprüft.
Sekunden 0-5. Der Nutzer führt npx tacticremote im Terminal aus. Beim ersten Lauf erkennt der Launcher, dass kein Binary im Cache liegt, und lädt es herunter. Bei späteren Läufen dauert die Cache-Prüfung nur Millisekunden.
Sekunden 5-10. Das Server-Binary startet. Es erkennt die lokale IP-Adresse des Rechners, erzeugt einen zufälligen API-Schlüssel und druckt direkt im Terminal einen QR-Code mit Unicode-Blockzeichen. Kein Browser, keine Web-UI.
Sekunden 10-15. Der Nutzer öffnet Tactic Remote auf dem Telefon. Der Startbildschirm zeigt eine Karte "Computer verbinden" mit dem Befehl npx tacticremote und einer Schaltfläche "QR scannen". Ein Tipp öffnet die Kamera.
Sekunden 15-25. Der Nutzer scannt den QR-Code im Terminal. Der QR-Code enthält ein JSON-Payload mit IP-Adresse, Port und API-Schlüssel des Servers. Die App dekodiert das Payload und speichert die Serverkonfiguration automatisch - kein "Server hinzufügen"-Bildschirm, keine manuelle IP-Eingabe, kein Copy-Paste des API-Schlüssels.
Sekunden 25-30. Der WebSocket-Handshake ist abgeschlossen. Die Terminalausgabe beginnt auf das Telefon zu streamen. Die Verbindungsanzeige wird grün.
Der QR-Auto-Save-Schritt hat die meiste Reibung entfernt. Früher musste man: "Server hinzufügen" tippen, IP-Adresse eingeben, Port eingeben, API-Schlüssel eingeben, speichern, verbinden. Fünf Taps und drei manuelle Eingaben, und jeder Schritt konnte schiefgehen. Der QR-Scan reduziert das auf eine einzige Geste.
Was wir anders machen würden
Ein paar Dinge sind noch suboptimal und bisher nicht behoben:
Noch keine Signaturprüfung der Artefakte. Der Launcher lädt über HTTPS herunter, prüft das Binary aber noch nicht gegen eine veröffentlichte Signatur oder Checksumme. Das ist die wichtigste verbleibende Integritätslücke.
Der Manifest-Übergang braucht Automatisierung. Wir halten ein Entwicklungs- und ein Publish-Manifest getrennt, brauchen aber noch eine strengere Publish-Zeit-Sicherung, die deren Gleichlauf garantiert.
Kein Fallback-Mirror. Wenn GitHub Releases nicht verfügbar ist, hat der Launcher keine zweite Quelle. Ein zusätzlicher Mirror würde den Downloadpfad robuster machen.
Das größere Bild
Dieser Distributionspfad folgt einem Grundsatz: Nutzer sollen nicht über Infrastruktur nachdenken müssen. Sie müssen nicht wissen, was Bun ist, welche CPU-Architektur ihre Linux-Maschine hat oder wie sie einen API-Schlüssel manuell kopieren. Der Befehl startet den Ablauf, der Launcher holt das richtige Binary, und der QR-Code trägt die Verbindungsdaten.
Die Architektur des Servers selbst - sein Single-Dependency-Design und sein Sicherheitsmodell - wird im Begleitbeitrag Eine Abhängigkeit: Einen Produktionsserver nur mit dem ws-Paket bauen beschrieben. Dieser Beitrag handelt davon, wie dieser Server schnell zu den Nutzern kommt.
Wenn du den 30-Sekunden-Flow noch nicht ausprobiert hast, geh auf /download und teste ihn.
Zur Server-Architektur siehe Eine Abhängigkeit: Einen Produktionsserver nur mit dem ws-Paket bauen. Für die Einrichtung siehe den Schnellstart.
Tactic Remote ausprobieren
Coding-Agenten vom Smartphone aus steuern
Verbinde dich mit Claude Code, Codex und anderen Agenten auf deinem Mac-, Windows- oder Linux-Rechner. Prüfe den Fortschritt und sende die nächste Anweisung vom iPhone oder iPad.