Skip to main content

Canhoto

Canhoto keeps your bank and card statements on your computer. It parses statements, categorizes spending, asks you about uncertain items, and exports a monthly PDF summary.

Using an AI agent? Connect canhoto-mcp. The agent gets a bounded set of tools to parse statements, review and categorize transactions, read monthly totals, and export reports. It never gets SQL access or a full ledger dump. The CLI gives you the same workflow by hand.

Package canhoto
CLI canhoto
MCP canhoto-mcp
Data ~/.canhoto or $CANHOTO_DATA_DIR

Install

Install the CLI and the MCP server from PyPI:

uv tool install canhoto

Or install from a checkout of this repository:

uv tool install .

Or run it in place:

uv sync
uv run canhoto --help

Then create the data directory and check it:

canhoto init
canhoto doctor

How it works

flowchart TD
  A[Statement PDF or text] --> B[Parser]
  B --> C[Ingest]
  C --> D[(Local SQLite ledger)]
  D --> E[Your rules, built-in rules, merchant memory]
  E --> F[Review uncertain items]
  F --> G[Apply categories]
  G --> H[Monthly breakdown]
  H --> I[Summary PDF]

Parsers only read statement rows. Canhoto handles categories, rules, merchant memory, reports, and exports.

Create a parser

Canhoto does not ship bank-specific parsers. Create one for your statement:

canhoto parsers scaffold --id my_bank_card --type card --institution my_bank

Edit ~/.canhoto/parsers/my_bank_card.py, then test and enable it:

canhoto parsers test --id my_bank_card --file ~/statements/sample.pdf
canhoto parsers enable --id my_bank_card

You can enable a parser only after a test extracts at least one transaction. A failed test disables the parser until it passes again. See examples/parsers/ for a small example.

For password-protected PDFs, pass --pdf-password or set CANHOTO_PDF_PASSWORD.

Process a month

# Read statements into the ledger.
canhoto ingest ~/statements/*.pdf

# Apply your rules, the built-in rules, and merchant memory.
canhoto categorize rules --month 2026-06

# List the items that still need a decision.
canhoto review --month 2026-06

# Apply your decisions.
canhoto categorize apply --file patches.json

# See income, expenses, and category totals.
canhoto breakdown --month 2026-06

# Write the PDF summary.
canhoto export pdf 2026-06

patches.json is a list of changes, one per transaction id from review:

[
  { "id": "my_bank_card-2026-06-02-0001", "category": "Groceries",
    "kind": "expense", "is_expense": true, "needs_review": false }
]

Canhoto marks every row you change this way as manual. No automatic rule changes a manual row again.

The PDF goes to ~/.canhoto/exports/2026-06-summary.pdf by default. It shows totals by category and the top merchants in each category. It never contains a transaction list or raw statement descriptions.

Teach Canhoto your rules

When a counterparty always means the same thing, store a rule. categorize rules applies it to every future statement.

canhoto rules add --pattern "PIX RECEBIDO ACME LTDA" --direction in \
  --min 1200 --max 1300 --category Income --kind income \
  --note "Monthly pay from my company"
canhoto rules list
canhoto rules remove --id 3
Option Meaning
--pattern Regular expression, case-insensitive, matched against the description.
--direction in (money received), out (money sent), or any.
--min, --max Amount range, inclusive, without the sign. Both are optional.
--source-kind Match only account or card rows.
--category The category to set.
--kind expense, income, transfer, internal_transfer, self_transfer, or card_payment. Transfers do not count as income or spending.
--review Set the category, but keep the row in the review queue. Use this when an amount range cannot separate two cases.
--note Why the rule exists. Agents read it during review. Do not put secrets in it.
--priority Lower numbers run first. The first matching rule wins.

Your rules run before the built-in rules. They never change manual rows. When you upgrade to the version with rules, Canhoto marks your existing reviewed rows as manual.

To remember one merchant without a full rule, use merchant memory:

canhoto categorize merchant --key CURSOR --category Subscriptions

PDF profiles

Choose a built-in style:

canhoto export pdf 2026-06 --profile canhoto
canhoto export pdf 2026-06 --profile modern --output ~/Documents/2026-06.pdf
canhoto export pdf 2026-06 --profile minimal
  • canhoto: receipt-style report with a category chart.
  • modern: clean report with metric cards and a category chart.
  • minimal: text-only report without a chart.

MCP

The CLI and the MCP server use the same service layer. For agent-assisted use, start the MCP server. The agent follows this flow:

statement_preview -> parser_* -> ingest -> rule_list -> run_rules -> review_batch -> set_categories -> month_breakdown -> export_pdf

When you explain a recurring counterparty to the agent, it can store it with rule_add. The MCP server exposes only domain tools. It does not give SQL access or full ledger dumps.

To let an agent write parsers, add this to ~/.canhoto/config.json:

{ "agent_view": { "allow_parser_writes": true } }

Example MCP host configuration:

mcp_servers:
  canhoto:
    command: canhoto-mcp

CLI commands

canhoto init | doctor
canhoto parsers scaffold|test|enable|list
canhoto ingest <files...> [--pdf-password PASSWORD]
canhoto categorize rules --month YYYY-MM
canhoto categorize apply --file patches.json
canhoto categorize merchant --key KEY --category CAT
canhoto rules add|list|remove
canhoto review --month YYYY-MM [--cursor ID] [--limit N]
canhoto breakdown --month YYYY-MM
canhoto export pdf YYYY-MM [--profile canhoto|modern|minimal] [--output PATH]

Develop

uv sync --extra dev
uv run pytest -q
uv run ruff check src/canhoto tests
uv run mypy -p canhoto

Or use the justfile with Just.

Release

Pushing a version tag publishes to PyPI through .github/workflows/release.yml. The workflow tests, builds, smoke-tests the wheel and the source distribution, and publishes with PyPI Trusted Publishing. The tag must match the version in pyproject.toml.

uv version --bump patch   # or minor / major
git commit -am "chore: release v$(uv version --short)"
git tag -a "v$(uv version --short)" -m "v$(uv version --short)"
git push origin main --tags

Privacy

Your statements and database stay in the data directory. Do not commit statements, tokens, database files, or ~/.canhoto. Back up canhoto.db before you upgrade: it holds your ledger and your rules.

Metadata

Release files for canhoto 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for canhoto 0.1.0
File Size Uploaded
canhoto-0.1.0.tar.gz 73.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for canhoto 0.1.0
File Interpreter ABI Platform
canhoto-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 130.7 kB

Release files / canhoto-0.1.0.tar.gz

Download URL canhoto-0.1.0.tar.gz
Size 73.6 kB
Tags Source
SHA-256 checksum
How to use checksums
d4683f0e6ab636a8bfe7c7c6ab4ac71555f5edd0f0d489634f7e3cd189f122ee
BLAKE2b-256 checksum
How to use checksums
cace52e0af013380889d040192adba97a14e5f6aea4ba308d5e7b8fdd43d3aaf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.

Transparency log

Release files / canhoto-0.1.0-py3-none-any.whl

Download URL canhoto-0.1.0-py3-none-any.whl
Size 57.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
543ff56c5487c647d1ead4b69a729fe63e72f5cb19dd67fa1787a5fc800a46cc
BLAKE2b-256 checksum
How to use checksums
502e35589bc5851b24676760d6c8cfb91f933739d59a1d6e04e1e8119e8d9d69
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.0

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page