Skills · Workflow
social-copy
/social-copyFast, bulk social post COPY — a ready-to-paste description + a trending-hashtag set for a finished reel / image / carousel, shaped per platform (TikTok hook + 3-5 tags · Reels c…
SKILL.mdsocial-copy
The fast, batchable, on-voice social post copy layer — description + trending hashtags for a finished piece, shaped for TikTok / Reels / Shorts in one invocation. The skill DRAFTS; ralphy unit caption PERSISTS the copy into unit.json.
ALSO FIRE
- The user just rendered / formed a unit and asks "and now? what do I write under it?" (any language).
- The user drops a niche + "give me trending hashtags" with no caption ask — still this skill (hashtag bank + format shaping).
- A batch of reels just finished and the user wants copy for all of them → bulk mode.
DO NOT FIRE
- The user wants aligned-to-VO subtitles / SRT burned into the video — that is
ralphy generate captions(a different surface). This skill is the social POST copy. - The user wants the script / scenario itself — that is the scenarist playbook.
- The user wants to publish to the library — that is
templater/dev-publish-template; caption is an input that path can carry.
Hard invariants
- Target-audience language drives the copy, NOT the chat language. Same rule as intake: if the audience is German, the post is German even if the user is chatting in English / Russian. Offer EN + the audience language. Pass
--language <lang>to the verb. - Source-ground every copy block. Pull from the unit's
title/blurb/tags/ provenance, the project brief, and — when present — the reference reel'smeta.info.json/ transcript / on-screen text. Copy must describe the ACTUAL content, never a generic guess. - Match the niche's native voice. Meme / brainrot register for the aura format; professional for a brand ad. The niche is read from the matched #412 content-mode / the unit's tags / provenance, then carried into the draft.
- Hashtags come from the maintained bank, not invented. The per-niche trending-tag bank lives at
cli/lib/social/hashtag-bank.ts(niche spine + format tags + broad-reach, deduped + ordered). Trend tags rot — see the staleness note below. - Append-only on the persisted caption.
ralphy unit captionnever clobbers an existing caption: re-drafting requires--force, which archives the prior intocaption_versions. AGENTS.md invariant #14.
Platform shaping (one invocation, all three)
| Platform | Shape | Tags |
|---|---|---|
| TikTok | one punchy hook line (1 sentence, scroll-stopping) | 3-5 inline tags |
| Reels | a fuller caption (2-4 sentences, on-voice, soft CTA) | 10-15 tags |
| Shorts | a title, ≤ 40 chars | tags optional |
The drafter writes the three copy bodies; the hashtag bank supplies the merged tag set (the verb caps it ~18, niche-first → format → broad-reach).
The bulk-mode loop
For N units / a batch, do NOT hand-write each. Run once across all units:
ralphy unit caption <project> --bulk --language English
This drafts + writes one caption block per unit (skipping any that already have one unless --force). Single unit:
ralphy unit caption <project> <slug> --language English --niche aura
Useful flags: --niche <hint> (overrides the auto-detected register / tag spine), --brief "<on-screen text / source caption>" (extra grounding), --force (re-draft, archive prior).
Source grounding
Before drafting, gather:
- The unit's
unit.json—title,blurb,tags,provenance(style / template / recipes / assets). - The project brief /
STORYBOARD.md/PRODUCTION_PLAN.mdif present. - The reference reel's own caption + on-screen text (we store the source reel + its
meta.info.json/ transcript). The copy should echo the actual hook, not a generic line.
The verb folds the unit fields + --brief into the draft automatically; pass --brief when the strongest grounding (the source reel's caption / on-screen text) lives outside unit.json.
The trending-hashtag bank + staleness
cli/lib/social/hashtag-bank.ts maps niche → curated tag spine (aura, analog-horror, ps1-horror, tech-explainer, ugc-ad, unboxing, beauty, fitness, food, saas, general) plus format tags (reel / short / tiktok / carousel / …) and a stable broad-reach set (#fyp etc.).
Trend tags rot. The bank stamps LAST_REVIEWED; the verb's JSON output reports bank_stale: true once it is older than 60 days. When stale (or for a hot niche), refresh BEFORE shipping:
- light path — run the
researcherskill / a trend scrape on the niche + platform, read the current top tags, update the bucket inhashtag-bank.ts, bumpLAST_REVIEWED. - manual path — open the niche's top recent posts and copy the tags they rank under.
Broad-reach tags rarely rot; format + niche buckets drift on a ~monthly cadence.
Worked example (one reel → TikTok / Reels / Shorts block)
A finished aura-moment-001 unit (PS1-horror "aura" meme reel, format video, tags ["aura","ps1core","meme"]), audience English:
ralphy unit caption aura-proj aura-moment-001 --language English
Drafts (niche auto-resolves to aura) and writes into unit.json:
{
"caption": {
"platform": {
"tiktok": "POV: your aura hits 999,999 and the room goes PS1",
"reels": "When the aura is too strong the graphics downgrade to PS1. Caught it on camera before reality re-rendered. Watch till the end. Follow for more aura moments.",
"shorts": "Aura: 999,999 (PS1 mode)"
},
"hashtags": ["#auramoment","#aura","#aurapoints","#ps1core","#brainrot","#meme","#sigma","#edit","#reels","#reelsinstagram","#instareels","#fyp","#foryou","#foryoupage","#viral","#trending"],
"language": "English",
"niche": "aura"
}
}
To also offer the audience-language variant, re-run with --language <lang> --force (the EN block is archived into caption_versions).
CLI cookbook
- Draft + persist one:
ralphy unit caption <project> <slug> [--language <lang>] [--niche <hint>] [--brief "<text>"] - Bulk across the project:
ralphy unit caption <project> --bulk [--language <lang>] - Re-draft (append-only): add
--force(prior caption →caption_versions). - Read it back:
ralphy unit show <project> <slug>(thecaptionfield).
Never write copy straight into unit.json by hand — the verb owns the write (logs the LLM draft to generations.jsonl, merges the bank, enforces append-only).