---
name: news-woodblock
description: Generate a ukiyo-e woodblock print for a science-news story, with limerick and summary, and publish to the woodblocks.sebland.com gallery. Trigger words - woodblock, news woodblock, science news woodblock, make a woodblock, daily woodblock.
triggers:
  - make a woodblock
  - news woodblock
  - science news woodblock
  - daily woodblock
  - generate a woodblock
---

# News-Woodblock Skill

Produce one curated ukiyo-e woodblock print tied to a recent science-news story, post it to the Discord channel, and publish to https://woodblocks.sebland.com/.

This skill is the single source of truth for the 8-step pipeline that produced woodblocks #162–170. Use it both for manual runs (triggered in chat) and for the daily cron at ~13:17:42 UTC.

## The 8-Step Pipeline

### 1. Pick a story (WebSearch, last 48 hours)

Search for one genuinely positive science breakthrough from the last 48 hours. Try these query shapes in order, broadening as needed:

```
WebSearch: "good news science breakthrough <month> <year> last 48 hours"
WebSearch: "science breakthrough <date> nature conservation discovery"
WebSearch: "<specific topic> breakthrough <month> <year>"
```

Preferred sources (most woodblocks land on these):
- sciencedaily.com/releases/
- nature.com, nature astronomy, proceedings of the royal society B
- eurekalert.org
- news-medical.net
- smithsonianmag.com

**Avoid:** politics, war, anything dark. The gallery is a celebration page.

**Dedup gate:** before committing to a story, run:

```bash
/root/seb/skills/news-woodblock/scripts/dedup-check.py \
    --candidate "<headline + one-sentence description of the story>"
```

Exit 0 = unique enough to proceed. Exit 1 = overlap ≥0.35 with an existing entry, pick a different story.

### 2. Write the limerick

Read `/root/seb/skills/limerick/SKILL.md` FIRST (don't compose from memory). Follow:
- Meter enforcement (tap test, 3/3/2/2/3 stressed beats, 7–10 / 5–6 / 7–10 syllables)
- **Subject-anchor rule:** line 1 must open with the article's literal subject noun phrase; line 5 must state the actual finding. Not a metaphor — the thing.

Self-review against the 10-item checklist in the limerick skill before accepting.

### 3. Generate the image

Read `/root/seb/skills/image-gen/SKILL.md` for the ukiyo-e preset reference, then call:

```bash
/root/seb/skills/news-woodblock/scripts/generate.py \
    --subject "A concrete visual scene drawn from the article — no abstractions" \
    --palette "indigo" "vermillion" "sumi ink black" "washi cream" "burnt sienna" \
    --output /root/box/app/static/woodblocks/images/<NUM>_<slug>.png
```

Subject rules:
- Must be what the article literally describes. If the article is about fossil bees in a hutia tooth socket, the subject is "burrowing bees nesting inside a Hispaniola cave hutia's fossilized jawbone in amber-lit gloom" — not "bees in a cave."
- 20–40 words. Include at least one concrete noun, one verb, one setting cue.
- No western faces, no text, no modern tech (unless article is about modern tech, then style it as paper-cut iconography).

Palette rules:
- 3–5 named colors. Always include one deep dark (sumi ink black, charcoal) and one warm neutral (washi cream, bone white).
- Thematically match the article (cave bees → amber, bone cream, deep indigo; reactor → steel grey, copper, deep red).

Get `<NUM>` from:

```bash
NUM=$(/root/seb/skills/news-woodblock/scripts/next-number.sh)
```

The `<slug>` is a short lowercase identifier (e.g. `beesbone`, `kalpakkam`, `satyrex`) that will appear in the filename. Keep it under 16 chars and a-z0-9 only.

### 4. Subagent review gate

Launch an Explore subagent to look at the generated PNG and judge:
1. **Style adherence** — thick-to-thin brush outlines? flat colors? visible wood grain? No photographic gradients?
2. **Subject match** — does the image depict the article's subject? (not a generic woodblock)

If either fails, retry up to 2 more times with an adjusted `--subject` or `--palette`. After 3 total attempts, pick a different story rather than ship something bad.

### 5. Add to gallery

```bash
/root/seb/skills/news-woodblock/scripts/add-to-gallery.py \
    --num <NUM> \
    --name <slug> \
    --date "$(date -u +%Y-%m-%d)" \
    --limerick $'Line 1\nLine 2\nLine 3\nLine 4\nLine 5' \
    --summary 'One or two sentences summarizing the article and source.' \
    --source 'https://direct-article-url'
```

This inserts the card into `/root/box/app/static/woodblocks/index.html` and bumps `window.__WB_V` by 1. Safe string-slice insertion — it refuses if a card with that number already exists.

### 6. Deploy

```bash
/root/seb/skills/news-woodblock/scripts/deploy.sh <NUM> <slug>
```

This script: generates the `.webp` via cwebp → rebuilds the web docker → brings it up → purges Cloudflare cache → verifies the gallery HTML has the new entry.

### 7. Post to Discord

**Always** — manual and cron runs alike post the finished woodblock to the channel with a gallery link. The post includes image + limerick + summary + source article link + https://woodblocks.sebland.com/ link.

```bash
/root/seb/skills/news-woodblock/scripts/post-discord.py \
    --num <NUM> \
    --headline '<Short headline, ~6 words>' \
    --limerick $'Line 1\nLine 2\nLine 3\nLine 4\nLine 5' \
    --summary 'One or two sentence summary.' \
    --source 'https://article-url' \
    --image /root/box/app/static/woodblocks/images/<NUM>_<slug>.png
```

### 8. Update memory

Save one fact entry so next session knows the current count:

```bash
curl -s -X POST http://localhost:7890/memory/save \
  -H "Content-Type: application/json" \
  -d '{"type":"fact","content":"Woodblock #<NUM> (<slug>) posted on <date>. <headline>","contact":"discord:1484079826166874162"}'
```

## Cron Setup (Systemd User Timer)

Two unit files (both already written by this skill):

- `/root/.config/systemd/user/news-woodblock.service` — oneshot, invokes `scripts/daily-cron.sh`
- `/root/.config/systemd/user/news-woodblock.timer` — fires daily at **13:17:42 UTC** (smart offset to avoid :00 collisions with every other timer on the box)

To activate:

```bash
systemctl --user daemon-reload
systemctl --user enable --now news-woodblock.timer
# Verify it's queued:
systemctl --user list-timers | grep news-woodblock
```

To disable:

```bash
systemctl --user disable --now news-woodblock.timer
```

The cron wrapper (`scripts/daily-cron.sh`) invokes `claude -p` non-interactively and hands it this skill's SKILL.md. Cron runs execute all 8 steps including the Discord post — the channel gets an automated announcement with the gallery link at the scheduled time.

Logs go to `/var/log/news-woodblock/<date>.log` and to `journalctl --user -u news-woodblock.service`.

## Manual Invocation (Discord trigger)

If a user in Discord says "make a woodblock" or similar, run all 8 steps interactively in a single message, and post the result via step 7. Takes ~2–4 minutes end-to-end.

## Failure Modes

| Symptom | Fix |
|---------|-----|
| `next-number.sh` outputs something lower than gallery max | Someone added a PNG without adding to index.html. Inspect `ls /root/box/app/static/woodblocks/images/*.png` vs gallery. |
| imagen returns empty `generated_images` | Safety filter tripped. Soften the subject (no violence, no gore, no politics) and retry. |
| `add-to-gallery.py` can't find the array terminator | The index.html structure changed. Read it and look for the `\n  }\n];` pattern near the end. |
| CF purge returns `success: false` | Check CF_API_TOKEN permissions in /root/seb/.env. |
| Deploy shows `(not yet visible)` | CF edge can take ~30s. Re-curl with `?_t=<ts>` bust after a minute. |
| Discord post returns 401 | DISCORD_TOKEN expired or malformed in /root/seb/.env. |
| Cron fires but `claude` not found | The user's PATH inside the systemd unit doesn't include claude-code's install dir. Add `Environment=PATH=...` to the .service file. |

## File Structure

```
/root/seb/skills/news-woodblock/
├── SKILL.md                       # This file
└── scripts/
    ├── next-number.sh             # Find next woodblock number
    ├── generate.py                # Imagen 4.0 ultra, ukiyo-e preset baked in
    ├── add-to-gallery.py          # AST-safe index.html insertion + cache bust
    ├── deploy.sh                  # cwebp + docker + CF purge + verify
    ├── post-discord.py            # Multipart PNG upload to Discord
    ├── dedup-check.py             # Jaccard overlap vs existing summaries
    └── daily-cron.sh              # systemd timer wrapper
```

## Related Skills

- `/root/seb/skills/limerick/SKILL.md` — meter rules + subject-anchor rule
- `/root/seb/skills/image-gen/SKILL.md` — ukiyo-e preset reference (opt-in; doesn't affect other image-gen usage)
- `/root/seb/skills/cloudflare/SKILL.md` — CF API token handling
