Extract newsletter scripts to a neutral scripts/newsletter/ engine and add side-by-side support for all three AI coding tools off one source of truth. - Move 9 scripts + substack config from .claude/skills/**/scripts to scripts/newsletter/ (history preserved); fix PROJECT_ROOT depth and cross-require/config paths; repoint all SKILL.md invocations - Add canonical AGENTS.md; reduce CLAUDE.md to an @AGENTS.md import - Add opencode.json (permissions, no MCP); skills auto-discovered in place - Add codex/prompts/*.md (6 prompts) + copy installers (install.sh/.ps1) - Add docs/multi-tool-usage.md (setup, per-tool invocation, teardown) + README pointer
3.3 KiB
description, argument-hint
| description | argument-hint |
|---|---|
| Add an image to today's newsletter Bonus → Images with a detected label | <image-url> |
Add an image to the newsletter (image handler)
Given an image URL, resolve a human-readable label and insert  into today's newsletter Bonus → Images. Does not classify/route — that is /prompts:mt-add-url's job.
Image URL: $ARGUMENTS
Shared scripts: scripts/newsletter/ (run from repo root). Shared procedure: .claude/skills/mt-add-url/references/newsletter-post-mechanics.md — follow it for post find/create, Bonus insertion, language rules.
Label priority: figure caption → source post title → user-typed input.
Workflow
1. Detect source
node scripts/newsletter/detect-image-source.js "<url>"
→ { original_url, clean_url, isSubstack, uuid?, innerUrl? }.
When invoked directly (not via mt-add-url), first check accessibility/duplicate and skip accordingly:
node scripts/newsletter/add-url.js "<url>" # expect route:image; skip if duplicate/!accessible
2a. Substack image (isSubstack: true with uuid)
node scripts/newsletter/find-substack-post.js --uuid <uuid>
found: false→ retry deeper (slower, ~3 months back, 40-fetch cap; warn the user):On a miss the result reportsnode scripts/newsletter/find-substack-post.js --uuid <uuid> --deepscanned,budget(40),cutoff— mention how far back it looked.- Still
found: false→ go to step 3 (ask) and/or step 4 (add publication).
When found: true — pick the label:
ByteByteGo attaches no captions and image order can't be auto-mapped, so the script returns candidates (the issue's topic titles).
- If
captionis non-empty → propose it as default. - Otherwise present
candidatesto the user: "Image is from [postTitle] (postUrl). Which title matches it?" Offer the candidates (numbered list if many). Always include a "Type my own" escape. - Label = user's pick (or typed). Localize to Vietnamese where natural; keep proper nouns.
2b. Non-Substack image (isSubstack: false)
Best-effort only: if you know the containing page, fetch it and read the OpenGraph title / nearby caption. Usually none → go to step 3.
3. Ask for a label (fallback)
If no label detected: "Couldn't detect a name for this image (<clean_url>). What label should I use?" (free-text).
4. Add-publication offer (Substack miss only)
If the user names the publication, offer to append that host to scripts/newsletter/config/substack-publications.json, then retry step 2a.
5. Insert into Bonus → Images
Follow the shared procedure to find/create today's post, then add under Images:
**Images:**

Create ### Bonus / **Images:** if missing.
6. Report
✅ Image added to Newsletter #[number] (Bonus → Images)
📄 content/post/YYYY/MM/DD/index.md
🏷️ label: "[label]" (source: candidate-pick | caption | user-input; post: [postTitle])
🖼️ [clean_image_url]
Checklist
- Source detected (substack + uuid, or non-substack)
- Label confirmed (caption, else user pick from candidates, else typed)
- Entry under Bonus → Images (section/subsection created if missing)
- Label localized to Vietnamese where natural (keep proper nouns)