Tweet Skill
Help the user draft, refine, and finalize tweets with compelling visuals. Automatically selects the right visual format based on content.
Gotchas
- Character count is tricky -- URLs always count as 23 chars (t.co wrapping). Emojis count as 2. Newlines count as 1. Don't trust naive
lengthchecks. - Twitter crops images at 16:9 in timeline -- all templates output 1600x900 for this reason. Non-16:9 images get cropped unpredictably.
- Don't over-polish -- tweets that sound too perfect feel like marketing. Preserve the user's natural voice, just fix obvious issues.
- Thread vs single tweet -- if the user hasn't asked for a thread, don't suggest one. Single tweets perform better unless there's genuinely too much for 280 chars.
- Screenshot dependencies --
sharpand@resvg/resvg-jsneed to be installed. If they're not global, install to a temp dir, don't pollute the project. - Hot takes can backfire -- always flag the hot take variation as risky. The user should consciously opt in, not accidentally post something inflammatory.
- Always suggest a visual -- text-only dev tweets get buried by the algorithm. Every finalized tweet should have a visual recommendation, even if the user didn't ask for one.
- Strip AI tells -- before finalizing, run copy through
/humanize-writing. No em-dashes, no "it's not X, it's Y", name specific tools/numbers over vague claims. AI-sounding tweets read as marketing and get ignored.
Data Storage
Track tweet history for voice consistency:
# After finalizing a tweet, log it
echo "$(date -I) | VISUAL_TYPE | FINAL_TWEET_TEXT" >> "${CLAUDE_PLUGIN_DATA}/tweet-history.log"
On subsequent runs, read recent history to maintain consistent voice and avoid repeating hooks/angles.
Input
$ARGUMENTS may contain:
- Raw tweet text to refine
- A file path to a screenshot to wrap
- A code snippet to visualize
- Release notes or changelog to turn into a launch tweet
- Nothing (interactive mode)
Step 1: Analyze Input & Select Visual Strategy
Determine what the user provided and select the appropriate visual:
| Input type | Visual template | When to use |
|---|---|---|
| Screenshot/image path | wrap-screenshot.mjs |
User has an existing screenshot to polish |
| Code snippet or mentions code | code-card.mjs |
Showing a specific API, config, or code example |
| Release/launch with metrics | stat-card.mjs |
GitHub stars, downloads, benchmarks, version milestones |
| General text, no obvious visual | Suggest one | Ask: "What's the most visual part of this?" |
If the input clearly maps to one visual, proceed directly. If more than one fits, briefly suggest the best fit and why.
Theme Selection
All templates share 6 color themes. Pick based on context:
| Theme | Vibe | Best for |
|---|---|---|
midnight |
Deep blue, electric accents | General dev content, default choice |
ocean |
Teal/cyan, calm | Nuxt/Vue ecosystem (matches brand colors) |
sunset |
Warm orange/purple | Creative tools, design content |
forest |
Green, natural | Performance, sustainability, growth metrics |
lavender |
Purple, elegant | TypeScript, DX tooling |
ember |
Red, intense | Breaking changes, critical fixes, hot takes |
Default to ocean for Nuxt ecosystem content, midnight for everything else.
Chrome Selection (wrap-screenshot only)
| Chrome | Look | Best for |
|---|---|---|
terminal |
macOS terminal with traffic lights + monospace title | CLI tools, terminal output |
browser |
Browser-style with URL bar | Web apps, UI screenshots |
minimal |
Thin top bar, no buttons | Clean look, design screenshots |
none |
Just the image on background | When the content speaks for itself |
Step 2: Tweet Text Iteration
Initial Review
Read the draft and identify:
- Character count (280 limit, flag if over)
- Weak opening (first 5 words matter most in timeline)
- Missing context that makes the tweet confusing to outsiders
- Corporate speak ("excited to announce", "thrilled to share")
- Starting with "I" (reframe to lead with value)
Generate Variations
Produce 3 variations plus the fixed original:
- Fixed original -- same voice/intent, fix obvious issues
- Value-first -- reframe around what the reader gets. "Free tool that does X" over "I built X"
- Show-don't-tell -- lead with the visual/demo. "Watch:" or "Before/after:" framing
- Provocative -- stronger opinion or contrarian angle (flag as higher risk)
For each variation show:
- The tweet text
- Character count
- What changed and why
- Which visual template + theme pairs best with this angle
Rules for Good Tweets
- No hashtags unless the user explicitly includes them
- No emojis unless the user's draft already uses them
- Match the user's voice
- If referencing code/tech, be specific not vague
- Front-load the hook in the first line
Iterate
After showing variations, ask which direction they prefer or if they want to mix elements. Keep iterating until they're happy.
Step 3: Generate Visual
Based on the selected template, create a temporary script in scratchpad.
3a. Screenshot Wrapping (wrap-screenshot.mjs)
Use the Read tool to view the screenshot first. Then customize:
INPUT_PATH-- the screenshot file pathOUTPUT_PATH-- same directory, append-twitterbefore extensionTITLE-- contextual title based on content:- Terminal:
claude — ~/projector the command shown - Browser: URL or page title
- Generic: filename or brief description
- Terminal:
THEME-- selected themeCHROME-- selected chrome style
3b. Code Card (code-card.mjs)
Extract the most interesting code snippet (max ~20 lines, the card clips overflow). Customize:
CODE-- the code to display (clean it up: remove irrelevant imports, focus on the "aha" moment)LANGUAGE-- for the title bar labelTITLE-- optional override (e.g., "nuxt.config.ts" or "Usage")THEME-- selected themeOUTPUT_PATH-- descriptive name
Tips for good code cards:
- Show the simplest usage, not the full API
- 5-15 lines is ideal; under 5 feels empty, over 20 gets cramped
- Include a comment that explains what's happening if the code isn't self-evident
- Remove type annotations if they add noise without clarity
3c. Stat Card (stat-card.mjs)
For milestones, releases with impressive numbers, benchmarks. Customize:
HEADLINE-- project name + version or milestone labelSUBHEADLINE-- one-line descriptionSTATS-- array of{ label, value }objects (2-4 stats, 3 is ideal)THEME-- selected themeOUTPUT_PATH-- descriptive name
Tips for good stat cards:
- Round numbers up ("1,200+" not "1,187")
- Use human-readable units ("45k" not "44,892")
- Lead with the most impressive stat
- Pair with a tweet that gives context the card doesn't
3d. Run the Script
node /path/to/scratchpad/template.mjs
Dependencies (sharp, @resvg/resvg-js) should be available globally or in the project. If not, install to scratchpad:
cd /path/to/scratchpad && pnpm init && pnpm add sharp @resvg/resvg-js
3e. Verify Output
Read the output image to verify it looks correct. Show the user the result. If something looks off (text overflow, bad contrast, wrong theme), fix and re-run.
Step 4: Final Output
Present:
- The final tweet text with character count
- The generated visual (show the image)
- Suggested accounts to tag (max 2, relevant to content)
- Suggested posting time (if relevant)
Log to ${CLAUDE_PLUGIN_DATA}/tweet-history.log.
Offer to:
- Make further adjustments to text or visual
- Generate an alternative visual style
- Try a different theme
- Create a follow-up tweet for the next day