Der letzte CLI: Der Wiederaufbau der Basis 51, gegen die meine Werkzeuge gebaut wurden
Jedes Modell griff nach einem anderen Satz Bibliotheken, also baute ich eine Basis und machte jedes Projekt upstream in sie. Dann fragte ich Claude Fable nach der CLI Menschheit, die noch in einem Jahrzehnt benutzen könnte, und Opus 5 und ich bauten, was zurückkam — 46 Klauseln, 92 Tests, null Abhängigkeiten.
Developed by Robert E. Beckner III (Merlin) | rbeckner.com
Ich habe 51 Kommandozeilenwerkzeuge auf diesem Rechner. Ich hatte nicht für diese Zahl geplant. Es geschah, weil ein CLI die kürzeste Entfernung zwischen einer Idee und etwas ist, das ich tatsächlich ausführen kann, und weil ich in den letzten Jahren Hilfe beim Schreiben bekommen habe, schneller als ich allein hätte können.
Diese Hilfe kam mit einer Gewohnheit, die ich früh bemerkte, als GPT-3.5 und die ersten Claude-Modelle die waren, mit denen ich arbeitete. Fragen Sie 3 verschiedene Modelle, um ein CLI zu strukturieren, und Sie erhalten 3 unterschiedliche Meinungen darüber, was ein CLI ist. Einer greift nach Commander. Einer greift nach Inquirer für die Prompting. Einer greift nach Chalk, weil die Ausgabe farbig sein sollte. Jede Antwort ist verteidigbar. Zusammen sind sie eine Steuer, weil ich jetzt 3 Codebasen besitze, die sich über Argumentparsing, darüber, wie ein Fehler aussieht, und darüber, welche dieser Bibliotheken ich jetzt verantwortlich bin zu überwachen, uneinig sind.
Jedes Modell griff auf eine andere Bibliotheksmenge zu, und ich war derjenige, der mit allen leben musste#
Die Steuer ist nicht die Bibliotheken. Es ist, dass Verbesserungen nicht weiterreisen.
Wenn 3 CLIs darüber streiten, wie ein Befehl einen Fehler meldet, ist eine Korrektur in einem eine Korrektur in einem. Es gibt nichts, worauf man es upstreamen kann. Die Arbeit akkumuliert sich nicht, und nach dem zehnten Tool baust du keinen Hebel auf, du pflegst ein Portfolio von beinahe Fehlern.
Ich hatte bereits darüber geschrieben, das Gegenteil davon zu wollen. Das ganze Argument in How to Turn AI Gains Into Compounding Infrastructure ist, dass ein Gewinn dauerhaft wird, wenn jedes abhängige Projekt ihn übernimmt. Eine gemeinsame Fähigkeitsoberfläche. Eine Promotionsregel. Ein Ort, an dem eine Verbesserung landet und sich verbreitet.
Ich hatte diese Schicht für AI-Fähigkeit, für Workflow, für Betrieb gebaut. Ich hatte sie nicht für das gebaut, was ich tatsächlich am häufigsten mache.
Also baute ich eine Basis und ließ jedes CLI Projekt seine Verbesserungen in sie upstreamen#
Die Regel war einfach und ich musste sie durchsetzen: wenn ein CLI in meinem Bestand etwas Besseres benötigte — einen saubereren Weg, Dienste zu registrieren, einen besseren Fehlerpfad, einen Testhelfer, der eine Suite lesbar machte — blieb diese Verbesserung nicht im Projekt. Sie ging in die Basis, und die Basis ging zu den anderen.
Das ist das ganze Design. Die Basis ist absichtlich klein. Sie hat keine Meinung darüber, was dein Tool tut. Sie hat eine starke Meinung darüber, was ein Befehl ist: etwas, das Argumente nimmt, Arbeit leistet, meldet, was passiert ist, und dann geht.
Der Ordner wurde am 6. Juli 2025 erstellt, und 2 meiner Tools waren von Version 1.0 abhängig.0 am selben Tag. Das ist die Aussage: es wurde nicht spekulativ gebaut und dann übernommen. Es wurde aus Arbeit extrahiert, die bereits existierte, an dem Punkt, an dem das Kopieren derselben Grundstruktur zwischen Projekten nicht mehr vernünftig war.
Es verbreitete sich schnell, weil Verbreitung die ganze Idee war. 8 Repositories waren innerhalb von 25 Tagen darauf. 10 innerhalb von 11 Wochen.
Repositories Adopting the Base in 2025Chart data
repositories
Jul 6
2
Jul 8
4
Jul 17
5
Jul 23
7
Jul 30
8
Sep 20
10
Git kam später als all das. Das Repository wurde am 12. November 2025 initialisiert, 4 Monate später, und am Tag danach veröffentlicht — deshalb stimmen die Versionsgeschichte und die tatsächliche Geschichte nicht überein, und deshalb habe ich das Dateisystem überprüft, anstatt dem Commit-Log zu vertrauen, als ich mich hinsetzte, dies zu schreiben.
Diese 10 Tools führen Cloudflare-Administration durch. Lokale DNS und nginx-Verwaltung. Bereitstellung gegen Coolify. Browser-Automatisierung. Kostenberichterstattung über Modellanbieter. Die meisten sind privat, weshalb ich sie nach ihrer Funktion und nicht nach Namen beschreibe. Die öffentlichen sind aia, die mehrere Modelle parallel konsultieren, und die Basis selbst. vssh, mein geschütztes Remote-Ausführungstool, ist ebenfalls öffentlich und entstand aus demselben Instinkt — die Operatoroberfläche einmal richtig bauen und nicht weiter neu aufbauen.
Die Dividenden waren real und langweilig, was die richtige Form für Infrastrukturdividenden ist. Eine Verstärkung in einem Tool zeigte sich in allen von ihnen. Als ich feststellte, dass ein Befehl eine rote Fehlermeldung ausgeben und trotzdem 0 beenden konnte — dem Menschen mitteilen, dass es fehlgeschlagen ist, und der Shell mitteilen, dass es funktioniert hat — wurde die Lösung nicht in die 16 Stellen in einem Tool implementiert, wo es passiert war. Sie ging in die Basis, und jedes Tool erwarb sie.
Nach 13 Monaten wollte ich sie neu aufgebaut haben, nicht gepatcht#
Bis August 2026 funktionierte die Basis und ich wollte sie trotzdem entfernen.
Nicht weil sie kaputt war. Weil sie sich angesammelt hatte. Weil die Exit-Code-Regel, auf die ich am stolzesten war, nachträglich angepasst wurde, anstatt von Anfang an entworfen zu werden. Weil die Welt, für die sie geschrieben wurde, sich darunter verschoben hatte: die meisten Aufrufe meiner CLIs werden nicht mehr von mir getippt. Sie werden von Agenten ausgegeben, die stdout, stderr und $? als ihre einzigen Sinne lesen.
Also anstatt zu patchen, habe ich die Bedingungen anders gesetzt. Ich gab Claude Fable eine einzige Anweisung, und ich machte sie absichtlich groß:
Wenn dies das letzte CLI Framework wäre, das die Menschheit gebaut hat – das noch in Dienst steht
in einem Jahrzehnt – hast du jetzt die Chance, es so zu machen.
Gestalte es von dort aus. Ich erwartete kein Dokument zurück. Ich erwartete einen Plan.
Fable kam mit einem Vertrag zurück, und die Einschränkung war, dass die Versprechen wenige sein mussten#
Was kam, war keine Funktionsliste. Es war strukturiert als Vertrag, geteilt in der Mitte durch eine harte Wand.
Eine Hälfte war ein Vertrag: Was jeder CLI auf dieser Basis baut, garantiert jedem Beobachter, geschrieben als nummerierte Klauseln in RFC-2119 Sprache — MUSS, MUSS NICHT, SOLLTE, MÖGLICH. Zwölf Familien davon. Beendigungscodes. Stream-Disziplin. Maschinenoutput. Selbstbeschreibung. Grammatik. Umgebung. Kündigung. Determinismus. Leistungsbudgets. Kompatibilität.
Die andere Hälfte war die Autorierungsoberfläche, die wachsen durfte, und die nur existierte, um die Erfüllung des Vertrags zum Weg mit geringstem Widerstand zu machen.
Die Argumentation darunter war der Teil, den ich überzeugend fand. Ein Design, das ein Jahrzehnt halten soll, kann nicht auf Mode setzen, weil Mode das ist, was abläuft. Es kann nicht auf Cleverness setzen, weil Cleverness das ist, was man im Jahr nicht vorhersagen kann 8. Es kann nur auf die Schnittstellen setzen, die seit den 1970s nicht verändert wurden: Argumentvektoren, 3-Streams, ein 8-Bit-Exit-Code, Umgebungsvariablen. Und es stellte die einzig wirklich neue Tatsache fest — dass der Hauptleser dieser Schnittstellen jetzt eine Maschine ist, die keine Folgefrage stellen kann.
Die Klausel, die alles andere organisierte, war die, mit der es begann:
Ein Ergebnis, viele Darstellungen. Ein Befehl berechnet ein einzelnes Ergebnis. Der Exit
Code, der menschliche Text, das JSON-Dokument und die Streaming-Zeilen sind alle
Projektionen dieses einen Wertes. Sie können sich nicht widersprechen, weil
es nur eine Quelle gibt.
Das ist der Satz, auf dem die ganze Rekonstruktion basiert.
Diagram source
graph LR
A["execute() gibt
ein Wert zurück"] --> B["Abbruchcode"]
A --> C["gerenderter Text
stdout"]
A --> D["JSON Umschlag
--json"]
A --> E["NDJSON Datenstrom
--ndjson"]
F["logger.error()
ctx.emit()"] -.-> B
F -.-> G["Ereignisse
stderr"]
Opus 5 und ich fand, dass die Spezifikation richtig über die These war und falsch über 3 Dinge#
Hier wurde die Arbeit unser statt meiner.
Ich brachte die Spezifikation zu Opus 5 und wir bauten sie in einem Tag. Kein sauberer Tag. Die nützlichen Teile sind die Stellen, an denen das Dokument die Erbschaft traf und verlor.
Die Spezifikation wollte, dass ctx.args zu einem Datensatz benannter Argumente wird. Es ist das
bessere Design in Isolation. Es hätte auch jeden Befehl in jedem
von den 10 Werkzeugen, weil sie alle ctx.args als Array lesen. und setzten die typisierten Argumente auf ctx.namedArgs daneben.
Die Regel, die entschied. Die Regel, die entschied
Es war bereits im Vertrag geschrieben, eine Klausel darüber: breche niemals einen
Verbraucher übertrifft jeden anderen Wert im Repository, einschließlich des Vertrags.
eigenen Vollständigkeit.
Die Spezifikation wollte, dass eine Befehlsgruppe ohne Verb ein Nutzungsfehler ist. Beim Ausführen eines
übergeordneten Befehls ohne Unterbefehl würde 2 beendet werden. Verteidigbar, und es hätte
das Verhalten jedes Skripts geändert, das einen reinen Gruppierungsbefehl ausführt, um
seine Hilfe anzuzeigen. Wir haben weiterhin Hilfe ausgegeben und 0 beendet.
Die Spezifikation nahm Streaming und das einzelne JSON Dokument für dasselbe
Feature an. Das ist nicht der Fall. Ein Millionenelement-Streaming im konstanten Speicher ist der
Zweck eines und unmöglich im anderen, weil ein Aufrufer, der ein
einzelnes Dokument anforderte, verlangte, dass es ein einzelnes sei. Wir haben das Verhalten getrennt und notiert
welche Klausel welche regelt.
Wir haben auch Dinge gefunden, die die Spezifikation nicht gekannt haben konnte, weil sie nur vom Artefakt aus sichtbar waren. Eine Testdatei, die 0 Tests ausführte und Erfolg meldete, nachdem sie den Runner teilweise beendet hatte. Signalbehandlung, die bei Ctrl-C 0 beendet – ein unterbrochener Befehl, der meldete, dass er erfolgreich war. Zwei Ausgabehelfer, die ihre Zeilen mit einem wörtlichen Backslash-n verknüpften, sodass jede Tabelle in einer Zeile zurückkam. Ein Farbhelfer, der, sobald wir die Abhängigkeit ersetzt hatten, die eigene Typsignatur stillschweigend schärfte und Code brach, der keinen einzigen Charakter geändert hatte.
Das letzte ist es wert, sich damit zu beschäftigen. Es wurde von keinem Test erfasst, den wir geschrieben haben. Es tauchte in einer Verbraucher Typprüfung während der Migration auf, was der einzige Ort war, an dem es sein konnte.
Der Vertrag zählt nur, weil der Build fehlschlägt, wenn eine Klausel keinen Test hat#
Ein Versprechen, das nichts prüft, ist ein Kommentar.
Daher parst die Konformitätssuite die Vertragsdatei, findet jede Klausel, die das Wort MUST enthält, und lässt den Build fehlschlagen, wenn eine davon keinen registrierten Test hat. Du kannst diesem Projekt kein Versprechen hinzufügen, ohne die Sache, die es beweist, im selben Commit hinzuzufügen.
Conformance Tests by Contract FamilyChart data
Value
Grammar
20
Exit codes (truth)
12
Machine output
11
Self-description
10
Environment
8
Prompt safety
6
Streams
5
Cancellation
5
Determinism
5
46 normative Klauseln. 92 Tests, die ihnen zugeordnet sind. 184 Tests insgesamt.
Und keiner dieser Konformitätstests läuft gegen den Quellcode. Sie bauen das Paket mit seinem eigenen Build-Skript, führen npm pack aus, entpacken das Tarball, schreiben Fixture-CLI's, die den entpackten Einstiegspunkt importieren, und starten sie unter Node, Bun und Deno — prüfen den Exit-Status und die Bytes genau wie ein Shell sie sehen würde.
Diese Form war keine ästhetische Wahl. Dieses Paket wurde einmal mit einem 65 KB Stub ausgeliefert. Ein einzelnes "sideEffects": false-Flag ließ den Bundler den Router und das Exit-Code-Modul aus dem Artefakt entfernen, während ihre Namen im Exportlisten blieben. Der Build endete mit 0. Die Quell-Suite blieb die ganze Zeit grün. Nur das Artefakt war Beweis, und niemand schaute auf das Artefakt.
Beim Migrieren 7 Tools wurden 3 Gates entdeckt, von denen niemand wusste, dass sie existierten#
Wir migrierten 7 der 10 CLIs am selben Tag, und die Migration ist der Punkt, an dem das Design seine tatsächliche Note erhielt.
Die Dividende kam sofort an und kostete nichts: weil Befehle in der alten Version bereits Werte zurückgaben — das Framework nutzte sie nur, um einen Exit-Code abzuleiten, dann verworfen — wurde jeder dieser Rückgabewerte am Upgrade-Tag zu einem JSON Payload. 7 Tools erhielten maschinenlesbare Ausgabe, ohne dass ein einziger Befehl neu geschrieben werden musste.
Was wir nicht erwartet haben, war derselbe Defekt in 3 verschiedenen Tools, von denen keines über die anderen wusste. Jedes hatte ein Gate vor dem Router: eine handgepflegte Liste gültiger Befehlsnamen oder einen Startschritt, der vor allem andere Schritte Anmeldeinformationen verlangte. In jedem Fall antwortete der neue manifest Befehl — derjenige, der die gesamte Oberfläche des Tools in einem Aufruf beschreibt, sodass ein Agent es lernen kann, ohne den Quellcode zu lesen — mit "unbekannter Befehl" oder "fehlendes Token."
Einer von ihnen behielt eine zweite Kopie seiner Befehlsliste und einen handgeschriebenen Hilfeschirm, die beide von dem abgewichen waren, was das Tool tatsächlich tat. Das Löschen beider nahm seine Suite von 52 Durchläufen mit 3 Fehlern zu 57 Durchläufen mit 0. Das größte Tool in der Gruppe hat 364 Tests, und sie bestanden vor und nach dem Upgrade ohne Quelländerung.
Das Muster generalisierte sich gut genug, dass es zu einer schriftlichen Prozedur wurde, die im Paket selbst enthalten ist. Es ist 9 Schritte, und die 2 Schritte, die die Zeit verbrauchen, sind die 2 niemand erwartet.
Zero Dependencies ist die einzige Zahl, die keine Überwachung benötigt#
Die Basis hatte 2 Laufzeitabhängigkeiten. Sie hat jetzt keine.
Das war teilweise ästhetisch und größtenteils arithmetisch. Am 8. September 2025 hat ein Angreifer das npm-Konto von Josh Junon, Betreuer einiger der am meisten abhängigen Pakete in JavaScript, gehackt, indem er eine gefälschte Domain und einen Live-Einmalcode verwendete. 18 Pakete wurden mit bösartigen Versionen veröffentlicht, darunter chalk und debug — Pakete, die zusammen etwas auf der Ordnung von 2,6 Milliarden Downloads pro Woche haben. Der Payload war ein Crypto-Clipper. Betreuer haben es entdeckt und innerhalb von ungefähr 2 Stunden zurückgesetzt, und die kompromittierten Versionen wurden immer noch etwa 2.6 Millionen Mal in diesem Zeitraum heruntergeladen.
Chalk ist eine der 3 Bibliotheken, zu denen die Modelle immer wieder griffen, als ich sie nach einem CLI fragte.
Die Basis war nicht betroffen — sie war nie auf Chalk angewiesen — und ich möchte präzise statt dramatisch sein, weil sie 2 Monate nach dem Vorfall erstellt wurde. Die Relevanz liegt nicht darin, dass wir etwas vermieden haben. Es ist, dass der Vorfall die Klasse des Risikos genau beschreibt: jede Abhängigkeit ist ein Jahrzehnt der Release-Entscheidungen anderer, und du vertraust einem Konto, das du nicht kontrollierst. Die Farbverarbeitung, die eine Abhängigkeit ersetzte, betrifft etwa 60 Zeilen. Das Prompting, das die andere ersetzte, betrifft etwa 120. Zero ist die einzige Zahl, die keine Überwachung benötigt.
Die ausgelieferte Version ist 85 KB, unminifiziert, ohne Laufzeitabhängigkeiten, läuft auf Node, Bun und Deno. Jeder darauf aufbauende Befehl erhält, ohne pro Befehl Code:
Garantie
Was das in der Praxis bedeutet
Ehrliche Exit-Codes
Ein Fehler, der einem Menschen gemeldet wird, wird an die Shell gemeldet
--json und --ndjson
Der Wert, den dein Befehl zurückgibt, in einer Form, die eine Maschine parsen kann
manifest
Das gesamte Tool, beschrieben in 1 deterministischem Aufruf, lädt nichts
Stream-Disziplin
stdout ist Payload; jede Logzeile ist auf stderr
Nutzungsfehler
Exit 2 für "du hast mich falsch aufgerufen", getrennt von 1 für "ich habe versucht und versagt"
Prompt-Sicherheit
Ein Prompt ohne Terminal schlägt in Millisekunden fehl statt für immer zu hängen
Stornierung
Ctrl-C unterbricht das Signal des Befehls, dann verlässt es 130
Das, worauf ich immer wieder zurückkehre, ist kein einzelnes Element auf dieser Liste. Es ist, dass die Liste jetzt prüfbar. Das eigene Beispiel der README läuft als Test gegen das veröffentlichte Tarball, und die in seiner Prosa zitierten Zahlen werden den Zahlen der Suite entsprechen – eine Regel, die ihren ersten Fehler innerhalb einer Minute nach dem Schreiben erfasste, als die Seite 87 KB angab und das Artefakt 85 war.
Die Basis ist Open Source bei github.com/light-merlin-dark/merlin-cli, und der Vertrag ist eine Datei im Repository statt einer Behauptung auf einer Website.
Vor vier Jahren war das Problem, dass jedes Modell eine andere Meinung darüber hatte, was ein CLI sein sollte. Die Antwort war nie, mit den Meinungen zu streiten. Es ging darum, die Basis zu besitzen, auf die sie alle bauen, und die Versprechen irgendwo niederzuschreiben, wo ein Build scheitern kann.