关于 shipyard
shipyard skill 帮助 Agent 配置和操作 macOS 项目通知应用,配置文件涵盖 PR、问题、工作流运行及通知规则,也支持 ping、便笺和主题管理。
主要功能
- 01通过 TOML 配置项目与通知规则
- 02发送可点击的 Agent ping 和通知
- 03驱动应用并查看屏幕截图
- 04管理 Notion 项目便笺
- 05配置面板主题
适用场景
- 01筛选并查看多个项目的 PR
- 02接收 Agent 状态消息并跳转
- 03整理项目便笺或调整面板外观
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ yahyabedirhan/ shipyard --skill shipyard安装后在 Claude Code 中输入/shipyard确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
presets.mdreferences/demo-items.mdnotes.mdnotices.mdthemes.md详细介绍
shipyard 配置
Shipyard 会在一个 TOML 文件中列出各个项目的拉取请求(以及启用后显示的问题和工作流运行记录),并针对其通知规则所选的事件发送 macOS 通知。该文件是设置的全部接口:没有设置窗口,也没有命令可以修改它。每次保存后,应用都会实时应用更改。
Agent 也会通过 shipyard ping 命令向用户发送 ping:简短消息,用户点击后会跳转到 Agent 指定的位置。请参阅配置之后的“发送 ping”。对于无需用户操作的状态更新,Agent 会改用 shipyard notice send 发布 notice:这是一条不会保留的通知。请参阅“Ping 还是 notice?”。
在 Mac 上,Agent 还可以通过 shipyard app 和 panel 打开、操控正在运行的应用并截取屏幕,使用用户的数据或演示数据,每次仅限一个 Agent 操作。请参阅文末的“操控应用”。
用户自己的笔记保存在 Notion 中,每个项目对应一个带编号的列表,菜单会列出尚未关闭的笔记。在记录用户口述的笔记、按编号查找笔记,或列出、整理或归档笔记之前,请阅读 references/notes.md:其中介绍了笔记的存放位置、所有 Agent 都必须遵循的规则、通过 Notion 连接器或 ntn 执行的各项操作,以及用于检查应用能否读取笔记的 shipyard notes check。
面板的外观就是它的主题:顶层 theme 键用于选择内置主题或用户的主题文件。
在选择主题、创建或修复主题文件,或解答面板为何以某种方式绘制 Shipyard 之前,请阅读 references/themes.md:其中介绍了内置主题、包含所有令牌、状态颜色和旋钮的主题文件格式、对比度检查,以及 shipyard config check 如何报告主题文件。
用户要求更改其 shipyard 时,就是要求修改此文件。如果以下键都无法实现所需更改,请说明 shipyard 没有此设置。
此技能通过 npx -y skills add yahyabedirhan/shipyard -g -y 安装。安装后,应用设置菜单中的齿轮会在 设置 /shipyard 技能旁显示勾选标记;该视图中的更新会运行同一命令以获取最新版本。用户要求获取最新技能时,请指引他们前往该处,或运行此命令。
文件
- 路径:如果
XDG_CONFIG_HOME已设置为绝对路径,则为$XDG_CONFIG_HOME/shipyard/config.toml;否则为~/.config/shipyard/config.toml。 - 所有键均为可选项。文件缺失或为空时,会使用不含任何项目的默认设置(随后应用会显示项目选择器)。
- 应用仅会通过以下三种方式自行写入文件:初始设置会写入整套预设(见下文),但仅限文件缺失或除
version外没有任何有效内容时;项目选择器会追加[[projects]]区块;菜单标题栏中的布局按钮会在[menu]下将layout依次设为下一个布局(列表,然后是标签页,再回到列表),并保留其他所有行。因此每次编辑前都要重新读取文件。 - 键采用 kebab-case。该文件第一行以
#:schema <url>命名的架构为https://raw.githubusercontent.com/yahyabedirhan/shipyard/main/schema/config.schema.json。
从预设开始
Shipyard 提供 3 个预设,即针对主要用途准备好的文件;设置应用时会显示这些预设,presets.md 中包含每个预设的完整文件:
my-agents:用户及其 Agent 创建的拉取请求和议题,每个仓库对应一个项目。incoming-contributions:其他人在用户仓库中创建的内容,隐藏机器人;此外,还包括所有仓库中等待用户审核的拉取请求。review-queue:仅包括所有仓库中等待用户审核的拉取请求。
如果用户指定了某个预设,或要求针对上述某种用途设置 Shipyard(或重新开始设置),且文件不存在或除了 version 外没有任何生效内容,则从该预设开始:按照 presets.md 所述,用用户的仓库写入预设文件。如果文件已经包含设置或项目,不要替换它:使用下方的键按请求进行修改。
编辑文件
-
阅读整个文件:用户会在其中写注释,也会手动编辑。只修改或添加请求所需的行,保留每条注释、空行和现有顺序。
-
将新增内容放在 TOML 能正确读取的位置:
- 顶层键(
refresh-interval、launch-at-login等)放在第一个[table]标头之前:放在标头之后时,TOML 会将其视为该表的键。 - 将
version = 1保留在顶部。不设置此键的文件仍可读取,但会显示警告;后续版本将拒绝读取。 - 表(
[menu]、[menu-bar]、[rate-limit]、[attention]、[defaults.issues]等)放在第一个[[projects]]块之前,且每个表只写一次:如果表已存在,就向其中添加键。重复书写表标头会导致 TOML 无效。 - 新项目应作为
[[projects]]块追加到文件末尾。 - 项目覆盖项(
pull-requests、issues、workflow-runs、pings、notes、notifications)应作为内联表放在各自的项目块中,以确保每个块保持独立完整。 - 内联表
{ … }应保持在一行内;数组[ … ]可以跨行。 - 应用创建的文件会以一个标头开头,其中包含以注释形式写出的常用设置及其默认值:
theme、[defaults.pull-requests]下的authors、[menu]下的layout、[menu-bar]下的count、[defaults]下的group-by、sort-by和show-first、[defaults.issues]下的show和states、[defaults.workflow-runs]下的show、[defaults.pings]下的show和seen-window、refresh-interval、5 条默认的[[defaults.notifications]]规则(pr.opened、ping.sent、agent.notice、control.started、control.ended),以及[rate-limit]下的max-share-percent。要设置其中某项,请取消其所在行(包含其键的表行)的注释,并修改值,而不是再添加一份。标头将顶层键放在各表之前,因此可以取消其中任何一项的注释;如果文件后来经过编辑,请检查你取消注释的表行下方没有顶层键,否则该键会被归入表中。
- 顶层键(
-
应用启动时,或在没有此文件时点击 Refresh 按钮,都会创建带有该标头的文件。如果文件仍不存在,请创建它(以及其所在目录),并以以下内容开头:
#:schema https://raw.githubusercontent.com/yahyabedirhan/shipyard/main/schema/config.schema.json version = 1 -
每次编辑后运行
shipyard config check,然后检查其余项目(请参阅“检查编辑”)。当shipyard config check以状态码 0 退出、taplo check以状态码 0 退出,且应用记录中你的保存显示"accepted": true时,编辑才算完成。
键和默认值
顶层:
| 键 | 默认值 | 允许值 |
|---|---|---|
version | 1 | 仅限 1 |
theme | "System" | 面板的主题名称,不区分大小写:System(遵循 macOS 外观和强调色)、Shipyard Light、Shipyard Dark、Paper Light、Paper Dark、Harbor、Heather、Brass、Ocean、Sunset,或与 config.toml 同目录下 themes/*.toml 中某个主题文件同名的名称;未知名称会报错,并列出所有主题。齿轮菜单的 Theme 子菜单也可设置此项。主题文件:references/themes.md |
refresh-interval | "2m" | 一个时间段(见下文),至少为 "30s":应用刷新频率;为不超过其速率限制份额而延长的最短间隔 |
launch-at-login | true | 布尔值 |
表格:
| 键 | 默认值 | 允许值 |
|---|---|---|
[menu-bar] count | "total" | "total"、"per-kind"(PR、issue、运行和 ping 分别计数)、"none" |
[menu] layout | "list" | "list"(所有项目显示在一个可滚动列表中,每项占一行)、"tabs"(一次显示一个项目) |
[menu] header-counts | 前五项 | 由 "pull-requests"、"issues"、"workflow-runs"、"pings"、"notes" 组成的列表,每项各出现一次:列表布局中每个项目标题显示计数的类型。项目标题会按固定顺序显示每种列出的类型的计数(仅当该项目包含此类型的条目时显示),与列表中的顺序无关。仅限全局设置:项目不能设置此项 |
[rate-limit] show | "always" | "always"、"when-low"(低于 25% 时)、"never" |
[rate-limit] max-share-percent | 10 | 1–50:shipyard 每小时可使用的各项 GitHub 限制额度份额(与用户的 Agent 共享) |
[attention] unseen | true | 尚未点击的条目需要关注 |
[attention] changed | true | 自上次点击后发生变化的条目需要关注 |
[attention] review-requested | true | 请求用户(或其所属团队)审查的 PR 需要关注 |
[attention] checks-failed | true | 检查失败的 PR(或运行)需要关注;即使已查看,只要检查尚未通过,PR 的关注圆点就会在 signal.attention-failed 中保持红色 |
[herdr] terminal | 未设置 | Herdr 运行所在的终端应用,可使用名称("Ghostty")或 bundle id("com.mitchellh.ghostty"):点击通过 --herdr 发送的 ping 时,会聚焦 Herdr 标签页,然后将此应用切到前台。未设置时,如果知道 ping 是从哪个终端发送的,则将该终端切到前台;否则只聚焦标签页 |
[remote] machines | [] | 你的其他机器,使用 Herdr 为其保存机器时所用的标签(["beta-vps"]),不能是主机名或地址:shipyard 会通过 Herdr 向每台机器请求其 Agent 的 ping,并在项目之后以机器名称为标题列出。标签不能为空、不能以 - 开头、不能包含控制字符、不能重复,也不能与项目的 slug 或标题相同 |
[notices] listen | false | 应用是否直接通过 tailnet 接收其他机器上 Agent 发来的通知(shipyard notice send)。只有设为 true 时才会监听;此时应用仅监听 127.0.0.1,并且只接收通过此 Mac 自己的 Tailscale 登录发送的通知。设置方法:references/notices.md |
[notices] port | 47420 | 1 到 65535:应用在 127.0.0.1 上监听的端口,tailscale serve 会将其公开到你的 tailnet |
[banners] snooze-duration | "1h" | 一个时间段(见下文),必须大于 0:点击项目上方横幅中的 ✕ 后,横幅保持隐藏的时间。此时间过后,如果其条件仍然成立,横幅会再次显示;如果条件先消失再重新成立,横幅会立即显示。较短的时间段(例如 "10s")便于快速确认横幅会重新出现 |
每个项目显示的内容由 [defaults.pull-requests]、[defaults.issues]、[defaults.workflow-runs]、[defaults.pings]、[defaults.notes](以及 [[defaults.notifications]])设置;项目也可在自己的配置块中覆盖这些设置:
| 键 | 默认值 | 允许值 |
|---|---|---|
pull-requests.show | true | 布尔值 |
pull-requests.states | ["open", "merged", "closed"] | 要列出的 PR:可选 "open"(也包括草稿)、"merged"、"closed"(未合并即关闭) |
pull-requests.closed-window | "7d" | 时间窗口(见下文):已关闭和已合并的 PR 保留在列表中的时长;"0" 表示隐藏 |
pull-requests.drafts | true | 布尔值;列出草稿 PR |
pull-requests.authors | { show = [], hide = [] } | 要列出谁的 PR:authors.show 中的作者减去 authors.hide 中的作者,每项都是作者选择器列表(见“谁的项目”) |
pull-requests.review-requested | false | 布尔值;true 表示只列出等待用户审核的开放 PR,包括请求用户本人或其所属团队审核的 PR |
issues.show | false | 布尔值 |
issues.states | ["open", "closed"] | 要列出的问题:可选 "open"、"closed" |
issues.closed-window | "7d" | 时间窗口:已关闭的问题保留在列表中的时长 |
issues.authors | { show = [], hide = [] } | 要列出谁的问题,规则同拉取请求 |
workflow-runs.show | false | 布尔值 |
workflow-runs.states | ["in-progress", "failed", "succeeded"] | 要列出的运行:可选 "in-progress"(排队中或运行中)、"failed"(也包括超时和启动失败)、"succeeded" |
workflow-runs.finished-window | "3h" | 时间窗口:已完成的运行保留在列表中的时长(运行中的始终保留) |
workflow-runs.branches | "default-and-pull-requests" | 或 "all" |
workflow-runs.authors | { show = [], hide = [] } | 要列出谁的运行(运行的作者是启动它的账户) |
pings.show | true | 布尔值;列出 Agent 使用 shipyard ping 发送的 ping,显示在监视其代码库的项目下,或显示在其指定的项目下。Ping 不支持 states、authors、drafts 或 review-requested:设置其中任何一项都会报错 |
pings.seen-window | "24h" | 时间窗口:已读 ping 保留在列表中的时长,从标记为已读时开始计算;未读 ping 会一直保留,直到被标记为已读。窗口期满后,已读 ping 会在一次刷新内移出列表并被删除 |
notes.show | true | 布尔值;在“Notes”分组中按时间倒序列出用户在 Notion 中尚未关闭的笔记:笔记位于项目数据库的“Shipyard Notes”页面下的“Projects”页面中,并按项目的精确标题匹配。每行显示笔记编号及其前缀(SHOP-7)、标题(或正文首行)和标签;点击笔记会在 Notion 中打开,点击标题栏中的笔记数量会打开项目数据库。项目标题栏中有一个新建笔记图标,可在项目数据库中创建空笔记(如果数据库不存在,则先创建数据库和 Projects 页面),并在 Notion 中打开。笔记无需处理,因此不会计入任何数量。没有数据库的项目不会列出笔记。应用每分钟以及菜单打开时,通过 Notion 的命令行工具 ntn 读取和写入笔记,并使用 ntn 自己的登录信息;应用不会保存自己的 Notion 令牌。在用户点击设置菜单中“Set up Notion”视图里的 Connect with ntn 之前,不会列出任何笔记,也不会运行 ntn。连接后,如果未安装 ntn、未登录,或 ntn 的默认工作区中没有 Shipyard Notes 页面,则不会列出笔记,并会显示横幅说明后续操作;在安装并登录 ntn 之前,新建笔记图标会隐藏(见笔记参考中的“Setting up ntn”)。笔记只接受 show:设置 states、authors、drafts、review-requested 或 seen-window 都会报错 |
notifications | 五条规则:pr.opened、ping.sent、agent.notice、control.started 和 control.ended,每条规则的 authors = [] | 规则列表(见下文) |
A 个 窗口是一个字符串:一个整数和一个单位,s、m、h 或 d,例如 "45s"、"30m"、"12h" 或 "7d";"0" 会立即隐藏已关闭(或已完成)的项目。不支持小数、负数、空格或两个单位:写成 "90m",不要写成 "1.5h" 或 "1h30m"。格式错误的值会被拒绝,并提示所在行和最接近的正确写法:"closed-window must be a whole number and one unit, s, m, h or d, such as "30m" (got "30min"; did you mean "30m"?)"。窗口时间一到,项目会在一次刷新内消失,无需点击。 |
如何在菜单中分组和排序每个项目的项目,应直接在 [defaults] 下设置(不要放在子表中);项目也可以在自己的区块中覆盖这些设置:
| 键 | 默认值 | 允许的值 |
|---|---|---|
[defaults] group-by | "kind" | "kind"(拉取请求,然后是 ping、问题、运行记录,最后是备注)、"repository"(A 到 Z)、"date"(今天、昨天、本周、本月、更早)、"author"(A 到 Z;按发送者对 ping 排序,然后将未注明发送者的 ping 归为 "Pings")、"none"(单个列表);仅支持一级分组 |
[defaults] subsections | 未设置 | 布尔值:true 会将每个分组显示在子标题下(标题为分组名称和数量),false 会在分组前显示分隔线;未设置时保留各布局自身的样式(列表中使用分隔线,标签页中使用子标题) |
[defaults] sort-by | "updated" | "updated"(最新优先)、"created"(最新优先)、"title"(A 到 Z);未关闭或正在运行的项目始终排在前面。"date" 按此日期分组(按标题排序时使用 "updated") |
[defaults] show-first | 0 | 大于或等于 0 的整数:每个分组显示前 N 行,并显示一行 "Show N more";选择后会显示其余内容,然后显示 "Show less";0 表示显示所有行。使用 group-by = "none" 时,会限制整个项目显示的行数。分组数量包含隐藏的行,菜单关闭后会恢复所有限制 |
标签页布局中的 All 标签页始终按类型分组,并按最新优先排序。
仓库分组和 owner/* 会包含哪些内容(参见 Which repositories),可在 [defaults] 和项目区块中设置:
| 键 | 默认值 | 允许的值 |
|---|---|---|
[defaults] archived | false | 布尔值;true 也会包含已归档的仓库 |
[defaults] forks | true | 布尔值;false 会排除 fork 仓库 |
每个项目对应一个 [[projects]] 区块,并按文件中的顺序显示为各个分区:
| 键 | 必填 | 允许的值 |
|---|---|---|
slug | 是 | 项目的 ID,在所有项目中必须唯一:小写字母和数字以单个连字符连接,例如 "e-commerce"。--project、panel fold 和 panel show-tab 使用此 ID |
title | 否 | 菜单中显示的项目名称,也是其分区和标签页的标题,例如 "E-commerce";未设置时使用 slug |
repositories | 是 | 至少一个仓库选择器:owner/name、owner/*、owned、organizations、collaborator 或 anywhere(参见 Which repositories);不能使用 URL |
pull-requests、issues、workflow-runs、pings、notes | 否 | 使用上述键的内联表 |
group-by、subsections、sort-by、show-first | 否 | 与上文 [defaults] 中的设置相同 |
archived、forks | 否 | 布尔值,可覆盖此项目的 [defaults] 设置 |
notifications | 否 | 规则列表 |
覆盖设置
- 项目的
pull-requests、issues、workflow-runs、pings和notes表会按键合并到[defaults.*]:issues = { show = true }会显示问题,并保留默认的closed-window。像states这样的列表是一个键:项目列表会替换默认列表。authors也按键合并:项目的authors = { hide = [...] }会替换默认的hide,并保留默认的show。 - 项目的
notifications会替换该项目的默认列表,而不是追加。若要保留任何默认规则,请重复写出。notifications = []表示该项目不发送通知。 [[defaults.notifications]]块同样会替换内置默认规则(来自所有人的pr.opened、ping.sent、agent.notice、control.started和control.ended):文件中一旦有该块,若要保留这些规则,也请一并写出。文件若只列出自定义规则而没有ping.sent,则不会收到 ping 通知;添加{ event = "ping.sent" }即可收到通知。某个项目的规则若未包含agent.notice,则会拒绝 Agent 的通知,因此在该项目中运行shipyard notice send会以状态码 1 退出;添加{ event = "agent.notice" }即可查看通知。同样,若没有control.started和control.ended,则不会收到 Agent 开始或停止使用应用时的通知。
选择哪些仓库:仓库选择器
项目的 repositories 列出仓库选择器,可以混合使用。语法与作者选择器(见下文)相同:裸词表示一个分组,/ 表示仓库。
| 选择器 | 监视对象 |
|---|---|
owner/name | 一个仓库:所有者由字母、数字和 - 组成;名称还可以包含 _ 和 . |
owner/* | 用户或组织拥有的所有仓库,例如 my-org/* |
owned | 登录账户拥有的所有仓库 |
organizations | 账户通过其所属组织(直接加入或通过团队加入)可访问的所有仓库 |
collaborator | 他人将该账户添加为协作者的仓库 |
anywhere | 在任何仓库中等待用户审核的开放拉取请求,即使用户从未向该仓库提交过代码 |
owned、organizations和collaborator是 GitHub 自身的归属关系:它们互不重叠,合起来涵盖账户可访问的所有仓库。没有me/*:请写owned。- 应用启动后、每次编辑后、按下 ⌘R 时,以及其他情况下大约每小时,都会查找分组和通配符,因此稍后创建的仓库也会显示,无需编辑文件。仓库中已有的项目会静默列出;只有之后发生的事件才会触发通知。
- 默认不包含已归档的仓库,并保留 fork,除非
archived = true或forks = false另有规定。以owner/name指定的仓库始终会列出,无论是否已归档。 - 如果两个选择器都选中了同一仓库,该仓库只会列出一次。
- 如果
owner/*中的所有者不存在,或账户无法查看该所有者,则项目中会显示错误行;没有仓库的分组会显示“没有未关闭项目”。 repositories中不接受作者分组(me、others、bots)或@login,并会显示提示:若要监视某人的仓库,请写owner/*。anywhere来自一次 GitHub 搜索,而不是仓库列表,因此只有在项目或其默认设置中列出review-requested = true的拉取请求,且不显示问题或运行时,才能使用它。其他情况均不接受:“anywhere需要pull-requests = { review-requested = true },并且不得列出问题或运行记录”。项目的其他筛选条件(authors、states、drafts)照常生效。搜索最多返回 100 个拉取请求;如果等待审核的数量更多,项目会说明这一点。
按状态筛选项目
每种类型的 states 都会根据项目所处的状态筛选项目;未纳入的项目不会显示、计数或触发通知。已关闭项目的时间窗口仍然适用:states 指定筛选哪些项目,closed-window(或 finished-window)指定已关闭、已合并或已完成的项目保留多久。因此,pull-requests = { states = ["open"] } 不会列出任何已合并或已关闭的 PR,而 states = ["merged"] 会列出在 closed-window 内合并的 PR。如果指定了该类型不接受的状态,就会被拒绝,并提示最接近的有效状态:“unknown pull request state merge (did you mean merged?)”。states = [] 不会列出该类型的任何项目;若要隐藏某种类型,使用 show = false 会更直观。
按作者筛选项目:作者选择器
每种类型的 authors = { show = [...], hide = [...] } 用于决定项目会列出哪些作者的项目。当项目作者与 show 匹配(show 为空时表示所有人),且不与 hide 匹配时,该项目就会列出;两者都匹配时,以 hide 为准。可以在 [defaults.pull-requests]、[defaults.issues] 或 [defaults.workflow-runs] 中为每个项目设置,也可以在单个项目的配置块中设置。拉取请求、问题和运行记录各自独立设置;若要在所有地方隐藏某人,就必须在项目显示的每种类型中都将其隐藏。
项目的筛选条件排除的内容会从 shipyard 中彻底消失:它不会显示,也不会计入菜单栏、项目标题或标签页,更不会触发通知。系统仍会记住每个已获取的项目,因此后续修改设置使某个项目显示时,不会将其作为新项目发送通知。
show 和 hide 的条目,以及通知规则中的 authors,都属于作者选择器。语法很简单:不带前缀的词表示一组作者,@ 表示某个人,/ 表示一个仓库。
| 选择器 | 包含范围 |
|---|---|
me | 当前登录用户,包括以其身份工作的 Agent |
others | 除用户和机器人之外的所有人 |
bots | GitHub Bot 账号或登录名以 [bot] 结尾的账号 |
@login | 单个账号,例如 @octocat 或 @dependabot[bot](保留 [bot] 后缀);匹配时不区分大小写 |
me、others 和 bots 合起来涵盖每位作者,且各只涵盖一次。登录名始终要加上 @:不属于任何一组的不带前缀词会被拒绝,并附带提示:“unknown author bots2 (did you mean bots or @bots2?)”。在 authors 中,仓库(owner/name)或仓库组也会被拒绝:仓库应填写在项目的 repositories 中。
通知规则
规则是 notifications 列表中的一个元素,格式为 { event = "…", authors = [...] };authors 是作者选择器列表,默认为 [],即所有作者。当项目列表中的规则指定了某个事件,且其中一个 authors 与条目的作者匹配时,就会发送该事件的通知。规则只会进一步筛选项目列表中的内容:项目的筛选条件(states、authors、drafts 或值为 0 的关闭时间窗口)排除的条目,无论规则如何设置,都不会发送通知;因此,pr.merged 需要在 states 中包含 merged,且 closed-window 大于 "0";run.failed 需要在 states 中包含 failed,且 finished-window 大于 "0"。每个事件只会通知一次;添加项目时,项目中已有的条目不会触发通知。
| 事件 | 条件 |
|---|---|
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 | 工作流运行成功结束 |
control.started | Agent 开始使用 shipyard 应用(shipyard app、panel 或 lease)时发送,标题为“Claude Code is using shipyard”,副标题说明其运行位置。此事件仅由 [[defaults.notifications]](或内置默认值)决定:项目自己的列表和规则中的 authors 均不适用,应用会对此发出警告。点击通知会打开面板,面板横幅会显示 Agent 名称 |
control.ended | Agent 已结束使用应用时发送,标题为“Claude Code is done with shipyard”,副标题说明原因(已释放、租约到期或你已停止它)。其决定方式与 control.started 相同 |
ping.sent | Agent 发送新 ping 时发送:标题为项目名称和 ping 标题,副标题为其正文和发送者。每个 ping 只通知一次;点击通知的效果与点击 ping 相同:执行其操作(打开链接或应用,或切换到其 Herdr 标签页)并将其标记为已读。Ping 没有作者,因此带有 authors 的规则不会选中它 |
agent.notice | Agent 使用 shipyard notice send 发布通知时发送:标题为项目名称和通知标题,副标题为其正文和发送者。每条通知都会单独发送通知,除非使用 --id 替换已有通知;通知不会列出或保留。若项目规则排除该通知,项目会拒绝该通知,并告知 Agent。通知没有作者,因此带有 authors 的规则不会选中它 |
问题事件要求项目显示问题,运行事件要求项目显示工作流运行。
检查编辑
命令。 每次编辑后运行 shipyard config check。它会像应用一样读取文件,无需启动应用;如果该命令有自己的 cli.toml,也会读取该文件:
shipyard config check; echo "config check exit status: $?"
/Users/me/.config/shipyard/config.toml: rejected
problem: config.toml line 14: unknown event `pr.openned` (did you mean `pr.opened`?)
/Users/me/.config/shipyard/cli.toml: accepted (no file: the defaults)
themes 文件夹中的每个主题文件都会得到单独的判定,显示在 config.toml 和 cli.toml 的判定之间;问题会标记为 themes/<file> line N: …。
退出状态为 0 表示所有文件都通过:仍要修复每一条 warning,通常是键名拼写错误。退出状态为 1 表示某个文件存在 problem:修复对应行上的每一项,然后再次运行命令。shipyard config check --json 会在各文件名(config.toml、themes/<file>、cli.toml)下,使用与下方应用记录相同的字段打印各文件的检查结果,方便你进行比较。它检查的是磁盘上的文件,而不是应用实际运行的配置:应用记录会说明应用是否采用了你保存的更改。
架构。 对文件运行 Taplo 并打印其退出状态;它会通过 #:schema 行查找架构:
taplo check ~/.config/shipyard/config.toml; echo "taplo exit status: $?"
退出状态为 0 表示通过;其他状态均表示失败,上方的行会说明原因。不要将输出通过 tail、grep 或 head 处理:成功时,Taplo 只会打印一行 INFO 信息,而管道会隐藏退出状态。如果尚未安装 Taplo,可用相同方式运行 npx -y @taplo/cli check <file>,或运行 brew install taplo。如果文件没有 #:schema 行,或无法获取其架构 URL,请使用上方的架构 URL 作为 --schema <url> 参数(本地副本可使用 file://<absolute path>)。架构对未知键的要求比应用更严格:它会拒绝应用只会发出警告并忽略的内容,因此这些问题也要修复。
架构无法检查以下三条规则;shipyard config check 和应用记录可以捕获这些问题,也可以直接阅读文件进行检查:
- 每个项目的
slug只能使用一次。 - 每个项目列出的仓库、通配符和组都只能出现一次,且不区分大小写(
owner/name和Owner/Name是同一个仓库)。 - 顶层键必须位于第一个
[table]标题之前。
应用。 Shipyard 会在每次保存后片刻重新读取文件,并将判定写入 ~/Library/Application Support/Shipyard/config-status.json。保存后等待一秒,然后读取记录和你所编辑文件的修改时间(如果设置了 $XDG_CONFIG_HOME,则在该目录下读取):
cat ~/Library/Application\ Support/Shipyard/config-status.json
date -u -r ~/.config/shipyard/config.toml +%Y-%m-%dT%H:%M:%SZ
{
"accepted" : false,
"checked" : "2026-09-25T12:05:01Z",
"config" : "/Users/me/.config/shipyard/config.toml",
"configModified" : "2026-09-25T12:05:00Z",
"problems" : [
{
"banner" : "config.toml line 14: unknown event `pr.openned` (did you mean `pr.opened`?)",
"line" : 14,
"message" : "unknown event `pr.openned` (did you mean `pr.opened`?)"
}
],
"version" : 1,
"warnings" : []
}
- 这是针对你刚才保存的更改吗? 只有当
configModified与date命令的输出相同(两者均为 UTC,精确到秒),且config是你所编辑的文件时,才是针对这次更改。如果记录时间更早,说明应用尚未重新读取:稍等片刻后再次读取。如果记录始终未更新,说明 shipyard 未在运行;应如实说明,而不是声称应用已采用更改。 "accepted": true:应用已采用更改。warnings会列出被忽略的设置,并标明各自的line:通常是键名拼写错误,请修复。"accepted": false:应用拒绝了该文件,并继续使用最后一份有效配置运行。修复problems中line所指行的所有问题(无法定位时为null),保存后再次读取记录。banner与用户在面板横幅中看到的内容相同,结尾是 "Using the last valid configuration."。
此记录还能捕获只有应用会检查的内容,包括上述三条规则。
请求示例
“在 shipyard 中监视这个仓库。” 从仓库的 origin 远程地址中提取 owner/name slug。如果已有项目列出了该仓库,则告知用户并停止。否则,追加一个配置块。其 slug 取自仓库名称,即 / 后面的部分:转换为小写,并将每段连续的其他字符替换为一个连字符(My_Shop → my-shop)。如果另一个项目已使用该 slug,则以完整的 owner/name 按相同规则生成 slug(yahyabedirhan-shipyard)。如果用户为项目指定了名称,则改用该名称生成 slug,并将该名称设为 title,除非名称已与 slug 相同("My Shop" → slug = "my-shop",title = "My Shop"):
[[projects]]
slug = "shipyard"
repositories = ["yahyabedirhan/shipyard"]
“将这些仓库归为一个项目。” 用一个代码块列出所有仓库。如果其中一些已经是项目,就将它们的仓库(以及应应用于该分组的任何覆盖设置)移入一个代码块,并删除因此变空的代码块;保留仍然适用的注释。
[[projects]]
slug = "e-commerce"
repositories = ["yahyabedirhan/e-commerce-frontend", "yahyabedirhan/e-commerce-backend"]
“查看我的所有仓库。” 创建一个包含 owned 分组的项目;如果用户还指其组织的仓库,也添加 organizations:
[[projects]]
slug = "mine"
repositories = ["owned"]
“查看 my-org 中的所有内容,以及我自己的 shipyard。” 在单个仓库旁添加通配符;设置 archived = true 还会包含该组织已归档的仓库:
[[projects]]
slug = "my-org"
repositories = ["my-org/*", "yahyabedirhan/shipyard"]
“当其他人提交 PR 时通知我。” 为该项目设置单独的列表(它会替代默认设置;请参阅“覆盖设置”):
[[projects]]
slug = "shipyard"
repositories = ["yahyabedirhan/shipyard"]
notifications = [
{ event = "pr.opened", authors = ["others"] },
]
若要应用于每个项目,请改为编写 [[defaults.notifications]] 代码块。
“隐藏 dependabot。” 在拉取请求的默认设置中添加 hide(如果列表已存在,就添加到该列表中;如果项目会显示议题,也在 [defaults.issues] 中添加相同设置)。按照 GitHub 显示的格式填写登录名,包括 @ 和 [bot] 后缀。若要隐藏所有机器人,请改为填写 bots。
[defaults.pull-requests]
authors = { hide = ["@dependabot[bot]"] }
“保留已合并和已关闭的项目 30 分钟。” 在每种类型的默认设置中设置 closed-window(运行则设置 finished-window),也可以在某个项目的代码块中设置,让它仅适用于该项目。
[defaults.pull-requests]
closed-window = "30m"
[defaults.issues]
closed-window = "30m"
“在此项目中只显示其他人提交的内容。” 在项目的代码块中,为其显示的每种类型隐藏 me 和 bots:
[[projects]]
slug = "shipyard"
repositories = ["yahyabedirhan/shipyard"]
pull-requests = { authors = { hide = ["me", "bots"] } }
issues = { show = true, authors = { hide = ["me", "bots"] } }
如果只想显示某个账号的内容(例如依赖更新项目),请改用 show:pull-requests = { authors = { show = ["@dependabot[bot]"] } }。
“在此项目中只显示等待我审核的 PR。” 在项目的拉取请求设置中设置 review-requested。用户团队收到的审核请求也会计入。此设置不会改变项目的议题和运行;若要创建纯审核队列,也请关闭它们:
[[projects]]
slug = "shipyard-reviews"
title = "shipyard reviews"
repositories = ["yahyabedirhan/shipyard"]
pull-requests = { review-requested = true }
“无论 PR 在哪里,都显示所有等待我审核的 PR。” 创建一个包含 anywhere 分组的项目;其中只能列出等待用户审核的 PR。按仓库分组可让大量仓库保持清晰易读,而 pr.review_requested 会在收到每个新请求时发送通知:
[[projects]]
slug = "review-queue"
title = "Review queue"
repositories = ["anywhere"]
pull-requests = { review-requested = true }
group-by = "repository"
subsections = true
notifications = [
{ event = "pr.review_requested" },
]
“按仓库对这个项目分组,并为每个仓库添加标题。” 在项目的代码块中设置 group-by 和 subsections(若要应用于每个项目,则在 [defaults] 中设置):
[[projects]]
slug = "contributions"
repositories = ["yahyabedirhan/shipyard", "yahyabedirhan/skills"]
group-by = "repository"
subsections = true
若要将最近打开的项目排在最前面,请添加 sort-by = "created";若要将今天有变动的项目置顶,请设置 group-by = "date"。
“让这个项目简短一些:每组显示几项,其余内容可展开查看。” 在项目的代码块中设置 show-first(若要应用于每个项目,则在 [defaults] 中设置)。每组会显示指定数量的行,并附带一个“再显示 N 项”行,点击后即可显示其余内容,直到菜单关闭:
[[projects]]
slug = "contributions"
repositories = ["yahyabedirhan/shipyard", "yahyabedirhan/skills"]
show-first = 5
“这里只显示未关闭的 PR 和议题。” 在项目的代码块中为每种类型设置 states:
[[projects]]
slug = "shipyard"
repositories = ["yahyabedirhan/shipyard"]
pull-requests = { states = ["open"] }
issues = { show = true, states = ["open"] }
运行的设置方式相同:workflow-runs = { show = true, states = ["failed", "in-progress"] } 会隐藏已成功的运行。若要应用于每个项目,请在 [defaults.pull-requests](或 [defaults.issues]、[defaults.workflow-runs])中设置 states。
“将菜单切换为标签页。” 在 [menu] 表中添加一个键;如果放置规则允许,请取消注释标头中的 # [menu] 行。菜单标头中的布局按钮也可以一键完成相同修改。
[menu]
layout = "tabs"
“使用 Ocean 主题。” 在第一个表之前设置一个顶层键;如果标头中有 # theme 行,请取消注释。菜单齿轮中的“主题”子菜单也可以一键完成相同修改。
若要使用用户自定义的主题(例如“改成橙色”),请先按照 references/themes.md 的说明编写主题文件,然后在此处填写其 name。
theme = "Ocean"
“在此项目中显示议题。” 在项目的代码块中添加 issues 覆盖设置:
[[projects]]
slug = "e-commerce"
repositories = ["yahyabedirhan/e-commerce-frontend", "yahyabedirhan/e-commerce-backend"]
issues = { show = true }
若要在每个项目中都显示议题,请改为在默认设置中设置一次:
[defaults.issues]
show = true
“显示此项目的 CI 运行情况,并在失败时告诉我。” 添加 workflow-runs 覆盖规则,并在通知列表中将默认规则 pr.opened、ping.sent 和 agent.notice 与 run.failed 一起保留:
[[projects]]
slug = "shipyard"
repositories = ["yahyabedirhan/shipyard"]
workflow-runs = { show = true }
notifications = [
{ event = "pr.opened" },
{ event = "ping.sent" },
{ event = "agent.notice" },
{ event = "run.failed" },
]
“Shipyard 不会通知我收到 ping。” 该文件列出了自己的规则,但没有 ping.sent(参见 Overrides)。在已有规则旁添加此规则,放入 [[defaults.notifications]],或者放入列出自身规则的每个项目的 notifications 中:
[[defaults.notifications]]
event = "pr.opened"
authors = ["others"]
[[defaults.notifications]]
event = "ping.sent"
若要让某个项目的 ping 不出现在菜单中,请在其配置块中设置 pings = { show = false };若要将已查看的 ping 保留一周,请在 [defaults.pings] 中设置 seen-window = "7d"。便笺的设置方式相同:在某个项目的配置块中设置 notes = { show = false },可让其便笺不出现在菜单中;在 [defaults.notes] 中设置 show = false,可隐藏所有项目的便笺,除非其配置块中设置了 notes = { show = true }。
“显示来自我的 VPS 的 ping。” Shipyard 只能通过 Herdr 连接到另一台机器,因此这台机器必须已经是用户在 Herdr 中保存的机器之一;如果不知道它的标签,请询问用户,绝不要填写主机名或地址。在 [remote] machines 中列出该标签(如果列表已存在,则将标签添加进去):
[remote]
machines = ["alpha-vps"]
然后,用户(或你在那台机器上运行时)需要在该机器上安装一次 herdr-shipyard 插件,每台机器安装一次。它会在那台机器上提供 shipyard 命令,并为被阻止的 Agent 发送 ping(参见 Sending pings):
herdr plugin install yahyabedirhan/herdr-shipyard
插件页面:https://github.com/yahyabedirhan/herdr-shipyard。Shipyard 大约每 30 秒向每台机器查询一次 ping。无法连接的机器会在菜单中显示一条静默提示,并保留上次收到的 ping,直到它再次响应。
告诉用户 Herdr 0.9.3 的一个已知限制:点击某台机器的 ping 会将该机器上的窗格置于前台,但只有当 Mac 上的 Herdr 窗口当前已显示那台机器时,窗口才会切换到该机器。如果窗口显示的是 Mac 或另一台机器,点击后看起来就像什么也没发生(但 ping 仍会标记为已查看);用户需要自行将 Herdr 切换到那台机器,才能查看窗格。Herdr 目前没有可将已打开的窗口切换到已保存机器的命令,因此无法通过更改设置来解决这个问题。
“让我收到 VPS 发来的通知。” 来自另一台机器的通知会通过用户的 tailnet 传送,因此 Mac 和那台机器都必须加入 tailnet,并以该用户身份登录。在 config.toml 中启用监听器:
[notices]
listen = true
其余配置在 config.toml 之外完成:在 Mac 上执行 tailscale serve,并在该机器自己的 cli.toml 中配置 app-machine:references/notices.md 中的 “Receive notices from another machine” 介绍了每个步骤和测试通知。
Ping 还是通知?
Shipyard 可向用户发送两种消息。根据用户需要采取的操作来选择:
Ping(shipyard ping) | 通知(shipyard notice send) | |
|---|---|---|
| 用途 | 用户应当采取行动或希望立即了解的事情:准备好接受审查的拉取请求、你因遇到阻碍而提出的问题、已完成或失败的长任务 | 不需要用户采取任何操作的状态更新:“编排已开始”“测试正在运行”“已部署到 staging” |
| 用户看到的内容 | 菜单中的一行,需要用户点击后才会消失;并发送一条通知(ping.sent) | 仅发送一条通知(agent.notice):绝不会列出、计数或保留 |
| 你能得知什么 | 其 ID;之后等待用户处理 | 当应用已显示通知,或通知已排队等待 Mac 轮询时,退出码为 0(此时会显示 queued);未显示时,退出码为 1 并说明原因 |
不确定时,就发送通知:用户可以忽略,不会造成任何影响。对于同一件事,绝不要同时发送两种消息。通知会遵循用户的通知规则,并告知你通知是否已显示;而你自行发布的通知不会提供这一信息。在 Mac 和运行 Herdr 的用户其他机器上都可以使用;完成设置后,也可以直接通过用户的 tailnet 从这些机器发送通知。references/notices.md 中包含命令、参数、路由、退出码和示例。
发送 ping
ping 是你通过 shipyard 发送给用户的简短消息,包含标题、可选的正文和发送者,以及至多一个点击后会执行的操作(打开 URL、将应用切到前台,或聚焦某个 Herdr 标签页)。它会列在监视你当前所在仓库的项目下,直到用户点击前都需要处理,并会发布通知(ping.sent 事件)。不会有任何内容发送到 GitHub。
在用户的 Mac 和其他运行 Herdr 并安装了 herdr-shipyard 插件的机器上,都可以使用同一条命令。你在哪台机器上运行命令,决定了 ping 如何送达用户:
| 在 Mac 上 | 在另一台机器上(VPS) | |
|---|---|---|
| 如何送达菜单 | 应用从 Mac 读取 | Mac 每隔约 30 秒通过 Herdr 向该机器查询一次,前提是 [remote] machines 中列出了它(参见已处理的请求) |
| 归档位置 | 发送时检查后,归在监视该仓库的项目下;若没有此类项目,则拒绝发送(参见命令) | 在 Mac 上,归在监视该仓库的项目下;若没有此类项目,则列在该机器自己的分区下(参见在另一台机器上) |
| 点击后没有操作时 | 标记为已查看 | 将你的窗格聚焦到该机器上 |
| 何时移除 | 查看后,seen-window 到期时 | 发送或最近一次替换后 24 小时,无论是否已查看 |
| 你在等用户回复、因此无法继续时 | 自己发送 ping | 插件会替你发送 ping |
当用户需要采取行动,或现在就需要知道某件事时,发送 ping:拉取请求已准备好等待审查、你在等待用户回复而受阻、用户要求的内容已发布、耗时较长的任务已完成或失败。不要在进度推进过程中发送 ping,也不要在用户正在与你交谈时发送。优先使用一个 ping 并替换它(使用相同的 --id),而不是发送一堆 ping;某个 ping 不再适用时,应将其撤回。
在安装了插件的另一台机器上,等待输入时会自动替你发送 ping。 在安装了 herdr-shipyard 插件的 Linux 机器上(herdr plugin list 会显示 yahyabedirhan.herdr-shipyard),Herdr 一旦将你标记为受阻,插件就会自动向用户发送 ping(“Claude 正在 中等待”),并在你继续工作,或你的窗格、标签页或工作区关闭时撤回该 ping。不要仅仅为了说明自己正在等待输入而在此处发送 ping:这会针对同一件事重复发送 ping。其他情况照常发送 ping。在 Mac 上,插件不会执行任何操作,因此,在 Mac 上以及未安装插件的机器上,你需要在等待时自己发送 ping。
命令
应用会将命令链接到 ~/.local/bin/shipyard(在初始设置期间,或在面板的设置菜单中点击齿轮图标,再选择 设置 shipyard 命令)。如果找不到 shipyard,请运行 ~/.local/bin/shipyard;如果该路径也不存在,请让用户通过 设置 shipyard 命令 创建链接;如果无法创建链接,此处也会说明原因。应用无需正在运行:期间发送的 ping 会在应用启动时显示。
shipyard ping send "<title>" [--body <text>] [--from <label>] [--id <id>]
[--open <url> | --app <bundle id or name> | --herdr [<tab or pane id>]]
[--repo <owner/name> | --project <name>]
shipyard ping withdraw <id>
shipyard ping list [--json]
每条命令的格式都是 shipyard <group> <command>,shipyard --help 会列出所有命令。
| 标志 | 作用 |
|---|---|
"<title>" | 菜单和通知中显示的一行文字;请加引号,因为它是一个参数 |
--body <text> | 标题放不下的更多内容,显示在标题下方和通知中 |
--from <label> | 发送者,例如你的名字和任务("claude · checkout");group-by = "author" 会按发送者对通知分组。请以你的 Agent 名称开头,大小写不限,后面可以跟其他文字,这样该行才会显示其徽标:claude、codex、opencode、cursor、pi、gemini、copilot、amp 或 droid(也包括 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> | 为通知命名,以便替换或撤回它:由 1 到 64 个小写字母、数字、- 和 _ 组成,且必须以字母或数字开头。不指定时会自动生成 |
--open <url> | 点击后打开该 URL:可以是网页、拉取请求、构建产物或应用的深层链接;URL 必须带有协议(https://…) |
--app <bundle id or name> | 点击后将应用置于前台,可使用名称("Claude")或 bundle ID(com.anthropic.claudefordesktop) |
--herdr [<id>] | 点击后聚焦 Herdr 标签页。单独使用时,聚焦你自己窗格的标签页(来自 HERDR_PANE_ID,因此只能在 Herdr 内使用);也可以指定 Herdr 显示的标签页或窗格 ID,例如 w1:t2 或 w1:p3。仅当 --herdr 后的参数符合这种格式时,才会将其视为 ID |
--repo <owner/name> | 将通知归入此仓库,而非工作文件夹 |
--project <name> | 将通知归入此项目,使用其在 config.toml 中的 slug,否则使用其 title |
-- | 结束标志:之后的所有内容都作为标题,即使某个词以 -- 开头 |
- 最多执行一个操作。 不执行操作时,点击 ping 只会将其标记为已查看。操作失败时(面板已关闭、没有此应用),ping 会保持未查看状态,其所在行会说明原因。
- 归档位置。 未指定
--repo或--project时,仓库取自工作文件夹的 git 远程origin,格式为owner/name;ping 会归档到所有监视该仓库的项目下:项目的repositories中列出了该仓库,或通过群组或owner/*将其纳入(以应用上次查找时的结果为准,因此新添加的项目需要应用刷新一次)。--repo和--project不能同时使用。 - 输出。 成功时会打印 ping 的 id,例如
k7qm2x,并以状态码 0 退出:该 id 可供--id和withdraw使用。Mac 会按照 ping 到达的顺序,在 ping 所列的每个项目中为其编号(所在行显示为#3);替换 ping 时编号不变。其他情况下,会向标准错误输出一行内容:拒绝请求时以状态码 1 退出(没有项目监视该仓库或使用了该名称;所有将归档该 ping 的项目都通过pings.show = false隐藏 ping;或config.toml无法读取,仅 Mac 会检查此项;要撤回的 id 未知);参数无法读取时以状态码 2 退出(缺少标题、指定了两个操作、id 无效,或在 Herdr 之外单独使用--herdr)。拒绝信息会列出用户的项目:用--project选择一个项目;或者,如果该仓库应该有自己的项目,则询问用户是否愿意监视它(参见已处理的请求),不要擅自编辑文件。如果 ping 因其所属项目隐藏 ping 而被拒绝,拒绝信息会列出这些项目,以及显示 ping 的项目:在其中一个显示 ping 的项目下使用--project发送,或告知用户无法联系到他们;用户是有意在这些项目中隐藏 ping 的,因此不要擅自将show改回开启状态。 - 替换。 发送已存在的 id 会替换该 ping:更新标题、正文、发送者、操作和项目,时间从替换时重新计算,并再次变为未查看状态,但不会再次通知。
- 撤回。
shipyard ping withdraw <id>会撤回 ping:它会从菜单中移除,其通知也会从 Notification Center 中移除。不存在对应 ping 的 id 会以状态码 1 退出;用户可能已将其关闭,或者它已在被查看后移除,因此无需再做任何事。 - 列出。
shipyard ping list会按从新到旧的顺序逐行打印这台计算机当前的 ping:每行包含 id、时间、标题和归档位置(没有 ping 时显示no pings)。使用--json时会将它们以 JSON 格式打印,供用户 Mac 上的 shipyard 读取;发送 ping 不需要此选项。 - Herdr 的终端。 点击
--herdr会聚焦 Herdr 中的标签页;该点击还会将发送 ping 的终端应用切换到前台,这一点由 CLI 自动记录。用户可以通过[herdr] terminal指定其他终端应用(参见键和默认值),该设置优先。
在另一台机器上
在安装了 herdr-shipyard 插件的 Linux 机器(例如 VPS)上,shipyard 位于 ~/.local/bin/shipyard,并接受相同的标志。用户的 Mac 会在 [remote] machines 中列出该机器时,通过 Herdr 向它请求 ping(参见已处理的请求)。不同之处如下:
- 归档由 Mac 完成。 机器没有项目,因此
shipyard ping不会因没有项目监视某个仓库而拒绝请求:它会保留 ping 及其仓库(取自origin或--repo),或其--project名称,由 Mac 将其归档到监视它的项目下。工作文件夹没有origin时,请传入--repo <owner/name>或--project <name>;两者都未指定,或没有项目接收该 ping 时,它会显示在菜单中该机器自己的分区下。 - 你的面板会随 ping 一起提供。 从没有指定操作的 Herdr 面板发送 ping 时,点击 ping 会聚焦你在该机器上的面板,因此不需要使用
--herdr就能再次找到它。 --open和--app在 Mac 上运行。 URL 会在 Mac 的浏览器中打开,应用也必须是 Mac 应用;机器上的localhostURL 无法在那里打开。- Ping 一天后移除。 无论是否已查看,ping 都会在发送或上次替换 24 小时后从该机器移除。使用相同的
--id替换它以保留 ping,或在它不再有效时将其撤回。
示例 ping
“PR 准备好后 ping 我。” 拉取请求打开且检查通过后,发送一个会打开它的 ping:
shipyard ping send "PR #57 is ready for review" --body "Adds the order export" --from "claude · orders" \
--open https://github.com/my-org/shop/pull/57
“需要我时,带我回到这个面板。” 在装有 herdr-shipyard 插件的另一台机器上,你遇到阻塞时插件已经会这样做;只有当它无法看到相关内容时才在那里 ping,例如你希望有人替你做出选择,而你继续工作时。在 Mac 上,从 Herdr 内部运行时,单独使用 --herdr 会指向你自己的面板。为它指定一个 id,以便之后更新或撤回:
shipyard ping send "Waiting for your input" --body "Postgres or SQLite for the cache?" --from "claude · cache" \
--herdr --id cache-question
如果过了一段时间仍在等待,再发送相同的 id:ping 会更新并再次需要处理,但不会发送第二条通知。
shipyard ping send "Still waiting for your input (10 min)" --body "Postgres or SQLite for the cache?" --from "claude · cache" \
--herdr --id cache-question
“我回答后就撤回它。” 用户作出回答后(无论是在面板中还是其他地方),撤回 ping,以免留下过时内容:
shipyard ping withdraw cache-question
“Claude 需要我时告诉我。” 将应用置于前台的操作;当工作文件夹不属于用户的任何代码仓库时,将其归入一个命名项目:
shipyard ping send "Claude is asking for permission" --app Claude --project shop
操控应用
在用户的 Mac 上,还有两组命令可以打开、控制并截取运行中的应用,让你查看 shipyard 显示的内容、检查更改或附加屏幕截图:shipyard app(使用 app capture 截取屏幕截图)和 shipyard panel。它们直接与应用通信;无需辅助功能或屏幕录制权限,也绝不会更改 config.toml。一次只能由一个 Agent 使用它们,而 shipyard lease 会在一次运行期间占用 shipyard:请参阅“轮流操作”。查找命令的方式与“发送 ping”一节相同(~/.local/bin/shipyard)。
shipyard app open [--demo <folder>]
shipyard app quit
shipyard app status [--json]
shipyard app capture <file.png> [--appearance light|dark] [--menu-bar-icon] [--with-indicator]
shipyard panel open
shipyard panel close
shipyard panel fold <project>
shipyard panel unfold <project>
shipyard panel show-more <project> <kind>
shipyard panel show-tab <name>
shipyard panel show-view <name>
shipyard lease take [--wait <seconds>] [--for <purpose>] [--key <k>]
shipyard lease release [--key <k>]
| 命令 | 功能 | 成功时输出 |
|---|---|---|
app open | 如果 shipyard 尚未运行,则在后台启动;并等待它响应(最多约 10 秒) | 状态,与 app status 相同 |
app open --demo <folder> | 退出 shipyard,并使用演示数据运行(参见演示运行) | 状态,另有一行 demo: |
app quit | 退出 shipyard,并等待它完全关闭 | shipyard quit |
app status | 显示 shipyard 是否正在运行,以及面板上显示的内容 | 状态(如下) |
app status --json | 相同内容,以单行 JSON 对象形式输出 | JSON 格式的状态 |
panel open、panel close | 打开或关闭菜单栏图标的面板,并等待操作完成。用户关闭面板后(包括点击其他位置关闭),panel open 会在 30 秒内被拒绝,并告知何时可以再次打开;等待到该时间,不要提前重试 | panel open、panel closed |
panel fold <project>、panel unfold <project> | 按项目在 config.toml 中的 slug(若没有,则按 title)折叠或展开项目区域 | folded <project>、unfolded <project> |
panel show-more <project> <kind> | 显示项目某一类别中超出 show-first 上限的所有行,直到面板关闭;<kind> 可以是 pull-requests、issues、workflow-runs、pings 或 notes | showing all <kind> in <project> |
panel show-tab <name> | 选择标签页布局中的标签:项目的 slug 或 title,或者 All | showing <title> |
panel show-view <name> | 像设置菜单中的选项一样,显示状态视图来替代项目:github、notion、skill 或 cli。github 会显示已登录账户(@login)及其连接方式(GitHub CLI gh 或使用 GitHub 登录);若已退出登录,则显示登录步骤,因此该视图的截图可能会显示用户的登录名。projects 会像 ‹ Back 一样重新显示项目。关闭面板也会重新显示项目 | showing <title>(Shipyard CLI),或 showing projects |
app capture <file.png> | 按面板当前样式保存截图;若面板已关闭,则在屏幕外渲染而不打开(会在标准错误中说明),因此要截取屏幕上实际显示的面板,请先运行 panel open;相对路径以运行命令时所在的文件夹为基准 | 文件的绝对路径 |
--appearance light 或 dark | 截图时按指定外观绘制面板,之后恢复为 Mac 的外观 | |
--menu-bar-icon | 仅保存菜单栏图标,而非面板,且不打开面板 | |
--with-indicator | 保留表示 Agent 正在使用 shipyard 的黄色圆点和横幅;否则截图中会省略它们 | |
lease take | 在一次运行期间占用 shipyard,直到占用后 5 分钟(参见轮流使用) | you hold shipyard until <HH:mm:ss> |
lease take --for <purpose> | 功能相同,并在面板横幅上用几个词向用户说明原因(--for "checking the header icons");限一行,最多 80 个字符。务必说明原因:横幅还会在执行每条命令时显示该命令(“Taking a screenshot…”,然后是 “Took a screenshot · 12s ago”),让用户知道是否应保持面板打开 | 相同 |
lease take --wait <seconds> | 其他 Agent 占用 shipyard 时,按先到先得的顺序排队等待,最长等待指定时长(最多 3600 秒) | 占用成功后输出相同内容 |
lease release | 释放 shipyard,供下一个 Agent 使用;如果当前并未占用,则不执行任何操作 | released shipyard |
app status 会输出多行;仅在演示运行时显示 demo:,仅在标签页布局中显示 tab::
shipyard <version> is running
lease: free
panel: closed
view: projects
layout: tabs
tab: All
projects: shop, blog
folded: none
showing all: pull-requests in shop
使用 --json 时,键包括:running(始终为 true)、version、panelOpen、view(projects,或显示的状态视图:github、notion、skill、cli)、layout(list 或 tabs)、tab(列表布局中为 null)、projects(项目,以及远程机器列表;panel fold 和 panel show-tab 接受这些名称)、folded、showingAll([{"kind":"pull-requests","project":"shop"}])、demo(演示文件夹,否则为 null)和 lease(空闲时为 null,否则为 {"holder", "place", "secondsLeft", "waiting"}:正在使用 shipyard 的 Agent,lease: 行会列出其名称);执行操作时,应读取这些键,而不是读取各行内容。
命令造成的更改,就像用户亲自点击一样。面板关闭后,折叠状态仍会保留,因此请在用户的应用中展开你折叠的内容;“显示更多”和面板外观会自行恢复。完成后关闭面板。切勿循环运行这些命令来等待某件事发生,例如等待数据加载:只检查一次 app status,然后截取一张屏幕截图。
轮流操作
Shipyard 每次只响应一个 Agent:持有其 租约 的 Agent。每条 app 和 panel 命令都需要租约,app status 除外,它会报告租约状态。你不需要报上自己的名字:shipyard 会通过你的 Claude Code 会话(CLAUDE_CODE_SESSION_ID)识别你;否则,会通过运行它的 Agent 进程识别你,因此你运行的每条命令都会被视为由你运行。同一个 Claude Code 会话的子 Agent 共用会话 ID,因此也共用一个租约:它不会让一个子 Agent 等另一个,所以你需要自行安排它们轮流操作。用户会在菜单栏图标上看到一个黄色圆点和你的名字及文件夹(或 Herdr 窗格),也会在面板上看到横幅;你开始和结束时,用户都会收到通知。
- 快速检查无需额外操作。 第一条命令会获取租约,之后的每条命令都会续租。租约会在你最后一条命令运行 1 分钟后结束,且最迟在获取租约 5 分钟后结束;之后你的下一条命令会获取新的租约,除非另一个 Agent 正在等待。
- 执行一组操作时持有租约。 如果步骤之间需要进行其他工作(例如先构建,再截取几张屏幕截图),请先运行
shipyard lease take,租约会持续到获取后 5 分钟,无论中间间隔多久。再次获取租约不会延长结束时间;如果操作超出时限,下一条命令会像上面所说的那样获取新租约。 - 每组操作结束时都要释放租约,演示结束时的
app open也完成后再释放:运行shipyard lease release,这样下一位 Agent 和用户就不用等租约到期。 - 重新启动会保留租约。
app open --demo <folder>以及结束演示时运行的不带参数的app open,都会在同一租约下启动下一个应用,直到租约结束。单独运行app quit和app open,两条命令之间 Shipyard 会处于空闲状态。 - 先用
app status查看状态:lease: free,或lease: <name> in <place>, <n>s left, <k> waiting(JSON 中的lease字段)。 - 屏幕截图不会显示圆点和横幅,因此展示的是用户平时看到的 Shipyard 状态;使用
--with-indicator可保留它们,以拍摄显示租约状态的图片。 - 如果 Shipyard 无法识别你的命令是由你发出的(每条命令都作为独立进程运行,不属于 Claude Code 会话,因此你自己的命令会互相拒绝,并显示“正在使用”),请在这组操作期间导出
SHIPYARD_CONTROL_KEY=<k>,并自行指定一个名称:之后每条app、panel和lease命令都会以<k>的身份运行。--key <k>只对单条lease take或release命令起同样作用。
如果 Shipyard 拒绝为你运行命令,命令会以退出码 1 结束,并显示以下某一行:
| 拒绝信息 | 处理方法 |
|---|---|
shipyard is in use by <name> in <place> until <HH:mm:ss> (<n>s left); `shipyard lease take --wait <seconds>` to queue | 另一个 Agent 正持有租约。运行 shipyard lease take --wait <seconds> 排队等待,等待时长应足以覆盖剩余租约时间;轮到你后命令会立即返回。为命令设置一个长于等待时长加 15 秒的超时时间;若命令提前中断,你就会失去轮候顺序。如果任务无法等这么久,请告知用户当前由谁持有 Shipyard,并继续处理不需要 Shipyard 的工作。 |
waited <n>s; shipyard is still in use by <name> in <place> until <HH:mm:ss> (<n>s left) | 等待时间已结束。告知用户当前由谁持有 Shipyard;只有用户让你再次排队时,才重新排队。 |
the user took shipyard back; ask them before using it again | 用户点击了横幅上的 Stop。立即停止对应用的控制,并询问用户的意图;只有用户同意后才可再次使用 Shipyard。用户可以立即允许你继续;否则,Shipyard 会拒绝你 5 分钟(包括 lease take),此时继续等待、重试或使用另一个密钥都违背了用户的 Stop 操作。 |
退出代码
- 0:完成。即使应用无法捕获其窗口,而是自行绘制面板,截图的退出代码仍为 0:它会写入该图片、打印路径,并在标准错误上说明情况:
captured by rendering: <why>。依赖这类图片前,请先查看。 - 1:拒绝执行,并在标准错误上用一行说明原因。另一个 Agent 正在使用 shipyard,或用户已取回控制权(参见“轮流操作”)。Shipyard 未运行(
shipyard isn't running; `shipyard app open`),或启动后 10 秒内没有响应。没有项目、类型或标签页使用该名称;该行会列出已有名称,例如no project is named `shopp`; the projects are `shop`, `blog`,请从中选一个重试。列表布局中的show-tab(the menu uses the list layout; tabs need `[menu] layout = "tabs"`)。对未按类型分组的项目使用show-more。用户关闭面板后 30 秒内执行panel open(该行会给出可重新打开的时间)。无法写入截图的情况,例如目标文件夹不存在。 - 2:参数无法解析(缺少名称、未知子命令或选项、路径不是
.png,或--demo指定的文件夹不存在);该行末尾会指出应查看的帮助信息,例如Run `shipyard panel --help` for usage.。在非 Mac 设备上,所有这些命令的退出代码也都是 2:shipyard: `shipyard app` runs on the Mac, where the app is.
演示运行
shipyard app open --demo <folder> 会使用示例数据显示 shipyard,以便截图或检查,且不会触碰用户的设置。该文件夹内包含配置文件 <folder>/shipyard/config.toml,格式与用户的配置相同(以上规则均适用);运行期间会将自身状态保存在 <folder>/support。它会退出用户的应用,然后在该文件夹上启动 shipyard;此后每次执行 shipyard app、panel 和 screenshot 都会连接到演示实例,无需再传入任何参数。运行期间,app status 会显示 demo: <folder>。
- 用户的文件会保持原样。 用户的
config.toml、状态和登录项都不会更改;运行只会在 shipyard 的支持文件夹中写入一个小型指针文件,下一次直接执行app open时会将其移除。 - 直接执行
shipyard app open会恢复用户自己的应用:它会退出演示实例并启动用户自己的应用。请始终以此结束演示,确保用户的 shipyard 保持运行。 - 虚构条目不会涉及用户的工作。 如果文件夹中有
demo-items.json,运行会列出该文件中指定账号的拉取请求、问题、工作流运行和备注,并且不会向 GitHub 和 Notion 发起任何请求。任何他人会看到的截图都请使用此文件。编写前请阅读 references/demo-items.md:其中包含格式、所有键和一个示例。 - 没有该文件时,运行会以已登录用户的身份读取 GitHub,因此请在演示的配置中指定公开仓库:显示的内容最终会出现在截图中。运行不会列出备注。在此类演示实例的面板中退出登录,也会让用户退出登录。
- 你在此期间发送的提示会发送到用户的应用,而不是演示实例;用户的应用恢复后会显示这些提示。
- 将文件夹放在用户的主目录中,例如
~/shipyard-demo,不要放在/tmp这样的共享文件夹中:Mac 上的另一个账号可能会先创建该文件夹,并在演示实例中响应你的app和panel命令。 - 保持文件夹路径简短:如果路径对其套接字来说太长,运行会拒绝执行,并提示
use a folder with a shorter path。
无法执行的操作
- 点击。 无法点击行、按钮、链接和齿轮菜单,也无法在面板中输入内容:只能通过
panel命令控制,并就其他操作询问用户。 - 悬停。 截图中不会显示悬停卡片和工具提示。
- 真正的菜单栏。 截图显示的是面板,或使用
--menu-bar-icon单独绘制的图标;无法截取菜单栏本身,包括其中的计数和其他应用图标。 - 另一台机器上的应用。 命令只能访问你当前运行的 Mac 上的应用。在另一台机器上没有可供控制的应用:请询问用户,或在他们的 Mac 上运行。
应用控制示例
“shipyard 是否正在运行?” 先询问;如果没有运行,则启动它:
shipyard app status --json || shipyard app open
“重启 shipyard。” 先退出,再重新打开;open 会在应用响应后返回。两次操作之间,Shipyard 处于空闲状态,其他 Agent 可能会接管:
shipyard app quit
shipyard app open
“给我看看面板。” 打开面板,查看后将其关闭:
shipyard panel open
shipyard panel close
“折叠 blog 项目。” 根据它的 slug 操作;以同样的方式展开:
shipyard panel fold blog
shipyard panel unfold blog
“显示 shop 中的所有拉取请求。” 这种类型的分组会展开,忽略 show-first 上限,直到面板关闭:
shipyard panel show-more shop pull-requests
“截取深色模式下的 shop 标签页。” 在标签页布局中选择该标签页,然后截图;打印出的路径就是文件路径:
shipyard panel show-tab shop
shipyard app capture shop-dark.png --appearance dark
“截取 Shipyard CLI 视图。” 打开面板,显示该视图并截图,然后返回项目列表:
shipyard panel open
shipyard panel show-view cli
shipyard app capture cli-view.png
shipyard panel show-view projects
“截取菜单栏图标。”
shipyard app capture icon.png --menu-bar-icon --appearance light
“截取面板在两种外观下的画面。” 按顺序执行一系列步骤:为此占用 shipyard,等待最多一分钟,直到占用它的 Agent 完成,然后在最后释放它:
shipyard lease take --wait 60 --for "screenshots in both appearances"
shipyard app capture panel-light.png --appearance light
shipyard app capture panel-dark.png --appearance dark
shipyard panel close
shipyard lease release
“展示你工作时用户看到的内容。” 占用 shipyard 时,横幅和圆点由你控制;将它们保留在画面中:
shipyard app capture lease.png --with-indicator
“用示例数据截取 shipyard。” 编写一份列出公开仓库的演示配置,运行演示并截图,然后恢复用户的应用:
mkdir -p ~/shipyard-demo/shipyard
cat > ~/shipyard-demo/shipyard/config.toml <<'TOML'
version = 1
[[projects]]
slug = "swift"
repositories = ["swiftlang/swift"]
TOML
shipyard app open --demo ~/shipyard-demo
shipyard app capture ~/shipyard-demo/panel.png
shipyard app open
shipyard lease release
如需展示虚构条目,而非仓库中的真实条目,请在运行 app open --demo 前写入 ~/shipyard-demo/demo-items.json,如 references/demo-items.md 所述。