# finance — financial tracking agent (PLANNED, not yet built)

## What this agent would do

Track Cameron's spending and net worth from his US Bank account (and others he adds later). Surface the data on `omidlab.net` as a private dashboard tab. Answer voice queries via [voice_interface](../voice_interface/) like "what did I spend on food this week?", "what's my checking balance?", "biggest expense this month?".

This is **planned, not started.** Spec lives here so we can build it once the VPS migration is done and we've decided the architecture.

## Why it's gated on the VPS migration

Putting bank credentials on the lab box is a hard NO:
1. Daily scrollback cron commits every agent's tmux pane to GitHub.
2. Multi-user box, root reads every file under `~/cameronsmith/`.
3. Cameron has already leaked PATs and sudo passwords through chat into scrollback during this session history — the leak vector is mechanical, not "be careful."

The VPS is dedicated, single-user, can have a stricter security posture (no chat-via-tmux for the finance agent, no scrollback commit of its pane, secrets in a dir Claude can't read by default).

## Architecture options (ranked, with security tradeoffs)

### Option A — Keyless display, push from Mac (STRONGLY RECOMMENDED)

```
Mac (only place keys live):
  launchd job, every 4h:
    1. Plaid SDK fetches transactions + balances → JSON
    2. POSTs JSON snapshot to omidhub via bearer token over HTTPS
  
omidhub (no credentials):
  serve.py /api/finance/ingest accepts the JSON, stores in /data/cameron/finance/snapshots/
  /finance dashboard route reads latest snapshot, renders charts
  voice_interface queries the same JSON for natural-language answers
```

**Trade-offs:**
- ✅ Plaid keys NEVER leave the Mac. ssh-out only.
- ✅ Cameron's bearer token to omidhub is per-device; if compromised, attacker can only push fake data (annoying, not catastrophic).
- ✅ The agent in the fleet (finance) never sees raw bank creds — it just reads JSON.
- ⚠️ Push interval is 4h — not "real-time" balance. Fine for personal finance.
- ⚠️ Mac needs to be powered on / launchd active. Hetzner pull as backup possible later.

### Option B — Read-only Plaid keys on omidhub, separate finance user

```
omidhub:
  /etc/finance/keys.env  (chmod 600, owned by finance user, root-readable)
  systemd timer runs as `finance` user, fetches Plaid → /data/cameron/finance/snapshots/
  cameronsmith user can READ the snapshots, can NOT read /etc/finance/
```

**Trade-offs:**
- ✅ Server-side fetching = no Mac dependency.
- ⚠️ Keys exist on the VPS. If VPS is compromised at the root level, keys leak.
- ⚠️ Sloppier: the finance Claude agent (running as cameronsmith) still can't see keys directly, but a misconfigured route handler in serve.py could expose them. Audit-prone.
- ⚠️ Doesn't compose well with the existing serve.py setup (which runs as cameronsmith).

### Option C — let me (Claude) handle Plaid keys directly

**NO.** Don't. Claude has access to write code that reads any path it can `cat`. Putting `PLAID_SECRET=...` in a chat message or a file path I'm told about means a future tool-call can print it to chat, which goes to GitHub via scrollback. This is the failure mode that bit the PAT and sudo passwords earlier in this session history. Even with the best operator discipline, mechanical leakage happens.

## What I'd build (Option A + voice integration)

1. **Mac side** (separate repo, e.g. `~/Projects/finance_fetcher/`)
   - Python script + Plaid SDK, runs hourly via launchd
   - Reads `PLAID_CLIENT_ID`, `PLAID_SECRET`, `PLAID_ACCESS_TOKEN_US_BANK` from `~/.config/finance_fetcher/.env` (chmod 600)
   - POSTs JSON to `https://omidlab.net/api/finance/ingest` with bearer auth
   - Bearer comes from same `.env` file

2. **omidhub serve.py side**
   - New `/api/finance/ingest` POST endpoint — bearer-auth, writes JSON to `/data/cameron/finance/snapshots/YYYYMMDD_HHMMSS.json`
   - New `/api/finance/summary` GET — internal-only, returns latest snapshot (used by dashboard + voice_interface)
   - New `/finance` dashboard page — auth-gated (same password as `/agents`), charts current balances + monthly spend by category

3. **voice_interface integration**
   - voice_interface reads `GET /api/finance/summary` when query matches finance intent
   - "spending on food this week" → filter snapshot by category=Food&Drink + last 7 days → "you spent $213 on food this week, mostly groceries"

4. **Cameron's setup steps (later, once VPS is up)**
   - Get Plaid sandbox account → run Plaid Link flow on Mac → get US Bank access token
   - Drop access token into `~/.config/finance_fetcher/.env` on Mac
   - Cron / launchd job
   - Done. Cameron's the only one who touches credentials.

## Cost considerations

- Plaid: free for sandbox, ~$0.30/account/month for Development tier (production-ready for personal use). 1 account = ~$4/yr.
- Hetzner VPS: $48/mo regardless.

## Open questions for Cameron (after migration)

- US Bank only, or want me to plan for Schwab / Coinbase / others too?
- Spending categories: Plaid's own taxonomy good enough, or custom rules?
- Net-worth tracking: just liquid (banking + investing) or also property/crypto/etc.?
- Privacy: should the `/finance` route be IP-allowlisted (only your Mac + iPhone Tailscale IPs) or password-gated like `/agents`?
- Voice queries: which categories of finance question would you actually ask? (helps prioritize the summary aggregations)

## When to actually build this

After the VPS migration is verified and stable for a few days. Doing it before means duplicating work (rebuild for the new host) AND putting bank creds anywhere near the leaky lab box, which is the whole reason to migrate.
