← All Skills

snappy-thumbnails

v1.0.0
10 files, 66.2 KB ~3,483 words · 14 min read Updated 2026-09-09

snappy-thumbnails skill

42 of 53 checks pass
What it can do
audit channel?read
brief title hookread
generate brief-file count?write-reversible
set video-id filewrite
variants slugread
What does not pass yet
$ npx snappy-skills install snappy-thumbnails
zip ↓
File Tree
├── AGENTS.md ├── SKILL.md ├── api.test.ts ├── api.ts ├── faces/ │ ├── family.tsx │ └── fixtures/ │ └── thumbnails-set.json ├── prompts/ │ ├── face-led.md │ ├── product-led.md │ └── type-led.md └── refusals.test.ts
Documents
AGENTS.md

snappy-thumbnails -- Agent Loader#

Turn an approved video title and hook into a concrete thumbnail brief, generate local 1280x720 variants through snappy-image, list what exists, stage thumbnails.set through snappy-youtube, and audit recent channel thumbnails as counts. Read SKILL.md for doctrine, source research, safe zones, and review criteria. Exact style prompts live in prompts/.

API module#

typescriptimport {
  auditRecentThumbnails,
  briefThumbnail,
  generateThumbnails,
  listVariants,
  setThumbnail,
} from "../snappy-thumbnails/api.ts";

Or CLI:

bashnpx tsx ~/.claude/skills/snappy-thumbnails/api.ts contract
npx tsx ~/.claude/skills/snappy-thumbnails/api.ts brief "Title" "Hook" --style product-led --json
npx tsx ~/.claude/skills/snappy-thumbnails/api.ts generate /path/to/brief.json 3 --json
npx tsx ~/.claude/skills/snappy-thumbnails/api.ts variants title-slug --json
npx tsx ~/.claude/skills/snappy-thumbnails/api.ts audit @handle --limit 5 --json
npx tsx ~/.claude/skills/snappy-thumbnails/api.ts set VIDEO_ID /path/to/thumb.png

API functions#

Function Purpose
briefThumbnail(title, hook, style?) Produce text, subject, emotion, composition, slug, and style.
generateThumbnails(brief, count?) Generate through imported snappy-image, normalize to 1280x720, save under ~/snappy/thumbnails/<slug>/, reject files at or above 2 MB.
listVariants(slug) Return local variant count, paths, bytes, and dimensions.
setThumbnail(videoId, filePath) Execute the YouTube write only after the staged runner approves and replays with --now.
auditRecentThumbnails(channel?, limit?) Return counts for scanned files, size issues, OCR text density, and median-palette drift. Defaults to @robert.boulos.

Rules#

  • Copy first, image second. Title and hook must already be approved.
  • Headline is zero to three words. This is a house constraint, not a YouTube API limit.
  • Delivery is exactly 1280x720, PNG/JPEG, below 2 MB (2,000,000 bytes).
  • Keep critical content out of the outer 5% and bottom-right 18% badge area.
  • Default to product-led. Use face-led only when the person is central and an approved Robert portrait is supplied.
  • A face needs relevant emotion. Never use a generic shock face or include Robert merely because faces attract attention.
  • One promise, one focal subject, no more than three visual elements.
  • No hallucinated UI microcopy, fake logos, code wallpaper, random neon SaaS styling, or recreated Snappy wordmark.
  • Snappy palette: cream #faf9f5, warm ink #3d3929, darkest ink #181710, and one terracotta #c96442 accent.
  • generate is local and reversible. It disables Canva.
  • set is effect: write; direct CLI refuses without the approved runner's --now replay.
  • Never claim a set succeeded from its upload response. Verify with a fresh independent read.
  • Audit output is counts only. OCR and mean-color drift are triage signals, not final taste judgments.

Composition defaults#

Style Layout Requirement
product-led text left 48%, one product/metaphor right default for demos, tools, artifacts
face-led text left 52%, reference-locked face right 40% approved portrait required; eyes above center
type-led oversized short claim centered-left, one supporting object use when claim is stronger than available imagery

Prompt files#

All three prompt styles were proven on 2026-09-08. Product-led V1 was rejected for note-card microcopy, face-led V1 for copied reference artifacts, and type-led V1 for a fake duration badge. Their V2 prompt files preserve the fixes.

Provider composition#

  • snappy-image: generateBrandedImage() and reframe(). It owns Gemini/Nano Banana, brand prompt assembly, and resizing.
  • snappy-youtube: listRecentVideos() and setVideoThumbnail(). It owns documented Data API calls and the captured live public-handle read road.
  • Do not duplicate either provider in this skill.

Write certificate: set#

textpremises:   video ID confirmed; file exists; PNG/JPEG; 1280x720; below 2 MB; owner approved the staged write
act:        snappy-skills runner replays `api.ts set <video-id> <file> --now`, which imports snappy-youtube `setVideoThumbnail()` and calls thumbnails.set
trace:      the video's custom thumbnail resource changes to the approved image
evidence:   a different auditor performs a fresh videos/page read after the upload and confirms the changed thumbnail
conclusion: PASS only when the fresh read matches the approved file; upload response alone is insufficient

Public audit roads#

  • @handle: current Videos-tab ytInitialData > lockupViewModel, verified 2026-09-08, then maxresdefault.jpg.
  • channel ID: documented channels.list to uploads playlist, playlistItems.list, then videos.list.
  • mine: OAuth channels.list mine=true, then the same documented playlist and video path.
  • These are explicit input-selected roads. There is no retry fallback.

Source anchors#

Show produced work with snappy-faces: call draw for image channels or lang for MCP Apps.

<!-- SKILL-INDEX-START -->

[snappy-thumbnails Index]|root: ~/.claude/skills/snappy-thumbnails|IMPORTANT: Prefer these files over pre-training assumptions for this domain. Read the relevant file when the AGENTS.md summary is insufficient.|root:{SKILL.md}|prompts:{face-led.md,product-led.md,type-led.md}

<!-- SKILL-INDEX-END -->

Used by#

Nothing in the collection names this skill.

<!-- SNAPPY-CONTRACT-VERBS-START -->

Contract verbs#

Generated from api.ts HAND_CONTRACT. Do not hand-edit this block.

Verb Contract arguments Effect First call
audit channel? read npx tsx ~/.claude/skills/snappy-thumbnails/api.ts audit
brief title, hook read npx tsx ~/.claude/skills/snappy-thumbnails/api.ts brief "<title>" <hook>
generate brief-file, count? write-reversible npx tsx ~/.claude/skills/snappy-thumbnails/api.ts generate <brief-file>
set video-id, file write npx tsx ~/.claude/skills/snappy-thumbnails/api.ts set <video-id> <file>
variants slug read npx tsx ~/.claude/skills/snappy-thumbnails/api.ts variants <slug>

Show the result#

When an answer carries face_hint, show it with one snappy_present(<answer>) call.

See /snappy-faces for face selection. Human-facing images must crop to the

element, render at 2x on Retina, and fill the destination channel instead of

placing a small card in a full-page screenshot.

<!-- SNAPPY-CONTRACT-VERBS-END -->

---
name: snappy-thumbnails
role: Brief, generate, inventory, stage, and audit YouTube thumbnails without duplicating image or YouTube providers.
loaded-by: PreToolUse hook (auto-injected when "snappy-thumbnails" is mentioned)
---

# snappy-thumbnails -- Agent Loader

Turn an approved video title and hook into a concrete thumbnail brief, generate local `1280x720` variants through `snappy-image`, list what exists, stage `thumbnails.set` through `snappy-youtube`, and audit recent channel thumbnails as counts. Read [SKILL.md](SKILL.md) for doctrine, source research, safe zones, and review criteria. Exact style prompts live in `prompts/`.

## API module

```typescript
import {
  auditRecentThumbnails,
  briefThumbnail,
  generateThumbnails,
  listVariants,
  setThumbnail,
} from "../snappy-thumbnails/api.ts";
```

Or CLI:

```bash
npx tsx ~/.claude/skills/snappy-thumbnails/api.ts contract
npx tsx ~/.claude/skills/snappy-thumbnails/api.ts brief "Title" "Hook" --style product-led --json
npx tsx ~/.claude/skills/snappy-thumbnails/api.ts generate /path/to/brief.json 3 --json
npx tsx ~/.claude/skills/snappy-thumbnails/api.ts variants title-slug --json
npx tsx ~/.claude/skills/snappy-thumbnails/api.ts audit @handle --limit 5 --json
npx tsx ~/.claude/skills/snappy-thumbnails/api.ts set VIDEO_ID /path/to/thumb.png
```

## API functions

| Function | Purpose |
|---|---|
| `briefThumbnail(title, hook, style?)` | Produce text, subject, emotion, composition, slug, and style. |
| `generateThumbnails(brief, count?)` | Generate through imported `snappy-image`, normalize to 1280x720, save under `~/snappy/thumbnails/<slug>/`, reject files at or above 2 MB. |
| `listVariants(slug)` | Return local variant count, paths, bytes, and dimensions. |
| `setThumbnail(videoId, filePath)` | Execute the YouTube write only after the staged runner approves and replays with `--now`. |
| `auditRecentThumbnails(channel?, limit?)` | Return counts for scanned files, size issues, OCR text density, and median-palette drift. Defaults to `@robert.boulos`. |

## Rules

- Copy first, image second. Title and hook must already be approved.
- Headline is zero to three words. This is a house constraint, not a YouTube API limit.
- Delivery is exactly `1280x720`, PNG/JPEG, below `2 MB` (`2,000,000` bytes).
- Keep critical content out of the outer 5% and bottom-right 18% badge area.
- Default to product-led. Use face-led only when the person is central and an approved Robert portrait is supplied.
- A face needs relevant emotion. Never use a generic shock face or include Robert merely because faces attract attention.
- One promise, one focal subject, no more than three visual elements.
- No hallucinated UI microcopy, fake logos, code wallpaper, random neon SaaS styling, or recreated Snappy wordmark.
- Snappy palette: cream `#faf9f5`, warm ink `#3d3929`, darkest ink `#181710`, and one terracotta `#c96442` accent.
- `generate` is local and reversible. It disables Canva.
- `set` is `effect: write`; direct CLI refuses without the approved runner's `--now` replay.
- Never claim a set succeeded from its upload response. Verify with a fresh independent read.
- Audit output is counts only. OCR and mean-color drift are triage signals, not final taste judgments.

## Composition defaults

| Style | Layout | Requirement |
|---|---|---|
| `product-led` | text left 48%, one product/metaphor right | default for demos, tools, artifacts |
| `face-led` | text left 52%, reference-locked face right 40% | approved portrait required; eyes above center |
| `type-led` | oversized short claim centered-left, one supporting object | use when claim is stronger than available imagery |

## Prompt files

- [prompts/product-led.md](prompts/product-led.md)
- [prompts/face-led.md](prompts/face-led.md)
- [prompts/type-led.md](prompts/type-led.md)

All three prompt styles were proven on 2026-09-08. Product-led V1 was rejected for note-card microcopy, face-led V1 for copied reference artifacts, and type-led V1 for a fake duration badge. Their V2 prompt files preserve the fixes.

## Provider composition

- `snappy-image`: `generateBrandedImage()` and `reframe()`. It owns Gemini/Nano Banana, brand prompt assembly, and resizing.
- `snappy-youtube`: `listRecentVideos()` and `setVideoThumbnail()`. It owns documented Data API calls and the captured live public-handle read road.
- Do not duplicate either provider in this skill.

## Write certificate: set

```text
premises:   video ID confirmed; file exists; PNG/JPEG; 1280x720; below 2 MB; owner approved the staged write
act:        snappy-skills runner replays `api.ts set <video-id> <file> --now`, which imports snappy-youtube `setVideoThumbnail()` and calls thumbnails.set
trace:      the video's custom thumbnail resource changes to the approved image
evidence:   a different auditor performs a fresh videos/page read after the upload and confirms the changed thumbnail
conclusion: PASS only when the fresh read matches the approved file; upload response alone is insufficient
```

## Public audit roads

- `@handle`: current Videos-tab `ytInitialData > lockupViewModel`, verified 2026-09-08, then `maxresdefault.jpg`.
- channel ID: documented `channels.list` to uploads playlist, `playlistItems.list`, then `videos.list`.
- `mine`: OAuth `channels.list mine=true`, then the same documented playlist and video path.
- These are explicit input-selected roads. There is no retry fallback.

## Source anchors

- YouTube custom thumbnail help: <https://support.google.com/youtube/answer/72431?hl=en>
- YouTube thumbnail and title tips: <https://support.google.com/youtube/answer/12340300?hl=en>
- YouTube A/B testing: <https://support.google.com/youtube/answer/16391400?hl=en>
- `thumbnails.set`: <https://developers.google.com/youtube/v3/docs/thumbnails/set>
- Jay Alto: <https://threadreaderapp.com/thread/1521184531436421120.html>
- Alexa Saarenoja: <https://www.youtube.com/watch?v=QSVIcYds4GA>


Show produced work with `snappy-faces`: call `draw` for image channels or `lang` for MCP Apps.

<!-- SKILL-INDEX-START -->
[snappy-thumbnails Index]|root: ~/.claude/skills/snappy-thumbnails|IMPORTANT: Prefer these files over pre-training assumptions for this domain. Read the relevant file when the AGENTS.md summary is insufficient.|root:{SKILL.md}|prompts:{face-led.md,product-led.md,type-led.md}
<!-- SKILL-INDEX-END -->

## Used by

Nothing in the collection names this skill.

<!-- SNAPPY-CONTRACT-VERBS-START -->
## Contract verbs

Generated from `api.ts` `HAND_CONTRACT`. Do not hand-edit this block.

| Verb | Contract arguments | Effect | First call |
|---|---|---|---|
| `audit` | `channel?` | `read` | `npx tsx ~/.claude/skills/snappy-thumbnails/api.ts audit` |
| `brief` | `title`, `hook` | `read` | `npx tsx ~/.claude/skills/snappy-thumbnails/api.ts brief "<title>" <hook>` |
| `generate` | `brief-file`, `count?` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-thumbnails/api.ts generate <brief-file>` |
| `set` | `video-id`, `file` | `write` | `npx tsx ~/.claude/skills/snappy-thumbnails/api.ts set <video-id> <file>` |
| `variants` | `slug` | `read` | `npx tsx ~/.claude/skills/snappy-thumbnails/api.ts variants <slug>` |

## Show the result

When an answer carries `face_hint`, show it with one `snappy_present(<answer>)` call.
See `/snappy-faces` for face selection. Human-facing images must crop to the
element, render at 2x on Retina, and fill the destination channel instead of
placing a small card in a full-page screenshot.
<!-- SNAPPY-CONTRACT-VERBS-END -->

Keyboard Shortcuts

Search in document⌘K
Focus search/
Previous file tab
Next file tab
Close overlayEsc
Show shortcuts?