finn-tracker 0.0.1__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 (30) hide show
  1. finn_tracker-0.0.1/LICENSE +21 -0
  2. finn_tracker-0.0.1/PKG-INFO +215 -0
  3. finn_tracker-0.0.1/README.md +185 -0
  4. finn_tracker-0.0.1/finn_tracker/__init__.py +0 -0
  5. finn_tracker-0.0.1/finn_tracker/__main__.py +175 -0
  6. finn_tracker-0.0.1/finn_tracker/app.py +835 -0
  7. finn_tracker-0.0.1/finn_tracker/dashboard/index.html +1989 -0
  8. finn_tracker-0.0.1/finn_tracker/ingest.py +64 -0
  9. finn_tracker-0.0.1/finn_tracker/mcp_server.py +210 -0
  10. finn_tracker-0.0.1/finn_tracker/models.py +276 -0
  11. finn_tracker-0.0.1/finn_tracker/parsers/__init__.py +0 -0
  12. finn_tracker-0.0.1/finn_tracker/parsers/csv_parser.py +325 -0
  13. finn_tracker-0.0.1/finn_tracker/parsers/pdf_parser.py +470 -0
  14. finn_tracker-0.0.1/finn_tracker/utils/__init__.py +0 -0
  15. finn_tracker-0.0.1/finn_tracker/utils/db.py +413 -0
  16. finn_tracker-0.0.1/finn_tracker.egg-info/PKG-INFO +215 -0
  17. finn_tracker-0.0.1/finn_tracker.egg-info/SOURCES.txt +28 -0
  18. finn_tracker-0.0.1/finn_tracker.egg-info/dependency_links.txt +1 -0
  19. finn_tracker-0.0.1/finn_tracker.egg-info/entry_points.txt +2 -0
  20. finn_tracker-0.0.1/finn_tracker.egg-info/requires.txt +10 -0
  21. finn_tracker-0.0.1/finn_tracker.egg-info/top_level.txt +2 -0
  22. finn_tracker-0.0.1/pyproject.toml +48 -0
  23. finn_tracker-0.0.1/sample_data/__init__.py +0 -0
  24. finn_tracker-0.0.1/sample_data/generators.py +423 -0
  25. finn_tracker-0.0.1/setup.cfg +4 -0
  26. finn_tracker-0.0.1/tests/test_app.py +1605 -0
  27. finn_tracker-0.0.1/tests/test_cli.py +209 -0
  28. finn_tracker-0.0.1/tests/test_db.py +568 -0
  29. finn_tracker-0.0.1/tests/test_ingest.py +118 -0
  30. finn_tracker-0.0.1/tests/test_pdf_parser.py +1587 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rachith P
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.
@@ -0,0 +1,215 @@
1
+ Metadata-Version: 2.4
2
+ Name: finn-tracker
3
+ Version: 0.0.1
4
+ Summary: Privacy-first local expense tracker — import bank CSVs/PDFs, auto-categorize, explore trends. No data ever leaves your machine.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/RachithP/finn-tracker
7
+ Project-URL: Issues, https://github.com/RachithP/finn-tracker/issues
8
+ Keywords: finance,expense,privacy,local,csv,pdf
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: End Users/Desktop
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Office/Business :: Financial
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: flask<4,>=3.0
21
+ Requires-Dist: pdfplumber<1,>=0.11
22
+ Requires-Dist: pandas>=1.5
23
+ Requires-Dist: pypdf>=3.0
24
+ Requires-Dist: reportlab>=4.0
25
+ Requires-Dist: requests>=2.28
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0; extra == "dev"
28
+ Requires-Dist: pytest-cov; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # finn-tracker
32
+
33
+ [![CI](https://github.com/RachithP/finn-tracker/actions/workflows/ci.yml/badge.svg)](https://github.com/RachithP/finn-tracker/actions/workflows/ci.yml)
34
+ [![coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/RachithP/9088e1501976e0528e0da2fa5d38a465/raw/badge.json)](https://github.com/RachithP/finn-tracker/actions/workflows/ci.yml)
35
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)](https://python.org)
36
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
37
+
38
+ A fully local expense tracking and visualization tool. Import bank CSVs and PDF statements, auto-categorize transactions, and explore spending trends through an interactive dashboard. **No data ever leaves your machine.**
39
+
40
+ ---
41
+
42
+ ## Features
43
+
44
+ - **Auto-import** — drop CSVs/PDFs into `~/Documents/finn-tracker/expense/` (or `income/`) and they load automatically on every page refresh (set `EXPENSE_TRACKER_DATA=/your/path` to use a different directory)
45
+ - **Smart categorization** — 200+ static rules auto-categorize merchants across 15 categories (including Donations); manual overrides are persisted, learned as reusable rules, and applied on future transactions
46
+ - **Interactive dashboard** — summary cards, spending-by-category bar chart, account donut chart, spending trend timeline, and category drill-down
47
+ - **Period filtering** — 1M, 3M, 6M, YTD, This Month, Last Month, All, or a custom date range
48
+ - **Export** — CSV or PDF report with masked merchant names
49
+ - **AI chat assistant** — ask questions about your spending in plain English ("How much did I spend on food last month?"). Powered by a local LLM ([llama.cpp](https://github.com/ggerganov/llama.cpp)) — your data never leaves your machine
50
+ - **MCP server** — connect Claude Desktop, Cursor, Kiro, and other AI tools directly to your expense data via the [Model Context Protocol](https://modelcontextprotocol.io)
51
+ - **Privacy-first** — server binds to `127.0.0.1` only; all state stored in a local SQLite DB; sensitive strings masked before any API response
52
+
53
+ ---
54
+
55
+ ## Quick Start
56
+
57
+ ### Step 1 — Install Python (if you haven't already)
58
+
59
+ `finn-tracker` requires Python 3.9 or later. Check if you have it:
60
+
61
+ ```bash
62
+ python3 --version
63
+ ```
64
+
65
+ If you see `Python 3.9` or higher, skip to Step 2. Otherwise, install it:
66
+
67
+ - **macOS**: [Download from python.org](https://python.org/downloads) or run `brew install python`
68
+ - **Ubuntu/Debian**: `sudo apt install python3`
69
+
70
+ > **Note:** finn-tracker is developed and tested on macOS and Ubuntu. It may work on other platforms but is not officially supported on Windows.
71
+
72
+ ### Step 2 — Install finn-tracker
73
+
74
+ Open a terminal and run:
75
+
76
+ ```bash
77
+ pip install finn-tracker
78
+ ```
79
+
80
+ > **Tip:** If `pip` isn't found, try `pip3 install finn-tracker` or `python3 -m pip install finn-tracker`.
81
+
82
+ > **Optional — use a virtual environment:** If you want to keep `finn-tracker` isolated from other Python packages, create a virtual environment first:
83
+ > ```bash
84
+ > python3 -m venv ~/.venvs/finn-tracker
85
+ > source ~/.venvs/finn-tracker/bin/activate
86
+ > pip install finn-tracker
87
+ > ```
88
+ > You'll need to activate the environment (`source ~/.venvs/finn-tracker/bin/activate`) each time before running `finn-tracker`.
89
+
90
+ ### Step 3 — Launch
91
+
92
+ ```bash
93
+ finn-tracker
94
+ ```
95
+
96
+ Your browser opens automatically at `http://localhost:5050`.
97
+
98
+ ### Step 4 — Add your bank statements
99
+
100
+ Drop your bank CSV or PDF exports into:
101
+
102
+ ```
103
+ ~/Documents/finn-tracker/expense/ ← charges, debits
104
+ ~/Documents/finn-tracker/income/ ← salary, deposits
105
+ ```
106
+
107
+ Then refresh the page — your transactions appear automatically.
108
+
109
+ > **Not sure where to find those folders?**
110
+ > - **macOS**: Open Finder, press **⌘ Shift H** to go to your home folder, then open `Documents → finn-tracker`.
111
+ > - **Ubuntu**: Open your file manager and navigate to `~/Documents/finn-tracker/`.
112
+
113
+ ---
114
+
115
+ ### Try it first with sample data
116
+
117
+ Not ready to import real statements yet? Run this to load synthetic demo data:
118
+
119
+ ```bash
120
+ finn-tracker --demo
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Privacy Guarantee
126
+
127
+ No data leaves your machine. finn-tracker:
128
+
129
+ - Runs at `127.0.0.1:5050` — not accessible from the network by default
130
+ - Stores everything in SQLite on your disk (`~/Documents/finn-tracker/finn_tracker.db`)
131
+ - Never makes outbound network calls
132
+ - Deletes uploaded files immediately after parsing
133
+ - Masks card numbers, SSNs, and account numbers in all API responses
134
+
135
+ ---
136
+
137
+ ## AI Chat Assistant
138
+
139
+ finn-tracker includes a built-in chat assistant that answers questions about your spending in plain English:
140
+
141
+ > "How much did I spend on groceries last month?"
142
+ > "What's my biggest expense category this year?"
143
+ > "Show me my top 5 merchants"
144
+ > "Filter the dashboard to last month"
145
+ > "Which transactions are uncategorized?"
146
+
147
+ The assistant can answer spending questions and control the dashboard — filtering by period or category on your behalf. It runs entirely on your machine using [llama.cpp](https://github.com/ggerganov/llama.cpp). No data is sent to any external service.
148
+
149
+ **To enable it:**
150
+
151
+ 1. Install and start [llama-server](https://github.com/ggerganov/llama.cpp#quick-start) on port 8080 (the default)
152
+ 2. Launch `finn-tracker` — the chat button in the top-right corner will show **AI Ready**
153
+
154
+ To use a different port: `LLAMA_CPP_URL=http://localhost:8081 finn-tracker`
155
+
156
+ ---
157
+
158
+ ## MCP Server (Claude Desktop, Cursor, Kiro)
159
+
160
+ finn-tracker ships an [MCP server](https://modelcontextprotocol.io) that lets AI tools query your expense data directly — no browser required.
161
+
162
+ **To connect Claude Desktop:**
163
+
164
+ Add this to `~/Library/Application Support/Claude/claude_desktop_config.json`:
165
+
166
+ ```json
167
+ {
168
+ "mcpServers": {
169
+ "finn-tracker": {
170
+ "command": "/path/to/your/python",
171
+ "args": ["/path/to/finn-tracker/mcp_server.py"]
172
+ }
173
+ }
174
+ }
175
+ ```
176
+
177
+ Once connected, you can ask Claude things like "summarize my spending this month" or "what did I spend on dining last quarter" directly in Claude Desktop.
178
+
179
+ ---
180
+
181
+ ## Supported File Formats
182
+
183
+ | Format | Auto-detected banks |
184
+ |---|---|
185
+ | CSV | Chase Bank (checking), Chase Credit, Bank of America, Capital One, generic |
186
+ | PDF | Capital One, Chase, Bank of America (Visa Signature), and any table-based statement (pdfplumber) |
187
+
188
+ ---
189
+
190
+ ## How It Works
191
+
192
+ 1. Files in your expense/income folders are scanned on every page load; unchanged files are cached in memory and not re-parsed.
193
+ 2. Manually imported files are parsed once and persisted to SQLite.
194
+ 3. All transactions are deduplicated by `(date, merchant, amount, account)`.
195
+ 4. Category overrides and learned merchant rules survive server restarts via SQLite.
196
+
197
+ When you manually categorize a transaction, the app saves a normalized merchant pattern as a rule. Future transactions matching that pattern are auto-categorized.
198
+
199
+ Use **🗑 Clear Session** to undo a bad import without losing your history. Use **🗑 Clear All** to start completely fresh.
200
+
201
+ ---
202
+
203
+ ## Platform Support
204
+
205
+ finn-tracker is developed and tested on **macOS** and **Ubuntu**. CI runs on both platforms across Python 3.9, 3.11, and 3.12. Other Unix-like systems should work but are not officially tested. Windows is not supported.
206
+
207
+ ---
208
+
209
+ ## Contributing
210
+
211
+ Found a bug or want to add a bank parser? See [CONTRIBUTING.md](CONTRIBUTING.md) for how to get started.
212
+
213
+ For performance optimization guidance when scaling beyond 10K transactions, see [SCALING.md](SCALING.md).
214
+
215
+ [Open an issue on GitHub](https://github.com/RachithP/finn-tracker/issues) — include the output of `finn-tracker --version`.
@@ -0,0 +1,185 @@
1
+ # finn-tracker
2
+
3
+ [![CI](https://github.com/RachithP/finn-tracker/actions/workflows/ci.yml/badge.svg)](https://github.com/RachithP/finn-tracker/actions/workflows/ci.yml)
4
+ [![coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/RachithP/9088e1501976e0528e0da2fa5d38a465/raw/badge.json)](https://github.com/RachithP/finn-tracker/actions/workflows/ci.yml)
5
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)](https://python.org)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
7
+
8
+ A fully local expense tracking and visualization tool. Import bank CSVs and PDF statements, auto-categorize transactions, and explore spending trends through an interactive dashboard. **No data ever leaves your machine.**
9
+
10
+ ---
11
+
12
+ ## Features
13
+
14
+ - **Auto-import** — drop CSVs/PDFs into `~/Documents/finn-tracker/expense/` (or `income/`) and they load automatically on every page refresh (set `EXPENSE_TRACKER_DATA=/your/path` to use a different directory)
15
+ - **Smart categorization** — 200+ static rules auto-categorize merchants across 15 categories (including Donations); manual overrides are persisted, learned as reusable rules, and applied on future transactions
16
+ - **Interactive dashboard** — summary cards, spending-by-category bar chart, account donut chart, spending trend timeline, and category drill-down
17
+ - **Period filtering** — 1M, 3M, 6M, YTD, This Month, Last Month, All, or a custom date range
18
+ - **Export** — CSV or PDF report with masked merchant names
19
+ - **AI chat assistant** — ask questions about your spending in plain English ("How much did I spend on food last month?"). Powered by a local LLM ([llama.cpp](https://github.com/ggerganov/llama.cpp)) — your data never leaves your machine
20
+ - **MCP server** — connect Claude Desktop, Cursor, Kiro, and other AI tools directly to your expense data via the [Model Context Protocol](https://modelcontextprotocol.io)
21
+ - **Privacy-first** — server binds to `127.0.0.1` only; all state stored in a local SQLite DB; sensitive strings masked before any API response
22
+
23
+ ---
24
+
25
+ ## Quick Start
26
+
27
+ ### Step 1 — Install Python (if you haven't already)
28
+
29
+ `finn-tracker` requires Python 3.9 or later. Check if you have it:
30
+
31
+ ```bash
32
+ python3 --version
33
+ ```
34
+
35
+ If you see `Python 3.9` or higher, skip to Step 2. Otherwise, install it:
36
+
37
+ - **macOS**: [Download from python.org](https://python.org/downloads) or run `brew install python`
38
+ - **Ubuntu/Debian**: `sudo apt install python3`
39
+
40
+ > **Note:** finn-tracker is developed and tested on macOS and Ubuntu. It may work on other platforms but is not officially supported on Windows.
41
+
42
+ ### Step 2 — Install finn-tracker
43
+
44
+ Open a terminal and run:
45
+
46
+ ```bash
47
+ pip install finn-tracker
48
+ ```
49
+
50
+ > **Tip:** If `pip` isn't found, try `pip3 install finn-tracker` or `python3 -m pip install finn-tracker`.
51
+
52
+ > **Optional — use a virtual environment:** If you want to keep `finn-tracker` isolated from other Python packages, create a virtual environment first:
53
+ > ```bash
54
+ > python3 -m venv ~/.venvs/finn-tracker
55
+ > source ~/.venvs/finn-tracker/bin/activate
56
+ > pip install finn-tracker
57
+ > ```
58
+ > You'll need to activate the environment (`source ~/.venvs/finn-tracker/bin/activate`) each time before running `finn-tracker`.
59
+
60
+ ### Step 3 — Launch
61
+
62
+ ```bash
63
+ finn-tracker
64
+ ```
65
+
66
+ Your browser opens automatically at `http://localhost:5050`.
67
+
68
+ ### Step 4 — Add your bank statements
69
+
70
+ Drop your bank CSV or PDF exports into:
71
+
72
+ ```
73
+ ~/Documents/finn-tracker/expense/ ← charges, debits
74
+ ~/Documents/finn-tracker/income/ ← salary, deposits
75
+ ```
76
+
77
+ Then refresh the page — your transactions appear automatically.
78
+
79
+ > **Not sure where to find those folders?**
80
+ > - **macOS**: Open Finder, press **⌘ Shift H** to go to your home folder, then open `Documents → finn-tracker`.
81
+ > - **Ubuntu**: Open your file manager and navigate to `~/Documents/finn-tracker/`.
82
+
83
+ ---
84
+
85
+ ### Try it first with sample data
86
+
87
+ Not ready to import real statements yet? Run this to load synthetic demo data:
88
+
89
+ ```bash
90
+ finn-tracker --demo
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Privacy Guarantee
96
+
97
+ No data leaves your machine. finn-tracker:
98
+
99
+ - Runs at `127.0.0.1:5050` — not accessible from the network by default
100
+ - Stores everything in SQLite on your disk (`~/Documents/finn-tracker/finn_tracker.db`)
101
+ - Never makes outbound network calls
102
+ - Deletes uploaded files immediately after parsing
103
+ - Masks card numbers, SSNs, and account numbers in all API responses
104
+
105
+ ---
106
+
107
+ ## AI Chat Assistant
108
+
109
+ finn-tracker includes a built-in chat assistant that answers questions about your spending in plain English:
110
+
111
+ > "How much did I spend on groceries last month?"
112
+ > "What's my biggest expense category this year?"
113
+ > "Show me my top 5 merchants"
114
+ > "Filter the dashboard to last month"
115
+ > "Which transactions are uncategorized?"
116
+
117
+ The assistant can answer spending questions and control the dashboard — filtering by period or category on your behalf. It runs entirely on your machine using [llama.cpp](https://github.com/ggerganov/llama.cpp). No data is sent to any external service.
118
+
119
+ **To enable it:**
120
+
121
+ 1. Install and start [llama-server](https://github.com/ggerganov/llama.cpp#quick-start) on port 8080 (the default)
122
+ 2. Launch `finn-tracker` — the chat button in the top-right corner will show **AI Ready**
123
+
124
+ To use a different port: `LLAMA_CPP_URL=http://localhost:8081 finn-tracker`
125
+
126
+ ---
127
+
128
+ ## MCP Server (Claude Desktop, Cursor, Kiro)
129
+
130
+ finn-tracker ships an [MCP server](https://modelcontextprotocol.io) that lets AI tools query your expense data directly — no browser required.
131
+
132
+ **To connect Claude Desktop:**
133
+
134
+ Add this to `~/Library/Application Support/Claude/claude_desktop_config.json`:
135
+
136
+ ```json
137
+ {
138
+ "mcpServers": {
139
+ "finn-tracker": {
140
+ "command": "/path/to/your/python",
141
+ "args": ["/path/to/finn-tracker/mcp_server.py"]
142
+ }
143
+ }
144
+ }
145
+ ```
146
+
147
+ Once connected, you can ask Claude things like "summarize my spending this month" or "what did I spend on dining last quarter" directly in Claude Desktop.
148
+
149
+ ---
150
+
151
+ ## Supported File Formats
152
+
153
+ | Format | Auto-detected banks |
154
+ |---|---|
155
+ | CSV | Chase Bank (checking), Chase Credit, Bank of America, Capital One, generic |
156
+ | PDF | Capital One, Chase, Bank of America (Visa Signature), and any table-based statement (pdfplumber) |
157
+
158
+ ---
159
+
160
+ ## How It Works
161
+
162
+ 1. Files in your expense/income folders are scanned on every page load; unchanged files are cached in memory and not re-parsed.
163
+ 2. Manually imported files are parsed once and persisted to SQLite.
164
+ 3. All transactions are deduplicated by `(date, merchant, amount, account)`.
165
+ 4. Category overrides and learned merchant rules survive server restarts via SQLite.
166
+
167
+ When you manually categorize a transaction, the app saves a normalized merchant pattern as a rule. Future transactions matching that pattern are auto-categorized.
168
+
169
+ Use **🗑 Clear Session** to undo a bad import without losing your history. Use **🗑 Clear All** to start completely fresh.
170
+
171
+ ---
172
+
173
+ ## Platform Support
174
+
175
+ finn-tracker is developed and tested on **macOS** and **Ubuntu**. CI runs on both platforms across Python 3.9, 3.11, and 3.12. Other Unix-like systems should work but are not officially tested. Windows is not supported.
176
+
177
+ ---
178
+
179
+ ## Contributing
180
+
181
+ Found a bug or want to add a bank parser? See [CONTRIBUTING.md](CONTRIBUTING.md) for how to get started.
182
+
183
+ For performance optimization guidance when scaling beyond 10K transactions, see [SCALING.md](SCALING.md).
184
+
185
+ [Open an issue on GitHub](https://github.com/RachithP/finn-tracker/issues) — include the output of `finn-tracker --version`.
File without changes
@@ -0,0 +1,175 @@
1
+ """
2
+ finn-tracker CLI entry point.
3
+
4
+ Usage:
5
+ finn-tracker [--version] [--help] [--demo]
6
+
7
+ Starts the finn-tracker server at http://localhost:5050 and opens your browser.
8
+ Data directory: ~/Documents/finn-tracker/
9
+ """
10
+ import importlib.metadata
11
+ import logging
12
+ import os
13
+ import socket
14
+ import sys
15
+ import threading
16
+ import urllib.error
17
+ import urllib.request
18
+ import webbrowser
19
+ from pathlib import Path
20
+
21
+
22
+ # ── Testable helpers ──────────────────────────────────────────────────────────
23
+
24
+ def _check_python_version() -> None:
25
+ """Exit with a clear message if Python is too old."""
26
+ if sys.version_info < (3, 9):
27
+ print(
28
+ f"finn-tracker requires Python 3.9 or later.\n"
29
+ f"Your version: {sys.version.split()[0]}\n"
30
+ f"Download Python at https://python.org/downloads"
31
+ )
32
+ sys.exit(1)
33
+
34
+
35
+ def _resolve_data_dir() -> Path:
36
+ """Return the data directory, creating subdirs if needed.
37
+
38
+ Override with EXPENSE_TRACKER_DATA env var (useful for dev:
39
+ EXPENSE_TRACKER_DATA=./data finn-tracker).
40
+ """
41
+ raw = os.environ.get("EXPENSE_TRACKER_DATA")
42
+ data_dir = Path(raw).expanduser().resolve() if raw else Path.home() / "Documents" / "finn-tracker"
43
+ # Write the resolved absolute path back so app.py and db.py see the same value
44
+ # regardless of whether the original was relative (./data), uses ~, etc.
45
+ os.environ["EXPENSE_TRACKER_DATA"] = str(data_dir)
46
+ try:
47
+ (data_dir / "expense").mkdir(parents=True, exist_ok=True)
48
+ (data_dir / "income").mkdir(parents=True, exist_ok=True)
49
+ except PermissionError:
50
+ print(
51
+ f"finn-tracker could not create its data directory:\n"
52
+ f" {data_dir}\n"
53
+ f"Check that you have write permission, or set a different location:\n"
54
+ f" EXPENSE_TRACKER_DATA=/path/to/writable/dir finn-tracker"
55
+ )
56
+ sys.exit(1)
57
+ return data_dir
58
+
59
+
60
+ def _check_port(port: int) -> bool:
61
+ """Return True if the port is free."""
62
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
63
+ return s.connect_ex(("127.0.0.1", port)) != 0
64
+
65
+
66
+ # ── Main entry point ──────────────────────────────────────────────────────────
67
+
68
+ def main() -> None:
69
+ _check_python_version()
70
+
71
+ # ── Parse args ────────────────────────────────────────────────────────────
72
+ args = sys.argv[1:]
73
+ demo_mode = False
74
+ for arg in args:
75
+ if arg == "--version":
76
+ try:
77
+ version = importlib.metadata.version("finn-tracker")
78
+ except importlib.metadata.PackageNotFoundError:
79
+ version = "dev"
80
+ print(f"finn-tracker {version}")
81
+ return
82
+ elif arg == "--help":
83
+ print(
84
+ "Usage: finn-tracker [--version] [--help] [--demo]\n"
85
+ "Starts the finn-tracker server at http://localhost:5050 and opens your browser.\n"
86
+ "Data: ~/Documents/finn-tracker/\n\n"
87
+ " --version Print version and exit\n"
88
+ " --help Show this help and exit\n"
89
+ " --demo Load synthetic sample data to try the dashboard"
90
+ )
91
+ return
92
+ elif arg == "--demo":
93
+ demo_mode = True
94
+ else:
95
+ print(f"Unknown option: {arg}\nRun `finn-tracker --help` for usage.")
96
+ sys.exit(1)
97
+
98
+ # ── Resolve port ──────────────────────────────────────────────────────────
99
+ port = int(os.environ.get("EXPENSE_TRACKER_PORT", 5050))
100
+
101
+ # ── Resolve data directory (MUST happen before importing app) ─────────────
102
+ data_dir = _resolve_data_dir()
103
+ os.environ["EXPENSE_TRACKER_DATA"] = str(data_dir)
104
+
105
+ # ── Demo mode: seed sample data ───────────────────────────────────────────
106
+ if demo_mode:
107
+ try:
108
+ from sample_data.generators import write_demo_files # type: ignore
109
+ write_demo_files(str(data_dir / "expense"))
110
+ print(f"Sample data loaded into {data_dir}/expense/ and income/ — starting finn-tracker...")
111
+ except Exception as e:
112
+ print(f"Warning: could not load sample data ({e}). Starting anyway.")
113
+
114
+ # ── First-run guidance ────────────────────────────────────────────────────
115
+ expense_dir = data_dir / "expense"
116
+ has_files = any(
117
+ p.suffix.lower() in {".csv", ".pdf"}
118
+ for p in expense_dir.iterdir()
119
+ if expense_dir.exists()
120
+ )
121
+ if not has_files and not demo_mode:
122
+ print(
123
+ f"No transactions yet. To get started:\n"
124
+ f" 1. Export a CSV or PDF from your bank\n"
125
+ f" 2. Drop it into {data_dir}/expense/\n"
126
+ f" 3. Refresh the page in your browser\n"
127
+ f"\n Or try: finn-tracker --demo"
128
+ )
129
+
130
+ # ── Port check ────────────────────────────────────────────────────────────
131
+ if not _check_port(port):
132
+ print(
133
+ f"Port {port} is already in use.\n"
134
+ f"Is finn-tracker already running? Check http://localhost:{port}\n"
135
+ f"To use a different port: EXPENSE_TRACKER_PORT=5051 finn-tracker"
136
+ )
137
+ sys.exit(1)
138
+
139
+ # ── Suppress werkzeug banner ──────────────────────────────────────────────
140
+ logging.getLogger("werkzeug").setLevel(logging.ERROR)
141
+
142
+ # ── Import app (after env var is set so init_app uses correct path) ──────
143
+ import finn_tracker.app as flask_app
144
+ flask_app.init_app()
145
+
146
+ # ── Start browser daemon thread ───────────────────────────────────────────
147
+ url = f"http://localhost:{port}"
148
+ print(f"finn-tracker running at {url} (Ctrl+C to stop)")
149
+
150
+ def _open_browser() -> None:
151
+ base = f"http://127.0.0.1:{port}/"
152
+ for _ in range(50): # 50 × 100ms = 5s max
153
+ try:
154
+ urllib.request.urlopen(base, timeout=0.5) # noqa: S310
155
+ break
156
+ except urllib.error.URLError:
157
+ import time
158
+ time.sleep(0.1)
159
+ else:
160
+ print(f"Could not open browser automatically. Visit {url} manually.")
161
+ return
162
+ try:
163
+ webbrowser.open(url)
164
+ except Exception:
165
+ pass
166
+
167
+ t = threading.Thread(target=_open_browser, daemon=True)
168
+ t.start()
169
+
170
+ # ── Start Flask in main thread (handles Ctrl+C correctly) ─────────────────
171
+ flask_app.app.run(host="127.0.0.1", port=port, use_reloader=False)
172
+
173
+
174
+ if __name__ == "__main__":
175
+ main()