跳到正文
cccskills
免费在 GitHub 公开

github-pr-images

Embed a local image file into an existing GitHub PR — either in the PR body or as a comment. Trigger when a request pairs a local image (screenshot, .png/.jpg, CleanShot capture, before/after) with an existing PR (by #number, URL, branch name, or "the open PR"), regardless of verb — attach, embed, add, put, post, drop, show, document. Also covers visually documenting test runs, bug repros, UI states, or CI failures on an existing PR. Uploads through `gh`'s `--attach` flag — except in the Claude Code web sandbox, which has no `gh` and hosts images through the PR branch's history instead. It also owns the PR's one `## Screenshots` comment — finding, creating, editing and verifying it — so other workflows (the `pr` skill's screenshot phase) hand it a composed body that references local image paths, and it posts it.

中文简介正在准备中。当前显示原文说明。

bikeindex/bike_index星标 308(GitHub 308 + cccskills 0)
集成工具GitHub

实测评分 未实测

查看安装方法

包含的文件(4)

  • SKILL.md8.1 KB
  • assets/commit_images.sh3.8 KB
  • assets/inline_attachments.rb1.2 KB
  • references/web-sandbox.md3.7 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Upload Image to PR

gh pr comment, gh pr edit and gh pr create take --attach '<file>#<alt text>', up to 50 per command. A markdown ![alt](tmp/x.png) reference to an attached path is rewritten in place to the uploaded user-attachments/assets/ URL. An <img src="tmp/x.png"> isn't — it keeps its path, and the upload is appended to the end as ![<basename>](url), as is any attached file the body doesn't reference.

There's no upload-only call: assets upload as part of posting, so callers hand over a body that references local paths, never URLs they need back first.

Who runs this

From a session with history, hand this skill to a subagent the way AGENTS.md's Who runs this describes — model: "sonnet" (it works out which comment and which upload route) — briefed with the PR number and either a composed body file referencing local image paths or the images plus where they go. It replies with the comment (or PR) URL and Step 4's verification result.

Preflight: which route this run takes

$CLAUDE_CODE_REMOTE decides which, before anything else here.

true — the web sandbox, which has no gh and follows references/web-sandbox.md for the whole flow.

Unset — make sure gh has --attach:

gh help pr comment | grep -q -- --attach || brew upgrade gh

gh help pr comment, not gh pr comment --help — the rtk hook swallows the latter's output. Still missing after the upgrade, or no gh at all → stop and say so, posting nothing.

Step 1: Resolve PR context

If the user didn't specify a PR number or URL, auto-detect it:

gh pr view --json number,url -q '"\(.number) \(.url)"'

Reference each image by its path from the repo root, the same in the body as in --attach. A path with special characters (Unicode narrow spaces from CleanShot X) goes through the project's tmp/ first:

cp /path/to/CleanShot*keyword*.png tmp/screenshot.png

Step 2: The body

A caller's body is posted verbatim — don't recompose it. Composing it yourself:

## Screenshots

<img src="tmp/screenshot.png" width="500">

When a ## Screenshots comment already exists (step 3 finds it), compose from its current body: a recapture replaces the image it supersedes in place — never a new "Updated"/"After merging main" section appended below the old shots. A state the comment doesn't show yet gets its own block, in the shape of the ones there.

Images in table cells want <img … width=…>; elsewhere ![alt](path) is fine. Don't give an <img>'s file a #alt on --attach — step 3's script finds its upload by the basename gh uses as the default alt.

Step 3: Post it

This skill owns the ## Screenshots comment: one per PR, body starting ## Screenshots even when what's under it isn't screenshots — retitle it and the next run can't find it.

gh api --paginate "repos/{owner}/{repo}/issues/$PR_NUMBER/comments" \
  --jq ".[] | {id, login: .user.login, body: .body[:14]}"

The comment whose body starts ## Screenshots is $SCREENSHOT_COMMENT_ID, whoever posted it — the web sandbox posts as a different account than local gh, so filtering on your own login misses a PR's sandbox-posted comment and adds a second. One by another login is never your last comment: update it through the stand-in below. --paginate matters — on a busy PR it often isn't on the first page.

A caller updating one page of a multi-page comment needs the current body to edit — hand it back on request: gh api repos/{owner}/{repo}/issues/comments/$SCREENSHOT_COMMENT_ID --jq .body.

Write the body to a file. With images to upload, attach one --attach per image the body references, and update $SCREENSHOT_COMMENT_ID in place rather than replacing it — a re-capture never posts a second comment. Which command depends on whether it's your last comment on the PR:

ME=$(gh api user --jq .login)
gh api --paginate "repos/{owner}/{repo}/issues/$PR_NUMBER/comments" --jq ".[] | select(.user.login == \"$ME\") | .id" | tail -1
  • No $SCREENSHOT_COMMENT_ID yet — post it: rtk proxy gh pr comment $PR_NUMBER --body-file <file> --attach tmp/a.png --attach tmp/b.png.
  • It's your last comment — --edit-last takes --attach, so the same command with --edit-last edits it in place and keeps its id.
  • You've commented since — --edit-last would hit that later one. Upload through a stand-in: post the body as a new comment with its --attaches, run the script below on it, write its body to a file (gh api …/issues/comments/<stand-in-id> --jq .body), PATCH that into $SCREENSHOT_COMMENT_ID as under With nothing to upload, then gh api -X DELETE the stand-in.

Then, on the comment the uploads landed on:

ruby .claude/skills/github-pr-images/assets/inline_attachments.rb {owner}/{repo} <comment-id>

rtk proxy because the hook reduces the output to "ok commented", and the comment URL it prints carries the id. The script moves each appended upload into its <img src> and PATCHes the comment; it stops without patching if a local src has no upload to take. An image already uploaded stays in the body as its asset URL — attach only what was recaptured.

With nothing to upload, edit in place: gh api -X PATCH repos/{owner}/{repo}/issues/comments/$SCREENSHOT_COMMENT_ID -F body=@<absolute-path> --jq .html_url. -F reads a file from @; -f would post the literal @<file>. The path is absolute because gh resolves @ against the shell's cwd.

Only edit the PR description when the user explicitly asks: gh pr edit $PR_NUMBER --body-file <file> --attach …, referencing images as ![alt](path) — the script patches comments only — with the existing body plus a ## Screenshots section — replace that section if it's already there rather than adding a second. The pr-guardrails hook denies gh pr edit until the pr skill is loaded.

Step 4: Verify

Read the posted body back:

gh api repos/{owner}/{repo}/issues/comments/<id> --jq .body

Pass: no tmp/ path left, an https://github.com/user-attachments/assets/ URL in place of each attached file, and still one ## Screenshots comment on the PR — rerun Step 3's listing. Don't curl those URLs — they 302 to a session-signed S3 URL that 403s unauthenticated.

Troubleshooting

IssueSolution
unknown flag: --attachbrew upgrade gh
no upload matched: <path>The <img>'s file wasn't attached, or its --attach gave a #alt — the script matches on the default alt, the basename
File path with special characters (e.g., Unicode narrow spaces from CleanShot)cp /path/CleanShot*keyword*.png tmp/screenshot.png
Upload refused--attach needs push access to the repo, and takes images and video only

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Read live production data from Bike Index through the admin OAuth token — Sidekiq and PgHero status, and the user-submitted bug reports — the same data as the cookie-gated dashboards, but agent-friendly JSON. Trigger when the user asks about production queue depth, job backlog, retries/dead jobs, running Sidekiq processes, or Postgres health (slow/blocked queries, index usage, unused/invalid indexes, connection counts, table/db sizes, vacuum/transaction-id danger) — and wants the *live* answer from production rather than logs or Honeybadger. Also trigger for anything about bug reports users have emailed in: what's been reported, searching/filtering them by status, tag or membership, or tagging one / linking it to the PR that fixes it. Also trigger when a request returns 401/expired and the AdminData token needs refreshing/re-authorizing. Not for reading log files (use production-log-inspection) or aggregated exception triage (bin/binx_hb).

中文简介正在准备中。当前显示原文说明。

bikeindex/bike_index3082026年10月29日 更新

How to fix a test that fails intermittently in Bike Index — one that passes locally but fails on CI, fails on one shard, passes on re-run, or is already tagged `:flaky`. **Read this before touching any spec that is failing intermittently, and before adding, raising, or relying on a `flaky:` tag or a `wait:` bump.** Trigger on a CI failure the user calls flaky, unreliable, intermittent, "green locally", "passes on retry", "fix CI", or a pasted `gh run`/Actions URL whose failure isn't reproducible; also whenever you catch yourself about to delete, skip, loosen, or retry an assertion to make a red build go green. **Equally: any spec that fails intermittently while you verify your own work** — deciding whether your change caused a flake, or whether to ship past one, is this skill's problem too, and it arrives with no CI run, no `:flaky` tag, and nobody but you calling it flaky.

中文简介正在准备中。当前显示原文说明。

bikeindex/bike_index3082026年10月29日 更新

Bike Index's frontend conventions — Tailwind class prefixing (`tw:`), the standard `twinput`/`twlabel`/`twlink` form/link classes, the `number_display` helper for numbers, `Counts` for site-wide figures (bikes registered, recoveries, organizations), the UI component library rule (every button is `UI::Button`/`UI::ButtonLink`, every typeahead/autocomplete is `UI::Forms::Combobox`, every FAQ is `UI::Accordion`, never hand-rolled markup), that **helpers are deprecated — render a view component taking full keyword arguments instead of adding or extending one**, ViewComponent rules (when to write a partial instead, keyword arguments, instance variables, `helpers.` prefix in templates), and `UI::Time::Component` for every date/time. Trigger when adding or modifying views (`.html.erb`), view components, Stimulus controllers, Tailwind classes, image or icon files under `app/assets/images` (which usually already exist), or any frontend code that touches styling or interactivity — including admin screens, whose unlayered legacy CSS inverts several of these rules. Stimulus.js is the JavaScript framework; SCSS and CoffeeScript files exist but are deprecated.

中文简介正在准备中。当前显示原文说明。

bikeindex/bike_index3082026年10月29日 更新

Capture desktop+mobile screenshots of Bike Index pages from the local `bin/dev` server via Playwright MCP, with a seeded-user identity gate that keeps PII out of uploaded images. Use whenever a task needs screenshots of local pages — PR documentation, bug repros, before/after comparisons across branches, design review, demos — including mid-interaction states like an open dropdown, a modal showing, a form mid-fill, or a hover. Use it even when the user just says "grab a screenshot" or "show me what this looks like" without naming Playwright. For a component that only renders under an env var / feature flag / hard-to-reach state (e.g. the review-app banner), screenshot its ViewComponent/Lookbook preview URL instead of a full page. **Also read the filename rule here before any `mcp__playwright__browser_take_screenshot` call**, including a one-off capture of some other site — it's what keeps PNGs out of the working tree.

中文简介正在准备中。当前显示原文说明。

bikeindex/bike_index3082026年10月29日 更新

Investigate and fix a specific Honeybadger exception in the Bike Index app — pull the fault, read its backtrace, find the offending code, write the fix. Trigger whenever a request names one Honeybadger error: a pasted fault URL (`app.honeybadger.io/projects/…/faults/…`), a fault ID, an error class plus "in production" (`Vips::Error`, `NoMethodError in AfterStolenRecordSaveJob`), or phrasings like "fix this Honeybadger error", "why is this erroring in prod", "what's causing this exception", "look at this HB fault". Use `bin/binx_hb` for fault and notice detail; it is the only Honeybadger reader this repo uses. Not for a broad production health sweep across many sources, and not for reading raw log files (that's production-log-inspection).

中文简介正在准备中。当前显示原文说明。

bikeindex/bike_index3082026年10月29日 更新

Bike Index conventions for browser specs (`type: :system, :js`) — **drive every step through the real UI** (no FactoryBot or `execute_script` shortcuts to skip what a user would do), every example pays a browser boot cost so bias toward fewer, denser examples that walk through state via clicks, prefer named-element matchers over CSS selectors, and combine same-setup work into one `it` even when scenarios feel independent. **Consult this skill any time you create or modify a `:js, type: :system` spec** — that includes everything under `spec/integration/` AND component system specs at `spec/components/**/*_system_spec.rb`. Read alongside the `rspec-testing` skill for the project's general `context`/`let` style.

中文简介正在准备中。当前显示原文说明。

bikeindex/bike_index3082026年10月29日 更新

查看 bikeindex 的全部技能

报告此技能的问题