gh-tracker — a Bespoke app
Open PRs and issues across up to 7 GitHub repos you care about, on your own
Bespoke instance. Read-only: it shows
you what's open and links out to GitHub.
This is an unofficial, third-party app. Nobody vetted it but its author.
Read What it does with your stuff before you
install it, the same as you would for any code you run as yourself.
Install
From your instance directory, with Bespoke v0.6.0 or newer:
go tool bespoke add gh-tracker
That pins this module, writes apps/gh-tracker/app.toml with a free port, and
recompiles your instance stylesheet. Pin a version you have actually read with
bespoke add gh-tracker@v0.1.0.
By hand (or on Bespoke v0.5.x)
Write apps/gh-tracker/app.toml (see app.toml.example),
picking a port that is free in your instance:
name = "GH Tracker"
slug = "gh-tracker"
port = 4107
icon = "git-pull-request"
description = "Open PRs and issues across your GitHub projects"
package = "github.com/bketelsen/bespoke-app-gh-tracker"
Then:
go tool bespoke ui # rescans templates, including this module's
just check
just dev # http://localhost:4107
Pin the module too, or go mod tidy will drop it — nothing in your instance
imports an app:
go get -tool github.com/bketelsen/bespoke-app-gh-tracker
The directory must be named gh-tracker either way: the slug names the
app's database, process, and subdomain, and this app's source picks it
(app-manifest spec).
Requires Bespoke v0.13.0 or newer — the app publishes platform events and
in-app notifications (new items, sustained fetch failures) and registers
automation-eligible chat tools, all of which need the v0.13.0 events plane.
Once it's running, add a GitHub token under /settings to raise the API rate
limit and to see private repos.
What it does with your stuff
- Stores in its own SQLite database (
data/gh-tracker.db): the repos you
track, cached open PR/issue titles, and the GitHub token you paste into
/settings — stored in plain text, like every other Bespoke app secret.
- Talks to
api.github.com only, at most one /search/issues call per
stale repo, refreshing on page load and every 5 minutes in the background.
- Never creates, comments on, or closes anything on GitHub, and never
sends your data anywhere else.
Like every Bespoke app it runs as your user, so nothing above is enforced by
the platform — it's a description you can check against the source
(ADR-0031).
Spec
The behavior this app is built to, unchanged from its original private build
(approved 2026-08-02).
Records
projects — id, login, owner_repo (text, e.g. "octocat/hello-world"),
position (int, display order), last_fetched_at (datetime, nullable),
cached_items (JSON blob: list of {type: pr|issue, title, url, number, updated_at}), created_at, consecutive_failures (int, resets on a
successful fetch), last_error (text, nullable), first_failed_at
(datetime, nullable — start of the current outage). Max 7 rows per login
(enforced server-side).
settings — login (primary key), github_token (text, nullable).
Views
GET / — list of project groups (repo name as header, linking to the
GitHub repo), each showing its combined open PR/issue list: a badge
(PR/Issue icon), item title linking out to GitHub, "updated X ago" per
item. A group whose cache is older than 15 minutes is refreshed from the
GitHub API before rendering. Empty state points at /settings when no
projects are configured.
GET /settings — manage the list of owner/repo entries (add, remove;
max 7) and the GitHub token field (used for API auth — raises the
unauthenticated rate limit and is required for private repos).
POST /settings/projects add, POST /settings/projects/{id}/delete
remove, POST /settings/token save the token.
Rules
- Fetching uses the GitHub REST API (
/search/issues with a
repo:owner/name is:open query, PRs and issues distinguished by the
pull_request field on the result) — one call per stale project, so a
page load with several stale repos does one call each, not one huge
query.
- A failed refresh (bad repo name, rate limit, network error) keeps
serving the last good cache with an inline error note on that group,
never blanks the list.
last_fetched_at and per-item updated_at are stored UTC and rendered
as local relative times ("updated 12 min ago" / "synced 3 min ago").
- Dashboard card: total open PR/issue count across all configured
projects (from cache, no live fetch), tap to open
/.
- Global search (
/_search): tracked repo names and cached item titles,
cache-only.
- Live region (ADR-0022): patched on project/token changes and on
background refresh.
- Events (ADR-0035): each refresh diffs the fresh snapshot against the
previous cache and publishes
gh_tracker.item_opened (with an in-app
notification, capped at 5 per repo per refresh — beyond that a single
"N new items" summary notification), plus silent
gh_tracker.item_updated / gh_tracker.item_closed events. The first
fetch of a repo publishes nothing (backfill). Three consecutive fetch
failures for a repo publish one gh_tracker.fetch_failed notification
pointing at /settings. Event data never includes the GitHub token.
- Chat tools: two read-only tools,
list_tracked_repos (repos with last
fetch time and failure state) and list_open_items (cached open
PRs/issues, optional repo filter). Both are cache-only and eligible for
read-only automations; there are no write tools.
Non-goals
- No creating/commenting/closing PRs or issues.
- No support for more than 7 projects.
- No web push — notifications for new items and fetch failures are in-app
only, through the platform notification inbox.
- No chat tools that write (no add/remove repo, no token access, no forced
refresh).
Later
- Filtering by author or label.
Developing
views/*_templ.go is committed on purpose — instances build this module
straight out of the read-only Go module cache and never run templ generate
over it. After editing a .templ, regenerate and commit the output:
go tool templ generate
just check
License
MIT — see LICENSE.