Si vous avez déjà confié à un collègue, un nouvel ordinateur portable ou une tâche cron nocturne une configuration Beancount fonctionnelle, vous savez que la comptabilité n'a jamais été la partie difficile. La partie difficile était la chaîne d'outils : un Python qui correspond, bean-check et bean-query dans le chemin, une bibliothèque de rapports installée pour un seul bilan, et un formateur qui réécrit vos fichiers dès que vous lui posez une question. bea 0.2.0, publié le 12 septembre 2026, remplace cette liste de contrôle par une seule installation. La commande bea embarque désormais la chaîne d'outils Beancount native complète, l'exécute dans un moteur géré qu'elle provisionne elle-même, et conserve le contrat lisible par machine dont les scripts et les agents IA dépendent déjà.
Ceci est la note de version pour 0.2.0, rédigée comme nous suivons une version en interne : ce qui a été livré, ce qui a changé en profondeur, comment cela a été vérifié avant d'atteindre un index de paquets, ce que cela ne fait volontairement pas encore, et comment mettre à niveau. Si vous préférez l'histoire de la première utilisation, le post de lancement 0.1.0 et le guide de démarrage rapide du CLI sont des lectures plus courtes.
La version en un coup d'œil
Deux canaux publient la même commande. Choisissez-en un, puis confirmez qu'il répond avec sa version :
$ brew install bex-co/tap/bea # macOS et Linuxbrew
$ uv tool install beancount-io # partout avec uv et Python 3.12 ou plus récent
$ 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.14La carte de version 0.2.0 : le tag et la date de publication, les versions Beancount et Beanquery épinglées par le moteur géré, les deux fonctionnalités optionnelles du moteur, et les versions Python sur lesquelles la version a été installée et testée.
| Champ | Valeur |
|---|---|
| Version | 0.2.0, tag cli-v0.2.0, publié sur PyPI et le tap Homebrew bex-co/homebrew-tap le 2026-09-12 |
| Version précédente | 0.1.0, taggé le 2026-09-09, trois jours plus tôt |
| Ensemble de modifications | 27 commits touchant le CLI, 119 fichiers modifiés, environ 12 300 lignes ajoutées et 2 100 supprimées |
| Épinglage du moteur | Beancount 3.2.3 et Beanquery 0.2.0 dans le moteur de base ; Beangulp 0.2.0 et Beanprice 2.1.0 comme fonctionnalités optionnelles |
| Titre principal | Chaque outil Beancount natif sous un seul préfixe, servi par un moteur géré ; l'enveloppe JSON et le contrat de codes de sortie de 0.1.0 sont inchangés |
Ce qui a changé en profondeur : le moteur géré
Dans 0.1.0, bea importait Beancount dans son propre processus, comme le ferait n'importe quel outil Python. Cela fonctionnait, mais cela rendait le graphe de dépendances du CLI identique à celui de Beancount, et laissait « installer Beancount d'abord » comme une étape non écrite dans chaque guide.
0.2.0 trace une ligne au milieu du programme. Le frontal bea, la partie qui possède les commandes, les options et le rendu, ne charge jamais Beancount, Beanquery ou le code de reporting Fava vendu. Le travail sur le grand livre local s'exécute dans un moteur géré : un environnement Python séparé que bea provisionne à partir d'un verrou épinglé par hash et lance comme interpréteur enfant. Le frontal envoie une requête JSON à travers cette frontière et restitue ce qui revient. Vous n'installez pas Beancount, ne mettez pas les outils bean-* dans votre chemin, et ne pensez pas au Python qu'ils ont trouvé.
La façon dont le moteur arrive dépend du canal :
- Homebrew crée les environnements frontal et moteur pendant l'installation. Les commandes locales utilisent le moteur local au keg sans téléchargement supplémentaire.
- PyPI (
uv tool installou pipx) provisionne à la première utilisation. La première commande locale qui a besoin du moteur télécharge la combinaison épinglée, ce qui nécessite un accès réseau etuvdans le chemin une fois. Les commandes suivantes le réutilisent hors ligne depuis~/.local/share/bea/engine/<version>, ou sousXDG_DATA_HOMEsi vous le définissez.
Trois propriétés découlent de cette conception, et chacune supprime un ticket de support que nous avons déjà vu :
- Les mises à niveau restent appariées.
bea upgradeconfie la mise à jour au gestionnaire de paquets qui a installé cette copie, puis reconstruit le moteur correspondant, de sorte qu'un frontal et un moteur ne peuvent jamais dériver vers des versions différentes. - Un moteur cassé se répare lui-même. Si un provisionnement échoue à mi-chemin, l'environnement géré est supprimé et reconstruit lors de la prochaine tentative réussie. Les binaires
bean-checkerrants ailleurs dans le chemin sont ignorés plutôt que pris par accident. - Les pièces optionnelles lourdes restent optionnelles. Le framework d'import Beangulp nécessite la bibliothèque système
libmagic, et Beanprice tire des dépendances de récupération de cotations. Aucune des deux n'est dans le moteur de base. Vous les activez explicitement, uniquement dans le moteur.
$ bea engine status
$ bea engine enable beangulp # aides d'import ; nécessite la bibliothèque système libmagic
$ bea engine enable beanprice # récupération de cotations bean-pricebea engine status indique si le moteur est provisionné et quelles fonctionnalités optionnelles sont activées, et cela ne nécessite aucun réseau pour le dire. Si un provisionnement à la première utilisation échoue, corrigez le réseau ou uv et relancez toute commande locale comme bea check. N'installez pas pip install beancount à côté : le frontal ne l'utilisera pas.
Chaque outil natif, un seul préfixe
Le moteur est le mécanisme. Le changement visible par l'utilisateur est la parité : chaque exécutable que le projet Beancount en amont fournit a maintenant un équivalent bea, avec les mêmes arguments transmis et la même sortie préservée.
$ bea check # bean-check, plus l'enveloppe --json de bea
$ bea format main.bean -o clean.bean # bean-format : stdout par défaut, -i réécrit
$ bea query "SELECT account, sum(position) GROUP BY account"
$ bea doctor context main.bean 2026-01-02 # les onze opérations bean-doctor
$ bea example --seed 1 -o example.beancount # bean-example
$ bea treeify < balances.txt # treeify
$ bea ingest identify --config ingest.py inbox # Beangulp, après activation du moteur
$ bea price -e USD:yahoo/AAPL # bean-price, après activation du moteurbean-checkbea checkbean-formatbea formatbean-querybea querybean-doctorbea doctorbean-examplebea exampletreeifybea treeifybeangulpbea ingest bea engine enable beangulpbean-pricebea price bea engine enable beanpriceLa carte de parité : les six exécutables Beancount natifs au-dessus de la ligne pointillée fonctionnent immédiatement ; les deux en dessous sont transmis à Beangulp et Beanprice une fois que vous activez cette fonctionnalité dans le moteur.
Certains de ces points méritent plus qu'une ligne dans un tableau.
bea check est bean-check avec l'enveloppe JSON de bea ajoutée par-dessus : les mêmes validations, les mêmes messages d'erreur, et sous --json les mêmes champs valid et errors que les scripts analysent déjà.
bea format a changé de comportement, et c'est le seul changement de cette version qui peut surprendre un script. Dans 0.1.0, bea format PATH réécrivait le fichier. Il affiche désormais le texte formaté sur stdout et laisse le fichier intact. --in-place (-i) est ce qui réécrit, --output FILE (-o) écrit ailleurs, --check est la porte de CI qui sort avec le code 1 lorsque les fichiers nécessitent un formatage, et --dry-run liste ce qui changerait. Cela suit bean-format, dont la valeur par défaut est la plus sûre : une commande qui lit un chemin et le réécrit silencieusement ne peut pas être essayée d'abord. Le formatage est une transformation de texte, pas une analyse syntaxique, donc il ne refuse plus un fichier avec une erreur de syntaxe ; il aligne ce qu'il reconnaît et laisse le reste. Exécutez bea check pour la validité.
bea query a grandi avec toute la surface native. Il prend BQL comme argument, depuis stdin, ou dans le shell interactif, qui est désormais le shell Beanquery en amont lancé comme processus enfant avec ses commandes .format, .output, .run et .set intactes. --format sélectionne le rendu text, csv ou beancount, --numberify divise les montants en une colonne par devise, -o écrit dans un fichier, et --source URI transmet une source Beanquery native directement.
bea doctor expose les onze opérations bean-doctor : lex, parse, roundtrip, directories, list-options, print-options, context, linked, region, missing-open et display-context. Si vous avez déjà débogué un problème de comptabilisation avec bean-doctor context, c'est le même outil à la même adresse.
bea example et bea treeify sont le générateur natif et le rendu d'arbre natif, transmis tels quels.
bea ingest et bea price transmettent respectivement à identify, extract et archive de Beangulp et à bean-price, après bea engine enable. Le chemin CSV sans Python, bea import --csv, n'a besoin d'aucun des deux et est inchangé.
Une règle relie les commandes transmises : doctor, example, treeify, price et ingest remettent leurs arguments en amont inchangés et conservent la sortie et le statut de sortie en amont. Cela signifie aussi qu'ils prennent le grand livre comme argument positionnel propre, comme dans bea doctor lex main.bean, plutôt que via --file global. L'enveloppe et les catégories de codes de sortie ci-dessous décrivent les propres commandes de bea.
Le contrat auquel les scripts peuvent continuer de faire confiance
Rien sur la surface lisible par machine n'a bougé. --json global place toujours une enveloppe sur stdout avec bea, target, data et truncated, plus limit sur les listes bornées et page sur les listes hébergées paginées. Les montants sont des chaînes décimales, jamais des flottants, et les dates sont au format ISO AAAA-MM-JJ. --json implique --no-input ; de même pour un stdin non-terminal ou une variable CI vraie, donc une tâche sans surveillance n'attend jamais un humain. --strict refuse les réponses partielles même dans un terminal, et l'option --allow-errors de chaque commande de lecture se réactive.
Un échec n'écrit rien sur stdout et exactement un objet sur stderr :
{
"error": {
"category": "validation",
"message": "Le grand livre a 3 erreur(s). Passez --allow-errors pour signaler quand même.",
"exit_code": 1,
"details": ["main.bean:1: La transaction ne balance pas : (2.50 USD)"]
}
}Les cinq codes de sortie et la chaîne category que chacun porte dans l'objet d'erreur JSON. Un script se ramifie sur le nombre ; un humain lit la catégorie.
| Code | Catégorie | Signification |
|---|---|---|
| 0 | aucune | Succès, y compris les aperçus et les sauts de doublons intentionnels |
| 1 | validation | Erreur de grand livre ou de validation, et le fourre-tout pour toute autre défaillance d'exécution |
| 2 | usage | Arguments incorrects, cible manquante ou en trop, ou saisie requise sous --no-input |
| 3 | auth | Échec d'authentification ou de permission, y compris une destination en lecture seule |
| 4 | conflict | Un changement concurrent, un import nécessitant un examen des doublons, ou une écriture dont le résultat est inconnu |
Deux détails comptent pour quiconque réessaie en cas d'échec. Un code de sortie non nul ne signifie pas universellement que rien n'a changé : add transactions --partial peut écrire les lignes acceptées, format -i sur plusieurs fichiers peut réécrire certains avant d'échouer sur un, et cloud ledger create --clone peut créer le grand livre avant que le clone ne échoue. Lisez error.result avant de réessayer une mutation. Et les commandes hébergées mappent le statut HTTP du serveur sur le même tableau, en conservant le message propre du serveur : 401 et 403 sortent avec 3, 400 avec 2, 409 avec 4, et tout le reste, y compris la limitation de débit, sort avec 1. Une écriture dont le CLI ne peut pas connaître le résultat, comme un délai d'attente au milieu d'une suppression, sort avec 4 et le dit plutôt que de deviner.
Le guide d'automatisation parcourt un pipeline jq à travers cette enveloppe de bout en bout.
Correctifs qui ont accompagné
Une version de parité est aussi une occasion de fermer les défauts qu'une première version révèle. Ceux-ci ont été intégrés entre les deux tags, chacun avec un test de régression :
- Les nombres sont écrits en texte à virgule fixe, jamais en notation scientifique, y compris les soldes d'ouverture que
bea initrestitue. Un grand livre qui dit1E+3est techniquement valide et pratiquement illisible. - Les lots de coûts survivent à la sérialisation JSON avec leurs dates et étiquettes intactes, et les étiquettes de lots sont échappées correctement lorsqu'une transaction est écrite.
- Les écritures nulles explicites sont de vrais montants lors de l'import, plutôt que d'être lues comme « omises, veuillez équilibrer ».
- Les imports CSV passent par un lecteur strict unique. La découverte d'en-tête supprimait les noms de colonnes tandis que l'extraction gardait les clés brutes, donc un en-tête rembourré que la documentation promettait d'accepter échouait comme colonne manquante. Maintenant, les noms sont supprimés une fois, une colonne mappée doit apparaître exactement une fois, et un guillemet non fermé échoue avec son numéro de ligne avant que quoi que ce soit ne soit écrit.
- BQL charge le chemin exact du grand livre plutôt qu'une chaîne de connexion analysée par URL, donc les chemins inhabituels se résolvent comme le reste du CLI les résout.
bea balance <terme>totalise uniquement ce qu'il montre. Un parent conservé ne signale plus les totaux des frères exclus, une détention non évaluée sans rapport n'échoue plus une sélection USD, et l'enveloppe signale le filtre appliqué. Un motif--accountmalformé sur les rapports sort avec 2 comme l'erreur d'usage qu'il est.- Le stderr en mode JSON est toujours un objet, même lorsque des avertissements tolérés précèdent l'échec.
- Les identifiants hébergés échouent tôt et de manière cohérente : un
BEA_TOKENcontenant des espaces est rejeté avant toute requête, un identifiant révoqué est signalé de la même manière parcloud statuset par les commandes de grand livre, etpropriétaire/nomest validé avant une invite de confirmation ou un appel authentifié.cloud logoutlaisseBEA_TOKENtranquille, etcloud ledger list --jsonrenvoie la page qu'il a réellement servie. - La formule Homebrew épingle l'URL exacte de l'artefact PyPI, donc une installation via tap et une installation via PyPI sont prouvablement les mêmes octets.
Comment cela a été vérifié avant que vous ne le voyiez
Une version est une affirmation, et le pipeline en est la preuve. Un tag cli-v0.2.0 doit nommer un commit sur main dont la version pyproject.toml correspond exactement ; le workflow refuse toute autre chose, y compris les suffixes de préversion. À partir de là :
- La suite de vérification complète s'exécute d'abord.
make check-allcouvre le lint, le formatage, mypy strict, la détection de code mort, la vérification de dérive de la référence générée et la suite de tests. La pull request de version enregistre 635 tests réussis. - Le verrou du moteur est exporté et épinglé par hash, et la distribution source et la wheel sont construites une fois. Chaque étape ultérieure teste ces artefacts exacts, pas une reconstruction.
- Installations propres sur trois systèmes d'exploitation et deux Python. La wheel est installée via
uv toolet le sdist viapipsur Linux, macOS et Windows, sur Python 3.12 et 3.14, y compris l'extra AI optionnel. Un travail Homebrew installe le sdist via un tap temporaire sur macOS et Linux. - La publication est séquentielle et sans jeton. PyPI reçoit les artefacts via la publication de confiance, donc aucun jeton API à longue durée de vie n'existe pour fuir ; la version GitHub est créée avec des déclarations de publication attachées ; et
Formula/bea.rbest poussé vers le tap public avec l'URL du sdist et le hash que PyPI a réellement servis. - Les tests de fumée post-publication installent depuis les index réels. Des travaux séparés installent la version épinglée depuis PyPI et depuis le tap public et exécutent les mêmes tests de fumée client contre l'exécutable installé. Un échec là-bas ne fait pas revenir en arrière, mais cela signifie que la version nécessite une attention avant que quiconque en soit informé.
Ce post est écrit de l'autre côté de l'étape cinq.
Mise à niveau depuis 0.1.0
Exécutez la mise à niveau via le gestionnaire qui a installé votre copie, ou laissez bea le faire :
$ bea upgrade --check # signale les versions installée et la plus récente et la commande qui serait exécutée
$ bea upgrade # brew upgrade bea, uv tool upgrade beancount-io, ou pipx upgrade beancount-ioAprès que le gestionnaire a terminé, bea upgrade rafraîchit le moteur géré afin que les deux restent appariés. Ensuite, vérifiez trois choses :
- Tout script qui exécutait
bea format PATHpour réécrire un fichier a maintenant besoin debea format -i PATH. L'ancien défaut ne pouvait pas être prévisualisé, et le nouveau le peut. - Tout script qui comptait sur
formatpour attraper une erreur de syntaxe devrait appelerbea checkpour cela, car le formatage n'analyse plus. - Les installations PyPI ont besoin de réseau et de
uvune fois pour la première commande locale après la mise à niveau, afin que le moteur puisse être provisionné. Les installations Homebrew n'ont besoin de rien.
Tout ce que vos scripts analysent déjà, les clés de l'enveloppe, les chaînes décimales et les codes de sortie, est inchangé. Le champ bea dans l'enveloppe lit maintenant 0.2.0.
Ce que cette version ne fait pas
- Le ciblage hébergé n'est pas implémenté. Il n'y a pas de drapeau
--ledger; les commandes locales lisent les fichiers locaux et n'en téléversent jamais un implicitement. Les grands livres hébergés sont gérés sousbea cloudet travaillés comme des clones git. bea asknécessite toujours l'extraasket des identifiants Beancount.io, et il ne prend pas en charge--json. L'installation par défaut ne contient aucune dépendance IA.- Beangulp et Beanprice sont optionnels, et Beangulp nécessite la bibliothèque système
libmagic.bea import --csvcouvre les exports bancaires sans aucun des deux. - Les commandes natives transmises n'émettent pas l'enveloppe. Si vous avez besoin d'une sortie structurée d'une opération doctor, c'est une demande que nous aimerions entendre.
Depuis le tag, main a déjà intégré la première série de QA sur 0.2.0, et elle suivra la prochaine version : bea format lit stdin comme un filtre et son mode -o FILE répond avec une enveloppe nommant ce qu'il a écrit ; --json check refuse les drapeaux réservés à bean-check, et --json est refusé catégoriquement sur doctor, example et treeify afin qu'un script ne puisse pas confondre le texte natif avec une enveloppe ; --json query -o FILE écrit l'enveloppe dans le fichier de manière atomique, avec --numberify appliqué aussi au JSON ; bea engine status nomme quel niveau de moteur sert ; une requête BQL qui s'ouvre par un commentaire s'exécute ; le --help natif en pass-through fonctionne avant que le moteur soit provisionné ; et la commande .output du shell de requête restaure le flux d'origine après une redirection échouée.
Où aller ensuite
- Démarrage rapide du CLI : installation, premier grand livre, premier achat, première vérification de solde.
- Votre premier mois avec bea : de
inità un rapport de fin de mois rapproché. - Importer des exports bancaires : le chemin CSV sans Python, les fichiers de règles et les importateurs Python.
- Automatiser la comptabilité avec bea : résoudre le grand livre, lire l'enveloppe, se ramifier sur les codes de sortie, planifier.
- Référence du CLI Beancount : chaque commande, option, variable d'environnement et code de sortie, vérifiés contre la référence générée du CLI.
- Donnez un grand livre à votre agent IA : la visite guidée priorisant l'agent depuis le lancement de 0.1.0.
- Journal des modifications : chaque version, la plus récente d'abord.
Gardez vos livres comme du code
Une chaîne d'outils que vous pouvez installer en une ligne est une chaîne d'outils que vous pouvez remettre à n'importe qui : un cofondateur, un comptable, un exécuteur CI, un agent IA. Beancount.io fournit une comptabilité en texte brut qui reste transparente, versionnée et reproductible, avec bea comme la commande qui garde un grand livre local honnête et le service hébergé comme l'endroit où votre équipe, votre téléphone et votre assistant rencontrent les mêmes livres. Installez bea et exécutez votre première vérification, et si la version fait quelque chose à laquelle vous ne vous attendiez pas, le dépôt GitHub est l'endroit où nous voulons l'entendre.





