shipyard

A macOS menu bar app for keeping up with your projects while your agents work.

Pull requests, issues, workflow runs and your agents’ pings from the repositories you watch, with one number for what needs you.

The tabs layout on the shipyard tab, Shipyard theme: pull requests, two pings from agents, issues and runs, three of each.
The list layout in the Paper theme: four projects, each with its pull requests, pings, issues and runs, two of each.

shipyard lists what your agents and other people open on GitHub, grouped into projects you name in one TOML file. You and your agents edit the file, and each save applies at once.

Start with Install and First run, or jump to the configuration reference.

What it is01

Your pull requests, issues, workflow runs, pings and notes, grouped by project in the menu bar, with one number for what needs you.

Coding agents open pull requests and issues and start workflow runs, often several at once across several repositories. shipyard lists them in the menu bar, from anyone, grouped into projects you name. The number beside its icon counts the items that need attention: new, changed since you last opened them, waiting on your review, or failing their checks.1

It can notify you when something happens, and your agents can ping you when they need you. What it shows lives in one TOML file that you, or your agents, edit.2

How shipyard worksGitHub, your agents' pings from this Mac and from other machines, and your Notion notes feed one refresh in the app, which turns them into the menu bar's number, the panel's projects and macOS notifications.SOURCESAPIGitHubgh or sign-inPINGSThis Macshipyard ping sendPINGSOther machinesHerdr, every 30 sNOTESNotionntn, every minuteMENU BAROne numberitems that need youPANELYour projectsevery item, groupedMACOSNotificationsby your rulesAPPShipyard.appone refresh, into one menu model01Resolve repos02Fetch items03Filter04Find changes05Publish
Pings and notes join the listings without spending GitHub requests. The refresh runs every two minutes by default, and stretches to stay within its share of your rate limit.

Install02

Download the app, or build it from source. Then give your agents the skill.

You need macOS 14 or later and a GitHub account. Building from source also needs the Command Line Tools with Swift 6, from xcode-select --install3

The download is Shipyard-<version>-macos.zip on the latest release. It isn't notarized yet, so clear the quarantine flag before the first launch, as below.4

The app
unzip Shipyard-<version>-macos.zip
mv Shipyard.app /Applications/
xattr -dr com.apple.quarantine /Applications/Shipyard.app
open /Applications/Shipyard.app

Get the zip from the latest release.

The agent skill
npx skills add yahyabedirhan/shipyard -g -y

Then ask your agent: “Watch this repo in shipyard”.

To uninstall, choose Sign out in the gear menu if you signed in with GitHub, quit shipyard, and delete the app and its files. Remove the agent skill with npx skills remove shipyard -g if you installed it.

rm -rf /Applications/Shipyard.app ~/.config/shipyard ~/Library/Application\ Support/Shipyard

First run03

Connect GitHub, choose a preset, and pick the repositories to watch.

shipyard lives in the menu bar, with no Dock icon. On first launch its panel asks you to connect GitHub: Sign in with GitHub, or use the GitHub CLI if gh is signed in.5 It then offers three starting configurations, called presets, and asks which repositories to watch.

Click an item to open it on GitHub and mark it seen; ⌥-click marks it seen without opening it. ↑, ↓ and Return move through the items and open one, and ⌘R refreshes. shipyard starts at login unless launch-at-login = false6

The gear menu lists the parts shipyard uses: Set up GitHub, Set up Notion, Set up /shipyard skill and Set up shipyard command, with a check mark beside each one that is set up. Each opens a view of its status in the panel.

Onboarding asking how you will use shipyard, with the three presets and the agent skill card.
Onboarding asking how you will use shipyard, with the three presets and the agent skill card.
The Set up GitHub status view: the signed-in account and how it connected.
The Set up GitHub status view: the signed-in account and how it connected.

Presets04

Three ready-made files for shipyard's main uses. Start from one, then change any key.

Onboarding writes the preset you choose into config.toml, with your repositories in place of the examples. It writes a preset only into a file that is missing or holds nothing but version, so it never replaces your settings.7

my-agents

Everything the user and their agents open in their repositories: pull requests and issues, grouped by kind, one project per repository. Notifications are the defaults: every new pull request, every ping and notice, and an agent starting or stopping with the app.

For
the pull requests and issues the user and their agents open, one project per repository
Asks for
the repositories to watch
The my-agents preset: one project per repository, pull requests then issues.
The my-agents preset: one project per repository, pull requests then issues.
The my-agents preset in the Paper theme: one project per repository, pull requests then issues.
The my-agents preset in the Paper theme: one project per repository, pull requests then issues.
my-agents config.toml
#:schema https://raw.githubusercontent.com/yahyabedirhan/shipyard/main/schema/config.schema.json
# shipyard configuration, started from the my-agents preset: the pull
# requests and issues you and your agents open, one project per
# repository.
# You and your agents edit this file; shipyard applies changes live.
# Every key is optional; keys, defaults and events are in the schema above.
version = 1

# Group each project's items by kind: pull requests, then issues.
[defaults]
group-by = "kind"

# List issues too, not only pull requests, in every project.
[defaults.issues]
show = true

# Projects: one [[projects]] block per repository. Add more at the end.
[[projects]]
slug = "hello-world"
repositories = ["octocat/hello-world"]

[[projects]]
slug = "spoon-knife"
title = "Spoon-Knife"
repositories = ["octocat/Spoon-Knife"]

incoming-contributions

What other people open on the user's repositories, with their own (and their agents') and bots' items hidden, grouped by repository under subheaders, and a second project of the pull requests waiting on their review in any repository. It notifies when someone else opens a pull request or an issue, on each new review request, on each ping and notice, and when an agent starts or stops using the app.

For
the pull requests and issues other people open on the user's repositories, bots hidden, plus the pull requests waiting on their review anywhere
Asks for
all their repositories (owned, as written below) or the ones they pick
The incoming-contributions preset: other people's pull requests and issues, grouped by repository under subheaders, bots hidden.
The incoming-contributions preset: other people's pull requests and issues, grouped by repository under subheaders, bots hidden.
The incoming-contributions preset in the Paper theme: other people's pull requests and issues, grouped by repository under subheaders, bots hidden.
The incoming-contributions preset in the Paper theme: other people's pull requests and issues, grouped by repository under subheaders, bots hidden.
incoming-contributions config.toml
#:schema https://raw.githubusercontent.com/yahyabedirhan/shipyard/main/schema/config.schema.json
# shipyard configuration, started from the incoming-contributions preset:
# the pull requests and issues other people open on your repositories,
# bots left out, and the pull requests waiting on your review anywhere.
# You and your agents edit this file; shipyard applies changes live.
# Every key is optional; keys, defaults and events are in the schema above.
version = 1

# Group each project's items by repository, each under a subheader.
[defaults]
group-by = "repository"
subsections = true

# List other people's pull requests: yours (and your agents') and
# bots' are hidden.
[defaults.pull-requests]
authors = { hide = ["me", "bots"] }

# List other people's issues too, hiding the same authors.
[defaults.issues]
show = true
authors = { hide = ["me", "bots"] }

# Notify when someone else opens a pull request or an issue, when
# one of your agents sends a ping or a notice, and when an agent
# starts and stops using the shipyard app.
[[defaults.notifications]]
event = "pr.opened"
authors = ["others"]

[[defaults.notifications]]
event = "issue.opened"
authors = ["others"]

[[defaults.notifications]]
event = "ping.sent"

[[defaults.notifications]]
event = "agent.notice"

[[defaults.notifications]]
event = "control.started"

[[defaults.notifications]]
event = "control.ended"

# Projects: one [[projects]] block each. owned is every repository
# your account owns, including ones you create later.
[[projects]]
slug = "incoming"
title = "Incoming"
repositories = ["owned"]

# The pull requests waiting on your review, or a team's you're in, in
# any repository. anywhere lists only those, so this project shows no
# issues, and it notifies each new request (and ping, and notice) instead.
[[projects]]
slug = "review-requests"
title = "Review requests"
repositories = ["anywhere"]
pull-requests = { review-requested = true }
issues = { show = false }
notifications = [
  { event = "pr.review_requested" },
  { event = "ping.sent" },
  { event = "agent.notice" },
]

review-queue

Only the pull requests waiting on the user's review, or a team's they're in, in any repository, grouped by repository under subheaders. Each new request notifies, and so does each ping and notice.

For
only the pull requests waiting on the user's review, in any repository
Asks for
nothing
The review-queue preset: the pull requests waiting on your review, grouped by repository.
The review-queue preset: the pull requests waiting on your review, grouped by repository.
The review-queue preset in the Paper theme: the pull requests waiting on your review, grouped by repository.
The review-queue preset in the Paper theme: the pull requests waiting on your review, grouped by repository.
review-queue config.toml
#:schema https://raw.githubusercontent.com/yahyabedirhan/shipyard/main/schema/config.schema.json
# shipyard configuration, started from the review-queue preset: only the
# pull requests waiting on your review, in any repository.
# You and your agents edit this file; shipyard applies changes live.
# Every key is optional; keys, defaults and events are in the schema above.
version = 1

# The pull requests waiting on your review, or a team's you're in, in
# any repository, grouped by repository under subheaders. Each new
# request notifies, and so does each ping and notice your agents send.
[[projects]]
slug = "review-queue"
title = "Review queue"
repositories = ["anywhere"]
pull-requests = { review-requested = true }
group-by = "repository"
subsections = true
notifications = [
  { event = "pr.review_requested" },
  { event = "ping.sent" },
  { event = "agent.notice" },
]

Layouts05

Every project in one list, or one project at a time in tabs.

layout = "list" under [menu] shows every project in one scrolling list, one line per item under pinned project headers. layout = "tabs" shows one project at a time, with an All tab first. The layout button in the panel's header switches between them and writes the change to the file.8

Within a project, group-by groups the items by kind, repository, date or author, or not at all; sort-by orders each group; subsections puts each group under a subheader; and show-first keeps each group to its first few rows, with a Show N more row for the rest.9

[menu]
layout = "tabs"
The list layout: every project in one scrolling list under pinned headers, grouped by repository with subheaders, four rows a group.
The list layout: every project in one scrolling list under pinned headers, grouped by repository with subheaders, four rows a group.
The tabs layout on one project's tab, grouped by kind.
The tabs layout on one project's tab, grouped by kind.

Config explorer06

Pick a snippet and see what it changes, on a mock of the panel.

Each snippet below is a few lines of config.toml. The mock under it applies them to a small set of made-up items, so you can see what the keys do before you write them.10

version = 1

Pull requests and pings only, grouped by kind, newest first.

5Mock
shop7
  • Add the order exportshop#57 · you · 1h
  • Cache the price lookupsshop-api#212 · you · 3h
  • Bump vite from 5.4.0 to 5.4.21shop#58 · dependabot[bot] · 5h
  • Fix the refund roundingshop-api#209 · ada · 20h
  • Split the checkout stepsshop#54 · you · 1d
  • Try a second payment providershop#52 · lin · 3d

  • PR #57 is ready for reviewshop · claude · orders · 30m
docs2
  • Rewrite the install pagedocs#33 · you · 2h
  • Update renovate configdocs#34 · renovate[bot] · 1d
A mock drawn in HTML from made-up items, not a screenshot of the app.

Pings and notices07

Your agents tell you when they need you, and when there's news.

A ping is a short message an agent sends with shipyard ping send: a title, an optional body and sender, and at most one action. It's listed under the projects that watch the repository of the agent's working folder, needs attention until you click it, and posts a notification. Clicking it opens a link, brings an app forward, or focuses the agent's Herdr tab.11

A notice, from shipyard notice send, is a status update that needs nothing from you, such as "tests running": a notification only, never listed, counted or kept. When in doubt, agents send a notice.

shipyard ping send "PR #57 is ready for review" --from claude --open https://github.com/my-org/shop/pull/57
shipyard ping send "Waiting for your input" --herdr --id shop-question
shipyard ping withdraw shop-question
shipyard notice send "Tests running" --from "claude · checkout" --id checkout-tests
A project with two pings from agents above its pull requests: one with a link, one that focuses a Herdr pane.
A project with two pings from agents above its pull requests: one with a link, one that focuses a Herdr pane.
The tabs layout's Pings group on one project's tab, in light: a ping that opens a link and one that focuses a Herdr pane, each with its sender's agent logo.
The tabs layout's Pings group on one project's tab, in light: a ping that opens a link and one that focuses a Herdr pane, each with its sender's agent logo.
ping sendWhat it does
"<title>"the one line the menu and the notification show; quote it, it's one argument
--body <text>more than the title fits, shown under it and in the notification
--from <label>who sent it, such as your name and the task ("claude · checkout"); group-by = "author" groups pings by it. Start it with your agent's name, in any case and with words after it, so the row shows its logo: claude, codex, opencode, cursor, pi, gemini, copilot, amp or droid (also Claude Code, Codex CLI, OpenAI Codex, Cursor Agent, Cursor CLI, Pi Agent, Pi Coding Agent, Gemini CLI, Copilot CLI, GitHub Copilot, Ampcode, Factory Droid)
--id <id>names the ping, so you can replace or withdraw it: 1 to 64 lowercase letters, digits, - and _, starting with a letter or digit. Without it, one is made up
--open <url>clicking it opens the URL: a page, a pull request, an artifact, or an app's deep link; the URL needs its scheme (https://…)
--app <bundle id or name>clicking it brings the app forward, by name ("Claude") or bundle id (com.anthropic.claudefordesktop)
--herdr [<id>]clicking it focuses a Herdr tab. Alone, your own pane's tab (from HERDR_PANE_ID, so only inside Herdr); or a tab or pane id as Herdr prints them, such as w1:t2 or w1:p3. The argument after --herdr is its id only when it has that shape
--repo <owner/name>file the ping by this repository instead of the working folder's
--project <name>file it under this one project, by its slug in config.toml, or else its title
--ends the flags: everything after it is the title, even a word starting with --
Every flag of notice send
notice sendWhat it does
"<title>"the one line the notification shows after the project's title; quote it, it's one argument
--subtitle <text>a line between the title and the body
--body <text>more than the title fits, shown under it
--from <label>who sent it, such as your name and the task ("claude · checkout"), shown under the body as "from <label>"
--repo <owner/name>file it by this repository instead of the working folder's
--project <name>file it under this one project, by its slug in config.toml, or else its title
--image <path>a PNG, JPEG or GIF shown with it, 5 MB at most; a relative path is from the working folder
--sound <sound>default (without the flag too), none for a quiet update, or a sound's name such as Glass
--thread <key>stacks it in Notification Center with the other notices of this key, such as one orchestration's; without it, it stacks with its project's
--level <level>passive: into Notification Center without a banner, for routine updates; active, the default: a banner. Critical and time-sensitive aren't offered
--id <id>shows it under this id: a later notice with the same id replaces it in place, and shipyard notice withdraw <id> takes it away. 1 to 64 lowercase letters, digits, - and _
--open <url>clicking it opens the URL (a pull request, a run, an artifact)
--app <bundle id or name>clicking it brings the app forward
--herdr [<id>]clicking it focuses this Herdr tab or pane (w1:t2, w1:p3), then your terminal; with no id, your own pane ($HERDR_PANE_ID), as a ping's --herdr. Not on the tailnet route (see Routes)
--button "<label>=<action>"a button, up to 3, each with its own action: open:<url>, app:<bundle id or name>, herdr:<tab or pane id>, or herdr alone for your own pane (no herdr button on the tailnet route)
--ends the flags: everything after it is the title, even a word starting with --

If your config.toml lists its own notification rules, add { event = "ping.sent" } to hear about pings, and { event = "agent.notice" } to let notices through: a project whose rules leave agent.notice out refuses notices, and the agent is told.

Notes in Notion08

Your own notes, one Notion database per project, listed beside the pull requests.

Each project can list your open notes from Notion, newest first, with their numbers (SHOP-7) and labels. Each project has one database, titled with the project's title, under a "Projects" page inside a "Shipyard Notes" page. Clicking a note opens it in Notion, and the pencil icon on a project's header starts a new one there.12

Notes are opt-in. shipyard reads and writes them through ntn, Notion's CLI, with ntn's own login, and keeps no Notion token of its own. Install ntn, run ntn login and choose your notes workspace, then press Connect with ntn in the gear menu's Set up Notion view.13

curl -fsSL https://ntn.dev | bash
ntn login
ntn doctor
A project's Notes group from Notion: numbered notes with labels, newest first.
A project's Notes group from Notion: numbered notes with labels, newest first.
The tabs layout on the shipyard tab, in light: its Notes group from Notion under its pull requests, pings and issues, each note with its number and label.
The tabs layout on the shipyard tab, in light: its Notes group from Notion under its pull requests, pings and issues, each note with its number and label.

The agent skill09

Ask in your own words. Your agent edits the file and checks it.

shipyard ships an agent skill that teaches coding agents (Claude Code, Codex and others that read skills) to edit its configuration, ping you, post notices and take notes. Install it during onboarding, from Set up /shipyard skill in the gear menu, or from a terminal.14

Then ask: "Watch this repo in shipyard", "Hide dependabot's pull requests", "Tell me when CI fails on this project", "Switch shipyard to tabs". The agent edits the file, checks it with shipyard config check and the schema, and reads shipyard's verdict on the save.15

The configuration loopYou ask your agent for a change; it edits config.toml with the shipyard skill; the app rereads the file on save and writes its verdict to config-status.json; the agent reads the verdict and fixes any problem it lists.ASKSEDITSSAVEDWRITESREADS, FIXESYOUAsk in words"switch to tabs"AGENTYour agent/shipyard skillFILEConfigurationconfig.tomlFILEThe verdictconfig-status.jsonThe verdict says accepted,with any warnings, or listseach problem with its line.A rejected file changesnothing: the app keeps thelast valid configuration.APPShipyard.apprereads on save
The verdict is in ~/Library/Application Support/Shipyard/config-status.json, written after every reload.
npx skills add yahyabedirhan/shipyard -g -y
The Set up /shipyard skill view, with the skill installed.
The Set up /shipyard skill view, with the skill installed.
The panel's quiet banner after an agent's edit left a misspelled key: the line and the nearest valid key.
The panel's quiet banner after an agent's edit left a misspelled key: the line and the nearest valid key.

Other machines10

Agents on your other machines ping you too, through Herdr.

When your Mac's Herdr knows another machine as a saved machine, shipyard can list that machine's pings. Install the herdr-shipyard plugin on each machine, which puts the same shipyard command there, and name the machines in [remote] machines by their Herdr labels.16

shipyard asks each machine for its pings about every 30 seconds and files them under the projects that watch their repositories, or under the machine's name. Clicking one sent from a Herdr pane takes you to that agent. On those machines the plugin also pings you by itself when Herdr marks an agent blocked, and takes the ping back when the agent goes on.17

Notices travel the same way, or straight over your tailnet once [notices] listen = true and the machine's own cli.toml names your Mac.

Pings across machinesAn agent on this Mac sends a ping into the ping store, filed by its repository; an agent on another machine sends one into the herdr-shipyard plugin, which keeps it until the Mac asks through Herdr about every 30 seconds. The app lists each ping under its project or its machine's section, notifies, and runs its action when you click it.THIS MACANOTHER MACHINEWATCHEDPOLLED · 30 SCLICKAGENTYour agentshipyard ping sendSTOREPing storefiled by its repositoryAGENTYour agentshipyard ping sendPLUGINherdr-shipyardkeeps it therePANELListedneeds you until seenMACOSNotificationevent ping.sentON CLICKIts actionlink, app or Herdr tabAPPShipyard.applists it under its project, or under the machine's section
A ping that no project's repository matches lists under its machine's own section, named after the machine's Herdr label.
herdr plugin install yahyabedirhan/herdr-shipyard

Configuration reference11

Every key of config.toml, its default and what it takes.

The file is ~/.config/shipyard/config.toml, or $XDG_CONFIG_HOME/shipyard/config.toml when that is set. Open configuration file in the gear menu opens it. Every key is optional and each save applies at once. A broken edit keeps the last good configuration and shows the error in the panel.18

A project's tables merge key by key onto [defaults], so issues = { show = true } keeps the default closed-window. A project's notifications replaces the default list instead. A window is a whole number and one unit, s, m, h or d, such as "30m" or "7d"19

#:schema https://raw.githubusercontent.com/yahyabedirhan/shipyard/main/schema/config.schema.json
version = 1

[[projects]]
slug = "shipyard"
repositories = ["yahyabedirhan/shipyard"]

Top level

Keys above the first table header.

KeyDefaultWhat it takes
version1integer: 1

The configuration format version. Only 1 exists. Set it in every file that sets a key: a file without it reads with a warning, and from version 2 it is an error.

theme"Shipyard"string

The theme the panel draws with, by name, ignoring case: a built-in theme (Shipyard, Native, Graphite, Paper, Midnight or Harbor) or the name of a theme file in the themes folder beside config.toml. An unknown name is an error; a theme whose text has too little contrast on its panel reads with a warning.

refresh-interval"2m"string

How often to refresh: a whole number and one unit, s, m, h or d, such as "2m", at least 30 seconds. A floor: shipyard stretches it when a refresh at this interval would spend more than max-share-percent of a GitHub rate limit.

launch-at-logintrueboolean

Start shipyard when you log in.

[menu-bar]

What the menu bar shows next to the icon.

KeyDefaultWhat it takes
count"total"string: "total", "per-kind", "none"

The attention count: one total, split by kind (pull requests, issues, runs), or no number.

[menu]

How the panel draws your projects.

KeyDefaultWhat it takes
layout"list"string: "list", "tabs"

One scrolling list of every project, one line per item under pinned project headers, or one project at a time in tabs.

header-counts["pull-requests", "issues", "workflow-runs", "pings", "notes"]list: "pull-requests", "issues", "workflow-runs", "pings", "notes"

The kinds with a count in each project header of the list layout, each listed once. A project's header shows a count for each listed kind it has items of. The counts keep their own order: pull requests, issues, workflow runs, pings, notes. Every project shares this list.

[rate-limit]

How much of your GitHub rate limit shipyard may use, and whether the panel shows it. The limit is shared with every other tool using your account, your agents included.

KeyDefaultWhat it takes
show"always"string: "always", "when-low", "never"

Show the rate limit in the panel footer always, only when it's low (below 25%), or never.

max-share-percent10integer: 1 to 50

The largest share of each hourly GitHub limit (GraphQL points, REST requests) shipyard spends, in percent.

[attention]

Which reasons make an open item need attention (count in the menu bar). Clicking an item clears all of them until it changes again. Closed items never need attention.

KeyDefaultWhat it takes
unseentrueboolean

An item you haven't clicked yet.

changedtrueboolean

An item that changed (commits, comments, reviews, checks, review request) since you clicked it.

review-requestedtrueboolean

A pull request that requests your review.

checks-failedtrueboolean

A pull request whose checks failed.

[banners]

The banners above the projects in the panel.

KeyDefaultWhat it takes
snooze-duration"1h"string

How long a banner stays hidden after you click its dismiss button: a whole number and one unit, s, m, h or d, such as "1h" or "10s". After it, the banner shows again if its condition still holds.

[herdr]

How a ping's Herdr action (shipyard ping send --herdr) takes you there: clicking it focuses the Herdr tab, then brings this terminal forward.

KeyDefaultWhat it takes
terminalunsetstring

The terminal app Herdr runs in, by name ("Ghostty") or bundle id ("com.mitchellh.ghostty"), brought forward after the tab is focused. Unset: only the tab is focused.

[remote]

Your other machines, whose agents' pings the menu lists beside this Mac's. Shipyard asks each one through Herdr's connection to it (herdr --machine), never over SSH of its own.

KeyDefaultWhat it takes
machines[]list

The machines to ask for their pings, by the labels your Herdr knows them by as saved machines ("beta-vps"): never a host or an address. Each machine's pings list in a section named after it, after the projects. A label can't start with "-" or be a project's slug or title.

[notices]

Notices from your other machines' agents (shipyard notice send), straight to this Mac over your tailnet. Off by default: nothing listens unless listen is true. The app listens on 127.0.0.1 only and takes only notices sent from this Mac's own Tailscale login; you expose the port with tailscale serve, and each other machine names this Mac in its own cli.toml.

KeyDefaultWhat it takes
listenfalseboolean

Whether the app listens for notices from your other machines.

port47420integer: 1 to 65535

The port on 127.0.0.1 the app listens on, which tailscale serve exposes to your tailnet: tailscale serve --bg --http=<port> http://127.0.0.1:<port>.

[defaults]

What every project shows and notifies about, unless the project overrides it.

KeyDefaultWhat it takes
group-by"kind"string: "kind", "repository", "date", "author", "none"

What a project's items are grouped by, one level: kind (pull requests, then issues, then runs), repository (A to Z), date (Today, Yesterday, This week, This month, Older, by the sort-by date; updated when sorting by title), author (A to Z; pings by who sent them), or none (one list).

subsectionsunsetboolean

Draw each group under a subheader (its name and count) instead of after a divider line. Unset, each layout keeps its own look: dividers in the list, subheaders in tabs. A value set applies to both layouts.

sort-by"updated"string: "updated", "created", "title"

The order within a group: last updated (newest first), created (newest first) or title (A to Z). Open or running items always come before closed or finished ones.

show-first0integer: 0 or more

How many rows each group shows before a "Show N more" row, which reveals the rest and then reads "Show less". 0 shows every row. With group-by = "none" it caps the whole project. A group's count includes the rows it hides; the menu shows every cap again each time it opens.

pull-requestsunsettable · its keys

Pull requests: open ones, then those closed within the closed window, newest first, from the authors authors allows.

issuesunsettable · its keys

Issues: open ones, then those closed within the closed window, from the authors authors allows.

workflow-runsunsettable · its keys

GitHub Actions runs: running now, plus finished within the finished window.

pingsunsettable · its keys

The pings agents send with the shipyard CLI (shipyard ping), each filed under a project. Pings take no states, authors, drafts or review-requested.

notesunsettable · its keys

Your own notes, kept in Notion: one database per project, titled with the project's name, under the "Shipyard Notes" page. The menu lists a project's open notes, newest first, read through ntn, Notion's CLI, once it's installed and logged in to your notes workspace. Notes take only show.

notifications5 rules: pr.opened, ping.sent, agent.notice, control.started, control.endedlist of rules · its keys

The notification rules for every project that doesn't list its own, and the only rules for app control's control.started and control.ended. Default: a new pull request by anyone, a new ping, an agent's notice, and an agent starting and ending its lease on the app.

archivedfalseboolean

Whether repository groups and owner/* bring in archived repositories. A repository named as owner/name is always listed.

forkstrueboolean

Whether repository groups and owner/* bring in forks. A repository named as owner/name is always listed.

[defaults.pull-requests]also pull-requests = { … } in a project

Pull requests: open ones, then those closed within the closed window, newest first, from the authors authors allows.

KeyDefaultWhat it takes
showtrueboolean

List pull requests.

states["open", "merged", "closed"]list: "open", "merged", "closed"

Which pull requests to list: open (drafts too), merged, closed (without merging). Merged and closed ones stay for closed-window. Those left out are never shown, counted or notified.

closed-window"7d"string

How long closed and merged pull requests stay listed: a whole number and one unit, s, m, h or d, such as "30m" or "7d"; "0" hides them.

draftstrueboolean

List draft pull requests (in gray).

authorsunsettable · its keys

Whose items this kind lists: those show matches (empty: everyone) minus those hide matches; hide wins. Items left out are never shown, counted or notified. A project's authors merges key by key onto the defaults'.

review-requestedfalseboolean

List only open pull requests waiting on your review, requested from you or from one of your teams.

[defaults.issues]also issues = { … } in a project

Issues: open ones, then those closed within the closed window, from the authors authors allows.

KeyDefaultWhat it takes
showfalseboolean

List issues.

states["open", "closed"]list: "open", "closed"

Which issues to list: open, closed. Closed ones stay for closed-window. Those left out are never shown, counted or notified.

closed-window"7d"string

How long closed issues stay listed: a whole number and one unit, s, m, h or d, such as "30m" or "7d"; "0" hides them.

authorsunsettable · its keys

Whose items this kind lists: those show matches (empty: everyone) minus those hide matches; hide wins. Items left out are never shown, counted or notified. A project's authors merges key by key onto the defaults'.

[defaults.workflow-runs]also workflow-runs = { … } in a project

GitHub Actions runs: running now, plus finished within the finished window.

KeyDefaultWhat it takes
showfalseboolean

List workflow runs.

states["in-progress", "failed", "succeeded"]list: "in-progress", "failed", "succeeded"

Which workflow runs to list: in-progress (queued or running), failed (timed out and failed to start too), succeeded. Finished ones stay for finished-window. Those left out are never shown, counted or notified.

finished-window"3h"string

How long finished runs stay listed: a whole number and one unit, s, m, h or d, such as "30m" or "3h"; "0" hides them. Running ones always are.

branches"default-and-pull-requests"string: "default-and-pull-requests", "all"

Which branches' runs to list: the default branch and open pull request branches, or all branches.

authorsunsettable · its keys

Whose items this kind lists: those show matches (empty: everyone) minus those hide matches; hide wins. Items left out are never shown, counted or notified. A project's authors merges key by key onto the defaults'.

[defaults.pings]also pings = { … } in a project

The pings agents send with the shipyard CLI (shipyard ping), each filed under a project. Pings take no states, authors, drafts or review-requested.

KeyDefaultWhat it takes
showtrueboolean

List pings.

seen-window"24h"string

How long a seen ping stays listed, counted from when it was seen: a whole number and one unit, s, m, h or d, such as "30m" or "24h"; "0" lets it leave at once. An unseen ping stays until it's seen.

[defaults.notes]also notes = { … } in a project

Your own notes, kept in Notion: one database per project, titled with the project's name, under the "Shipyard Notes" page. The menu lists a project's open notes, newest first, read through ntn, Notion's CLI, once it's installed and logged in to your notes workspace. Notes take only show.

KeyDefaultWhat it takes
showtrueboolean

List notes.

authors = { show, hide }also in each kind’s table

Whose items this kind lists: those show matches (empty: everyone) minus those hide matches; hide wins. Items left out are never shown, counted or notified. A project's authors merges key by key onto the defaults'.

KeyDefaultWhat it takes
show[]list

List only these authors' items; empty lists everyone.

hide[]list

Never list these authors' items, even when show matches them.

[[defaults.notifications]]also notifications = [{ … }] in a project

KeyDefaultWhat it takes
eventrequiredunsetstring: "pr.opened", "pr.merged", "pr.closed", "pr.reopened", "pr.review_requested", "pr.checks_failed", "pr.commented", "issue.opened", "issue.closed", "issue.commented", "run.failed", "run.succeeded", "ping.sent", "agent.notice", "control.started", "control.ended"

The change to notify about. agent.notice is an agent's notice (shipyard notice send), refused for a project whose rules leave it out. control.started and control.ended (an agent starting and ending its lease on the app) are decided by the top-level rules only: a project's rule for one is ignored.

authors[]list

Whose items the rule covers, as author selectors (me, others, bots or an @login); empty is everyone. It only narrows what the project lists. A ping or a notice has no author, so a rule with authors never selects one, and authors are ignored on control.started and control.ended.

[[projects]]

A named group of one or more GitHub repositories, shown as one section. Tables here merge key by key onto [defaults]; notifications replaces the default list.

KeyDefaultWhat it takes
slugunsetstring

The project's ID, unique across projects: lowercase letters and digits joined by single hyphens, such as "e-commerce". Required. shipyard ping send --project and shipyard notice send --project find a project by its slug, then by its title.

titleunsetstring

The text the menu shows for the project: its section's and its tab's title. Unset: the slug.

repositoriesrequiredunsetlist

The project's repository selectors, each listed once: "owner/name" (one repository, always listed, archived or not), "owner/*" (every repository a user or organization owns), or a repository group: "owned" (your own account's), "organizations" (reached through an organization you're a member of, teams included), "collaborator" (someone else's that added you), "anywhere" (open pull requests waiting on your review in any repository, from GitHub's search; the project must set pull-requests.review-requested = true and show no issues or runs). Groups and wildcards are looked up again at least hourly, so new repositories appear on their own; archived and forks decide what they bring in. Shipyard also rejects two spellings that differ only in case, which this check can't see.

group-by"kind"string: "kind", "repository", "date", "author", "none"

What a project's items are grouped by, one level: kind (pull requests, then issues, then runs), repository (A to Z), date (Today, Yesterday, This week, This month, Older, by the sort-by date; updated when sorting by title), author (A to Z; pings by who sent them), or none (one list).

subsectionsunsetboolean

Draw each group under a subheader (its name and count) instead of after a divider line. Unset, each layout keeps its own look: dividers in the list, subheaders in tabs. A value set applies to both layouts.

sort-by"updated"string: "updated", "created", "title"

The order within a group: last updated (newest first), created (newest first) or title (A to Z). Open or running items always come before closed or finished ones.

show-first0integer: 0 or more

How many rows each group shows before a "Show N more" row, which reveals the rest and then reads "Show less". 0 shows every row. With group-by = "none" it caps the whole project. A group's count includes the rows it hides; the menu shows every cap again each time it opens.

archivedfalseboolean

Whether repository groups and owner/* bring in archived repositories. A repository named as owner/name is always listed.

forkstrueboolean

Whether repository groups and owner/* bring in forks. A repository named as owner/name is always listed.

pull-requestsunsettable · its keys

Pull requests: open ones, then those closed within the closed window, newest first, from the authors authors allows.

issuesunsettable · its keys

Issues: open ones, then those closed within the closed window, from the authors authors allows.

workflow-runsunsettable · its keys

GitHub Actions runs: running now, plus finished within the finished window.

pingsunsettable · its keys

The pings agents send with the shipyard CLI (shipyard ping), each filed under a project. Pings take no states, authors, drafts or review-requested.

notesunsettable · its keys

Your own notes, kept in Notion: one database per project, titled with the project's name, under the "Shipyard Notes" page. The menu lists a project's open notes, newest first, read through ntn, Notion's CLI, once it's installed and logged in to your notes workspace. Notes take only show.

notificationsunsetlist of rules · its keys

This project's notification rules; replaces [[defaults.notifications]]. An empty list means no notifications for this project.

cli.toml

The shipyard command reads its own settings file on every machine that runs it: beside config.toml on the Mac, in ~/.config/shipyard/ elsewhere. Unlike config.toml, an unknown key there is an error.

Top level

Keys above the first table header.

KeyDefaultWhat it takes
version1integer: 1

The cli.toml format version. Only 1 exists; a file without it is version 1.

[notices]

Where this machine's shipyard notice send delivers a notice. With app-machine set, straight to the app on that Mac over your tailnet, at <app-scheme>://<app-machine>:<app-port>; without it, through the herdr-shipyard plugin for the Mac's poll.

KeyDefaultWhat it takes
app-machineunsetstring

The Mac running the app, by its MagicDNS name alone, short or full, such as "my-mac" or "my-mac.tail1234.ts.net": no scheme and no port. None by default.

app-scheme"http"string: "http", "https"

How the request travels, as tailscale serve exposes the app's port on the Mac (--http or --https).

app-port47420integer: 1 to 65535

The port tailscale serve exposes on the Mac.

The shipyard command12

One command for agents and scripts: pings, notices, checks and the app.

The shipyard command comes inside the app. Link it onto your PATH from onboarding or Set up shipyard command in the gear menu, which puts it at ~/.local/bin/shipyard. The herdr-shipyard plugin installs the same command on your Linux machines.20

Every command is shipyard <group> <verb>. shipyard --help lists them all, shipyard help <group> prints one group's help, and shipyard help --json lists every command for agents.21

The Set up shipyard command view, with the command linked.
The Set up shipyard command view, with the command linked.
A terminal showing shipyard --help: the command groups and their verbs.
A terminal showing shipyard --help: the command groups and their verbs.

Tell the user (any machine)

CommandWhat it does
shipyard notice send <title> [options]show the user a notice, a status message that isn't kept
shipyard notice withdraw <id>take a shown notice away
shipyard ping send <title> [options]send the user a ping that waits until they click it
shipyard ping withdraw <id>take a ping back
shipyard ping list [--json]list this machine's live pings

Drive the app (on the Mac)

CommandWhat it does
shipyard app open [--demo <folder>]launch the app, or a demo run, and wait until it answers
shipyard app quitquit the app and wait until it's gone
shipyard app status [--json]print whether the app runs, its version, layout and projects
shipyard app capture <file.png> [options]save the panel, or the menu bar icon, as a PNG
shipyard panel openopen the menu bar icon's panel
shipyard panel closeclose the panel
shipyard panel fold <project>collapse a project's section
shipyard panel unfold <project>expand a project's section
shipyard panel show-more <project> <kind>show every row of a project's group past its cap
shipyard panel show-tab <name>show a tab of the tabs layout
shipyard panel show-view <name>show a status view, or the projects again
shipyard lease take [options]hold app control for a run of steps, waiting in line with --wait
shipyard lease release [--key <k>]give app control up to the next agent

Check the setup

CommandWhat it does
shipyard config check [--json]check the settings files, each problem and warning with its line
shipyard notes checkcheck that the app can read the notes workspace in Notion

Plumbing (run by Herdr's plugin hooks, not by hand)

CommandWhat it does
shipyard herdr-eventping when a Herdr agent is blocked, and withdraw it when it goes on

Options and environment

-h, --helpshow help; shipyard <group> --help for a group's commands
--versionprint the version
SHIPYARD_CONTROL_KEYthe holder key app, panel and lease commands are sent as

Exit codes: 0 success, 1 failed or refused, 2 wrong usage. On a machine without the app, app</code>, <code>panel</code>, <code>lease</code>, <code>notes exit 2 with a pointer to the Mac.

Limits13

What shipyard does not do yet.

macOS only
The app runs on macOS 14 or later. The shipyard command also runs on Linux, without the app.
github.com only
It reads github.com. GitHub Enterprise Server is not supported.
Not notarized
macOS asks you to clear the quarantine flag before the first launch of the download.
No Homebrew or updates
There is no Homebrew cask and no automatic update yet.
Settings in a file
The gear menu shows the setup of GitHub, Notion, the skill and the command, and the layout button switches layouts. Everything else is edited in config.toml, by you or your agents.
Herdr windows
With Herdr 0.9.3, clicking another machine's ping focuses its pane on that machine, but the Mac's Herdr window moves there only when it already shows that machine.

Troubleshooting14

Short answers to what goes wrong most.

macOS says the app can't be opened

The download isn't notarized. Run xattr -dr com.apple.quarantine /Applications/Shipyard.app, then open it again.

The panel says the configuration has a problem

shipyard keeps running on the last valid configuration. Run shipyard config check: it names each problem with its line, and the nearest spelling when a key or value is misspelled.

Pings don't notify me

Your file lists its own notification rules without ping.sent. Add { event = "ping.sent" } beside the rules you have.

shipyard isn't found

Link it from Set up shipyard command in the gear menu, which puts it at ~/.local/bin/shipyard, and make sure that folder is on your PATH. The view says what is in the way when the link can't be made.

A ping is refused

No project watches the repository of the folder it was sent from. Send it with --project <slug>, or watch the repository in a project.

No notes are listed

Check that ntn is installed and logged in (ntn doctor), that its default workspace has a Shipyard Notes page, and that you pressed Connect with ntn. shipyard notes check says what's wrong.

Another machine's pings don't show

Check that its label is in [remote] machines exactly as Herdr saves it, and that herdr plugin list on the machine shows herdr-shipyard.

anywhere is rejected

anywhere comes from one GitHub search, so its project must set pull-requests = { review-requested = true } and show no issues or runs.

The rate limit runs low

shipyard spends at most max-share-percent of each hourly GitHub limit, 10% by default, and stretches refresh-interval to stay within it. Lower the share, or watch fewer repositories.

Changelog15

What changed in each version, with its pull requests.

Every release has an entry in CHANGELOG.md, newest first, and a download on GitHub releases. Ideas and bugs go to the issues.

Latest release
0.7.1, 2026-10-15. The skill install output shows each line once, and the code explains itself without narration comments. It gathers #262 and #273.
These docs
Describe 0.7.1 and what has merged since. Read the changelog.

shipyard is open source under Apache 2.0. Ideas and bugs are welcome in its issues.