Saltar al contenido principal

bea 0.2.0: una instalación, toda la cadena de herramientas de Beancount

Publicado 19 min de lecturaMike ThriftMike Thrift
bea 0.2.0: una instalación, toda la cadena de herramientas de Beancount
En esta página

Si alguna vez le has entregado a un colega, a un portátil nuevo o a un trabajo cron nocturno una configuración funcional de Beancount, sabes que la contabilidad nunca fue la parte difícil. La parte difícil era la cadena de herramientas: un Python que coincida, bean-check y bean-query en la ruta, una biblioteca de informes añadida para un solo balance, y un formateador que reescribe tus archivos en cuanto le haces una pregunta. bea 0.2.0, lanzado el 12 de septiembre de 2026, reemplaza esa lista de verificación con una sola instalación. El comando bea ahora lleva la cadena de herramientas nativa completa de Beancount, la ejecuta dentro de un motor gestionado que se aprovisiona a sí mismo, y mantiene el contrato legible por máquina del que ya dependen los scripts y los agentes de IA.

Esta es la nota de lanzamiento para 0.2.0, escrita de la forma en que seguimos un lanzamiento internamente: qué se publicó, qué cambió por debajo, cómo se verificó antes de llegar a un índice de paquetes, qué deliberadamente aún no hace, y cómo actualizar. Si quieres la historia del primer uso en su lugar, la publicación de lanzamiento de 0.1.0 y la guía de inicio rápido de CLI son las lecturas más cortas.

El lanzamiento de un vistazo

Dos canales publican el mismo comando. Elige uno y luego confirma que responde con su versión:

$ brew install bex-co/tap/bea        # macOS y Linuxbrew
$ uv tool install beancount-io       # en cualquier lugar con uv y Python 3.12 o más reciente
$ 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

La tarjeta de lanzamiento 0.2.0: la etiqueta y la fecha de publicación, las versiones de Beancount y Beanquery que fija el motor gestionado, las dos características opcionales del motor y las versiones de Python en las que se instaló y probó el lanzamiento.

CampoValor
Versión0.2.0, etiqueta cli-v0.2.0, publicada en PyPI y en el tap de Homebrew bex-co/homebrew-tap el 2026-09-12
Lanzamiento anterior0.1.0, etiquetado 2026-09-09, tres días antes
Conjunto de cambios27 commits que tocan la CLI, 119 archivos modificados, aproximadamente 12.300 líneas añadidas y 2.100 eliminadas
Fijaciones del motorBeancount 3.2.3 y Beanquery 0.2.0 en el motor base; Beangulp 0.2.0 y Beanprice 2.1.0 como características opcionales
TitularCada herramienta nativa de Beancount bajo un solo prefijo, servida por un motor gestionado; el sobre JSON y el contrato de códigos de salida de 0.1.0 no cambian

Qué cambió por debajo: el motor gestionado

En 0.1.0, bea importaba Beancount en su propio proceso, como lo haría cualquier herramienta de Python. Eso funcionaba, pero hacía que el gráfico de dependencias de la CLI fuera el gráfico de dependencias de Beancount, y dejaba "instala Beancount primero" como un paso no escrito en cada guía.

0.2.0 traza una línea en el medio del programa. El frontend de bea, la parte que controla los comandos, las opciones y el renderizado, nunca carga Beancount, Beanquery ni el código de informes de Fava incluido. El trabajo de libro local se ejecuta en un motor gestionado: un entorno de Python separado que bea aprovisiona desde un bloqueo fijado por hash y lanza como un intérprete hijo. El frontend envía una solicitud JSON a través de ese límite y renderiza lo que regresa. No instalas Beancount, pones las herramientas bean-* en tu ruta ni piensas en qué Python encontraron.

Cómo llega el motor depende del canal:

  • Homebrew crea los entornos de frontend y motor durante la instalación. Los comandos locales utilizan el motor local de keg sin más descargas.
  • PyPI (uv tool install o pipx) se aprovisiona en el primer uso. El primer comando local que necesita el motor descarga la combinación fijada, lo que requiere acceso a la red y uv en la ruta una vez. Los comandos posteriores lo reutilizan sin conexión desde ~/.local/share/bea/engine/<version>, o bajo XDG_DATA_HOME si lo configuras.

Tres propiedades se derivan de ese diseño, y cada una elimina un ticket de soporte que ya hemos visto:

  1. Las actualizaciones se mantienen emparejadas. bea upgrade entrega la actualización al gestor de paquetes que instaló esta copia y luego reconstruye el motor correspondiente, de modo que un frontend y un motor nunca pueden desviarse a versiones diferentes.
  2. Un motor roto se cura a sí mismo. Si un aprovisionamiento falla a mitad de camino, el entorno gestionado se descarta y se reconstruye en el siguiente intento exitoso. Los binarios bean-check sueltos en otros lugares de la ruta se ignoran en lugar de recogerse por accidente.
  3. Las piezas opcionales pesadas siguen siendo opcionales. El marco de importación de Beangulp necesita la biblioteca del sistema libmagic, y Beanprice incluye dependencias de obtención de cotizaciones. Ninguna está en el motor base. Las habilitas explícitamente, solo en el motor.
$ bea engine status
$ bea engine enable beangulp     # ayudantes de ingesta; necesita la biblioteca del sistema libmagic
$ bea engine enable beanprice    # obtención de cotizaciones de bean-price

bea engine status informa si el motor está aprovisionado y qué características opcionales están habilitadas, y no necesita red para decirlo. Si un aprovisionamiento de primer uso falla, arregla la red o uv y vuelve a ejecutar cualquier comando local como bea check. No hagas pip install beancount junto a él: el frontend no lo usará.

Cada herramienta nativa, un prefijo

El motor es el mecanismo. El cambio orientado al usuario es la paridad: cada ejecutable que el proyecto upstream de Beancount distribuye ahora tiene una contraparte bea, con los mismos argumentos reenviados y la misma salida conservada.

$ bea check                                    # bean-check, más el sobre --json de bea
$ bea format main.bean -o clean.bean           # bean-format: stdout por defecto, -i reescribe
$ bea query "SELECT account, sum(position) GROUP BY account"
$ bea doctor context main.bean 2026-01-02      # las once operaciones de bean-doctor
$ bea example --seed 1 -o example.beancount    # bean-example
$ bea treeify < balances.txt                   # treeify
$ bea ingest identify --config ingest.py inbox # Beangulp, después de engine enable
$ bea price -e USD:yahoo/AAPL                  # bean-price, después de 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

El mapa de paridad: los seis ejecutables nativos de Beancount por encima de la línea discontinua funcionan de inmediato; los dos debajo se reenvían a Beangulp y Beanprice una vez que habilitas esa característica en el motor.

Algunos de estos merecen más que una fila en una tabla.

bea check es bean-check con el sobre JSON de bea añadido encima: la misma validación, los mismos mensajes de error y, bajo --json, los mismos campos valid y errors que los scripts ya analizan.

bea format cambió su comportamiento, y es el único cambio en este lanzamiento que puede sorprender a un script. En 0.1.0, bea format PATH reescribía el archivo. Ahora imprime el texto formateado en stdout y deja el archivo en paz. --in-place (-i) es lo que reescribe, --output FILE (-o) escribe en otro lugar, --check es la puerta de CI que sale con 1 cuando los archivos necesitan formato, y --dry-run enumera lo que cambiaría. Esto sigue a bean-format, cuyo valor predeterminado es el seguro: un comando que lee una ruta y la reescribe silenciosamente no se puede probar primero. El formato es una transformación de texto, no un análisis, por lo que ya no rechaza un archivo con un error de sintaxis; alinea lo que reconoce y deja el resto. Ejecuta bea check para la validez.

bea query creció con toda la superficie nativa. Toma BQL como argumento, desde stdin o en el shell interactivo, que ahora es el shell upstream de Beanquery lanzado como un proceso hijo con sus comandos .format, .output, .run y .set intactos. --format selecciona el renderizado text, csv o beancount, --numberify divide los montos en una columna por moneda, -o escribe en un archivo y --source URI entrega una fuente nativa de Beanquery directamente.

bea doctor expone las once operaciones de bean-doctor: lex, parse, roundtrip, directories, list-options, print-options, context, linked, region, missing-open y display-context. Si alguna vez has depurado un problema de registro con bean-doctor context, es la misma herramienta en la misma dirección.

bea example y bea treeify son el generador nativo y el renderizador de árbol nativo, reenviados tal cual.

bea ingest y bea price reenvían a identify, extract y archive de Beangulp y a bean-price, respectivamente, después de bea engine enable. La ruta CSV sin Python, bea import --csv, no necesita ninguna de las dos y no cambia.

Una regla une los comandos reenviados: doctor, example, treeify, price e ingest pasan sus argumentos a upstream sin cambios y mantienen la salida y el estado de salida de upstream. Eso también significa que toman el libro mayor como su propio argumento posicional, como en bea doctor lex main.bean, en lugar de a través del --file global. El sobre y las categorías de códigos de salida a continuación describen los propios comandos de bea.

El contrato en el que los scripts pueden seguir confiando

Nada sobre la superficie legible por máquina se movió. --json global todavía pone un solo sobre en stdout con bea, target, data y truncated, más limit en listas limitadas y page en listas alojadas paginadas. Los montos son cadenas decimales, nunca flotantes, y las fechas son ISO AAAA-MM-DD. --json implica --no-input; también un stdin no terminal o una variable CI verdadera, para que un trabajo desatendido nunca espere a un humano. --strict se niega a dar respuestas parciales incluso en una terminal, y el --allow-errors de cada comando de lectura opta por volver a entrar.

Un fallo escribe nada en stdout y exactamente un objeto en 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

Los cinco códigos de salida y la cadena category que cada uno lleva en el objeto de error JSON. Un script se bifurca en el número; un humano lee la categoría.

CódigoCategoríaSignificado
0ningunaÉxito, incluidos avances y omisiones de duplicados intencionales
1validationError del libro mayor o de validación, y la opción general para cualquier otro fallo en tiempo de ejecución
2usageArgumentos incorrectos, un objetivo o extra faltante, o entrada necesaria bajo --no-input
3authFalla de autenticación o permiso, incluido un destino de solo lectura
4conflictUn cambio concurrente, una importación que necesita revisión de duplicados o una escritura cuyo resultado es desconocido

Dos detalles importan para cualquiera que reintente tras un fallo. Un código de salida distinto de cero no significa universalmente que nada cambió: add transactions --partial puede escribir las filas aceptadas, format -i en varios archivos puede reescribir algunos antes de fallar en uno, y cloud ledger create --clone puede crear el libro mayor antes de que falle el clon. Lee error.result antes de reintentar una mutación. Y los comandos alojados mapean el código de estado HTTP del servidor en la misma tabla, manteniendo el propio mensaje del servidor: 401 y 403 salen con 3, 400 con 2, 409 con 4, y todo lo demás, incluida la limitación de velocidad, sale con 1. Una escritura cuyo resultado la CLI no puede conocer, como un tiempo de espera a mitad de una eliminación, sale con 4 y lo dice en lugar de adivinar.

La guía de automatización recorre una canalización de jq a través de este sobre de principio a fin.

Correcciones que vinieron de paso

Un lanzamiento de paridad también es una oportunidad para cerrar los defectos que un primer lanzamiento saca a la luz. Estos aterrizaron entre las dos etiquetas, cada uno con una prueba de regresión:

  • Los números se escriben como texto de punto fijo, nunca notación científica, incluidos los saldos de apertura que renderiza bea init. Un libro que dice 1E+3 es técnicamente válido y prácticamente ilegible.
  • Los lotes de costo sobreviven a la serialización JSON con sus fechas y etiquetas intactas, y las etiquetas de lote se escapan correctamente cuando se escribe una transacción.
  • Los asientos explícitos de cero son montos reales durante la importación, en lugar de leerse como "omitidos, por favor equílibrame".
  • Las importaciones CSV pasan por un lector estricto único. El descubrimiento de encabezados solía eliminar los nombres de columna mientras la extracción mantenía las claves crudas, por lo que un encabezado rellenado que los documentos prometían aceptar fallaba como columna faltante. Ahora los nombres se eliminan una vez, una columna mapeada debe aparecer exactamente una vez, y una comilla sin cerrar falla con su número de línea antes de que se escriba nada.
  • BQL carga la ruta exacta del libro mayor en lugar de una cadena de conexión analizada por URL, por lo que las rutas inusuales se resuelven como el resto de la CLI las resuelve.
  • bea balance <término> totaliza solo lo que muestra. Un padre retenido ya no informa los totales de hermanos excluidos, una tenencia no valorada no relacionada ya no falla una selección de USD, y el sobre informa el filtro que se aplicó. Un patrón --account malformado en informes sale con 2 como el error de uso que es.
  • El stderr en modo JSON es siempre un objeto, incluso cuando las advertencias toleradas preceden al fallo.
  • Las credenciales alojadas fallan temprano y de manera consistente: un BEA_TOKEN que contiene espacios en blanco se rechaza antes de cualquier solicitud, una credencial revocada se informa de la misma manera por cloud status y por los comandos del libro mayor, y owner/name se valida antes de un aviso de confirmación o una llamada autenticada. cloud logout deja BEA_TOKEN solo, y cloud ledger list --json repite la página que realmente sirvió.
  • La fórmula de Homebrew fija la URL exacta del artefacto de PyPI, por lo que una instalación de tap y una instalación de PyPI son comprobablemente los mismos bytes.

Cómo se verificó antes de que lo vieras

Un lanzamiento es una afirmación, y la canalización es la evidencia. Una etiqueta cli-v0.2.0 tiene que nombrar un commit en main cuya versión de pyproject.toml coincida exactamente; el flujo de trabajo se niega a cualquier otra cosa, incluidos los sufijos de prelanzamiento. A partir de ahí:

  1. La suite de verificación completa se ejecuta primero. make check-all cubre lint, formato, mypy estricto, detección de código muerto, la verificación de desviación de referencia generada y la suite de pruebas. La solicitud de extracción del lanzamiento registra 635 pruebas pasando.
  2. El bloqueo del motor se exporta y se fija por hash, y la distribución de origen y la rueda se construyen una vez. Cada paso posterior prueba esos artefactos exactos, no una reconstrucción.
  3. Instalaciones limpias en tres sistemas operativos y dos Pythons. La rueda se instala a través de uv tool y el sdist a través de pip en Linux, macOS y Windows, en Python 3.12 y 3.14, incluido el extra de IA opcional. Un trabajo de Homebrew instala el sdist a través de un tap temporal en macOS y Linux.
  4. La publicación es secuencial y sin tokens. PyPI recibe los artefactos a través de publicación confiable, por lo que no existe un token API de larga duración que pueda filtrarse; la Release de GitHub se crea con atestaciones de publicación adjuntas; y Formula/bea.rb se empuja al tap público con la URL del sdist y el hash que PyPI realmente sirvió.
  5. Las pruebas de humo posteriores a la publicación se instalan desde los índices reales. Trabajos separados instalan la versión fijada desde PyPI y desde el tap público y ejecutan las mismas pruebas de humo de clientes contra el ejecutable instalado. Un fallo allí no revierte nada, pero significa que el lanzamiento necesita atención antes de que se le diga a alguien.

Esta publicación se está escribiendo al otro lado del paso cinco.

Actualización desde 0.1.0

Ejecuta la actualización a través del gestor que instaló tu copia, o deja que bea lo haga:

$ bea upgrade --check      # informa las versiones instalada y más reciente y el comando que se ejecutaría
$ bea upgrade              # brew upgrade bea, uv tool upgrade beancount-io, o pipx upgrade beancount-io

Después de que el gestor termine, bea upgrade refresca el motor gestionado para que los dos se mantengan emparejados. Luego verifica tres cosas:

  • Cualquier script que ejecutara bea format PATH para reescribir un archivo ahora necesita bea format -i PATH. El antiguo valor predeterminado no se podía previsualizar, y el nuevo sí.
  • Cualquier script que dependiera de format para detectar un error de sintaxis debería llamar a bea check para eso, porque el formato ya no analiza.
  • Las instalaciones de PyPI necesitan red y uv una vez para el primer comando local después de la actualización, para que el motor pueda aprovisionarse. Las instalaciones de Homebrew no necesitan nada.

Todo lo que tus scripts ya analizan, las claves del sobre, las cadenas decimales y los códigos de salida, no cambia. El campo bea en el sobre ahora lee 0.2.0.

Lo que este lanzamiento no hace

  • El direccionamiento alojado no está implementado. No hay flag --ledger; los comandos locales leen archivos locales y nunca cargan uno implícitamente. Los libros alojados se gestionan bajo bea cloud y se trabajan como clones de git.
  • bea ask todavía necesita el extra ask y credenciales de Beancount.io, y no soporta --json. La instalación predeterminada no lleva dependencias de IA.
  • Beangulp y Beanprice son opcionales, y Beangulp necesita la biblioteca del sistema libmagic. bea import --csv cubre las exportaciones bancarias sin ninguna de las dos.
  • Los comandos nativos reenviados no emiten el sobre. Si necesitas salida estructurada de una operación de doctor, esa es una solicitud que nos gustaría escuchar.

Desde la etiqueta, main ya ha recogido la primera ronda de QA sobre 0.2.0, y viajará en el próximo lanzamiento: bea format lee stdin como un filtro y su modo -o FILE responde con un sobre que nombra lo que escribió; --json check rechaza flags solo de bean-check, y --json se rechaza por completo en doctor, example y treeify para que un script no confunda texto nativo con un sobre; --json query -o FILE escribe el sobre en el archivo atómicamente, con --numberify aplicado también al JSON; bea engine status nombra qué nivel de motor está sirviendo; una consulta BQL que comienza con un comentario se ejecuta; el --help nativo de paso directo funciona antes de que se aprovisione el motor; y el .output del shell de consultas restaura el flujo original después de una redirección fallida.

Dónde ir a continuación

Mantén tus libros como código

Una cadena de herramientas que puedes instalar en una línea es una cadena de herramientas que puedes entregar a cualquiera: un cofundador, un contable, un ejecutor de CI, un agente de IA. Beancount.io proporciona contabilidad en texto plano que permanece transparente, controlada por versiones y reproducible, con bea como el comando que mantiene un libro local honesto y el servicio alojado como el lugar donde tu equipo, tu teléfono y tu asistente se encuentran con los mismos libros. Instala bea y ejecuta tu primera verificación, y si el lanzamiento hace algo que no esperabas, el repositorio de GitHub es donde queremos escucharlo.

Comparte este artículo

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

Publicado: 16 de septiembre de 2026