关于 uploads-cli
uploads-cli 是一项技能,介绍如何将文件上传至 uploads.sh 并附加到 GitHub 拉取请求或议题。内容涵盖本地 uploads CLI 和托管 MCP,包括暂存、元数据、托管评论及文件审批。
主要功能
- 01上传文件并返回公开 URL
- 02向 GitHub 拉取请求和议题附加文件
- 03在拉取请求创建前暂存文件
- 04按元数据查找文件并管理元数据
- 05审批被暂扣审核的文件
适用场景
- 01向拉取请求添加截图或报告
- 02暂存分支文件以便稍后提升
- 03按元数据查找已上传文件
- 04审核并放行暂扣的附件
MCP 相关
该技能介绍 https://agents.uploads.sh/mcp 上的托管 MCP,以及本地 stdio MCP 和 uploads CLI。托管工具处理已提供的文件内容或公开 URL;本地 CLI 则可访问文件系统路径和 Git 上下文。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ buildinternet/ uploads --skill uploads-cli安装后在 Claude Code 中输入/uploads-cli确认已加载。
详细介绍
原文在翻译后有更新,译文可能不是最新。
上传文件到 uploads.sh 并嵌入 GitHub
功能与用途
GitHub 原生图片托管(github.com/user-attachments/…)可通过浏览器会话访问;自 GitHub CLI 2.99(2026 年 9 月)起,也可通过对现有 PR 或议题使用 gh … --attach 并具备推送权限来访问。目前仍没有公开的 REST 端点,而且托管文件仅对 GitHub 可见。使用 gh … --body-file 编写 PR/议题正文时,正文中放入的任何其他图片 URL 都必须指向已公开托管的内容。github-screenshots 技能会说明何时单独使用 gh --attach 就足够。
此技能涵盖两种传输方式:uploads CLI(本地文件、git、localhost)和托管 MCP(https://agents.uploads.sh/mcp,适用于已有的字节数据或可供抓取的公共 HTTPS URL,无需检出)。两者都会向 uploads.sh API 发送 PUT 请求,并返回稳定的公共 URL 和可直接粘贴的 Markdown。PR 和议题均可使用托管附件评论。CLI 可回退到本地 gh。托管 MCP 仅供机器人使用。
MCP 与 CLI
同一产品,两种传输方式。技能不会安装二进制文件。
| 需求 | 使用 | 原因 |
|---|---|---|
| 已在上下文中的字节(ChatGPT 附件、base64) | 托管 MCP put | files: [{ filename, contentBase64 }]。传入 repo + (pr | branch)。不推断 git 信息。 |
| 文件已位于公开 HTTPS URL | CLI put --url 或托管 MCP put | CLI:uploads put --url https://… --pr 123。托管版:{ contentUrl }(当 URL 路径末尾包含文件名时,filename 可选)。Worker/CLI 会抓取;不支持认证标头。托管版会拒绝私有/内部主机。CLI(以及 stdio MCP)也会抓取 http://localhost / 127.0.0.1 / *.localhost。局域网和链路本地地址仍会被拒绝。 |
| 列出、查找、查看元数据、评论、提升 | 任一 | 托管版:list、find_files、get_metadata / set_metadata、comment、promote。CLI:uploads list / find / meta / comment / attach --promote。 |
| 按拉取请求或仓库列出最近标记的文件 | 托管 MCP list_activity / list_repo_files | 仅托管版。list_activity 按拉取请求(默认)或仓库分组。list_repo_files 需要 repo。时间是标记元数据中的时间,而非上传时间。不会发布动态。 |
| 此 PR(或仓库)的截图实时页面 | 任一 | CLI uploads feed create 或 MCP feed_create。全仓库:仅需 repo。单个 PR:repo + pr,或 github(owner/repo#123 / GitHub URL)。这是同一产品,不是精选图库。feed_get / uploads feed show 会打开现有动态页面。 |
| 本地路径或当前分支附件 | CLI | 托管服务没有文件系统,也没有 attach 工具。请改用 put。 |
localhost / 私有网络截图 | CLI uploads screenshot --via local | 远程渲染无法访问你的计算机。 |
| 对实时页面上的选择器进行标注 | CLI uploads screenshot --annotate --via local | 远程后端会拒绝包含选择器的规范。 |
| 两种传输方式都不适用 | 停止 | 不要将 npm install -g 视为 ChatGPT 的使用方式。https://agents.uploads.sh/mcp 上的 OAuth 是已发布的远程方式。 |
本技能其余部分中的 CLI 示例均假设已有代码库检出,并且有 uploads | ||
| 二进制文件。托管工具的契约见下文 备注和注意事项(MCP | ||
| 条目)。 |
对于常见情况,请使用 uploads attach <file...>。它会推断当前分支的
PR,以稳定的附件键上传所有文件(并行执行),并默认维护评论。单个文件
出错不会影响其余文件——JSON 包含 uploads 和 failures(有任何文件失败时
退出码为 1):
uploads attach ./before.png ./after.png
uploads attach ./shot.png --issue 45 --repo buildinternet/uploads
如果只需要稳定 URL,请传入 --no-comment。如需更精细地控制命名和输出,
请使用 put。
附加已上传的对象(issue #702)。 如果某个 attach 参数在磁盘上
不存在,但能解析为工作区对象键(例如
f/AbC123/shot.webp)或 uploads.sh URL(存储主机、嵌入主机或
/f/ 页面),则会通过服务端复制进行附加,而不是报错
file not found——无需先下载再重新上传。来源对象自身的派生元数据
(path/url/viewport/state/…)会一并保留;gh.repo/
gh.kind/gh.number/gh.ref 则会重新写入。默认采用复制;成功复制后,
--move 会删除来源对象。若某个路径在磁盘上存在,则始终优先视为本地文件,
即使它也能解析为键。
uploads attach f/AbC123/shot.webp --pr 123
uploads attach https://storage.uploads.sh/<workspace>/f/AbC123/shot.webp --pr 123 --move
在 PR 创建前,边做边暂存。 uploads attach ./shot.png --branch [name] 会将文件暂存到 gh/<owner>/<repo>/branch/<branch>/<filename>,
而不是 PR/issue 编号下——上传路径相同,无需指定目标标志,也不会添加评论
(此时还没有可评论的内容)。如果未提供值,--branch 会解析当前 git
分支;名称中的 / 会清理为 -。请在工作中的每个视觉里程碑都这样附加文件,
而不只是最后操作一次。暂存文件会带有 gh.status=staged,直到晋升后改为
promoted,因此使用 uploads find gh.status=staged(添加
gh.branch=<name> 可缩小范围)即可列出仍在处理中内容。对于带有 gh.*-tagged
标签的上传,服务端还会写入 gh.uploader/gh.uploader-id(来自令牌铸造用户),
因此使用 uploads find gh.status=staged gh.uploader=<login> 可筛选出某位
贡献者仍在处理中的文件。
查看暂存内容:uploads staged。 这是专用的只读视图——“此分支有哪些
暂存内容?会自动附加吗?”——无需手动构造上述 find/list 查询:
uploads staged # current branch, repo from gh/git remote
uploads staged --branch feature/thing --repo owner/name
uploads staged --format json
分支/仓库解析方式与 attach --branch 相同(默认使用当前 git 分支,且兼容
worktree)。已在拉取请求中的文件不会列出。包括带有 gh.status=promoted、
gh.promoted-to 或 gh.promoted-at 标记的文件,以及拉取请求中的同名文件副本
时间不早于 gh.staged-at 的情况。同名文件若被重新暂存且时间更新,则仍会列出。
在私有仓库中,暂存键为 gh/private/<id>/branch/<filename>。键中不包含分支名称,
因为该 id 只属于一个分支。uploads staged --branch 会列出该 id,并保留
gh.branch 与该分支相同的文件。没有 gh.branch 的文件也会保留,因为它是通过
该分支的前缀列出的。带有 gh.status=unstaged 的文件会被省略。
uploads staged 不会写入任何内容。
放行暂存文件:uploads approve。 分类器可能会拦截它认为包含秘密或敏感
个人数据的文件。此时,受管理的评论会显示 🔒 N files are held for review,
而不是图片。批准前请检查每个文件;如果其中显示真实凭据,请轮换凭据并替换
文件。需要 files:write。
uploads approve --pr 123 # list held files and why; changes nothing
uploads approve --pr 123 --all # approve every held file on the PR
uploads approve gh/owner/name/pull/123/settings.png
清理分支:uploads unstage。 将文件标记为 gh.status=unstaged,使其从
uploads staged、自动晋升和 Claude 插件区域中移除。对象及其 URL 会保留。
需要 files:write,而非 files:delete。
uploads unstage --branch feature/thing
uploads unstage --branch feature/thing --repo owner/name --dry-run
uploads unstage gh/owner/name/branch/feature-thing/shot.png
--branch 不会默认使用当前 git 分支。请传入键而非 --branch,以取消暂存这些
对象,包括已晋升的对象。在私有仓库中,该命令会列出此分支的前缀 id
(gh/private/<id>/branch/…),与 uploads staged 使用的集合相同。键与
--branch 不能同时使用,否则会报错。
本地 stdio MCP 将其映射为 unstage(branch 或 keys,可选的
repo 和 dryRun)。托管 MCP 工具也叫 unstage。托管端没有
git 默认值,因此使用 branch 时必须提供 repo。
uploads list 不支持 --branch。包括 --branch 在内的未知标志都会报错。
请使用 uploads staged --branch <name>。
人类可读模式会为每个暂存文件打印一行简洁信息(文件名、大小、
gh.staged-at、公开 URL),然后打印一行 binding:,以及 once the PR exists: uploads attach --promote(对于 binding: other
会省略 promote 行——从非所属工作区进行提升会被跨租户防护机制拒绝)。
没有暂存文件时,只打印一行零状态信息。
--format json(或全局 --json)始终会输出有效文档 —
{ repo, branch, files, binding } — 即使没有文件也不例外;files 为 [],
绝不会输出空 stdout。
binding 会纳入暂存时警告所用的同一项 repo↔workspace 检查(见下文“Repo binding”),
因此你无需另行判断:
binding.state | binding.autoAttach | 含义 |
|---|---|---|
self | true | Repo 已绑定到此工作区 — 暂存文件会在 PR 打开时自动附加。 |
none | false | Repo 尚未关联 — 请关联它(uploads github link),否则不会自动附加任何文件。 |
other | false | Repo 已关联到另一个工作区 — 这些文件不会从此处自动附加。 |
unknown | false | 绑定检查失败(离线,或较旧的服务没有此路由)— 仅供参考,绝不会阻止查看。 |
none/other 的措辞与 attach --branch 暂存时警告(issue #398)完全一致 —
以此作为唯一信息源,确保两处内容不会偏离。
本地 stdio MCP 将其映射为 staged 工具(branch/repo 参数,
返回相同的 { repo, branch, files, binding } 结构)。托管 MCP 没有
专用的 staged 工具(没有 git 默认值)— 请参阅下文 注意事项
中的 list/find_files 配方,以及带显式 repo/branch 的托管 put/promote(MCP 条目)。
只要该分支已有 PR,将这些文件添加到 PR 的附件评论中就无需额外步骤:
- 已在 Repo 上安装 GitHub App:webhook 会在 PR 打开、重新打开或收到新提交时, 自动将暂存文件提升到 PR 的附件前缀中,并创建或更新托管评论。
- 未安装 GitHub App:下一次针对该 PR 的
uploads attach会在刷新评论之前,自动提升这些暂存文件到 PR 的附件前缀中。如果首次附加没有 新文件可上传,请运行uploads attach --promote(不带文件参数), 单独执行提升并刷新评论;即使没有暂存文件,也会以退出码0结束。使用--no-promote可在某次调用中跳过自动提升。
提升仅适用于 PR,不适用于 issue;如果工作区的服务尚不支持提升,这两种方式都会静默降级
(不报错)。
PR 尚未创建前分支就已重命名? 暂存键会在暂存时嵌入分支名,因此如果在新的头部分支引用下进行推广,就会找不到任何内容。系统会自动处理此情况:在重命名后的分支上运行任何 uploads 暂存或推广命令(attach --branch、put、attach --pr、attach --promote),都会从该分支的 git reflog 中读取重命名记录,将其注册到服务器,并在推广时一并扫描旧名称。这需要在 PR 打开前,再在该分支上运行一次 uploads。如果分支已重命名或删除,而 PR 在此之前就已打开,文件会继续处于暂存状态(URL 仍可用),但永远不会附加——可通过
uploads attach --pr <n> --from-branch <old-branch-name> 恢复(无需提供任何文件参数):它会将该过期分支前缀推广到 PR,并刷新评论。普通暂存和私有仓库暂存都适用。
推广要求仓库已绑定到工作区。 Webhook 和上述由 CLI 触发的路径都依赖同一仓库↔工作区绑定,该绑定也用于托管评论(见下文“仓库绑定”)——之前对该仓库成功调用过任何 attach/comment/promote 命令,都会隐式绑定;或者通过 uploads github link 显式认领仓库。一个从未绑定、且始终只使用 --branch 暂存的仓库不会报错,也不会发布评论——在 PR 打开时,推广会静默地不执行任何操作。如果无法确认仓库已绑定,就不要承诺会自动附加;无论绑定历史如何都能生效的零配置回退方式,是在 PR 创建后运行一次 uploads attach --promote(或任何指定目标的 uploads attach)。
评论不见了? 首先,稍等片刻——如果已安装 App 并订阅了 issue_comment,被删除或损坏的机器人评论会在下一次 Webhook 投递时自动修复;不要急着重新发布。如果评论仍未出现,请检查仓库↔工作区绑定——uploads github link --status(只读,只显示绑定状态,不会认领仓库)。见下文“仓库绑定”。
GitHub 上的杀手级功能:--pr/--issue 会生成不含哈希、稳定的键
(gh/<owner>/<repo>/pull/<num>/<name>),因此重新上传同名文件会就地覆盖,URL 永不改变。不会显示确认提示——对 Agent 和重复运行而言,热替换是有意为之。人类可读模式会在覆盖后显示
>> replaced existing object (same URL);JSON 中则包含
"replaced": true|false。使用 --dry-run 可预览:如果键已存在,会显示
>> would replace existing object (same URL),但不会写入。
其他所有键都采用严格模式(issue #174):无论是显式指定的 --key,还是未使用 --pr/--issue 的默认 put 路径,都会拒绝覆盖已有对象——CLI 错误会指出现有对象的 URL,并提示添加
--replace(MCP:replace: true)。设置 UPLOADS_OVERWRITE=1 可恢复这些路径的始终覆盖行为。--dry-run 也会预览拒绝操作:
>> would refuse: key already exists。这绝不适用于 --pr/--issue
键;这些键始终采用热替换。
当共享的双主机配置适用时,响应中会包含两个公开 URL:
| 字段 | 主机(默认) | 用途 |
|---|---|---|
url | storage.uploads.sh | 持久链接、点击访问、非 GitHub 嵌入 |
embedUrl | embed.uploads.sh | GitHub PR/issue Markdown(<img src> / ![]()) |
embedUrl 指向同一对象,但使用徽章样式的 no-cache 标头,以便 GitHub Camo 在覆盖后重新验证。CLI/MCP 的 markdown 和托管附件评论都已优先使用 embedUrl。可通过 UPLOADS_EMBED_PUBLIC_BASE_URL 覆盖(设为空可禁用;自托管时请设置为你的 no-cache CDN 基址)。
前置条件
- 没有 shell / ChatGPT? 跳过此部分。使用托管的 MCP
(
https://agents.uploads.sh/mcp)和上方的表格。不要安装 CLI。 - Node.js ≥ 22。
- CLI。 如果要反复供 Agent 使用,请全局安装;也可以用
npx运行一次:
本技能中的所有示例都使用全局npm install --global @buildinternet/uploads npx @buildinternet/uploads --help uploads --versionuploads …二进制文件(即安装后的用法)。仅在 uploads monorepo 中,pnpm uploads …会先从本地源码构建——不要用这种方式编写产品或 PR 示例。 对脚本化步骤,优先使用--json或--quiet(保持 stderr 清爽,并跳过可选的更新提示)。 - 已配置的令牌(一次性设置,见下文)。使用
uploads doctor检查。 - 已完成身份验证的
ghCLI——仅--comment/comment功能需要它们来向 PR/issue 写入内容。普通上传不需要。
一次性设置
配置保存在用户所有的文件中,因此重新安装技能后仍会保留:
~/.config/buildinternet/config # or $XDG_CONFIG_HOME/buildinternet/config
解析规则是按键逐项查找,采用第一个匹配项:CLI 标志(--api-url、--token、--workspace)→ UPLOADS_* 环境变量 → --env-file <path> →
$BUILDINTERNET_CONFIG → 共享配置文件。若要临时使用不同的 API 或工作区,只需导出变量或传入 --env-file。
最快的方式是运行 uploads login。请先让工作区管理员将你的邮箱邀请到工作区,然后以交互方式运行一次以登录:
uploads login # opens a browser to approve sign-in, saves config, runs doctor
uploads login --workspace acme # only needed if your account can access more than one
如果账户还没有工作区,login 会提示输入名称,并提供一个根据你的 GitHub 登录名生成的名称作为方括号中的默认值——按 Enter 接受,或输入自己的名称。如果无法生成有效且未被占用的名称,则不会预填任何内容。--workspace <name> --create 可完全跳过提示,适合在脚本中使用。
这是一次性、需要人工参与的步骤(设备登录需要浏览器);配置文件写入后,之后每次调用 uploads——包括由非交互式 Agent 发起的调用——都会直接读取已保存的令牌。日常使用的 Agent 从不需要 ADMIN_TOKEN。
邀请队友(仅限工作区管理员/所有者):在浏览器中打开 /account/workspaces/<name>/people 下的人员选项卡(可邀请成员、撤销待处理邀请、将成员提升为管理员),或者:
uploads invite create --email [email protected] --workspace acme
以你自己的身份进行设备登录(不是 ADMIN_TOKEN,也不是工作区令牌)。如果未配置邮箱,CLI 会打印可分享的接受邀请 URL。受邀者接受邀请后,再运行 uploads login。工作区管理员可以在该人员选项卡中将现有成员提升为管理员;只有工作区所有者可以降级或移除其他管理员。
对于完全没有浏览器的无头机器,操作员可以直接创建令牌(/admin/tokens,由 ADMIN_TOKEN 控制访问——参见 docs/admin-tokens.md),并通过 UPLOADS_TOKEN 将其交给 Agent;也可以使用注册代码(upe_…,一种替代的邀请链接/代码方式——在不知道接收者邮箱时很有用),通过 uploads login --code 兑换。这两种都不是新设置的常规方式。
生成的令牌默认有效期为 90 天,并具有 files:read 和 files:write 权限。该令牌可以删除服务器标记为属于该用户的 gh/ 对象。被标记之前上传的文件、其他人的对象、非 gh/ 键,以及运行 purge-expired,仍需要 files:delete 权限。缺少权限时会返回 403 insufficient_scope,并指出所需权限及
https://uploads.sh/account/developers。服务令牌或没有上传者 ID 的旧版令牌无法使用仅限本人对象的操作。可随时验证或检查
设置:
uploads setup # shows effective configuration
uploads doctor # version + health + auth + workspace
uploads doctor --json
工作区令牌中包含其工作区信息(up_<workspace>_…;工作区服务令牌则为 ups_<workspace>_…),因此如果你未设置 --workspace,CLI 会自动推断。/account/developers 创建的令牌格式相同,并且可以跳过到期时间设置(此时只有撤销令牌才能停用它)。旧版管理员创建的令牌仍然有效。
参见“配置命令”,了解如何一次性设置 put 命令的默认值(默认仓库、前缀、图片宽度),而不是每次执行命令时都设置。
核心工作流:uploads put
上传一个或多个文件,并获取 URL 和可直接粘贴的 Markdown。
多个路径会并行上传;多文件 JSON 格式为 { uploads, failures }
(有任何失败时退出码为 1)。单文件 JSON 仍为扁平对象。
uploads put ./shot.png --repo myorg/myapp --ref 1722 --alt "New live feed cards" --width 700
uploads put ./before.png ./after.png
uploads put --url https://cdn.example/shot.png --pr 123 --name hero.png
人类可读的输出会写入 stderr;URL 和 Markdown 会写入 stdout,因此可以将它们通过管道传递或捕获。使用 - 作为文件可从 stdin 读取。
关键选项(运行 uploads put --help 查看全部选项):
| 标志 | 用途 |
|---|---|
--alt <text> | Markdown 的替代文本(默认值:文件名)。请始终编写有意义的替代文本。 |
--width <px> | 输出带尺寸的 <img width=…> HTML,而不是 ![]()(Markdown 无法设置图像尺寸)。 |
--repo <owner/repo> | 自动生成的键中的仓库部分(默认值:git remote,或 UPLOADS_DEFAULT_REPO)。 |
--ref <id> | PR/议题/分支/日期部分(默认值:今天,或 UPLOADS_DEFAULT_REF)。 |
--destination <id> | 指定类型的根目录:screenshots | gh | f(设置键前缀)。 |
--prefix <path> | 键前缀(默认值:screenshots,或 UPLOADS_DEFAULT_PREFIX)。 |
--key <key> | 显式设置对象键;跳过下方的自动命名。 |
--name <leaf> | 为键的叶节点设置简洁文件名,并将其用作默认替代文本(不能包含 /);保留 --pr/默认路径。不能与 --key 同时使用。 |
--url <url> | 获取此 URL 并上传其内容(可重复使用)。不能与文件参数同时使用。支持公共 HTTPS,或本机上的 http://localhost / 127.0.0.1 / *.localhost。文件名取自 URL 路径,或使用 --name 指定。其他私有/内部主机将被拒绝;不会转发身份验证信息。 |
--replace | 允许在严格键(--key/默认路径)上覆盖现有对象。对 --pr/--issue(或 UPLOADS_OVERWRITE=1)无效。 |
--dry-run | 解析并输出键和最终公开 URL,但不上传;报告键是否会覆盖现有对象(或在严格键上被拒绝)。不能与 --gallery 同时使用;即使使用 --pr/--issue,也会跳过托管评论同步。 |
--content-type <mime> | 覆盖内容类型(否则根据扩展名推断;优化操作重写内容时,此设置会被忽略)。 |
--frame <id> | 在优化前选择性添加浏览器外框:phone、browser、iphone-16-pro。 |
--frame-url <url> | --frame browser 的地址栏文本。 |
--frame-fit cover|contain | 截图填充屏幕的方式(默认值:cover)。 |
--no-optimize | 跳过客户端图像优化(默认:静态图像转为 WebP)。或者设置 UPLOADS_NO_OPTIMIZE=1。 |
--optimize-max-edge <px> | 优化时的最长边上限(默认值:2400)。 |
--optimize-quality <1-100> | 优化时的 WebP 质量(默认值:85)。 |
--keep-exif | 优化时保留 EXIF/XMP/ICC 信息(默认:出于隐私考虑,移除)。或者设置 UPLOADS_KEEP_EXIF=1。 |
--no-git | 不从 git remote 推导 --repo(或设置 UPLOADS_NO_GIT=1)。 |
--format human|url|markdown|json | 控制标准输出。全局选项 --json 会强制使用 json。 |
-w, --workspace <name> | 覆盖工作区(优先于环境变量和令牌推断结果)。 |
| 图像优化(默认启用): PNG/JPEG 等静态图像会在上传前重新编码为 | |
WebP(最长边限制为 2400px,质量为 85),以减小 PR/issue 中嵌入图像的体积。对象键/文件名扩展名会与输出格式一致(例如,shot.png → | |
…/shot.webp)。默认会剥除 EXIF/XMP(使用公开 URL,保护隐私);如果讨论需要嵌入的图像元数据,请传入 | |
--keep-exif。动画 GIF、SVG、视频和非图像文件保持不变;如果优化后的文件体积没有变小,则上传原始文件。需要无损原图时,请使用 | |
--no-optimize。 |
边框(可选): --frame phone(通用设备边框)、--frame browser 或 --frame iphone-16-pro(社区制作的设备图,缓存于
~/.cache/uploads/frames)。默认不加边框。
键的工作方式——只有三种路径,不提供额外的命名模式:
| 用途 | 命令 |
|---|---|
| 只需上传并获取 URL | uploads put ./file.png |
| 已托管在公开的 HTTPS URL 上 | uploads put --url https://cdn.example/file.png |
| 指定类型化目标 | uploads put ./file.png --destination screenshots |
| 稳定的 GitHub 嵌入路径,之后可能重新上传 | uploads put ./file.png --pr <num> |
使用稳定的 --pr 路径,但叶文件名简洁 | uploads put ./capture-2026-…Z.png --pr <num> --name hero.png |
| 清楚文件的确切存放位置 | uploads put ./file.png --key screenshots/…/x.png |
带时间戳的截图会导致 --pr 键不稳定——请传入 --name hero.webp,以保持叶文件名简洁。使用
--dry-run 可在上传前预览确切的公开 URL。
默认的 put 是快速路径;无需使用 --key、--prefix 或 --repo。
在 git 仓库中且位于非默认分支时,单独运行 put 现在会自动暂存——键/元数据与 attach --branch 相同
(gh/<owner>/<repo>/branch/<branch>/<filename>),因此该分支有 PR 时会自动附加到对应 PR。只要以下选项均未设置,且未传入
--no-git,就会触发此行为:--pr/--issue/--key/--ref/--prefix/--destination;设置其中任一选项(或者当前处于默认分支、HEAD 分离、不在 git 仓库中,或传入 --no-git)时,都会回退到经典的
带日期布局:
<prefix>/<repo-name>/<ref-or-date>/<basename>-<shorthash>.<ext>——短哈希可避免冲突,无需随机名称或单独的“保留名称”标记。优先使用 --destination screenshots(或者搭配 --pr/--issue 使用 gh),不要自行设计根路径——工作区可能只允许使用列出的目标。只有在有理由时才通过 --key 覆盖,并确保键位于允许的根路径下。在分支上执行普通的带日期上传(退出暂存行为)时,请显式传入 --ref/--prefix/--destination。
输出格式——选择你要使用的格式:
uploads put ./shot.png --format url # just the URL, for scripting
uploads put ./shot.png --format markdown # just the ![]()/<img> snippet
uploads put ./shot.png --json # {workspace,key,url,size,markdown}
单独运行 put 时的暂存提示。 由于现在在非默认分支上单独运行 put 默认会暂存(见上文),它会打印一行提示确认此操作,而不再提醒你自行暂存——人工模式会将提示写入 stderr,--format json 则会在同一响应中将其作为可选的附加 hint 字段:
note: staged for branch fix-header — auto-comments to pull request when opened
(or run: uploads attach --promote once it exists). Use --ref/--prefix for a
plain dated upload.
如果同一次调用还触发了暂存时的绑定警告(issue #398/#400——仓库未绑定到此工作区),该警告会占用 hint 字段(因为它更具可操作性);人工模式下,两条提示仍都会写入 stderr。使用
--quiet、UPLOADS_NO_NUDGE=1(环境变量),或在配置文件中设置 UPLOADS_NO_NUDGE=1(uploads config set UPLOADS_NO_NUDGE 1),即可隐藏提示(但不会取消暂存)。
**旧版“使用 --pr 重新运行”的提示(issue #393)**仍会按原样触发,但仅限于较窄的情况:单独运行 put 后仍使用带日期的布局,且能检测到 PR——实际中,即显式传入 --ref/--prefix 以退出暂存行为,同时该分支有未关闭的 PR:
note: on branch fix-header (PR #142 open) — rerun with --pr 142 for a stable
key plus a managed comment that collects this PR's media, or stage pre-PR
files with: uploads attach <file> --branch
这是尽力而为的处理(快速执行 gh pr view 查询,超时限制为 3s)——没有打开的 PR
时,只会将措辞放宽为通用的 --pr <num>。抑制逻辑与上文相同。
捕获屏幕截图:uploads screenshot
捕获一个 URL 或本地 .html 文件并托管——无需单独的屏幕截图
工具,也无需安装浏览器即可使用默认路径:
uploads screenshot https://uploads.sh --pr 128
uploads screenshot ./card.html --out ./card.png
uploads screenshot ./card.html --no-upload --out ./card.png
捕获后,屏幕截图使用与上文所述相同的 put 上传流程:可选的 --frame、默认优化、--pr/--issue 附加
--comment、--gallery、--meta,以及相同的输出格式。它也作为 MCP 工具(screenshot)随 CLI 命令一同提供。
两种捕获后端,通过 --via 选择:
| 后端 | 说明 | 需要 |
|---|---|---|
local | 通过 playwright-core 驱动已安装的 Chrome/Chromium | 磁盘上可发现的浏览器,或 --cdp |
remote | 通过 uploads.sh 渲染端点在服务端渲染 | 无需本地资源;用量计入工作区的每月上传额度 |
--via auto(默认值)会在找到可用浏览器时优先使用本地后端,
否则回退到远程后端。通过
UPLOADS_SCREENSHOT_VIA=auto|local|remote(环境变量、--env-file 或用户
配置文件——参见“配置命令”)设置持久默认值;--via 标志始终优先。
localhost/专用网络目标仅支持本地后端。 使用 --via remote
(或 auto 回退到远程后端)时,这些目标会快速失败并显示明确的错误,
而不会发送注定无法成功的请求。本地 .html 文件在两种后端上均可用——
远程后端会以内联方式接收文件内容(≤ 2 MiB),
因此,页面通过 file:// 或相对路径引用的内容只能在 --via local 下解析。数值型
--wait <ms>(加载后固定等待时长)也仅支持本地后端;若要使用与后端无关的等待方式,请使用 --wait load|domcontentloaded|networkidle。
使用 --cdp <endpoint> 附加到已在运行的 Chrome
(http://host:port 或 ws://…),而不是启动新实例——当
Agent 已打开 Playwright MCP 或 agent-browser 会话时,这很实用。
--browser <path>(或 UPLOADS_CHROME_PATH / CHROME_PATH)用于指定
可执行文件的路径。
主要选项(查看 uploads screenshot --help 获取全部选项):
| 标志 | 用途 |
|---|---|
--via auto|local|remote | 捕获后端(默认值:auto,或 UPLOADS_SCREENSHOT_VIA)。 |
--browser <path> | 指定本地浏览器可执行文件(或 UPLOADS_CHROME_PATH / CHROME_PATH)。 |
--cdp <endpoint> | 通过 CDP 连接到正在运行的 Chrome,而不是启动新实例(仅限本地后端)。 |
--viewport <WxH[@Sx]> | 尺寸 + 设备缩放因子(默认值:1280x800@2)。 |
--selector <css> | 捕获单个元素,而不是视口。 |
--full-page | 捕获整个可滚动页面。 |
--max-height <px> | 限制 --full-page 捕获高度,单位为 CSS px(默认值:5000;0 = 不限)。超过上限的页面会被截断,同时向 stderr 输出提示,并在 --format json 中提供 hint。需要 --full-page;适用于 --via local 和 --via remote。 |
--dark / --light | 模拟 prefers-color-scheme(仅 --via local 支持完整的媒体查询模拟;--via remote 只会设置 CSS color-scheme 属性,因此页面自身的 prefers-color-scheme 查询不会切换)。 |
--wait <load|domcontentloaded|networkidle|ms> | 页面稳定策略(默认值:load);毫秒数仅适用于本地模式。 |
--wait-for <js> | 在执行 --eval 和捕获之前,持续轮询页面中的此 JS 表达式,直到其值为真(仅限本地后端)。可等待框架完成水合——load / networkidle 会在 React/Next 附加处理程序之前结束等待,因此 --eval 中的模拟点击会作用于尚未启用交互的 SSR DOM。请指定应用自身的信号,例如 --wait-for 'window.__hydrated===true'。超时时间与捕获超时相同。 |
--eval <js> / --init-script <file> | 页面稳定后运行设置用 JS / 在导航前注入脚本(仅限本地后端)。应用完成水合前,模拟事件(el.click())不会触发框架处理程序——对于 React/Next 应用,请将 --eval 与 --wait-for 配合使用。 |
--out <file> | 还会将 PNG 写入本地文件,并生成旁置清单(<file>.uploads.json),其中包含此次捕获的派生元数据(path / url / env / viewport,以及在指定时的 --state)和内容哈希。之后对该文件执行 put / attach 时会自动恢复这些元数据——显式指定的 --meta / --state 仍优先。参见 --no-sidecar。 |
--no-sidecar | 不在 --out 文件旁写入 <file>.uploads.json 旁置文件。 |
--no-upload | 跳过托管;需要 --out(仅本地文件)。 |
--key / --pr / --issue / --comment | 与 put 使用相同的目标位置和附件选项(见上文);--pr / --issue 还会提供一个稳定且不含哈希的键。 |
--branch [name] | 在分支上暂存,先于 PR——键与 attach --branch 相同(见下文);在非默认分支上单独运行 screenshot 时也会自动执行此操作。 |
--frame / --no-optimize / --gallery / --meta | 与 put 相同——复用共享上传流程。 |
在 Git 仓库的非默认分支上,直接执行 screenshot 现在也会自动暂存——与 --branch/attach --branch 使用相同的键/元数据(gh/<owner>/<repo>/branch/<branch>/<filename>),并将所有派生信息(path/url/env/viewport,以及 --state)一并带到之后创建的 PR 中。这解决了编码 Agent 在 PR 尚不存在时捕获截图遇到的问题:使用普通命令 uploads screenshot <url> --out shot.png 提前捕获,元数据会随之保存,无需在执行 attach --pr <num> 时重新填写(或担心丢失)。只要未设置 --pr/--issue/--branch/--key/--ref/--prefix/--destination 中的任何选项,且未传入 --no-git,就会触发此行为;使用同一组选项(或处于默认分支、HEAD 分离状态、不在 Git 仓库中,或传入 --no-git)时,则回退到经典的带日期布局 screenshots/<repo>/<date>/...。会像直接执行 put 一样打印暂存提示(见上文)——stderr 文案相同、JSON 的 hint 字段相同,--quiet/UPLOADS_NO_NUDGE 的抑制行为也相同。 |
**错误与提示:**本地捕获时,如果没有可用的浏览器,则会失败并返回 BROWSER_NOT_FOUND(退出码 2)——提示:尝试使用 --via remote,或安装浏览器(npx playwright install chromium)。如果远程服务无法完成渲染,则会返回 RENDER_FAILED。渲染端点触发突发速率限制时,会返回 RATE_LIMITED(退出码 4)——提示:等待约 60 秒后重试。远程渲染超出工作区的月度上传额度时,会显示常规的 UPLOAD_BUDGET 代码和提示(运行 uploads usage,然后删除对象或提高额度)——渲染和上传共用同一个月度计数器。
uploads doctor 会报告检测到的本地浏览器(如果有),以及 --via auto 当前会选择的后端。
**键的派生与 --state。**如果对象的文件名是根据捕获的 URL(主机名 + 路径)自动派生的——例如默认的带日期布局,或 --pr/--issue 的末级名称——传入 --state 会将其合并到派生文件名主体中——
localhost-docs-mcp.webp 会变成 localhost-docs-mcp-before.webp /
localhost-docs-mcp-after.webp——因此,对同一个 URL 先使用 --state before 再使用 --state after 捕获两次,会生成两个不同的对象,而不是第二次捕获时静默覆盖第一次。使用相同状态重新捕获同一个 URL,仍会就地替换已有对象(幂等地重新捕获)。显式指定的 --key 不受 --state 合并影响。覆盖时,人类可读模式会在 stderr 中打印 >> replaced existing object (same URL)(文案与上文 put 的热替换提示相同),而 --format json 会添加 "replaced": true;如果使用 --state 捕获时替换了已有对象,还会添加 hint 字段。
在捕获图像上绘制标注:--annotate
--annotate <file|-> 会在上传前将手绘框、箭头、标签、自由笔画和涂黑内容绘制到捕获图像上(JSON 规格,可以是文件路径,也可以用 - 表示标准输入)。选择器会根据实时页面解析——v1 仅支持本地后端,因此使用 --via remote 时,包含选择器的规格会被提前拒绝:
uploads screenshot http://localhost:3000 --via local --annotate ./callouts.json
有关规格格式以及现有图像的等效命令(uploads annotate <image> --spec <file|->,仅限像素、不支持选择器),请参阅
annotate-screenshots 技能。
自定义元数据与搜索
每个对象都可以携带可查询的键值元数据(与优化/帧来源信息不同)——上传时为内容添加标签,之后即可查找。
规范词汇
只有每次拼写都一致,元数据才有用。以下十个键是约定好的词汇;大多数会自动为你生成,因此主要任务不是另创不同的拼写来替代它们。
| 键 | 来源 | 示例 |
|---|---|---|
url | 自动 — 截图目标 | https://app.example/settings |
path | 自动 — 路径名 | /settings |
env | 自动 — 仅限 local | local |
theme | 自动 — 仅在强制指定时 | dark |
viewport | 自动 — 捕获选项 / EXIF | 1280x800@2x |
device | 自动 — 图像 EXIF | Apple iPhone 16 Pro |
software | 自动 — 图像 EXIF | Figma |
captured | 自动 — 图像 EXIF | 2026-07-20T20:35:39 |
state | 由你指定 — --state | before | after |
app | 由你指定 — --app | web | ios |
path 和 state 是价值最高的两个键——养成习惯,每次都传入这两个键。 path 是最值得准确填写的键(Agent 最常把它拼错为 route、page 或 screen——拼写相近时会在 stderr 上发出警告并建议使用规范拼写,但绝不会替你改写,因此请从源头修正);state 则记录 PR 截图中最常见的前后状态模式——这两个键都无法仅凭图像推断出来。state 是一个封闭集合:before、after、empty、error、loading(像 --state post 这样的近似值会快速报错,并建议使用 after)。
uploads screenshot 会根据捕获的 URL 自动推导 path,所以你只需添加 --state。对于已存在文件的 uploads put/uploads attach,无法据此推导 path,因此请用 --meta path=/route 显式传入——如果图片上传时没有 path 元数据,attach/put --pr/put --issue 会在 stderr 上输出 tip: add --meta path=/route so this shot is findable by page(并附带 JSON hint 字段)作为提醒(遵循 --quiet 设置)。
uploads screenshot https://app.example/settings --state before
# → stamps url, path=/settings, viewport, state=before
uploads put ./after.png --pr 123 --meta path=/settings --state after --app web
uploads find path=/settings state=after # what it was all for
推导内容及其时机
uploads screenshot知道自己的目标,因此会写入url、path(去除查询参数)、viewport、本地目标的env=local,以及在通过--dark/--light强制指定主题时写入theme。uploads put/attach会在优化器剥除图片 EXIF 信息之前读取这些信息,并提升允许列表中的元数据:viewport(根据像素尺寸和 DPI 得出)、device、software、captured。uploads put/attach还会读取旁车清单,该清单由先前对同一文件执行的screenshot --out留下(<file>.uploads.json,受内容哈希保护——重新生成或编辑文件后,清单会被静默丢弃),并合并其中推导出的元数据。这填补了先截图、后关联的空缺:在 PR 尚不存在时拍摄的截图,现在可以先执行uploads screenshot ... --out shot.png --state after,之后再执行uploads attach shot.png --pr 123,PR 键控对象仍会获得path/url/env/viewport/state。使用--no-sidecar可禁用写入旁车清单。
env 只会是 local。绝不会设置为 prod——根据“不是 localhost”来推断会把所有预发布和预览 URL 都错误标记,而错误的元数据比没有元数据更糟。
无论是否使用 --keep-exif,都不会从 EXIF 提升: 所有 GPS 标签、机身和镜头序列号、Artist/Copyright/OwnerName,以及自由格式的用户评论。反过来说,device 和 software 之前会被丢弃,现在则会成为可查询的元数据,并显示在公开的 /f/ 页面上。
优先级: 显式指定的 --meta/--state/--app > 截图采集信息 > 旁车清单 > EXIF > 未设置。达到 24 个键的上限时,也会优先丢弃推导出的键——绝不会丢弃你自己的键,而且键数达到上限也不会导致上传失败。
使用 --no-auto 或 UPLOADS_NO_AUTO_META=1 可关闭整个推导层。
规则和保留键
客户端会在上传前验证,并在失败时立即终止:键必须符合 ^[a-z][a-z0-9._-]{0,63}$(小写,允许使用点号命名空间,例如 gh.repo);值必须是 1–512 个可打印的 ASCII 字符;每次请求最多可重复使用 24 次 --meta k=v;值本身可以包含 =(只有第一个 = 用于分隔键和值)。content-sha256 和 visibility 是保留键(分别由服务器计算/作为实际的 R2 可见性门控)。uploads attach 会自动写入自己的按惯例保留的 gh.* 键——见下文。
对 gh/… 键控对象执行 meta set 时,只要写入涉及会渲染的键(path/state),也会刷新由系统管理的 PR/issue 评论——尽力而为,并且在元数据写入成功后执行。成功时会向 stderr 输出 refreshed the managed comment on <repo>#<num>;如果 Bot 端点不可用,则会输出 tip: run \uploads comment --pr ` to refresh the PR comment`;无论哪种情况,元数据写入本身都不会失败。
uploads meta get screenshots/myapp/42/settings.webp
uploads meta set screenshots/myapp/42/settings.webp path=/onboarding --delete url
uploads meta set screenshots/myapp/42/settings.webp --meta path=/onboarding # same thing
uploads list --meta app=web --meta path=/settings # ANDed, repeatable
uploads find app=web path=/settings # same filter, positional pairs
uploads find --meta app=web # --meta works here too
uploads find hero # bare name = filename substring
uploads find --name hero --meta app=web # name + meta, either order
uploads find app=web --all # follow the cursor (max 20 pages)
uploads find app=web --cursor "$CURSOR" # resume one page by hand
uploads meta keys # which meta keys exist here
uploads meta values app # values (with counts) for one key
meta set 和 find 接受两种形式的键值对:位置参数 k=v,或可重复使用的 --meta k=v,后者也用于 put、attach、screenshot 和 list。一次调用中可以同时使用两种形式。find 还接受不区分大小写的文件名子串(--name <term>,或不含 = 的裸位置参数)。如果不知道有哪些键,可先使用 meta keys / meta values <key>(或 MCP list_metadata_keys 工具)——键由用户/Agent 定义,并非固定架构。
实验性 AI 标签(ai.*)。 默认关闭。当 Flagship
llm-file-classifier 为工作区 slug(org / workspace
定位上下文)提供服务时,服务端介导的上传操作之后可能会写入 ai.kind
(screenshot|photo|diagram|document|code|ui|other)、ai.surface
(mobile|desktop|tablet|unknown)、ai.screen
(login|signup|settings|profile|dashboard|analytics|list|detail|form|search|modal|onboarding|empty|error|checkout|other),
以及 ai.classifier=v5(为 Jev 调整过图像尺寸时为 v6,启用 Luna 尝试时为 v4,模型未看到图像时为 skipped)。ai.skip
的值为 small、unsupported、resize_failed 或 resize_timeout。跳过时不会写入 kind、surface、screen 或安全值。调用失败时会写入
ai.classifier=failed 和 ai.error(timeout|provider_error|no_answer)。同一次调用还可能
写入内部字段 ai.rating
(safe|suggestive|explicit)、ai.content
(none|sexual|violence|gore|drugs|hate|self_harm|other)、ai.secret
(none|api_key|password|private_key|token|connection_string|other),以及
ai.pii(none|contact|identity_document|financial|other)。每个存储的
标记还会有 ai.<flag>.confidence,值为 high(0.85 及以上)、medium(0.70 至
0.85)或 low(0.50 至 0.70)。这四个标记均为内部字段:它们不会显示为标签,公开页面、图库、信息流、嵌入内容和拉取请求
评论都会省略它们。若通过 CLI 0.59.0 或更新版本上传的文件,其 ai.secret 置信度较高(不为 none),或
ai.pii 置信度较高且值为 identity_document|financial|other,则会暂存该文件(ai.hold=pending):托管评论会省略该文件并显示暂存数量,公开信息流和图库会隐藏该文件,其 /f/ 页面会要求登录。在托管存储上,原始 URL 会返回 404,直至文件获批;批准后,同一 URL 即可恢复访问。检查文件后,运行
uploads approve --pr <n> 列出暂存文件,并使用 --all 批准这些文件;也可以运行 uploads approve <key>。如果文件中确实显示了凭据,请轮换凭据并替换或删除文件,而不是批准文件。不会因此提升分类器版本。标签器不会写入 ai.tags 或 ai.summary。旧文件可能仍含有这些键。
使用 uploads meta get <key> 或 MCP get_metadata 读取;使用 uploads find ai.kind=screenshot、uploads find ai.screen=login 或
uploads find ai.secret=api_key 筛选。页眉中的姓名或电子邮件不属于 ai.secret,也不属于 ai.pii。已存储的标记会一直保留,直到后续分类写入该键。跳过时不会写入安全值。
重新上传相同字节不会清除标记。新字节会从头开始处理,若跳过这些字节,则会保持标记为空。请勿自行设置 ai.*
——此前缀由服务端管理,API 会拒绝客户端写入。标签不会出现在 put
响应中;它们会在稍后通过 waitUntil 写入。预签名上传不会进行分类。参见 docs/ops.md(实验性 LLM 文件分类器)。
搜索结果会分页。还有更多匹配项时,find 会在 stderr 上打印 truncated: true 和下一页的不透明 cursor。--json 会在负载中包含相同的
cursor。使用 --cursor 传回该值即可读取下一页。--all 会自动沿用 cursor,最多读取 20 页。请将 cursor 视为
不透明值,并且仅与生成它的查询一同发送。
在默认的 screenshots/… 路径下,put 还会自动派生 GitHub 上下文,并根据当前分支的 PR(或数字形式的 --ref)
写入 gh.repo/gh.kind/gh.number/gh.ref,使文件的 /f/ 页面显示“关联到”链接。此功能默认开启,尽力而为;可通过 --no-auto、--no-git 或 UPLOADS_NO_AUTO_META=1 禁用。
在此自动处理路径中,显式指定的 --meta gh.* 会覆盖自动派生的值;这与下文 --pr/--issue 的优先级相反,在后者中,目标自身的 gh.* 始终优先。
两种路径还会在本地 gh 能解析出 PR/issue 标题时写入 gh.title;此操作尽力而为,即使无法解析,也绝不会阻止上传。
重新上传语义:重新上传到现有键并提供元数据时,会替换该文件的整个元数据集(先删除再设置,而非合并);重新上传时完全不提供元数据,则会原样保留现有元数据。派生键也算作元数据,因此只要重新上传时派生出任何内容,就会替换整个集合——重新上传已通过 uploads meta set 精心设置元数据的键时,请传入 --no-auto。
通过 --meta(CLI)或 metadata(MCP)提供的非 gh.* 元数据值会显示在对象的公开 /f/<key> 文件页面上。请像对待 URL 本身一样谨慎处理这些值:不要在其中放入内部备注、机密、令牌、ID 或私有路径。
公开媒体图库
当需要将多个现有公开上传内容作为一个有序集合分享时,请使用图库。图库拥有不透明的、由 API 返回的公开 URL;请勿在脚本中推导该 URL。任何知道该 URL 的人都可以查看图库及其中的媒体内容。GitHub 仓库的可见性不会使图库变为私有,图库也不会阻止对象按保留策略清理。
uploads gallery create --title "Settings redesign"
uploads gallery add gal_example screenshots/app/settings-before.webp screenshots/app/settings-after.webp
uploads put ./after.png --gallery gal_example --alt "Updated settings page"
uploads gallery show gal_example
uploads gallery link gal_example --github buildinternet/uploads#58
uploads gallery list --github https://github.com/buildinternet/uploads/pull/58
gallery add 会按顺序处理键,因此会在每次变更前获取当前的乐观版本。使用 --json 时,稳定的 added 和 failures 数组可让 Agent 安全地检查部分失败。一个工作区最多可有 100 个活动图库;每个图库最多允许 100 个条目和 20 个关联的外部引用。删除图库只会移除图库记录,不会删除对象。
可选择使用 uploads gallery link <gallery-id> --github <owner/repo#number> 将图库关联到 GitHub issue 或 PR。CLI 也接受严格格式为 https://github.com/<owner>/<repo>/issues|pull/<number> 的 URL。使用 uploads gallery list --github <coordinate-or-url> 可进行经过身份验证的反向查找。这只是元数据操作:不会使图库变为私有,也不会改变其不透明标识。
变更动态
动态页是一个公开的、按时间倒序显示已添加 gh.repo(以及可选的 gh.number)标签的截图页面。两者属于同一种产品,只是多了一个筛选条件。它是实时查询结果,而非精心整理的图库。任何知道 URL 的人都可以查看。
应创建哪种内容:
- 图库——由你选择并排列文件(
uploads gallery create)。 - 仓库动态——显示某个仓库中的最新截图。用于创建项目级分享链接。
- PR(或 issue)动态——相同的动态,但限定为某个拉取请求。适用于只希望审阅者看到该 PR 内容的情况。
uploads feed create owner/repo
uploads feed create --repo owner/repo
uploads feed create --repo owner/repo --pr 123
uploads feed create --github owner/repo#123
uploads feed create --repo owner/repo --path /settings
uploads feed show feed_example
位置参数 owner/repo 等同于 --repo。不同仓库对应不同动态。--pr 和 --issue 接受一个编号,并且需要指定该仓库(或当前 Git 远程仓库)。--github 接受 owner/repo#123 或 GitHub issue/PR URL,也可以直接提供仓库。--pr、--issue 和 --github 不能同时使用。可选的 --path 可与其中任意参数组合使用。
每张截图也都有一个 /c/<id>/<item> 页面,其中包含上一张/下一张导航和 N 项中的第 1 项计数。同步托管的 PR 评论会在需要时创建该 PR 专属动态(与 --pr 一样,创建操作具有幂等性),并将每张图片的点击跳转目标设为条目页面。<img src> 仍指向嵌入 URL。独立的 /f/… 页面仍可用于一次性分享。
MCP(stdio 和托管模式):使用 feed_create,并提供 repo,以及与上述标志对应的 pr/issue/github/path。feed_get 接受 feedId。再次创建相同范围的动态时,会返回现有 URL。
在 GitHub PR 或 issue 中嵌入
有两种方式,具体取决于你需要持久 URL、托管评论,还是两者兼有。
选项 A — 稳定附件 URL(--pr / --issue)
为文件提供不含哈希的稳定键,因此重新上传会覆盖原文件,且此 URL 适合硬编码到之后还会编辑的 PR 正文中:
uploads put ./after.png --pr 123 --alt "Dashboard after"
# key: gh/<owner>/<repo>/pull/123/after.webp → stable public URL (PNG optimized to WebP)
--issue <num> 会在 .../issues/<num>/ 下执行相同操作。<owner>/<repo> 来自 --repo 或 git 远程地址。--pr/--issue 不能与 --key、--ref 或 --prefix 同时使用(键的布局固定),并且两者互斥。
这些键是有意设计成可预测的:其中包含所有者、仓库、PR 或议题编号,以及文件名。uploads.sh 不会检查 GitHub 可见性,因此私有或内部仓库不会让上传的文件变为私有文件。使用此模式前,请确认媒体内容适合放在公开且可猜测的 URL 中;否则请先将其脱敏,或不要上传。
如果上传用的 GitHub App 能确认目标仓库为私有仓库,此键布局会自动改变:键会变为 gh/private/<id>/...,其中 <id> 是按分支(议题则按仓库)生成的随机 ID,而不是可推导出的 gh/<owner>/<repo>/...。无需任何标志——公开仓库以及 App 无法查看的仓库仍会使用可推导的布局。此 URL 仍无需身份验证且持久有效,并不受访问控制——任何获取该 URL 的人都能读取文件,除非你使用 uploads github rotate-prefix --branch <branch>(议题/已导入资源则使用 --repo-level)轮换 ID。完整威胁模型请参阅 docs/private-attachments.md。
然后在你使用 gh 编写的 PR/议题 Markdown 中引用 embed URL(CLI --format markdown / MCP markdown 已会这样处理)。描述为可选内容——适合放在说明中的视觉素材可写在这里;其他内容已在托管评论中:
<img width="700" alt="Dashboard after" src="https://embed.uploads.sh/default/gh/myorg/myapp/pull/123/after.webp">
如果需要在 GitHub 之外分享一个持久链接,请保留 url(存储主机)。
put --pr/--issue(以及下文的 uploads attach)会自动将 gh.repo/gh.kind/gh.number/gh.ref 写入可查询的元数据,因此 uploads find gh.ref=myorg/myapp#123 或 uploads list --meta gh.repo=myorg/myapp 无需 gh/... 前缀,就能找到附加到该 PR/议题的所有内容。可添加 --meta k=v 作为额外的自定义键值对——与目标自身的 gh.* 值冲突时,--meta gh.* 覆盖值不会生效。它还会在本地 gh 能解析 PR/议题标题时,将实际标题写入 gh.title(尽力而为;如果 gh 无法解析标题,则省略该字段,不会因此导致上传失败)。
选项 B — 由工具管理的附件评论(--pr/--issue 默认启用,或使用 comment)
默认情况下,put --pr/--issue(与 attach 一样)会上传文件,并且在 PR/issue 上创建或更新一条由标记管理的评论——无需额外标志。评论会将未归类的 gh/... 附件与链接到该 PR/issue 的所有公开图库分别放在清晰分隔的区块中,并以内嵌形式展示最多三张可用的图库图片。未粘贴到描述中的暂存文件也会显示在这里。它会通过顶部的隐藏 HTML 注释(``)找到自己之前发布的评论,并直接编辑该评论——绝不会触碰描述或其他评论。机器人发布的评论来自 uploads-sh[bot]。本地 gh 回退方案使用相同的标记,并添加提示安装 App 的页脚:
uploads put ./after.png --pr 123
传入 --no-comment 可跳过同步(仅上传),与 attach --no-comment 的行为一致。put 仍接受 --comment,但它不执行任何操作——现在同步是默认行为,这个选项已多余;保留它只是为了兼容在此改动前编写的脚本(#537)。
上传是权威操作;评论同步则尽力而为——如果缺少 gh 或未通过身份验证,上传仍会成功,但你会收到警告。若要在不上传任何内容的情况下(例如,在多次使用 --pr 上传或添加图库链接后)重新同步评论,请使用独立命令:
uploads comment --pr 123
uploads comment --issue 45 --repo buildinternet/uploads
删除了错误的截图?使用 delete 删除相应对象。删除 gh/…/pull/ 或 gh/…/issues/ 键时,如果机器人可以发布,就会刷新由工具管理的评论;如果不能,则会打印 uploads comment --pr <n>(或 --issue)。如果仍需刷新,请自行重新运行 comment。当最后一个附件和图库都被移除后,由工具管理的评论会原地改写为中性的空状态(No attachments are currently associated with this pull request.)——评论绝不会被删除(后续上传会重新填充评论),也不会仅仅为了说明内容为空而创建评论:
uploads delete gh/owner/name/pull/123/after.png # remove the asset
uploads comment --pr 123 # comment now shows the empty state
超过 16 张内嵌图片后,评论会将其余图片折叠到 <details> 链接列表中,让截图很多的 PR 仍然易于阅读。共享仓库中的每个工作区都有自己的由工具管理的评论(在底层使用命名空间),不会覆盖其他工作区的评论——使用 uploads github link(见下文)查看或设置某个仓库绑定到哪个工作区。
仓库绑定(uploads github link / unlink / doctor)
受管理评论和 Webhook 自动晋升功能会通过“先声明者优先”的方式将仓库绑定到工作区。通常,这种绑定会在首次调用 comment / put --comment / promote 时隐式创建。检查、声明或释放绑定:
uploads github link # claim the current repo for this workspace
uploads github link --repo owner/name # claim a specific repo
uploads github link --status # read-only: show the current binding, don't claim
uploads github unlink --repo owner/name # release a binding this workspace owns
uploads github doctor # check the App itself (config + webhook events)
声明已绑定的仓库不会抢走绑定;命令会报告现有所有者。unlink 仅释放由当前工作区拥有的绑定;如果仓库属于其他工作区,则会返回 403(操作员可以改为从管理面板重新分配或移除绑定)。在不支持这些路由的旧版/可自托管服务器上,这些命令会失败,并明确提示“server does not support repo bindings/GitHub App health check yet”。
声明尚未绑定的仓库需要经过授权,而不只是先到先得(issue #297):服务器只有在工作区关联的 GitHub 账户拥有该仓库的推送权限(或更高权限)时,才允许工作区首次声明该仓库。服务器会通过 App 的安装令牌,实时向 GitHub 检查权限。没有关联 GitHub 身份的令牌(包括旧版、注册用/共享令牌以及 default 的令牌)永远无法声明新仓库,但仍可正常用于已绑定到该令牌的任何仓库。如果检查失败,声明操作会返回 claimed: false, reason: "not_authorized";请关联一个对该仓库拥有推送权限的 GitHub 账户,或请操作员从管理面板显式绑定仓库。
comment / attach --comment 返回 not_authorized,表示仓库已绑定到其他工作区,或者仓库尚未绑定且无法验证当前工作区有权声明它(见上文)。无论哪种情况,都会直接拒绝,而不是降级处理;与其他机器人发帖失败的情况不同,此时 CLI 不会改用本地 gh 发帖。运行 uploads github link --status 查看当前绑定(如果有),切换到关联了有权访问该仓库的 GitHub 账户的工作区,或请操作员显式绑定仓库。
uploads github doctor 会检查 App 自身的配置和 Webhook 事件订阅(需要 issues + pull_request;建议启用 issue_comment,这样被删除或损坏的机器人评论可以自动修复,而不必等到下一次 PR 推送)。当 Webhook 驱动的行为(自动晋升、标题更新、自我修复)似乎悄无声息地失效时,此命令会很有用。
镜像 GitHub 原生附件(uploads ingest)
直接通过 github.com/user-attachments/… 上传到 PR/issue 的图片,只存在于 GitHub 自己的身份验证托管服务中,永远不会成为公开 URL。同一命令还会复制公开的 Cursor 云 Agent 重写 URL(cursor.com/artifacts/c/art-*)。查看器(/artifacts/v/)和需要登录的 Agent 页面链接会被跳过;请启用 Allow posting artifacts to GitHub,让 PR 获得公开的 /c/ URL。uploads ingest --pr <n>(或 --issue <n>)会扫描描述和评论中的此类媒体,将尚未镜像的媒体复制到工作区(建立索引,但不会添加到受管理评论中),并解除已不再被引用的媒体关联;如果之后重新关联,则会恢复关联,而无需重新获取:
uploads ingest --pr 123
uploads ingest --issue 45 --repo owner/name --json
仓库必须关联到工作区(uploads github link),并且已安装 GitHub App;否则命令会明确报错,而不会自行猜测。这是手动导入/补录的入口。关联仓库的正文或评论在创建或编辑时,App Webhook 会自动执行相同的导入操作,其中也包括只含有公开 cursor.com/artifacts/c/art-* URL 的正文。.uploads.yml 中的 ingestGithubAttachments 开关仅控制 Webhook 路径,不影响直接运行 ingest。
嵌入最佳实践
- 始终使用有意义的替代文本——关闭图片的读者和搜索引擎都能看到它。
- 使用
--width限制宽度,避免大图占据页面太多空间。 - 前后对比最好并排展示在表格中:
| Before | After | | ------------------------------------ | ----------------------------------- | | <img width="380" src="…/before.png"> | <img width="380" src="…/after.png"> | - 优先将正文写入文件,然后使用
gh pr edit --body-file/gh issue comment --body-file,而不是使用内联 HEREDOC。 - 上传主机不受限制——相同的 URL 可用于 issue、PR 评论、讨论和普通 Markdown 文档。
- 附加到 PR 时,优先使用简短、压缩后的视频片段(或动画录屏),而不是大型原始视频文件。每个文件和工作区的存储上限仍然适用,而且几分钟的未压缩录屏无论如何都不适合作为 PR 嵌入内容——请剪辑出相关片段并压缩后再上传。
管理上传内容
uploads list --prefix screenshots/ # list objects (key + url)
uploads list --pr 123 # everything attached to a PR
uploads list --meta app=myapp # filter by metadata (repeatable, ANDed)
uploads list --name hero --meta app=web # filename substring (+ optional meta)
uploads find app=myapp path=/settings # same filter, human-friendly positional pairs
uploads find hero # filename substring alone
uploads list --all --json # paginate fully, machine-readable
uploads meta get <key> # show an object's metadata
uploads meta set <key> k=v [k=v…] [--delete k]… # merge-set / delete metadata pairs
uploads meta set <key> --meta k=v # same, in put/list's flag spelling
uploads meta keys # discover workspace metadata keys
uploads meta values <meta-key> # distinct values for one key
uploads delete <key> # remove an object
uploads delete <key> --dry-run # show what would be deleted
uploads usage # storage / monthly upload counters (+ limits)
uploads reconcile # rebuild ledger from storage
uploads purge-expired # delete past retentionDays (if set)
uploads health # API liveness (no auth)
uploads doctor # version + health + auth + workspace + usage
uploads changelog # recent product updates + link to uploads.sh/changelog
uploads changelog --json
uploads docs # catalog of public docs
uploads docs "stage before a PR" # search
uploads docs attach # fetch one page as markdown
uploads docs --json
uploads --version
遇到问题时,首先运行 doctor——它会报告已安装的 CLI 版本,区分 API 无法访问、令牌无效、工作区不匹配、本地 URL 与生产环境 URL 不一致等情况,并给出针对性的提示。
破坏性操作预览:delete 支持 --dry-run。purge-expired 暂不支持(#78);可通过 list / usage 和保留设置进行预览。
配置命令
设置一次共享默认值,避免每次都传入标志:
uploads config show # effective settings (token redacted)
uploads config path # resolved config file path
uploads config set UPLOADS_DEFAULT_REPO myorg/myapp
uploads config set UPLOADS_DEFAULT_WIDTH 700
uploads config init --api-url http://localhost:8787 --workspace acme --token up_acme_…
init 只写入你传入的键。不带标志时,它只会写入 UPLOADS_API_URL,并且会有意不设置工作区:配置文件中的 UPLOADS_WORKSPACE 优先于令牌中编码的工作区,因此写入该值会将后续所有 uploads login 固定到此工作区。想要固定工作区时,请传入 --workspace。
可识别的键:UPLOADS_API_URL、UPLOADS_WORKSPACE、UPLOADS_TOKEN、UPLOADS_DEFAULT_PREFIX、UPLOADS_DEFAULT_REPO、UPLOADS_DEFAULT_REF、UPLOADS_DEFAULT_WIDTH、UPLOADS_NO_GIT、UPLOADS_NO_OPTIMIZE、UPLOADS_KEEP_EXIF、UPLOADS_NO_AUTO_META、UPLOADS_SCREENSHOT_VIA、UPLOADS_NO_NUDGE。
也会读取(仅限环境变量,不是配置文件键):UPLOADS_EMBED_PUBLIC_BASE_URL、UPLOADS_OVERWRITE。
本地开发
指向本地运行的 API(pnpm dev 会在 :8787 上提供服务)。使用 workspace:add --local 创建的令牌只能用于 localhost;生产环境令牌需要设置 UPLOADS_API_URL=https://api.uploads.sh。doctor 会标记这种不匹配。
uploads --api-url http://localhost:8787 doctor
说明与注意事项
-
上传内容公开且实际上永久保留,直到删除为止。GitHub 仓库的可见性并非访问控制:私有/内部仓库的 PR 和 issue 附件仍然公开,而且
gh/<owner>/<repo>/pull|issues/<num>/<filename>键可预测。切勿上传机密、令牌、包含敏感数据的内部仪表板,或截图中可见的客户 PII——请先裁剪/遮盖。 -
**边缘缓存 / 双主机:**稳定的
url响应带有Cache-Control: max-age=60。对于 GitHub,请使用embedUrl(无缓存主机),以便覆盖内容通过 Camo 传播。优先使用 CLI/MCP 的markdown,而不是手动将存储 URL 写入 PR 正文。参见仓库中的docs/ops.md(双公共主机)。 -
退出代码:
2表示用法/令牌/文件错误,3表示身份验证/策略错误,4表示网络错误,1表示其他错误。--json会输出{error,code,status}——请根据code分支处理。脚本格式(json|url|markdown)也会在 stdout 上输出失败信息。用法错误:hint: uploads <cmd> --help。 -
**错误信息保持简短(输出到 stderr),因此截断输出也不会隐藏错误。**缺少参数时,会输出一行
error:、一条可运行的示例(uploads put ./shot.png --pr 123)以及该提示——不会输出命令帮助。使用--json时,示例会作为example一并返回。命令拼写错误时,会输出错误、一行did you mean: uploads <cmd>以及帮助指引;使用--json时,会在 stdout 上返回{error,code:"USAGE",didYouMean}。请查看第一行;运行uploads <cmd> --help获取完整帮助。 -
**更新提示(stderr):**成功的人类可读输出中可能会提示有较新的 npm 版本(每日检查)。使用
--quiet/--json/UPLOADS_NO_UPDATE=1可关闭提示。 -
**遥测:**匿名命令名称 ping(不包含路径/令牌)。使用
UPLOADS_TELEMETRY_DISABLED=1、DO_NOT_TRACK=1或uploads telemetry disable选择退出。 -
**报告:**仅在用户提出要求时发送——
uploads report "what broke"或--file ./trace.log。切勿自动发送日志。MCP 工具:report。 -
MCP:
uploads mcp(stdio)提供与 CLI 对应的工具;托管 MCP 位于https://agents.uploads.sh/mcp——当 Agent 没有本地文件系统或 git 检出目录可供运行命令时,应使用它(发送 base64 内容;如果文件已位于公共 HTTPS URL,则使用contentUrl)。身份信息:whoami(工作区 + scopes;也用于确认服务已启动)。产品更新:changelog(与uploads changelog相同;无需身份验证)。公共文档:search_docs(与uploads docs相同;无需身份验证)。变更信息流:feed_create/feed_get(与uploads feed create/feed show相同)。 传入repo可创建覆盖整个仓库的信息流;传入repo+pr(或将github设为owner/repo#123/ GitHub URL)可将同一信息流限定到单个拉取请求;issue对 issue 执行相同操作。元数据:get_metadata/set_metadata/find_files/list_metadata_keys(分别与meta get/meta set/find/meta keys|meta values相同)。find_files可选接受name(文件名子串),可与filters一起使用或单独使用。托管 MCP 还提供list_activity和list_repo_files(本地uploads mcp服务不提供)。list_activity会列出最近带有 GitHub 标签的活动。by可设为pull(默认)或repo。Pull 行是接收过带有gh.kind=pull标签媒体的拉取请求,按last_media_at从新到旧排序;除非all为 true,否则仅显示最近 90 天的数据。只有当该仓库已关联到此工作区时,pull 的title才会显示。Repo 行是不同的gh.repo值,按元数据updated_at从新到旧排序。list_repo_files必须传入repo,并可选传入pr、path和type(screenshot、video或other;PDF 属于other)。它会返回该仓库中带标签的文件,按元数据updated_at从新到旧排序,并包含url和embedUrl。这些时间不是对象的uploaded-at。未加标签的上传内容不会显示。这两个工具都不会发布/c/<id>信息流。若需要该公共页面,请使用feed_create。 标准输入/输出和托管版put都支持在一次调用中处理多个文件。标准输入/输出将files作为路径传入。托管版接受files: [{ filename?, contentBase64 | contentUrl, alt? }](每次最多 20 个)。 使用contentBase64时必须提供文件名;使用contentUrl时,如果 URL 路径包含末级名称,则文件名可选。每项的alt会覆盖顶层的alt。结果为{ uploads, failures },包含每项的处理结果。托管版contentUrl仅支持 HTTPS,不会转发身份验证信息,并会拒绝 私有/内部主机。GitHubuser-attachmentsURL 不是公开 URL, 获取会失败。uploads install会安装此技能和托管 MCP (Claude Code、Codex 和 Grok;缺少的 CLI 会跳过),以及 Grok/Cursor 钩子(简短进度信息;可使用--verbose/--dry-run)。 Claude 和 Codex 通过各自的插件提供相同的 PR 前提醒 (uploads hook pre-pr-screenshot)。如果 CLI 不在PATH中,钩子会静默地不执行任何操作。托管 MCP
put的评论功能一致性。 托管版put工具 接受pr/issue(二者互斥,与 CLI 的--pr/--issue一致),以及必填的repo(owner/name——托管服务没有 git 上下文可供推断), 从而生成与 CLI 相同的稳定gh/…键。提供pr/issue时,默认会发布/更新由uploads-sh[bot]管理的附件评论,与 CLIput --pr相同(#537)——传入comment: false可跳过。此服务仅支持机器人,不会回退到本地gh; 默认同步需要files:read权限范围,如果令牌只有写入权限,则会静默跳过(显式传入comment: true则会报错)。 如果调用方要将结果粘贴到 PR/issue 中,请优先使用pr/issue,而不是只 返回原始url/embedUrl:这样可以获得稳定的原位覆盖键,并直接将结果放入汇总附件评论,而不是生成 需要调用方手动嵌入的一次性链接。评论失败不会导致上传失败——失败原因会如实写入结果的comment字段,取值为not_installed(未在仓库安装 App)、not_authorized(仓库 绑定到其他工作区,或尚未绑定且此工作区无权认领该仓库),或forbidden(已安装 App,但尚未批准 Issues/PR 写入 权限——其中会包含指向组织权限审核 页面的fixUrl),绝不会作为抛出的工具错误返回;意外错误会单独显示为commentError。托管 MCP 独立的
comment工具。 托管服务还提供一个comment工具({ repo, pr | issue };由于同样没有 git 上下文,repo为必填项),用于刷新管理评论,而不 重新上传——相当于 CLI 的uploads comment。删除资源后可用它重新同步:先通过delete删除gh/…键,再调用comment。 当最后一个附件和图库都已删除时,评论会在原位改写为中性的空状态(不会删除评论,也不会创建空评论)。此工具仅支持机器人,且会像put的comment字段一样如实返回拒绝原因。该工具和带pr/issue的put都会遵循目标仓库的.uploads.yml(与机器人路径相同,无需单独的 MCP 配置;参见 https://uploads.sh/docs/comment-config)。标准输入/输出 MCP
attach晋升功能一致性。 标准输入/输出的attach工具与 CLI 的uploads attach一致:提供pr时,会尽力晋升当前分支中已暂存的文件,与attach --pr相同(结果位于promotion/promoteError中,结构与put的托管评论字段相同)。fromBranch会覆盖源分支,与--from-branch一致。noPromote可选择不执行晋升,与--no-promote一致。托管 MCP:分支暂存与晋升。 托管服务上没有
attach工具 (不支持文件系统路径)——请改用put:# Stage pre-PR (CLI attach --branch parity). repo + branch required. put { contentBase64, filename, repo: "owner/name", branch: "feature/x", state: "after" } put { contentUrl: "https://cdn.example/after.png", repo: "owner/name", branch: "feature/x", state: "after" } # → key gh/owner/name/branch/feature-x/<filename>, gh.status=staged # Drop staged files without deleting them (CLI uploads unstage). unstage { repo: "owner/name", branch: "feature/x" } unstage { keys: ["gh/owner/name/branch/feature-x/after.png"], dryRun: true } # Promote staged files into a PR once it exists (CLI attach --promote). promote { repo: "owner/name", pr: 123, branch: "feature/x" } # optional comment: false to skip the managed comment refresh (default on) # Or attach a new file to the PR and promote that branch in one call: put { contentBase64, filename, repo: "owner/name", pr: 123, branch: "feature/x" }托管 MCP:检查暂存内容。 托管服务没有专用的
staged工具——它没有本地 git 上下文可用于设置默认branch,因此始终需要调用方提供自己的repo/branch。list_activity和list_repo_files显示的是最新标记文件, 而不是此处的暂存分支视图。要回答“暂存了什么?”,请改用现有 工具:list { prefix: "gh/<owner>/<repo>/branch/<branch>/" } # or find_files { filters: { "gh.branch": "<branch>" } }find_files会在每个匹配结果中内联返回元数据,因此gh.staged-at无需再次调用即可显示 最近活动时间;如果你正在使用的多个仓库中分支名称不唯一,请在筛选条件中添加"gh.repo": "<owner>/<repo>"。对于绑定问题(“这些文件会自动附加吗?”),请使用托管版的repo_link_status工具(issue #422):repo_link_status { repo: "<owner>/<repo>" }它会返回
{ binding: "self" | "other" | "none" }:"self"表示此 仓库已绑定到此工作区,暂存文件会自动附加;"other"表示它已绑定到其他工作区,不会自动附加——且刻意不会透露该工作区的名称;"none"表示此仓库尚未绑定。 -
Worker 端的 Agent: 此软件包还会从
@buildinternet/uploads/agent导出createUploadsWorkerFileTools(),用于在 Worker 中将上传、列出和删除功能作为 AI-SDK 工具提供——这只适用于 构建在服务器上运行的 Agent 工具,并不适用于日常 PR 嵌入。