Pular para o conteúdo principal

bea 0.2.0: uma instalação, toda a toolchain do Beancount

Publicado 19 min para lerMike ThriftMike Thrift
bea 0.2.0: uma instalação, toda a toolchain do Beancount
Nesta página

Se você já entregou a um colega, a um laptop novo ou a um cron job noturno uma configuração funcional do Beancount, sabe que a contabilidade nunca foi a parte difícil. A parte difícil era a toolchain: um Python compatível, bean-check e bean-query no path, uma biblioteca de relatórios puxada para um único balanço patrimonial e um formatador que reescreve seus arquivos no momento em que você lhe faz uma pergunta. O bea 0.2.0, lançado em 12 de setembro de 2026, substitui essa lista de verificação por uma única instalação. O comando bea agora carrega a toolchain nativa completa do Beancount, executa-a dentro de um motor gerenciado que ele mesmo provisiona e mantém o contrato legível por máquina do qual scripts e agentes de IA já dependem.

Esta é a nota de lançamento do 0.2.0, escrita do jeito que acompanhamos um lançamento internamente: o que foi entregue, o que mudou por baixo, como foi verificado antes de chegar a um índice de pacotes, o que ele deliberadamente ainda não faz e como atualizar. Se você prefere a história da primeira execução, o post de lançamento do 0.1.0 e o guia rápido da CLI são as leituras mais curtas.

O lançamento em resumo

Dois canais publicam o mesmo comando. Escolha um e depois confirme que ele responde com sua versão:

$ brew install bex-co/tap/bea        # macOS e Linuxbrew
$ uv tool install beancount-io       # em qualquer lugar com uv e Python 3.12 ou mais recente
$ bea --version
bea 0.2.0
bea 0.2.0
cli-v0.2.02026-09-12
motor
beancount 3.2.3 beanquery 0.2.0
opcional
beangulp 0.2.0 beanprice 2.1.0
python
3.12 3.14

O cartão de lançamento do 0.2.0: a tag e a data de publicação, as versões do Beancount e do Beanquery que o motor gerenciado fixa, os dois recursos opcionais do motor e as versões do Python nas quais o lançamento foi instalado e testado.

CampoValor
Versão0.2.0, tag cli-v0.2.0, publicada no PyPI e no tap Homebrew bex-co/homebrew-tap em 2026-09-12
Lançamento anterior0.1.0, com tag de 2026-09-09, três dias antes
Conjunto de mudanças27 commits tocando a CLI, 119 arquivos alterados, cerca de 12.300 linhas adicionadas e 2.100 removidas
Versões fixadas do motorBeancount 3.2.3 e Beanquery 0.2.0 no motor base; Beangulp 0.2.0 e Beanprice 2.1.0 como recursos opcionais
DestaqueToda ferramenta nativa do Beancount sob um único prefixo, servida por um motor gerenciado; o envelope JSON e o contrato de códigos de saída do 0.1.0 permanecem inalterados

O que mudou por baixo: o motor gerenciado

No 0.1.0, o bea importava o Beancount para dentro do seu próprio processo, como qualquer ferramenta Python faria. Funcionava, mas fazia do grafo de dependências da CLI o grafo de dependências do Beancount, e deixava "instale o Beancount primeiro" como um passo não escrito em todos os guias.

O 0.2.0 traça uma linha pelo meio do programa. O frontend bea, a parte que é dona dos comandos, das opções e da renderização, nunca carrega o Beancount, o Beanquery ou o código de relatórios do Fava incorporado. O trabalho com o razão local roda em um motor gerenciado: um ambiente Python separado que o bea provisiona a partir de um lock fixado por hash e inicia como interpretador filho. O frontend envia uma requisição JSON através dessa fronteira e renderiza o que volta. Você não instala o Beancount, não coloca ferramentas bean-* no seu path nem pensa em qual Python elas encontraram.

Como o motor chega depende do canal:

  • Homebrew cria os ambientes do frontend e do motor durante a instalação. Comandos locais usam o motor local do keg, sem nenhum download adicional.
  • PyPI (uv tool install ou pipx) provisiona no primeiro uso. O primeiro comando local que precisa do motor baixa a combinação fixada, o que exige acesso à rede e uv no path uma única vez. Os comandos seguintes o reutilizam offline a partir de ~/.local/share/bea/engine/<version>, ou sob XDG_DATA_HOME se você o definir.

Três propriedades decorrem desse design, e cada uma elimina um chamado de suporte que já vimos:

  1. As atualizações permanecem pareadas. bea upgrade delega a atualização ao gerenciador de pacotes que instalou esta cópia e depois reconstrói o motor correspondente, de modo que frontend e motor nunca podem divergir para versões diferentes.
  2. Um motor quebrado se cura sozinho. Se um provisionamento falha no meio do caminho, o ambiente gerenciado é descartado e reconstruído na próxima tentativa bem-sucedida. Binários bean-check perdidos em outro lugar do path são ignorados em vez de capturados por acidente.
  3. As peças opcionais pesadas continuam opcionais. O framework de importação Beangulp precisa da biblioteca de sistema libmagic, e o Beanprice traz dependências de busca de cotações. Nenhum dos dois está no motor base. Você os habilita explicitamente, apenas no motor.
$ bea engine status
$ bea engine enable beangulp     # auxiliares de ingestão; precisa da biblioteca de sistema libmagic
$ bea engine enable beanprice    # busca de cotações do bean-price

bea engine status informa se o motor está provisionado e quais recursos opcionais estão habilitados, e não precisa de rede para dizê-lo. Se um provisionamento de primeiro uso falhar, corrija a rede ou o uv e execute novamente qualquer comando local, como bea check. Não faça pip install beancount ao lado dele: o frontend não vai usá-lo.

Toda ferramenta nativa, um único prefixo

O motor é o mecanismo. A mudança visível ao usuário é a paridade: todo executável que o projeto Beancount upstream distribui agora tem um equivalente bea, com os mesmos argumentos repassados e a mesma saída preservada.

$ bea check                                    # bean-check, mais o envelope --json do bea
$ bea format main.bean -o clean.bean           # bean-format: stdout por padrão, -i reescreve
$ bea query "SELECT account, sum(position) GROUP BY account"
$ bea doctor context main.bean 2026-01-02      # todas as onze operações do bean-doctor
$ bea example --seed 1 -o example.beancount    # bean-example
$ bea treeify < balances.txt                   # treeify
$ bea ingest identify --config ingest.py inbox # Beangulp, após engine enable
$ bea price -e USD:yahoo/AAPL                  # bean-price, após engine enable
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

O mapa de paridade: os seis executáveis nativos do Beancount acima da linha tracejada funcionam de imediato; os dois abaixo dela são repassados ao Beangulp e ao Beanprice depois que você habilita esse recurso no motor.

Alguns deles merecem mais do que uma linha em uma tabela.

bea check é o bean-check com o envelope JSON do bea sobreposto: a mesma validação, as mesmas mensagens de erro e, sob --json, os mesmos campos valid e errors que os scripts já interpretam.

bea format mudou de comportamento, e é a única mudança deste lançamento que pode surpreender um script. No 0.1.0, bea format PATH reescrevia o arquivo. Agora ele imprime o texto formatado em stdout e deixa o arquivo intocado. --in-place (-i) é o que reescreve, --output FILE (-o) grava em outro lugar, --check é o portão de CI que sai com 1 quando há arquivos precisando de formatação, e --dry-run lista o que mudaria. Isso segue o bean-format, cujo padrão é o seguro: um comando que lê um caminho e o reescreve silenciosamente não pode ser experimentado antes. Formatar é uma transformação de texto, não um parse, então ele não recusa mais um arquivo com erro de sintaxe; alinha o que reconhece e deixa o resto como está. Execute bea check para validar.

bea query ganhou toda a superfície nativa. Aceita BQL como argumento, via stdin ou no shell interativo, que agora é o shell upstream do Beanquery iniciado como processo filho, com seus comandos .format, .output, .run e .set intactos. --format seleciona a renderização em text, csv ou beancount, --numberify divide os valores em uma coluna por moeda, -o grava em um arquivo e --source URI repassa uma fonte nativa do Beanquery diretamente.

bea doctor expõe todas as onze operações do bean-doctor: lex, parse, roundtrip, directories, list-options, print-options, context, linked, region, missing-open e display-context. Se você já depurou um problema de booking de lotes com bean-doctor context, é a mesma ferramenta no mesmo endereço.

bea example e bea treeify são o gerador nativo e o renderizador de árvore nativo, repassados como estão.

bea ingest e bea price repassam para identify, extract e archive do Beangulp e para o bean-price, respectivamente, após bea engine enable. O caminho CSV sem Python, bea import --csv, não precisa de nenhum dos dois e permanece inalterado.

Uma regra une os comandos repassados: doctor, example, treeify, price e ingest entregam seus argumentos ao upstream sem alteração e mantêm a saída e o status de saída do upstream. Isso também significa que eles recebem o razão como seu próprio argumento posicional, como em bea doctor lex main.bean, em vez de pelo --file global. O envelope e as categorias de códigos de saída abaixo descrevem os comandos próprios do bea.

O contrato em que os scripts podem continuar confiando

Nada na superfície legível por máquina mudou de lugar. O --json global ainda coloca um único envelope em stdout com bea, target, data e truncated, mais limit em listas limitadas e page em listas hospedadas paginadas. Valores são strings decimais, nunca floats, e datas são ISO YYYY-MM-DD. --json implica --no-input; o mesmo vale para um stdin que não é terminal ou para uma variável CI verdadeira, de modo que um job não supervisionado nunca fica esperando por um humano. --strict recusa respostas parciais mesmo em um terminal, e o --allow-errors de cada comando de leitura permite optar de volta.

Uma falha não escreve nada em stdout e exatamente um objeto em stderr:

{
  "error": {
    "category": "validation",
    "message": "Ledger has 3 error(s). Pass --allow-errors to report anyway.",
    "exit_code": 1,
    "details": ["main.bean:1: Transaction does not balance: (2.50 USD)"]
  }
}
0
ok
1
validation
2
usage
3
auth
4
conflict

Os cinco códigos de saída e a string category que cada um carrega no objeto de erro JSON. Um script ramifica pelo número; um humano lê a categoria.

CódigoCategoriaSignificado
0nenhumaSucesso, incluindo pré-visualizações e omissões intencionais de duplicatas
1validationErro de razão ou de validação, e o coletor geral para qualquer outra falha em tempo de execução
2usageArgumentos inválidos, um alvo ausente ou a mais, ou entrada necessária sob --no-input
3authFalha de autenticação ou de permissão, incluindo um destino somente leitura
4conflictUma alteração concorrente, uma importação que precisa de revisão de duplicatas, ou uma gravação cujo resultado é desconhecido

Dois detalhes importam para quem repete a operação após uma falha. Uma saída diferente de zero não significa universalmente que nada mudou: add transactions --partial pode gravar as linhas aceitas, format -i sobre vários arquivos pode reescrever alguns antes de falhar em um, e cloud ledger create --clone pode criar o razão antes que o clone falhe. Leia error.result antes de repetir uma mutação. E os comandos hospedados mapeiam o status HTTP do servidor para a mesma tabela, preservando a própria mensagem do servidor: 401 e 403 saem com 3, 400 sai com 2, 409 sai com 4, e todo o resto, incluindo limitação de taxa, sai com 1. Uma gravação cujo resultado a CLI não pode conhecer, como um timeout no meio de uma exclusão, sai com 4 e diz isso em vez de adivinhar.

O guia de automação percorre um pipeline jq por esse envelope de ponta a ponta.

Correções que vieram junto

Um lançamento de paridade também é uma oportunidade de fechar os defeitos que um primeiro lançamento revela. Estas entraram entre as duas tags, cada uma com um teste de regressão:

  • Números são escritos como texto de ponto fixo, nunca em notação científica, incluindo os saldos de abertura que bea init renderiza. Um razão que diz 1E+3 é tecnicamente válido e praticamente ilegível.
  • Lotes de custo sobrevivem à serialização JSON com suas datas e rótulos intactos, e os rótulos de lote são escapados corretamente quando uma transação é gravada.
  • Postings explícitos com zero são valores reais durante a importação, em vez de serem lidos como "omitido, por favor me balanceie".
  • Importações CSV passam por um único leitor estrito. A descoberta de cabeçalho costumava remover os espaços dos nomes das colunas enquanto a extração mantinha as chaves brutas, então um cabeçalho com espaços extras que a documentação prometia aceitar falhava como coluna ausente. Agora os nomes têm os espaços removidos uma única vez, uma coluna mapeada deve aparecer exatamente uma vez, e uma aspa não fechada falha com o número da linha antes que qualquer coisa seja gravada.
  • O BQL carrega o caminho exato do razão em vez de uma string de conexão interpretada como URL, de modo que caminhos incomuns são resolvidos do mesmo jeito que o resto da CLI os resolve.
  • bea balance <term> totaliza apenas o que mostra. Um pai retido não informa mais os totais de irmãos excluídos, uma posição sem preço não relacionada não faz mais falhar uma seleção em USD, e o envelope informa o filtro que foi aplicado. Um padrão --account malformado em relatórios sai com 2, o erro de uso que ele é.
  • O stderr em modo JSON é sempre um único objeto, mesmo quando avisos tolerados precedem a falha.
  • Credenciais hospedadas falham cedo e de forma consistente: um BEA_TOKEN contendo espaços em branco é rejeitado antes de qualquer requisição, uma credencial revogada é relatada da mesma maneira por cloud status e pelos comandos de razão, e owner/name é validado antes de um prompt de confirmação ou de uma chamada autenticada. cloud logout deixa BEA_TOKEN em paz, e cloud ledger list --json ecoa a página que realmente serviu.
  • A fórmula do Homebrew fixa a URL exata do artefato do PyPI, de modo que uma instalação pelo tap e uma instalação pelo PyPI são comprovadamente os mesmos bytes.

Como foi verificado antes de chegar até você

Um lançamento é uma afirmação, e o pipeline é a evidência. Uma tag cli-v0.2.0 precisa nomear um commit em main cuja versão no pyproject.toml coincida exatamente; o workflow recusa qualquer outra coisa, incluindo sufixos de pré-lançamento. A partir daí:

  1. A suíte completa de verificações roda primeiro. make check-all cobre lint, formatação, mypy estrito, detecção de código morto, a verificação de drift da referência gerada e a suíte de testes. O pull request do lançamento registra 635 testes passando.
  2. O lock do motor é exportado e fixado por hash, e a distribuição de código-fonte e o wheel são construídos uma única vez. Cada passo posterior testa exatamente esses artefatos, não uma reconstrução.
  3. Instalações limpas em três sistemas operacionais e dois Pythons. O wheel é instalado via uv tool e o sdist via pip no Linux, macOS e Windows, em Python 3.12 e 3.14, incluindo o extra opcional de IA. Um job do Homebrew instala o sdist por meio de um tap temporário no macOS e no Linux.
  4. A publicação é sequencial e sem tokens. O PyPI recebe os artefatos por publicação confiável, então não existe nenhum token de API de longa duração para vazar; o GitHub Release é criado com atestações de publicação anexadas; e Formula/bea.rb é enviado ao tap público com a URL e o hash do sdist que o PyPI de fato serviu.
  5. Testes de fumaça pós-publicação instalam a partir dos índices reais. Jobs separados instalam a versão fixada a partir do PyPI e do tap público e executam os mesmos testes de fumaça de cliente contra o executável instalado. Uma falha ali não reverte nada, mas significa que o lançamento precisa de atenção antes que alguém seja avisado sobre ele.

Este post está sendo escrito do outro lado do passo cinco.

Atualizando a partir do 0.1.0

Execute a atualização pelo gerenciador que instalou sua cópia, ou deixe o bea fazer isso:

$ bea upgrade --check      # informa as versões instalada e mais recente e o comando que seria executado
$ bea upgrade              # brew upgrade bea, uv tool upgrade beancount-io ou pipx upgrade beancount-io

Depois que o gerenciador termina, bea upgrade atualiza o motor gerenciado para que os dois permaneçam pareados. Em seguida, verifique três coisas:

  • Qualquer script que executava bea format PATH para reescrever um arquivo agora precisa de bea format -i PATH. O padrão antigo não podia ser pré-visualizado; o novo pode.
  • Qualquer script que dependia do format para capturar um erro de sintaxe deve chamar bea check para isso, porque a formatação não faz mais parse.
  • Instalações via PyPI precisam de rede e de uv uma única vez para o primeiro comando local após a atualização, para que o motor possa ser provisionado. Instalações via Homebrew não precisam de nada.

Tudo o que seus scripts já interpretam, as chaves do envelope, as strings decimais e os códigos de saída, permanece inalterado. O campo bea no envelope agora lê 0.2.0.

O que este lançamento não faz

  • O direcionamento a razões hospedados não está implementado. Não existe flag --ledger; comandos locais leem arquivos locais e nunca enviam um implicitamente. Razões hospedados são gerenciados sob bea cloud e trabalhados como clones git.
  • bea ask ainda precisa do extra ask e de credenciais do Beancount.io, e não suporta --json. A instalação padrão não carrega dependências de IA.
  • Beangulp e Beanprice são opcionais, e o Beangulp precisa da biblioteca de sistema libmagic. bea import --csv cobre extratos bancários sem nenhum dos dois.
  • Comandos nativos repassados não emitem o envelope. Se você precisa de saída estruturada de uma operação do doctor, esse é um pedido que gostaríamos de ouvir.

Desde a tag, main já incorporou a primeira rodada de QA do 0.2.0, que seguirá no próximo lançamento: bea format lê stdin como filtro e seu modo -o FILE responde com um envelope nomeando o que gravou; --json check recusa flags exclusivas do bean-check, e --json é recusado de imediato em doctor, example e treeify, para que um script não confunda texto nativo com um envelope; --json query -o FILE grava o envelope no arquivo atomicamente, com --numberify aplicado também ao JSON; bea engine status nomeia qual camada de motor está servindo; uma consulta BQL que começa com um comentário é executada; o --help de repasse nativo funciona antes que o motor seja provisionado; e o .output do shell de consulta restaura o fluxo original após um redirecionamento falho.

Para onde ir em seguida

Mantenha seus livros como código

Uma toolchain que você instala em uma linha é uma toolchain que você pode entregar a qualquer pessoa: um cofundador, um contador, um runner de CI, um agente de IA. O Beancount.io oferece contabilidade em texto simples que permanece transparente, versionada e reproduzível, com o bea como o comando que mantém um razão local honesto e o serviço hospedado como o lugar onde sua equipe, seu telefone e seu assistente encontram os mesmos livros. Instale o bea e execute sua primeira verificação, e se o lançamento fizer algo que você não esperava, o repositório no GitHub é onde queremos ouvir sobre isso.

Partilhar este artigo

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

Publicado: 16 de setembro de 2026