canhoto 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. canhoto-0.1.0/.gitignore +14 -0
  2. canhoto-0.1.0/AGENTS.md +86 -0
  3. canhoto-0.1.0/LICENSE +21 -0
  4. canhoto-0.1.0/PKG-INFO +275 -0
  5. canhoto-0.1.0/README.md +244 -0
  6. canhoto-0.1.0/examples/parsers/README.md +107 -0
  7. canhoto-0.1.0/examples/parsers/demo_line_parser.py +178 -0
  8. canhoto-0.1.0/examples/parsers/fixtures/demo_statement.txt +5 -0
  9. canhoto-0.1.0/pyproject.toml +85 -0
  10. canhoto-0.1.0/src/canhoto/__init__.py +3 -0
  11. canhoto-0.1.0/src/canhoto/__main__.py +10 -0
  12. canhoto-0.1.0/src/canhoto/cli.py +386 -0
  13. canhoto-0.1.0/src/canhoto/core/__init__.py +1 -0
  14. canhoto-0.1.0/src/canhoto/core/breakdown.py +136 -0
  15. canhoto-0.1.0/src/canhoto/core/categorize.py +469 -0
  16. canhoto-0.1.0/src/canhoto/core/config.py +84 -0
  17. canhoto-0.1.0/src/canhoto/core/migrate.py +91 -0
  18. canhoto-0.1.0/src/canhoto/core/models.py +336 -0
  19. canhoto-0.1.0/src/canhoto/core/pdf_text.py +74 -0
  20. canhoto-0.1.0/src/canhoto/core/policy.py +48 -0
  21. canhoto-0.1.0/src/canhoto/core/redaction.py +117 -0
  22. canhoto-0.1.0/src/canhoto/core/store.py +593 -0
  23. canhoto-0.1.0/src/canhoto/core/user_rules.py +49 -0
  24. canhoto-0.1.0/src/canhoto/exporters/__init__.py +1 -0
  25. canhoto-0.1.0/src/canhoto/exporters/pdf_summary.py +433 -0
  26. canhoto-0.1.0/src/canhoto/exporters/protocol.py +23 -0
  27. canhoto-0.1.0/src/canhoto/mcp/__init__.py +1 -0
  28. canhoto-0.1.0/src/canhoto/mcp/__main__.py +6 -0
  29. canhoto-0.1.0/src/canhoto/mcp/allowlist.py +39 -0
  30. canhoto-0.1.0/src/canhoto/mcp/server.py +189 -0
  31. canhoto-0.1.0/src/canhoto/migrations/__init__.py +1 -0
  32. canhoto-0.1.0/src/canhoto/migrations/env.py +48 -0
  33. canhoto-0.1.0/src/canhoto/migrations/script.py.mako +26 -0
  34. canhoto-0.1.0/src/canhoto/migrations/versions/001_initial_schema.py +100 -0
  35. canhoto-0.1.0/src/canhoto/migrations/versions/002_user_rules.py +59 -0
  36. canhoto-0.1.0/src/canhoto/parsers/__init__.py +35 -0
  37. canhoto-0.1.0/src/canhoto/parsers/loader.py +191 -0
  38. canhoto-0.1.0/src/canhoto/parsers/protocol.py +40 -0
  39. canhoto-0.1.0/src/canhoto/parsers/scaffold.py +138 -0
  40. canhoto-0.1.0/src/canhoto/service.py +969 -0
  41. canhoto-0.1.0/tests/guardrails/test_mcp_allowlist.py +73 -0
  42. canhoto-0.1.0/tests/guardrails/test_models_contracts.py +131 -0
  43. canhoto-0.1.0/tests/guardrails/test_policy.py +66 -0
  44. canhoto-0.1.0/tests/guardrails/test_redaction.py +142 -0
  45. canhoto-0.1.0/tests/guardrails/test_review_batch.py +261 -0
  46. canhoto-0.1.0/tests/test_categorize.py +159 -0
  47. canhoto-0.1.0/tests/test_cli_ingest.py +106 -0
  48. canhoto-0.1.0/tests/test_config.py +146 -0
  49. canhoto-0.1.0/tests/test_doctor.py +208 -0
  50. canhoto-0.1.0/tests/test_export_pdf.py +378 -0
  51. canhoto-0.1.0/tests/test_ingest.py +170 -0
  52. canhoto-0.1.0/tests/test_mcp_server.py +128 -0
  53. canhoto-0.1.0/tests/test_merchant_memory.py +181 -0
  54. canhoto-0.1.0/tests/test_migrate.py +82 -0
  55. canhoto-0.1.0/tests/test_month_breakdown.py +269 -0
  56. canhoto-0.1.0/tests/test_parser_flow.py +290 -0
  57. canhoto-0.1.0/tests/test_parser_loader.py +250 -0
  58. canhoto-0.1.0/tests/test_review.py +124 -0
  59. canhoto-0.1.0/tests/test_store.py +258 -0
  60. canhoto-0.1.0/tests/test_user_rules.py +504 -0
@@ -0,0 +1,14 @@
1
+
2
+ # Python
3
+ __pycache__/
4
+ *.py[cod]
5
+ .venv/
6
+ dist/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .finance-ingest/
12
+ .canhoto/
13
+ *.db
14
+ .pi-subagents/
@@ -0,0 +1,86 @@
1
+ # Agent bootstrap — Canhoto
2
+
3
+ Read this before editing code.
4
+
5
+ ## Product
6
+
7
+ | Layer | Value |
8
+ |---|---|
9
+ | Product | **Canhoto** (stub/counterfoil you keep) |
10
+ | Package / import | `canhoto` |
11
+ | CLI | `canhoto` |
12
+ | MCP | `canhoto-mcp` |
13
+ | Data | `~/.canhoto` / `$CANHOTO_DATA_DIR` |
14
+ | DB | `canhoto.db` |
15
+
16
+ User docs: [`README.md`](README.md).
17
+
18
+ ## Layout
19
+
20
+ ```text
21
+ src/canhoto/
22
+ core/ # models, config, store, migrate, policy, redaction, categorize, …
23
+ migrations/ # Alembic scripts (SQLite)
24
+ parsers/ # Protocol, loader, scaffold (no bank logic)
25
+ exporters/ # pdf_summary
26
+ mcp/ # allowlist + Fast/MCP server → service only
27
+ service.py # façade for CLI + MCP
28
+ cli.py
29
+ examples/parsers/ # docs-only demo (not auto-loaded)
30
+ tests/guardrails/ # privacy/allowlist contracts
31
+ ```
32
+
33
+ Dependency direction: `cli` / `mcp` → `service` → `core` + ports.
34
+
35
+ ## Hard rules
36
+
37
+ | Do | Do not |
38
+ |---|---|
39
+ | Keep money out of git | Commit statements, tokens, DB files |
40
+ | MCP tools ⊆ `MCP_TOOL_ALLOWLIST` | Add `sql_query` or full ledger dumps |
41
+ | Review via `to_review_item` + policy | Return raw `LedgerTransaction` to agents |
42
+ | Parser enable only after successful `parser_test` (non-empty txs) | Auto-download parsers; stamp OK on empty parse |
43
+ | PDF summary only | Full transaction listing PDF |
44
+ | Country-agnostic core | Hardcode one bank as the kernel |
45
+ | Prefer deleting dead code | Keep unused alternate trees |
46
+
47
+ ## MCP happy path
48
+
49
+ 1. `statement_preview` → `parser_scaffold` / `parser_write` → `parser_test` → `parser_enable`
50
+ 2. `ingest`
51
+ 3. `rule_list` → `run_rules` → `review_batch` loop → `set_categories` (and `set_merchant_category` as needed).
52
+ Store recurring decisions with `rule_add` and a note.
53
+ Rules never overwrite rows set by `set_categories`.
54
+ 4. `month_breakdown` → `export_pdf`
55
+
56
+ `parser_write` requires `agent_view.allow_parser_writes=true`. CLI may always write local parsers.
57
+
58
+ ## Data dir
59
+
60
+ ```text
61
+ $CANHOTO_DATA_DIR or ~/.canhoto/
62
+ config.json
63
+ canhoto.db
64
+ parsers/
65
+ exports/
66
+ raw/
67
+ fixtures/
68
+ ```
69
+
70
+ ## Commands
71
+
72
+ ```bash
73
+ uv sync --extra dev
74
+ uv run pytest -q
75
+ uv run ruff check src/canhoto tests
76
+ uv run mypy -p canhoto
77
+ uv run canhoto --help
78
+ uv run canhoto-mcp # stdio; host-spawned
79
+ ```
80
+
81
+ ## Quality bar
82
+
83
+ - TDD for behavior changes; meaningful tests only
84
+ - No Google/Sheets in core
85
+ - Guardrails in `tests/guardrails/` must stay green
86
+
canhoto-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 luabagg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
canhoto-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,275 @@
1
+ Metadata-Version: 2.5
2
+ Name: canhoto
3
+ Version: 0.1.0
4
+ Summary: Local bank statement ledger: parse, categorize, and export monthly PDF summaries (CLI and MCP)
5
+ Project-URL: Homepage, https://github.com/luabagg/canhoto
6
+ Project-URL: Documentation, https://github.com/luabagg/canhoto/blob/main/README.md
7
+ Project-URL: Issues, https://github.com/luabagg/canhoto/issues
8
+ Author: luabagg
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: cli,finance,ledger,mcp,pdf
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Office/Business :: Financial
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: alembic>=1.19.1
22
+ Requires-Dist: fpdf2>=2.8.7
23
+ Requires-Dist: mcp>=1.0
24
+ Requires-Dist: pydantic>=2.0
25
+ Requires-Dist: pymupdf>=1.24.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy>=1.10; extra == 'dev'
28
+ Requires-Dist: pytest>=8.0; extra == 'dev'
29
+ Requires-Dist: ruff>=0.6; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # Canhoto
33
+
34
+ Canhoto keeps your bank and card statements on your computer. It parses
35
+ statements, categorizes spending, asks you about uncertain items, and exports a
36
+ monthly PDF summary.
37
+
38
+ > **Using an AI agent?** Connect `canhoto-mcp`. The agent gets a bounded set of
39
+ > tools to parse statements, review and categorize transactions, read monthly
40
+ > totals, and export reports. It never gets SQL access or a full ledger dump.
41
+ > The CLI gives you the same workflow by hand.
42
+
43
+ | | |
44
+ |---|---|
45
+ | Package | `canhoto` |
46
+ | CLI | `canhoto` |
47
+ | MCP | `canhoto-mcp` |
48
+ | Data | `~/.canhoto` or `$CANHOTO_DATA_DIR` |
49
+
50
+ ## Install
51
+
52
+ Install the CLI and the MCP server from PyPI:
53
+
54
+ ```bash
55
+ uv tool install canhoto
56
+ ```
57
+
58
+ Or install from a checkout of this repository:
59
+
60
+ ```bash
61
+ uv tool install .
62
+ ```
63
+
64
+ Or run it in place:
65
+
66
+ ```bash
67
+ uv sync
68
+ uv run canhoto --help
69
+ ```
70
+
71
+ Then create the data directory and check it:
72
+
73
+ ```bash
74
+ canhoto init
75
+ canhoto doctor
76
+ ```
77
+
78
+ ## How it works
79
+
80
+ ```mermaid
81
+ flowchart TD
82
+ A[Statement PDF or text] --> B[Parser]
83
+ B --> C[Ingest]
84
+ C --> D[(Local SQLite ledger)]
85
+ D --> E[Your rules, built-in rules, merchant memory]
86
+ E --> F[Review uncertain items]
87
+ F --> G[Apply categories]
88
+ G --> H[Monthly breakdown]
89
+ H --> I[Summary PDF]
90
+ ```
91
+
92
+ Parsers only read statement rows. Canhoto handles categories, rules, merchant
93
+ memory, reports, and exports.
94
+
95
+ ## Create a parser
96
+
97
+ Canhoto does not ship bank-specific parsers. Create one for your statement:
98
+
99
+ ```bash
100
+ canhoto parsers scaffold --id my_bank_card --type card --institution my_bank
101
+ ```
102
+
103
+ Edit `~/.canhoto/parsers/my_bank_card.py`, then test and enable it:
104
+
105
+ ```bash
106
+ canhoto parsers test --id my_bank_card --file ~/statements/sample.pdf
107
+ canhoto parsers enable --id my_bank_card
108
+ ```
109
+
110
+ You can enable a parser only after a test extracts at least one transaction.
111
+ A failed test disables the parser until it passes again. See
112
+ [`examples/parsers/`](examples/parsers/) for a small example.
113
+
114
+ For password-protected PDFs, pass `--pdf-password` or set
115
+ `CANHOTO_PDF_PASSWORD`.
116
+
117
+ ## Process a month
118
+
119
+ ```bash
120
+ # Read statements into the ledger.
121
+ canhoto ingest ~/statements/*.pdf
122
+
123
+ # Apply your rules, the built-in rules, and merchant memory.
124
+ canhoto categorize rules --month 2026-06
125
+
126
+ # List the items that still need a decision.
127
+ canhoto review --month 2026-06
128
+
129
+ # Apply your decisions.
130
+ canhoto categorize apply --file patches.json
131
+
132
+ # See income, expenses, and category totals.
133
+ canhoto breakdown --month 2026-06
134
+
135
+ # Write the PDF summary.
136
+ canhoto export pdf 2026-06
137
+ ```
138
+
139
+ `patches.json` is a list of changes, one per transaction id from `review`:
140
+
141
+ ```json
142
+ [
143
+ { "id": "my_bank_card-2026-06-02-0001", "category": "Groceries",
144
+ "kind": "expense", "is_expense": true, "needs_review": false }
145
+ ]
146
+ ```
147
+
148
+ Canhoto marks every row you change this way as `manual`. No automatic rule
149
+ changes a manual row again.
150
+
151
+ The PDF goes to `~/.canhoto/exports/2026-06-summary.pdf` by default. It shows
152
+ totals by category and the top merchants in each category. It never contains a
153
+ transaction list or raw statement descriptions.
154
+
155
+ ### Teach Canhoto your rules
156
+
157
+ When a counterparty always means the same thing, store a rule. `categorize
158
+ rules` applies it to every future statement.
159
+
160
+ ```bash
161
+ canhoto rules add --pattern "PIX RECEBIDO ACME LTDA" --direction in \
162
+ --min 1200 --max 1300 --category Income --kind income \
163
+ --note "Monthly pay from my company"
164
+ canhoto rules list
165
+ canhoto rules remove --id 3
166
+ ```
167
+
168
+ | Option | Meaning |
169
+ |---|---|
170
+ | `--pattern` | Regular expression, case-insensitive, matched against the description. |
171
+ | `--direction` | `in` (money received), `out` (money sent), or `any`. |
172
+ | `--min`, `--max` | Amount range, inclusive, without the sign. Both are optional. |
173
+ | `--source-kind` | Match only `account` or `card` rows. |
174
+ | `--category` | The category to set. |
175
+ | `--kind` | `expense`, `income`, `transfer`, `internal_transfer`, `self_transfer`, or `card_payment`. Transfers do not count as income or spending. |
176
+ | `--review` | Set the category, but keep the row in the review queue. Use this when an amount range cannot separate two cases. |
177
+ | `--note` | Why the rule exists. Agents read it during review. Do not put secrets in it. |
178
+ | `--priority` | Lower numbers run first. The first matching rule wins. |
179
+
180
+ Your rules run before the built-in rules. They never change manual rows. When
181
+ you upgrade to the version with rules, Canhoto marks your existing reviewed
182
+ rows as manual.
183
+
184
+ To remember one merchant without a full rule, use merchant memory:
185
+
186
+ ```bash
187
+ canhoto categorize merchant --key CURSOR --category Subscriptions
188
+ ```
189
+
190
+ ### PDF profiles
191
+
192
+ Choose a built-in style:
193
+
194
+ ```bash
195
+ canhoto export pdf 2026-06 --profile canhoto
196
+ canhoto export pdf 2026-06 --profile modern --output ~/Documents/2026-06.pdf
197
+ canhoto export pdf 2026-06 --profile minimal
198
+ ```
199
+
200
+ - `canhoto`: receipt-style report with a category chart.
201
+ - `modern`: clean report with metric cards and a category chart.
202
+ - `minimal`: text-only report without a chart.
203
+
204
+ ## MCP
205
+
206
+ The CLI and the MCP server use the same service layer. For agent-assisted use,
207
+ start the MCP server. The agent follows this flow:
208
+
209
+ `statement_preview` -> `parser_*` -> `ingest` -> `rule_list` -> `run_rules` ->
210
+ `review_batch` -> `set_categories` -> `month_breakdown` -> `export_pdf`
211
+
212
+ When you explain a recurring counterparty to the agent, it can store it with
213
+ `rule_add`. The MCP server exposes only domain tools. It does not give SQL
214
+ access or full ledger dumps.
215
+
216
+ To let an agent write parsers, add this to `~/.canhoto/config.json`:
217
+
218
+ ```json
219
+ { "agent_view": { "allow_parser_writes": true } }
220
+ ```
221
+
222
+ Example MCP host configuration:
223
+
224
+ ```yaml
225
+ mcp_servers:
226
+ canhoto:
227
+ command: canhoto-mcp
228
+ ```
229
+
230
+ ## CLI commands
231
+
232
+ ```text
233
+ canhoto init | doctor
234
+ canhoto parsers scaffold|test|enable|list
235
+ canhoto ingest <files...> [--pdf-password PASSWORD]
236
+ canhoto categorize rules --month YYYY-MM
237
+ canhoto categorize apply --file patches.json
238
+ canhoto categorize merchant --key KEY --category CAT
239
+ canhoto rules add|list|remove
240
+ canhoto review --month YYYY-MM [--cursor ID] [--limit N]
241
+ canhoto breakdown --month YYYY-MM
242
+ canhoto export pdf YYYY-MM [--profile canhoto|modern|minimal] [--output PATH]
243
+ ```
244
+
245
+ ## Develop
246
+
247
+ ```bash
248
+ uv sync --extra dev
249
+ uv run pytest -q
250
+ uv run ruff check src/canhoto tests
251
+ uv run mypy -p canhoto
252
+ ```
253
+
254
+ Or use the [`justfile`](justfile) with [Just](https://just.systems/).
255
+
256
+ ### Release
257
+
258
+ Pushing a version tag publishes to PyPI through
259
+ [`.github/workflows/release.yml`](.github/workflows/release.yml). The workflow
260
+ tests, builds, smoke-tests the wheel and the source distribution, and publishes
261
+ with PyPI Trusted Publishing. The tag must match the version in
262
+ `pyproject.toml`.
263
+
264
+ ```bash
265
+ uv version --bump patch # or minor / major
266
+ git commit -am "chore: release v$(uv version --short)"
267
+ git tag -a "v$(uv version --short)" -m "v$(uv version --short)"
268
+ git push origin main --tags
269
+ ```
270
+
271
+ ## Privacy
272
+
273
+ Your statements and database stay in the data directory. Do not commit
274
+ statements, tokens, database files, or `~/.canhoto`. Back up `canhoto.db` before
275
+ you upgrade: it holds your ledger and your rules.
@@ -0,0 +1,244 @@
1
+ # Canhoto
2
+
3
+ Canhoto keeps your bank and card statements on your computer. It parses
4
+ statements, categorizes spending, asks you about uncertain items, and exports a
5
+ monthly PDF summary.
6
+
7
+ > **Using an AI agent?** Connect `canhoto-mcp`. The agent gets a bounded set of
8
+ > tools to parse statements, review and categorize transactions, read monthly
9
+ > totals, and export reports. It never gets SQL access or a full ledger dump.
10
+ > The CLI gives you the same workflow by hand.
11
+
12
+ | | |
13
+ |---|---|
14
+ | Package | `canhoto` |
15
+ | CLI | `canhoto` |
16
+ | MCP | `canhoto-mcp` |
17
+ | Data | `~/.canhoto` or `$CANHOTO_DATA_DIR` |
18
+
19
+ ## Install
20
+
21
+ Install the CLI and the MCP server from PyPI:
22
+
23
+ ```bash
24
+ uv tool install canhoto
25
+ ```
26
+
27
+ Or install from a checkout of this repository:
28
+
29
+ ```bash
30
+ uv tool install .
31
+ ```
32
+
33
+ Or run it in place:
34
+
35
+ ```bash
36
+ uv sync
37
+ uv run canhoto --help
38
+ ```
39
+
40
+ Then create the data directory and check it:
41
+
42
+ ```bash
43
+ canhoto init
44
+ canhoto doctor
45
+ ```
46
+
47
+ ## How it works
48
+
49
+ ```mermaid
50
+ flowchart TD
51
+ A[Statement PDF or text] --> B[Parser]
52
+ B --> C[Ingest]
53
+ C --> D[(Local SQLite ledger)]
54
+ D --> E[Your rules, built-in rules, merchant memory]
55
+ E --> F[Review uncertain items]
56
+ F --> G[Apply categories]
57
+ G --> H[Monthly breakdown]
58
+ H --> I[Summary PDF]
59
+ ```
60
+
61
+ Parsers only read statement rows. Canhoto handles categories, rules, merchant
62
+ memory, reports, and exports.
63
+
64
+ ## Create a parser
65
+
66
+ Canhoto does not ship bank-specific parsers. Create one for your statement:
67
+
68
+ ```bash
69
+ canhoto parsers scaffold --id my_bank_card --type card --institution my_bank
70
+ ```
71
+
72
+ Edit `~/.canhoto/parsers/my_bank_card.py`, then test and enable it:
73
+
74
+ ```bash
75
+ canhoto parsers test --id my_bank_card --file ~/statements/sample.pdf
76
+ canhoto parsers enable --id my_bank_card
77
+ ```
78
+
79
+ You can enable a parser only after a test extracts at least one transaction.
80
+ A failed test disables the parser until it passes again. See
81
+ [`examples/parsers/`](examples/parsers/) for a small example.
82
+
83
+ For password-protected PDFs, pass `--pdf-password` or set
84
+ `CANHOTO_PDF_PASSWORD`.
85
+
86
+ ## Process a month
87
+
88
+ ```bash
89
+ # Read statements into the ledger.
90
+ canhoto ingest ~/statements/*.pdf
91
+
92
+ # Apply your rules, the built-in rules, and merchant memory.
93
+ canhoto categorize rules --month 2026-06
94
+
95
+ # List the items that still need a decision.
96
+ canhoto review --month 2026-06
97
+
98
+ # Apply your decisions.
99
+ canhoto categorize apply --file patches.json
100
+
101
+ # See income, expenses, and category totals.
102
+ canhoto breakdown --month 2026-06
103
+
104
+ # Write the PDF summary.
105
+ canhoto export pdf 2026-06
106
+ ```
107
+
108
+ `patches.json` is a list of changes, one per transaction id from `review`:
109
+
110
+ ```json
111
+ [
112
+ { "id": "my_bank_card-2026-06-02-0001", "category": "Groceries",
113
+ "kind": "expense", "is_expense": true, "needs_review": false }
114
+ ]
115
+ ```
116
+
117
+ Canhoto marks every row you change this way as `manual`. No automatic rule
118
+ changes a manual row again.
119
+
120
+ The PDF goes to `~/.canhoto/exports/2026-06-summary.pdf` by default. It shows
121
+ totals by category and the top merchants in each category. It never contains a
122
+ transaction list or raw statement descriptions.
123
+
124
+ ### Teach Canhoto your rules
125
+
126
+ When a counterparty always means the same thing, store a rule. `categorize
127
+ rules` applies it to every future statement.
128
+
129
+ ```bash
130
+ canhoto rules add --pattern "PIX RECEBIDO ACME LTDA" --direction in \
131
+ --min 1200 --max 1300 --category Income --kind income \
132
+ --note "Monthly pay from my company"
133
+ canhoto rules list
134
+ canhoto rules remove --id 3
135
+ ```
136
+
137
+ | Option | Meaning |
138
+ |---|---|
139
+ | `--pattern` | Regular expression, case-insensitive, matched against the description. |
140
+ | `--direction` | `in` (money received), `out` (money sent), or `any`. |
141
+ | `--min`, `--max` | Amount range, inclusive, without the sign. Both are optional. |
142
+ | `--source-kind` | Match only `account` or `card` rows. |
143
+ | `--category` | The category to set. |
144
+ | `--kind` | `expense`, `income`, `transfer`, `internal_transfer`, `self_transfer`, or `card_payment`. Transfers do not count as income or spending. |
145
+ | `--review` | Set the category, but keep the row in the review queue. Use this when an amount range cannot separate two cases. |
146
+ | `--note` | Why the rule exists. Agents read it during review. Do not put secrets in it. |
147
+ | `--priority` | Lower numbers run first. The first matching rule wins. |
148
+
149
+ Your rules run before the built-in rules. They never change manual rows. When
150
+ you upgrade to the version with rules, Canhoto marks your existing reviewed
151
+ rows as manual.
152
+
153
+ To remember one merchant without a full rule, use merchant memory:
154
+
155
+ ```bash
156
+ canhoto categorize merchant --key CURSOR --category Subscriptions
157
+ ```
158
+
159
+ ### PDF profiles
160
+
161
+ Choose a built-in style:
162
+
163
+ ```bash
164
+ canhoto export pdf 2026-06 --profile canhoto
165
+ canhoto export pdf 2026-06 --profile modern --output ~/Documents/2026-06.pdf
166
+ canhoto export pdf 2026-06 --profile minimal
167
+ ```
168
+
169
+ - `canhoto`: receipt-style report with a category chart.
170
+ - `modern`: clean report with metric cards and a category chart.
171
+ - `minimal`: text-only report without a chart.
172
+
173
+ ## MCP
174
+
175
+ The CLI and the MCP server use the same service layer. For agent-assisted use,
176
+ start the MCP server. The agent follows this flow:
177
+
178
+ `statement_preview` -> `parser_*` -> `ingest` -> `rule_list` -> `run_rules` ->
179
+ `review_batch` -> `set_categories` -> `month_breakdown` -> `export_pdf`
180
+
181
+ When you explain a recurring counterparty to the agent, it can store it with
182
+ `rule_add`. The MCP server exposes only domain tools. It does not give SQL
183
+ access or full ledger dumps.
184
+
185
+ To let an agent write parsers, add this to `~/.canhoto/config.json`:
186
+
187
+ ```json
188
+ { "agent_view": { "allow_parser_writes": true } }
189
+ ```
190
+
191
+ Example MCP host configuration:
192
+
193
+ ```yaml
194
+ mcp_servers:
195
+ canhoto:
196
+ command: canhoto-mcp
197
+ ```
198
+
199
+ ## CLI commands
200
+
201
+ ```text
202
+ canhoto init | doctor
203
+ canhoto parsers scaffold|test|enable|list
204
+ canhoto ingest <files...> [--pdf-password PASSWORD]
205
+ canhoto categorize rules --month YYYY-MM
206
+ canhoto categorize apply --file patches.json
207
+ canhoto categorize merchant --key KEY --category CAT
208
+ canhoto rules add|list|remove
209
+ canhoto review --month YYYY-MM [--cursor ID] [--limit N]
210
+ canhoto breakdown --month YYYY-MM
211
+ canhoto export pdf YYYY-MM [--profile canhoto|modern|minimal] [--output PATH]
212
+ ```
213
+
214
+ ## Develop
215
+
216
+ ```bash
217
+ uv sync --extra dev
218
+ uv run pytest -q
219
+ uv run ruff check src/canhoto tests
220
+ uv run mypy -p canhoto
221
+ ```
222
+
223
+ Or use the [`justfile`](justfile) with [Just](https://just.systems/).
224
+
225
+ ### Release
226
+
227
+ Pushing a version tag publishes to PyPI through
228
+ [`.github/workflows/release.yml`](.github/workflows/release.yml). The workflow
229
+ tests, builds, smoke-tests the wheel and the source distribution, and publishes
230
+ with PyPI Trusted Publishing. The tag must match the version in
231
+ `pyproject.toml`.
232
+
233
+ ```bash
234
+ uv version --bump patch # or minor / major
235
+ git commit -am "chore: release v$(uv version --short)"
236
+ git tag -a "v$(uv version --short)" -m "v$(uv version --short)"
237
+ git push origin main --tags
238
+ ```
239
+
240
+ ## Privacy
241
+
242
+ Your statements and database stay in the data directory. Do not commit
243
+ statements, tokens, database files, or `~/.canhoto`. Back up `canhoto.db` before
244
+ you upgrade: it holds your ledger and your rules.