mcpField
S
shipyard

shipyard

github.com/yahyabedirhan开发198 次安装1 周前更新
代码仓库

关于 shipyard

shipyard skill 帮助 Agent 配置和操作 macOS 项目通知应用,配置文件涵盖 PR、问题、工作流运行及通知规则,也支持 ping、便笺和主题管理。

主要功能

  1. 01通过 TOML 配置项目与通知规则
  2. 02发送可点击的 Agent ping 和通知
  3. 03驱动应用并查看屏幕截图
  4. 04管理 Notion 项目便笺
  5. 05配置面板主题

适用场景

  1. 01筛选并查看多个项目的 PR
  2. 02接收 Agent 状态消息并跳转
  3. 03整理项目便笺或调整面板外观

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/yahyabedirhan/shipyard --skill shipyard

安装后在 Claude Code 中输入/shipyard确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

presets.md
references/
demo-items.md
notes.md
notices.md
themes.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 所述,用用户的仓库写入预设文件。如果文件已经包含设置或项目,不要替换它:使用下方的键按请求进行修改。

编辑文件

  1. 阅读整个文件:用户会在其中写注释,也会手动编辑。只修改或添加请求所需的行,保留每条注释、空行和现有顺序。

  2. 将新增内容放在 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。要设置其中某项,请取消其所在行(包含其键的表行)的注释,并修改值,而不是再添加一份。标头将顶层键放在各表之前,因此可以取消其中任何一项的注释;如果文件后来经过编辑,请检查你取消注释的表行下方没有顶层键,否则该键会被归入表中。
  3. 应用启动时,或在没有此文件时点击 Refresh 按钮,都会创建带有该标头的文件。如果文件仍不存在,请创建它(以及其所在目录),并以以下内容开头:

    #:schema https://raw.githubusercontent.com/yahyabedirhan/shipyard/main/schema/config.schema.json
    version = 1
    
  4. 每次编辑后运行 shipyard config check,然后检查其余项目(请参阅“检查编辑”)。当 shipyard config check 以状态码 0 退出、taplo check 以状态码 0 退出,且应用记录中你的保存显示 "accepted": true 时,编辑才算完成。

键和默认值

顶层:

键默认值允许值
version1仅限 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-logintrue布尔值

表格:

键默认值允许值
[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-percent101–50:shipyard 每小时可使用的各项 GitHub 限制额度份额(与用户的 Agent 共享)
[attention] unseentrue尚未点击的条目需要关注
[attention] changedtrue自上次点击后发生变化的条目需要关注
[attention] review-requestedtrue请求用户(或其所属团队)审查的 PR 需要关注
[attention] checks-failedtrue检查失败的 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] listenfalse应用是否直接通过 tailnet 接收其他机器上 Agent 发来的通知(shipyard notice send)。只有设为 true 时才会监听;此时应用仅监听 127.0.0.1,并且只接收通过此 Mac 自己的 Tailscale 登录发送的通知。设置方法:references/notices.md
[notices] port474201 到 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.showtrue布尔值
pull-requests.states["open", "merged", "closed"]要列出的 PR:可选 "open"(也包括草稿)、"merged"、"closed"(未合并即关闭)
pull-requests.closed-window"7d"时间窗口(见下文):已关闭和已合并的 PR 保留在列表中的时长;"0" 表示隐藏
pull-requests.draftstrue布尔值;列出草稿 PR
pull-requests.authors{ show = [], hide = [] }要列出谁的 PR:authors.show 中的作者减去 authors.hide 中的作者,每项都是作者选择器列表(见“谁的项目”)
pull-requests.review-requestedfalse布尔值;true 表示只列出等待用户审核的开放 PR,包括请求用户本人或其所属团队审核的 PR
issues.showfalse布尔值
issues.states["open", "closed"]要列出的问题:可选 "open"、"closed"
issues.closed-window"7d"时间窗口:已关闭的问题保留在列表中的时长
issues.authors{ show = [], hide = [] }要列出谁的问题,规则同拉取请求
workflow-runs.showfalse布尔值
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.showtrue布尔值;列出 Agent 使用 shipyard ping 发送的 ping,显示在监视其代码库的项目下,或显示在其指定的项目下。Ping 不支持 states、authors、drafts 或 review-requested:设置其中任何一项都会报错
pings.seen-window"24h"时间窗口:已读 ping 保留在列表中的时长,从标记为已读时开始计算;未读 ping 会一直保留,直到被标记为已读。窗口期满后,已读 ping 会在一次刷新内移出列表并被删除
notes.showtrue布尔值;在“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-first0大于或等于 0 的整数:每个分组显示前 N 行,并显示一行 "Show N more";选择后会显示其余内容,然后显示 "Show less";0 表示显示所有行。使用 group-by = "none" 时,会限制整个项目显示的行数。分组数量包含隐藏的行,菜单关闭后会恢复所有限制

标签页布局中的 All 标签页始终按类型分组,并按最新优先排序。

仓库分组和 owner/* 会包含哪些内容(参见 Which repositories),可在 [defaults] 和项目区块中设置:

键默认值允许的值
[defaults] archivedfalse布尔值;true 也会包含已归档的仓库
[defaults] forkstrue布尔值;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除用户和机器人之外的所有人
botsGitHub 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.startedAgent 开始使用 shipyard 应用(shipyard app、panel 或 lease)时发送,标题为“Claude Code is using shipyard”,副标题说明其运行位置。此事件仅由 [[defaults.notifications]](或内置默认值)决定:项目自己的列表和规则中的 authors 均不适用,应用会对此发出警告。点击通知会打开面板,面板横幅会显示 Agent 名称
control.endedAgent 已结束使用应用时发送,标题为“Claude Code is done with shipyard”,副标题说明原因(已释放、租约到期或你已停止它)。其决定方式与 control.started 相同
ping.sentAgent 发送新 ping 时发送:标题为项目名称和 ping 标题,副标题为其正文和发送者。每个 ping 只通知一次;点击通知的效果与点击 ping 相同:执行其操作(打开链接或应用,或切换到其 Herdr 标签页)并将其标记为已读。Ping 没有作者,因此带有 authors 的规则不会选中它
agent.noticeAgent 使用 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 应用;机器上的 localhost URL 无法在那里打开。
  • 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 或 notesshowing all <kind> in <project>
panel show-tab <name>选择标签页布局中的标签:项目的 slug 或 title,或者 Allshowing <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 所述。

评论

支持 Markdown