Maintenance
| Agent | Version | Last Changed |
|---|---|---|
| Link | v2.19.0 | July 03, 2026 |
Change Log
v2.19.0 — July 03, 2026
- Corrected the Zernio publish mechanics to the real MCP schema (fixes the v2.18.0 migration bug). v2.18.0 wrongly repointed
late_presign_upload→media_generate_upload_link, but that Zernio tool is a browser-only upload flow — PUTting to it silently fails. Clips now upload viamedia_get_media_presigned_url(filename, content_type, size)→ PUT → public URL,validate_mediabefore publish.posts_createcorrected to the real signature:content,platform(single string),account_id,media_urls(string),publish_now/schedule_minutes— one call per platform (noplatforms[], noplatformSpecificData); scheduling uses integerschedule_minutesvia Pythondatetime.
v2.18.0 — July 01, 2026
- Zernio migrated to its own MCP (gateway v1.7.4). Repointed
late_presign_upload→media_generate_upload_linkandlate_create_post→posts_createto Zernio's native MCP tool names; dropped thefiveagents_api_keyparam from those calls (Zernio is now OAuth-connected, not gateway-routed); renamed${BRAND}_LATE_*env vars to${BRAND}_ZERNIO_*and internallate_*PublishLog fields tozernio_*.
v2.14.0 — May 29, 2026
- New skill. Phase 4–5 of the YouTube-First content pipeline. Downloads the week's YouTube video via yt-dlp, reads the Clip Release Schedule from the Notion social calendar, extracts clips via ffmpeg, writes platform-native captions, and publishes to connected platforms via Zernio at the times specified in the schedule.
SKILL.md — Video Repurposer
Before Executing
Read agents/link.md and brands/{brand}/brand.md before starting. Load credentials from .claude/settings.local.json.
Role
Take the week's published YouTube video, extract clips per the Clip Release Schedule in the social calendar, write platform-native captions, and publish each clip to the correct platform at the scheduled time.
Step 0 — Load credentials + validate
import os, json
from pathlib import Path
def load_credentials():
search = Path(os.getcwd())
for p in [search] + list(search.parents):
settings_file = p / ".claude" / "settings.local.json"
if settings_file.exists():
data = json.loads(settings_file.read_text())
for k, v in data.get("env", {}).items():
if not os.environ.get(k):
os.environ[k] = v
return True
return False
load_credentials()
Get the YouTube video URL from the user or from this week's social calendar page in ${BRAND}_NOTION_DB.
Step 1 — Read the Clip Release Schedule from Notion
Fetch this week's social calendar page from ${BRAND}_NOTION_DB (search for SocialCalendar_[DDMon]-[DDMonYYYY]). Read the Clip Release Schedule table:
- Clip #, Platform, Publish Day, Publish Time, Duration, Moment to Clip, Caption Angle, Hook Archetype, CTA
Only process clips for platforms where the matching env var is set (${BRAND}_ZERNIO_LI, ${BRAND}_ZERNIO_IG, ${BRAND}_ZERNIO_TT, ${BRAND}_ZERNIO_TW, ${BRAND}_ZERNIO_FB). Skip platforms with missing env vars and log the gap.
Step 2 — Download video
python3 -m pip install yt-dlp --break-system-packages -q
python3 -m yt_dlp -o "tmp/{brand}/source_video.%(ext)s" "{youtube_url}"
Step 3 — Extract clips
For each clip in the schedule:
pip install ffmpeg-python --break-system-packages -q
ffmpeg -i tmp/{brand}/source_video.mp4 -ss {start_time} -to {end_time} -c copy tmp/{brand}/clip_{n}_{platform}.mp4
If exact timestamps are not in the schedule, use the Moment to Clip description to identify the segment. Use Whisper transcription if needed:
python3 -m pip install openai-whisper --break-system-packages -q
python3 -m whisper tmp/{brand}/source_video.mp4 --output_dir tmp/{brand}/ --output_format srt
Match the described moment against the transcript to find the timestamp.
Step 4 — Write captions
For each clip, the caption is already written in the Notion calendar Captions section. Read it verbatim. If absent, write a new caption using:
- Voice and tone from
brand.md - Caption angle and hook archetype from the Clip Release Schedule
- Comment-to-DM CTA from the schedule
- Platform-native format (length, hashtag count per platform guidelines)
Step 5 — Publish via Zernio
The clip is a local video file, so upload it programmatically via media_get_media_presigned_url — not media_generate_upload_link (that returns a browser upload-page URL for a human; PUTting to it silently fails). Resolve account IDs via accounts_list at run start.
For each clip:
media_get_media_presigned_url(filename, content_type:"video/mp4", size) → returns an upload URL (PUT target) + a public URL- Upload the clip via
requests.putto that upload URL validate_media(url=public_url)→ confirmvalid: trueposts_create(one call per platform):content(caption),platform(single string),account_id,media_urls(the public URL), and eitherpublish_now: trueorschedule_minutes(integer minutes from now, computed via Pythondatetime)
# Platform account ID mapping (verify against accounts_list at run start)
platform_ids = {
"LinkedIn": os.environ.get(f"{brand_upper}_ZERNIO_LI"),
"Instagram": os.environ.get(f"{brand_upper}_ZERNIO_IG"),
"TikTok": os.environ.get(f"{brand_upper}_ZERNIO_TT"),
"Twitter/X": os.environ.get(f"{brand_upper}_ZERNIO_TW"),
"Facebook": os.environ.get(f"{brand_upper}_ZERNIO_FB"),
}
Schedule each clip at the Publish Time from the Clip Release Schedule: convert the target time to UTC, then compute schedule_minutes as integer minutes from now via Python datetime. posts_create takes one platform + account_id per call (no platforms[] array) and has no platformSpecificData param.
Step 6 — Update Notion + PublishLog
- Update each clip row in the Notion calendar page:
Status = Published, add post URL - Append to
outputs/{brand}/published/PublishLog_[DDMonYYYY].md— use the exact same columns associal-publishersocontent-performance-analystcan join onZernio ID:
| Date | Platform | Topic | Zernio ID | Post URL | Status | Published At |
|---|---|---|---|---|---|---|
| {date} | {platform} | {clip moment} | {zernio_id} | {url} | Published | {publish_time} |
Step 7 — Slack notification
Load slack_send_message via ToolSearch before calling. DM $SLACK_NOTIFY_USER:
🎬 [{brand}] Video repurposed — {YouTube title}
• {N} clips published/scheduled
• {one line per connected platform: Platform: {publish_time}}
• Notion calendar updated
Step 8 — Log to dashboard
fiveagents_log_run:
- skill: "video-repurposer"
- brand: {brand}
- status: success | partial | failed
- summary: "{N} clips published to {platforms} from: {YouTube title}"
- metrics: {
"youtube_url": "{url}",
"clips_published": N,
"platforms": ["LinkedIn", "Instagram", ...],
"publish_times": { "LinkedIn": "...", ... },
"skipped_platforms": [] // any planned platform whose _ZERNIO_ env var is unset
}
Quality Checklist
- Credentials loaded
- Clip Release Schedule read from Notion calendar
- Only connected platforms published (missing env vars skipped + logged)
- Each clip extracted at the correct moment (timestamp or transcript match)
- Captions read from Notion or written platform-natively with comment-to-DM CTAs
- Clips scheduled at correct Publish Time from the schedule
- Notion clip rows updated to Published with post URL
- PublishLog written
- Slack notification sent
- Dashboard logged
Part of the pipeline
Phase 4–5 of the YouTube-First content loop. Runs after the YouTube video is uploaded.
social-calendar (Clip Release Schedule) → video-repurposer (download → extract → publish) → PublishLog (feeds content-performance-analyst Phase 1)