IrisBooks Manual
Chapters
Manual / CLI command reference
Chapter 08

CLI command reference

Every iris command, grouped by purpose. The CLI has two surfaces:

  • File-mode commands operate on a local book folder. With no path argument they walk up from the current directory to find the book, so you can run them from anywhere inside the book tree.
  • Cloud commands (iris api …) talk to the IrisBooks server and operate on a book ID, not a path. They need you to be signed in (iris api login) or to set IRIS_API_TOKEN.

Run iris <command> -h for the authoritative, up-to-date flags of any command. iris -h lists everything; iris api -h lists the cloud subcommands.

Commands that emit JSON share one convention: amounts are integers in the book currency’s minor units (¥1,200 is 1200 at scale 0; $12.00 is 1200 at scale 2), dates are YYYY-MM-DD strings, and the document is pretty-printed with a two-space indent. The output shapes below are written as key skeletons — ? marks a key that is omitted when empty.

Setup

iris update

iris update --check [--json]   # read-only check against the latest stable release
iris update [--json]           # install after signature, checksum and version checks

Updates the executable currently running (resolving symlinks), so MCP registrations keep pointing at the same path. JSON includes currentVersion, latestVersion, status, updateAvailable, executablePath, pathExecutable, pathMismatch, installKind, downloadURL, updateCommand (an argument array), instructions, updated, restartRequired, and optional error. updateAvailable is null for a failed check or development/prerelease build. Status is update_available, up_to_date, newer_than_latest, development, check_failed, or updated. Exit 0 means a successful check/update (including no update needed); exit 1 means failure; exit 2 means invalid arguments.

No implicit network checks occur during book operations. IRIS_DL_BASE selects the download base (default https://irisbooks.io/dl); the release signature is always checked against the built-in key, without requiring minisign to be installed. A failed verification preserves the old executable. No downgrade or development-build replacement is automatic.

For .mcpb installations the check provides a matching bundle URL; installation is done through the client’s extension settings. After a CLI update, rerun iris onboard in your book and restart MCP; call check_update to confirm the agent’s running version. On Windows, stop MCP servers and retry if locked. Older binaries without this command use the installer to upgrade first.

iris init

Create a new book. Bare iris init runs a guided wizard in the current directory. With flags, supply a destination path (. for the current directory) to skip the prompts.

iris init [flags] <path>
FlagMeaning
--nameBook display name
--regionRegion code (JP, US; default JP)
--languageLanguage (ISO 639-1)
--chartStarter-chart variant, where the region offers several — JP: general / it / food (default general). A region without a chart of its own gets a neutral starter chart
--currencyCurrency (ISO 4217; default per region)
--fiscal-start-monthFiscal-year start month 1–12 (default 1, January — pass 4 for a year that starts in April)
--fiscal-yearThe fiscal year the book covers (YYYY; default: the one holding today) — e.g. last year’s, when you’re catching up on its filing
--sampleSeed ~40 demo journals to explore
--postingPosting policy written into LLM-GUIDE.md: approve (default — you review and post) / auto (your assistant posts everything that validates)
--book-idPre-link to an existing server book (advanced)

iris onboard

Wire this machine’s AI assistant to the book: register iris mcp serve with MCP clients and write the Claude Code skill. Claude Code’s project config (.mcp.json) is always written; Claude Desktop, the Codex CLI, Cursor, Gemini CLI, and VS Code are registered when detected on the machine, or forced with their flag.

iris onboard [--book PATH] [--claude-desktop] [--codex] [--cursor] [--gemini] [--vscode] [--install-path] [--no-skill] [--dry-run]
iris onboard --status [--json]    # read-only report: what is registered where

--status reports each client’s registration state (including whether the registered binary still exists — the stale state re-running iris onboard fixes). --json makes the report machine-readable. Re-running iris onboard is always safe: registrations are updated in place, never duplicated.

iris offboard

The inverse of iris onboard: remove the irisbooks MCP registration from every client config (including leftovers from clients no longer installed) and the Claude Code skill. Does not touch the book’s data or your sign-in. Idempotent — safe to run before a clean reinstall.

iris offboard [--book PATH] [--keep-skill] [--remove-path] [--dry-run]
FlagMeaning
--keep-skillKeep the Claude Code skill (when other books still use it)
--remove-pathAlso remove the binary installed by onboard --install-path and revert its PATH entry
--dry-runPrint what would be removed without removing anything

iris uninstall

Remove iris itself from this machine: the iris binaries, your sign-in and local config (~/.config/irisbooks — the CLI session is revoked server-side first), the Claude Code skill, machine-level MCP registrations (Claude Desktop, Codex CLI), and the PATH entry from iris onboard --install-path. It always prints the exact list of what it found on this machine and asks for confirmation before deleting anything.

Your books are never touched — they are plain files you own. Per-book agent wiring (.mcp.json inside a book) is also left in place; run iris offboard --book PATH first if you want that removed too.

iris uninstall [--yes] [--dry-run]
FlagMeaning
--yesSkip the confirmation prompt
--dry-runPrint the deletion plan without removing anything

iris clone

Fetch a server book to disk (the lifecycle peer of init). Requires sign-in. Works immediately on a brand-new book created in the web app or with iris api books new outside a book folder — those are born with config/book.yaml and a starter chart of accounts. (A cloud book created from inside a local book starts empty and receives that folder’s files on its first iris sync.) It also writes the local guides for your AI assistant (LLM-GUIDE.md, CLAUDE.md, README.md) when the book has none — they never sync, so a book created in the web app arrives without them.

iris clone [--force] <book-id> [dest]

Inspect & validate

iris status

Print a book’s identity, fiscal-year start, archive flags, and how many draft drafts exist (drafts are excluded from reports).

iris status [--json] [path]

Output shape (--json):

{ name, bookId, region, language, currency, fiscalStartMonth,
  archived, archive?{ sourceBookId, fiscalYear },
  draftCount?, remoteDeleted? }

archive appears only inside an archive folder, draftCount only when the drafts could be counted, and remoteDeleted only when the server reports the book gone.

iris validate

Validate a book’s structure and entries: YAML, double-entry balance, account existence, date consistency, status values, and (JP taxable books) tax classification.

iris validate [--v] [--json] [--fix] [path]

--fix records the values the region overlay derives for you — reported as hints, such as the consumption tax inside a line on a 税抜経理 book — into the files, then validates again.

--v prints every file scanned; --json emits issues + counts and exits 1 on errors.

Output shape (--json):

{ book, bookOk, chartOk, journals, assets, notes,
  errors, warnings, hints, ok,
  issues[{ severity, file, code?, message }] }

code is a stable catalogue key — journal.date-required, chart.alias-shadow-path, asset.ikkatsu-cost-range, and so on. Branch on it rather than on message, which is translated. ok is errors == 0; warnings and hints affect neither ok nor the exit code.

iris autocomplete

Preview completion of an existing draft journal, using the book’s overlays and built-in rules. Money-only journals can leave one balancing amount as null. The inventory overlay fills disposal cost and proposes a gain/loss line from recorded quantities, settlement amounts and earlier posted acquisitions.

iris autocomplete [--book PATH] [--view NAME] [--apply] [--revision HASH] <journal>
iris autocomplete journals/2026-10/2026-10-21-sale.md
iris autocomplete --apply --revision <preview-revision> journals/2026-10/2026-10-21-sale.md

The path is relative to the book. Omitted --book finds the current book; omitted --view uses its default view. A selected view receives its own lines, leaving shared lines and other views intact.

Output is always JSON: proposal contains the complete Markdown, allocations lists quantities, costs, lots and effective methods, and revision identifies the inputs and proposal. Preview writes nothing. --apply recalculates before writing and refuses a stale revision; it keeps the journal a draft. It does not post or sync. Archived books and known sealed years refuse apply.

FIFO is implicit. Configure inventory.params.gain_account once in config/book.yaml; no journal-level method or automatic lot labels are needed. Matching inventory.params.method overrides the opening method, then FIFO applies. accounts.exact and commodity optionally narrow the scope. See defaults and inferred values for a minimal sale example. Completion leaves recorded amounts intact and rejects conflicts, missing settlement/fee amounts, insufficient holdings and unknown opening acquisition basis. Use null on an amount you want calculated. Fees stay explicit; short positions and zero-cost disposals are unsupported.

The default package in the public overlays repository holds these rules. IrisBooks bundles and includes it automatically; no default pin is required. An explicit default@<version> in overlays selects that available version without mixing in newer defaults. Specialist or trusted book-local completions can override matching default IDs.

iris overlay list --json and local MCP describe_overlay list completions. Trusted book-local or selected published completions/*.yaml definitions can choose native balance or inventory primitives. A matching ID replaces the built-in rule; conflicting active proposals fail. Unavailable or untrusted selected policy must be repaired before completing a journal.

iris hash

Print the canonical content hash of a single file (useful when comparing local bytes against what the server accepted).

iris hash [--raw] <file>

iris organize

Bring a book’s layout into canonical shape. Dry-run by default.

iris organize [--apply] [--fix month-folders,extensions,empty-raw,config-typos] [--json] [path]

month-folders moves a journal filed under a journals/YYYY-MM/ folder that isn’t its date’s month into the right one, and updates the parse caches’ target: links in notes/raw/ to the new paths. Subfolders below the month (such as auto/) are kept; journals outside month folders are left alone.

Output shape (--json) — the plan, which is exactly what --apply executes:

[ { family, code, path, new_path?, delete?, reason } ]

family is one of the --fix categories. A move carries new_path; a removal carries delete: true.

iris balance

Trial balance as of a date (posted entries only).

iris balance [--as-of YYYY-MM-DD] [path]

Output is the same trial-balance document as iris report tb (see below).

iris report

Financial statements. Subcommands:

iris report tb [--as-of YYYY-MM-DD] [path]               # trial balance
iris report pl [--from D] [--to D] [path]                # profit & loss
iris report bs [--as-of YYYY-MM-DD] [path]               # balance sheet
iris report ledger --account PATH [--as-of D] [path]     # general ledger for one account
iris report sum --by KEY[,KEY...] [--from D] [--to D] [--year YYYY] [path]
                                                         # group-by sums over journal lines

Report output is JSON (amounts in minor units). Cloud responses may include additional revision or freshness metadata. There is no text-table mode: terminal column alignment is unreliable for Japanese account names, your AI consumes JSON anyway, and the web app is the formatted view for humans. A --json flag is still accepted as a no-op for compatibility.

report sum groups posted journal lines by the keys you choose — account, unit, payee, month, or a dotted field like tax.category — and returns debit/credit/net totals plus a line count per group (e.g. --by tax.category,tax.rate --year 2026 for consumption-tax figures). Lines missing a key form an explicit empty-keys group, so unclassified lines are visible rather than dropped. --year uses the fiscal year (begins-in convention); --from/--to take arbitrary inclusive dates.

Default dates. A date you leave out comes from the book’s fiscal year (fiscal_year — a book is one year). Balances (balance, tb, bs, ledger) are as of today, where today is the date where the book is kept (Japan time for a JP book), or as of the year’s last day once that year is over; pl and sum run from the year’s first day to the same date. So last year’s book, opened in February to file its return, shows last year in full. Give only --to and the range starts on the first day of that date’s fiscal year; give only --from and it runs through today. An entry dated after today stays out until its date arrives. The JSON always states the dates it used, and the web app, the MCP tools and the remote connector use the same defaults. For filing figures, name the period (--year, or --from/--to).

Output shapes. All five build on one row type:

Balance = { account, debits, credits, type?, net? }

tb      { asOf?, rows[Balance], totalDebit, totalCredit, balanced }
pl      { from?, to?, income[Balance], expenses[Balance],
          totalIncome, totalExpense, net }
bs      { asOf?, assets[Balance], liabilities[Balance], equity[Balance],
          totalAssets, totalLiabilities, totalEquity,
          currentEarnings, balanced }
ledger  { account, asOf?,
          entries[{ date, file, payee, debit, credit, balance }],
          totalDebits, totalCredits, balance }
sum     { groupBy[], from?, to?,
          rows[{ keys[], debit, credit, net, lines }],
          totalDebit, totalCredit, totalNet, totalLines }

type and net on a Balance populate once the account is classified against the chart. ledger.entries[].balance is the running balance after that entry, and file is the journal it came from, so every ledger row traces back to its journal. sum.rows[].keys is positional: one entry per --by key, in the order you gave them.

Find journals by any combination of filters (combined with AND). Deterministic and offline.

iris search [--from D] [--to D] [--min N] [--max N] [--payee S] \
  [--status draft,posted,closed] [--account S] [--tag S] [--json] [path]

The status column shows the effective status: a journal dated inside a sealed fiscal year displays as closed regardless of what its file says, and --status closed filters on exactly those.

Output shape (--json):

[ { path, date, payee, status, amount, lines } ]

amount is the entry’s debit total in minor units; lines is its line count.

iris show

Cross-references for a path: for a document, the journals that cite it; for a journal, the documents it cites plus siblings.

iris show [--json] <path> [path]

Output shape (--json):

{ ref, mode,
  citedBy[{ path, date, payee, status }],
  cites[{ path, type?, locator?, alsoCitedBy[] }] }

mode says whether ref was read as a document or as a journal. type is the attachment’s provenance (receipt, invoice, bank_statement) and is absent on supporting material. alsoCitedBy lists the other journals citing the same document — how you spot a receipt booked twice.

iris export

Export journals, trial balance, ledgers, or assets as CSV (UTF-8 with BOM).

iris export [--out DIR] [--year YYYY] [--as-of YYYY-MM-DD] [path]
iris export assets [--year YYYY] [--out FILE] [path]

Fixed assets

iris asset

Fixed-asset depreciation and reporting.

iris asset schedule [--year YYYY] [path]    # print each asset's depreciation schedule
iris asset depreciate --month YYYY-MM [path]  # generate that month's entries (status: draft)
iris asset depreciate --year YYYY [path]      # one FY-total entry per asset, dated the FY's last day

Pick one cadence per fiscal year: depreciate refuses to write annual entries into a year that already has monthly ones, and vice versa (mixing them would double-count).

Region overlay

iris overlay

The region overlay is the versioned set of country-specific rules, recipes and derivations the book validates and computes with — pinned by overlay: or overlays: in config/book.yaml, extended by the book’s own config/overlays/ when present.

iris overlay list [--json] [path]                                   # the overlay in effect: pin, layers, rules, recipes, derivations
iris overlay guide <id> [path]                                     # read a pinned package workflow guide
iris overlay recipe <id> --set name=value ... [--write <path>] [path]  # run a recipe; --write records it with provenance
iris overlay test [--json] [path]                                   # run the golden tests of every layer in effect
iris overlay test --dir <overlay-dir> [--json]                      # run one published overlay's golden tests, outside any book
iris overlay fetch [path]                                           # download + verify the version the book pins, if this iris lacks it
iris overlay upgrade [--to <id>@<version>] [--dry-run] [path]                   # move the pin to the newest published version (or --to)
iris overlay trust [path]                                           # trust this book's config/overlays/ on this machine

list --json includes pins, fields with allowed values, rule conditions and assertions, parameter schemas, and guide IDs. Local MCP exposes this through describe_overlay and read_overlay_guide. With plural pins, fetch verifies every member and upgrade checks the complete candidate set. --dry-run may populate the verified cache but leaves book files unchanged. See book configuration examples for stored defaults and account selectors.

recipe prints the proposal; with --write a schedule recipe rewrites the asset file (schedule: + schedule_source:) and a figures recipe writes a filing under filings/. Map-valued params are passed as --set name='{"1": 90}'.

upgrade and fetch download from the releases of the public irisbooks/overlays repository and check the signature before a version is used. Versions released before your iris was built are included in it and never downloaded. Downloaded versions are kept in ~/.config/irisbooks/overlays/ (never in the book) and checked again every time they load. upgrade changes nothing unless the new version passes its own tests and the book’s config/overlays/ still loads on it. iris validate never downloads anything.

Editing & promoting

iris post

Promote journals from draft to posted, then sync (if linked). If the book owner turned on posting approval (web app → Settings), posting is web-only: iris post refuses locally and the server rejects any pushed status flip with APPROVAL_REQUIRED. Validates each file is non-empty and balanced first. A journal dated inside a sealed fiscal year is refused (“FY <n> is sealed (closed period) — run iris reopen <n> to amend it, then re-seal”).

iris post [--dry-run] <file>...
iris post [--dry-run] --all

--all posts every draft draft in the book instead of naming files — including drafts you didn’t author (phone/connector drafts, email-ingested entries, generated depreciation). Drafts that fail validation are reported and left as drafts. This is the ritual for books with the auto posting policy.

iris diff

Show what would be pushed — local changes versus the last-synced snapshot.

iris diff                # list all pending changes
iris diff <relpath>      # line-level diff for one file
iris diff --paths        # names + kind only

Sync & conflicts (cloud)

iris sync

One explicit pass: push local changes, pull remote changes, report per-file accept/reject. Needs the book linked to the cloud and you signed in.

iris sync [--quiet] [--json] [--allow-bulk-delete] [path]

Exit codes: 0 clean · 1 rejections/conflicts/IO · 2 usage/setup · 3 terminal disconnect (book deleted, access revoked, session expired).

If a sync would delete most of the book’s cloud files at once, the server refuses those deletions (BULK_DELETE_REFUSED) as a safety net. When the mass deletion is really what you want, re-run with --allow-bulk-delete.

Output shape (--json) — one document that replaces all of the above, and the one your AI reads after every edit:

{ status, counts{ pushed, pulled, deleted, conflicts }, queueLeft,
  disconnected, disconnectReason?,
  newRejections[], allRejections[], applyErrors[], blockedByConflicts[],
  error? }
  • status — ok | rejected | conflicts | disconnected | error
  • disconnectReason — deleted | forbidden | auth_expired
  • newRejections / allRejections — attention records, the same shape iris attention list --json returns
  • blockedByConflicts — canonical paths with an unresolved .conflicted sidecar. Non-empty means the whole pass was a no-op: nothing was pushed or pulled, so merge or resolve first.
  • applyErrors — remote changes that failed to land locally. The working tree may be incomplete; re-run rather than trusting the files as they stand.

This is why an edit → sync → read loop needs no second call: the per-file accept and reject are both in this one response.

iris conflicts

When a push conflicts (409), the server version takes the canonical path and your version becomes a .conflicted sidecar.

iris conflicts list [path]
iris conflicts resolve <path> --keep mine|cloud [path]

The usual resolution is to merge by hand (edit the canonical file, delete the sidecar, iris sync). See Troubleshooting.

iris attention

Manage the local queue of server-side rejections.

iris attention list [path]              # show path + code + issues
iris attention retry [path]             # drop suppression; the next `iris sync` re-pushes
iris attention retry --path <relpath> [path]   # just one file

Output shape (list --json):

{ records[ { path, local_fs, local_sha, code?,
             issues[{ field, message, rule? }], detected_at, reason? } ] }

code is the server invariant that refused the file — UNBALANCED_JOURNAL, UNKNOWN_ACCOUNT, PERIOD_SEALED, BULK_DELETE_REFUSED, and so on. OVERLAY_RULE means a region-overlay rule refused it (the published overlay’s or your book’s own in config/overlays/); each issue’s rule names the rule, which iris overlay list shows.

iris yearend

End a fiscal year by starting the next year’s book — the accounting half of a year-end close (the compliance half, locking the year, is iris api seal). A book covers one fiscal year, so iris yearend creates the next one as a standalone copy, in a new folder beside this one (acme-2025 → acme-2026; --to picks the folder). The new book gets:

  • config/ — book.yaml with fiscal_year advanced and its own book ID, the chart of accounts, rules.yaml and config/overlays/;
  • the guides (README.md, LLM-GUIDE.md, CLAUDE.md) and the policy notes (decisions.md, workflow.md, todos.md, open-questions.md);
  • the fixed assets still held when the new year starts;
  • an opening-balances journal (期首残高) at journals/<YYYY-MM>/0000-opening-balances.md, dated the new year’s first day and tagged opening-balance. Balance-sheet accounts carry forward at their closing balance (with quantities for currency, crypto and other unit-tracked holdings); income and expense reset to zero; net income folds into the equity account marked retained_earnings: true in config/chart-of-accounts.yaml, or the chart’s only equity account when none is marked. With several equity accounts and none marked, the run stops and asks you to mark one.

Entries already dated in the new year move to the new book, with the raw files they cite. Earlier journals, compiled/, filings/, parse caches and disposed assets stay here. --carry notes,raw copies all of notes/ or raw/ as well.

If this book is linked to the cloud, the same run creates the new book’s cloud copy — everyone with access to this book gets the same access, and this book’s inbound email address moves to the new one — then syncs both books. It asks first; --yes skips the question (needed when no terminal is attached), and --local makes only the local book.

Nothing links the two books afterwards. To carry a late correction to the old year, fix it in the old book and run iris yearend again with the same --to: the new book’s opening entry is refreshed (identical results are a no-op), and entries dated in the new year that appeared in the old book since are moved. The new book’s config and notes are left as they are. Once the new year is sealed, its opening entry is frozen — a mismatch is reported with remedies, never silently rewritten. Unposted drafts dated in the closed year are warned about (they don’t carry).

<fiscal-year> is optional — it defaults to the book’s fiscal_year, and naming any other year is refused.

iris yearend [<fiscal-year>] [--to PATH] [--carry notes,raw] [--yes | --local] [--dry-run] [path]

--dry-run returns a JSON preview of opening balances, moves and cloud actions, plus the count of unposted drafts excluded. It writes nothing.

iris reopen

Unlock a sealed fiscal year for amendment. The seal is unlocked on the server (the year’s journals and config/, assets/, filings/, compiled/ are locked until this runs; edits to that scope after reopening are permanently flagged as post-seal edits). raw/ and notes/ remain writable throughout. The year’s files never left the working tree — sealing locks the year, it does not remove files — so there is nothing to restore: edit them directly, run iris sync to push your edits, and iris api seal to close the year again.

iris reopen <fiscal-year> [path]

Prices (Net Worth)

iris networth

iris networth --books-dir ~/IrisBooks --as-of 2026-10-06
iris networth --currency JPY ~/IrisBooks/business-2026 ~/IrisBooks/personal-2026
iris networth history --books-dir ~/IrisBooks --months 12
iris networth movers --books-dir ~/IrisBooks --compare 2026-09-30

Values posted local journals and the local price log without network access. Select immediate child books of a parent folder or supply explicit book folders. Mixed currencies require --currency. Explicitly linked annual books form one continuous position history; successor openings must reconcile and replace prior checkpoints. Without a successor the last known holdings carry forward. Drafts are excluded. Missing prices use book value and appear in unpriced. Duplicate folders/book identities are refused to avoid counting copies twice. Output is JSON. Cloud inclusion settings do not apply.

iris analytics

iris analytics export --books-dir ~/IrisBooks --out portfolio.duckdb --currency JPY
duckdb -readonly portfolio.duckdb

export reads posted local journals and the local price log, including unsynced edits. Install the native DuckDB CLI or pass --duckdb /path/to/duckdb. Supply either --books-dir or explicit book folders. The output must be a new file. Resolve local price conflicts before exporting.

The file contains sources, journal flows, position checkpoints, prices and coverage issues, plus a portfolio_at table macro. You can query or pivot it:

SELECT account, unit, valuation, cost, inventory_method FROM portfolio_at('2026-10-19', 'JPY', 0);
PIVOT journal_lines ON unit USING sum(debit-credit) GROUP BY book_id, account;

iris yearend carries complete opening lots and costs into each new book. Each annual book works alone and contributes only within its fiscal year. Select historical books to include their historical periods. Missing years remain gaps; no relationship between books is stored or required.

Acquisition cost is the cost of remaining holdings. It is separate from book value, cumulative contributions and realized profit. FIFO is the default; an explicit matching root inventory method overrides a persisted opening method. Moving average and specific identification are also supported, with explicit lot selection required for specific disposals. Missing acquisition history or opening basis remains unknown. inventory_method states the effective method per position. Named alternative views are outside this export’s supported ledger contract.

iris price

Record/list unit prices offline, and sync them to the server. User-scoped (not stored in the book).

iris price add --unit BTC --price 9850000 [--currency JPY] [--date YYYY-MM-DD] [--source S]
iris price list [--unit BTC] [--currency JPY] [--year 2026] [--json]
iris price sync [--json]
iris price conflicts [--json]
iris price resolve --unit BTC --currency JPY --date 2026-10-07 --choose local --revision 43 --edit <edit-id>

Output shapes. list --json:

[ { unit, currency, date, valueMicro, source?, origin, recordedAt,
    revision?, baseRevision?, editId?, conflict?, pending? } ]

valueMicro is the value of one whole unit × 1,000,000, so a ¥9,850,000 bitcoin is 9850000000000. origin is local (captured on this machine) or server. sync --json returns { pushed, pulled, conflicts }; unresolved conflicts give exit code 1 after other cells have synced.

Prices are stored by observation calendar year under ~/.config/irisbooks/prices/YYYY.jsonl. The old prices.jsonl migrates on first use and is retained. Sync downloads changes after the last successful page; corrections to older years are included.

For concurrent edits of the same unit/currency/date, identical value and source converge automatically. Different edits retain both candidates. Local reports use the accepted server price for known conflicts and identify affected positions in priceConflicts; history annotates each affected point and movers include both dates. Other prices and bookkeeping reports continue working. Inspect price conflicts --json, then resolve using the displayed server revision and local edit ID. --choose local keeps the correction for the next sync; --choose server discards it. To supply a replacement, keep local and record the replacement before syncing. Stale resolution choices fail.

MCP server

iris mcp

Run a Model Context Protocol server that exposes the iris verbs to a BYO agent, for one book or a parent folder containing several books.

iris mcp serve [--book PATH | --books-dir PATH] [--http 127.0.0.1:PORT]

Local tools: validate, diff, balance, report, status, check_update. Asset tools: asset_schedule (the engine’s per-asset figures) plus three depreciation calculators — declining_table, flat_table, straight_line_table — your AI composes into a recorded schedule: for methods no recipe covers. Recipe tools: one per recipe of the book’s overlay (jp_teiritsu, jp_shouhizei-general, jp_shouhizei-simplified), each recording its result with provenance when given a write path. Cloud tools (when authenticated): sync, seal, export_from_cloud, create_cloud_book, link_book. Host sign-in tools: login, auth_status, logout. list_cloud_books lists accessible cloud books, separately from local folder discovery.

Use filesystem tools (rg/grep, find, cat, sed, or a file editor) for routine file listing, reading, text searches and draft/note edits when the book folder is accessible. Keep operations within the selected book and run validation after bookkeeping edits. Local MCP is for accounting calculations, validation, authentication, sync and guarded workflows such as posting, seals, depreciation and year-end. Structured search and other inspection tools are optional helpers, useful for structured filters, derived closed status or when the client cannot access files directly.

Local MCP has no show tool. Use filesystem searches to inspect document/journal references; the CLI iris show command remains available.

Bookkeeping tools: autocomplete_journal (path, optional view, apply, revision; the same draft-only preview/apply as iris autocomplete), search_journals (date, amount, payee, status, account and tag filters), post (paths or all, with dry_run=true to preview), list_conflicts, resolve_conflict (path, keep="mine"|"cloud"), list_attention, retry_attention (optional path) and export_local (kind="journals"|"assets", optional year, as_of for journals and out_dir, default exports/). Exports stay inside the selected book. Keeping mine during conflict resolution immediately syncs; keeping cloud removes the sidecar. Retrying attention clears rejections for the next explicit sync.

Lifecycle tools: asset_depreciate (month or year, creates drafts without posting), fiscal_years, history (optional path, from, to, limit, all) and reopen (year, server-enforced owner access). The latter three require a linked book and sign-in. Parent mode additionally offers yearend(book="freelance-2025", next_book="freelance-2026"): it previews the opening entry, journal moves and cloud actions by default. Use preview=false to execute, or local_only=true to skip cloud creation/sync. It may move journals, copy grants and transfer the inbound address; it does not seal the old year. next_book is an immediate child folder, with the same confinement rules as book creation. The CLI also supports iris yearend --dry-run.

Portfolio reads: get_holdings reads the selected linked cloud book. get_networth values local folders, including unsynced posted edits, without login or network: use books_dir="." for child books of the configured root, or books=["business-2026", "personal-2026"]. Paths stay inside that root. It accepts as_of, currency, and mode="current"|"history"|"movers" (with months for history or compare for movers). It uses the local price log; mixed currencies require currency. A single-book server can select only folders inside its pinned book; use parent mode for sibling books. list_prices defaults to cloud, or accepts source="local" for the offline log. record_price(unit="USD", currency="JPY", price="155.123456", date="2026-10-06") records an observation locally; price must be a decimal string with at most six fractional digits. sync_prices publishes it and pulls other-device observations, separately from book sync. Prices live in the user’s config folder, outside tax book files. Except get_holdings, these portfolio tools need no local book selector; local valuation requires book folders.

Cloud list_prices accepts unit, currency, year, limit and cursor, and returns one page; follow nextCursor with the same filters. Local reads flag known conflicts and return the effective report price. Use price_conflicts to inspect both candidates, and resolve_price_conflict(unit, currency, date, choose="local"|"server", revision, edit) only with the user’s explicit choice and the displayed identifiers. Resolution is local; sync afterward to publish a retained correction. Identical price/source edits converge without a prompt.

With --books-dir, select an existing parent folder, such as ~/IrisBooks, whose immediate children are book folders. Call list_books to discover them, then pass the child folder name as book on each book operation, such as validate(book="freelance-2026"). New books appear without restarting. An empty parent is supported: create_book(book="freelance-2026") creates a child book; optional name, region, fiscal_start_month and fiscal_year customize it. The parent itself is never initialized as a book, and existing books or non-empty child folders are never overwritten.

In parent mode, use list_recipes(book="freelance-2026") to discover that book’s recipe names and input schemas, then run_recipe(book="freelance-2026", recipe="jp_teiritsu", params={...}). The three arithmetic calculators and check_update need no book. Book references cannot be paths, and symlinks escaping the selected book are refused. export_from_cloud writes inside the selected book, including when out_dir is omitted.

For Cowork or another MCP client, connect a local book without a terminal:

  1. Call create_book(book="freelance-2026"), or select a folder from list_books.
  2. Call login. It opens the host browser and returns immediately with an authorization_url and a five-minute expiry. If the browser did not open, open that URL yourself. Sign in and approve access.
  3. Call auth_status to confirm status="signed_in". This checks the server; validate=false checks only local credential presence. Repeating login while approval is pending returns the same URL.
  4. Call create_cloud_book(book="freelance-2026"). It uses the local book’s settings, creates its cloud mirror and saves the link. Retrying an uncertain creation uses the same retry key. For an existing cloud book, call list_cloud_books, then link_book(book="freelance-2026", cloud_book_id="bk_...").
  5. Call sync(book="freelance-2026") and read its accepted/rejected results.

login, auth_status, logout and list_cloud_books need no book argument. Sign-in is saved on the host and shared with the CLI and other local MCP instances; credentials never appear in tool results. logout cancels this server’s pending login and revokes/removes the saved device session. An IRIS_API_TOKEN environment credential takes precedence: remove it from the host MCP configuration and restart before using browser sign-in. Cloud creation/linking refuse archives and already-linked books. Linking an existing cloud book can produce file conflicts on sync; for a new cloud mirror of your folder, use create_cloud_book.

To download an existing cloud book in parent-folder mode, use login → auth_status → list_cloud_books → clone_book(book="freelance-2026", cloud_book_id="bk_..."). Choose an absent or empty child folder; do not call create_book first. clone_book downloads the files, installs the local AI guides and initializes sync state and snapshots, so diff starts with no pending changes. The result includes the host path and book identity, and list_books discovers it without a restart. It refuses existing content, symlinks, archives and any pre-existing <book>.partial staging entry. There is no force option. It is available only with --books-dir.

--book keeps the single-book tools described above; an empty or absent target is initialized automatically. The two directory flags cannot be combined.

With --http, the server listens on a localhost address instead of stdio and accepts only requests carrying the header Authorization: Bearer <token>. The token is created the first time you start it, in ~/.config/irisbooks/mcp-http.token (readable only by you), and stays the same after restarts, so set it once as a header in your MCP client. Delete the file to get a new token on the next start, then update the header in your MCP client to match.

iris version

iris version

Cloud commands (iris api …)

These operate on a book ID and require authentication.

Account-scoped

iris api login

Browser-assisted sign-in. This device gets its own CLI session: signing out of the web app does not affect it, it expires only after 90 days unused (each use renews it), and you can revoke it in the web app under your Settings (avatar menu) → API tokens.

iris api login
iris api whoami     # validate the cached token
iris api logout     # revoke this device's session and remove the token

For an LLM running in a VM, use phone approval. The VM needs no browser, passkey or shared download folder:

iris api login --device
# Give the user the printed IrisBooks URL and request number.
# On their own phone: sign in, enter the number, review access and approve.
iris api login --device-complete
iris api whoami

The user can sign in with a registered passkey or password + authenticator code. Enter passwords and authenticator codes only in the user’s browser; never give them to the agent. Number entry alone does not approve access: check the account, permissions and warning before approving. The device name is supplied by the requesting device and is not verified.

--device-complete checks once. A waiting message with exit code 0 means approval is still pending and no new credential was saved; wait at least five seconds between checks. Continue only after Authorized and a successful whoami. Requests expire ten minutes after start. Starting again replaces the local pending request; use the latest number. Denied, expired or uncertain completion needs a new request. Completion uses the original environment. Only the CLI handles private proof and credential storage; do not inspect or share its pending/token files. iris api logout clears local pending state.

File delivery is also available when the browser’s download folder is accessible to the CLI:

iris api login --handoff-file
# Open the printed URL, sign in, and allow access. The browser downloads iris-login.json.
iris api login --complete /path/to/downloads/iris-login.json
iris api whoami

Pass only the downloaded file’s path; the CLI reads it and stores the session credential internally. The file contains a single-use authorization code, not a session token. It is bound to the private PKCE verifier retained by the CLI and expires 5 minutes after approval. The pending login expires 10 minutes after starting; starting again replaces it, so use the new URL and download. Completion uses the environment selected when login started. Cancel on the consent page downloads a cancellation result; completing that file clears the pending login without saving a credential. After completion, remove the downloaded file. Normal iris api books … and iris sync then work unchanged.

iris api books

List or create cloud books, or link a local book to one.

iris api books list [--json]
iris api books new [flags]
iris api books link [--force] <book-id> [path]

Run new from inside a local book that has no cloud id yet and the new book is linked to that folder automatically — the same effect as books link, so the next iris sync just works. The cloud book is created from that folder’s config/book.yaml (name, fiscal year, region, currency and fiscal-year start), so no flags are needed; a flag that contradicts the file is refused. It starts empty, and that first sync pushes the folder’s own files. --no-link skips all of this. Run new from inside a book that is already linked and it refuses: a second cloud book there would sit empty while iris sync keeps pushing to the first.

Outside a book folder, provide at least --name and --fy:

iris api books new --name "Acme Design" --fy 2026 --region JP

Other flags are --currency, --scale, --no-link, and --idempotency-key. Creation prints a retry key; retain it and reuse --idempotency-key <key> if the result is uncertain, to avoid a duplicate book. Inside a local book, the default key is derived from its stable local ID, so retrying the same creation uses the same key.

iris api token

List or revoke your Personal Access Tokens. Minting a new PAT is web-only (your Settings → API tokens); revocation is available here too so an automated run can revoke the PAT it used when it finishes. Requires a signed-in session or a Full-access PAT.

iris api token list [--json]
iris api token revoke <token-id>

iris api config

Show or set per-user preferences. Reporting currency controls cross-book reports. Default unit scales are a template copied into config/book.yaml by signed-in iris init when available; existing books are unchanged. Without default units, new books start with no explicit unit entries. Known currencies still use built-in precision; templates can override their quantity precision or declare custom units. --remove-unit removes a unit from the account template, not existing books. iris init --no-user-defaults skips the template.

iris api config
iris api config set --base-currency USD
iris api config set --set-unit BTC:8,XAU:4
iris api config set --remove-unit XAG

Per-book

iris api grants

Manage book access (owner-only).

iris api grants list [--json] <book-id>
iris api grants invite --email EMAIL <book-id>
iris api grants role --user USER_ID --to OWNER|BOOKKEEPER|REVIEWER <book-id>
iris api grants revoke --user USER_ID <book-id>

iris api inbox

Inbound email for a book: show the receiving address, manage the sender allowlist, and review quarantined mail. See “Receipts by email”.

iris api inbox show [--json] <book-id>
iris api inbox allow <pattern> <book-id>       # user@host or *@host
iris api inbox disallow <pattern> <book-id>
iris api inbox quarantine [--json] <book-id>

iris api seal

Close a fiscal year (period seal). The server locks journals dated in the year and config/, assets/, filings/, compiled/. raw/ and notes/ remain writable. Local files can still be edited, but locked changes cannot sync; the year’s files stay in your working tree. To amend a sealed year, run iris reopen <fy> — changes to the locked scope while reopened are flagged in the audit trail (iris api history) — then re-run this command to close the year again (the new seal supersedes the old one). --preview lists the files the seal would lock, without sealing. Sealing is refused while the year still contains draft drafts — a sealed year must be fully resolved. Post each draft if it belongs in the year, delete it if abandoned, or change its date into an open year; --preview lists the blockers.

Before previewing or closing, recorded filings must replay successfully against the cloud’s current posted journals and recipe sources. A stale filing after a correction, or an unavailable recipe source, can block even the preview before it lists drafts. Re-run the filing recipe with the intended inputs, run iris validate, sync the filing and its inputs, and preview again. If the cloud still reports an unavailable replay projection after a successful sync, seek support from the Discord community; repeated close attempts will not repair it.

iris api seal --period YYYY [--type yearly] [--preview] <book-id>

iris api balance / holdings / history

iris api balance [--as-of YYYY-MM-DD] <book-id>              # server-side trial balance (JSON output)
iris api holdings [--as-of YYYY-MM-DD] [--json] <book-id>    # per-unit net positions
iris api history [--path PATH] [--from D] [--to D] [--all] [--limit N] [--json] <book-id> # durable correction/deletion record

Output shapes:

balance   [ { account, debits, credits, type?, net? } ]
holdings  [ { account, unit, quantity } ]
history   [ { id, path, op, sha?, version_id?, size_bytes,
              actor, actor_display?, source?, reason?,
              post_seal_period?, moved_from_path?, ts } ]

holdings counts only unit-tagged lines, and positions that net to zero are omitted server-side.

In history — the durable correction/deletion record — actor is the stable account identifier (the audit identity) and actor_display is the server’s read-time resolution of it to a name or email, for human readers. post_seal_period is set when the change landed after that fiscal year was sealed; that is the flag an auditor looks for. moved_from_path records a rename rather than a delete plus a create.

history returns the newest 100 events unless you narrow it:

  • --path — one file’s timeline.
  • --all — every event. A book is one fiscal year, so this is the year’s whole record, corrections made after it was closed included.
  • --from / --to — changes made between two dates, in the book’s time zone (Japan time for a JP book).

With --from, --to or --all it returns every matching event; --limit still caps it when you give one. To hand the record over as a file, for example when a tax office asks for it, write it out as JSON:

iris api history --all --json <book-id> > history-2025.json

books list --json emits a stable list with bookId, fiscalYear, role, name, region, and currency. The other cloud commands’ --json (grants list, token list, inbox show, inbox quarantine) passes the server’s response through unchanged.

iris api export

iris api export [--out DIR] [--year YYYY] [--as-of YYYY-MM-DD] <book-id>  # CSVs

With --year, the export is restricted to journals dated in that fiscal year. Sealed years stay in the live tree, so they export like any other year.

iris api price / networth

iris api price add --unit BTC --price 9850000 [--date YYYY-MM-DD] [--source S] [--revision N]
iris api price list [--unit BTC] [--currency JPY] [--year 2026] [--page] [--limit 500] [--cursor C] [--json]
iris api networth --book <book-id> [--as-of YYYY-MM-DD] [--json]

iris api price list follows every server page by default. --page returns { items, nextCursor, syncCursor, hasMore, userId }; follow nextCursor using --cursor with the same filters. Page size is 1–1000. Each item includes its revision. Creation uses revision 0; correcting an existing cell requires add --revision N from the displayed row. A stale different edit fails with a conflict; identical value/source retries acknowledge the accepted row.

iris api networth values one explicitly selected cloud book. Cloud cross-book totals, history, movers and inclusion settings were removed from the CLI. Use local iris networth or the web app instead. Web and remote MCP cloud cross-book reports share at most one automatic snapshot generation per user per UTC day. Changing dates or book selections does not regenerate it. To include synced changes sooner, choose Run aggregation on the web app’s Dashboard or Net Worth page. Responses include the generation time and next automatic refresh time.

Environment variables

VariablePurpose
IRIS_API_TOKENPersonal Access Token; takes precedence over the cached login token
IRIS_BOOKDefault book ID for cloud commands (a positional arg still wins)
IRIS_API_ENDPOINTAPI endpoint override; takes precedence over everything below
IRISBOOKS_APP_BASEApp base URL (default https://irisbooks.io)
IRISBOOKS_API_BASEAPI base URL (default <app-base>/api)

When none of the endpoint variables are set, commands use the endpoint saved by your last iris api login — so a session signed in to one environment keeps talking to that same environment.

Common exit codes

CodeMeaning
0Success
1Validation errors, rejections, or I/O failure
2Usage or setup error (not in a book, bad flags)
3Terminal disconnect — sync commands only (book deleted, access revoked, session expired)