LinkedIn Ghostwriter
Draft LinkedIn posts that sound like the user, then publish to their own profile via LinkedIn's official API — only after they approve the draft. Never auto-publish.
The repo root is the directory containing this skill's scripts/, voice/, and drafts/
folders. All commands below are run from that repo root.
Personal data lives in ~/.claude/ghostwriter/, not the repo. The voice profile
(voice/voice-profile.md, voice-notes.md, interests.md), the brand guide
(assets/diagram.css), and LinkedIn credentials (.env) are read from
~/.claude/ghostwriter/{voice,assets,.env} — the same location whether the skill is running
from this repo, an installed Claude Code plugin, or Claude Desktop, so editing your voice or
brand once is visible everywhere. voice/algorithm.md (LinkedIn reach tuning) stays bundled in
the repo — it's shipped, identical content, not personal. data/, drafts/, images/,
scripts/ also stay repo-local since they're tied to running the actual publish flow from one
place.
Decide which mode you're in
- Setup —
~/.claude/ghostwriter/.envhas noLINKEDIN_ACCESS_TOKEN, or~/.claude/ghostwriter/voice/voice-profile.mdis missing, or the user says "set up", "configure", "connect my LinkedIn". → Run Setup. - Generate — the user wants a post (the common case). → Run Generate.
- Publish — the user approves a draft you already showed. → Run Publish.
Before generating, quietly confirm setup is done: ~/.claude/ghostwriter/voice/voice-profile.md
exists and ~/.claude/ghostwriter/.env contains LINKEDIN_ACCESS_TOKEN + LINKEDIN_PERSON_URN.
If not, switch to Setup.
Keep this invisible. Do the setup check (and any other bookkeeping — idea-board/radar
freshness, directory orientation) in as few, terse tool calls as possible: one chained
existence/content check, not a parade of separate Bash calls with printed section headers.
Skip exploratory commands that don't feed an immediate decision (a bare pwd, an ls "just to
look around"). The first thing the user should see is your one-sentence status line, not a
scroll of raw command output. This doesn't apply to the real research in Generate step 2 (the
HN check, radar read, recent_projects.py) — that work produces content the user actually sees
reflected in the menu.
Mode: Setup
Walk the user through this once. Do the steps you can; hand them the steps only they can do.
- LinkedIn app. Ask them to create an app at https://www.linkedin.com/developers/apps,
add the Share on LinkedIn and Sign In with LinkedIn using OpenID Connect products,
and under Auth add the redirect URL
http://localhost:8765/callback. They give you the Client ID and Client Secret. - .env. Run
mkdir -p ~/.claude/ghostwriter && cp .env.example ~/.claude/ghostwriter/.env, then write their Client ID/Secret into~/.claude/ghostwriter/.env(edit the file; never echo the secret back in chat). - Authorize. Tell them to run
python3 scripts/linkedin_auth.pythemselves (it opens a browser for them to click "Allow"). It writes the token + person URN into~/.claude/ghostwriter/.env. - Export posts. Tell them to request their data from LinkedIn (Settings → Data privacy →
Get a copy of your data → Posts), and drop the resulting
Shares.csvintodata/. The email takes ~10 minutes. - Extract. Once
data/Shares.csvexists, runpython3 scripts/extract_posts.py. - Build the voice profile. Do the Voice Profile step below.
- Interests & voice notes. If they don't exist yet (e.g. a fresh clone), seed them from
the templates:
mkdir -p ~/.claude/ghostwriter/voice && cp voice/interests.example.md ~/.claude/ghostwriter/voice/interests.mdandcp voice/voice-notes.example.md ~/.claude/ghostwriter/voice/voice-notes.md. Then help them fill in~/.claude/ghostwriter/voice/interests.md(interview them if it's empty).voice-notes.mdships with sensible defaults; append the user's own feedback to it as it comes up.
If the user has no usable export (few/no past posts), skip 4–5 and build voice-profile.md
by interviewing them: ask about tone, the 3–5 topics they're known for, formatting habits
(emoji? hashtags? short lines?), and what they never want to sound like.
Voice Profile (the heart of "sounds like me")
Read data/my_posts.md in full, then write ~/.claude/ghostwriter/voice/voice-profile.md
(mkdir -p ~/.claude/ghostwriter/voice first if it doesn't exist yet) capturing:
- Voice & tone — e.g. direct, contrarian, warm, wry. Quote 2–3 lines that exemplify it.
- Sentence rhythm — short and punchy? long and layered? fragments for emphasis?
- Openers — how do their best posts hook in the first line? (question, bold claim, story, stat). List the patterns they actually use.
- Closers / CTAs — do they end with a question, a one-liner, a call to engage, nothing?
- Structure — line breaks between every sentence? lists? the "1 idea per line" style?
- Vocabulary & tics — recurring phrases, signature words, how they swear or don't.
- Emoji & hashtags — none / sparing / heavy; which ones; where.
- Topics they own — the themes they return to.
- Never do — anti-patterns to avoid (corporate buzzwords, em-dash overuse, "I'm humbled to announce", fake vulnerability, generic AI-slop phrasing). Be specific to this person.
Keep it concrete and example-driven — it's a generation guide, not an essay.
Mode: Generate
Posture: propose, don't interrogate. The default is you surface concrete, already-real ideas and the user taps one — not a blank "what do you want to post about?" The picked idea is the post's real anchor, so there's no generic interview.
Outcome check-in (max one, fast — the feedback loop). Before anything else, read
~/.claude/ghostwriter/published.jsonl (written automatically on every publish). If the newest
record is ≥2 days old and has no outcome, ask ONE check-in question — "How did
'' do?" with options great / normal / flopped (notes via "Other") — then record it:
python3 scripts/post_outcome.py --latest --outcome <answer> --notes "<notes>". One dialog to
start: if the idea menu (step 2) is also due, the check-in and the menu ride in the SAME single
AskUserQuestion call — the check-in takes the first question slot and the flat idea question
(step 2) takes the second — still one dialog, one round trip, never two sequential question
dialogs to get a session moving. Only when no menu is due (the topic came in concrete)
may the check-in be its own question. Never ask more
than once per session; nothing to score → skip silently, don't mention it. Use the accumulated
outcomes everywhere you choose: lean the idea menu toward lanes that scored great and away
from repeated flopped, and let format outcomes steer the visual-form recommendation (step 8).
Say why when it's relevant ("your last carousel did great"). This is the only compliant
performance signal we have (no scraping — COMPLIANCE.md), so actually use it.
Short-circuit if the topic is already concrete. If the user named a specific topic, pointed you at a source, or said "draft a post from item N in the radar," skip the menu and go straight to grounding + drafting (step 3). The menu below is the default only for an open-ended "write me a post."
No topic given → ONE flat idea question, pick and go. Gather concrete, ready-to-write ideas from the four lanes below yourself, then flatten them into a single ranked list (lane priority order below, bent by outcome history) and present the top 3 as ONE single-select
AskUserQuestion— options are the 3 ideas plus a 4th, "Show more ideas." Never go back to asking one question per lane: that forced paging past unrelated cards even after the user had already picked, which is exactly backwards. Rules of the question:- Every idea option carries a
preview(≤ ~9 lines so the pane never clips): the working hook (the post's first ~2 lines as they'd actually read), the suggested angle in one sentence, and a source-freshness line prefixed with its lane (e.g.Trending · HN 612 pts / 340 comments · Jul 18,Radar · Jul 17 · anthropic.com). A user should be able to pick on the preview alone. - Picking a real idea goes straight to grounding + draft (step 3) — nothing else to answer or dismiss. The auto "Other" on the question takes a typed topic directly (same short-circuit as step 1).
- Picking "Show more ideas" asks exactly ONE follow-up single-select question with the next batch (the remaining candidates, up to 3 + auto "Other"), same preview format. This is the only path that costs a second round trip, and only because the user explicitly asked.
- One provenance line total in chat, not per lane (radar date + job health, live-search date, repo names) — don't dump a duplicate board into chat; the question options carry the ideas.
- When the outcome check-in is due it rides as the first question in the SAME call (see above); the flat idea question is the second. Still one dialog, one round trip.
The four lanes, in priority order (used to rank the flattened list, not to structure separate questions):
- Trending now (live, run-day — VERIFIED trending, not vibes). "Trending" means you can
point at the surge, not that a web search returned articles; vendor blogs and SEO listicles
are not trending signals. Check measurable surfaces directly, TODAY: Hacker News via the
Algolia API (top stories from the last
3 days, e.g./.claude/ghostwriter/voice/interests.mdcurl 'https://hn.algolia.com/api/v1/search?tags=story&numericFilters=points>150,created_at_i>'"$(date -v-3d +%s)"), top posts this week in the relevant subreddits, and news coverage from the last ~48 h (search with explicit recency). Filter through the trending areas in `, propose **2–3 topics**, each with the specific angle the user could own (a trending topic without their angle is just news), and put the ACTUAL signal in the preview's source line — points, comments, story volume, date (trending · HN 612 pts / 340 comments · Jul 18`). No citable signal → the item doesn't go in the lane; fewer real trending items beat padded ones. - Release radar — current through TODAY, not through the last digest. Read the newest
research/release-radar-*.mdand the tail ofresearch/.radar.log, and state provenance in the board ("Jul 17 radar, job ran clean"). If the digest is older than today, top the lane up: one quick live search for AI releases since the digest date, so the lane is current through the day the user actually runs ghostwriter — label digest itemsradar · <date>and top-upslive · today. Reuse digest items' title + "suggested angle" (already how-to-shaped and source-backed; the twice-weeklyscripts/release_radar.shjob scans the broader AI industry, not just Anthropic). Never add experience claims the digest didn't establish; the digest's Discussion radar items feed opinion/hot-take slots the same way. Skip items already published (checkpublished.jsonl). Radar stale (>4 days) or missing → say so, note whether the log shows the job failing, and run the lane fully live; if the job is broken (e.g. exit 127 — usually the repo moved), offer to repair it:bash scripts/install_radar.shre-renders the launchd agent against the repo's current path. - Interests & hot takes (1–3 entries). Read
~/.claude/ghostwriter/voice/interests.md— core themes, the "Strong opinions" list, and the story bank — for specific angles not covered recently (checkpublished.jsonland recent drafts). A strong uncovered story-bank item beats a generic theme; label eachinterests · <theme or story>. - Your recent Claude projects (2–3 entries). Run
python3 scripts/recent_projects.pyand take the top 2–3 repos with recent Claude Code sessions; for each, read the recentgit log- last session summary for the one real thing shipped (that's the anchor). Respect
~/.claude/ghostwriter/voice/interests.md→ Off-limits: never surface or post anything work-confidential (e.g. GoodLeap internals); personal/OSS repos only.
- last session summary for the one real thing shipped (that's the anchor). Respect
Build the list fast and honestly. Gather all four lanes in parallel (the HN check, the radar read + top-up, interests,
recent_projects.py) so the question is the first thing the user waits on. An idea appears in exactly ONE lane — highest-signal lane wins (a release that's surging on HN is Trending, not Radar). Filter every candidate againstpublished.jsonland recentdrafts/so nothing already covered resurfaces. Rank the flattened list by lane priority and the outcome history, and say so in the provenance line when it bends the order ("release how-tos lead; your last two ran great").Persist the full list — research the user paid for doesn't evaporate. Whether or not it was shown, write
research/idea-board-YYYY-MM-DD.md: every idea gathered (not just the 3 surfaced) with its lane, signal, angle, and status (picked/on deck). On the next open-ended run, read the newest board (≤7 days old) and fold still-good unpicked ideas back into the flattened ranking labeledon deck · <date>— re-verify a trending idea's signal before reusing it, and drop anything that went stale.After the pick: lock it in, zero extra dialogs. Echo a compact brief and go —
Locked in: <idea> · <lane>, then one line each for the angle, the real anchor, the save (the thing a reader keeps), and the sources you'll verify against. Then straight to grounding + draft (step 3); no second drill. A release-how-to pick follows the How-to posts playbook below; a topic typed via "Other" is the short-circuit path (step 1).- Every idea option carries a
Confirm the anchor, then draft. Every post still needs one concrete, real, first-person anchor — the actual tool, a real number, a specific decision, a thing that actually happened (see voice-notes.md → Substance bar + Authenticity). The menu pick normally is that anchor. Only the personal-project lane sometimes needs a single sharp follow-up to nail the specific detail — ask one
AskUserQuestion, never the old generic 2–3-question interview. Never fabricate a detail to clear this bar. If there's genuinely no real anchor, say so rather than shipping a generic post.Draft against the voice profile. Read
~/.claude/ghostwriter/voice/voice-notes.md,~/.claude/ghostwriter/voice/voice-profile.md, ANDvoice/algorithm.md(bundled, repo-relative) first, every time (voice-notes.md holds direct user feedback and takes priority; algorithm.md is reach optimization and must never override voice). If a voice file is missing — e.g. a fresh setup — copyvoice/voice-notes.example.mdto~/.claude/ghostwriter/voice/voice-notes.mdand proceed with what you have (~/.claude/ghostwriter/voice/interests.mdplus the defaults). Write the post to match them — their openers, rhythm, formatting, emoji/hashtag habits. Apply the Engagement craft rules below AND the reach rules invoice/algorithm.md(hook in the first ~210 chars, default 50–120 words, optimize for saves, no links in the body). Aim for one strong post, not three mediocre options. Never fabricate or exaggerate details that aren't true to the user's real experience — authenticity over drama (see voice-notes.md).Save the draft to
drafts/asYYYY-MM-DD-slug.md(ask the user for today's date if you don't have it; do not invent one).Research & fact-check — every external claim must be backed by ≥3 real, live sources (the post is generated from sources). Do this after Save (you need the slug) and before showing the draft. List every external/world claim the draft makes — a vendor shipped X, a research finding, a statistic, a definition; anything about the outside world, not the user's own first-person experience. For each, research it (WebSearch / firecrawl / WebFetch) and actually read the source to confirm it supports the claim — a live URL is not enough, the content has to back the statement. Prefer primary/authoritative sources (official docs, release notes, the vendor's own announcement, standards bodies, reputable engineering writing); skip SEO/hype blogs. Radar-lane posts: reuse the digest's source URLs. Then write a sidecar
drafts/YYYY-MM-DD-slug.sources.jsonpairing each claim to its URL(s) — every claim needs ≥1 source, and the post needs ≥3 distinct live source hosts overall — and runpython3 scripts/verify_sources.py --file drafts/YYYY-MM-DD-slug.mduntil it passes. The sources live only in the sidecar; never put sources, links, or a "Sources" section in the post body (in-body links also crush reach — seevoice/algorithm.md). If a claim can't reach ≥3 reputable sources, cut it or don't ship the post — never fabricate a citation or a fact.- Pure first-person posts (no external claims — e.g. a personal/vulnerable story) make no
outside-world assertion. Write a sidecar declaring
{"external_claims": false, "claims": []}; the gate passes trivially. The authenticity/substance bar in~/.claude/ghostwriter/voice/voice-notes.mdcovers these. Be honest: if the post mixes a real external claim into a personal story, it is notexternal_claims:false. - Narrate the gate — it's the slow step; never go silent through it. Emit one short status
line per claim as it resolves —
checking: "Sonnet 5 ships computer-use GA" → vendor announcement + docs ✓— and one close line when the gate passes:3 claims · 5 distinct hosts · gate passed. One line each, no tables; the user should see the research happening, not a minute of dead air followed by a draft. - Re-verify on edit. The show→edit→re-show loop below can add a claim after the sidecar was written. Whenever an edit adds or changes an external claim, re-run this step and update the sidecar before publishing.
- Pure first-person posts (no external claims — e.g. a personal/vulnerable story) make no
outside-world assertion. Write a sidecar declaring
Pre-show self-check, then show the draft. Before the user sees it, verify against
~/.claude/ghostwriter/voice/voice-notes.md, hardest first:- The ending — the #1 AI tell, flagged more than anything else. The post stops on the last real point. No inverted-parallel closer, no clever-symmetry aphorism, no reflexive "what's your…?" CTA.
- Nothing fabricated — no invented details, motivations, or timeline drama the user didn't actually live.
- Length — default 50–120 words (see Engagement craft).
- No banned tics — em dashes, rule-of-three fragments, credential flexing, hedge words.
- The hook — the post's single most specific number or sharpest tension appears in the first ~210 chars (before "…see more"). If the best number sits below the fold, move it up.
- The save — name (to yourself) the thing a reader keeps: a command, a checklist, a reusable model. If there's nothing to keep, either rework toward reference-worthy or accept it's a lower-reach personal post on purpose — don't pad it with fake utility. Fix what fails, then show the full draft in the LinkedIn-true format:
- The draft text in a fenced block, with a visible fold line —
┄┄┄ …see more (fold ~210 chars) ┄┄┄— inserted at the line break nearest char 210, so the user sees exactly what shows above the fold. (A draft that ends before the fold needs no marker.) - One metadata line under the block:
N words · save: <the thing a reader keeps> · lane: <lane>. - Re-shows lead with the delta: after any edit, the first line is
Changed: <one-line summary>, then the full draft in the same format — the user should never re-read the whole post hunting for the edit. Then ask with a singleAskUserQuestion— options Publish / Edit (the auto "Other" takes typed edit instructions directly) / Scrap — and wait for the answer. The Publish tap immediately after seeing the exact full text is the explicit approval; an edited draft is re-shown and re-asked the same way. Do not publish unprompted. Any voice/style feedback the user gives — append it to~/.claude/ghostwriter/voice/voice-notes.mdin the same turn, BEFORE redrafting, and say you did ("added to voice notes"). Fixing only the draft loses the correction and the user has to repeat it next session.
Settle the visual with ONE question — build nothing first. After the text is approved, ask a single
AskUserQuestion: text-only / single card (name the Press hero component you'd compose around, e.g. "a duel" or "a ledger") / carousel — with your recommendation first, chosen from the post's shape and the outcome history: how-to / educational → carousel (highest-reach native format, seevoice/algorithm.md) or a composed Press card; one punchy idea → card; personal story → text-only. A strong text post beats a weak image, so text-only is always a respectable pick. Give every option an ASCIIpreviewsketch of what THIS post would get: the card option sketches the actual proposed Press composition as labeled blocks (masthead / hero / colophon, with this post's real headline and hero named, e.g.[ DUEL: cron vs launchd ]); the carousel option sketches the slide strip (cover → 5 steps → recap → CTA, using this post's slide titles); text-only previews the draft's first ~2 lines above the fold marker. Sketches are text in the question, not builds — authoring still waits for the pick. Only after the pick do you author and render (see Visuals); never render a form the user didn't choose. Cards are composed, not templated: readassets/card-language.md, checkimages/card-history.jsonl, and differ from the last 3 cards on ≥2 variation axes. If the post is about the user's own agent, CLI, or code — any visual that would show its output (a heroterm,code, orclaudecard) — settle the output source in the SAME single question, via the option descriptions: you capture it live (run their CLI / call their MCP tool from this session), they paste or screenshot a real session, or — only if neither is possible — compose from facts already in the draft. One question total, never a second round-trip. See Real-output cards below for what to do with the capture.
How-to posts (technical, from AI releases)
The priority lane, and the one radar items feed directly. When the anchor is a recent AI release, write a genuine how-to — not a news recap.
- Structure: implication → steps → gotcha → outcome. Lead with what the reader can now do (the implication), not "X shipped." Then the concrete steps they'd take, the one real gotcha, and the outcome. Prescriptive, for the reader (voice-notes → Framing & audience).
- Real technical meat, accessible entry. Use real commands, real config, real names — the
"accessible-but-substantive" bar in
~/.claude/ghostwriter/voice/voice-notes.md: a curious non-expert can follow the entry, an engineer still learns the mechanism. This is what earns saves (algorithm.md's #1 lever). - Authenticity — how-to ≠ "I did this." A release how-to makes external/world claims, so it is
exactly the case the source gate is for: the
*.sources.jsonsidecar +verify_sources.pystep (step 6) is mandatory. Never fabricate or imply the user personally ran a release they haven't — write the steps generically ("map which jobs call X"), not as a first-person story. - Default visual: a composed Press card (step 8) — a single high-quality image, usually
built around a ledger (numbered steps + the real command in a
.cmdbar) or tiles (exactly 4 compact steps). Compose it fresh perassets/card-language.mdand vary the composition againstimages/card-history.jsonlso how-to posts never look the same twice in a row.
Visuals (optional — diagrams & cards)
Only when the user opts in. Requires the diagram dependency (see README; if render_image.py
reports Playwright/Chromium is missing, point them at the install step and stop).
Brand guide (per-user). Styling + byline live in ~/.claude/ghostwriter/assets/diagram.css —
the user's personal brand guide, shared across every install of the skill. On first use, if it
doesn't exist, copy it from the template: mkdir -p ~/.claude/ghostwriter/assets && cp assets/diagram.css.example ~/.claude/ghostwriter/assets/diagram.css, then set their --byline
(shown at the bottom of every visual), their Press identity (--press-sig signature color +
--stamp monogram initials), and tweak the palette. Cards use
<div class="footer brand"></div> to pull the byline automatically — don't hardcode it.
The Press system (THE brand — default for every card). Editorial-poster identity: warm paper canvas, huge black type, serif standfirst, ONE loud signature accent, heavy ink rules, giant numerals, an issue-numbered masthead with the personal monogram stamp. Cards are portrait 4:5 (1200×1500) and composed, not templated: read
assets/card-language.md(the component vocabulary, composition rules, and variation axes), pick the 2–3 body components that prove the post's point (a duel proves a decision, a ledger proves a method, a big stat proves a claim, a terminal proves it's real), and author a bespokeimages/<slug>.html.assets/card-template-press.htmlis one example composition (the how-to ledger shape), not the shape. Anti-sameness contract: before authoring, readimages/card-history.jsonland differ from the last 3 approved cards on ≥2 variation axes (hero component, headline treatment, density, numeral presence, support texture); after the user approves the render, append the card's fingerprint line to that file.Real-output cards (the fidelity contract). Whenever a card shows the output of the user's own agent, tool, or code — a hero
termcomponent, acodecard, aclaudesession card — the terminal content is a transcription of a real session, not an invention. A round of "make it look like my actual agent" is a defect: get the ground truth before authoring, not after the user complains.- Capture first. In preference order: run it yourself (the user's CLI or MCP tool
is often reachable from this session — call it and capture real output); else take the
user's paste or screenshot (offered in the step-8 question). Save the raw capture —
transcribing a screenshot faithfully if that's what you got — to
images/<slug>.source.txt(gitignored, stays local), and iterate every render against that file, not against memory of it. The card gets published: scrub secrets before transcribing — tokens, keys, emails, home-directory paths, private hostnames get redacted or generalized in the card even though the capture keeps them (same "never print secrets" guardrail). - Author as condensation, never invention. Keep the session's anatomy — the prompt
row, the tool-call indicator line, the real table with its actual metric names, values,
baselines, and deltas, the verdict, the closing directive (see
assets/card-language.md→ The hero terminal). Cut whole rows or sections to fit the budget; never smooth real output into summary prose, and never "clean up" the texture that makes it real. - Unknown value →
—or one question. Real CLIs print dashes for missing data; do the same. If one real number would complete the card (a baseline, a total), ask for that ONE number — never invent it, especially health or personal metrics. - Feed the post too. Pull the capture's 1–2 strongest real numbers into the draft body (re-running the source gate if that adds an external claim) — real specifics are what get posts saved and shared.
- Mirror check when a reference exists. If the user supplied a screenshot or paste, then before EVERY showing: Read the reference and the render side by side and enumerate the structural mismatches yourself — missing prompt/tool-call lines, missing table columns or rows, invented phrasing, dead whitespace where the real session is dense. Fix and re-render until you find none; only then show the user. The user saying "closer" is the failure mode, not the workflow.
- Capture first. In preference order: run it yourself (the user's CLI or MCP tool
is often reachable from this session — call it and capture real output); else take the
user's paste or screenshot (offered in the step-8 question). Save the raw capture —
transcribing a screenshot faithfully if that's what you got — to
The legacy light gallery (reference compositions). The pre-Press light-system templates below remain shipped and renderable — use them as structural references when a Press composition wants a proven skeleton, or when the user explicitly asks for the light look. Two rules still apply when one is used:
- The topic graphic is the hero (~3/4); any type-motif is a small accent. Don't let decoration (e.g. the STEM blocks) dominate — the real diagram of THIS post carries the card.
- Icons must fit the post. The
<svg>icons in every template are EXAMPLES, flagged with anICONS: …comment. Pick topic-matching glyphs fromassets/card-icons.mdand swap them in for each card — never ship a template's default icons or placeholder strings; delete theICONS:comment once swapped (the render lint fails the card otherwise). Meaningful and few (2–4) beats many.
Pick the form — Press composition first; the gallery table below maps legacy shapes.
Post shape Template One-liner ANY (the default) — compose it pressbrand system; pick hero: ledger / duel / pull / bigstat / tiles / term / bars How-to — 3–5 steps (legacy) howtonumbered spine, icon + command chips How-to — 4 steps, compact howto-grid2×2 numbered tiles How-to — 4–5 quick steps howto-checksaveable green checklist How-to — 3–4 punchy steps howto-stackeditorial big-number rows Teaching / how-it-works briefheadline + before/after concept + thesis band Architecture / pipeline flowstage chips on a numbered spine Comparison matrixscorecard, winning cell per row Accelerating progression ramprising bars to a payoff figure Launch / deprecation / event dateADMIT-ONE ticket, the date is the hero Education / outreach stemsmall toy-block STEM accent over a real graphic Code snippet codedark terminal, hand-highlighted Claude Code session claudetranscript: request → actions → result Multi-slide step-by-step carouselPDF document (see Carousels) A Mermaid diagram (
--type mermaid, a.mmd) also works for structured/technical content; a designed card (--type card, an.html) is the default for one punchy idea. Card templates:assets/card-template-press.html— press (THE default): one example composition of the Press brand system. Don't fill it in — compose:assets/card-language.mddocuments every component (.ledger,.duel,.pull,.bigstat,.facts,.tiles,.term,.bars,.stand,.marginal), the composition rules, and the variation axes.- The how-to family (4 on-brand layouts — rotate them; never use the same how-to card twice
in a row). All share the light system (eyebrow + byline, headline,
.lead, optional.bandgotcha,.captionoutcome) and put the real command/flag in a monospace<code class="cmd">chip — the meat readers save. Pick by step count / rhythm (see the table above):assets/card-template-howto.html— howto (spine, the default):.steprows on an auto-numbered spine, each an icon chip + bold imperative.ttitle + a muted.edetail or a.cmdchip. Best 3–5 steps. Reach for it first for a release how-to.assets/card-template-howto-grid.html— howto-grid: a 2×2 tile grid (.gstep= a.gnumbadge +.gictopic icon +.gttitle +.ge/.cmd). Best with exactly 4 steps.assets/card-template-howto-check.html— howto-check: a saveable checklist on one panel (.check= a green check +.cttitle +.ce/.cmd; the check is the motif, no icon swap). Best 4–5 quick steps (6 only if every detail is one line).assets/card-template-howto-stack.html— howto-stack: an editorial big-number list (.sstep= a giant ghost numeral +.sttitle +.se/.cmd). Bold, magazine feel. Best 3–4.
assets/card-template-brief.html— brief type (the default explainer): the flagship — headline + lead, an explainer.panel(a before/after.concept), a dark thesis.band, and an icon.statrow. Reach for it first for teaching / how-it-works posts.assets/card-template-flow.html— flow type (architecture / pipeline): light stage chips threaded on a numbered spine, each with a topic icon + a bold title + one muted example (layer classes.detgreen /.toolsteal /.agentblue /.outgrey). Prefer over a Mermaid diagram for architecture posts. 3–5 stages; sub-steps inline asA -> B -> C.assets/card-template-matrix.html— matrix type (comparison): a premium scorecard — solid colour header pills (.col-h .green/.grey/.pink), every value in a contained tile (.vnumber /.vtphrase), the winning cell per row marked.bestfor an instant verdict;.switchrows group. Setcols2/cols4to match the option count (3 is the default); translate insider units into plain words.assets/card-template-ramp.html— ramp type (accelerating progression): a light analytics chart — neutral rising bars to an accent payoff bar, a trend line, a delta pill. Bars are illustrative; the labeled figures must be accurate.assets/card-template-date.html— date type (a launch / deprecation / event): a realistic ADMIT-ONE ticket as the centerpiece; the headline names the event, the date is the hero.assets/card-template-brochure.html— brochure (a Press composition): the product page for a shipped release of one of YOUR skills. Masthead → headline → standfirst →.facts(version, ship date, one proof figure) →.pullcarrying what the skill refuses to do in its own words → both install steps → colophon, with a slender vertical.platedown the left third holding an illustration composed for that release (ink only — the h1.sigis the card's one signature moment). Start from the scaffold, never by hand:python3 scripts/release_facts.py <skill> --scaffold images/<slug>.htmlwrites the card with every factual slot already filled from the released artifact — version, ship date, both install steps, the one rule quoted — and leaves the judgment slots markedTODO. Compose the plate, the headline, the standfirst and the proof figure; the render lint fails while the example plate (id="plate-example") survives, so a demo drawing cannot ship. Keep the refusal: a brochure that only lists features is an advert.assets/card-template-stem.html— STEM type (education / outreach): the warm one — a SMALL toy-block S·T·E·M accent over a real topic graphic (the build / experiment / result). Reach for it when the tone is kid-energy / inspirational.assets/card-template-code.html— code type (a snippet): a dark macOS terminal floating on the light canvas. Highlight by hand (<span class="t-kw/t-fn/t-str/t-num/t-com">), mark the money lineclass="line hot", cap with<span class="caret">. ≤42 chars, ≤10 rows.assets/card-template-claude.html— Claude Code session: the transcript variant of the code type (clay request band, action bullets,└result branches). Be honest — real request, real outcome; the Real-output cards contract applies (capture the actual session first).assets/card-template-carousel.html— carousel type (a multi-slide document). See Carousels below — the highest-reach native format, best for educational / step-by-step posts. Card styling lives in~/.claude/ghostwriter/assets/diagram.css(the brand guide) — use its classes, don't add one-off inline CSS. Let the user choose the form if unsure.
CONTENT BUDGET (hard limits — the same numbers live in every template header, and the render lint enforces the measurable ones):
Template Count Field limits Notes press2–3 body components eyebrow ≤24 · h1 ≤2 lines (~13/line; compact~20) ·.stand≤3 lines ·.lt≤38 ·.le≤60 ·.cmdbar≤44 one line ·.marginal≤2 lines ·.colophon .out≤52 ·.termaccent ≤10 rows×42 / hero ≤20 rows×56full budgets per component in assets/card-language.md; the lint fails misaligned.termtablesall light cards — eyebrow ≤24, one line · h1 ≤2 lines (~28/line) · caption ≤60 howto3–5 steps .t≤38 ·.e≤60 ·.cmd≤455 steps ⇒ one-line titles + one-line h1 howto-stack3–4 .st≤32 one line ·.se≤64 ·.cmd≤454 steps ⇒ ≤2 cmd chips total; 3 steps auto-scale howto-gridexactly 4 (3 auto-spans) .gt≤22/line, ≤2 lines ·.cmd≤30howto-check4–6 .ct≤34 one line ·.ce≤666 rows ⇒ one-line titles AND details flow(light)3–5 stages .t≤345 ⇒ h1 ≤2 lines, one-line titles matrix(light)2–4 options, ≤5 rows set cols2/cols4to match6–7 rows ⇒ class denseramp3 bars .val≤7 chars, dates ≤10units go in the kicker briefkeep all blocks h1 ≤2 · lead ≤3 lines · scol .cap1 linestem≤2 nodes + ≤3 scols when lead ≥3 lines code/claude≤10 rows ≤42 chars/line ask band + final caret line must fit date— date-sub ≤40 chars brochure(press)exactly 3 facts .fval≤11 beside the plate ·.pull .q≤3 lines · 2.cmdbar≤52 each ·.stand≤4 lines · plateviewBox="0 0 300 900"needs one .pull .q, one.plate svg, and BOTH install steps; facts come fromrelease_facts.py, never typedcarousel7–9 slides ≤30 words/slide --i/--nand pageno text must match countCount-adaptive layouts (stack/howto/check/flow at 3, grid at 3, matrix
cols2/cols4/dense) are automatic or one class — the budget table says which.Author the source into
images/<slug>.mmdorimages/<slug>.html. Keep it to one idea; never invent structure, numbers, or relationships that aren't true (same authenticity rule as~/.claude/ghostwriter/voice/voice-notes.md— a misleading diagram is worse than none). Card copy follows the voice rules too: the voice-notes bans (em dashes, hedge words, clever-symmetry lines) apply to every headline, lead, band, and caption, not just the post body.Render:
.venv/bin/python scripts/render_image.py --type <mermaid|card> --in images/<slug>.<ext> --out images/<slug>.png—--size 1200x1500is the default (a viewport hint; the screenshot crops to#canvas, and Mermaid auto-fits), so cards need no size flag. Pass--stricton the pre-publish render so any lint FAIL exits non-zero. Never pass--no-openin an interactive Generate session — the command auto-opens the PNG in the user's own image viewer by default, and that auto-open (not a chat-embe
…(truncated)