chore(.agents/skills): upload GitHub attachment skill

This commit is contained in:
Neko Ayaka
2026-08-05 20:30:41 +08:00
parent 55ea1767e4
commit a9473f0cac
4 changed files with 152 additions and 2 deletions
+2 -2
View File
@@ -40,7 +40,7 @@ Create a reviewable PR from the exact commits intended for publication.
```
Use `before: absent` for a new state and `after: removed` for a deleted state. A capture failure is blocking; record its reason instead of silently omitting the state.
9. Upload every local image as a GitHub user asset while composing the PR. Use the available GitHub/`gh` CLI workflow; do not commit screenshots to the source branch or another repository.
9. Upload every local image as a GitHub user asset by invoking `$upload-github-attachment` while composing the PR.
10. Put all pairs under `## Visual changes`, with an image row followed by its component or page name row:
```markdown
@@ -50,7 +50,7 @@ Create a reviewable PR from the exact commits intended for publication.
| Settings / Connection | Settings / Connection |
```
11. Verify that every user-asset URL renders in the created PR. Remove temporary worktrees only after upload succeeds; clear ignored `.vishot` captures when they are no longer useful locally.
11. Verify that every user-asset URL matches the PR intent. Remove temporary worktrees only after upload succeeds; clear ignored `.vishot` captures when they are no longer useful locally.
## Visual Evidence Contract
@@ -0,0 +1,41 @@
---
name: upload-github-attachment
description: Upload a local image or file to GitHub's user-attachments storage and return a URL suitable for issue, pull request, discussion, or comment Markdown. Use when Codex must embed local screenshots, videos, logs, or other evidence in GitHub content without committing the files to a repository.
---
# Upload GitHub Attachment
Use the bundled script so authentication, repository resolution, MIME detection, and URL extraction remain consistent.
## Requirements
- Require the `gh` CLI and `curl`.
- Prefer an existing `gh auth login` session.
- When interactive `gh` authentication is unavailable, require `GH_TOKEN` or `GITHUB_TOKEN` in the environment.
- Stop and ask the user to configure one of those authentication methods if `gh auth token` cannot resolve a token. Never print, persist, or interpolate the token into diagnostic output.
## Upload
Run from a checkout of the destination repository:
```bash
.agents/skills/upload-github-attachment/scripts/upload-github-attachment.sh /absolute/path/to/image.png
```
Specify the repository when the current directory cannot resolve it:
```bash
.agents/skills/upload-github-attachment/scripts/upload-github-attachment.sh \
--repo moeru-ai/airi \
/absolute/path/to/image.png
```
The script prints only the final `https://github.com/user-attachments/assets/...` URL. Embed it with `![](URL)` for an image or `[label](URL)` for another file.
## Verification
1. Request the returned URL and follow redirects.
2. Require HTTP 200 and the expected content type.
3. After creating or editing the GitHub content, reopen it and verify that the attachment renders.
The upload endpoint is currently undocumented by GitHub. Treat a rejected request as a blocking capability change instead of falling back to committing PR-only artifacts. Background and the observed Bearer-token flow: <https://island94.org/2026/08/programmatically-upload-attachments-to-github-issues-pull-requests-comments>.
@@ -0,0 +1,4 @@
interface:
display_name: "Upload GitHub Attachment"
short_description: "Upload files as GitHub user attachments"
default_prompt: "Use $upload-github-attachment to upload this file for a GitHub issue or pull request."
@@ -0,0 +1,105 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
printf 'Usage: %s [--repo OWNER/REPO] [--content-type MIME] FILE\n' "$(basename "$0")"
}
repository=''
content_type=''
while (($# > 0)); do
case "$1" in
--repo)
repository="${2:-}"
shift 2
;;
--content-type)
content_type="${2:-}"
shift 2
;;
--help|-h)
usage
exit 0
;;
--*)
printf 'Unknown option: %s\n' "$1" >&2
usage >&2
exit 2
;;
*)
if [[ -n "${attachment_file:-}" ]]; then
printf 'Only one file can be uploaded per invocation.\n' >&2
exit 2
fi
attachment_file="$1"
shift
;;
esac
done
if ! command -v gh >/dev/null 2>&1; then
printf 'GitHub CLI (gh) is required. Install it, then run gh auth login or set GH_TOKEN/GITHUB_TOKEN.\n' >&2
exit 127
fi
if ! command -v curl >/dev/null 2>&1; then
printf 'curl is required.\n' >&2
exit 127
fi
if [[ -z "${attachment_file:-}" || ! -f "$attachment_file" ]]; then
printf 'Provide one existing local file.\n' >&2
usage >&2
exit 2
fi
if [[ -z "$repository" ]]; then
repository="$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || true)"
fi
if [[ -z "$repository" ]]; then
printf 'Could not resolve a GitHub repository. Run inside a checkout or pass --repo OWNER/REPO.\n' >&2
exit 2
fi
attachment_token="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
if [[ -z "$attachment_token" ]]; then
attachment_token="$(gh auth token 2>/dev/null || true)"
fi
if [[ -z "$attachment_token" ]]; then
printf 'GitHub authentication is required. Run gh auth login or set GH_TOKEN/GITHUB_TOKEN.\n' >&2
exit 1
fi
if [[ -z "$content_type" ]]; then
if ! command -v file >/dev/null 2>&1; then
printf 'The file utility is required for MIME detection; install it or pass --content-type.\n' >&2
exit 127
fi
content_type="$(file --brief --mime-type "$attachment_file")"
fi
repository_id="$(GH_TOKEN="$attachment_token" gh api "repos/$repository" --jq .id)"
attachment_name="$(basename "$attachment_file")"
response="$(curl --silent --show-error --fail-with-body \
--request POST \
--header "Authorization: Bearer $attachment_token" \
--header 'Accept: application/json' \
--url-query "name=$attachment_name" \
--url-query "content_type=$content_type" \
--url-query "repository_id=$repository_id" \
--data-binary "@$attachment_file" \
'https://uploads.github.com/user-attachments/assets')"
attachment_url="$(printf '%s' "$response" | sed -n 's/.*"url"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
if [[ "$attachment_url" != https://github.com/user-attachments/* ]]; then
printf 'GitHub did not return a user-attachment URL.\n' >&2
exit 1
fi
printf '%s\n' "$attachment_url"