uploads-cli
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUploading files to uploads.sh and embedding in GitHub
将文件上传至uploads.sh并嵌入GitHub
What this does and why
功能与用途
GitHub's native image hosting () is only reachable
through an authenticated browser session — there is no CLI or REST endpoint
for it. So any image URL you put in a PR/issue body written with
must already point at something publicly hosted.
github.com/user-attachments/…ghgh … --body-fileThis skill covers both transports: the CLI (local files, git,
localhost) and the hosted MCP at (bytes you
already have, no checkout). Both PUT to the uploads.sh API and return a stable
public URL plus ready-to-paste markdown. For PRs and issues the managed
attachments comment is available on both — CLI via local as a fallback,
hosted MCP bot-only.
uploadshttps://agents.uploads.sh/mcpghGitHub原生图片托管服务()仅支持通过已认证的浏览器会话访问——没有对应的 CLI或REST端点。因此,使用编写PR/issue正文时,其中的图片URL必须指向已公开托管的资源。
github.com/user-attachments/…ghgh … --body-file本技能涵盖两种传输方式: CLI(适用于本地文件、git仓库、localhost资源),以及托管在的MCP服务(适用于已获取字节数据、无需代码检出的场景)。两者均会向uploads.sh API执行PUT请求,并返回稳定的公共URL及可直接粘贴的Markdown代码。针对PR和issue,两种方式均支持托管附件评论功能——CLI会以本地作为备选方案,托管MCP则仅通过机器人实现。
uploadshttps://agents.uploads.sh/mcpghMCP vs CLI
MCP与CLI对比
Same product, two transports. Skills do not install a binary.
| Need | Use | Why |
|---|---|---|
| Bytes already in context (ChatGPT attachment, base64) | Hosted MCP | |
| List, find, metadata, comment, promote | Either | Hosted: |
| Local path or current-branch attach | CLI | Hosted server has no filesystem and no |
| CLI | Remote render cannot reach your machine. |
| Selector annotate on a live page | CLI | Remote backend rejects selector-bearing specs. |
| Neither transport | Stop | Do not treat |
CLI examples in the rest of this skill assume a checkout and the
binary. Hosted tool contracts live under Notes and cautions (the MCP
bullet) below.
uploadsFor the common case, use . It infers the current branch's
PR, uploads every file under stable attachment keys (in parallel), and maintains
the comment by default. One bad file does not block the rest — JSON includes
and (exit when any failed):
uploads attach <file...>uploadsfailures1bash
uploads attach ./before.png ./after.png
uploads attach ./shot.png --issue 45 --repo buildinternet/uploadsPass when only stable URLs are wanted. Use for lower-level
naming and output control.
--no-commentputStage as you go, before a PR exists. stages files under
instead of a PR/issue number — same upload path, no target flags, no comment
(there's nothing to comment on yet). With no value, resolves the
current git branch; in the name sanitizes to . Attach this way at every
visual milestone during the work, not just once at the end. Staged files carry
until promotion flips them to , so
(add to narrow) lists what's
still in flight. The server also stamps / (from
the token's minting user) on gh.*-tagged uploads, so
narrows to one
contributor's in-flight files.
uploads attach ./shot.png --branch [name]gh/<owner>/<repo>/branch/<branch>/<filename>--branch/-gh.status=stagedpromoteduploads find gh.status=stagedgh.branch=<name>gh.uploadergh.uploader-iduploads find gh.status=staged gh.uploader=<login>Check what's staged: . A dedicated read-only view —
"what's staged for this branch, and will it auto-attach?" — instead of
hand-building the / query above:
uploads stagedfindlistbash
uploads staged # current branch, repo from gh/git remote
uploads staged --branch feature/thing --repo owner/name
uploads staged --format jsonSame branch/repo resolution as (current git branch by
default, worktree-safe). Human mode prints one compact line per staged file
(filename, size, , public URL), then a line and
(the promote line is omitted
for — promoting from a non-owning workspace would be
rejected by the cross-tenant gate). Nothing staged prints a
single zero-state line. (or global ) always emits a
valid document — — even with zero files;
is , never empty stdout.
attach --branchgh.staged-atbinding:once the PR exists: uploads attach --promotebinding: other--format json--json{ repo, branch, files, binding }files[]binding | | Meaning |
|---|---|---|
| | Repo is bound to this workspace — staged files auto-attach on PR open. |
| | Repo isn't linked yet — link it ( |
| | Repo is linked to a different workspace — these files won't auto-attach from here. |
| | Binding check failed (offline, or an older server without the route) — advisory only, never blocks the view. |
The / wording is the exact same advisory text as the
stage-time warning (issue #398) — one source of truth, so
the two surfaces never drift.
noneotherattach --branchLocal stdio MCP mirrors this as the tool (/ args,
same shape). The hosted MCP has no
dedicated tool (no git defaults) — list/find_files recipes and
hosted / with explicit / are under Notes and
cautions (the MCP bullet) below.
stagedbranchrepo{ repo, branch, files, binding }stagedputpromoterepobranchGetting those files into the PR's attachments comment needs no extra step
once a PR exists for that branch:
- GitHub App installed on the repo: a webhook auto-promotes staged files into the PR's attachment prefix and creates/updates the managed comment the moment the PR opens, reopens, or gets a new commit.
- No GitHub App: the next targeting that PR auto-promotes those staged files into the PR's attachment prefix before the comment refresh. If that first attach has nothing new to upload, run
uploads attach(zero file arguments) to promote and refresh the comment on its own; it exitsuploads attach --promoteeven when nothing was staged. Skip auto-promotion on a given call with0.--no-promote
Promotion only applies to PRs, never issues, and both paths degrade silently
(no error) if the workspace's server doesn't support promotion yet.
Promotion needs the repo already bound to the workspace. Both the webhook
and the CLI-triggered path above rely on the same repo↔workspace binding used
by the managed comment (see "Repo binding" below) — any earlier successful
//promote call against that repo binds it implicitly, or
claims it explicitly. A repo that has never been
bound and is only ever staged with sees no error and no comment —
promotion is a silent no-op at PR-open time. If you can't confirm the repo is
already bound, don't promise auto-attach; the zero-setup fallback that works
regardless of binding history is running (or any
targeted ) once the PR exists.
attachcommentuploads github link--branchuploads attach --promoteuploads attachComment missing? First, give it a moment — if the App is installed and
subscribed to , a deleted or mangled bot comment self-heals on
the next webhook delivery; don't panic-repost. If it's still missing, check
the repo↔workspace binding — (read-only, shows
the binding without claiming it). See "Repo binding" below.
issue_commentuploads github link --statusThe killer feature for GitHub: / produce hash-free, stable keys
(), so re-uploading the same filename overwrites
in place and the URL never changes. There is no confirmation prompt — hot-swap is
intentional for agents and re-runs. Human mode prints
after overwrite; JSON has
. Use to preview: it prints
when the key already exists,
without writing.
--pr--issuegh/<owner>/<repo>/pull/<num>/<name>>> replaced existing object (same URL)"replaced": true|false--dry-run>> would replace existing object (same URL)Every other key is strict (issue #174): an explicit , or the
default path with no /, refuses to overwrite an existing
object — the CLI error names the existing object's URL and tells you to add
(MCP: ). Set to restore
always-overwrite for those paths. previews the refusal too:
. This never applies to /
keys, which always hot-swap regardless.
--keyput--pr--issue--replacereplace: trueUPLOADS_OVERWRITE=1--dry-run>> would refuse: key already exists--pr--issueResponses include two public URLs when the
shared dual-host setup applies:
| Field | Host (default) | Use for |
|---|---|---|
| | Durable link, click-through, non-GitHub embeds |
| | GitHub PR/issue markdown ( |
embedUrlmarkdownembedUrlUPLOADS_EMBED_PUBLIC_BASE_URL两者属于同一产品的不同传输方式,技能无需安装二进制文件。
| 需求场景 | 使用方式 | 原因说明 |
|---|---|---|
| 已获取字节数据(如ChatGPT附件、base64编码) | 托管MCP的 | 传入 |
| 列表查询、文件查找、元数据操作、评论管理、文件升级 | 任意一种方式 | 托管MCP: |
| 本地路径或当前分支附件上传 | CLI | 托管服务器无文件系统,也无 |
| CLI的 | 远程渲染无法访问你的本地机器。 |
| 对实时页面进行选择器标注 | CLI的 | 远程后端会拒绝包含选择器的请求。 |
| 无需上述两种传输方式 | 停止操作 | 不要将 |
本技能后续的CLI示例均假设已完成代码检出并安装了二进制文件。托管工具的契约说明请查看下方注意事项中的MCP部分。
uploads常见场景下,使用命令即可。它会自动推断当前分支对应的PR,并行上传所有文件至稳定的附件密钥下,并默认维护评论。单个文件上传失败不会阻塞其他文件——JSON输出会包含和字段(若有失败则退出码为):
uploads attach <file...>uploadsfailures1bash
uploads attach ./before.png ./after.png
uploads attach ./shot.png --issue 45 --repo buildinternet/uploads若仅需稳定URL,可传递参数。如需更底层的命名和输出控制,请使用命令。
--no-commentput在PR创建前逐步上传文件。 使用命令可将文件暂存至路径下,而非PR/issue编号路径——上传路径相同,但无需指定目标标志,也不会生成评论(此时尚无可用的评论对象)。若未指定分支名称,会自动解析当前git分支;分支名称中的会被替换为。在工作过程中的每个可视化里程碑阶段都采用这种方式上传,而非仅在最终阶段一次性上传。暂存文件会携带元数据,直至执行升级操作后变为,因此使用(可添加缩小范围)即可列出所有待处理文件。服务器还会为标签的上传文件添加/元数据(来自令牌的创建用户),因此使用可筛选出指定贡献者的待处理文件。
uploads attach ./shot.png --branch [name]gh/<owner>/<repo>/branch/<branch>/<filename>--branch/-gh.status=stagedpromoteduploads find gh.status=stagedgh.branch=<name>gh.*gh.uploadergh.uploader-iduploads find gh.status=staged gh.uploader=<login>查看暂存文件:。 这是一个只读的专用视图——显示“当前分支的暂存文件,以及是否会自动附加至PR”——无需手动构建上述/查询:
uploads stagedfindlistbash
uploads staged # 查看当前分支的暂存文件,仓库信息来自gh/git远程仓库
uploads staged --branch feature/thing --repo owner/name
uploads staged --format json分支/仓库解析逻辑与一致(默认使用当前git分支,支持工作树安全)。人类可读模式会为每个暂存文件打印一行简洁信息(文件名、大小、、公共URL),随后打印行和提示(若则省略升级提示——从非所属工作区执行升级会被跨租户网关拒绝)。若无暂存文件,则打印一行空状态信息。(或全局参数)始终会输出有效的JSON文档————即使没有文件;字段为,不会输出空内容。
attach --branchgh.staged-atbinding:once the PR exists: uploads attach --promotebinding: other--format json--json{ repo, branch, files, binding }files[]binding | | 含义 |
|---|---|---|
| | 仓库已绑定至当前工作区——暂存文件会在PR创建时自动附加。 |
| | 仓库尚未关联——执行 |
| | 仓库已关联至其他工作区——这些文件无法从当前工作区自动附加。 |
| | 绑定检查失败(离线,或服务器版本过旧不支持该路由)——仅为提示信息,不会阻塞视图。 |
noneotherattach --branch本地标准输入输出MCP也提供了对应的工具(支持/参数,返回相同的结构)。托管MCP没有专用的工具(无git默认值)——列表/文件查找方法以及托管/命令的/显式参数说明,请查看下方注意事项中的MCP部分。
stagedbranchrepo{ repo, branch, files, binding }stagedputpromoterepobranchPR创建后,无需额外步骤即可将这些文件添加至PR的附件评论:
- 仓库已安装GitHub App:PR创建、重新打开或收到新提交时,Webhook会自动将暂存文件升级至PR的附件前缀路径,并创建/更新托管评论。
- 未安装GitHub App:下一次针对该PR执行命令时,会自动升级暂存文件至PR的附件前缀路径,然后刷新评论。若首次执行
uploads attach命令时无新文件可上传,可运行attach(无需传入文件参数)单独执行升级和评论刷新;即使没有暂存文件,该命令也会返回退出码uploads attach --promote。若需在某次调用中跳过自动升级,可使用0参数。--no-promote
升级操作仅适用于PR,不适用于issue;若工作区服务器暂不支持升级功能,两种方式均会静默降级(无错误提示)。
升级操作需要仓库已绑定至工作区。 上述Webhook和CLI触发的升级路径均依赖托管评论所使用的仓库↔工作区绑定逻辑(见下方“仓库绑定”)——此前针对该仓库执行的任何成功//promote操作都会隐式绑定仓库,或通过显式绑定仓库。若仓库从未绑定,仅通过暂存文件,则不会出现错误提示,也不会生成评论——PR创建时升级操作会静默失效。若无法确认仓库是否已绑定,请不要承诺自动附加功能;无论绑定历史如何,最稳妥的零配置备选方案是PR创建后执行(或任何指定PR的命令)。
attachcommentuploads github link--branchuploads attach --promoteuploads attach评论缺失? 首先稍作等待——若已安装App并订阅事件,被删除或损坏的机器人评论会在下次Webhook触发时自动修复;不要急于重新发布。若评论仍缺失,请检查仓库↔工作区绑定情况——运行(只读,仅显示绑定状态,不会执行绑定操作)。详情见下方“仓库绑定”。
issue_commentuploads github link --status针对GitHub的核心特性:/参数会生成无哈希值的稳定密钥(),因此重新上传相同文件名的文件会直接覆盖原有内容,URL保持不变。该操作无确认提示——这是为代理和重复执行场景设计的热替换机制。人类可读模式会在覆盖后打印;JSON输出会包含字段。使用参数可预览操作:若密钥已存在,会打印,但不会执行写入操作。
--pr--issuegh/<owner>/<repo>/pull/<num>/<name>>> replaced existing object (same URL)"replaced": true|false--dry-run>> would replace existing object (same URL)其他所有密钥均为严格模式(issue #174):使用显式参数,或未指定/的默认路径,均不允许覆盖现有对象——CLI会输出错误信息,包含现有对象的URL,并提示添加参数(MCP对应参数为)。设置可恢复这些路径的自动覆盖行为。参数也会预览拒绝操作:。此规则不适用于/密钥,这类密钥始终允许热替换。
--key--pr--issueput--replacereplace: trueUPLOADS_OVERWRITE=1--dry-run>> would refuse: key already exists--pr--issue当启用双宿主共享配置时,响应会包含两个公共URL:
| 字段 | 默认宿主 | 用途 |
|---|---|---|
| | 持久链接、直接访问、非GitHub嵌入场景 |
| | GitHub PR/issue Markdown( |
embedUrlmarkdownembedUrlUPLOADS_EMBED_PUBLIC_BASE_URLPrerequisites
前置条件
- No shell / ChatGPT? Skip this section. Use the hosted MCP
() and the table above. Do not install the CLI.
https://agents.uploads.sh/mcp - Node.js ≥ 22.
- The CLI. Install globally for repeated agent use, or run it once with :
npxEvery example in this skill uses the globalbashnpm install --global @buildinternet/uploads npx @buildinternet/uploads --help uploads --versionbinary (as after install). Inside the uploads monorepo only,uploads …builds from local source first — do not write product/PR examples that way. Preferpnpm uploads …or--jsonfor scripted steps (keeps stderr clean and skips optional update-available hints).--quiet - A configured token (one-time — see below). Check with .
uploads doctor - CLI, authenticated — only for the
gh/--commentfeatures that write to a PR/issue. Plain uploads don't need it.comment
- 无Shell环境 / 使用ChatGPT? 跳过本节。使用托管MCP服务()及上述表格。无需安装CLI。
https://agents.uploads.sh/mcp - Node.js ≥ 22版本。
- CLI工具。 如需重复使用,可全局安装;或通过一次性运行:
npx本技能中的所有示例均使用全局bashnpm install --global @buildinternet/uploads npx @buildinternet/uploads --help uploads --version二进制文件(安装后即可使用)。仅在uploads单仓库内部,uploads …会先从本地源码构建——请勿以此方式编写产品/PR示例。 脚本化步骤中建议使用pnpm uploads …或--json参数(保持stderr干净,跳过可选的更新提示)。--quiet - 已配置令牌(仅需一次——见下文)。可通过检查配置。
uploads doctor - 已认证的CLI——仅适用于向PR/issue写入内容的
gh/--comment功能。普通上传无需此工具。comment
One-time setup
一次性配置
Config lives in a user-owned file so it survives skill reinstalls:
~/.config/buildinternet/config # or $XDG_CONFIG_HOME/buildinternet/configResolution is per key, first match wins: CLI flags (, ,
) → environment vars → →
→ the shared config file. For a one-off against a different
API or workspace, just export the var or pass .
--api-url--token--workspaceUPLOADS_*--env-file <path>$BUILDINTERNET_CONFIG--env-fileThe fastest path is . Have a workspace admin invite your
email to a workspace first, then run it once, interactively, to sign in:
uploads loginbash
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 oneIf the account has no workspace yet, prompts for a name and offers one
derived from your GitHub login as a bracketed default — press Enter to take it,
or type your own. Nothing is prefilled when no valid, unclaimed name can be
derived. skips the prompt entirely, which is the
form to use in scripts.
login--workspace <name> --createThat's a one-time, human-in-the-loop step (device sign-in needs a browser); once the
config file is written, every later invocation — including from a
non-interactive agent — just reads the saved token. Routine agents never need
.
uploadsADMIN_TOKENInviting a teammate (workspace admin/owner only): open the people tab under
in the browser (invite, revoke pending
invites, promote members to admin), or:
/account/workspaces/<name>/peoplebash
uploads invite create --email teammate@example.com --workspace acmeDevice login as you (not / not a workspace token). The CLI prints an
accept URL to share if email isn’t configured. Invitee accepts, then .
Workspace admins can promote existing members to admin on that people tab; only the
workspace owner can demote or remove other admins.
ADMIN_TOKENuploads loginFor headless machines with no browser at all, an operator can mint a token directly
(, -gated — see ) and hand it to the
agent as , or an enrollment code (, an alternative invite-link/code path — useful
when you don't have the recipient's email) can be exchanged with .
Neither is the normal path for new setups.
/admin/tokensADMIN_TOKENdocs/admin-tokens.mdUPLOADS_TOKENupe_…uploads login --codeThe resulting token defaults to 90 days and plus ; it cannot
delete files unless an administrator explicitly grants . Verify or inspect
setup at any time:
files:readfiles:writefiles:deletebash
uploads setup # shows effective configuration
uploads doctor # version + health + auth + workspace
uploads doctor --jsonTokens encode their workspace (), so the CLI infers
when you don't set it. Legacy administrator-minted tokens remain valid. See "Config
commands" for setting put defaults
(default repo, prefix, image width) once instead of per-command.
up_<workspace>_…--workspace配置信息存储在用户自有文件中,因此技能重装后配置不会丢失:
~/.config/buildinternet/config # 或$XDG_CONFIG_HOME/buildinternet/config配置解析优先级为按键匹配,首次匹配生效:CLI标志(、、)→ 环境变量→ → → 共享配置文件。若需针对不同API或工作区执行一次性操作,只需导出环境变量或传递参数即可。
--api-url--token--workspaceUPLOADS_*--env-file <path>$BUILDINTERNET_CONFIG--env-file最快的配置方式是运行。请先让工作区管理员将你的邮箱邀请至工作区,然后运行一次该命令,通过交互式方式登录:
uploads loginbash
uploads login # 打开浏览器进行登录授权,保存配置并运行诊断
uploads login --workspace acme # 仅当你的账户可访问多个工作区时需要指定若账户尚未关联工作区,会提示输入工作区名称,并提供基于你的GitHub登录名生成的默认名称(方括号标注)——按Enter键使用默认名称,或输入自定义名称。若无法生成有效且未被占用的名称,则不会预填充内容。参数可跳过提示,适用于脚本场景。
login--workspace <name> --create这是一次性的人工交互步骤(设备登录需要浏览器);配置文件写入后,后续所有调用——包括非交互式代理调用——都会直接读取已保存的令牌。常规代理无需使用。
uploadsADMIN_TOKEN邀请团队成员(仅工作区管理员/所有者可用):在浏览器中打开页面(邀请成员、撤销待处理邀请、将成员提升为管理员),或运行以下命令:
/account/workspaces/<name>/peoplebash
uploads invite create --email teammate@example.com --workspace acme以你的身份进行设备登录(非 / 非工作区令牌)。若未配置邮箱,CLI会打印接受链接供你分享。被邀请者接受邀请后,运行即可登录。工作区管理员可在上述人员页面将现有成员提升为管理员;仅工作区所有者可降级或移除其他管理员。
ADMIN_TOKENuploads login对于完全无浏览器的无头机器,管理员可直接生成令牌(,需权限——见),并通过环境变量传递给代理;或使用注册码(,替代邀请链接/码的路径,适用于无法获取收件人邮箱的场景),通过完成登录。这两种方式均非新配置的常规路径。
/admin/tokensADMIN_TOKENdocs/admin-tokens.mdUPLOADS_TOKENupe_…uploads login --code生成的令牌默认有效期为90天,拥有和权限;除非管理员明确授予权限,否则无法删除文件。可随时验证或查看配置:
files:readfiles:writefiles:deletebash
uploads setup # 显示有效配置
uploads doctor # 版本 + 健康状态 + 认证信息 + 工作区信息
uploads doctor --json令牌中包含工作区信息(),因此若未指定参数,CLI会自动推断工作区。旧版管理员生成的令牌仍然有效。如需设置命令的默认值(默认仓库、前缀、图片宽度),请查看“配置命令”。
up_<workspace>_…--workspaceputCore workflow: uploads put
uploads put核心工作流:uploads put
uploads putUpload one or more files and get back URL(s) plus ready-to-paste markdown.
Multiple paths upload in parallel; multi-file JSON is
(exit when any failed). Single-file JSON stays a flat object.
{ uploads, failures }1bash
uploads put ./shot.png --repo myorg/myapp --ref 1722 --alt "New live feed cards" --width 700
uploads put ./before.png ./after.pngHuman output goes to stderr; the URL and markdown to stdout, so you can pipe or
capture them. Use as the file to read from stdin.
-Key options ( for all):
uploads put --help| Flag | Purpose |
|---|---|
| Alt text for the markdown (default: filename). Always write meaningful alt text. |
| Emit sized |
| Repo segment of the auto key (default: git remote, or |
| PR/issue/branch/date segment (default: today, or |
| Typed root: |
| Key prefix (default: |
| Set the object key explicitly; skips the auto-naming below. |
| Clean filename for the key's leaf + default alt (no |
| Allow overwriting an existing object on a strict key ( |
| Resolve + print the key and final public URL without uploading; reports if the key would replace (or, on a strict key, be refused). Not with |
| Override the content type (else inferred from extension; ignored when optimize rewrites the body). |
| Opt-in chrome before optimize: |
| Address bar text for |
| How the shot fills the screen (default: |
| Skip client-side image optimization (default: still images → WebP). Or |
| Max long edge when optimizing (default: 2400). |
| WebP quality when optimizing (default: 85). |
| Keep EXIF/XMP/ICC when optimizing (default: strip for privacy). Or |
| Don't derive |
| Control stdout. |
| Override workspace (wins over env and token inference). |
Image optimization (default on): PNG/JPEG and similar still images are re-encoded to
WebP (long edge capped at 2400px, quality 85) before upload so PR/issue embeds stay
lean. The object key/filename extension follows the output (e.g. →
). EXIF/XMP is stripped by default (public URLs + privacy); pass
when the discussion needs the embedded image metadata. Animated GIF,
SVG, video, and non-images are left alone; if the optimized payload is not smaller,
the original is uploaded. Use when you need lossless originals.
shot.png…/shot.webp--keep-exif--no-optimizeFrames (opt-in): (generic bezel), , or
(community device art, cached under
). Default is no frame.
--frame phone--frame browser--frame iphone-16-pro~/.cache/uploads/framesHow keys work — three paths, no extra naming modes:
| Intent | Command |
|---|---|
| Just upload it, give me a URL | |
| Explicit typed destination | |
| Stable GitHub embed I might re-upload | |
Stable | |
| I know exactly where it goes | |
Timestamped captures break stable keys — pass to keep a
clean leaf. Use to preview the exact public URL before uploading.
--pr--name hero.webp--dry-runDefault is the fast path; you don't need , , or .
Inside a git repo, on a non-default branch, a bare now stages
automatically (issue #403) — same key/metadata as
(), so it auto-attaches to that
branch's PR when one opens. This fires whenever none of
///// is set and
isn't passed; any of those flags (or the default branch, detached
HEAD, not being in a git repo, or ) falls back to the classic
dated layout:
— the short
hash prevents collisions without random names or a separate "preserve name"
flag. Prefer (or with /) over
inventing roots — workspaces may allowlist only those destinations. Override
with only when you have a reason, and keep the key under an allowed
root. Pass // explicitly for a plain dated
upload on a branch (the opt-out).
put--key--prefix--repoputattach --branchgh/<owner>/<repo>/branch/<branch>/<filename>--pr--issue--key--ref--prefix--destination--no-git--no-git<prefix>/<repo-name>/<ref-or-date>/<basename>-<shorthash>.<ext>--destination screenshotsgh--pr--issue--key--ref--prefix--destinationOutput formats — pick what you'll consume:
bash
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}The bare-put staging note (issue #403). Since a bare on a
non-default branch now stages by default (see above), it prints a one-line
note confirming that instead of nudging you to do it yourself — human mode
writes it to stderr, adds it as an additive optional
field on the same response:
put--format jsonhinttext
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.If the same call also trips the stage-time binding warning (issue #398/#400
— the repo isn't bound to this workspace), that warning takes the
slot instead (it's the more actionable of the two); both still print on
stderr in human mode. Suppress the note (not the staging itself) with
, (env), or in the config
file ().
hint--quietUPLOADS_NO_NUDGE=1UPLOADS_NO_NUDGE=1uploads config set UPLOADS_NO_NUDGE 1The old "rerun with --pr" nudge (issue #393) still fires, unchanged, for
the narrower case a bare put still lands on the dated layout with a
detectable PR — in practice, an explicit / opting out of
staging while a PR is open for that branch:
--ref--prefixtext
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> --branchIt's best-effort (a quick lookup, bounded to 3s) — no open PR
just widens the wording to a generic . Same suppression as above.
gh pr view--pr <num>上传一个或多个文件,并返回URL及可直接粘贴的Markdown代码。多文件上传会并行执行;多文件JSON输出为(若有失败则退出码为)。单文件JSON输出为扁平对象。
{ uploads, failures }1bash
uploads put ./shot.png --repo myorg/myapp --ref 1722 --alt "New live feed cards" --width 700
uploads put ./before.png ./after.png人类可读输出会发送至stderr;URL和Markdown代码会发送至stdout,因此可通过管道或捕获方式获取。使用作为文件名可从stdin读取内容。
-关键参数(所有参数请查看):
uploads put --help| 参数 | 用途 |
|---|---|
| Markdown的替代文本(默认值:文件名)。请始终填写有意义的替代文本。 |
| 生成带尺寸的 |
| 自动生成密钥的仓库段(默认值:git远程仓库,或 |
| PR/issue/分支/日期段(默认值:今日日期,或 |
| 类型化根路径: |
| 密钥前缀(默认值: |
| 显式设置对象密钥;跳过自动命名逻辑。 |
| 密钥叶子节点的简洁文件名(无 |
| 允许覆盖严格密钥( |
| 解析并打印密钥和最终公共URL,但不执行上传;报告密钥是否会被替换(或在严格密钥下被拒绝)。不可与 |
| 覆盖内容类型(否则从文件扩展名推断;优化重写内容时会忽略该参数)。 |
| 优化前选择添加边框: |
| |
| 截图填充屏幕的方式(默认值: |
| 跳过客户端图片优化(默认:静态图片会转换为WebP格式)。或设置 |
| 优化时的最大长边尺寸(默认值:2400)。 |
| WebP格式的优化质量(默认值:85)。 |
| 优化时保留EXIF/XMP/ICC元数据(默认:移除以保护隐私)。或设置 |
| 不从git远程仓库推断 |
| 控制stdout输出格式。 |
| 覆盖工作区设置(优先级高于环境变量和令牌推断)。 |
图片优化(默认启用):PNG/JPEG等静态图片会在上传前重新编码为WebP格式(长边限制为2400px,质量为85),以确保PR/issue嵌入内容保持轻量化。对象密钥/文件扩展名会随输出格式变化(例如→)。默认会移除EXIF/XMP元数据(公共URL + 隐私保护);若讨论需要图片嵌入的元数据,请传递参数。动态GIF、SVG、视频和非图片文件不会被修改;若优化后的文件体积未减小,则会上传原始文件。如需无损原始文件,请使用参数。
shot.png…/shot.webp--keep-exif--no-optimize边框(可选启用):(通用边框)、或(社区设备素材,缓存于)。默认无边框。
--frame phone--frame browser--frame iphone-16-pro~/.cache/uploads/frames密钥工作逻辑——三种路径,无额外命名模式:
| 意图 | 命令 |
|---|---|
| 仅上传文件并获取URL | |
| 显式指定类型化目标路径 | |
| 生成可重新上传的稳定GitHub嵌入URL | |
使用稳定 | |
| 明确指定文件存储路径 | |
带时间戳的捕获文件会破坏稳定的密钥——传递可保留简洁文件名。使用参数可预览上传前的精确公共URL。
--pr--name hero.webp--dry-run默认命令是快速路径;无需使用、或参数。在git仓库内的非默认分支上执行裸命令,会自动暂存文件(issue #403)——密钥/元数据与命令相同(),因此当该分支的PR创建时,文件会自动附加至PR。当未设置/////参数,且未传递时,会触发自动暂存;若设置了上述任一参数(或处于默认分支、分离HEAD状态、不在git仓库内、传递了),则会回退至经典的日期化路径:——短哈希值可避免冲突,无需随机名称或单独的“保留名称”标志。建议使用(或结合/使用),而非自定义根路径——工作区可能仅允许这些目标路径。仅当有明确需求时才使用参数覆盖,并确保密钥位于允许的根路径下。如需在分支上执行普通日期化上传,请显式传递//参数(选择退出自动暂存)。
put--key--prefix--repoputattach --branchgh/<owner>/<repo>/branch/<branch>/<filename>--pr--issue--key--ref--prefix--destination--no-git--no-git<prefix>/<repo-name>/<ref-or-date>/<basename>-<shorthash>.<ext>--destination screenshots--pr--issuegh--key--ref--prefix--destination输出格式——选择适合你的格式:
bash
uploads put ./shot.png --format url # 仅输出URL,适用于脚本
uploads put ./shot.png --format markdown # 仅输出![]()/<img>代码片段
uploads put ./shot.png --json # {workspace,key,url,size,markdown}裸命令的暂存提示(issue #403)。由于非默认分支上的裸命令现在默认会自动暂存(见上文),因此会打印一行提示信息确认该操作,而非提示手动执行——人类可读模式会将提示写入stderr,会在响应中添加可选的字段:
putput--format jsonhinttext
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——仓库未绑定至当前工作区),则警告信息会替代字段(更具可操作性);两种信息仍会在人类可读模式下打印至stderr。可通过、(环境变量)或配置文件中的()抑制提示信息(不会影响暂存操作本身)。
hint--quietUPLOADS_NO_NUDGE=1UPLOADS_NO_NUDGE=1uploads config set UPLOADS_NO_NUDGE 1**旧版“使用--pr重新运行”提示(issue #393)**仍会在特定场景触发,未做修改:裸命令仍会生成日期化路径,且可检测到PR——实际上是显式设置/选择退出暂存,但该分支的PR已打开:
put--ref--prefixtext
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这是尽力而为的检测(快速执行查询,超时时间为3秒)——若无打开的PR,提示文本会泛化为。抑制方式同上。
gh pr view--pr <num>Capturing a screenshot: uploads screenshot
uploads screenshot截图捕获:uploads screenshot
uploads screenshotCapture a URL or a local file and host it — no separate screenshot
tool needed, and no browser install required for the default path:
.htmlbash
uploads screenshot https://uploads.sh --pr 128 --comment
uploads screenshot ./card.html --out ./card.png
uploads screenshot ./card.html --no-upload --out ./card.pngAfter capture, a screenshot shares the exact upload pipeline described
above: optional , optimize-by-default, / attachment +
, , , and the same output formats. It also
ships as an MCP tool () alongside the CLI command.
put--frame--pr--issue--comment--gallery--metascreenshotTwo capture backends, selected with :
--via| Backend | What it is | Needs |
|---|---|---|
| Drives an already-installed Chrome/Chromium via | A discoverable browser on disk, or |
| Renders server-side via the uploads.sh render endpoint | Nothing local; counts against the workspace's monthly upload budget |
--via autoUPLOADS_SCREENSHOT_VIA=auto|local|remote--env-file--vialocalhost/private-network targets are local-only. With
(or falling back to remote) these fail fast with a clear error instead
of sending a request that could never work. Local files work on both
backends — the remote backend receives the file's contents inline (≤ 2 MiB),
so anything the page references via or relative paths only resolves
with . A numeric
(fixed settle delay after load) is also local-only; use
for a backend-agnostic wait.
--via remoteauto.htmlfile://--via local--wait <ms>--wait load|domcontentloaded|networkidleUse to attach to a Chrome that's already running
( or ) instead of launching a new one — handy when
an agent already has a Playwright MCP or session open.
(or / ) points at an
explicit executable.
--cdp <endpoint>http://host:portws://…agent-browser--browser <path>UPLOADS_CHROME_PATHCHROME_PATHKey options ( for all):
uploads screenshot --help| Flag | Purpose |
|---|---|
| Capture backend (default: |
| Explicit local browser executable (or |
| Attach to a running Chrome via CDP instead of launching one (local backend only). |
| Size + device scale factor (default: |
| Capture one element instead of the viewport. |
| Capture the full scrollable page. |
| Cap on |
| Emulate |
| Settle strategy (default: |
| Also write the PNG to a local file, plus a sidecar manifest ( |
| Don't write the |
| Skip hosting; requires |
| Same destination and attachment options as |
| Stage against a branch, pre-PR — same key as |
| Same as |
Inside a git repo, on a non-default branch, a bare now stages
automatically too (issue #469, mirroring 's issue #403 default) — same
key/metadata as /
(), carrying every derived fact
(///, plus ) through to the PR once it
opens. This is what closes the gap a coding agent hits capturing before the PR
exists: capture early with a plain ,
and the metadata rides along instead of being re-stated (or lost) at
time. Fires whenever none of
////// is set
and isn't passed; the same set of flags (or the default branch,
detached HEAD, not being in a git repo, or ) falls back to the
classic dated layout. Prints the same
staging note as bare (see above) — same stderr wording, same JSON
field, same / suppression.
screenshotput--branchattach --branchgh/<owner>/<repo>/branch/<branch>/<filename>pathurlenvviewport--stateuploads screenshot <url> --out shot.pngattach --pr <num>--pr--issue--branch--key--ref--prefix--destination--no-git--no-gitscreenshots/<repo>/<date>/...puthint--quietUPLOADS_NO_NUDGEErrors and hints: a local capture with no usable browser fails with
(exit ) — hint: try , or install a
browser (). A remote render that the server
can't complete returns . A burst rate limit on the render
endpoint returns (exit ) — hint: wait ~60s and retry. A
remote render over the workspace's monthly upload budget surfaces the usual
code and hint (, then delete objects or raise
limits) — renders and puts share one monthly counter.
BROWSER_NOT_FOUND2--via remotenpx playwright install chromiumRENDER_FAILEDRATE_LIMITED4UPLOAD_BUDGETuploads usageuploads doctor--via autoKey derivation and . Whenever the object's filename is
auto-derived from the captured URL (host + path) — the default dated
layout, or the / leaf name — passing folds it into
that derived filename stem —
becomes /
— so capturing the same URL twice with
then produces two distinct objects instead
of the second silently overwriting the first. Re-capturing the same URL with
the same state still replaces the existing object in place (idempotent
re-capture). An explicit is unaffected by folding. On
overwrite, human mode prints to
stderr (same wording as 's hot-swap note, above) and
adds , plus a field when a capture
replaced an existing object.
--state--pr--issue--statelocalhost-docs-mcp.webplocalhost-docs-mcp-before.webplocalhost-docs-mcp-after.webp--state before--state after--key--state>> replaced existing object (same URL)put--format json"replaced": truehint--state捕获URL或本地文件并托管——无需单独的截图工具,默认路径无需安装浏览器:
.htmlbash
uploads screenshot https://uploads.sh --pr 128 --comment
uploads screenshot ./card.html --out ./card.png
uploads screenshot ./card.html --no-upload --out ./card.png捕获完成后,截图会使用上述命令的上传流程:可选参数、默认优化、/附件+、、参数,以及相同的输出格式。该功能也作为MCP工具()与CLI命令并行提供。
put--frame--pr--issue--comment--gallery--metascreenshot两种捕获后端,通过参数选择:
--via| 后端 | 说明 | 依赖条件 |
|---|---|---|
| 通过 | 磁盘上存在可检测到的浏览器,或使用 |
| 通过uploads.sh渲染端点进行服务器端渲染 | 无需本地依赖;会占用工作区的月度上传配额 |
--via autoUPLOADS_SCREENSHOT_VIA=auto|local|remote--env-file--vialocalhost/私有网络目标仅支持本地后端。使用(或回退至远程)时,此类目标会快速失败并返回清晰错误,而非发送无法成功的请求。本地文件在两种后端均可用——远程后端会接收文件内容(≤2MiB),因此页面通过或相对路径引用的资源仅在模式下可解析。数值型(加载后的固定等待延迟)也仅支持本地后端;如需跨后端的等待策略,请使用。
--via remoteauto.htmlfile://--via local--wait <ms>--wait load|domcontentloaded|networkidle使用可连接至已运行的Chrome浏览器(或),而非启动新浏览器——当代理已打开Playwright MCP或会话时非常实用。(或/)指定浏览器可执行文件的路径。
--cdp <endpoint>http://host:portws://…agent-browser--browser <path>UPLOADS_CHROME_PATHCHROME_PATH关键参数(所有参数请查看):
uploads screenshot --help| 参数 | 用途 |
|---|---|
| 捕获后端(默认值: |
| 本地浏览器可执行文件的路径(或 |
| 通过CDP连接至已运行的Chrome浏览器,而非启动新浏览器(仅本地后端支持)。 |
| 尺寸 + 设备缩放因子(默认值: |
| 捕获单个元素,而非整个视口。 |
| 捕获可滚动的完整页面。 |
| |
| 模拟 |
| 等待策略(默认值: |
| 同时将PNG文件写入本地,并生成附带清单文件( |
| 执行 |
| 跳过托管;需配合 |
| 与 |
| 针对分支暂存文件(PR创建前)——密钥与 |
| 与 |
在git仓库内的非默认分支上执行裸命令,会自动暂存文件(issue #469,镜像命令的issue #403默认行为)——密钥/元数据与/相同(),并将所有派生信息(///,以及)传递至PR。这解决了编码代理在PR创建前捕获截图的痛点:现在执行即可捕获截图,元数据会随文件保留,无需在时重新指定(或丢失)。当未设置//////参数,且未传递时,会触发自动暂存;若设置了上述任一参数(或处于默认分支、分离HEAD状态、不在git仓库内、传递了),则会回退至经典的日期化路径。打印的暂存提示与裸命令相同(见上文)——stderr文本一致,JSON字段一致,/抑制方式一致。
screenshotput--branchattach --branchgh/<owner>/<repo>/branch/<branch>/<filename>pathurlenvviewport--stateuploads screenshot <url> --out shot.pngattach --pr <num>--pr--issue--branch--key--ref--prefix--destination--no-git--no-gitscreenshots/<repo>/<date>/...puthint--quietUPLOADS_NO_NUDGE错误与提示:本地捕获时若未找到可用浏览器,会返回错误(退出码)——提示:尝试,或安装浏览器()。远程渲染失败会返回错误。渲染端点触发突发速率限制会返回错误(退出码)——提示:等待约60秒后重试。远程渲染超过工作区月度上传配额会返回常规错误码和提示(,然后删除对象或提升配额)——渲染和上传共享同一月度计数器。
BROWSER_NOT_FOUND2--via remotenpx playwright install chromiumRENDER_FAILEDRATE_LIMITED4UPLOAD_BUDGETuploads usageuploads doctor--via auto密钥推导与参数。当对象文件名从捕获URL(主机+路径)自动推导时——默认日期化路径,或/的叶子名称——传递参数会将其整合至推导的文件名中——会变为/——因此针对同一URL分别使用和捕获时,会生成两个不同的对象,而非第二个覆盖第一个。针对同一URL使用相同参数重新捕获时,仍会覆盖现有对象(幂等重新捕获)。显式参数不受参数影响。覆盖时,人类可读模式会在stderr打印(与命令的热替换提示文本相同),会添加字段,若捕获替换了现有对象,还会添加字段。
--state--pr--issue--statelocalhost-docs-mcp.webplocalhost-docs-mcp-before.webplocalhost-docs-mcp-after.webp--state before--state afterstate--key--state>> replaced existing object (same URL)put--format json"replaced": true--statehintBaking in callouts: --annotate
--annotate添加标注:--annotate
--annotate--annotate <file|->---via remotebash
uploads screenshot http://localhost:3000 --via local --annotate ./callouts.jsonFor the spec format and an existing-image equivalent (, pixel-only, no selectors), see the
annotate-screenshots skill.
uploads annotate <image> --spec <file|->--annotate <file|->---via remotebash
uploads screenshot http://localhost:3000 --via local --annotate ./callouts.json关于规范格式和现有图片的等效命令(,仅基于像素,无选择器),请查看annotate-screenshots技能。
uploads annotate <image> --spec <file|->Custom metadata & search
自定义元数据与搜索
Every object can carry queryable key-value metadata (distinct from optimize/frame
provenance) — tag uploads at put time, then find them later.
每个对象都可携带可查询的键值对元数据(与优化/边框来源不同)——上传时标记文件,以便后续查找。
The canonical vocabulary
标准词汇表
Metadata is only useful if it is spelled the same way every time. These ten keys
are the agreed vocabulary; most are derived for you, so the main job is not
to fight them by inventing a different spelling.
| Key | Source | Example |
|---|---|---|
| auto — screenshot target | |
| auto — pathname | |
| auto — | |
| auto — only when forced | |
| auto — capture opts / EXIF | |
| auto — image EXIF | |
| auto — image EXIF | |
| auto — image EXIF | |
| you — | |
| you — | |
pathstatepathroutepagescreenstatestatebeforeafteremptyerrorloading--state postafteruploads screenshotpath--stateuploads putuploads attachpath--meta path=/routeattachput --prput --issuetip: add --meta path=/route so this shot is findable by pagehintpath--quietbash
uploads screenshot https://app.example/settings --state before元数据只有在拼写一致时才有用。以下十个键是约定的词汇表;大部分会自动推导,因此主要工作是不要自定义拼写,避免破坏一致性。
| 键 | 来源 | 示例 |
|---|---|---|
| 自动推导——截图目标URL | |
| 自动推导——路径名 | |
| 自动推导——仅本地模式 | |
| 自动推导——仅强制设置时生效 | |
| 自动推导——捕获选项/EXIF | |
| 自动推导——图片EXIF | |
| 自动推导——图片EXIF | |
| 自动推导——图片EXIF | |
| 手动设置—— | |
| 手动设置—— | |
pathstatepathroutepagescreenstatestatebeforeafteremptyerrorloading--state postafteruploads screenshotpath--stateuploads putuploads attachpath--meta path=/routeattachput --prput --issuepathtip: add --meta path=/route so this shot is findable by pagehint--quietbash
uploads screenshot https://app.example/settings --state before→ stamps url, path=/settings, viewport, state=before
→ 标记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
undefineduploads put ./after.png --pr 123 --meta path=/settings --state after --app web
uploads find path=/settings state=after # 这就是元数据的用途
undefinedWhat is derived, and when
自动推导规则与时机
- knows its own target, so it stamps
uploads screenshot,url(query stripped),path,viewportfor a local target, andenv=localwhentheme/--darkforced one.--light - /
uploads putread the image's own EXIF before the optimizer strips it, promoting an allowlist:attach(from pixel dimensions and DPI),viewport,device,software.captured - /
uploads putalso read a sidecar manifest left by a priorattachof that exact file (screenshot --out, content-hash guarded — a regenerated/edited file silently loses it) and merge in its derived metadata. This closes the capture-then-attach gap where a shot is taken before a PR exists:<file>.uploads.jsonnow,uploads screenshot ... --out shot.png --state afterlater, still getsuploads attach shot.png --pr 123/path/url/env/viewporton the PR-keyed object. Disable writing it withstate.--no-sidecar
envlocalprodNever promoted from EXIF, regardless of : all GPS tags, body and
lens serial numbers, //, and free-form user
comments. Note the flip side: and were previously discarded
and now become queryable metadata that renders on the public page.
--keep-exifArtistCopyrightOwnerNamedevicesoftware/f/Precedence: explicit // > screenshot capture facts >
sidecar manifest > EXIF > unset. Derived keys are also dropped first if the
24-key cap is reached — your own keys are never dropped, and a full key
budget never fails an upload.
--meta--state--appTurn the whole derived tier off with or .
--no-autoUPLOADS_NO_AUTO_META=1- 知晓目标URL,因此会标记
uploads screenshot、url(移除查询参数)、path,本地目标会标记viewport,强制设置env=local/--dark时会标记--light。theme - /
uploads put会在优化器移除EXIF前读取图片的EXIF元数据,提取允许的字段:attach(从像素尺寸和DPI推导)、viewport、device、software。captured - /
uploads put还会读取之前attach生成的附带清单文件(screenshot --out,由内容哈希值保护——重新生成/编辑的文件会静默丢失该清单),并合并派生元数据。这解决了PR创建前捕获然后上传的痛点:现在执行<file>.uploads.json,后续执行uploads screenshot ... --out shot.png --state after时,PR密钥的对象仍会携带uploads attach shot.png --pr 123/path/url/env/viewport元数据。可通过state参数禁止生成清单文件。--no-sidecar
envlocalprod无论是否设置,均不会从EXIF提取的字段:所有GPS标签、机身和镜头序列号、//,以及自由格式用户评论。请注意另一面:和之前会被丢弃,现在会变为可查询的元数据,并显示在公共页面上。
--keep-exifArtistCopyrightOwnerNamedevicesoftware/f/优先级:显式//参数 > 截图捕获信息 > 附带清单文件 > EXIF元数据 > 未设置。若达到24个键的上限,会优先移除自动推导的键——手动设置的键永远不会被移除,键数量达到上限也不会导致上传失败。
--meta--state--app可通过或禁用所有自动推导的元数据。
--no-autoUPLOADS_NO_AUTO_META=1Rules and reserved keys
规则与保留键
Validated client-side, fail-fast, before uploading: key
(lowercase, dot-namespacing allowed, e.g. );
value 1–512 printable ASCII characters; may repeat up to 24 times per
request; a value may itself contain (only the first splits key from value).
and are reserved (server-computed / the real R2
visibility gate, respectively). writes its own
reserved-by-convention keys automatically — see below.
^[a-z][a-z0-9._-]{0,63}$gh.repo--meta k=v==content-sha256visibilityuploads attachgh.*meta setgh/…pathstaterefreshed the managed comment on <repo>#<num>tip: run \bash
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 meta keys # which meta keys exist here
uploads meta values app # values (with counts) for one keymeta setfindk=v--meta k=vputattachscreenshotlistfind--name <term>=meta keysmeta values <key>list_metadata_keysOn the default path, also auto-derives GitHub context and
stamps /// from the current branch's PR (or
a numeric ), so the file's page shows an "Attached to" link. This is
on by default and best-effort; disable it with , , or .
On this auto path an explicit overrides the auto-derived value — the
opposite of the / precedence below, where the target's own always wins.
Both paths also stamp with the resolved PR/issue title when local
can resolve one — best-effort, never blocks the upload if it can't.
screenshots/…putgh.repogh.kindgh.numbergh.ref--ref/f/--no-auto--no-gitUPLOADS_NO_AUTO_META=1--meta gh.*--pr--issuegh.*gh.titleghRe-upload semantics: re-uploading to an existing key with metadata replaces
that file's entire metadata set (delete-then-set, not a merge); re-uploading with
no metadata at all preserves the existing metadata untouched. Derived keys
count as metadata here, so a re-upload that derives anything replaces the set —
pass when re-uploading a key whose metadata you curated with
.
--no-autouploads meta setNon- metadata values supplied via (CLI) or (MCP) render
on the object's public file page. Treat them like the URL itself:
don't put internal notes, secrets, tokens, IDs, or private paths in them.
gh.*--metametadata/f/<key>客户端会验证元数据,快速失败,不会上传:键名需符合(小写,允许点号命名空间,例如);值为1–512个可打印ASCII字符;参数每个请求最多重复24次;值中可包含(仅第一个用于分隔键和值)。和为保留键(由服务器计算 / 实际R2可见性控制)。会自动写入自己的约定保留键——见下文。
^[a-z][a-z0-9._-]{0,63}$gh.repo--meta k=v==content-sha256visibilityuploads attachgh.*针对密钥的对象执行操作时,若写入操作涉及可渲染的键(/),还会刷新托管PR/issue评论——尽力而为,元数据写入完成后执行。成功时会在stderr打印;若机器人端点不可用,会打印uploads comment --pr <num>` to refresh the PR comment`提示;无论哪种情况,元数据写入本身都不会失败。
gh/…meta setpathstaterefreshed the managed comment on <repo>#<num>tip: run \bash
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 # 效果相同
uploads list --meta app=web --meta path=/settings # 多条件AND,可重复
uploads find app=web path=/settings # 相同筛选条件,位置参数对
uploads find --meta app=web # --meta参数也可用于此处
uploads find hero # 裸名称 = 文件名子串
uploads find --name hero --meta app=web # 文件名子串 + 可选元数据,顺序任意
uploads meta keys # 查看工作区存在的元数据键
uploads meta values app # 查看某个键的所有值(含计数)meta setfindk=vputattachscreenshotlist--meta k=vfind--name <term>=meta keysmeta values <key>list_metadata_keys在默认路径下,命令还会自动推导GitHub上下文,并从当前分支的PR(或数值型)标记///元数据,因此文件的页面会显示“Attached to”链接。默认启用,尽力而为;可通过、或禁用。在该自动路径下,显式参数会覆盖自动推导的值——与下文/的优先级相反,后者的值始终优先。两种路径还会在本地可解析时标记为PR/issue的实际标题——尽力而为,若无法解析则不会阻塞上传。
screenshots/…put--refgh.repogh.kindgh.numbergh.ref/f/--no-auto--no-gitUPLOADS_NO_AUTO_META=1--meta gh.*--pr--issuegh.*ghgh.titlegh重新上传语义:重新上传至已有密钥且携带元数据时,会替换该文件的整个元数据集(先删除再设置,而非合并);重新上传无元数据时,会保留现有元数据不变。自动推导的键属于元数据,因此重新上传时若有自动推导的键,会替换元数据集——若已通过手动整理元数据,重新上传时请传递参数。
uploads meta set--no-auto通过(CLI)或(MCP)设置的非元数据值会显示在对象的公共页面上。请像对待URL一样处理这些值:不要在其中添加内部注释、密钥、令牌、ID或私有路径。
--metametadatagh.*/f/<key>Public media galleries
公共媒体画廊
Use galleries when several existing public uploads should be shared as one ordered collection.
A gallery has an opaque, API-returned public URL; do not derive one in scripts. Anyone who
knows the URL can view the gallery and its media. GitHub repository visibility does not make
it private, and a gallery does not pin objects against retention.
bash
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/58gallery add--jsonaddedfailuresOptionally link a gallery to a GitHub issue or PR with . The CLI also accepts strict URLs. Use for the authenticated reverse lookup. This is metadata only: it does not make a gallery private or change its opaque identity.
uploads gallery link <gallery-id> --github <owner/repo#number>https://github.com/<owner>/<repo>/issues|pull/<number>uploads gallery list --github <coordinate-or-url>当多个现有公共上传文件需要作为有序集合共享时,可使用画廊功能。画廊拥有API返回的不透明公共URL;请勿在脚本中自行推导URL。知晓URL的任何人都可查看画廊及其媒体。GitHub仓库可见性不会使画廊私有,画廊也不会固定对象以防止被删除。
bash
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/58gallery add--jsonaddedfailures可通过将画廊链接至GitHub issue或PR。CLI也接受严格的 URL。使用可进行已认证的反向查询。这仅为元数据操作:不会使画廊私有,也不会改变其不透明身份。
uploads gallery link <gallery-id> --github <owner/repo#number>https://github.com/<owner>/<repo>/issues|pull/<number>uploads gallery list --github <coordinate-or-url>Embedding in a GitHub PR or issue
嵌入GitHub PR或issue
Two ways, depending on whether you want a durable URL, a managed comment, or both.
两种方式,取决于是否需要持久URL、托管评论,或两者都需要。
Option A — stable attachment URL (--pr
/ --issue
)
--pr--issue选项A——稳定附件URL(--pr
/ --issue
)
--pr--issueGives the file a hash-free, stable key so re-uploads overwrite in place and the
URL is safe to hard-code in a PR body you'll edit later:
bash
uploads put ./after.png --pr 123 --alt "Dashboard after"为文件生成无哈希值的稳定密钥,因此重新上传会直接覆盖原有内容,URL可安全硬编码至后续编辑的PR正文中:
bash
uploads put ./after.png --pr 123 --alt "Dashboard after"key: gh/<owner>/<repo>/pull/123/after.webp → stable public URL (PNG optimized to WebP)
密钥: gh/<owner>/<repo>/pull/123/after.webp → 稳定公共URL(PNG优化为WebP)
`--issue <num>` does the same under `.../issues/<num>/`. The `<owner>/<repo>` comes
from `--repo` or the git remote. `--pr`/`--issue` can't be combined with `--key`,
`--ref`, or `--prefix` (the key layout is fixed), and are mutually exclusive.
These keys are deliberately predictable: they include the owner, repository, PR or
issue number, and filename. uploads.sh does not check GitHub visibility, so a private
or internal repository does **not** make the uploaded file private. Before using this
mode, confirm the media is safe for a public, guessable URL; otherwise redact it or do
not upload it.
If the uploads GitHub App can see that the target repo is private, this key layout
changes automatically: the key becomes `gh/private/<id>/...`, where `<id>` is a random
id minted per branch (or per repo, for issues), instead of the derivable
`gh/<owner>/<repo>/...`. No flag needed — public repos, and repos the App can't see,
keep the derivable layout. The URL is still unauthenticated and durable, not
access-controlled — anyone who obtains it can read it until you rotate the id with
`uploads github rotate-prefix --branch <branch>` (or `--repo-level` for issues/ingested
assets). See `docs/private-attachments.md` for the full threat model.
Then reference the **embed** URL in the PR/issue markdown you write with `gh`
(CLI `--format markdown` / MCP `markdown` already do this):
```markdown
<img width="700" alt="Dashboard after" src="https://embed.uploads.sh/default/gh/myorg/myapp/pull/123/after.webp">Keep (storage host) when you need a durable share link outside GitHub.
urlput --pr--issueuploads attachgh.repogh.kindgh.numbergh.refuploads find gh.ref=myorg/myapp#123uploads list --meta gh.repo=myorg/myappgh/...--meta k=v--meta gh.*gh.*gh.titleghgh
`--issue <num>`会生成`.../issues/<num>/`路径下的密钥。`<owner>/<repo>`来自`--repo`参数或git远程仓库。`--pr`/`--issue`不可与`--key`、`--ref`或`--prefix`同时使用(密钥结构固定),且两者互斥。
这些密钥是可预测的:包含所有者、仓库、PR或issue编号,以及文件名。uploads.sh不会检查GitHub可见性,因此私有或内部仓库的上传文件仍为公共文件。使用此模式前,请确认媒体内容适合公开、可猜测的URL;否则请裁剪/遮敏感内容,或不要上传。
若uploads GitHub App可检测到目标仓库为私有,密钥结构会自动变化:密钥变为`gh/private/<id>/...`,其中`<id>`为每个分支(或每个仓库,针对issue)生成的随机ID,而非可推导的`gh/<owner>/<repo>/...`。无需额外标志——公共仓库和App无法检测的仓库仍使用可推导结构。URL仍为未认证的持久链接,无访问控制——任何人获取URL后均可读取,直到通过`uploads github rotate-prefix --branch <branch>`(或针对issue/导入资源使用`--repo-level`)旋转ID。完整威胁模型请查看`docs/private-attachments.md`。
然后在使用`gh`编写的PR/issue Markdown中引用**embed** URL(CLI`--format markdown`/MCP`markdown`已自动处理):
```markdown
<img width="700" alt="Dashboard after" src="https://embed.uploads.sh/default/gh/myorg/myapp/pull/123/after.webp">若需在GitHub外部使用持久共享链接,请使用(存储宿主)。
urlput --pr--issueuploads attachgh.repogh.kindgh.numbergh.refuploads find gh.ref=myorg/myapp#123uploads list --meta gh.repo=myorg/myappgh/...--meta k=v--meta gh.*gh.*ghgh.titleghOption B — managed attachments comment (default with --pr
/--issue
, or comment
)
--pr--issuecomment选项B——托管附件评论(--pr
/--issue
默认启用,或comment
命令)
--pr--issuecommentput --pr--issueattachgh/...bash
uploads put ./after.png --pr 123Pass to skip the sync (upload only), matching . is still accepted on as a no-op — it's redundant now that the sync is the default, kept only for scripts written before this changed (#537).
--no-commentattach --no-comment--commentputThe upload is authoritative; the comment is best-effort — if is missing or
unauthenticated, the upload still succeeds and you get a warning. To (re)sync the
comment without uploading anything (e.g. after several uploads or gallery links), use the standalone command:
gh--prbash
uploads comment --pr 123
uploads comment --issue 45 --repo buildinternet/uploadsRemoved the wrong screenshots? the object(s) and re-run to
re-sync. When the last attachment and gallery are gone, the managed comment
is rewritten in place to a neutral empty state () — it is never deleted (a later upload
repopulates it) and never created just to say it's empty:
deletecommentNo attachments are currently associated with this pull request.bash
uploads delete gh/owner/name/pull/123/after.png # remove the asset
uploads comment --pr 123 # comment now shows the empty statePast 16 inline images, the comment collapses the rest into a link
list so a heavily-screenshotted PR stays readable. Each workspace gets its own
managed comment on a shared repo (namespaced under the hood) instead of
clobbering another workspace's — use (below) to see or
set which workspace a repo is bound to.
<details>uploads github linkput --pr--issueattachgh/...bash
uploads put ./after.png --pr 123传递参数可跳过同步(仅上传),与一致。命令仍接受参数,但为无操作——自默认同步功能变更后(#537),该参数已冗余,仅为兼容旧脚本保留。
--no-commentattach --no-commentput--comment上传操作是权威的;评论操作是尽力而为的——若缺失或未认证,上传仍会成功,并打印警告。若需在不重新上传的情况下(重新)同步评论(例如多次上传或画廊链接后),可使用独立命令:
gh--prbash
uploads comment --pr 123
uploads comment --issue 45 --repo buildinternet/uploads误删了截图?删除对象后重新运行命令即可同步。当最后一个附件和画廊被移除后,托管评论会原地重写为中性空状态()——永远不会被删除(后续上传会重新填充),也不会仅为显示空状态而创建:
commentNo attachments are currently associated with this pull request.bash
uploads delete gh/owner/name/pull/123/after.png # 删除资源
uploads comment --pr 123 # 评论现在显示空状态当内联图片超过16张时,评论会将剩余图片折叠为链接列表,以便截图较多的PR保持可读性。每个工作区在共享仓库上拥有自己的托管评论(内部命名空间),不会覆盖其他工作区的评论——使用(见下文)可查看或设置仓库绑定的工作区。
<details>uploads github linkRepo binding (uploads github link
/ unlink
/ doctor
)
uploads github linkunlinkdoctor仓库绑定(uploads github link
/ unlink
/ doctor
)
uploads github linkunlinkdoctorThe managed comment and webhook auto-promotion use a first-claim-wins binding
between a repo and a workspace, normally created implicitly by your first
//promote call. Inspect, claim, or release it:
commentput --commentbash
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)Claiming an already-bound repo never steals it — the command reports the
existing owner instead. only releases a binding this workspace owns;
it 403s if another workspace owns it (an operator can reassign or remove it
from the admin panel instead). On an older/self-hosted server without these
routes they fail with a clear "server does not support repo bindings/GitHub
App health check yet" message.
unlinkClaiming an unbound repo is authorized, not just first-come (issue #297):
the server only lets a workspace make that first claim when its linked
GitHub account has push (or higher) access to the repo, checked live against
GitHub via the App's installation token. A token with no linked GitHub
identity — a legacy/enrollment/shared token, including 's — can
never claim a new repo, though it keeps working normally on any repo already
bound to it. Claiming reports when
this check fails; link a GitHub account with push access to the repo, or ask
an operator to bind it explicitly from the admin panel.
defaultclaimed: false, reason: "not_authorized"not_authorizedcommentattach --commentghuploads github link --statusuploads github doctorissuespull_requestissue_comment托管评论和Webhook自动升级功能使用仓库与工作区的先到先得绑定逻辑,通常由首次//promote调用隐式创建。可查看、声明或解除绑定:
commentput --commentbash
uploads github link # 将当前仓库绑定至当前工作区
uploads github link --repo owner/name # 将指定仓库绑定至当前工作区
uploads github link --status # 只读:查看当前绑定状态,不执行绑定
uploads github unlink --repo owner/name # 解除当前工作区拥有的仓库绑定
uploads github doctor # 检查App本身(配置 + Webhook事件)声明已绑定的仓库不会抢占绑定——命令会报告现有所有者。仅能解除当前工作区拥有的绑定;若绑定属于其他工作区,会返回403错误(管理员可从控制面板重新分配或移除绑定)。在不支持这些路由的旧版/自托管服务器上,命令会失败并显示清晰的“server does not support repo bindings/GitHub App health check yet”信息。
unlink声明未绑定仓库需要授权,并非先到先得(issue #297):仅当工作区关联的GitHub账户拥有仓库推送(或更高)权限时,服务器才允许工作区进行首次绑定,该检查会通过App的安装令牌实时向GitHub验证。无关联GitHub身份的令牌——旧版/注册/共享令牌,包括令牌——永远无法声明新仓库,但在已绑定的仓库上仍可正常工作。当验证失败时,声明会返回;请关联拥有仓库推送权限的GitHub账户,或请管理员从控制面板显式绑定仓库。
defaultclaimed: false, reason: "not_authorized"commentattach --commentnot_authorizedghuploads github link --statusuploads github doctorissuespull_requestissue_commentMirroring GitHub-native attachments (uploads ingest
)
uploads ingest镜像GitHub原生附件(uploads ingest
)
uploads ingestImages someone drops straight into a PR/issue via
only exist behind GitHub's own authenticated hosting — they're never public
URLs. (or ) scans the description and
comments for that media, mirrors any new ones into the workspace (indexed,
not added to the managed comment), and detaches ones no longer referenced —
a reattached one un-detaches without a re-fetch:
github.com/user-attachments/…uploads ingest --pr <n>--issue <n>bash
uploads ingest --pr 123
uploads ingest --issue 45 --repo owner/name --jsonRequires the repo be linked to the workspace () and the
GitHub App installed — otherwise it fails with a clear error rather than
guessing. This is the manual/backfill entry point; the
knob only gates the automatic webhook path and has
no effect on running directly.
uploads github link.uploads.ymlingestGithubAttachmentsingest直接通过上传至PR/issue的图片仅存在于GitHub的认证托管服务后——并非公共URL。(或)会扫描描述和评论中的媒体内容,将新内容镜像至工作区(建立索引,不会添加至托管评论),并分离不再被引用的内容——重新引用的内容会重新关联,无需重新获取:
github.com/user-attachments/…uploads ingest --pr <n>--issue <n>bash
uploads ingest --pr 123
uploads ingest --issue 45 --repo owner/name --json需要仓库已链接至工作区()且已安装GitHub App——否则会失败并显示清晰错误,而非猜测。这是手动/回填入口;中的开关仅控制自动Webhook路径,对直接运行命令无影响。
uploads github link.uploads.ymlingestGithubAttachmentsingestEmbedding best practices
嵌入最佳实践
- Meaningful alt text, always — it's what readers with images off and search see.
- Constrain width on large shots with so they don't dominate the page.
--width - Before/after reads best side by side in a table:
markdown
| Before | After | | ------------------------------------ | ----------------------------------- | | <img width="380" src="…/before.png"> | <img width="380" src="…/after.png"> | - Prefer writing the body to a file and using /
gh pr edit --body-fileover inline HEREDOCs.gh issue comment --body-file - The host is agnostic — the same URLs work in issues, PR comments, discussions, and plain markdown docs.
- Prefer short, compressed clips (or an animated capture) over large raw video files when attaching to PRs. Per-file and workspace storage caps apply underneath, and a multi-minute uncompressed recording is a poor PR embed regardless — trim to the relevant seconds and compress before uploading.
- 始终添加有意义的替代文本——这是关闭图片的读者和搜索工具看到的内容。
- 使用限制大截图的宽度,避免页面被截图占据。
--width - 前后对比内容并排显示效果最佳,可使用表格:
markdown
| Before | After | | ------------------------------------ | ----------------------------------- | | <img width="380" src="…/before.png"> | <img width="380" src="…/after.png"> | - 建议将正文写入文件,使用/
gh pr edit --body-file,而非内联HEREDOC。gh issue comment --body-file - 宿主无关——相同URL可用于issue、PR评论、讨论和普通Markdown文档。
- PR附件优先选择短时长压缩剪辑(或动态捕获),而非大型原始视频文件。底层会应用单文件和工作区存储上限,多分钟未压缩录屏作为PR嵌入体验不佳——裁剪至相关片段并压缩后再上传。
Managing uploads
管理上传文件
bash
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 --versiondoctorDestructive preview: supports . does not
yet (#78); preview via
/ and retention settings.
delete--dry-runpurge-expiredlistusagebash
uploads list --prefix screenshots/ # 列出对象(密钥 + URL)
uploads list --pr 123 # 列出附加至指定PR的所有文件
uploads list --meta app=myapp # 按元数据筛选(可重复,多条件AND)
uploads list --name hero --meta app=web # 文件名子串(+ 可选元数据)
uploads find app=myapp path=/settings # 相同筛选条件,更友好的位置参数对
uploads find hero # 仅按文件名子串筛选
uploads list --all --json # 完整分页,机器可读
uploads meta get <key> # 查看对象的元数据
uploads meta set <key> k=v [k=v…] [--delete k]… # 合并设置 / 删除元数据对
uploads meta set <key> --meta k=v # 相同操作,使用put/list的标志拼写
uploads meta keys # 发现工作区的元数据键
uploads meta values <meta-key> # 查看某个键的所有不同值(含计数)
uploads delete <key> # 删除对象
uploads delete <key> --dry-run # 预览会删除的内容
uploads usage # 存储 / 月度上传计数器(+ 限制)
uploads reconcile # 从存储重建分类账
uploads purge-expired # 删除超过retentionDays的文件(若设置)
uploads health # API存活状态(无需认证)
uploads doctor # 版本 + 健康状态 + 认证信息 + 工作区 + 使用情况
uploads --version出现问题时,首先运行命令——它会报告已安装的CLI版本,区分API宕机/令牌错误/工作区不匹配/本地与生产URL差异,并打印针对性提示。
doctorConfig commands
配置命令
Set shared defaults once instead of passing flags every time:
bash
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_…initUPLOADS_API_URLUPLOADS_WORKSPACEuploads login--workspaceRecognized keys: , , ,
, , ,
, , , ,
, , .
Also read (env only, not config-file keys): ,
.
UPLOADS_API_URLUPLOADS_WORKSPACEUPLOADS_TOKENUPLOADS_DEFAULT_PREFIXUPLOADS_DEFAULT_REPOUPLOADS_DEFAULT_REFUPLOADS_DEFAULT_WIDTHUPLOADS_NO_GITUPLOADS_NO_OPTIMIZEUPLOADS_KEEP_EXIFUPLOADS_NO_AUTO_METAUPLOADS_SCREENSHOT_VIAUPLOADS_NO_NUDGEUPLOADS_EMBED_PUBLIC_BASE_URLUPLOADS_OVERWRITE一次性设置共享默认值,无需每次传递标志:
bash
uploads config show # 显示有效设置(令牌已脱敏)
uploads config 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_…initUPLOADS_API_URLUPLOADS_WORKSPACEuploads login--workspace支持的键:、、、、、、、、、、、、。还支持读取(仅环境变量,非配置文件键):、。
UPLOADS_API_URLUPLOADS_WORKSPACEUPLOADS_TOKENUPLOADS_DEFAULT_PREFIXUPLOADS_DEFAULT_REPOUPLOADS_DEFAULT_REFUPLOADS_DEFAULT_WIDTHUPLOADS_NO_GITUPLOADS_NO_OPTIMIZEUPLOADS_KEEP_EXIFUPLOADS_NO_AUTO_METAUPLOADS_SCREENSHOT_VIAUPLOADS_NO_NUDGEUPLOADS_EMBED_PUBLIC_BASE_URLUPLOADS_OVERWRITELocal development
本地开发
Point at a locally running API ( serves it on ). Tokens minted with
only work against localhost; prod tokens need
. flags this mismatch for you.
pnpm dev:8787workspace:add --localUPLOADS_API_URL=https://api.uploads.shdoctorbash
uploads --api-url http://localhost:8787 doctor指向本地运行的API(会在端口启动)。使用生成的令牌仅对localhost有效;生产环境令牌需要设置。会标记这种不匹配。
pnpm dev:8787workspace:add --localUPLOADS_API_URL=https://api.uploads.shdoctorbash
uploads --api-url http://localhost:8787 doctorNotes and cautions
注意事项
-
Uploads are public and effectively permanent until deleted. GitHub repository visibility is not an access control: private/internal PR and issue attachments remain public, andkeys are predictable. Never upload secrets, tokens, internal dashboards with sensitive data, or customer PII visible in a shot — crop/redact first.
gh/<owner>/<repo>/pull|issues/<num>/<filename> -
Edge cache / dual host: stableresponses carry
url. For GitHub, useCache-Control: max-age=60(no-cache host) so overwrites propagate through Camo. Prefer CLI/MCPembedUrlrather than hand-building storage URLs into PR bodies. See repomarkdown(dual public hosts).docs/ops.md -
Exit codes:usage/token/file,
2auth/policy,3network,4other.1emits--json— branch on{error,code,status}. Scripted formats (code) also print failures on stdout. Usage errors:json|url|markdown.hint: uploads <cmd> --help -
Errors stay short (stderr), so trimming output never hides them. A missing argument prints oneline, a runnable example (
error:), and that hint — not the command's help. Inuploads put ./shot.png --pr 123the example rides along as--json. A mistyped command prints the error, aexampleline, and the help pointers; withdid you mean: uploads <cmd>it returns--jsonon stdout. Read the first line; run{error,code:"USAGE",didYouMean}for the full help.uploads <cmd> --help -
Update hints (stderr): successful human runs may note a newer npm release (daily). Silence with/
--quiet/--json.UPLOADS_NO_UPDATE=1 -
Telemetry: anonymous command-name pings (no paths/tokens). Opt out with,
UPLOADS_TELEMETRY_DISABLED=1, orDO_NOT_TRACK=1.uploads telemetry disable -
Reports: only when the user asks —or
uploads report "what broke". Never auto-send logs. MCP tool:--file ./trace.log.report -
MCP:(stdio) mirrors CLI tools; hosted MCP at
uploads mcp— the one to reach for when an agent has no local filesystem or git checkout to shell out from (send base64 content directly). Metadata:https://agents.uploads.sh/mcp/get_metadata/set_metadata/find_files(same aslist_metadata_keys/meta get/meta set/find|meta keys).meta valuesaccepts optionalfind_files(filename substring) with or withoutname. Both support multi-filefiltersin one call — stdio takesputas paths, hosted takesfiles(max 20/call; per-itemfiles: [{ filename, contentBase64, alt? }]overrides the top-level one) — returningaltwith per-item results.{ uploads, failures }sets up this skill + hosted MCP + Grok/Cursor hooks (short progress;uploads install/--verboseavailable). Claude and Codex ship the same pre-PR reminder via their plugins (--dry-run).uploads hook pre-pr-screenshotHosted MCPcomment parity (issue #392). The hostedputtool acceptsput/pr(mutually exclusive, mirroring the CLI'sissue/--pr) plus a required--issue(repo— the hosted server has no git context to infer it from) to get the same stableowner/namekey the CLI produces. Withgh/…/prthe managedissueattachments comment is posted/updated by default, same as CLIuploads-sh[bot](#537) — passput --prto skip it. Bot-only on this server, no local-comment: falsefallback; the default sync needs theghscope and is silently skipped on a write-only token (explicitfiles:readerrors instead). Prefercomment: true/prover just returning a rawissue/urlwhenever the caller is going to paste the result into a PR/issue: it gets the stable overwrite-in-place key and lands straight in the collected attachments comment instead of a one-off link the caller has to hand-embed. A comment failure never fails the upload — it's returned honestly in the result'sembedUrlfield as one ofcomment(App not installed on the repo),not_installed(repo bound to a different workspace, or unbound and this workspace isn't entitled to claim it), ornot_authorized(App installed but Issues/PR write access not yet approved — includes aforbiddento the org's permission-review page), never as a thrown tool error; an unexpected error surfaces separately asfixUrl.commentErrorHosted MCP standalonetool. The hosted server also has acommenttool (comment,{ repo, pr | issue }required for the same no-git-context reason) that refreshes the managed comment without re-uploading — the hosted equivalent of CLIrepo. Use it to re-sync after deleting an asset:uploads commentthedeletekey, then callgh/…. When the last attachment and gallery are gone the comment is rewritten in place to a neutral empty state (never deleted, never created empty). It is bot-only with the same honest declines ascomment's comment field. Both this tool andputwithput/prhonor the target repo'sissue(same as the bot path — no separate MCP config; see https://uploads.sh/docs/comment-config)..uploads.ymlHosted MCP: branch staging + promote. There is still notool on the hosted server (no filesystem paths) — useattachinstead:puttext# Stage pre-PR (CLI attach --branch parity). repo + branch required. put { contentBase64, filename, repo: "owner/name", branch: "feature/x", state: "after" } # → key gh/owner/name/branch/feature-x/<filename>, gh.status=staged # 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" }Hosted MCP: checking what's staged (issue #405). There's no dedicatedtool on the hosted server — it has no local git context to defaultstagedfrom, so it always needs the caller's ownbranch/repo. Answer "what's staged?" with the existing tools instead:branchtextlist { prefix: "gh/<owner>/<repo>/branch/<branch>/" } # or find_files { filters: { "gh.branch": "<branch>" } }returns each match's metadata inline, sofind_filesgives recency without a second call; addgh.staged-atto the filters when branch names aren't unique across repos you're working in. For the binding question ("will these auto-attach?"), use the hosted"gh.repo": "<owner>/<repo>"tool (issue #422):repo_link_statustextrepo_link_status { repo: "<owner>/<repo>" }It returns:{ binding: "self" | "other" | "none" }means this repo is bound to this workspace and staged files will auto-attach,"self"means it's bound to a different workspace and they won't — deliberately without ever naming that workspace — and"other"means the repo is unbound."none" -
Agents on the Worker side: the package also exportsfrom
createUploadsWorkerFileTools()for exposing upload/list/delete as AI-SDK tools inside a Worker — only relevant if you're building agent tooling that runs on the server, not for everyday PR embeds.@buildinternet/uploads/agent
-
上传文件为公共文件,且实际上是永久的,直到被删除。GitHub仓库可见性并非访问控制:私有/内部PR和issue的附件仍为公共文件,密钥是可预测的。永远不要上传密钥、令牌、包含敏感数据的内部仪表板,或截图中可见的客户PII——请先裁剪/遮敏感内容。
gh/<owner>/<repo>/pull|issues/<num>/<filename> -
边缘缓存 / 双宿主:稳定响应携带
url头。针对GitHub,请使用Cache-Control: max-age=60(无缓存宿主),以便覆盖操作可通过Camo传播。建议使用CLI/MCP的embedUrl输出,而非手动构建存储URL至PR正文。详情见仓库markdown(双公共宿主)。docs/ops.md -
退出码:表示使用/令牌/文件错误,
2表示认证/策略错误,3表示网络错误,4表示其他错误。1会输出--json——可根据{error,code,status}分支处理。脚本化格式(code)也会在stdout打印失败信息。使用错误会提示:json|url|markdown。hint: uploads <cmd> --help -
错误信息简短(stderr),因此裁剪输出不会隐藏错误。缺少参数会打印一行信息、一个可运行示例(
error:),以及提示——不会打印命令帮助。uploads put ./shot.png --pr 123输出会包含--json字段。命令拼写错误会打印错误信息、example行,以及帮助指针;did you mean: uploads <cmd>会在stdout返回--json。请查看第一行错误信息;运行{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"。永远不会自动发送日志。MCP工具:--file ./trace.log。report -
MCP:(标准输入输出)镜像CLI工具;托管MCP位于
uploads mcp——当代理无本地文件系统或git检出权限时,可使用该服务(直接发送base64内容)。元数据操作:https://agents.uploads.sh/mcp/get_metadata/set_metadata/find_files(与list_metadata_keys/meta get/meta set/find|meta keys相同)。meta values接受可选的find_files(文件名子串),可配合或不配合name使用。两者均支持单次调用上传多个文件——标准输入输出接受filters为路径,托管服务接受files(每次调用最多20个文件;单个文件的files: [{ filename, contentBase64, alt? }]会覆盖顶层alt)——返回alt,包含每个文件的结果。{ uploads, failures }会设置该技能+托管MCP+Grok/Cursor钩子(进度简短;支持uploads install/--verbose参数)。Claude和Codex通过其插件提供相同的PR前提醒(--dry-run)。uploads hook pre-pr-screenshot托管MCP命令的评论一致性(issue #392)。托管put工具接受put/pr(互斥,镜像CLI的issue/--pr),以及必填的--issue(repo——托管服务器无git上下文可推断),以生成与CLI相同的稳定owner/name密钥。指定gh/…/pr时,默认会发布/更新托管的issue附件评论,与CLIuploads-sh[bot]相同(#537)——传递put --pr可跳过。该服务器仅支持机器人发布,无本地comment: false备选方案;默认同步需要gh权限,若使用仅写令牌会静默跳过同步(显式files:read会返回错误)。当调用者要将结果粘贴至PR/issue时,建议使用comment: true/pr参数,而非仅返回原始issue/url:这样可获得稳定的原地覆盖密钥,并直接添加至收集的附件评论,而非需要手动嵌入的一次性链接。评论失败不会导致上传失败——会在结果的embedUrl字段中如实返回comment(仓库未安装App)、not_installed(仓库已绑定至其他工作区,或未绑定且当前工作区无权限声明)或not_authorized(已安装App,但尚未批准Issues/PR写入权限——包含forbidden指向组织权限审核页面),不会抛出工具错误;意外错误会单独显示为fixUrl。commentError托管MCP独立工具。托管服务器还提供comment工具(comment,{ repo, pr | issue }必填,原因同上无git上下文),用于刷新托管评论无需重新上传——相当于CLI的repo命令。删除资源后可使用该命令重新同步:删除uploads comment密钥,然后调用gh/…。当最后一个附件和画廊被移除后,评论会原地重写为中性空状态(永远不会删除,不会为空状态创建)。仅支持机器人发布,与comment命令的评论字段有相同的如实拒绝逻辑。该工具和指定put/pr的issue命令均遵循目标仓库的put配置(与机器人路径相同——无单独MCP配置;见https://uploads.sh/docs/comment-config)。.uploads.yml托管MCP:分支暂存 + 升级。托管服务器仍无工具(无文件系统路径)——请使用attach替代:puttext# PR创建前暂存(与CLI attach --branch一致)。必填repo + branch。 put { contentBase64, filename, repo: "owner/name", branch: "feature/x", state: "after" } # → 密钥gh/owner/name/branch/feature-x/<filename>, gh.status=staged # PR创建后升级暂存文件(与CLI attach --promote一致)。 promote { repo: "owner/name", pr: 123, branch: "feature/x" } # 可选comment: false跳过托管评论刷新(默认启用) # 或在一次调用中上传新文件至PR并升级该分支: put { contentBase64, filename, repo: "owner/name", pr: 123, branch: "feature/x" }托管MCP:查看暂存文件(issue #405)。托管服务器无专用工具——无本地git上下文可默认staged,因此始终需要调用者指定branch/repo。可使用现有工具回答“哪些文件已暂存?”:branchtextlist { prefix: "gh/<owner>/<repo>/branch/<branch>/" } # 或 find_files { filters: { "gh.branch": "<branch>" } }会在结果中包含每个匹配项的元数据,因此find_files可显示暂存时间,无需二次调用;当分支名称在多个仓库中不唯一时,可在筛选器中添加gh.staged-at。关于绑定问题(“这些文件会自动附加吗?”),可使用托管"gh.repo": "<owner>/<repo>"工具(issue #422):repo_link_statustextrepo_link_status { repo: "<owner>/<repo>" }返回:{ binding: "self" | "other" | "none" }表示仓库已绑定至当前工作区,暂存文件会自动附加;"self"表示仓库已绑定至其他工作区,不会自动附加——故意不显示其他工作区名称;"other"表示仓库未绑定。"none" -
Worker端代理:包还从导出
@buildinternet/uploads/agent,用于在Worker内部将上传/列表/删除功能作为AI-SDK工具暴露——仅在构建服务器端代理工具时相关,日常PR嵌入无需关注。createUploadsWorkerFileTools()