Metadata-Version: 2.4
Name: unitbook
Version: 0.1.1
Summary: Unitized fund ledger for a pooled trading account: signed append-only log, units per investor, NAV per unit, fee by unit burn.
Author: Gesamt
Author-email: Gesamt <235901389+confirmxxx@users.noreply.github.com>
License-Expression: LicenseRef-Proprietary
License-File: LICENSE
Requires-Dist: pynacl
Requires-Dist: openpyxl
Requires-Dist: mcp>=2.1,<3
Requires-Dist: rich
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# GESAMT//Unitbook

[![PyPI](https://img.shields.io/pypi/v/unitbook)](https://pypi.org/project/unitbook/)

Every artifact it produces carries the mark `GESAMT//Unitbook <version> · <ledger head>`,
naming the software and the exact ledger state the sheet was rendered from.

You trade one exchange account, and part of the money in it belongs to other people.
Unitbook tracks who owns how many units of that account, prices the units every time you
record the account equity, charges your performance fee by burning units instead of moving
cash, and writes every step into a signed, hash-chained log. It answers one question
exactly: if this investor withdrew today, how much would they get, and can they prove the
number is not something you typed into a spreadsheet last night.

It runs on your laptop. There is no server, no database, no account to open, and no network
call except a version check against PyPI. Your book is a folder of files. You can drive it
from a chat agent over MCP, from a spreadsheet and a double click, or by typing commands.
All three write the same log.

[Install](docs/install.md) ·
[Owner guide](docs/owner-guide.md) ·
[Agent guide](docs/agent-guide.md) ·
[Concepts](docs/concepts.md) ·
[Investor verification](docs/investor-verify.md) ·
[FAQ](docs/faq.md)

## Quick start

Download `GESAMT-Unitbook-<version>-arm64.pkg`, double click it, and a setup window opens.
It asks for your activation code, walks you through creating the book, and offers to
connect your AI agent. The installer is not signed by Apple yet, so on the very first launch
right-click the `.pkg` and choose **Open** instead of double clicking; macOS asks once.

If you would rather use Terminal, paste the one line the developer sent you. Your
activation code is already in it:

```sh
curl -LsSf https://astral.sh/uv/install.sh | sh && ~/.local/bin/uv tool install unitbook && ~/.local/bin/uv tool update-shell && ~/.local/bin/unitbook setup ub1.xxxxxxxx
```

Either way you end up in `unitbook setup`, the whole onboarding in one word: it activates
your licence, runs the book wizard where you are standing, offers to connect your agent,
and prints what to do next. Rerun it any time; it skips what is done. The wizard asks, in
your own language, for the language, book name, currency, your name as manager (plus a
short latin code, unless your name already is one), fee percentage, high-water mark style
and timezone, confirms with a summary, then prints a 24 word recovery phrase **once**.
Photograph it before pressing Enter. It is the only backup of the key that signs your log.

The folder is now the book, and you never pass its path again:

```
book.toml  book.pub  events.jsonl  holders.csv  terms.csv
AGENTS.md  journal.xlsx  Unitbook.command
```

Full detail, including Linux and PATH, is in [install](docs/install.md).

Record the first day. A mark is the equity on the exchange *before* any money moved that
day, so a book always starts from a mark. On a terminal every verb asks for whatever you
left out and shows the diff before it writes, so you can type them bare:

```sh
unitbook mark
unitbook holders add
unitbook deposit
unitbook state
```

```
book.currency: USD
book.name: Fund
equity: 50000.00
head.id: ab3756f851ad5b8c36c9a957ae96844d8d692fd156a86a4dcc44130ddcb43cf1
head.seq: 6
holders:
  id     manager  name   status  units         value
  farm   true     Farm   active  0             0.00
  vasya  false    Vasya  active  50000.000000  50000.00
nav: 1
total_units: 50000.000000
version: 0.1.0
```

Abridged: the real body also carries the book's language and timezone, the terms in force,
any pending proposals, your licence, an update flag, `first_run` (true on a book never
used), and `next`, a short list of suggested follow-up verbs every call carries. `unitbook
state` is the one call that shows everything; bare `unitbook` at a terminal is a numbered
menu of the same actions.

## How it works

A book is an append-only log of typed events. Nothing is ever edited or deleted. Every
number you see is recomputed by folding that log from the first event, so two people with
the same log always get the same answer.

Money in buys **units** at the current unit price. Profit does not create units, it makes
each existing unit worth more. That is the whole trick: a deposit in the middle of a
profitable month cannot dilute anybody, because the depositor buys in at that day's price.

Here is a real book. Three holders on day one, at a unit price of 1.00. Written out with
flags so you can reproduce the numbers; on a terminal you would type `unitbook deposit` and
answer the questions.

```sh
unitbook mark --equity 0 --on 2026-09-01
unitbook deposit --holder farm  --amount 20000.00 --on 2026-09-01
unitbook deposit --holder vasya --amount 50000.00 --on 2026-09-01
unitbook deposit --holder petya --amount 30000.00 --on 2026-09-01
```

The account is worth 100 000 and there are 100 000 units. A month later the exchange shows
112 000. Recording that mark shows what it would do before it does it:

```sh
unitbook mark --equity 112000.00 --on 2026-09-30 --dry-run
```

```
What will change:
Holder  Units                         Value
farm    20000.000000 -> 22571.428571  20000.00 -> 25279.99
petya   30000.000000 -> 29035.714285  30000.00 -> 32519.99
vasya   50000.000000 -> 48392.857142  50000.00 -> 54199.99
Equity: 100000.00 -> 112000.00
Unit price: 1 -> 1.120000000000
A performance fee is charged: petya burns 964.285715 units.
A performance fee is charged: vasya burns 1607.142858 units.
```

Read it in numbers. The unit price went from 1.00 to 1.12. Vasya holds 50 000 units, so he
gained 50 000 x 0.12 = 6 000, and the fee is 30 % of that, 1 800. Instead of moving 1 800 in
cash, the book burns 1 800 / 1.12 = 1 607.142858 of his units and mints 1 607.142857 of them
to the manager. He keeps 48 392.857142 units worth 54 199.99. Petya gained 3 600, pays
1 080, keeps 32 519.99. The manager now holds 25 279.99.

54 199.99 + 32 519.99 + 25 279.99 = 111 999.97, three cents under the equity. Units are
issued down and burned up, so no holder can ever withdraw a cent more than they are
entitled to; the residual stays in the pool.

Confirm, and the three events are appended, signed and chained. `unitbook explain --seq 12`
prints the arithmetic behind any one of them in words. See [concepts](docs/concepts.md).

## Two ways to use it

### With an agent

Register the book with every MCP host on the machine with `unitbook mcp --install`. It
detects Codex, Claude Code, Cursor and Claude Desktop from a shipped registry, writes one
server entry into each config it finds, leaves everything else in those files alone, and
reports one row per file saying `created`, `updated` or `unchanged`. `--dry-run` shows what
it would write; `unitbook mcp --print` gives you the JSON to paste into a host the registry
does not know.

The MCP server exposes one tool per verb with real argument names, and repeats the
operating rules in every tool description. `init` also drops `AGENTS.md` beside the book,
which is what a cold agent reads before its first call. The rules: never invent a mark, and
ask when the equity is unknown; mark before flow; normalise any input (text, a voice note,
a screenshot, a spreadsheet) to typed events and ask when a field is ambiguous; propose,
show the diff, and commit only after the owner confirms; never edit the book's files,
because `undo` is the only correction; report one line per event, being date, NAV, units
moved and value.

So the ritual is always propose, show, commit. The agent sends typed events to `propose`,
which prices them against the current head and writes nothing:

```sh
echo '[{"type":"mark","on":"2026-10-31","equity":"120000.00"},
       {"type":"withdraw","on":"2026-10-31","holder":"farm","amount":"3000.00"}]' \
  | unitbook propose --table
```

```
What will change:
Holder  Units                         Value
farm    22571.428571 -> 21619.986903  25279.99 -> 25943.98
petya   29035.714285 -> 28455.005951  32519.99 -> 34146.00
vasya   48392.857142 -> 47425.007142  54199.99 -> 56910.00
Equity: 112000.00 -> 117000.00
Unit price: 1.120000000000 -> 1.200000000024
A performance fee is charged: petya burns 580.708334 units.
A performance fee is charged: vasya burns 967.850000 units.
```

That is the terminal view; piped to another program the same call answers in JSON instead.

It shows you that diff, and only then `unitbook commit 2e6e2a6596e8` writes it:
`committed: 13, 14, 15, 16`. Commit can only ever write the events priced in that
proposal. If the log moved in between it refuses rather than repricing silently, and
proposals expire after 24 hours.

The exit code carries the meaning, so an agent never has to read prose. `0` is done. `2` is
refused, with `reason` from a fixed enum and `hint` saying what to pass instead. `3` needs
input, with `questions` already phrased in the book's language to relay verbatim. `4` is an
internal error, with `bundle`, the path to a crash report.

A refusal: `{"hint": "holder 'nobody' is not an active holder", "reason": "unknown_holder", "schema_version": "1"}`.
A question: `{"questions": ["What was the equity on 2026-10-10, before the day's flows?"], "schema_version": "1"}`.

Payload shapes, idempotency keys, dry runs and `events --since` are in the
[agent guide](docs/agent-guide.md).

### Without an agent

No agent, no internet, no terminal typing. Two spreadsheet files with one writer each.

`journal.xlsx` is yours. Open it in Google Sheets, Numbers or Excel and add rows: date,
type, holder, deposit, withdrawal, withdrawal type, equity, note. The headers are in the
book's language, and Unitbook only ever reads this file.

Then double click `Unitbook.command`. It is a file, not an app: it sits in the book folder
right next to `journal.xlsx`, and you open it in Finder the same way you open anything
else. It imports the new rows, prints what they would do, waits for Enter, and commits:

```
Equity: 135000.00 -> 140000
Unit price: 1.384615384672 -> 1.384615384712
  petya 27316.812617 -> 30927.923727
    37823.27 -> 42823.27

Review the events above.
Commit to the book? Enter to confirm, Ctrl+C to cancel:
```

After Enter it rewrites `book.xlsx` (the engine's mirror of the whole book),
`statements/<holder>.xlsx` (one per investor, with the pin they can verify) and
`export.zip`. Clicking twice commits once: each row's identity is a hash of its contents,
so a re-import proposes nothing and an edited row proposes a replacement, not a duplicate.

## Proof

Every event carries the hash of the previous one and an ed25519 signature over its own id.
Change one byte anywhere and every event after it stops matching:

```sh
unitbook verify
```

```
findings:
  code     hint                                                                                                                                                     seq
  bad_id   id is e2cbd7957b257ccc0d48bbc6defcb8f4ea36a18fe415e13a805ef35440d251a4, body hashes to f2cbd7957b257ccc0d48bbc6defcb8f4ea36a18fe415e13a805ef35440d251a4  1
  bad_sig  does not verify against key a0482ad9ba8b0d14                                                                                                             1
head_seq: 9
ok: false
```

On an intact book, `findings` is empty and `ok: true`. `verify` reports every finding it
can see rather than stopping at the first, and always exits 0: a broken book is a result,
not a crash.

The signing key lives in `~/.unitbook/keys/` at mode 0600 and never enters the book folder.
`unitbook keys restore --phrase "..."` brings it back on a new machine from the 24 words.
`unitbook keys rotate` writes the new public key into the chain as an event, so old
signatures keep verifying at their own position and history is never re-signed. Losing the
phrase never loses the ledger, only the ability to append until you restore or rotate.

Every statement carries a pin: the sequence number and hash of the log head when it was
cut. An investor checks the statement against the log you sent them with
`unitbook verify --seq 21 --hash 59fadeb0684fbb...`, and gets the same `ok: true` and empty
`findings`. A truncated log fails the pin. A statement cut from a longer log fails it too. All of it
runs offline, on the investor's own machine, against `book.pub`. See
[investor verification](docs/investor-verify.md).

## Files in a book

| file | written by | what it is |
|---|---|---|
| `events.jsonl` | Unitbook | the log. One signed, chained event per line. The book. |
| `book.toml` | `init` | name, currency, language, decimals, genesis date, timezone, key id |
| `book.pub` | `init` | the public key an investor verifies against |
| `holders.csv` | Unitbook | projection of the holder events, for reading |
| `terms.csv` | Unitbook | projection of the terms events, for reading |
| `AGENTS.md` | `init` | the operating rules an agent reads before its first call |
| `CLAUDE.md` | `mcp --install` | one line, `@AGENTS.md`, for hosts that do not read AGENTS.md |
| `journal.xlsx` | **you** | your input rows. Unitbook only reads it. |
| `Unitbook.command` | `init` | double click: import, confirm, commit, render, export |
| `book.xlsx` | `render` | the engine's mirror of the whole book |
| `statements/<id>.xlsx` | `statement` | one investor statement, with its pin |
| `export.zip` | `export` | the investor package and the off-box backup, in one file |
| `trace.jsonl` | every call | one line per call: verb, exit code, duration. No amounts, no names. |
| `proposals/` | `propose` | pending proposals, expiring after 24 hours |
| `crashes/` | on exit 4 | crash bundles |
| `.lock` | every write | single writer. A second writer is refused with `locked`. |

## Verbs

`unitbook <verb> --help` prints the arguments for any of these. Every mutating verb also
takes `--idem <key>`, where repeating a key returns the original result and writes nothing,
and `--dry-run`, which is identical to `propose`.

| verb | mutating | what it does |
|---|---|---|
| `setup` | yes | activate, create the book, connect the agent, all in one |
| `activate` | no | install a licence from a code or a file |
| `init` | yes | create a book in an empty directory |
| `state` | no | the whole picture in one call: holders, units, values, NAV, terms, head |
| `schema` | no | JSON schema of every verb plus the refusal-reason enum |
| `guide` | no | print a shipped guide: `owner`, `agent` or `investor` — offline copies of these docs |
| `events` | no | log envelopes after a seq |
| `explain` | no | the arithmetic behind one event, in words |
| `doctor` | no | check the book and its surroundings |
| `replay` | no | re-fold the log and rewrite the csv projections |
| `verify` | no | check the log, and a statement's pin, against the public key |
| `propose` | no | typed events in, one diff out, nothing written |
| `mark` | yes | record equity before the day's flows |
| `deposit` | yes | pay money in |
| `withdraw` | yes | take money out |
| `terms` | yes | record fee terms (`terms set`) |
| `holders` | yes | record or retire a holder (`holders add`, `holders retire`) |
| `commit` | yes | commit exactly the events of one proposal |
| `undo` | yes | reverse one event |
| `import` | yes | read new rows from `journal.xlsx` |
| `render` | no | write `book.xlsx` |
| `statement` | no | write a holder statement |
| `export` | no | write the investor package zip |
| `keys` | yes | show, restore or rotate the signing key |
| `mcp` | no | serve this book over MCP, or print or install the host config |

Output is readable text on a terminal and JSON everywhere else; `--json` and `--table`
force either. Every body carries `schema_version`. The same call twice produces the same
bytes, so two runs can be diffed.

## Configuration

Nothing numeric lives in code. Three nouns, all data. `book.toml` is written once by the
wizard (`name`, `currency`, `lang`, `money_dp`, `units_dp`, `nav_dp`, `genesis`, `tz`,
`signer`); its immutable fields are hashed into the first event, so changing them later
breaks verification. `holders.csv` and `terms.csv` are projections: you append events and
Unitbook rewrites them, and `verify` reports config drift if one was edited by hand.

Terms rows are effective-dated, so a new row never reprices an earlier period:

```
from,holder,fee_rate,hwm,crystallise,hurdle
2026-09-03,*,0.30,every_mark,every_mark,0
```

`hwm` is `every_mark` or `ratchet`; `crystallise` is `every_mark` or `on_withdraw`, and a
withdrawal settles its own fee either way. Change them with `unitbook terms set`; `monthly`
is refused, so mark once a month with `every_mark`. The arithmetic is in
[concepts](docs/concepts.md), the day-to-day in the [owner guide](docs/owner-guide.md).

One book counts in exactly one currency. A deposit in another asset records `asset`, `qty`,
`rate` and `rate_source`, which the engine checks against the amount. A second currency is
a second book.

## Licence, updates and reporting

Unitbook is proprietary software, licensed to named people. Your grant is a signed
`license.json` in `~/.unitbook/`, installed by `unitbook activate`. Without a valid licence
every verb refuses with `reason: unlicensed`.

On start, at most once an hour, Unitbook asks `pypi.org` for the newest published version;
a successful check also renews your licence for 30 days. It never blocks the ledger, and if
PyPI is unreachable the check is skipped. If no check succeeds for 30 days the licence stops
working; upgrade with `uv tool upgrade unitbook`.

The software reports crashes and anonymous usage statistics (no amounts, no holder names)
to the licensor. This reporting is part of the software and cannot be disabled. What goes
out is listed in the [FAQ](docs/faq.md).

Every statement, `book.xlsx`, export zip and JSON body carries the provenance mark
`GESAMT//Unitbook <version> · <ledger head>`. It says which software produced the sheet
and which exact ledger state it was rendered from, and it cannot be switched off.

Copyright (c) 2026 Gesamt Team. See [LICENSE](LICENSE).

## Быстрый старт

Установка на Mac, один раз, около пяти минут.

1. Скачай `GESAMT-Unitbook-<версия>-arm64.pkg` и открой его. Установщик пока не подписан у
   Apple, поэтому в первый раз кликни по файлу правой кнопкой и выбери «Открыть»;
   macOS спросит один раз. Откроется окно установки.

2. Оно спросит код активации, потом язык, название книги, валюту, твоё имя как
   управляющего (и короткий код латиницей, если имя не подходит), комиссию, тип максимума
   и часовой пояс, покажет сводку для проверки и предложит подключить агента. В конце
   покажет 24 слова ОДИН раз: сфотографируй их, потом жми Enter. Без них нельзя восстановить
   ключ подписи (сама книга при этом не теряется). Папку книги держи внутри Google Drive в
   режиме «Зеркало» (Mirror), не «Поток» (Stream). Кто предпочитает Терминал: вставь ту одну
   строку, которую прислал разработчик, в ней уже есть код активации.

3. В папке книги появится `journal.xlsx`. Это твой журнал: Дата · Тип · Инвестор ·
   Пополнение · Вывод · Тип вывода · Капитал на счёте · Комментарий. Правь его в Google
   Таблицах, Numbers или Excel, как привык.

4. После новых строк: двойной клик по `Unitbook.command`. Это файл в папке книги, рядом с
   `journal.xlsx`; открывается двойным кликом в Finder. Он покажет, что запишет, ждёт
   Enter, и готово: `book.xlsx` (зеркало книги), `statements/<инвестор>.xlsx` (выписки),
   `export.zip` (пакет для инвесторов). Второй клик без новых строк ничего не запишет.

Через агента (Codex, Claude, Cursor): один раз `unitbook mcp --install`, дальше просто
говоришь «Вася внёс 500», агент сам предложит запись и попросит подтвердить.

Проверка: `unitbook doctor` — всё зелёное, значит порядок.

Отчёты об ошибках и статистика использования отправляются разработчику. Суммы и имена не
отправляются.
