Features

The complete list of what the desktop app and the terminal CLI do, area by area, with the limits that apply.

Board and queues

  • Two queues, switched from the title bar, with 1 / 2, or with v: Involving me (My PRs when one repository is selected) and Review queue. A third, All open (3), lists every open PR in the selected repository (F-board-14); v skips it while All repositories is selected.
  • Involving me is the zero-config default: open PRs involving your GitHub login across all repositories. Your own PRs get actionable Notes, and other people's get status Notes ("alice's PR · approved"). It never widens to PRs that don't involve you. Across all repositories the most recently updated PRs come first, so the first page holds the ones with fresh activity rather than the newest opened.
  • My PRs in one repository lists the PRs you opened there.
  • Review queue splits PRs that request your review (Requested from you) from other people's PRs with no pending user or team reviewer request (Available to review), an optional pool rather than an assignment. Your own PRs never appear, and it goes by review requests, not issue assignees.
  • Sections come in one order, the same in every view, and empty ones are left out. By default:
    • My PRs: Approved, Needs action, Awaiting review, Drafts.
    • Involving me: Approved, Needs action, Available to review, Awaiting review, Drafts.
    • Review queue: Requested from you, Available to review, Reviewed, Drafts.
    • All open: Approved, Requested from you, Needs action, Available to review, Awaiting review, Drafts.

    A section has one name in every view. Where other people's PRs sit beside yours, one that nobody was asked to review and nobody has reviewed is under Available to review, as in the Review queue, rather than under Awaiting review, where it would wait on nobody.

    Snoozed PRs follow in their own group. Any section folds (F-board-15). Within Needs action, approved PRs come first. Hover a section's title for one sentence on what puts a PR there; the CLI's JSON carries the same sentence as each section's explanation. You can put the sections in your own order (F-settings-8).

  • GitHub's native stacks are grouped inside each section and ordered by layer from the base up.
    • Each row carries a layer marker (├─ 2/3, └─ 3/3).
    • The stack header reads "3 layers", or "2 of 3 layers shown" when part of the stack is elsewhere.

    Grouping uses GitHub's stack data, never labels or guessed branch relationships.

  • Columns fit the table in three width classes.
    • Below 1,120 px wide the Labels column is hidden; from 1,360 px it gets wider.
    • The width is the table's, so with Details beside it (F-board-16) the columns fit what is left.
    • With All repositories, Title and Note may narrow to 168 and 224 px to leave room for the Repo column, so the columns never run past the table's edge.
    • Note always gets at least as much room as Title.
    • A width you drag is kept for that queue and width class until you quit.
    • Columns can't be sorted or reordered.
  • Ways to open and select a PR:
    • ↑ / ↓ selects a row.
    • Enter, o, or a double-click opens the PR in the browser.
    • A single click on the blue PR number opens the PR, and on a linked-issue id opens the issue.
  • Right-click a row for Open on GitHub, Show details, copy actions (URL, number, owner/name#N reference, title, all details), Watch / Unwatch, Snooze…, and Cancel snooze. The binoculars icon in the PR cell toggles watch.
  • Details (Space or the Details button) shows the selected PR's loaded data:
    • labels, Note, author, CI, and size;
    • reviewers and reviews (your own review in the Review queue);
    • when the wait for a reviewer started;
    • stack layer and base branch;
    • whether it is changed, watched, or snoozed.

    It makes no extra request, and Esc closes it. It sits below the table or beside it (F-board-16).

  • Switching queues keeps each queue's rows, selection, and scroll position, and refreshes it in the background.
  • The header counts:
    • PRs this view has loaded.
    • How many of them need you, counted over the view on screen; snoozed PRs never count. It is the Dock badge's rule in every view: your own PRs under Needs action plus Requested from you. Other people's PRs under Needs action (Involving me, All open) and Available to review (optional: nobody asked you) never count.
    • How many PRs in this view you watch or snoozed. The explanation says how many of every PR you follow, in any view, each refresh checked (up to 50 at a time).

    Hover it for the explanation. While search or Changed filters rows, the toolbar shows "N of M loaded". In All open the header says how many of GitHub's total are loaded ("60 of 759 open"), and "40 match" while a label or author filter narrows the search.

  • Draft rows are dimmed. Statuses show as coloured dots with text, never emoji; the one exception is 🐛 on a bug label.
  • All open lists every open PR in one repository, whoever opened it.
    • It needs one repository: with All repositories selected its tab is disabled and says why.
    • Its sections are Involving me's, anyone's PRs in each (F-board-5), plus Requested from you, which holds the same PRs as the Review queue's section of that name, team requests included.
    • Your own PRs keep their My PRs Note, a PR asking for your review gets its Review queue Note, and anyone else's gets a status Note ("approved", "merge conflict · CI failing"). The Author column says whose it is, and copied text reads "by alice · approved".
    • The Note ends with the size band (F-note-7). The Review column is hidden below 1,120 px. From 1,360 px it widens to at most 320 px, taking room the Note doesn't need and never the Title's.
    • A label more than half of at least ten rows carry, such as cla-signed, is drawn after the row's other labels, so the chip that fits tells rows apart. +n still lists every hidden label as a filter.
    • It loads 60 PRs per page, and Load more fetches up to five pages, like the other views.
    • Other people's PRs stay quiet: changed markers and notifications cover only PRs you watch, your own, those asking for your review, and ones another view already follows, and the Dock badge never counts this view.
  • Click a section's header, or press c, to fold it. A folded section keeps its header and count and says what it holds in at most two facts, such as "1 failing CI · 1 merge conflict", "longest wait 4d", or "1 needs you".
    • c folds the selected PR's section. A folded header can be selected with ↑ / ↓, and c there unfolds it.
    • Folding never changes a count, a filter, or what notifies.
    • What is folded is remembered for each view (My PRs, Involving me, Review queue, All open) as [collapsed_sections] in config.toml. By default only Snoozed is folded, and it too is remembered.
  • Settings → Details position puts Details below the table or beside it on the right.
    • Automatic (the default) puts it on the right when the table keeps at least 1,120 px beside it, so from a 1,480 px window; narrower windows keep it below.
    • Bottom or Right always put it there.
    • Both list each fact as a label and a value (Author, CI, Unresolved comments, Requested reviewers, Reviews, Waiting, Size, Issue, Stack), under the PR's title and label chips. Beside the table it is 360 px wide and one column; below it keeps 210 px and puts who is involved and how the PR stands in two columns, scrolling inside.
    • Saved as details_position = "auto", "bottom" or "right" in config.toml.

Categorization and notes

  • Every PR is sorted into a category from its CI, reviews, review requests, unresolved comments, conflicts, and draft state: needs action, awaiting review, or draft for your PRs; requested, available, reviewed, or draft in the Review queue. The rules are pinned field for field against the original shell prototype by golden tests.
  • Every row has a plain-language Note that leads with the exception: a merge conflict, then a branch GitHub can't rebase in a repository that allows only rebase merges, then failing CI, then requested changes, then unresolved comments, then "no reviewers". Only a conflict, a branch that can't be rebased there, failing CI, or requested changes turns it red; routine steps stay muted. Hover the Note for the full text.
  • In the Review queue, a Note starts with "new commits since your review" when the PR has moved past the commit you reviewed.
  • When one of your non-draft PRs has no reviewer requested and no qualifying review, its Note suggests reviewers ("assign alice + bob"). The list comes from [repo_reviewers] for that repository, then for its owner, then from default_reviewers; an empty list suggests nobody. PR Marmot never assigns them.
  • Pickup age: the Note ends with how long the PR has waited for a reviewer (· 16h, · 3d). It turns amber at stale_after_days, which defaults to 3.
    • A PR requested from you counts from your latest request, or your team's.
    • Your own PR counts from its oldest pending request.
    • An available PR, or yours with nobody requested, counts from when it opened.
    • A wait never starts before the last "ready for review".
    • Drafts and reviewed PRs show no age; in the Review queue, only your own review counts.
    • The age reads each PR's latest 10 review-request and ready-for-review events; a request older than those counts from when the PR opened.
  • When only a team was asked and nobody has reviewed, the Note says "team requested, nobody responded".
  • In the Review queue, the Note ends with a size band. Small is at most 100 changed lines and at most 10 files, Large is more than 400 lines or more than 30 files, and Medium is everything between. Changed lines are additions plus deletions as GitHub counts them. A PR GitHub gave no counts for shows no band. The band is never turned into a time estimate.
  • Requested from you, Available to review, and Awaiting review list the longest wait first. In the Review queue, Smallest first sorts Requested from you and Available to review by size band, then changed lines, then wait, with unsized PRs last. It lasts until you quit.
  • The Review column shows each reviewer's standing review. A later comment doesn't cancel an approval or a change request, but a later change request or a dismissal does.
  • Reviews from the github-actions and chatgpt-codex-connector bots never count.
  • A merge conflict stays marked while GitHub briefly reports mergeability as unknown after a push to the base branch, so the marker doesn't flicker between refreshes.
  • Optional issue links: [issue_link] gives a regular expression matched in PR titles and a URL template with {id}. No tracker or prefix is built in. When links are set up, a leading [ID] is dropped from the title, as is a leading WIP.
  • Labels show as chips, up to 20 per PR, with a bug label always first. +n opens a menu of the labels that didn't fit.
  • An approved PR's Note says "mergeable" only when GitHub's merge state is clean. Otherwise it names the reason:
    • "waiting for CI" while checks run.
    • "checks not passing" when GitHub says a check hasn't passed.
    • "blocked by branch rules" when a rule such as a required review or check isn't met.
    • "branch out of date" when the branch must be updated first.
    • A plain "approved" while GitHub is still working out the merge state.

    When the repository allows rebase merges and GitHub can't rebase an approved PR's branch, the Note adds "can't rebase" and the PR stays in Approved. If rebase is the only merge method the repository allows, your PR whose branch can't be rebased is in Needs action with "can't rebase — rebase locally", and someone else's gets "can't rebase" in its Note. watch --until mergeable also waits for a clean merge state.

  • A cancelled check doesn't make CI fail. GitHub's combined check status reads failing when any check was cancelled, even one no rule requires, so PR Marmot counts the latest commit's checks by state: a failed, timed-out, action-required or start-up-failure check, a failing commit status, or a state PR Marmot doesn't recognise makes CI fail. When every other check passed or was skipped, CI shows as passing (running while a check still runs). This applies to the CI column, the Note, the section a PR lands in, notifications and watch --until.

Search and filters

  • Search the loaded PRs with Search, /, ⌘F / Ctrl+F, or Edit → Find.
    • Words match the PR number (#123 too), repository, title, author, labels, linked issue, and Note.
    • Every word must match, quotes keep a phrase whole, and matching ignores case.
    • A count shows how many loaded PRs match. Search never makes a request.
  • label:NAME, author:LOGIN, and repo:OWNER/NAME keep PRs whose label, author, or repository is exactly that, ignoring case. Quote a value with spaces (label:"help wanted"). A term becomes a chip after a space or Enter; the box holds up to 8 chips, and all of them must match. Several author: or repo: chips keep PRs matching any one of them, as GitHub's search does; words, labels, and is:stale must all match.
  • is:stale keeps PRs that have waited stale_after_days or longer for a reviewer. stale is the only is: value.
  • Clicking a label, author, or repository in the table or in Details adds it as a chip.
  • A chip's × removes it, Backspace in an empty box removes the last chip, the × at the right clears the search, and an empty search closes when you leave it.
  • The desktop search box and prmarmot-cli --filter share one grammar, pinned by a golden test, so a saved query means the same in both.
  • In All open, label: and author: chips are sent to GitHub's search, so they filter every open PR in the repository, not only the loaded ones.
    • A test pins that GitHub's reading and the local one keep the same PRs.
    • author:dependabot also finds the bot's PRs (author:app/dependabot).
    • A changed filter costs one request, sent after typing pauses; refresh timing is unchanged.
    • Words, is:stale, and anything past 8 labels or 8 authors check only the loaded PRs. While more are left to load, a line under the header names those terms and offers Load more.
  • Two buttons beside Changed and Snoozed narrow the board with one click. Each shows how many loaded PRs it would keep, and its tooltip says what it does.
    • Needs you keeps the PRs the header counts as needing you (F-board-12); its number is the header's.
    • Stale adds or removes the is:stale chip (F-search-3), as if typed.

Change tracking

  • A blue marker flags a PR that changed since you last looked. It survives restarts until you select the PR. Hovering it says what changed:
    • new commits, or draft and ready-for-review changes;
    • CI, merge conflicts, and the review decision;
    • reviews and review requests, and unresolved comments;
    • "Updated on GitHub (a comment, edit, or label)", or "changed, then changed back".

    PR Marmot remembers the last state of the 1,000 most recently seen PRs.

  • Changed filters the loaded rows to changed PRs and shows how many match the search. A selection restored at launch doesn't clear a marker. The count is every changed PR loaded, whatever the search, and so is Snoozed's: every button counts the loaded PRs.
  • Watch (w) follows a PR even outside the current view. Up to 50 watches are kept; a 51st drops the oldest, and the footer says which. Watched and snoozed PRs refresh in the same request as the board, at most 50 per refresh, taking turns when there are more. A watched PR already seen merged is no longer fetched.
  • Snooze (s) moves a PR to a collapsed Snoozed group, where it adds no alerts or counts until it wakes. Snoozed shows or collapses the group, which stays as you left it across launches.
    • One hour.
    • Until tomorrow, which is 24 hours from now.
    • Waiting for CI wakes when checks change to passing or failing.
    • Review again when changed wakes on a new commit, review, review request, review decision, unresolved-comment count, or draft change.
    • Waiting on the PR's author wakes when the author submits a newer review. It isn't offered on your own PRs, where it could never wake.

    A conditional snooze wakes only when PR Marmot sees the PR, and missing data never wakes one. Snoozing again replaces a snooze, and Cancel snooze ends it. Up to 200 snoozes are kept; a 201st drops the oldest.

  • Desktop notifications for these changes:
    • merge conflict, changes requested, review again after new commits;
    • CI passed (with "and is approved" when it is);
    • pull request changed;
    • a watched PR merged, closed, or became unavailable.

    By default only watched PRs notify. Notify for every PR entering Needs action adds your own PRs. Snoozed PRs, the row you have selected, and the first refresh after launch never notify. Clicking a notification selects the PR, or opens a watched PR that isn't loaded on GitHub. Notifications and their sound can be turned off, and they arrive only while the app runs. Clicking one for any PR the board isn't showing opens it on GitHub.

  • The Dock badge counts, across both views, your PRs that need action plus review requests. It can be turned off.
  • Watches, snoozes, and change history are stored apart from preferences under $XDG_STATE_HOME/prmarmot (or ~/.local/state/prmarmot), separately for each GitHub host and account. Writes are bounded, batched, and atomic. A corrupt file, or one written by a newer version, is kept and reported rather than overwritten.

Sharing

  • Copy a whole section from its header (hover and click Copy, or right-click). The copy respects the active search.
    • Copy list for Slack & docs pastes titled links into Slack, Teams, Google Docs, email, Notion, Linear, and Jira.
    • Copy Markdown for GitHub is for GitHub, Discord, and editors.
    • Copy as table is for docs and slides.
    • Copy URLs gives one link per line.
  • Y copies the selected PR's section as a list.
  • y copies the selected PR's URL, and the footer confirms it.
  • In Details you can select text and copy it with ⌘C / Ctrl+C. Its Copy menu copies the PR URL, number, owner/name#N reference, title, or all details.

Repositories and scope

  • All repositories is the default scope and always first in the repository picker.
  • Choosing one repository makes a fresh request scoped to it. It never filters the partial all-repositories page and passes that off as complete.
  • The picker is searchable and fills from your accessible repositories. It lists up to 1,000 repositories, read in 10 pages of 100, plus configured, pinned, and current ones. If discovery fails, those three still show, and Repos retries. Organization SSO and GitHub permissions decide what's visible.
  • Pin adds a repository shortcut to the toolbar, and Pinned removes it. Up to 12 pins are kept in order. Pins never fetch in the background.
  • A repository that doesn't exist, or that the account can't see, gets its own error instead of an empty list.
  • The starting scope comes from --repo owner/name or --all-repos, then PRMARMOT_REPO / PRMARMOT_SCOPE, then the config file, then the current checkout's GitHub remote.

Sign-in and hosts

  • PR Marmot uses your GitHub CLI (gh) login when there is one. In auto, the default, a token you chose comes first (PRMARMOT_TOKEN, then a pasted personal access token), then the gh login, then a device-flow sign-in, then the sign-in screen. The GitHub CLI is exempt from organizations' OAuth-app restrictions, and PR Marmot's own app isn't.
  • Sign in with GitHub uses GitHub's device flow with PR Marmot's own registration, so there is nothing to set up. You type an eight-character code at github.com/login/device. It asks for repo and read:org, and there is no client secret and no server of ours. The code screen says that an organization which restricts OAuth apps hides its repositories until an owner approves PR Marmot, and that GitHub offers the request when you authorize. While it waits, the code screen says so in still text, in one sentence core owns for both front ends: open the page, sign in, enter the code, and PR Marmot continues on its own. Nothing on the screen moves: the wait is on you, in a browser.
  • Use a token takes a personal access token. A classic one with repo and read:org reaches every organization you belong to and shows CI. A fine-grained one needs Pull requests: read and Metadata: read and covers one owner. The token is never displayed. The sign-in screen names the classic token first, says what it reaches — every organization, their private repositories, ones that restrict OAuth apps, and CI — and says what a fine-grained one cannot: an organization's private repositories, absent with no error.
  • A fine-grained token still loads the board. CI it may not read shows as "hidden", and one line under the header explains why. When a pasted fine-grained token carries the board and the GitHub CLI is signed in to the same host, a second line says the gh login would also reach organization private repositories.
  • GitHub Enterprise Server: set [auth] host, PRMARMOT_HOST, or GH_HOST. Sign in with a token, or with the device flow once [auth] client_id names that instance's own registration. A host typed in the sign-in screen's Enterprise host field is used right away and saved as [auth] host.
  • A stored token lives in the login keychain on macOS (service dev.prmarmot.auth) and in $XDG_STATE_HOME/prmarmot/auth.json with mode 0600 on Linux. [auth] store = "file" uses the file on macOS too.
  • Settings shows the host and the signed-in account. Disconnect removes a stored token from this machine; revoking the grant at GitHub is a separate step.
  • Settings and prmarmot-cli auth status name the sign-in in use, including PRMARMOT_TOKEN, and point out a stored token that goes unused while the GitHub CLI is signed in.
  • [auth] mode or PRMARMOT_AUTH picks auto, gh, device, or token. PRMARMOT_TOKEN supplies a token for one run and is never stored. It comes before a GitHub CLI login, so it suits CI.

Refresh, limits and rate budget

  • Refresh runs automatically every 5 minutes by default, and r refreshes now. The interval can't go below 30 seconds.
  • The header shows "synced Xm ago" and the live GraphQL rate-limit budget, updated once a minute. The budget turns the warning colour when less than a tenth of it is left, well before refreshes pause (F-refresh-5).
  • Each refresh is one GraphQL request.
    • My PRs and Involving me return up to 60 PRs and cost about 4 points.
    • The Review queue returns up to 60 requested and 60 candidate PRs and costs about 8 points.
    • Nothing is fetched separately for each PR.

    All open returns up to 60 PRs plus the ids of up to 100 that request your review, and costs about 4 points.

  • When GitHub says there is more, a partial results notice appears. Load more fetches the next page, up to five pages for each search: 300 authored results or 600 review candidates. A refresh returns to page one, and a failed page keeps what's already loaded. The new rows join their sections, and the footer says how many arrived ("60 more PRs loaded, sorted into their sections").
  • Refreshes pause while fewer than 50 points remain, leaving the rest of the hourly budget to your own tools, and the header says so. A rate-limit wait is always between 60 seconds and 15 minutes, never until a far-off reset. When GitHub refuses a request, the wait is the retry-after it asked for, or else until a spent budget resets, rather than always the one-minute floor.
  • Each PR's data is capped: 20 labels, 15 review requests, 60 reviews, 100 review threads (so the unresolved count stops at 100), and the latest commit's CI rollup. The reviews and threads read are the newest ones, and a PR with more than 100 threads shows its unresolved count as a minimum ("5+ unresolved comments").
  • If the first load fails, Retry tries again. A watched PR you can no longer see is reported as unavailable, and the refresh still succeeds.
  • An idle window repaints at most once a minute, with no animated spinner, so the GPU stays idle.
  • GitHub stops a request after about 10 seconds, and a page of 60 PRs spread over many repositories can take longer. When GitHub gives up, PR Marmot asks again at once for 30 PRs, and that view keeps loading 30 at a time, Load more included, until you quit or change the repository. prmarmot-cli watch does the same between polls. A smaller page shows like any other partial page, with the partial results notice and Load more (F-refresh-4); nothing says why it is smaller.

Settings, theme, shortcuts

  • Settings edits these and applies them without a restart:
    • reviewer suggestions, the refresh interval, and the theme;
    • notifications and their sound;
    • the Dock badge and automatic update checks;
    • the section order.
    • where Details sits (F-board-16).

    Advanced holds issue links, whether every PR entering Needs action notifies, and the config file's path. Save checks each field and puts any error beside it, and Cancel discards your edits. Reviewer names must be valid GitHub logins, and an issue URL must be http(s) and contain {id}. A field set by an environment variable is disabled and names that variable. Settings also shows the version.

  • Every setting lives in an optional TOML file at $XDG_CONFIG_HOME/prmarmot/config.toml or ~/.config/prmarmot/config.toml. The app writes changes back and keeps your comments. A file it can't parse is never overwritten, and the app starts with defaults. A banner says the file was ignored and why, until you dismiss it or a Settings save succeeds. The same banner lists, one line each, every single value that is ignored (a bad issue-link pattern, a [repo_reviewers] key that isn't owner or owner/name, stale_after_days = 0, an unknown [auth] mode or store, and a section_order entry that is not a section; a [collapsed_sections] view or section it doesn't know, and a details_position other than auto, bottom or right), from the file or the environment. It shows four lines at most; the last counts the rest and lists them in its tooltip.
  • Settings are applied in this order: command-line options, then environment variables (PRMARMOT_REPO, PRMARMOT_SCOPE, PRMARMOT_REFRESH_SECS, PRMARMOT_THEME, PRMARMOT_DEFAULT_REVIEWERS, PRMARMOT_ISSUE_PATTERN with PRMARMOT_ISSUE_URL_TEMPLATE), then the file.
  • The theme is system, light, or dark, and t cycles through them. System follows the OS appearance live.
  • The window opens at 1440 × 860 and can't be made smaller than 900 × 560. Across launches the app remembers the scope, the last repository, the theme, the starting view, the window size, and pins.
  • Keyboard shortcuts work from any dashboard control, but never while you type in search, the repository picker, or Settings. The footer opens the full list.
  • Quit from the app menu or with ⌘Q / Ctrl+Q, which works everywhere, even in text fields. Plain q quits when no text field or dialog is active.
  • Section order in Settings puts the sections in your own order: one list for every view, each section moved with its up and down buttons, and Reset to default. The list says which views show each section; each view shows the ones it has, in that order. Snoozed stays last and stacks stay inside their section, and nothing is hidden or counted differently. It is saved as section_order in config.toml, written with the JSON section keys (section_order = ["available", "await"]); sections left out follow in their default order. prmarmot-cli prints its views in the same order.

Updates and install

  • Prebuilt releases are signed with a Developer ID and notarized. The installer checks the published checksum, the signature, the notarization ticket, and Gatekeeper acceptance before installing.
  • Homebrew: brew install --cask oliver-kriska/tap/prmarmot. The cask also links prmarmot-cli and its shell completions.
  • Every install path uses one /Applications/prmarmot.app, and the installer removes an older ~/Applications copy.
  • install.sh has these options:
    • --repo sets the first repository;
    • --dir and --bin-dir choose where the app and the CLI go;
    • --from-source builds on your machine.

    It writes a config file only when it can resolve a repository you can reach, and never overwrites an existing one.

  • The GitHub CLI is optional: the cask doesn't depend on it, and without it the installer tells you the app will ask you to sign in.
  • On launch, and at most once a day after that, the app checks for the latest stable release and shows a banner when there is one.
    • For a direct install, the banner opens the release page.
    • For a Homebrew install, a helper process upgrades the cask after the app quits, reopens it, and reports any failure on the next launch.

    The check can be turned off. It asks through the GitHub CLI when that can answer, and otherwise asks GitHub directly without a token.

  • The first PR Marmot launch copies the former prboard app's config and state once and leaves the originals alone. If the copy fails, the app stops with an error instead of starting empty.
  • Linux and Intel Macs build from source, and install.sh --from-source puts the binaries in ~/.local/bin. There are no prebuilt Linux packages yet.
  • Nix: the repository has a flake. nix develop, or direnv, gives the build environment, and nix build or nix run builds the app from source. Only Apple silicon has been tried; Linux and Intel Macs are untested.

Terminal and agent CLI

  • prmarmot-cli mine and prmarmot-cli review print the app's My PRs and Review queue. They use the same query, categories, Notes, sections, stacks, config file, and sign-in. The CLI reads the app's watches and snoozes but never changes them, and never clears a changed marker. It no longer asks GitHub who you are on every run: it uses the login saved at sign-in, or on the gh path gh's own ten-minute cache, which is kept per token.
  • It is bundled inside the app and linked by every installer. It has no GPUI dependency, so it builds without Metal (cargo install --locked --git https://github.com/oliver-kriska/prmarmot prmarmot-cli), with Rust 1.85 or newer.
  • Scope and view options:
    • --repo owner/name or --all-repos choose the scope, and --authored keeps only PRs you opened.
    • --changed keeps changed PRs and says what changed, and --watched keeps watched PRs.
    • --snoozed expands the Snoozed group.
    • --pages N loads up to five pages.
  • --stale keeps PRs that have waited too long. --sort smallest or --sort wait orders the pickup sections, and Notes end with the wait and the size band.
  • --filter "<query>" runs the desktop search grammar over the loaded PRs.
  • Output formats: a width-aware table on a terminal, Markdown when piped, --format markdown, and --json. The table follows COLUMNS, and --no-color, NO_COLOR, or TERM=dumb turn colour off.
  • --json follows the versioned prmarmot-cli/board@1 contract.
    • Sections have stable keys.
    • Each PR carries its categorization facts, pickup age, size, and attention state.
    • Within @1, fields are only added. A field's values can grow: ci: "hidden" arrives in 0.9.1.
  • JSON Schemas (draft 2020-12) for the board and for watch events live in cli/schema/. The CLI's tests check real output against them.
  • prmarmot-cli watch [mine|review] streams changes, one line each: text on a terminal, NDJSON (prmarmot-cli/event@1) when piped.
    • It polls at refresh_secs, one request per poll for the view's first page. --interval overrides it but never goes below 30 seconds.
    • Events: ready, changed, added, removed, rate_limited, and error.
    • A removed PR costs one small extra request to learn whether it merged or closed, for up to 50 removals per poll; the rest are reported as unknown. That request asks for their state alone, about one point.
    • --events N exits after N events.
  • watch --pr owner/name#N, or the PR's URL, follows one pull request in any repository. The stream ends when it merges, closes, or becomes inaccessible.
  • --until turns a --pr follow into a wait whose exit code is the answer. Several conditions can be given, and a merge always counts as met. --timeout (90s, 30m, 1h30m) gives up with exit 6.
    • ci-pass, approved, mergeable, and merged.
    • Exit 0 when a condition is met, 5 when none can hold any more.
  • Exit codes:
    • 0 ok
    • 1 GitHub, network, or file error
    • 2 usage error
    • 3 not signed in
    • 4 rate limited
    • 5 --until unmet
    • 6 --timeout reached

    Errors go to stderr, and stdout carries only the view or the events.

  • prmarmot-cli auth login, status, and logout handle signing in from the terminal. login can use the device flow, --with-token on stdin, --host for Enterprise, or --client-id.
  • prmarmot-cli skill prints an embedded skill for coding agents.
    • skill install installs it for Claude Code; --agent agents targets ~/.agents/skills (Codex, Copilot, Cursor, Gemini CLI, OpenCode, Amp), and --agent all does both.
    • It won't overwrite an edited copy without --force.
  • prmarmot-cli completions bash|zsh|fish prints a completion script for commands, flags, and flag values. The cask installs all three. install.sh and make install link the bash or fish script for your login shell and print the zsh line.
  • prmarmot-cli all --repo owner/name prints All open (F-board-14) with an Author column.
    • --filter sends its label: and author: terms to GitHub like the app does, and the status line reads "60 of 762 open" or "40 match". A footer names the terms that checked only the loaded PRs.
    • --json adds total and mode: "all".
    • Without a repository it exits 2 before any request. watch doesn't take all, and --sort and --authored don't apply.

Platform notes

  • Prebuilt, signed releases are for Apple-silicon Macs. Intel Macs and Linux build from source. There is no Windows build.
  • Linux needs a working Vulkan stack plus fontconfig, xkbcommon, and X11 or Wayland libraries.
  • Shortcuts use ⌘ on macOS and Ctrl on Linux.
  • On macOS, the section list and table copy as rich text with a plain-text fallback. On Linux they copy as plain text.
  • On macOS, the system permission prompt appears only after you click Enable notifications… in the footer and confirm in its dialog. Until permission is granted, nothing is sent and nothing prompts. On Linux notifications go to the desktop notification service directly, with no permission step.
  • The Dock badge and the Homebrew upgrade helper exist only on macOS. On Linux an available update opens the release page.
  • A stored token uses the login keychain on macOS and a 0600 file on Linux (F-auth-6).

Not in the app, by design

These are decisions, not gaps.

  • No AI and no summaries.
  • Read-only: PR Marmot opens PRs and issues in the browser and copies links. It never merges, assigns reviewers, comments, submits or dismisses reviews, or changes anything on GitHub.
  • No Windows build.
  • No PR Marmot account and no server between you and GitHub, and the app sends no telemetry.
  • No custom key bindings and no custom shell actions.
Something unclear? Tell us on GitHub