# 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.
