A cross-platform GitHub / GitLab PR monitor with a terminal UI and a desktop GUI, written in Rust.
┌──────────────────────────────────────────────────────────────────┐
│ config.toml · repos, tokens, poll interval, daemon port │
└──────────────────────┬───────────────────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ daemon │
│ ┌──────────┐ ┌──────────┐ │
│ │ poller │→ │ state │ │
│ │ (github/ │ │(in-mem + │ │
│ │ gitlab) │ │ SQLite) │ │
│ └──────────┘ └──────────┘ │
│ │
│ JSON / TCP 127.0.0.1:7878 │
└──────┬───────────────────────┘
│
┌───────┴────────┐
│ │
▼ ▼
crates/tui crates/tauri-app
Terminal UI Desktop GUI
(ratatui) (Tauri + React)
- Rust stable (
rustup update stable) - A GitHub Personal Access Token with the
reposcope
For the desktop app only:
- Node.js ≥ 18
cargo tauriCLI:cargo install tauri-cli
-
Copy and edit the config:
cp examples/config.example.toml config.toml $EDITOR config.toml -
Fill in your GitHub token and repos:
[[repos]] provider = "github" name = "myorg/myrepo" token = "ghp_..."
Alternatively, export the token and omit it from the config:
export GITHUB_TOKEN=ghp_...
# Build everything (daemon + TUI):
cargo build --workspace
# Build the desktop app (requires Node.js):
cargo tauri build -p tauri-appcargo run -p tuiThe TUI auto-starts the daemon if it isn't already running — no need to
launch two terminals. It locates the daemon binary as a sibling of the
devwatch-tui executable, or falls back to daemon on PATH.
Try it without a config using demo mode:
cargo run -p tui -- --demoDebug logging (written to a file so it doesn't corrupt the terminal):
DEVWATCH_TUI_LOG=/tmp/tui.log cargo run -p tui| Key | Action |
|---|---|
j / ↓ |
Select next row |
k / ↑ |
Select previous row |
Enter |
Open PR in browser |
s |
Cycle sort column |
S |
Toggle sort direction |
/ |
Filter by text |
Esc |
Clear filter / exit mode |
Tab / → |
Enter column-select mode |
o |
Enter column-reorder mode (H/L to move) |
c |
Open config editor |
q |
Quit |
cargo tauri dev -p tauri-appThis starts the Vite dev server for the React UI and the Tauri window.
Demo mode is available via the flask icon in the top-right of the nav bar — no daemon or config needed to explore the UI.
cargo run -p daemonWith verbose logging:
RUST_LOG=debug cargo run -p daemonThe daemon listens on 127.0.0.1:7878 (configurable via daemon_port).
Messages are newline-delimited JSON.
# Open a connection:
nc 127.0.0.1 7878
# Ping/pong:
{"Ping":null}
# Get current PR snapshot:
{"GetState":null}
# Subscribe — receive StateSnapshot, then live events:
{"Subscribe":null}Client → Daemon
| Message | Effect |
|---|---|
{"Ping":null} |
Responds with {"Pong":null} |
{"GetState":null} |
Responds with {"StateSnapshot": {...}} |
{"Subscribe":null} |
StateSnapshot, then streams {"Event": ...} |
Daemon → Client
| Message | Description |
|---|---|
{"Pong":null} |
Liveness response |
{"StateSnapshot":{"pull_requests":[...]}} |
Current PR list |
{"Event":{"NewPullRequest":{...}}} |
New open PR detected |
{"Event":{"PullRequestUpdated":{"old":{...},"new":{...}}}} |
PR changed |
{"Event":{"PullRequestClosed":{...}}} |
PR closed or merged |
{"Error":{"message":"..."}} |
Error message |
The daemon stores PR state in SQLite at:
- macOS / Linux:
~/.local/share/devwatch/state.db - Windows:
%LOCALAPPDATA%\devwatch\state.db
This prevents duplicate notifications across daemon restarts. The TUI and desktop app share the same database for persisted settings (column order, notification mode, close behaviour).
The TUI demo GIF is generated with VHS:
# Install VHS (macOS)
brew install vhs
# Re-record
vhs demo/tui.tapeThe tape script is at demo/tui.tape.
crates/
├── core/ # Shared types, VcsProvider trait, IPC messages
├── daemon/ # Background polling service
├── providers/
│ ├── github/ # octocrab-based GitHub provider
│ └── gitlab/ # GitLab provider (stub)
├── tui/ # ratatui terminal UI
└── tauri-app/ # Tauri + React desktop app
└── ui/ # Vite + React + shadcn/ui frontend
demo/
└── tui.tape # VHS script for TUI demo GIF
examples/
└── config.example.toml # Starter config
| Key | Default | Description |
|---|---|---|
daemon_port |
7878 |
TCP port for the IPC server |
poll_interval_secs |
60 |
How often to poll each repo |
repos[].provider |
— | "github" or "gitlab" |
repos[].name |
— | "owner/repo" |
repos[].token |
— | Per-repo PAT (falls back to GITHUB_TOKEN) |
Environment variables are also supported, prefixed with DEVWATCH__
(double-underscore separator), e.g. DEVWATCH__DAEMON_PORT=9000.