# shipyard > A macOS menu bar app for keeping up with your projects while your agents work. One number for what needs you, one file your agents edit, pings from any machine. A macOS menu bar app. It lists the pull requests, issues and workflow runs of the GitHub repositories you watch, grouped into projects, with one number in the menu bar for what needs attention, and your agents' pings. Everything it shows is set in one TOML file, `~/.config/shipyard/config.toml`, that you and your agents edit. These docs describe version 0.8.0. ## For AI agents - To change someone's shipyard, edit their `config.toml` with the agent skill: `npx skills add yahyabedirhan/shipyard -g -y`. The skill knows every key, checks each edit and reads the app's verdict. - To tell the user something, use the `shipyard` command on their machine: `shipyard ping send ""` for what they must act on, `shipyard notice send "<title>"` for a status that needs nothing from them. - Never invent a key: every key is in [docs/configuration.md](https://shipyard.yahyapak.dev/docs/configuration.md), generated from the JSON schema. - Optional: identify yourself on your first page with `?analytics_agent=<name>&analytics_purpose=<purpose>` (see [privacy](https://shipyard.yahyapak.dev/privacy)). ## Install - Download `Shipyard-<version>-macos.zip` from https://github.com/yahyabedirhan/shipyard/releases/latest (macOS 14 or later), or build from source: ```sh git clone https://github.com/yahyabedirhan/shipyard.git cd shipyard make install ``` - The agent skill: `npx skills add yahyabedirhan/shipyard -g -y`, then ask your agent "Watch this repo in shipyard". - Other machines: `herdr plugin install yahyabedirhan/herdr-shipyard` (https://github.com/yahyabedirhan/herdr-shipyard). ## Docs - [What it is](https://shipyard.yahyapak.dev/docs/what.md): Your pull requests, issues, workflow runs, pings and notes, grouped by project in the menu bar, with one number for what needs you. - [Install](https://shipyard.yahyapak.dev/docs/install.md): Download the app, or build it from source. Then give your agents the skill. - [First run](https://shipyard.yahyapak.dev/docs/first-run.md): Connect GitHub, choose a preset, and pick the repositories to watch. - [Presets](https://shipyard.yahyapak.dev/docs/presets.md): Three ready-made files for shipyard's main uses. Start from one, then change any key. - [Layouts](https://shipyard.yahyapak.dev/docs/layouts.md): Every project in one list, or one project at a time in tabs. - [Config explorer](https://shipyard.yahyapak.dev/docs/explorer.md): Pick a snippet and see what it changes, on a mock of the panel. - [Pings and notices](https://shipyard.yahyapak.dev/docs/pings.md): Your agents tell you when they need you, and when there's news. - [Notes in Notion](https://shipyard.yahyapak.dev/docs/notes.md): Your own notes, one Notion database per project, listed beside the pull requests. - [The agent skill](https://shipyard.yahyapak.dev/docs/skill.md): Ask in your own words. Your agent edits the file and checks it. - [Other machines](https://shipyard.yahyapak.dev/docs/machines.md): Agents on your other machines ping you too, through Herdr. - [Configuration reference](https://shipyard.yahyapak.dev/docs/configuration.md): Every key of config.toml, its default and what it takes. - [The shipyard command](https://shipyard.yahyapak.dev/docs/commands.md): One command for agents and scripts: pings, notices, checks and the app. - [Limits](https://shipyard.yahyapak.dev/docs/limits.md): What shipyard does not do. - [Troubleshooting](https://shipyard.yahyapak.dev/docs/troubleshooting.md): Short answers to what goes wrong most. - [Changelog](https://shipyard.yahyapak.dev/docs/changelog.md): What changed in each version, with its pull requests. - [Everything in one file](https://shipyard.yahyapak.dev/llms-full.txt) ## Links - Source: https://github.com/yahyabedirhan/shipyard - Releases: https://github.com/yahyabedirhan/shipyard/releases/latest - Changelog: https://github.com/yahyabedirhan/shipyard/blob/main/CHANGELOG.md - Schema: https://raw.githubusercontent.com/yahyabedirhan/shipyard/main/schema/config.schema.json --- # What it is 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. 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. Notes: - Clicking an item opens it on GitHub and clears its attention until it changes again. Closed items never need attention. - There is no settings window. The file is the whole interface for settings, and each save applies at once. --- # Install 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 --install` The download is `Shipyard-<version>-macos.zip` on the [latest release](https://github.com/yahyabedirhan/shipyard/releases/latest). It isn't notarized, so clear the quarantine flag before the first launch, as below. ### Download Get the zip from the [latest release](https://github.com/yahyabedirhan/shipyard/releases/latest). ```sh unzip Shipyard-<version>-macos.zip mv Shipyard.app /Applications/ xattr -dr com.apple.quarantine /Applications/Shipyard.app open /Applications/Shipyard.app ``` ### From source Builds, signs and installs the app, then opens it. ```sh git clone https://github.com/yahyabedirhan/shipyard.git cd shipyard make install ``` ### The agent skill ```sh 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. ```sh rm -rf /Applications/Shipyard.app ~/.config/shipyard ~/Library/Application\ Support/Shipyard ``` Notes: - `make install` builds, signs and copies `Shipyard.app` to `/Applications`, then opens it. To update, run `git pull && make install` again. - To update a download, replace the app with a newer zip. There is no automatic update. --- # First run 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](https://cli.github.com) if `gh` is signed in. 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 = false` 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. Notes: - Sign in with GitHub uses GitHub's device flow: the panel shows a code to enter on github.com, and keeps the token in your Keychain. - Every refresh spends a small share of your GitHub rate limit, 10% at most by default, since your agents share it. --- # Presets 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. ### `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 ```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 ```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 ```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" }, ] ``` Notes: - With the agent skill installed, an agent can start you from one too: "set shipyard up as a review queue". --- # Layouts 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. 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. ```toml [menu] layout = "tabs" ``` ```toml [defaults] group-by = "repository" subsections = true show-first = 4 ``` Notes: - The layout button is one of only three ways the app writes the file. The others are onboarding's preset and the project picker, which appends projects. - The All tab always groups by kind, newest first. Each project's own tab follows its configuration. --- # Config explorer 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. The page shows a mock panel for a few snippets. The keys they set are in [configuration.md](configuration.md). Notes: - The mock is drawn in HTML from these docs, not by the app. It follows the same rules for grouping, sorting and hiding, but it is not a screenshot. --- # Pings and notices 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. 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. ```sh 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 ``` ### `shipyard ping send` flags | Flag | What 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 `--` | ### `shipyard notice send` flags | Flag | What 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: - A ping leaves a day after you've seen it (`seen-window`), or when you dismiss it. Pings never reach GitHub. --- # Notes in Notion 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. Notes are opt-in. shipyard reads and writes them through [`ntn`](https://developers.notion.com/cli/get-started/overview), 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. ```sh curl -fsSL https://ntn.dev | bash ntn login ntn doctor ``` ```toml [[projects]] slug = "shop" repositories = ["my-org/shop"] notes = { show = false } ``` Notes: - The pencil creates the project's database the first time, then an empty note, and opens it for you to type or dictate. Any agent with the skill can take a note you dictate. - Once connected, notes are read every minute and when you open the menu. `shipyard notes check` reads the workspace the way the app does and says what's wrong. --- # The agent skill 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. 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. ```sh npx skills add yahyabedirhan/shipyard -g -y ``` ```sh shipyard config check taplo check ~/.config/shipyard/config.toml ``` Notes: - The Set up /shipyard skill view shows whether the skill is installed, and its **Update** runs the same command for the newest version. - After each save the app writes its verdict to `~/Library/Application Support/Shipyard/config-status.json`, so the agent knows whether the app took the edit. --- # Other machines Agents on your other machines ping you too, through Herdr. When your Mac's [Herdr](https://github.com/herdrdev/herdr) knows another machine as a saved machine, shipyard can list that machine's pings. Install the [herdr-shipyard](https://github.com/yahyabedirhan/herdr-shipyard) plugin on each machine, which puts the same `shipyard` command there, and name the machines in `[remote] machines` by their Herdr labels. 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. Notices travel the same way, or straight over your tailnet once `[notices] listen = true` and the machine's own `cli.toml` names your Mac. ```sh herdr plugin install yahyabedirhan/herdr-shipyard ``` ```toml [remote] machines = ["build-box"] ``` Notes: - shipyard reaches another machine only through Herdr's connection to it, never over SSH of its own, and the file names a label, never a host or an address. - A machine that doesn't answer shows a quiet line in the menu and keeps its last pings until it answers again. --- # Configuration reference 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. 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"` ```toml #: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. | Key | Default | Type | What it does | |---|---|---|---| | `version` | `1` | integer: 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-login` | `true` | boolean | Start shipyard when you log in. | ### `[menu-bar]` What the menu bar shows next to the icon. | Key | Default | Type | What it does | |---|---|---|---| | `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. | Key | Default | Type | What it does | |---|---|---|---| | `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. | Key | Default | Type | What it does | |---|---|---|---| | `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-percent` | `10` | integer: 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. | Key | Default | Type | What it does | |---|---|---|---| | `unseen` | `true` | boolean | An item you haven't clicked yet. | | `changed` | `true` | boolean | An item that changed (commits, comments, reviews, checks, review request) since you clicked it. | | `review-requested` | `true` | boolean | A pull request that requests your review. | | `checks-failed` | `true` | boolean | A pull request whose checks failed. | ### `[banners]` The banners above the projects in the panel. | Key | Default | Type | What it does | |---|---|---|---| | `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. | Key | Default | Type | What it does | |---|---|---|---| | `terminal` | unset | string | 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. | Key | Default | Type | What it does | |---|---|---|---| | `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. | Key | Default | Type | What it does | |---|---|---|---| | `listen` | `false` | boolean | Whether the app listens for notices from your other machines. | | `port` | `47420` | integer: 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. | Key | Default | Type | What it does | |---|---|---|---| | `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). | | `subsections` | unset | boolean | 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-first` | `0` | integer: 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-requests` | unset | table | Pull requests: open ones, then those closed within the closed window, newest first, from the authors authors allows. | | `issues` | unset | table | Issues: open ones, then those closed within the closed window, from the authors authors allows. | | `workflow-runs` | unset | table | GitHub Actions runs: running now, plus finished within the finished window. | | `pings` | unset | table | The pings agents send with the shipyard CLI (shipyard ping), each filed under a project. Pings take no states, authors, drafts or review-requested. | | `notes` | unset | table | 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. | | `notifications` | `5 rules: pr.opened, ping.sent, agent.notice, control.started, control.ended` | list of rules | 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. | | `archived` | `false` | boolean | Whether repository groups and owner/* bring in archived repositories. A repository named as owner/name is always listed. | | `forks` | `true` | boolean | 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. | Key | Default | Type | What it does | |---|---|---|---| | `show` | `true` | boolean | 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. | | `drafts` | `true` | boolean | List draft pull requests (in gray). | | `authors` | unset | 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'. | | `review-requested` | `false` | boolean | 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. | Key | Default | Type | What it does | |---|---|---|---| | `show` | `false` | boolean | 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. | | `authors` | unset | 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'. | ### `[defaults.workflow-runs]` (also workflow-runs = { … } in a project) GitHub Actions runs: running now, plus finished within the finished window. | Key | Default | Type | What it does | |---|---|---|---| | `show` | `false` | boolean | 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. | | `authors` | unset | 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'. | ### `[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. | Key | Default | Type | What it does | |---|---|---|---| | `show` | `true` | boolean | 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. | Key | Default | Type | What it does | |---|---|---|---| | `show` | `true` | boolean | 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'. | Key | Default | Type | What it does | |---|---|---|---| | `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) | Key | Default | Type | What it does | |---|---|---|---| | `event` (required) | unset | string: "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. | Key | Default | Type | What it does | |---|---|---|---| | `slug` | unset | string | 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. | | `title` | unset | string | The text the menu shows for the project: its section's and its tab's title. Unset: the slug. | | `repositories` (required) | unset | list | 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). | | `subsections` | unset | boolean | 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-first` | `0` | integer: 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. | | `archived` | `false` | boolean | Whether repository groups and owner/* bring in archived repositories. A repository named as owner/name is always listed. | | `forks` | `true` | boolean | Whether repository groups and owner/* bring in forks. A repository named as owner/name is always listed. | | `pull-requests` | unset | table | Pull requests: open ones, then those closed within the closed window, newest first, from the authors authors allows. | | `issues` | unset | table | Issues: open ones, then those closed within the closed window, from the authors authors allows. | | `workflow-runs` | unset | table | GitHub Actions runs: running now, plus finished within the finished window. | | `pings` | unset | table | The pings agents send with the shipyard CLI (shipyard ping), each filed under a project. Pings take no states, authors, drafts or review-requested. | | `notes` | unset | table | 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. | | `notifications` | unset | list of rules | 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. An unknown key there is an error. ### `Top level` Keys above the first table header. | Key | Default | Type | What it does | |---|---|---|---| | `version` | `1` | integer: 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. | Key | Default | Type | What it does | |---|---|---|---| | `app-machine` | unset | string | 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-port` | `47420` | integer: 1 to 65535 | The port tailscale serve exposes on the Mac. | Notes: - The file's first line, `#:schema`, names [the JSON schema](https://raw.githubusercontent.com/yahyabedirhan/shipyard/main/schema/config.schema.json) this table is made from, so editors that read it complete and check every key. - Unknown keys are ignored with a warning that names the nearest key shipyard reads. --- # The shipyard command 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. 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. ### Tell the user (any machine) | Command | What 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) | Command | What it does | |---|---| | `shipyard app open [--demo <folder>]` | launch the app, or a demo run, and wait until it answers | | `shipyard app quit` | quit 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 open` | open the menu bar icon's panel | | `shipyard panel close` | close 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 | Command | What it does | |---|---| | `shipyard config check [--json]` | check the settings files, each problem and warning with its line | | `shipyard notes check` | check that the app can read the notes workspace in Notion | ### Plumbing (run by Herdr's plugin hooks, not by hand) | Command | What it does | |---|---| | `shipyard herdr-event` | ping when a Herdr agent is blocked, and withdraw it when it goes on | ### Options and environment - `-h, --help`: show help; `shipyard <group> --help` for a group's commands - `--version`: print the version - `SHIPYARD_CONTROL_KEY`: the holder key app, panel and lease commands are sent as Exit codes: 0 success, 1 failed or refused, 2 wrong usage. Notes: - The app needn't be running for a ping: one sent meanwhile shows when it starts. - The `app`, `panel` and `lease` groups drive the app on the Mac, one agent at a time. The holder shows as a yellow dot on the menu bar icon and a banner on the panel. --- # Limits What shipyard does not do. - **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. - **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. --- # Troubleshooting 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. --- # Changelog What changed in each version, with its pull requests. Every release has an entry in [`CHANGELOG.md`](https://github.com/yahyabedirhan/shipyard/blob/main/CHANGELOG.md), newest first, and a download on [GitHub releases](https://github.com/yahyabedirhan/shipyard/releases). Ideas and bugs go to [the issues](https://github.com/yahyabedirhan/shipyard/issues). Latest release: 0.8.0 (2026-10-22). The panel has a new look and themes, your own theme files included, and the old `config.toml` forms are gone. GitHub's timeouts are tried again, the Set up Notion view finishes its check, and shipyard has a website. It gathers [#275](https://github.com/yahyabedirhan/shipyard/pull/275), [#276](https://github.com/yahyabedirhan/shipyard/pull/276), [#277](https://github.com/yahyabedirhan/shipyard/pull/277), [#279](https://github.com/yahyabedirhan/shipyard/pull/279), [#280](https://github.com/yahyabedirhan/shipyard/pull/280), [#281](https://github.com/yahyabedirhan/shipyard/pull/281), [#304](https://github.com/yahyabedirhan/shipyard/pull/304), [#305](https://github.com/yahyabedirhan/shipyard/pull/305), [#306](https://github.com/yahyabedirhan/shipyard/pull/306), [#307](https://github.com/yahyabedirhan/shipyard/pull/307), [#308](https://github.com/yahyabedirhan/shipyard/pull/308), [#309](https://github.com/yahyabedirhan/shipyard/pull/309), [#310](https://github.com/yahyabedirhan/shipyard/pull/310), [#311](https://github.com/yahyabedirhan/shipyard/pull/311), [#312](https://github.com/yahyabedirhan/shipyard/pull/312), [#314](https://github.com/yahyabedirhan/shipyard/pull/314), [#315](https://github.com/yahyabedirhan/shipyard/pull/315), [#317](https://github.com/yahyabedirhan/shipyard/pull/317), [#318](https://github.com/yahyabedirhan/shipyard/pull/318), [#319](https://github.com/yahyabedirhan/shipyard/pull/319) and [#320](https://github.com/yahyabedirhan/shipyard/pull/320).