Fenrir

A Canadian PC-part price tracker with a split personality. JSON CLI for AI agents, Textual TUI for humans, the same Ontario price data underneath.

4ontario retailers
64tests
SQLiteappend-only price history
--jsonagent-friendly output
fenrir search "RTX 5070" --json --proxy    # AI agents
fenrir tui                                  # humans

Why this exists

I am building toward a homelab and I want my agents watching the Canadian market for me. US price trackers are useless here. So Fenrir searches Canada Computers, Memory Express, Newegg.ca, and Best Buy Canada, caches everything in SQLite with append-only price history, and serves it two ways: a --json flag agents can pipe through jq, and a TUI when I want to browse myself.

How a search flows

fenrir search "RTX 5070" │ ├─ cache hit? (15 min TTL) ── serve from sqlite, done ▼ retailer adapters ── one per store, same interface. │ add a store without touching ui or cache code ├─ canada computers ── 25+ stores ├─ memory express ─── needs a canadian ip: │ tailscale SOCKS5 through my linux box ├─ newegg.ca └─ best buy canada │ playwright stealth handles the js-rendered SPAs ▼ unified Product schema ── one pydantic model for all four ▼ confidence scoring ── token-based. an actual RTX 5070 │ scores 1.0. a "compatible power cable" scores 0.2. │ prebuilts and bundles get penalized ▼ sqlite cache + price history → cli / tui / json

The details that matter

confidence scoring
Search results on retailer sites are full of accessories pretending to be GPUs. The scorer filters them before you ever see them.
tailscale proxy
Memory Express blocks non-Canadian IPs. Requests route through my Linux box over Tailscale SOCKS5 for a residential IP.
price history
Append-only. Every search adds data points, so the longer it runs the more useful it gets.
64 tests
Unit, fixture-based parser tests, integration, and gated live tests against the real sites.

Open source on GitHub.