5 Commits
Author SHA1 Message Date
EislM0203andClaude Sonnet 4.6 082b6081b7 feat: fin CLI + agent skill + opt-in API bearer auth
Adds `fin` — a typer/httpx CLI for the deployed API — so Claude Code and
other agents can consume fin over HTTP without an MCP server.

Backend:
- api/app/auth.py: require_api_token dependency (no-op unless FIN_API_TOKEN
  env is set; RFC 7235 case-insensitive Bearer, constant-time compare). All
  nine routers wired; /api/health exempt for k8s probes.
- GET /api/openapi.json added as an explicit token-gated route (FastAPI's
  built-in openapi_url bypasses dependency injection).
- GET /api/transactions gains an optional `limit` query param (ge=1).

CLI (cli/):
- fin_cli/client.py: FinClient — sync httpx wrapper, zero typer/rich imports
  (MCP-ready core for a future MCP server).
- Subcommands: networth, accounts, categories, tx, transfer, settle,
  split-expense, splitwise-paid, reconcile, holdings, stock, rsu,
  spending, prices. Every command supports --json for agent use.
- fin tx add refuses transfer/settlement types to prevent single-leg writes.
- Config via FIN_API_URL (required) and FIN_API_TOKEN (optional).

Agent skill: .claude/skills/fin/SKILL.md — command map, jq patterns, and
domain invariants (two-leg atomicity, net-share splitwise math, derived
holdings, funded-buy, soft-deactivate, FIN_API_TOKEN web-UI lockout warning).

Tests: 25 e2e tests (FinClient → real routes → temp SQLite) covering auth
on/off, all key invariants (two-leg linkage, fee-in-balance, net-share math),
transfer whole-group delete, and linked-leg edit rejection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPBt3s7Jyr2nozQTrVVGA4
2026-07-03 19:22:15 +00:00
EislM0203andClaude Sonnet 4.6 e36d719cbe fix: stock trades and receivable/payable settles no longer inflate spending/income
Asset movements between own accounts (buy/sell cash leg, reconciling a
receivable or payable) were typed 'expense'/'income', so they showed up in
the spending and income metrics. They are now typed 'transfer'/'settlement',
which the spending queries already exclude.

Backend:
- funded-buy/sell bank leg now typed 'transfer' (linked to investment account)
  instead of 'expense' — buys no longer inflate spending
- FundedBuyRequest accepts type 'sell' in addition to 'buy'; the sell leg
  credits proceeds (shares × price − fee) to the bank account
- /reconcile legs retyped to 'settlement' — collecting a receivable no longer
  inflates income, paying off a payable no longer inflates spending
- RSU vest picks currency from PriceCache instead of hardcoding 'USD'
- create_account writes account + opening balance in one atomic commit
- create_split_expense links the expense row to the transfer legs so deleting
  any one of the three rows deletes the whole group
- delete_transaction follows linked_transaction_id in both directions to
  collect and delete the full linked group atomically
- splitwise-i-paid rejects partner_share >= total_amount (422)
- Migration h8c9d0e1f2a3 retypes historical rows the same way

Frontend:
- StockTradeFlow: sell now shows bank-account selector ('Credit proceeds…');
  selector shown for both buy and sell, hidden only for rsu_vest
- Portfolio Add Trade form: same buy/sell bank-account selector added,
  routes to createFundedBuy when an account is chosen
- Portfolio price-fetch handlers guard against stale ticker closures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 14:27:39 +00:00
EislM0203andClaude Fable 5 bc256f6c6e fix: bug hunt findings — currency derivation, local dates, atomic split expense, RSU vest unlink
Frontend:
- Derive transaction currency from the selected account instead of
  hardcoding EUR in SplitwiseIPaid/SplitwiseSettle/SplitwiseShePayed/IOwe
  flows; add currency-mismatch guards and dynamic labels (IAmOwed too)
- Replace toISOString().split('T')[0] with local-time todayLocalISO()
  helper in Dashboard, Portfolio, Spending and all QuickAdd flows —
  UTC dates booked trades/vests/settles to yesterday before ~1-2 AM
- SplitExpenseFlow now calls the new atomic /transactions/split-expense
  endpoint instead of two sequential requests that could partially fail
- Round partner_share to cents and reject 0% split client-side
- Portfolio: don't render accounts when holdings fetch failed
- client.ts: deleteAccount returns the Account, not void

Backend:
- New POST /api/transactions/split-expense writing expense + both
  transfer legs in one commit
- delete_transaction legacy fallback now matches date/type and closest
  id, so it can no longer delete a leg of a different transfer between
  the same two accounts
- delete_stock_transaction reverts any grant vested through the deleted
  transaction to pending, fixing the undeletable vested-grant deadlock
  (422 on grant delete, FK 500 on transaction delete)
- FundedBuyRequest.type constrained to Literal['buy'] (422 instead of 500)
- calculate_net_worth logs a warning before falling back to EUR/USD=1.0
- Scheduler engine applies PRAGMA foreign_keys=ON and uses proper URL
  parsing via db_config helpers
- fetch_all_prices counts only real updates; stale retry exception no
  longer logged after a clean no-data result

Infra:
- nginx: Cache-Control no-cache on index.html so deploys aren't served
  from stale browser caches
- api Dockerfile: COPY --chown=app:app

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019uL1qrmNyCP8Bmybns1qTQ
2026-07-01 21:06:19 +00:00
EislM0203andClaude Fable 5 3c2766e98f chore: migrate npm dependencies from Dynatrace mirror to public registry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019uL1qrmNyCP8Bmybns1qTQ
2026-07-01 21:06:19 +00:00
EislM0203 d6001dc90b fixes 2026-07-01 07:10:33 +02:00