annotate-screenshots · git:20260724.ea80710 · 2026-07-24 · sha256 e72a922bb910856c
annotate-screenshots git:20260724.ea80710B
Immutable. This exact content is served forever at /api/v1/blob/e72a922bb910856c.
---
name: annotate-screenshots
description: >-
Bake hand-drawn boxes, arrows, labels, freeform strokes, and redactions onto
a screenshot so a PR reviewer or teammate sees exactly what changed and
where to look, instead of reading a caption and hunting for it. Use this
whenever a screenshot needs a callout — "point at the new button", "circle
the bug", "arrow to the diff", "blur out the API key in this screenshot",
"mark up this image before I attach it". Covers both annotating a live page
during capture (`uploads screenshot --annotate`, CSS selector targeting) and
annotating an image you already have (`uploads annotate`, pixel/freeform
coordinates only). For getting the resulting image into a GitHub PR or
issue, hand off to the github-screenshots skill — this skill only covers
producing the annotated image.
---
# Annotating screenshots
## When to annotate
Add annotations when a screenshot needs to draw the reader's eye somewhere
specific — a new element, a bug, a diff — rather than leaving them to find it
in the caption. A plain before/after pair is often enough; reach for
annotation when "look at the top-right corner" is easier to say with an
arrow than with words, or when a screenshot contains a secret that must be
hidden before it's shared anywhere (uploads.sh URLs are public).
Two commands, same underlying spec format:
| Command | Input | Targeting |
| ------------------------------------------------------- | ------------------------- | ----------------------------------------------- |
| `uploads screenshot <url\|file.html> --annotate <spec>` | captures a live page | CSS selectors (preferred) or pixel coordinates |
| `uploads annotate <image> --spec <spec>` | an existing PNG/JPEG file | pixel coordinates only — selectors are rejected |
**Prefer selectors, resolved live, over guessing pixel coordinates.** An
agent estimating `x`/`y`/`w`/`h` by eye is unreliable — it's easy to point at
the wrong element or clip a box awkwardly. `uploads screenshot --annotate`
resolves a CSS selector against the real DOM at capture time (local backend
only in v1 — `--via remote` rejects selector-bearing specs up front), so use
it whenever you're capturing the page yourself. Fall back to pixel
coordinates with `uploads annotate` only when you're marking up an image you
already have and there's no live page to query — a screenshot someone else
took, a photo, a diagram exported from elsewhere.
## The spec format
A spec is a JSON document: `{ "version": 1, "annotations": [...] }`.
`annotations` is a non-empty array; each entry has a `type` plus fields for
that type. House style (color, stroke weight, sketchy roughness, font) is
fixed and not configurable — the one style knob is a per-annotation `color`
override.
Annotation types:
| Type | Required fields | Notes |
| -------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `box` | `x, y, w, h` (or `selector`) | outlines a region |
| `arrow` | `from, to` points (or `selector`) | `to` = element center when resolved from a selector; `from` defaults to an offset above-right if omitted |
| `label` | `text`, plus `target` or `at` point (or `selector`) | a callout bubble with a leader line to `target` |
| `draw` | `points` (>= 2 `[x, y]` pairs) | freeform stroke, pixel-only, no selector support |
| `redact` | `x, y, w, h` (or `selector`), optional `style: "blur" \| "solid"` | hides a region (default `solid`; always `solid` for secrets) |
| `svg` | `fragment` (raw SVG; no `<script`, `href=`, or `url()`) | escape hatch, injected verbatim |
Rules: a geometric annotation (`box`/`arrow`/`label`/`redact`) takes **either**
pixel geometry **or** `selector`, never both — supplying both is rejected as
ambiguous. `draw` and `svg` never take a selector. Every type accepts an
optional `color` override.
### Worked example — selector mode (preferred)
Point at a specific element on a live page, label it, and redact a nearby
API key, then capture and annotate in one call:
```json
{
"version": 1,
"annotations": [
{ "type": "box", "selector": "#save-button" },
{ "type": "label", "text": "New: bulk save", "selector": "#save-button" },
{ "type": "redact", "selector": "[data-testid=api-key]", "style": "solid" }
]
}
```
```bash
uploads screenshot http://localhost:4321/settings --via local \
--annotate ./callouts.json --out settings.png
```
### Worked example — pixel mode (existing image only)
Annotating a screenshot you already have — no live page to query, so
geometry is given directly:
```json
{
"version": 1,
"annotations": [
{ "type": "box", "x": 120, "y": 84, "w": 220, "h": 48, "color": "#2563eb" },
{ "type": "arrow", "from": [520, 60], "to": [360, 108] },
{
"type": "draw",
"points": [
[40, 400],
[80, 380],
[120, 420],
[160, 390]
]
}
]
}
```
```bash
uploads annotate ./before.png --spec ./callouts.json --out ./before.marked.png
```
Coordinates are in image pixels, not CSS/viewport pixels — for a capture at
`--viewport 1280x800@2x` the image is 2560x1600, so pixel-mode geometry must
be scaled accordingly. This is exactly why selector mode is preferable
whenever a live page is available: the resolver handles device-scale
conversion for you.
Read a spec from stdin with `-`:
```bash
cat ./callouts.json | uploads annotate ./shot.png --spec -
```
## Redacting secrets
Every uploads.sh object is public once uploaded — see the github-screenshots
skill's "Cautions" section. Before capturing or attaching a screenshot that
shows an API key, token, password, session cookie, or other secret, add a
`redact` annotation with `style: "solid"` over it. Always use `solid` for
secrets — blurred text can be recoverable, so reserve `style: "blur"` for
non-sensitive visual cleanup where the surrounding shape should stay
legible, never for credentials. Do this at
capture time with `screenshot --annotate` when possible — it's one less
chance to forget before the image goes anywhere public.
## Workflow: capture, annotate, attach
1. **Capture and annotate together** when there's a live page:
`uploads screenshot <url> --via local --annotate ./spec.json ...`. This is
one call — no separate "capture then annotate" round-trip.
2. **Annotate an existing image** with `uploads annotate <image> --spec ...`
when there's no live page (an image from elsewhere, or a second annotation
pass on something already captured).
3. **Attach the result to a PR or issue** — this skill stops at producing the
annotated PNG. Hand off to the **github-screenshots** skill for hosting,
embedding, and the managed attachments comment (`uploads attach`,
`uploads put --pr`, before/after tables, etc.).
Invalid specs fail fast with per-annotation error messages
(`annotations[i]: <reason>`); a selector that matches nothing on the page
fails the command naming the selector rather than silently skipping it. See
`uploads annotate --help` / `uploads screenshot --help` for the full flag
reference, including `--seed` (deterministic sketchy rendering) and
`--format json`.