A narrow terminal dashboard for GitHub PRs, GitHub stacks (gh-stack), reviews, and OMP AI usage.
Keep Dev-Dashboard open in a side split of tmux, Ghostty, WezTerm, or any terminal. It answers the
questions you would otherwise open five browser tabs for: which PR is red, which one is approved,
who still has to review, and what waits on you. The repository, package, and command are all
named devdash.
- Your PRs, grouped the way you work. Grouped by repository. Stacks made with gh-stack are drawn top to bottom, including merged layers. Plain branch chains (a PR whose base is another of your PRs) are drawn as stacks too.
- Everything that blocks a merge, in one row. CI (with the first failed check), draft, review decision, each reviewer's latest verdict, bot reviews, unresolved threads, conflicts, behind-base, and merge-queue position.
- Review requests. PRs that wait for you, newest first, with the author and age, and a marker when someone requests your review again.
- OMP AI usage (optional). Rate-limit bars and reset countdowns for providers with
usage reports in the selected OMP profile. OpenRouter spend and remaining
credits are read from its key
and credits endpoints.
Nous Portal credits are read from the Portal account API when omp has a
nous-portalornouscredential. Hidden whenompis not installed. Weekly and monthly bars show a pace icon (>>too fast,<<too slow,|on pace). - Hide what you do not want to see. Exclude single repositories or whole organizations.
- Your own sections. Add as many custom integrations as you like (weather, service health, a tutor, a build queue): any command whose output devdash shows in a slot you choose.
PR numbers are clickable in terminals that support hyperlinks.
- Install
- Usage
- Configuration
- Custom integrations
- Troubleshooting
- Related projects
- Contributing
- License
You need Python 3.12 or later and the GitHub CLI, signed in:
gh auth loginThen install devdash with uv or pipx:
uv tool install git+https://github.com/shayan-ys/devdash
# or
pipx install git+https://github.com/shayan-ys/devdashdevdash.py is also a self-contained script. It declares its dependencies inline
(PEP 723), so with uv installed you can copy one file:
curl -fsSLo ~/.local/bin/devdash https://raw.githubusercontent.com/shayan-ys/devdash/main/devdash.py
chmod +x ~/.local/bin/devdashTo update, run uv tool upgrade devdash (or pipx upgrade devdash), or download the file again.
devdash # live view; press r to refresh now, q to quit
devdash --once # print one frame and exit
devdash --exclude acme/monorepo # hide one repository
devdash --exclude acme # hide every repository of a user or organization
devdash --no-review --no-usage # show only your own PRs
devdash --no-integration weather # hide one custom integration (--no-integrations hides all)
devdash --profile personal --once # show usage from the personal omp profile
devdash --github alice # MY PRS / REVIEW REQUESTED for this gh login
devdash --help # all flags--once is also the behavior when standard input is not a terminal, so devdash | less -R works.
--profile NAME uses the same isolated auth, settings, and usage cache as omp --profile NAME.
Without the flag, devdash follows omp's default profile (or $OMP_PROFILE). Usage from one
profile is never reused as the last-good reading for another. Providers with an authenticated
account but no available usage data appear under no data. With --profile, each provider
heading includes that provider's account email in white, for example Codex (user@example.com).
--github USER is the GitHub username from gh auth status (not an email). It scopes MY PRS
and REVIEW REQUESTED to that login without running gh auth switch. It does not label usage.
OpenRouter shows per-key spending, not your overall account balance. An API key limit
appears as a spend bar; an unlimited key shows dollars spent. If its key is not available
through omp token openrouter, or OpenRouter rejects it, the provider stays hidden.
Nous Portal has no published read-only usage/balance API; view its usage at
Nous subscription management.
| Mark | Meaning |
|---|---|
✓ci ●ci2 ✗ci1 |
Latest run of each check passed, 2 still running, 1 failed (the first failed check is named at the end of the row) |
approved changes needs review |
The PR's review decision |
✓dana ✗lee ✎sam |
A reviewer's latest review: approved, requested changes, commented |
bot✎2 |
Reviews from 2 bots |
⚑3 |
3 unresolved review threads |
⚠conflict ↓behind |
Merge conflict, or the branch is behind its base |
⇢queued #2 |
Position in the merge queue |
Weekly and monthly bars have a pace icon beside the percentage: fast and on-pace icons on its
right, slow icons on its left. It compares the share of
the quota you used with the share of the window that has passed. For example, 34% used with
19 of 30 days gone (63%) is 29 points slow: <<.
| Icon | Points from pace | Meaning |
|---|---|---|
| |
less than 5 | On pace |
> >> >>> |
5, 15, 30 or more ahead | Too fast; >>> runs out well before the reset |
< << <<< |
5, 15, 30 or more behind | Too slow; you have quota to spare |
There is no icon on 5-hour windows, or in the first 10% (at least 12 hours) of a window,
where one busy hour looks like a runaway pace. Set usage.pace = false to hide the icons.
Every setting is optional. devdash reads a TOML file from the first of:
--config PATH$DEVDASH_CONFIG$XDG_CONFIG_HOME/devdash/config.toml, which is usually~/.config/devdash/config.toml
interval = 60 # seconds between refreshes
[keys]
refresh = "r" # "" disables a built-in; Ctrl-C always quits
quit = "q"
focus = "tab"
scroll_up = "up" # scroll when the dashboard is taller than the pane
scroll_down = "down"
[github]
exclude = ["acme/monorepo", "some-org"] # "owner/repo", or "owner" for all of its repositories
review_requested = true # show the REVIEW REQUESTED section
[usage]
enabled = "auto" # true, false, or "auto" (only when omp is installed)
refetch_after = 180 # force a fresh usage read after this many seconds; 0 = never
pace = true # pace icons (> fast, < slow, | on pace) on weekly and monthly bars
order = ["openai-codex", "anthropic"]
[usage.names]
anthropic = "Claude"
[usage.colors]
anthropic = "#d97757"Command-line flags override the file, and each --exclude adds to github.exclude. An unknown
key or a value of the wrong type stops devdash with an error, so a typo cannot fail silently.
config.example.toml lists every key with its default.
An integration is your own section. Declare one [[integrations]] table per section; there is
no limit on how many. devdash runs each one's command and shows what it prints to standard
output, under a heading:
[[integrations]]
name = "weather" # required; unique; used by --no-integration
title = "WEATHER" # heading; defaults to the name in capitals
command = ["curl", "-fsS", "https://wttr.in/Toronto?format=3"]
position = "top" # top, after-usage, after-my-prs, or bottom (the default)
interval = 900 # seconds between runs, up to 86400; 0 (the default) uses the global interval
timeout = 10 # seconds, 1 to 86400, before the command and its children are killed
max_rows = 20 # longer output is cut, ending in "… N more rows"
enabled = true # false keeps the table but hides the section
env = { WTTR_LANG = "en" } # extra environment variables for the commandwatch is an optional list of file or directory paths. Paths expand ~ and environment variables;
directories are watched recursively, and missing paths are watched for creation. Keep watch roots
small data directories: devdash takes a full stat snapshot on each polling pass. A watched
integration runs at startup and after watched files change, with a short debounce. With watch
set and no positive interval, it does not poll on the global interval; actions, r, and file
changes still run it. Set a positive interval to keep scheduled runs alongside watching. An
empty list disables watching.
watch = ["~/.local/share/prompt-tutor", "~/.config/prompt-tutor"]Integrations that share a position appear in the order they are listed in the file.
In watch mode, [keys] configures the built-in refresh, quit, focus, scroll_up, and
scroll_down keys. An empty string disables a built-in. Key names are case-sensitive: one
printable ASCII character other than space, or up, down, left, right, enter, or tab.
Arrow keys and Enter/Tab are decoded by devdash; Ctrl-C always quits.
An integration can declare keys = { j = "newer", k = "older" }, mapping keys to action names
that the integration itself understands. Bindings are case-sensitive; they cannot use a key
assigned to an enabled built-in. Remap that built-in in [keys] to resolve a conflict. Two
integrations can use the same binding; only the focused one receives it. Focus starts on the
first integration with bindings in screen order, and the configured focus key cycles through
those integrations. Each section lists its own keys on its last row: an integration's bindings in
config order under its output, and the refresh key under the last PR section. The bottom row lists
the keys for the whole dashboard: quit, the focus key when two or more integrations are
focusable, and the scroll keys when the dashboard is taller than the pane. The focused section's
heading starts with ▸ .
Watch mode draws on the terminal's full-screen view, which has no scrollback, so a dashboard taller
than the pane scrolls inside devdash: each scroll key press moves it one row, and a scrollbar in the
rightmost column shows which part is on screen. On the full-screen view, most
terminals turn mouse-wheel scrolls into arrow keys (devdash also turns on the alternate scroll mode,
DEC mode 1007, while it runs), so the wheel scrolls the dashboard with the default bindings.
--once prints every row.
Section headings show how long ago the last result was fetched and count down to the next fetch,
updating every second. Integrations use their own intervals; MY PRS, REVIEW REQUESTED, and USAGE
use the global interval. A watched integration without an interval shows watching instead of a
countdown. While a section is fetching, its heading says fetching…. --once shows no countdown
and does not watch files.
Global PR and usage refreshes run in the background, so input remains responsive. Pressing r
while one is running schedules one follow-up refresh; key presses after r in the same input batch
are still handled.
Each binding press runs the integration's command again with DEVDASH_ACTION set to its action.
Other runs have no DEVDASH_ACTION, even if it was inherited from the environment. Presses queue
in order (up to eight waiting actions); a run already in progress finishes first, and queued
actions run before the next interval refresh. The interval starts at the end of the last run.
refresh wakes all integrations, and any run satisfies that request.
In watch mode, an enabled integration that declares a keys table receives DEVDASH_STATE_FILE
pointing to an initially empty file in a private directory. The file is mode 0600; its directory
is mode 0700. devdash never reads the file: the integration owns its format and can use it to
preserve its view or selection between command runs. State lasts only for one devdash process.
--once and non-TTY runs set neither DEVDASH_ACTION nor DEVDASH_STATE_FILE.
What a command must do. Print the section body and exit with status 0. Any language works:
a shell script, a Python file, or an existing CLI with a one-shot mode. devdash sets COLUMNS
to the pane width and LINES to max_rows, so the command can fit its output to the room it has;
when the pane is resized, devdash runs every waiting integration again at once (one that is
already running finishes first). Colors (SGR) and hyperlinks (OSC 8) are kept. Cursor movement,
screen clears, and every other escape sequence are dropped, and any other control character
shows as �. A link whose target contains a control character loses its link. Rows longer
than the pane end in …. Trailing blank rows are removed. devdash keeps
the first 64 KiB of standard output: a command that prints more is stopped there, and that part
is shown. Of standard error it keeps only the last 4 KiB, so a noisy command cannot use up memory.
Failures. A non-zero exit, a timeout, or a command that cannot start shows ⚠ and the
reason under the heading: for a non-zero exit, the last line of its standard error, as plain
text. The last good output stays on screen below the error, and the heading tells you how old it is.
Safety. An integration runs with your user's rights. Only list commands you trust.
devdash runs the command list directly, without a shell; to use pipes or &&, write
["sh", "-c", "…"] or point at a script. The first element may start with ~. Each
integration runs on its own thread, with no standard input and in its own process group, so a
slow or hung command cannot freeze the dashboard or read your keystrokes. A timeout kills the
command together with every process it started that stayed in its process group; a program
that starts its own session (a daemon, for example) escapes this. Quitting devdash with the
configured quit key, Ctrl-C, or by closing the pane kills running commands the same way. Pressing
the configured refresh key runs every waiting integration again at once. With --once, devdash
runs all integrations in parallel, each within its own timeout, alongside the built-in GitHub
and usage reads.
prompt-tutor reviews the English of the prompts you
send to omp and Claude Code, and its Watcher shows the latest Review. Its one-shot mode, prompt-tutor --once,
prints one frame as wide as COLUMNS and as tall as its content, and exits, so it works as an integration as is;
devdash scrolls the dashboard when the frame is taller than the pane.
-
Install prompt-tutor and put its Watcher on your
PATHby following prompt-tutor's Set up steps. -
Check that the frame fits a narrow pane:
COLUMNS=50 prompt-tutor --once < /dev/null | cat
-
Add it to your devdash config (
~/.config/devdash/config.tomlby default), here at the bottom:[[integrations]] name = "prompt-tutor" title = "PROMPT TUTOR" command = ["prompt-tutor", "--once"] position = "bottom" max_rows = 100 watch = ["~/.local/share/prompt-tutor", "~/.config/prompt-tutor"] interval = 1 keys = { j = "newer", k = "older", s = "scope" }
If devdash shows
⚠ cannot run prompt-tutor, the Watcher link is not on thePATHthat devdash sees; use its full path, for examplecommand = ["~/.local/bin/prompt-tutor", "--once"]. -
Run
devdash. The section updates when prompt-tutor data changes, andinterval = 1reruns it every second so thereviewing… Nstimer of a pending Review keeps counting; pressj/kfor newer/older Prompts andsto change Scope. The integration keeps its view and selection in the state file. A long Review makes the dashboard taller than the pane; scroll it with the dashboard's scroll keys. Pressrto refresh the dashboard, and usedevdash --no-integration prompt-tutorto hide it.
gh is not signed in. Run gh auth login. With more than one account, pass --github USER
(the username from gh auth status) or set github.account in the config file.
A PR is missing. devdash shows the first 50 open PRs you wrote and the first 50 that request your review. Excluded repositories do not count toward the 50.
Stacks are shown as chain → main instead of stack #N. The PRs were not made with
gh-stack, or your GitHub host does not provide stack data (for example, GitHub Enterprise Server).
devdash then rebuilds stacks from branch chains.
The usage section shows last read … ago. Some providers rate-limit their usage endpoints,
and omp then drops the provider from its report. devdash keeps the last good read, marks it stale,
and saves it in $XDG_CACHE_HOME/devdash/. To make fewer requests, raise refetch_after or use
--cached.
OpenRouter is missing. omp usage does not report every configured model provider.
devdash queries OpenRouter's per-key usage and remaining-credits APIs with the key returned by
omp --profile NAME token openrouter; the key stays in memory and is never saved in
the dashboard cache. Check that the key belongs to the selected profile and remains valid.
Nous Portal is missing. Portal credits are not part of omp usage. devdash calls
https://portal.nousresearch.com/api/oauth/account with omp --profile NAME token nous-portal
(or nous). A browser or Hermes login is not visible to omp; store the Portal credential in
the selected omp profile first. Inference keys that cannot read the account API are omitted.
An integration shows ⚠ exit 127 or cannot run …. devdash could not find the command.
Use an absolute path, or check that the program is on the PATH that devdash sees.
An integration's layout is broken. The command probably draws for a full-screen terminal.
Run it as devdash does, for example COLUMNS=50 LINES=20 your-command < /dev/null | cat, and
check that it fits. A command that sizes itself from the terminal must fall back to COLUMNS
and LINES when its output is a pipe.
- gh-dash is a full-screen, interactive dashboard for PRs and issues. Use it to act on PRs. Use devdash to watch them from a narrow pane.
- gh-stack creates and manages the stacks that devdash draws.
Questions, bug reports, and pull requests are welcome. Please open an issue first for a large change. CONTRIBUTING.md explains the local setup, and everyone who takes part agrees to the Code of Conduct. Report security problems as described in SECURITY.md.
MIT © Shayan