# Media Upload

> Upload a media file (image OR video/screen-recording) — or a whole directory of them — to the shared public asset repo and get back raw.githubusercontent.com/main URLs. For images that URL embeds inline via ![](); for video it is a LINK (GitHub won't inline-play raw video). Binary-safe: never MCP file-write (#1079). The mechanism is two universal plugin scripts (gh-upload-media.sh single, gh-upload-dir.sh dir); this skill is the discoverable entry + the doctrine. Used by test-sweep, ui-verify, and any repo's screenshot/recording flow.

- Skill: `arcblock/media-upload` (Agent Skill)
- Install (CLI): `npx skillmds@latest add arcblock/media-upload`
- Raw SKILL.md: https://api.skillmd.com/api/skills/arcblock/media-upload/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: ArcBlock (https://skillmd.com/u/arcblock)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/arcblock/media-upload

---


# media-upload — 媒体上传（图片/视频），输出 raw.githubusercontent.com URL

把媒体文件（截图、动图、录屏——单个或整目录）上传到公共图床仓（默认 `ArcBlock/loop-agent-assets`），
返回**可匿名访问**的 `raw.githubusercontent.com/.../main/...` URL。

> **Output language: 中文**；代码 / 路径 / 命令保持原文。

## ⚠️ 上传统一，内嵌不统一（最容易踩的坑）

**上传对 image/video 是同一套；但在 GitHub markdown 里怎么内嵌，按类型分流：**

| 类型 | 扩展名 | 内嵌方式 |
|---|---|---|
| 图片 | png / jpg | `![](<raw-url>)` **内联显示**（#1334） |
| 动图 | gif | `![](<raw-url>)` **内联播放**（gif 是 `image/gif`，同图片路径） |
| 视频 | webm / mp4 / mov | **链接** `[▶ 录屏](<raw-url>)`，或尽力 `<video src=...>`；**绝不 `![](x.webm)`**——GitHub 不会把 raw 视频渲染成播放器，`![]()` 只会破图 |

判据只看**扩展名**：`.png/.jpg/.gif` → `![]()`；`.webm/.mp4/.mov` → 链接。

## 机制 = 插件里两个通用脚本（任何仓库同样跑，自动从 `git remote` 探 source repo，落在 `<slug>/<ctx>/`）

先解析插件根，脚本都在其 `scripts/` 下：

```bash
PLUGIN="${AGENTLOOP_ROOT:-$HOME/.claude/plugins/marketplaces/arcblock-agent-skills/plugins/agentloop}"
```

- **单个文件**：`bash "$PLUGIN/scripts/gh-upload-media.sh" <media> [name]`
  → 自动选写通道（gh contents API / git push）→ content-type=`image/*`|`video/*` 闸 → stdout 打印 raw.githubusercontent/main URL。
- **整目录**：`SCREENSHOT_DIR=<dir> CONTEXT=<ctx> bash "$PLUGIN/scripts/gh-upload-dir.sh"`
  → 遍历目录（png/jpg/jpeg/gif/webm/mp4/mov）逐个调上面那个 → stdout 每行 `filename\turl`；任一失败追加一行
  `UPLOAD_FAILED\t<原因>`（**始终 exit 0**，失败看这行不看退出码）。

## Usage

```
/agentloop:media-upload <dir>          # 上传目录下所有媒体 → filename\turl map
/agentloop:media-upload <file>         # 单个（图片或视频）

# 消费方 skill 的 bash 里直接调脚本（自动化调机制、不调 slash 命令）：
SCREENSHOT_DIR="$SHOT_DIR" CONTEXT="pr${PR}" bash "$PLUGIN/scripts/gh-upload-dir.sh" > url-map.tsv
```

拿到 map 后按上表**分流内嵌**。收到 `UPLOAD_FAILED` 行时在报告顶部显示 `> ⚠️ 媒体上传失败 — <原因>`
并降级 `SendUserFile` 直投（不能内嵌，但不产出损坏文件）。

## 上传器两通道都不可用时：浏览器原生附件上传（第三层兜底，issue #3010）

`SendUserFile` 只把文件发给当前对话的人，**不会**让证据落地到 GitHub——PR/issue 上依旧没有可核验的
截图。这类 review 门控（`ui-verify` 的 UI 证据闸最典型）需要证据**持久发布在 GitHub 上**，`gh` 与图床
仓 clone 又都不可用时，唯一确定性可重复的兜底是**驱动一个已登录 GitHub 的浏览器**走原生附件上传
（本轮真实产出过 arc#2991 / arcblock-site#165 / Site PR #166 的截图证据）：

1. 打开目标 PR/issue 的评论框，触发 file chooser（拖拽或点击 attach 按钮），选中本地媒体文件。
2. GitHub 把文件传到自己的附件 CDN，返回 `https://github.com/user-attachments/assets/<uuid>`
   （或旧版 `user-images.githubusercontent.com/...`）URL 并自动插入引用到评论正文。
3. 提交前用浏览器截图/读 DOM 确认媒体已渲染成可见图片/视频，不是破链接——不能假设上传成功。
4. **同时贴到 PR 和它关联的 issue**（`Fixes #<n>` / `Part of #<n>`），不要只贴一处。

这条 URL 家族（`github.com/user-attachments/assets/…`、`user-images.githubusercontent.com/…`）与
`raw.githubusercontent.com/<repo>/main/…` 一样被下游门控（如 `.claude/skills/ui-verify/scripts/gate-comment.ts`
的 `isPublishedUrl()`）当作**已发布**证据；本地文件路径、`SendUserFile` 附件、或任何未验证可匿名读的
URL 都不算——**上传/发布失败绝不能被下游报告描述成"已完成验证"**，该标注 `BLOCKED`/"证据缺失"就
标注，不用中性措辞掩盖（术语与 `ui-verify`/`pr-review`/`pr-sweep`/`issue-review` 保持一致）。

此兜底需要**已认证的 GitHub 浏览器会话**，与 `gh-upload-media.sh` 的两条自动化通道不同层级——不总是
每个环境都可行；不可行时按上一节降级 `SendUserFile`，消费方门控按"证据缺失"处理，不得放行。

## 为什么必须是 raw.githubusercontent.com/main（脚本已强制，别自己换）

- **#1334**：GitHub MCP 写侧（`add_issue_comment` / `issue_write`）的 sanitizer 把 `![](url)` **剥成纯链接**——
  唯一例外是 host=`raw.githubusercontent.com` **且** ref=`main`。绝不用 `cdn.jsdelivr.net`、绝不用 session 分支 ref。
  （`gh pr comment` / `gh issue comment` 发的评论不受此限，但统一走 raw.githubusercontent/main 对两条发帖通道都安全。）
- **#1079**：MCP 文件写工具（`create_or_update_file` / `push_files`）对二进制**二次 base64 编码** → 存成
  ASCII 字符串不是媒体。**媒体二进制永远不走任何 MCP 写工具**——脚本走 gh contents API 或 git push，
  并对返回 URL 做 content-type=`image/*`|`video/*` 抽查（非二者 = 双编码/损坏，判失败）。

## 双通道（脚本自动选，调用方无需关心）

- **通道 A — `gh` contents API**：`gh` 存在且对图床仓有写权限时用（本地 PAT / 有写权限的 token）；带瞬时 5xx 重试。
- **通道 B — git push**：无 `gh`（或无写权限）时，`cp` 进已 clone 的图床仓工作区（`$ASSETS_REPO_DIR` 或探测
  `~/loop-agent-assets` 等）→ `commit` + `push origin main`（带 fetch+rebase 重试）。cloud routine 里
  gh token 是 placeholder（对本 org 写接口 403，#1334），走的就是这条——前提是图床仓配为第二个 git source。

两通道都不可用 → 脚本 exit 2 → 消费方降级 `SendUserFile`。

## 消费方

- `test-sweep`（arc）、`ui-verify`（arcblock-site，及将来 did）等在自己的 bash 里调 `gh-upload-dir.sh`。
- **机制永远是那两个脚本，别再各仓手撸 `for f in *.png` 循环**（arc 与 arcblock-site 曾各写一份、已漂移——#1037）。
  需要人读的 doctrine + 可发现入口就是本 skill。

