Wenn Sie jemals einem Kollegen, einem neuen Laptop oder einem nächtlichen Cron-Job eine funktionierende Beancount-Einrichtung übergeben haben, wissen Sie, dass die Buchhaltung nie der schwierige Teil war. Der schwierige Teil war die Toolchain: ein passendes Python, bean-check und bean-query im Pfad, eine Berichtsbibliothek, die für eine einzige Bilanz herangezogen wurde, und ein Formatierer, der Ihre Dateien in dem Moment umschreibt, in dem Sie ihm eine Frage stellen. bea 0.2.0, veröffentlicht am 12. September 2026, ersetzt diese Checkliste durch eine einzige Installation. Der Befehl bea trägt jetzt die vollständige native Beancount-Toolchain, führt sie in einer verwalteten Engine aus, die er selbst bereitstellt, und behält den maschinenlesbaren Vertrag bei, auf den Skripte und KI-Agenten bereits angewiesen sind.
Dies ist die Versionshinweise für 0.2.0, geschrieben so, wie wir eine Veröffentlichung intern verfolgen: was ausgeliefert wurde, was sich darunter geändert hat, wie es verifiziert wurde, bevor es einen Paketindex erreichte, was es bewusst noch nicht tut und wie man aktualisiert. Wenn Sie stattdessen die Geschichte der ersten Verwendung möchten, sind der 0.1.0-Startbeitrag und die CLI-Schnellstart die kürzeren Lektüren.
Die Veröffentlichung auf einen Blick
Zwei Kanäle veröffentlichen denselben Befehl. Wählen Sie einen und bestätigen Sie dann, dass er mit seiner Version antwortet:
$ brew install bex-co/tap/bea # macOS und Linuxbrew
$ uv tool install beancount-io # überall mit uv und Python 3.12 oder neuer
$ bea --version
bea 0.2.0cli-v0.2.02026-09-12beancount 3.2.3 beanquery 0.2.0beangulp 0.2.0 beanprice 2.1.03.12 3.14Die 0.2.0-Release-Karte: das Tag und das Veröffentlichungsdatum, die von der verwalteten Engine festgelegten Beancount- und Beanquery-Versionen, die beiden optionalen Engine-Funktionen und die Python-Versionen, auf denen die Veröffentlichung installiert und getestet wurde.
| Feld | Wert |
|---|---|
| Version | 0.2.0, Tag cli-v0.2.0, veröffentlicht auf PyPI und im bex-co/homebrew-tap Homebrew-Tap am 2026-09-12 |
| Vorherige Veröffentlichung | 0.1.0, getaggt am 2026-09-09, drei Tage zuvor |
| Änderungssatz | 27 Commits mit Bezug auf die CLI, 119 geänderte Dateien, etwa 12.300 hinzugefügte und 2.100 entfernte Zeilen |
| Engine-Festlegungen | Beancount 3.2.3 und Beanquery 0.2.0 in der Basis-Engine; Beangulp 0.2.0 und Beanprice 2.1.0 als optionale Funktionen |
| Schlagzeile | Jedes native Beancount-Tool unter einem Präfix, bereitgestellt von einer verwalteten Engine; der JSON-Envelope und der Exit-Code-Vertrag aus 0.1.0 sind unverändert |
Was sich darunter geändert hat: die verwaltete Engine
In 0.1.0 importierte bea Beancount in seinen eigenen Prozess, wie es jedes Python-Tool tun würde. Das funktionierte, aber es machte den Abhängigkeitsgraphen der CLI zum Abhängigkeitsgraphen von Beancount, und es ließ „Zuerst Beancount installieren“ als ungeschriebenen Schritt in jeder Anleitung.
0.2.0 zieht eine Linie durch die Mitte des Programms. Das bea-Frontend, der Teil, der die Befehle, die Optionen und die Darstellung besitzt, lädt Beancount, Beanquery oder den eingebetteten Fava-Berichtscode nie. Lokale Ledger-Arbeit läuft in einer verwalteten Engine: einer separaten Python-Umgebung, die bea aus einem hash-pinned Lock bereitstellt und als untergeordneten Interpreter startet. Das Frontend sendet eine JSON-Anfrage über diese Grenze und rendert, was zurückkommt. Sie installieren Beancount nicht, setzen bean-*-Tools nicht in Ihren Pfad und denken nicht darüber nach, welches Python sie gefunden haben.
Wie die Engine ankommt, hängt vom Kanal ab:
- Homebrew erstellt die Frontend- und Engine-Umgebungen während der Installation. Lokale Befehle verwenden die keg-lokale Engine ohne weiteren Download.
- PyPI (
uv tool installoder pipx) stellt bei der ersten Verwendung bereit. Der erste lokale Befehl, der die Engine benötigt, lädt die festgelegte Kombination herunter, was Netzwerkzugriff unduvim Pfad einmal erfordert. Spätere Befehle verwenden sie offline von~/.local/share/bea/engine/<version>oder unterXDG_DATA_HOME, wenn Sie diese gesetzt haben.
Drei Eigenschaften folgen aus diesem Design, und jede entfernt ein Support-Ticket, das wir bereits gesehen haben:
- Upgrades bleiben gepaart.
bea upgradeübergibt das Update an den Paketmanager, der diese Kopie installiert hat, und baut dann die passende Engine neu auf, sodass Frontend und Engine nie auf verschiedene Versionen driften können. - Eine defekte Engine heilt sich selbst. Wenn eine Bereitstellung auf halbem Wege fehlschlägt, wird die verwaltete Umgebung verworfen und beim nächsten erfolgreichen Versuch neu aufgebaut. Verirrte
bean-check-Binärdateien anderswo im Pfad werden ignoriert, nicht versehentlich aufgegriffen. - Schwere optionale Teile bleiben optional. Das Beangulp-Import-Framework benötigt die Systembibliothek
libmagic, und Beanprice zieht Abhängigkeiten für den Kursabruf nach sich. Beides ist nicht in der Basis-Engine. Sie aktivieren sie explizit, nur in der Engine.
$ bea engine status
$ bea engine enable beangulp # Import-Helfer; benötigt die Systembibliothek libmagic
$ bea engine enable beanprice # bean-price Kursabrufbea engine status meldet, ob die Engine bereitgestellt ist und welche optionalen Funktionen aktiviert sind, und benötigt dafür kein Netzwerk. Wenn eine Bereitstellung bei der ersten Verwendung fehlschlägt, beheben Sie das Netzwerk oder uv und führen Sie einen beliebigen lokalen Befehl wie bea check erneut aus. Installieren Sie nicht pip install beancount daneben: Das Frontend wird es nicht verwenden.
Jedes native Tool, ein Präfix
Die Engine ist der Mechanismus. Die benutzerseitige Änderung ist Parität: Jedes ausführbare Programm, das das Upstream-Beancount-Projekt ausliefert, hat jetzt ein bea-Pendant, mit denselben weitergeleiteten Argumenten und derselben erhaltenen Ausgabe.
$ bea check # bean-check, plus beas --json-Envelope
$ bea format main.bean -o clean.bean # bean-format: stdout standardmäßig, -i schreibt neu
$ bea query "SELECT account, sum(position) GROUP BY account"
$ bea doctor context main.bean 2026-01-02 # alle elf bean-doctor-Operationen
$ bea example --seed 1 -o example.beancount # bean-example
$ bea treeify < balances.txt # treeify
$ bea ingest identify --config ingest.py inbox # Beangulp, nach engine enable
$ bea price -e USD:yahoo/AAPL # bean-price, nach engine enablebean-checkbea checkbean-formatbea formatbean-querybea querybean-doctorbea doctorbean-examplebea exampletreeifybea treeifybeangulpbea ingest bea engine enable beangulpbean-pricebea price bea engine enable beanpriceDie Paritätskarte: Die sechs nativen Beancount-Programme über der gestrichelten Linie funktionieren sofort; die beiden darunter werden an Beangulp und Beanprice weitergeleitet, sobald Sie diese Funktion in der Engine aktivieren.
Einige davon verdienen mehr als eine Zeile in einer Tabelle.
bea check ist bean-check mit beas JSON-Envelope obenauf: dieselbe Validierung, dieselben Fehlermeldungen und unter --json dieselben Felder valid und errors, die Skripte bereits parsen.
bea format hat sein Verhalten geändert, und es ist die eine Änderung in dieser Veröffentlichung, die ein Skript überraschen kann. In 0.1.0 schrieb bea format PATH die Datei neu. Jetzt gibt es den formatierten Text auf stdout aus und lässt die Datei unberührt. --in-place (-i) ist das, was neu schreibt, --output FILE (-o) schreibt woanders hin, --check ist das CI-Gate, das mit 1 endet, wenn Dateien formatiert werden müssen, und --dry-run listet auf, was sich ändern würde. Dies folgt bean-format, dessen Standard der sichere ist: Ein Befehl, der einen Pfad liest und ihn stillschweigend neu schreibt, kann nicht zuerst ausprobiert werden. Formatierung ist eine Texttransformation, kein Parse, daher verweigert sie nicht mehr eine Datei mit einem Syntaxfehler; sie richtet aus, was sie erkennt, und lässt den Rest. Führen Sie bea check für die Gültigkeit aus.
bea query ist um die gesamte native Oberfläche gewachsen. Es akzeptiert BQL als Argument, von stdin oder in der interaktiven Shell, die jetzt die Upstream-Beanquery-Shell ist, die als untergeordneter Prozess mit ihren .format, .output, .run- und .set-Befehlen intakt gestartet wird. --format wählt text, csv oder beancount-Darstellung, --numberify teilt Beträge in eine Spalte pro Währung auf, -o schreibt in eine Datei und --source URI reicht eine native Beanquery-Quelle direkt durch.
bea doctor legt alle elf bean-doctor-Operationen offen: lex, parse, roundtrip, directories, list-options, print-options, context, linked, region, missing-open und display-context. Wenn Sie jemals ein Buchungsproblem mit bean-doctor context debuggt haben, ist es dasselbe Werkzeug an derselben Adresse.
bea example und bea treeify sind der native Generator und der native Baum-Renderer, unverändert weitergeleitet.
bea ingest und bea price leiten an Beangulps identify, extract und archive bzw. an bean-price weiter, nach bea engine enable. Der Python-freie CSV-Pfad, bea import --csv, benötigt keines von beiden und ist unverändert.
Eine Regel verbindet die weitergeleiteten Befehle: doctor, example, treeify, price und ingest geben ihre Argumente unverändert an Upstream weiter und behalten die Ausgabe und den Exit-Status von Upstream bei. Das bedeutet auch, dass sie das Ledger als eigenes Positionsargument nehmen, wie in bea doctor lex main.bean, nicht über globales --file. Der Envelope und die Exit-Code-Kategorien unten beschreiben beas eigene Befehle.
Der Vertrag, dem Skripte weiterhin vertrauen können
Nichts an der maschinenlesbaren Oberfläche hat sich bewegt. Globales --json legt weiterhin einen Envelope auf stdout mit bea, target, data und truncated, plus limit auf begrenzten Listen und page auf paginierten gehosteten Listen. Beträge sind Dezimalzeichenfolgen, nie Floats, und Daten sind ISO YYYY-MM-DD. --json impliziert --no-input; das gilt auch für ein Nicht-Terminal-stdin oder eine wahre CI-Variable, sodass ein unbeaufsichtigter Job nie auf einen Menschen wartet. --strict verweigert Teilantworten auch in einem Terminal, und --allow-errors jedes Lesebefehls optet wieder ein.
Ein Fehlschlag schreibt nichts auf stdout und genau ein Objekt auf stderr:
{
"error": {
"category": "validation",
"message": "Ledger hat 3 Fehler. Übergeben Sie --allow-errors, um trotzdem zu berichten.",
"exit_code": 1,
"details": ["main.bean:1: Transaktion ist nicht ausgeglichen: (2.50 USD)"]
}
}Die fünf Exit-Codes und die category-Zeichenfolge, die jeder im JSON-Fehlerobjekt trägt. Ein Skript verzweigt auf die Zahl; ein Mensch liest die Kategorie.
| Code | Kategorie | Bedeutung |
|---|---|---|
| 0 | keine | Erfolg, einschließlich Vorschauen und absichtlicher Duplikat-Überspringungen |
| 1 | validation | Ledger- oder Validierungsfehler und der Sammelbegriff für jeden anderen Laufzeitfehler |
| 2 | usage | Schlechte Argumente, ein fehlendes Ziel oder Extra, oder Eingabe unter --no-input erforderlich |
| 3 | auth | Authentifizierungs- oder Berechtigungsfehler, einschließlich eines schreibgeschützten Ziels |
| 4 | conflict | Eine gleichzeitige Änderung, ein Import, der eine Duplikat-Überprüfung benötigt, oder ein Schreiben, dessen Ergebnis unbekannt ist |
Zwei Details sind für jeden wichtig, der bei einem Fehlschlag erneut versucht. Ein Nicht-Null-Exit bedeutet nicht universell, dass nichts geändert wurde: add transactions --partial kann die akzeptierten Zeilen schreiben, format -i über mehrere Dateien kann einige neu schreiben, bevor es bei einer fehlschlägt, und cloud ledger create --clone kann das Ledger erstellen, bevor der Klon fehlschlägt. Lesen Sie error.result, bevor Sie eine Mutation erneut versuchen. Und gehostete Befehle bilden den HTTP-Status des Servers auf dieselbe Tabelle ab, wobei sie die eigene Nachricht des Servers behalten: 401 und 403 enden mit 3, 400 mit 2, 409 mit 4, und alles andere, einschließlich Ratenbegrenzung, endet mit 1. Ein Schreiben, dessen Ergebnis die CLI nicht kennen kann, wie ein Timeout mitten im Löschen, endet mit 4 und sagt dies, anstatt zu raten.
Der Automatisierungsleitfaden führt eine jq-Pipeline Ende zu Ende durch diesen Envelope.
Behobene Probleme, die mitfuhren
Eine Paritätsveröffentlichung ist auch eine Gelegenheit, die Defekte zu schließen, die eine erste Veröffentlichung aufdeckt. Diese landeten zwischen den beiden Tags, jeweils mit einem Regressionstest:
- Zahlen werden als Festkommatext geschrieben, nie in wissenschaftlicher Notation, einschließlich der Eröffnungssalden, die
bea initrendert. Ein Ledger, das1E+3sagt, ist technisch gültig und praktisch unlesbar. - Kostenlose Lose überleben die JSON-Serialisierung mit ihren Daten und Beschriftungen intakt, und Lose-Beschriftungen werden korrekt maskiert, wenn eine Transaktion geschrieben wird.
- Explizite Null-Posten sind während des Imports echte Beträge, anstatt als „weggelassen, bitte gleiche mich aus“ gelesen zu werden.
- CSV-Importe laufen durch einen strengen Leser. Die Header-Erkennung entfernte früher Spaltennamen, während die Extraktion die Rohschlüssel behielt, sodass ein aufgefüllter Header, den die Dokumente zu akzeptieren versprachen, als fehlende Spalte fehlschlug. Jetzt werden Namen einmal entfernt, eine zugeordnete Spalte muss genau einmal erscheinen, und ein ungeschlossenes Anführungszeichen schlägt mit seiner Zeilennummer fehl, bevor etwas geschrieben wird.
- BQL lädt den exakten Ledger-Pfad statt einer URL-geparsten Verbindungszeichenfolge, sodass ungewöhnliche Pfade so aufgelöst werden, wie der Rest der CLI sie auflöst.
bea balance <term>summiert nur, was es zeigt. Ein beibehaltenes Elternteil meldet nicht mehr die Summen ausgeschlossener Geschwister, ein nicht zusammenhängender, nicht bewerteter Bestand lässt eine USD-Auswahl nicht mehr fehlschlagen, und der Envelope meldet den angewendeten Filter. Ein fehlerhaftes--account-Muster bei Berichten endet mit 2 als der Nutzungsfehler, der es ist.- Stderr im JSON-Modus ist immer ein Objekt, auch wenn tolerierte Warnungen vor dem Fehlschlag kommen.
- Gehostete Anmeldeinformationen schlagen früh und konsistent fehl: Ein
BEA_TOKENmit Leerzeichen wird vor jeder Anfrage abgelehnt, eine widerrufene Anmeldeinformation wird voncloud statusund von Ledger-Befehlen gleich gemeldet, undowner/namewird vor einer Bestätigungsaufforderung oder einem authentifizierten Aufruf validiert.cloud logoutlässtBEA_TOKENunberührt, undcloud ledger list --jsongibt die Seite zurück, die es tatsächlich bedient hat. - Die Homebrew-Formel legt die genaue PyPI-Artefakt-URL fest, sodass eine Tap-Installation und eine PyPI-Installation nachweislich dieselben Bytes sind.
Wie es verifiziert wurde, bevor Sie es sahen
Eine Veröffentlichung ist eine Behauptung, und die Pipeline ist der Beweis. Ein cli-v0.2.0-Tag muss einen Commit auf main benennen, dessen pyproject.toml-Version genau übereinstimmt; der Workflow weigert sich bei allem anderen, einschließlich Prerelease-Suffixen. Von dort:
- Die vollständige Prüfungssuite läuft zuerst.
make check-alldeckt Linting, Formatierung, striktes mypy, Dead-Code-Erkennung, den Drift-Check der generierten Referenz und die Testsuite ab. Der Release-Pull-Request verzeichnet 635 bestandene Tests. - Der Engine-Lock wird exportiert und hash-pinned, und die Quelldistribution und das Wheel werden einmal gebaut. Jeder spätere Schritt testet genau diese Artefakte, nicht einen Neubau.
- Saubere Installationen auf drei Betriebssystemen und zwei Pythons. Das Wheel wird durch
uv toolund die sdist durchpipauf Linux, macOS und Windows installiert, auf Python 3.12 und 3.14, einschließlich des optionalen KI-Extras. Ein Homebrew-Job installiert die sdist durch ein temporäres Tap auf macOS und Linux. - Die Veröffentlichung ist sequentiell und tokenlos. PyPI erhält die Artefakte durch vertrauenswürdige Veröffentlichung, sodass kein langlebiges API-Token existiert, das lecken könnte; die GitHub-Release wird mit angehängten Veröffentlichungsattestaten erstellt; und
Formula/bea.rbwird mit der sdist-URL und dem Hash, den PyPI tatsächlich ausgeliefert hat, in das öffentliche Tap gepusht. - Smoke-Tests nach der Veröffentlichung installieren aus den echten Indizes. Separate Jobs installieren die festgelegte Version von PyPI und vom öffentlichen Tap und führen dieselben Kundensmoke-Tests gegen das installierte Programm aus. Ein Fehlschlag dort rollt nichts zurück, bedeutet aber, dass die Veröffentlichung Aufmerksamkeit benötigt, bevor jemand davon erfährt.
Dieser Beitrag wird jenseits von Schritt fünf geschrieben.
Aktualisieren von 0.1.0
Führen Sie das Upgrade über den Manager aus, der Ihre Kopie installiert hat, oder lassen Sie es bea tun:
$ bea upgrade --check # installierte und neueste Versionen und den Befehl melden, der ausgeführt würde
$ bea upgrade # brew upgrade bea, uv tool upgrade beancount-io, oder pipx upgrade beancount-ioNachdem der Manager fertig ist, aktualisiert bea upgrade die verwaltete Engine, sodass die beiden gepaart bleiben. Dann prüfen Sie drei Dinge:
- Jedes Skript, das
bea format PATHausgeführt hat, um eine Datei neu zu schreiben, benötigt jetztbea format -i PATH. Der alte Standard konnte nicht in der Vorschau angesehen werden, der neue kann es. - Jedes Skript, das sich darauf verlassen hat, dass
formateinen Syntaxfehler abfängt, sollte dafürbea checkaufrufen, da die Formatierung nicht mehr parst. - PyPI-Installationen benötigen einmal Netzwerk und
uvfür den ersten lokalen Befehl nach dem Upgrade, damit die Engine bereitgestellt werden kann. Homebrew-Installationen benötigen nichts.
Alles, was Ihre Skripte bereits parsen, die Envelope-Schlüssel, die Dezimalzeichenfolgen und die Exit-Codes, ist unverändert. Das Feld bea im Envelope liest jetzt 0.2.0.
Was diese Veröffentlichung nicht tut
- Gehostete Zielsteuerung ist nicht implementiert. Es gibt kein
--ledger-Flag; lokale Befehle lesen lokale Dateien und laden nie implizit eine hoch. Gehostete Ledger werden unterbea cloudverwaltet und als Git-Klone bearbeitet. bea askbenötigt weiterhin dasask-Extra und Beancount.io-Anmeldeinformationen, und es unterstützt kein--json. Die Standardinstallation trägt keine KI-Abhängigkeiten.- Beangulp und Beanprice sind optional, und Beangulp benötigt die Systembibliothek
libmagic.bea import --csvdeckt Bankexporte ohne beide ab. - Weitergeleitete native Befehle geben keinen Envelope aus. Wenn Sie strukturierte Ausgabe von einer Doctor-Operation benötigen, ist das eine Anfrage, die wir gerne hören würden.
Seit dem Tag hat main bereits die erste Runde QA auf 0.2.0 aufgenommen, und sie wird mit der nächsten Veröffentlichung fahren: bea format liest stdin als Filter, und sein -o FILE-Modus antwortet mit einem Envelope, der benennt, was es geschrieben hat; --json check weigert sich bei bean-check-nur-Flags, und --json wird auf doctor, example und treeify direkt verweigert, damit ein Skript nativen Text nicht für einen Envelope hält; --json query -o FILE schreibt den Envelope atomar in die Datei, mit --numberify auch auf JSON angewendet; bea engine status benennt, welche Engine-Stufe bedient; eine BQL-Abfrage, die mit einem Kommentar beginnt, läuft; natives Pass-through --help funktioniert, bevor die Engine bereitgestellt ist; und die .output der Abfrage-Shell stellt den ursprünglichen Stream nach einer fehlgeschlagenen Umleitung wieder her.
Wohin als Nächstes
- CLI-Schnellstart: Installation, erstes Ledger, erster Kauf, erste Saldenprüfung.
- Ihr erster Monat mit bea: von
initbis zu einem abgestimmten Monatsendbericht. - Bankexporte importieren: der Python-freie CSV-Pfad, Regeldateien und Python-Importeure.
- Buchhaltung mit bea automatisieren: das Ledger auflösen, den Envelope lesen, auf Exit-Codes verzweigen, Planung.
- Beancount-CLI-Referenz: jeder Befehl, jede Option, jede Umgebungsvariable und jeder Exit-Code, geprüft gegen die generierte Referenz der CLI.
- Geben Sie Ihrem KI-Agenten ein Ledger: der Agenten-zuerst-Durchlauf aus dem 0.1.0-Start.
- Changelog: jede Veröffentlichung, neueste zuerst.
Behalten Sie Ihre Bücher als Code
Eine Toolchain, die Sie in einer Zeile installieren können, ist eine Toolchain, die Sie jedem geben können: einem Mitgründer, einem Buchhalter, einem CI-Läufer, einem KI-Agenten. Beancount.io bietet Klartext-Buchhaltung, die transparent, versioniert und reproduzierbar bleibt, mit bea als dem Befehl, der ein lokales Ledger ehrlich hält, und dem gehosteten Dienst als dem Ort, an dem Ihr Team, Ihr Telefon und Ihr Assistent dieselben Bücher treffen. Installieren Sie bea und führen Sie Ihre erste Prüfung durch, und wenn die Veröffentlichung etwas tut, das Sie nicht erwartet haben, ist das GitHub-Repository der Ort, an dem wir es hören möchten.





