mcpField
U
uploads-cli

uploads-cli

github.com/buildinternet开发1995 次安装今天更新
代码仓库

关于 uploads-cli

uploads-cli 是一项技能,介绍如何将文件上传至 uploads.sh 并附加到 GitHub 拉取请求或议题。内容涵盖本地 uploads CLI 和托管 MCP,包括暂存、元数据、托管评论及文件审批。

主要功能

  1. 01上传文件并返回公开 URL
  2. 02向 GitHub 拉取请求和议题附加文件
  3. 03在拉取请求创建前暂存文件
  4. 04按元数据查找文件并管理元数据
  5. 05审批被暂扣审核的文件

适用场景

  1. 01向拉取请求添加截图或报告
  2. 02暂存分支文件以便稍后提升
  3. 03按元数据查找已上传文件
  4. 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 putfiles: [{ filename, contentBase64 }]。传入 repo + (pr | branch)。不推断 git 信息。
文件已位于公开 HTTPS URLCLI put --url 或托管 MCP putCLI: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.statebinding.autoAttach含义
selftrueRepo 已绑定到此工作区 — 暂存文件会在 PR 打开时自动附加。
nonefalseRepo 尚未关联 — 请关联它(uploads github link),否则不会自动附加任何文件。
otherfalseRepo 已关联到另一个工作区 — 这些文件不会从此处自动附加。
unknownfalse绑定检查失败(离线,或较旧的服务没有此路由)— 仅供参考,绝不会阻止查看。

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:

字段主机(默认)用途
urlstorage.uploads.sh持久链接、点击访问、非 GitHub 嵌入
embedUrlembed.uploads.shGitHub 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 --version
    
    本技能中的所有示例都使用全局 uploads … 二进制文件(即安装后的用法)。仅在 uploads monorepo 中,pnpm uploads … 会先从本地源码构建——不要用这种方式编写产品或 PR 示例。 对脚本化步骤,优先使用 --json 或 --quiet(保持 stderr 清爽,并跳过可选的更新提示)。
  • 已配置的令牌(一次性设置,见下文)。使用 uploads doctor 检查。
  • 已完成身份验证的 gh CLI——仅 --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)。默认不加边框。

键的工作方式——只有三种路径,不提供额外的命名模式:

用途命令
只需上传并获取 URLuploads 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自动 — 仅限 locallocal
theme自动 — 仅在强制指定时dark
viewport自动 — 捕获选项 / EXIF1280x800@2x
device自动 — 图像 EXIFApple iPhone 16 Pro
software自动 — 图像 EXIFFigma
captured自动 — 图像 EXIF2026-07-20T20:35:39
state由你指定 — --statebefore | after
app由你指定 — --appweb | 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,不会转发身份验证信息,并会拒绝 私有/内部主机。GitHub user-attachments URL 不是公开 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] 管理的附件评论,与 CLI put --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 嵌入。

评论

支持 Markdown