A local-first GitHub dashboard for project maintainers. Syncs PRs and issues from your repos into SQLite, serves a fast Svelte 5 frontend from a single binary, and keeps you out of GitHub's notification inbox.
Middleman runs entirely on your machine -- no hosted service, no telemetry, no account to create. One binary, one config file, and you're up.
A unified timeline of comments, reviews, and commits across all your repos. Switch between flat and threaded views. Threaded view groups events by PR/issue and collapses long commit runs for readability.
Filter by time range (24h / 7d / 30d / 90d), event type, repo, item type (PRs vs issues), or free-text search. Hide closed items and bot noise with a toggle.
Browse, search, and filter PRs across repos. Group by repo or show a flat list. From the detail view you can:
- Comment directly on a PR
- Approve a PR
- Merge with your choice of merge commit, squash, or rebase
- Mark draft PRs as ready for review
- Close and reopen PRs
- Star items for quick filtering
Review decisions, diff stats (additions/deletions), CI status, merge conflict indicators, and branch info are visible at a glance.
Inline diffs with a collapsible file tree sidebar. Files are grouped by directory and show status badges (modified, added, deleted, renamed) with per-file addition/deletion counts. Syntax highlighting via Shiki with light/dark theme support.
Filter the file tree by name, toggle whitespace visibility, and adjust tab width. Navigate between files with j/k. Each file section is independently collapsible.
Track PRs through New / Reviewing / Waiting / Awaiting Merge columns with drag-and-drop. Kanban state is local to middleman -- it doesn't touch your GitHub labels or projects.
Same filtering, search, and detail view as PRs. Post comments, close/reopen, and star issues without context-switching to GitHub.
Expandable check run section on each PR shows pass/fail/pending status with color-coded indicators and direct links to the failing run on GitHub.
- Runs immediately on startup, then on a configurable interval (default 5 minutes)
- Opening a PR or issue triggers an immediate sync for that item
- The active detail view polls every 60 seconds for new comments
- Progress is visible in the status bar; errors surface clearly
| Key | Action |
|---|---|
j / k |
Move through the list (or between files in diff view) |
1 / 2 |
Switch between list and kanban views |
Escape |
Close detail view / clear selection |
- Dark mode -- auto-detects system preference, with a manual toggle
- GitHub Enterprise -- set
platform_hostper repo to connect to GHE instances - Copy to clipboard -- one-click copy of PR/issue bodies and comments
- Settings UI -- add/remove repos and configure activity feed defaults from the browser
- Reverse proxy support -- deploy behind a proxy with the
base_pathconfig - Version info --
middleman versionprints the version, commit, and build date
git clone https://github.com/wesm/middleman.git
cd middleman
make buildSet your token and start middleman:
export MIDDLEMAN_GITHUB_TOKEN=ghp_your_token_here
./middlemanIf you use the GitHub CLI, middleman will use gh auth token automatically -- no env var needed.
On first run, middleman creates a default config at ~/.config/middleman/config.toml and serves the UI at http://localhost:8090. Add repositories from the Settings page, or edit the config file directly:
[[repos]]
owner = "your-org"
name = "your-repo"
[[repos]]
owner = "your-org"
name = "another-repo"make install # installs to ~/.local/binAll fields are optional. Repos can be added in the config file or through the Settings UI.
| Field | Default | Description |
|---|---|---|
sync_interval |
"5m" |
How often to pull from GitHub |
github_token_env |
"MIDDLEMAN_GITHUB_TOKEN" |
Env var holding your token |
host |
"127.0.0.1" |
Listen address |
port |
8090 |
Listen port |
base_path |
"/" |
URL prefix for reverse proxy deployments |
data_dir |
"~/.config/middleman" |
Directory for the SQLite database |
activity.view_mode |
"threaded" |
"flat" or "threaded" |
activity.time_range |
"7d" |
"24h", "7d", "30d", or "90d" |
activity.hide_closed |
false |
Hide closed/merged items in the feed |
activity.hide_bots |
false |
Hide bot activity |
Add platform_host and optionally token_env to repos hosted on a GHE instance:
[[repos]]
owner = "team"
name = "internal-app"
platform_host = "github.corp.example.com"
token_env = "GHE_TOKEN"Each distinct host can use a separate token env var. Repos without platform_host default to github.com.
Middleman can be embedded as a Go library inside another application. The host creates an Instance, which provides an http.Handler for the API and frontend:
inst, err := middleman.New(middleman.Options{
Token: os.Getenv("GITHUB_TOKEN"),
DBPath: "/path/to/middleman.db",
BasePath: "/middleman/",
SyncInterval: 5 * time.Minute,
Repos: []middleman.Repo{
{Owner: "org", Name: "repo"},
},
})
if err != nil {
log.Fatal(err)
}
defer inst.Close()
inst.StartSync(ctx)
mux.Handle("/middleman/", inst.Handler())The EmbedConfig option controls theming (light/dark mode, custom colors, fonts, radii) and UI defaults (hide sync controls, pin to a single repo, collapse sidebar). The EmbedHooks option provides lifecycle callbacks (OnMRSynced, OnSyncCompleted) so the host can react to sync events.
The frontend is also available as the @middleman/ui Svelte package, which exports individual views (PRListView, KanbanBoardView, ActivityFeedView), store factories, and context accessors. The @middleman/ui Provider component accepts an action registry for injecting custom buttons into PR and issue detail views.
Middleman is a single Go binary with the Svelte frontend embedded at build time. No external services -- just SQLite on disk.
middleman binary
|- Config loader (TOML)
|- Sync engine -> GitHub API (go-github)
|- SQLite database (WAL mode, pure Go driver)
+- HTTP server (Huma) -> REST API + embedded SPA
- No CGO required -- uses modernc.org/sqlite, a pure Go SQLite implementation
- Loopback only -- binds to 127.0.0.1 by default; this is a personal tool, not a shared service
- Graceful shutdown -- handles SIGINT/SIGTERM cleanly
Middleman uses SQLite with a schema defined in internal/db/schema.sql. The schema is applied via CREATE TABLE IF NOT EXISTS on startup.
There is no migration system yet. The database tracks its schema version in a middleman_schema_version table, and the binary embeds a matching SchemaVersion constant. On startup:
- Fresh database (version 0): schema is applied and the version is stamped.
- Matching version: proceeds normally.
- Stale database (version < binary): refuses to start. Delete the database file and let middleman recreate it.
- Newer database (version > binary): refuses to start. Upgrade middleman.
When real migrations are implemented, the stale case will run forward migrations instead of refusing. Until then, after a schema change, delete ~/.config/middleman/middleman.db and let middleman recreate it. Sync data will be repopulated from GitHub on the next run; local-only state (kanban columns, stars, and worktree links) is lost.
Run the Go backend and Vite dev server in parallel:
make air-install # one-time: install air for live reload
make dev # Go server on :8090 with live reload
make frontend-dev # Vite on :5173, proxies /api to GoOther targets:
make build # Debug build with embedded frontend
make build-release # Optimized, stripped release binary
make test # All Go tests
make test-short # Fast tests only
make lint # golangci-lint
make frontend-check # Svelte and TypeScript checks
make api-generate # Regenerate OpenAPI spec and clients
make clean # Remove build artifactsManaged with prek:
brew install prek
prek installMIT