One idempotent pipeline, from GitHub to a live board.
No webhooks to babysit, no warehouse to feed. Limn pulls your PR activity on a schedule, stores it leanly, and serves a deliberately thin read layer to the timeline. Here’s the whole machine.
- 01gh CLI / OAuthtoken source
- 02GitHub APIGraphQL + REST
- 03Syncevery 5 min · idempotent
- 04SQLite | Postgreslocal | cloud
- 05Fastify APIlean read layer
- 06React SPAvis-timeline
How the board stays current.
The sync is the load-bearing part — and it’s built to be boring: safe to re-run, cheap to keep current, and quick to fill a fresh repo.
Triggers
A node-cron job runs every five minutes, plus on repo-add and on a manual deep sync. In the cloud, the periodic pass follows the user: a tenant with no open tab for 15 minutes stops being re-synced, so idle accounts don’t burn API quota. Local is always on.
Two-phase backfill
A never-synced repo fills in seconds: a fast ~14-day foreground pass paints the recent board, then the deep backfill walks back to 90 days in the background, continuing the same cursor so no page is fetched twice.
Incremental + overlap
After that, each run re-walks from the last sync minus a 20-minute overlap. GitHub doesn’t bump a PR’s updatedAt for everything that matters — a CI run finishing, a thread resolving — so the overlap deliberately re-checks the trailing window and reconciles.
One fat query
Each repo is one paginated GraphQL query — 25 PRs a page, newest-first — walked until a PR predates the window. Per-commit changed-file paths come from REST and are cached permanently, since a commit SHA is immutable.
Idempotent by structure
Every entity upserts on its GitHub node id; timeline events upsert on a deterministic dedupe key. Re-running — after a crash, a cancel, or an overlapping window — is always safe, because conflict targets reconcile instead of duplicating.
Lean storage
By default Limn skips the bulky, regenerable text — PR descriptions, diff hunks, commit messages — keeping the DB small. It’s hydrated on demand when you open a PR and cached in your browser’s IndexedDB, so an unchanged PR never re-downloads. Flip one flag to store everything for fully-offline detail.
Local-first. Cloud when you need it.
A single environment variable selects the whole stack. The query layer is written once against a portable async surface, so the same code drives SQLite on your laptop and Postgres on a server.
Local
default- Runs entirely on your machine — SQLite, no hosted backend.
- Authenticates with your logged-in gh CLI; stores no credentials.
- One synthesized account; opens straight to the Activity console.
- Runs the Pro intelligence layer, including agentic review & fix (opt-in).
- npx pierre-review and you’re in.
Cloud
multi-tenant- A public landing, GitHub-App OAuth, per-user accounts.
- Postgres, with every entity owned and isolated by account.
- Encrypted per-user tokens; sessions behind a sealed cookie.
- Self-hostable on Railway from the same image.
- Sync follows active users to keep quota lean.
- Per-account isolation is load-bearing: every list query filters by account, every id-addressed read scopes ownership, and a cross-account IDOR check (verify:isolation) guards the query layer.
- Cloud OAuth tokens are sealed with AES-256-GCM and decrypted per request — never held in a module-level cache.
- Local mode stores no credentials at all: it borrows your authenticated gh CLI and talks to a SQLite file on your disk.
What happens when you run the command.
Requires Node ≥ 20 and gh auth login. Installed globally? The short pierre command does the same.
What’s next.
Limn is useful today, and deliberately scoped. Here’s where it’s heading — listed because it’s planned, not because it’s done.
Metered advanced AI
Pay-as-you-go for Claude Review, AI Analysis and AI Fix at API list price — no key of your own required, usage tracked in the same in-app credits.
BYO AI endpoints
OpenAI-compatible endpoints — Bedrock, self-hosted, open models — so you choose the model and where your code goes, for cost and privacy control.
Deeper Jira/Linear integration
Ticket links on PRs ship today. Next: pulling ticket status and titles into the board, so a stalled PR is visibly a stalled ticket.
Email digests
The same sprint report and repo digests that reach Slack today, delivered to an inbox — for the teams whose “one place” isn’t Slack.
Hosted cloud Pro rollout
Pro runs in the local and self-hosted deployment today; the hosted cloud tier is rolling out, same features, zero setup.
A phone-friendly build
Limn is a dense, desktop-first tool today. A responsive build for skimming the feed and triaging “My Turn” from your phone is planned.
Want something Limn doesn’t do yet? Open an issue.