PG

prokopsimek/gitwall

Developer tools
1 stars Quality 35 Trend 13

Open pull requests and issues from GitHub and GitLab in your macOS menu bar and desktop widgets

Overview

Pull requests, merge requests and issues from GitHub and GitLab, in your Mac menu bar and on your desktop. Gitwall is a free, open-source macOS app for everyone who waits on code review or keeps an eye on a team's work. It gathers open pull requests (merge requests on GitLab) and issues from the repositories you choose, sorts them by latest activity and shows review, CI, draft and merge-conflict state at a glance. Click an item to open it in the browser. There is no server: your Mac talks to GitHub and GitLab directly. , unzip it and move Gitwall.app to Applications. The build is signed with a Developer ID and notarized by Apple, so it opens without a Gatekeeper warning. It does not update itself; the Mac App Store version does. Each account you add comes with three presets: pull requests assigned to you, issues assigned to you, and pull requests waiting for your review.

README

Gitwall

Pull requests, merge requests and issues from GitHub and GitLab, in your Mac menu bar and on your desktop.

Gitwall is a free, open-source macOS app for everyone who waits on code review or keeps an eye on a team’s work. It gathers open pull requests (merge requests on GitLab) and issues from the repositories you choose, sorts them by latest activity and shows review, CI, draft and merge-conflict state at a glance. Click an item to open it in the browser. There is no server: your Mac talks to GitHub and GitLab directly.

Download

Download the latest release, unzip it and move Gitwall.app to Applications. The build is signed with a Developer ID and notarized by Apple, so it opens without a Gatekeeper warning. It does not update itself; the Mac App Store version does.

Requires macOS 14 Sonoma or later.

Features

Presets for every account, out of the box. Each account you add comes with three presets: pull requests assigned to you, issues assigned to you, and pull requests waiting for your review. Change their filters, delete them or build your own from any mix of accounts, repositories, pull requests and issues.

Filters that work the same on both platforms. Relation to you (author, assignee, requested reviewer), drafts, review state, checks, merge conflicts, labels, author logins, age, milestone and text. Each preset also decides which events notify you.

Archived repositories never show up: nothing in them can be merged or closed, so Gitwall leaves them out of every list and does not offer them when you pick repositories.

Pull requests that a cloud agent opened for you stay in “Waiting for my review” even though they are drafts: Copilot and the Claude and Codex GitHub apps request the review before a person marks the pull request ready. Filter the agents themselves with author logins such as copilot-swe-agent or cursor.

A query field that speaks GitHub search syntax. When the tick boxes run out, every preset has a Query field. Gitwall reads it with GitHub’s own rules and checks it against the items it already has, so it works for GitLab too and the widget always agrees with the menu bar.

assignee:@me AND (assignee:franta-dxh OR assignee:lumir-sokol OR assignee:tomgilsky)

That one means assigned to me and to at least one of the three, and it finds the same items when pasted into GitHub’s issue search. A space works as AND too, AND binds tighter than OR, parentheses group up to five levels deep, and a leading - excludes a term. Gitwall also reads a comma inside a qualifier as OR (assignee:franta-dxh,lumir-sokol), but GitHub documents the comma only for label: and ignores it for assignee:, so prefer OR when the query should work in both places.

Qualifier Matches
assignee: anyone assigned
author: who opened it
label: a label
milestone: the milestone
reviewed-by: someone who reviewed it
review-requested: someone whose review was requested
involves: author, assignee or reviewer
repo: / org: acme/app / acme
is: / type: pr, issue, draft, open
a bare word the title, the repository or #number

@me is the account you signed in with; assignee:@octocat and assignee:octocat mean the same. Put values with spaces in quotes: milestone:"Q4 2026". A bare @lumir-sokol is an error, as on GitHub; write assignee:lumir-sokol, or "@lumir-sokol" to search the text. There is no NOT and no minus in front of parentheses. A query Gitwall cannot read matches nothing and says why, so a typo can never quietly widen a preset.

Issues too, not just pull requests.

Desktop widgets in four sizes. Counter, List, Board and Wide Board. Add as many as you like; each widget shows the preset you pick for it.

Right there in your menu bar, with the number of reviews waiting for you next to the icon.

And also:

  • Sign in with GitHub or GitLab in one click, or paste a personal access token. OAuth tokens refresh in the background, so you sign in once.
  • GitHub.com, GitHub Enterprise Server, GitLab.com and self-managed GitLab (16 or newer), several accounts at once.
  • Local notifications for new items, review requests, approvals, requested changes, failed checks, merges and closes.
  • A heads-up a week before a personal access token expires.
  • Private by design: no analytics, no third-party services, credentials only in the macOS Keychain. See the privacy policy.

Quick start

  1. Connect an account. The first launch opens a short walkthrough. Choose Sign in with GitHub (you confirm a code at github.com) or Sign in with GitLab, or paste a personal access token. The account’s three presets appear right away.
  2. Pick repositories. Tick single repositories, or a whole GitHub organization or GitLab group so new repositories are picked up automatically.
  3. Add a widget. Right-click the desktop › Edit Widgets… › search “Gitwall” › drag a size onto the desktop. Right-click the widget › Edit “Gitwall” › choose its preset.

Supported hosts

Host Sign in Or paste a token
github.com OAuth device flow, built in Fine-grained or classic
GitHub Enterprise Server Your own OAuth App’s client ID (Settings › Accounts › Advanced) Classic, or fine-grained where your server offers them
gitlab.com OAuth with PKCE, built in Fine-grained or legacy
Self-managed GitLab 16+ Your own application’s client ID, redirect gitwall://oauth/gitlab Legacy, or fine-grained on GitLab 19.2+

Personal access tokens

Signing in is the quickest way. A token is the way to go when your organization blocks OAuth apps, when your own server has no application for Gitwall, or when you want a credential that can only read. Paste it in Settings › Accounts › + › Use a personal access token instead. Gitwall keeps it in the macOS Keychain and warns you a week before it expires.

To move an account from a token to signing in, or back, use its menu in Settings › Accounts (Sign in with GitHub…, Use a Token Instead…). The account keeps its repositories, presets and widgets. Removing the account and adding it again does not: repositories and presets belong to the account, so a new one starts empty. Add Account notices when you are already connected and offers to replace the sign-in instead.

Fine-grained token Classic (GitHub) or legacy (GitLab) token
GitHub Read-only. One token covers one owner: your account or one organization. Scopes repo and read:org. One token covers all your organizations, but repo also allows writing.
GitLab Read-only, GitLab 19.2 or newer, but without each reviewer’s review state. Needed where a group or the instance enforces fine-grained tokens. Scope read_api, GitLab 16 or newer.

GitHub fine-grained token

GitHub classic token

GitLab fine-grained token

GitLab legacy token

Development

You need Xcode 26 and XcodeGen.

brew install xcodegen
git clone https://github.com/prokopsimek/gitwall.git && cd gitwall
make run          # generates the project, builds Debug and launches it
Command What it does
make run Debug build and launch
make test swift test for every package, then the app tests
make test-packages Package tests only (fast, no Xcode UI)
make install Release build into ~/Applications, for daily use
make screenshots App Store and README screenshots from built-in demo data
make archive / make release App Store archive / notarized download (see RELEASING)

project.yml is the source of truth; the generated Gitwall.xcodeproj is not committed.

How it fits together

flowchart LR
    subgraph App["Gitwall.app (menu bar, sandboxed)"]
        UI["Menu bar, main window,settings, onboarding"]
        Sync["SyncEngine"]
        Tokens["Keychain tokens+ TokenRefresher"]
    end
    GH["GitHubProviderGraphQL + REST"]
    GL["GitLabProviderGraphQL + REST"]
    Store[("App Groupconfig.json · snapshot.json")]
    Widget["GitwallWidget(never touches the network)"]

    UI --> Sync
    Tokens --> Sync
    Sync --> GH & GL
    GH --> API1(("GitHub API"))
    GL --> API2(("GitLab API"))
    Sync --> Store
    Store --> Widget
    Store --> UI

The app fetches and writes a snapshot into the App Group; widgets only read it and apply their preset with the same FilterEngine the app uses, so both always agree.

Where What lives there
Packages/ All logic, test-first: models, filters, sync, providers, OAuth
Gitwall/ App target: windows, menu bar, settings, onboarding
GitwallWidget/ The four widgets (and why their kinds must never be renamed)
Scripts/ App Store Connect client, screenshot pipeline, icon rendering
docs/adr/ Why things are the way they are

Handy launch arguments (Debug builds)

Argument Use it for
--debug-fresh A throwaway container and in-memory Keychain: try onboarding or sign-in without touching your real accounts
--debug-demo Fictional accounts and items, no network; add --debug-demo-widgets to see all widget sizes
--debug-onboarding-step Jump straight to one walkthrough screen
--debug-github-token --debug-repos a/b Create a GitHub account without clicking
build/DerivedData/Build/Products/Debug/Gitwall.app/Contents/MacOS/Gitwall --debug-fresh

Tests

Logic is developed test-first with Swift Testing, against fixtures captured from the real APIs. Tests that need a live server or a human are skipped unless you opt in:

GITWALL_GITHUB_TOKEN=github_pat_… GITWALL_GITHUB_REPO=owner/private-repo \
  swift test --package-path Packages/GitwallGitHub --filter Integration
GITWALL_GITLAB_TOKEN=glpat-… GITWALL_GITLAB_URL=https://gitlab.example.com \
  swift test --package-path Packages/GitwallGitLab --filter Integration
GITWALL_OAUTH_INTERACTIVE=1 swift test --package-path Packages/GitwallAuth --filter Interactive

Run the provider suites with both a classic and a fine-grained token when you touch the queries: fine-grained tokens only reach what their permissions list, so a new field can work with one and fail with the other.

Contributing

Issues and pull requests are welcome. A few house rules, spelled out in AGENTS.md:

  • Logic goes into a package with a test first; the app and widget targets stay thin.
  • Provider-specific types never reach the UI; add a capability instead of if kind == .github.
  • Never commit tokens or keys. OAuth client IDs in OAuthClients.swift are public by design.
  • Commit messages follow Conventional Commits.

License

MIT, see LICENSE. Made by Prokop Simek.

View this README on GitHub

Recommended Tools

Try a different keyword or remove a filter.

Install

npx skillfish add prokopsimek/gitwall