Aller au contenu principal

bea 0.2.0 : une installation, toute la chaîne d'outils Beancount

Publié 19 minutes de lectureMike ThriftMike Thrift
bea 0.2.0 : une installation, toute la chaîne d'outils Beancount
Sur cette page

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.0
bea 0.2.0
cli-v0.2.02026-09-12
moteur
beancount 3.2.3 beanquery 0.2.0
optionnel
beangulp 0.2.0 beanprice 2.1.0
python
3.12 3.14

La 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.

ChampValeur
Version0.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édente0.1.0, taggé le 2026-09-09, trois jours plus tôt
Ensemble de modifications27 commits touchant le CLI, 119 fichiers modifiés, environ 12 300 lignes ajoutées et 2 100 supprimées
Épinglage du moteurBeancount 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 principalChaque 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 install ou 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 et uv dans le chemin une fois. Les commandes suivantes le réutilisent hors ligne depuis ~/.local/share/bea/engine/<version>, ou sous XDG_DATA_HOME si 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 :

  1. Les mises à niveau restent appariées. bea upgrade confie 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.
  2. 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-check errants ailleurs dans le chemin sont ignorés plutôt que pris par accident.
  3. 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-price

bea 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 moteur
bean-check
bea check
bean-format
bea format
bean-query
bea query
bean-doctor
bea doctor
bean-example
bea example
treeify
bea treeify
beangulp
bea ingest bea engine enable beangulp
bean-price
bea price bea engine enable beanprice

La 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)"]
  }
}
0
ok
1
validation
2
usage
3
auth
4
conflict

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.

CodeCatégorieSignification
0aucuneSuccès, y compris les aperçus et les sauts de doublons intentionnels
1validationErreur de grand livre ou de validation, et le fourre-tout pour toute autre défaillance d'exécution
2usageArguments incorrects, cible manquante ou en trop, ou saisie requise sous --no-input
3authÉchec d'authentification ou de permission, y compris une destination en lecture seule
4conflictUn 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 init restitue. Un grand livre qui dit 1E+3 est 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 --account malformé 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_TOKEN contenant des espaces est rejeté avant toute requête, un identifiant révoqué est signalé de la même manière par cloud status et par les commandes de grand livre, et propriétaire/nom est validé avant une invite de confirmation ou un appel authentifié. cloud logout laisse BEA_TOKEN tranquille, et cloud ledger list --json renvoie 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à :

  1. La suite de vérification complète s'exécute d'abord. make check-all couvre 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.
  2. 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.
  3. Installations propres sur trois systèmes d'exploitation et deux Python. La wheel est installée via uv tool et le sdist via pip sur 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.
  4. 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.rb est poussé vers le tap public avec l'URL du sdist et le hash que PyPI a réellement servis.
  5. 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-io

Aprè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 PATH pour réécrire un fichier a maintenant besoin de bea format -i PATH. L'ancien défaut ne pouvait pas être prévisualisé, et le nouveau le peut.
  • Tout script qui comptait sur format pour attraper une erreur de syntaxe devrait appeler bea check pour cela, car le formatage n'analyse plus.
  • Les installations PyPI ont besoin de réseau et de uv une 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 sous bea cloud et travaillés comme des clones git.
  • bea ask nécessite toujours l'extra ask et 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 --csv couvre 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

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.

Partager cet article

Source : https://beancount.io/fr/blog/2026/09/16/bea-0-2-0-one-install-whole-beancount-toolchain

Publié: 16 septembre 2026