Якщо ви колись передавали колезі, новому ноутбуку чи нічному cron-завданню робочу конфігурацію Beancount, ви знаєте, що бухгалтерія ніколи не була складною частиною. Складною була інструментальна частина: Python, який підходить, bean-check і bean-query у PATH, бібліотека звітів, додана для одного балансового звіту, і форматувальник, який переписує ваші файли, щойно ви ставите йому запитання. bea 0.2.0, випущений 12 вересня 2026 року, замінює цей контрольний список одним встановленням. Команда bea тепер несе повний нативний інструментарій Beancount, запускає його всередині керованого рушія, який вона сама забезпечує, і зберігає машиночитаний контракт, на який уже покладаються скрипти та AI-агенти.
Це примітки до випуску 0.2.0, написані так, як ми відстежуємо випуск усередині: що вийшло, що змінилося під капотом, як це було перевірено до потрапляння в пакетний індекс, чого воно навмисно ще не робить і як оновитися. Якщо вам потрібна історія першого запуску, пост про запуск 0.1.0 і швидкий старт CLI — коротші варіанти.
Випуск одним поглядом
Два канали публікують ту саму команду. Виберіть один, потім підтвердьте, що він відповідає своєю версією:
$ brew install bex-co/tap/bea # macOS і Linuxbrew
$ uv tool install beancount-io # будь-де з uv і Python 3.12 або новішим
$ 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.14Картка випуску 0.2.0: тег і дата публікації, версії Beancount і Beanquery, які закріплює керований рушій, дві опційні функції рушія та версії Python, на яких випуск було встановлено й протестовано.
| Поле | Значення |
|---|---|
| Версія | 0.2.0, тег cli-v0.2.0, опубліковано в PyPI та Homebrew-тапі bex-co/homebrew-tap 2026-09-12 |
| Попередній випуск | 0.1.0, тег 2026-09-09, три дні раніше |
| Набір змін | 27 коммітів, які стосуються CLI, змінено 119 файлів, приблизно 12 300 рядків додано та 2 100 видалено |
| Закріплення рушія | Beancount 3.2.3 і Beanquery 0.2.0 у базовому рушії; Beangulp 0.2.0 і Beanprice 2.1.0 як опційні функції |
| Головне | Кожен нативний інструмент Beancount під одним префіксом, керований рушієм; JSON-конверт і контракт кодів виходу з 0.1.0 незмінні |
Що змінилося під капотом: керований рушій
У 0.1.0 bea імпортував Beancount у власний процес, як це робить будь-який Python-інструмент. Це працювало, але робило граф залежностей CLI графом залежностей Beancount і залишало «спершу встановіть Beancount» неписаним кроком у кожному посібнику.
0.2.0 проводить лінію через середину програми. Фронтенд bea, частина, яка володіє командами, опціями та відтворенням, ніколи не завантажує Beancount, Beanquery чи вбудований код звітів Fava. Локальна робота з реєстром виконується в керованому рушії: окремому середовищі Python, яке bea забезпечує з хеш-закріпленого лок-файлу та запускає як дочірній інтерпретатор. Фронтенд надсилає JSON-запит через цю межу та відтворює те, що повертається. Ви не встановлюєте Beancount, не додаєте bean-* інструменти до свого шляху та не думаєте, який Python вони знайшли.
Те, як з'являється рушій, залежить від каналу:
- Homebrew створює середовища фронтенду та рушія під час встановлення. Локальні команди використовують рушій у форматі keg-local без додаткових завантажень.
- PyPI (
uv tool installабо pipx) забезпечує при першому використанні. Перша локальна команда, якій потрібен рушій, завантажує закріплену комбінацію, що вимагає доступу до мережі таuvу PATH один раз. Пізніші команди використовують її офлайн із~/.local/share/bea/engine/<version>, або підXDG_DATA_HOME, якщо ви його встановили.
Три властивості випливають із цього дизайну, і кожна усуває один тікет підтримки, який ми вже бачили:
- Оновлення залишаються парними.
bea upgradeпередає оновлення менеджеру пакетів, який встановив цю копію, а потім перебудовує відповідний рушій, тож фронтенд і рушій ніколи не можуть розійтися на різні версії. - Зламаний рушій самовідновлюється. Якщо забезпечення провалюється на півдорозі, кероване середовище відкидається та перебудовується під час наступної успішної спроби. Зайві двійники
bean-checkв інших місцях шляху ігноруються, а не підхоплюються випадково. - Важкі опційні частини залишаються опційними. Фреймворк імпорту Beangulp потребує системної бібліотеки
libmagic, а Beanprice тягне залежності для отримання котирувань. Жодна з них не в базовому рушії. Ви вмикаєте їх явно, лише в рушії.
$ bea engine status
$ bea engine enable beangulp # помічники імпорту; потребує системної бібліотеку libmagic
$ bea engine enable beanprice # отримання котирувань bean-pricebea engine status повідомляє, чи рушій забезпечено та які опційні функції ввімкнено, і для цього не потрібна мережа. Якщо перше забезпечення провалюється, виправте мережу або uv і повторно запустіть будь-яку локальну команду, наприклад bea check. Не встановлюйте pip install beancount поруч: фронтенд його не використовуватиме.
Кожен нативний інструмент, один префікс
Рушій — це механізм. Зміна, яку бачить користувач, — це паритет: кожен виконуваний файл, який постачає вихідний проєкт Beancount, тепер має відповідник bea, з тими самими аргументами, переданими далі, і тим самим виводом.
$ bea check # bean-check, плюс JSON-конверт bea
$ bea format main.bean -o clean.bean # bean-format: stdout за замовчуванням, -i переписує
$ bea query "SELECT account, sum(position) GROUP BY account"
$ bea doctor context main.bean 2026-01-02 # усі одинадцять операцій bean-doctor
$ bea example --seed 1 -o example.beancount # bean-example
$ bea treeify < balances.txt # treeify
$ bea ingest identify --config ingest.py inbox # Beangulp, після engine enable
$ bea price -e USD:yahoo/AAPL # bean-price, після engine enablebean-checkbea checkbean-formatbea formatbean-querybea querybean-doctorbea doctorbean-examplebea exampletreeifybea treeifybeangulpbea ingest bea engine enable beangulpbean-pricebea price bea engine enable beanpriceКарта паритету: шість нативних виконуваних файлів Beancount вище пунктирної лінії працюють одразу; два нижче передаються до Beangulp і Beanprice після ввімкнення цієї функції в рушії.
Декілька з них заслуговують більшого, ніж рядок у таблиці.
bea check — це bean-check із накладеним JSON-конвертом bea: та сама валідація, ті самі повідомлення про помилки, і під --json ті самі поля valid та errors, які вже розбирають скрипти.
bea format змінив поведінку, і це єдина зміна в цьому випуску, яка може здивувати скрипт. У 0.1.0 bea format PATH переписував файл. Тепер він друкує відформатований текст у stdout і залишає файл недоторканим. --in-place (-i) — це те, що переписує, --output FILE (-o) записує в інше місце, --check — це CI-гейт, який виходить із кодом 1, коли файли потребують форматування, а --dry-run перелічує, що змінилося б. Це слідує за bean-format, чиє за замовчуванням безпечне: команда, яка читає шлях і мовчки переписує його, не може бути спершу випробувана. Форматування — це текстова трансформація, а не парсинг, тому воно більше не відмовляє у файлі з синтаксичною помилкою; воно вирівнює те, що розпізнає, а решту залишає. Запустіть bea check для перевірки дійсності.
bea query розширився до всієї нативної поверхні. Він приймає BQL як аргумент, зі stdin або в інтерактивній оболонці, яка тепер є вихідною оболонкою Beanquery, запущеною як дочірній процес із її командами .format, .output, .run та .set незмінними. --format вибирає відтворення text, csv або beancount, --numberify розбиває суми на один стовпець на валюту, -o записує у файл, а --source URI передає нативне джерело Beanquery напряму.
bea doctor відкриває всі одинадцять операцій bean-doctor: lex, parse, roundtrip, directories, list-options, print-options, context, linked, region, missing-open та display-context. Якщо ви колись налагоджували проблему з проведенням за допомогою bean-doctor context, це той самий інструмент за тією ж адресою.
bea example і bea treeify — це нативний генератор і нативний відтворювач дерева, передані без змін.
bea ingest і bea price передаються до identify, extract і archive від Beangulp та до bean-price відповідно, після bea engine enable. Шлях без Python для CSV, bea import --csv, не потребує жодного з них і незмінний.
Одне правило пов'язує передані команди: doctor, example, treeify, price та ingest передають свої аргументи вихідним інструментам без змін і зберігають їхній вивід і статус виходу. Це також означає, що вони приймають реєстр як власний позиційний аргумент, як у bea doctor lex main.bean, а не через глобальний --file. Конверт і категорії кодів виходу нижче описують власні команди bea.
Контракт, якому скрипти можуть далі довіряти
Нічого в машиночитаній поверхні не зрушилося. Глобальний --json досі розміщує один конверт у stdout із bea, target, data та truncated, плюс limit для обмежених списків і page для посторінкових розміщених списків. Суми — це десяткові рядки, ніколи не числа з плаваючою комою, а дати — у форматі ISO YYYY-MM-DD. --json передбачає --no-input; так само роблять нетермінальний stdin або істинна змінна CI, тож бездоглядна робота ніколи не чекає на людину. --strict відмовляється від часткових відповідей навіть у терміналі, а --allow-errors кожної команди читання повертає це назад.
Збій нічого не записує в stdout і рівно один об'єкт у 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)"]
}
}П'ять кодів виходу та рядок category, який кожен несе в JSON-об'єкті помилки. Скрипт розгалужується за номером; людина читає категорію.
| Код | Категорія | Значення |
|---|---|---|
| 0 | немає | Успіх, включно з попередніми переглядами та навмисним пропуском дублікатів |
| 1 | validation | Помилка реєстру або валідації, а також загальний для будь-якого іншого збою під час виконання |
| 2 | usage | Неправильні аргументи, відсутній цільовий об'єкт або зайве, або потрібен ввід під --no-input |
| 3 | auth | Помилка автентифікації або дозволу, включно з призначенням лише для читання |
| 4 | conflict | Одночасна зміна, імпорт, що потребує перегляду дублікатів, або запис, результат якого невідомий |
Дві деталі важливі для тих, хто повторює спробу після збою. Ненульовий вихід не завжди означає, що нічого не змінилося: add transactions --partial може записати прийняті рядки, format -i над кількома файлами може переписати деякі перед збоєм на одному, а cloud ledger create --clone може створити реєстр до того, як клонування провалиться. Прочитайте error.result перед повторенням мутації. І розміщені команди зіставляють HTTP-статус сервера з тією самою таблицею, зберігаючи власне повідомлення сервера: 401 і 403 виходять із кодом 3, 400 — із кодом 2, 409 — із кодом 4, а все інше, включно з обмеженням швидкості, — із кодом 1. Запис, результат якого CLI не може знати, наприклад тайм-аут під час видалення, виходить із кодом 4 і так і каже, а не вгадує.
Посібник з автоматизації проводить конвеєр jq через цей конверт від початку до кінця.
Виправлення, які поїхали разом
Випуск паритету — це також можливість закрити дефекти, які виявляє перший випуск. Вони потрапили між двома тегами, кожен із регресійним тестом:
- Числа записуються як текст із фіксованою комою, ніколи не в науковій нотації, включно з початковими залишками, які відтворює
bea init. Реєстр, який каже1E+3, технічно дійсний, але практично нечитабельний. - Партії витрат переживають JSON-серіалізацію із незмінними датами та мітками, а мітки партій правильно екрануються під час запису транзакції.
- Явні нульові проводки є справжніми сумами під час імпорту, а не читаються як «опущено, будь ласка, збалансуйте мене».
- CSV-імпорти проходять через один строгий читач. Раніше виявлення заголовка зрізало назви стовпців, тоді як витяг зберігало сирі ключі, тож доповнений заголовок, який документація обіцяла приймати, провалювався як відсутній стовпець. Тепер назви зрізаються один раз, зіставлений стовпець має з'являтися рівно один раз, а незакрита цитата провалюється з номером рядка до будь-якого запису.
- BQL завантажує точний шлях реєстру, а не рядок підключення, розібраний як URL, тож незвичайні шляхи розв'язуються так само, як решта CLI.
bea balance <term>підсумовує лише те, що показує. Збережений батьківський рахунок більше не звітує про підсумки виключених дочірніх, непов'язане неоцінене утримання більше не провалює вибірку USD, а конверт повідомляє про застосований фільтр. Неправильний шаблон--accountу звітах виходить із кодом 2 як помилку використання.- Stderr у JSON-режимі — завжди один об'єкт, навіть коли попередньо йдуть прийняті попередження.
- Розміщені облікові дані провалюються рано й послідовно:
BEA_TOKEN, що містить пробіли, відхиляється до будь-якого запиту, відкликані облікові дані повідомляються однаково командоюcloud statusі командами реєстру, аowner/nameперевіряється перед підказкою підтвердження чи автентифікованим викликом.cloud logoutзалишаєBEA_TOKENнедоторканим, аcloud ledger list --jsonвідлунює сторінку, яку він фактично обслужив. - Формула Homebrew закріплює точний URL артефакту PyPI, тож встановлення через tap і встановлення через PyPI є доказово тими самими байтами.
Як це було перевірено до того, як ви це побачили
Випуск — це твердження, а конвеєр — це доказ. Тег cli-v0.2.0 має називати комміт у main, чия версія pyproject.toml збігається точно; робочий процес відмовляє в усьому іншому, включно з попередніми суфіксами релізів. Звідти:
- Спочатку виконується повний набір перевірок.
make check-allохоплює лінт, форматування, суворий mypy, виявлення мертвого коду, перевірку дрейфу згенерованого довідника та тестовий набір. Пулликвест випуску фіксує 635 успішних тестів. - Лок-файл рушія експортується та хеш-закріплюється, а дистрибутив джерела та колесо будуються один раз. Кожен наступний крок тестує саме ці артефакти, а не перебудову.
- Чисті встановлення на трьох операційних системах і двох Python. Колесо встановлюється через
uv tool, а sdist — черезpipна Linux, macOS і Windows, на Python 3.12 і 3.14, включно з опційним AI додатком. Завдання Homebrew встановлює sdist через тимчасовий tap на macOS і Linux. - Публікація послідовна та без токенів. PyPI отримує артефакти через довірену публікацію, тож жодного довгоживучого API-токена не існує для витоку; GitHub Release створюється з прикріпленими атестаціями публікації; а
Formula/bea.rbнадсилається до публічного tap з URL і хешем sdist, які PyPI фактично обслужив. - Післяпублікаційні смоук-тести встановлюють із реальних індексів. Окремі завдання встановлюють закріплену версію з PyPI та з публічного tap і запускають ті самі тести клієнтського смоуку проти встановленого виконуваного файлу. Збій там нічого не відкочує, але означає, що випуск потребує уваги, перш ніж про нього комусь повідомлять.
Цей пост пишеться по той бік кроку п'ятого.
Оновлення з 0.1.0
Запустіть оновлення через менеджер, який встановив вашу копію, або нехай bea це зробить:
$ bea upgrade --check # звітуйте про встановлену та останню версії та команду, яка б виконалась
$ bea upgrade # brew upgrade bea, uv tool upgrade beancount-io, або pipx upgrade beancount-ioПісля того, як менеджер завершиться, bea upgrade оновлює керований рушій, щоб вони залишалися парними. Потім перевірте три речі:
- Будь-який скрипт, який запускав
bea format PATHдля переписування файлу, тепер потребуєbea format -i PATH. Старий варіант за замовчуванням не можна було переглянути, а новий можна. - Будь-який скрипт, який покладався на
formatдля виявлення синтаксичної помилки, має викликати для цьогоbea check, бо форматування більше не парсить. - Встановлення через PyPI потребують мережі та
uvодин раз для першої локальної команди після оновлення, щоб рушій міг бути забезпечений. Встановлення через Homebrew нічого не потребують.
Усе, що ваші скрипти вже розбирають, ключі конверта, десяткові рядки та коди виходу, незмінне. Поле bea у конверті тепер читає 0.2.0.
Чого цей випуск не робить
- Розміщене цілеспрямування не реалізовано. Немає прапора
--ledger; локальні команди читають локальні файли та ніколи не завантажують один імпліцитно. Розміщені реєстри керуються підbea cloudі обробляються як git-клони. bea askдосі потребує додаткаaskі облікових даних Beancount.io, і він не підтримує--json. Стандартне встановлення не несе жодних AI-залежностей.- Beangulp і Beanprice є опційними, а Beangulp потребує системної бібліотеки
libmagic.bea import --csvпокриває банківські експорти без жодної з них. - Передані нативні команди не генерують конверт. Якщо вам потрібен структурований вивід від операції doctor, це запит, який ми хотіли б почути.
Відтоді, як тег, main уже взяв перший раунд QA для 0.2.0, і він поїде з наступним випуском: bea format читає stdin як фільтр, а його режим -o FILE відповідає конвертом, який називає те, що він записав; --json check відмовляє прапорам, характерним лише для bean-check, а --json відмовляється повністю на doctor, example і treeify, щоб скрипт не міг прийняти нативний текст за конверт; --json query -o FILE записує конверт у файл атомарно, із --numberify, застосованим також до JSON; bea engine status називає, який рівень рушія обслуговує; BQL-запит, що відкривається коментарем, виконується; нативний --help із наскрізною передачею працює до забезпечення рушія; а .output оболонки запитів відновлює вихідний потік після невдалого перенаправлення.
Куди далі
- Швидкий старт CLI: встановлення, перший реєстр, перша покупка, перша перевірка балансу.
- Ваш перший місяць із bea: від
initдо звіту за звірений кінець місяця. - Імпорт банківських експортів: CSV-шлях без Python, файли правил і Python-імпортери.
- Автоматизуйте бухгалтерію з bea: розв'язання реєстру, читання конверта, розгалуження за кодами виходу, планування.
- Довідник CLI Beancount: кожна команда, опція, змінна середовища та код виходу, перевірені проти згенерованого довідника CLI.
- Дайте своєму AI-агенту реєстр: проходження, орієнтоване на агентів, із запуску 0.1.0.
- Журнал змін: кожен випуск, новіші першими.
Тримайте свої книги як код
Інструментарій, який можна встановити за один рядок, — це інструментарій, який можна передати будь-кому: співзасновнику, бухгалтеру, CI-ранеру, AI-агенту. Beancount.io надає бухгалтерію в звичайному тексті, яка залишається прозорою, під версійним контролем і відтворюваною, із bea як командою, що тримає локальний реєстр чесним, а розміщений сервіс — місцем, де ваша команда, ваш телефон і ваш помічник зустрічають ті самі книги. Встановіть bea та запустіть першу перевірку, і якщо випуск робить щось, чого ви не очікували, репозиторій GitHub — це місце, де ми хочемо це почути.





