# bokf Self-hosted bookkeeping for Swedish organizations. One C daemon, one SQLite file, one JSON API shared by the CLI, the ncurses UI and LLM agents. Status: **0.1.0-dev — working, not production-proven.** See `docs/COMPLIANCE.md` §9. ## Features Everything below lives in the daemon and one SQLite file; every feature is a JSON command in `docs/PROTOCOL.md`, so the TUI, the CLI and an AI agent can do the same things. **Bookkeeping** - BAS chart of accounts (K2 and K3 variants), per organization; multi-org with owner/bookkeeper/viewer roles. - Verifikat with unbroken numbering per series and fiscal year, balanced rows in integer öre, ändringsverifikat for corrections — nothing is ever edited or deleted. - Konteringsmallar: reusable templates with a small formula language (`x*0.25`, `-x`), applied at posting time. - Underlag: attachments stored in the database (SHA-256 verified, append-only), an inbox for unlinked documents. - Fiscal years, period locks, ingående balans, year-end close. **Reports and filing** - Saldobalans, resultatrapport, balansrapport, huvudbok, verifikationslista — Kapitas-style tables in the TUI, JSON on the API. - Momsrapport ruta för ruta with per-org editable rules; eSKD XML export for the momsdeklaration. - Bokslut: automated year-end postings (avskrivningar, periodiseringsfond, skatt, resultatdisposition) from a dry-run plan. - Årsredovisning K2 text draft and INK2/SRU files (INK2, INK2R, INK2S). - SIE 4 import and export (CP437), multi-year history import. **Invoicing, bank and payroll** - Customer register; invoices rendered as PDF (own renderer, no dependencies), OCR numbers, issue = number + PDF + voucher in one transaction, e-mail over SMTP with the password encrypted at rest. - Bank reconciliation: import SEB CSV statements, match against vouchers, create prefilled vouchers from unmatched transactions. - Payroll engine: employees, Skatteverket tax tables, monthly runs with preview/post, payslips (PDF + e-mail) and AGI underlag — API and TUI complete (Lönekörningar, Anställda, Skattetabeller). **Compliance and operations** (`docs/COMPLIANCE.md`) - SHA-256 hash chains over vouchers and the audit log; `audit.verify` recomputes them and re-hashes attachments. - Every mutation is audited; secrets never reach the log. - `backup.snapshot` (`VACUUM INTO`) for consistent backups; pre-migration snapshots before schema upgrades. - Unix socket, optional TLS listener with certificate reload, Argon2id passwords, scoped revocable API tokens, login rate limiting. ## Build Requirements: Linux, a C11 compiler, GNU make, libncursesw for the TUI and OpenSSL (`libssl-dev`) for TLS and SMTP. SQLite, yyjson, SHA-256 and Argon2 are vendored. ```sh make -j$(nproc) make test ``` `make test` runs the server/protocol suite, the TUI unit tests and a docs/code consistency check. Extra targets: `make test-asan` / `make test-ubsan` (sanitizers) and `make test-pty` (drives the real TUI over a pty against a throwaway `/tmp` daemon and asserts on screen text). Produces `build/bokfd` (daemon), `build/bokfctl` (CLI) and `build/bokftui` (ncurses UI). Builds on x86_64 and arm64 (Armbian, Raspberry Pi, …). ## Run ```sh ./build/bokfd init --db /var/lib/bokfd/bokfd.db --user admin # non-interactive, same command with the password in the environment: BOKFD_PASSWORD=secret ./build/bokfd init --db /var/lib/bokfd/bokfd.db \ --user admin ./build/bokfd --db /var/lib/bokfd/bokfd.db --socket /run/bokfd/bokfd.sock ``` In another shell, the terminal UI (or script with `bokfctl`): ```sh BOKFD_SOCKET=/run/bokfd/bokfd.sock BOKFD_USER=admin BOKFD_PASSWORD=secret \ ./build/bokftui BOKFD_USER=admin BOKFD_PASSWORD=secret \ ./build/bokfctl --socket /run/bokfd/bokfd.sock org.create '{"name":"AB Ett","org_nr":"5560123456"}' ``` ## Run the backend with Docker The image carries `bokfd` and `bokfctl` only (Alpine, ~30 MB). It is built from the checkout; there is no registry. The TUI is built on the machine you sit at and talks to the container over TLS. ```sh # 1. Image docker build -t bokf:dev . # 2. A directory for compose.yaml, .env and the state (var/db, var/run) mkdir -p ~/bokf && cp compose.yaml ~/bokf/ && cd ~/bokf printf 'BOKF_IMAGE=bokf\nBOKF_TAG=dev\nLEGO_DOMAIN=localhost\n' > .env # 3. A certificate. Self-signed is fine on a LAN; compose.yaml also has a # lego sidecar for Let's Encrypt via DNS-01 (see docs/DEPLOY.md). mkdir -p var/db/certs/certificates openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \ -days 3650 -subj /CN=localhost -addext subjectAltName=DNS:localhost \ -keyout var/db/certs/certificates/localhost.key \ -out var/db/certs/certificates/localhost.crt # 4. Create the database and the admin user, then start docker compose run --rm -e BOKFD_PASSWORD=secret123 bokfd init --user admin docker compose up -d bokfd docker compose ps # wait for "healthy" ``` Then from any machine that trusts the certificate: ```sh export BOKFD_SOCKET=tls:localhost:8788 \ BOKFD_TLS_CA=~/bokf/var/db/certs/certificates/localhost.crt BOKFD_USER=admin BOKFD_PASSWORD=secret123 ./build/bokfctl health BOKFD_USER=admin BOKFD_PASSWORD=secret123 ./build/bokftui ``` `init` refuses to run twice. State lives in `var/db` (database, `backup/`, `export/`, `certs/`) and `var/run` (the Unix socket, for clients on the same host). Set `BOKFD_SECRET_KEY` in `.env` (32 random bytes, hex or base64) before storing SMTP passwords or employee personnummer (payroll needs it). Use `LEGO_DOMAIN`, `LEGO_EMAIL`, `INWX_USERNAME`/`INWX_PASSWORD` for a real certificate, and `scripts/deploy.sh` to ship releases to a remote host over SSH — both in `docs/DEPLOY.md`. ## Working with an AI agent bokf is designed so that an LLM agent can do the bookkeeping, with a human approving what matters: - **One protocol, self-describing.** `describe` returns every command with its permission, argument schema and defaults; `agent.instructions` returns the workflow rules in Markdown. An agent needs no other documentation. - **Safe by construction.** Every mutation supports `dry_run:true`, and postings carry an idempotent `client_ref`; the ledger is append-only, so a wrong posting is corrected by a new voucher, never hidden. Locked periods and closed years are hard stops the agent cannot override. - **Least privilege.** Give the agent a scoped API token (`read`, `write`, never `admin`), bound to one organization, shown once and revocable. Owner actions (closing a year in the TUI, token administration in `bokfctl`) stay with the human. - **Fully audited.** Every call the agent makes is in the hash-chained audit log with the token that made it. Setup is one command per agent: ```sh BOKFD_USER=admin BOKFD_PASSWORD=… ./build/bokfctl --org 1 token.create \ '{"label":"claude","scopes":["read","write"]}' ``` Point the agent at the socket (or `tls:host:8788`) and the token, tell it to fetch `agent.instructions` first, and prompts like these are enough: - *"Post the September invoices from `~/underlag/2026-09/` — attach each PDF, use the `Försäljning 25 %` template, dry-run everything and show me the list before posting."* - *"Import `seb-2026-09.csv`, match what you can against existing vouchers, and prepare — but don't post — vouchers for the rest."* - *"Give me the momsrapport for Q3, explain any box that changed more than 20 % from Q2, and save the eSKD file."* - *"Run the October payroll preview for all employees and list the differences from September."* `bokfctl` speaks the same NDJSON protocol from shell scripts and MCP-style tool wrappers alike: `bokfctl ''`. The same applies to developing bokf. `AGENTS.md` holds the invariants and the recipe for adding a command; `make check` fails when code and `docs/PROTOCOL.md` drift; `make gate` is the pre-push bar. Most of the project was written by agents working from short briefs. ## Documentation - `docs/PROTOCOL.md` — transport, auth, command reference - `docs/SCHEMA.md` — database schema, invariants, hash chains - `docs/COMPLIANCE.md` — BFL/BFNAR mapping and operator duties - `docs/DEPLOY.md` — Docker, TLS certificates, deploying to a remote host - `docs/INVOICING.md`, `docs/PAYROLL.md` — design notes for those modules - `docs/TUI-GUIDELINES.md` — keys, widgets and conventions for the TUI - `AGENTS.md` — how to work on the code (for humans and agents) ## License GPL-3.0-or-later. Vendored components have their own compatible licenses; see `THIRD_PARTY_NOTICES.md`.