Files
blog/.claude/skills/mt-add-image/SKILL.md
T
tiennm99 8f4e1928d8 feat(tooling): run blog from Claude Code, OpenCode & Codex on shared engine
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
2026-06-02 14:07:22 +07:00

4.7 KiB

name, description
name description
mt-add-image Image handler for the Hugo blog newsletter. Adds an image URL to today's newsletter Bonus → Images with a detected label. For Substack-hosted images it finds the source post (ByteByteGo and other configured publications) via RSS then sitemap, using the figure caption or post title as the label; for other sites it is best-effort; if no label is found it asks you to type one. Normally invoked by the mt-add-url meta skill after classification, but can be used directly for a known image URL.

Overview

mt-add-image is the image handler: given an image URL, it resolves a human-readable label and inserts ![label](image_url) into today's newsletter Bonus → Images. It does not classify/route — that is mt-add-url's job.

Shared scripts: scripts/newsletter/. Shared procedure: ../mt-add-url/references/newsletter-post-mechanics.md — follow it for post find/create, Bonus insertion, and language rules.

Label priority: figure caption → source post title → your typed input.

Input

A clean image URL (passed by mt-add-url, or given directly).

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 run the router to get accessibility + duplicate status and skip accordingly:

node scripts/newsletter/add-url.js "<url>"   # expect route:image; skip if duplicate/!accessible

(When dispatched by mt-add-url, that check already ran — don't repeat it.)

2a. Substack image (isSubstack: true with uuid)

Find the source post:

node scripts/newsletter/find-substack-post.js --uuid <uuid>
  • found: false → retry with the deeper sitemap crawl (slower — fetches posts ~3 months back, capped at 40 fetches total across all publications; warn the user it may take a while):
    node scripts/newsletter/find-substack-post.js --uuid <uuid> --deep
    
    On a miss the result reports scanned (posts fetched), budget (the 40-fetch cap), and cutoff (oldest date looked at) — mention how far back it looked.
  • found: false after --deep → no source post; go to step 3 (ask) and/or step 4 (add publication).

When found: true — pick the label (confirm-from-candidates): ByteByteGo doesn't attach captions to images (verified: empty alt, no <figcaption>), and image order can't be mapped to titles automatically because sponsor/video items interleave. So the script returns candidates — the issue's topic titles from the post's TOC. Pick the label this way:

  1. If caption is non-empty (some publications do caption images) → propose it as the default.
  2. Otherwise present candidates to the user via AskUserQuestion (the correct title is in this list):
    • "Image is from [postTitle] (postUrl). Which title matches it?"
    • Offer the candidates. If there are more than 4, show them as a numbered list in text and ask the user to pick a number (AskUserQuestion allows max 4 options).
    • Always include a "Type my own" escape.
  3. Label = the user's pick (or the typed value). Localize to Vietnamese where natural; keep proper nouns.

2b. Non-Substack image (isSubstack: false)

Best-effort only (no reverse-image-search): if you happen to know the page that contains the image, WebFetch it and read the OpenGraph title / nearby caption. Usually there's no containing page from just an image URL → go to step 3.

3. Ask for a label (fallback)

If no label was detected, use AskUserQuestion:

  • "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 a Substack image wasn't found and the user tells you which publication it's from, offer to append that host to scripts/newsletter/config/substack-publications.json, then retry step 2a. This grows coverage for next time.

5. Insert into Bonus → Images

Follow ../mt-add-url/references/newsletter-post-mechanics.md to find/create today's post, then add under Images:

**Images:**
![label](clean_image_url)

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 if present, else user picked from candidates, else typed)
  • Entry under Bonus → Images (section/subsection created if missing)
  • Label localized to Vietnamese where natural (keep proper nouns)