x-writer
Writing format, image generation, and pipeline for X (Twitter) content. Paragraph-based medium form, Fischerian Hooks, anti-performance writing. Progressive disclosure: article format scoring, thread/article images, comic strip generation, full brainstorm→polish→schedule pipeline
Install
npx skills add https://github.com/travsteward/openwriter/tree/main/skills/x-writer
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install travsteward-openwriter@llmmart
git clone https://github.com/travsteward/openwriter.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole travsteward/openwriter collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
X Writer
The writing format for X content. This skill defines HOW to write — not WHAT to write about. Apply these patterns to all tweets, threads, quote tweets, and replies.
Core Philosophy
The line-by-line style is dead. Broken up, one-sentence-per-line writing signals EFFORT. It says "I am trying to get your attention." People see through it. They turn off. The pithy fragment style is the format of engagement farmers, not thinkers.
We write in paragraphs. Clean, flowing, natural paragraphs. Like a smart person talking to you, not a copywriter performing for you. The authority comes from the IDEAS, not the formatting tricks.
The Fischerian Hook
Named after Fischer King (@FischerKing64), who demonstrates the pattern naturally.
The first sentence of every paragraph is the hook. It's a strong, declarative statement that pulls the reader in. Not a question. Not a teaser. A position.
Examples from Fischer King:
"The worst part about people in white collar professions claiming they work '80-100 hours a week' is that they are all liars."
"Every living President is watching Trump and realizing he lost the opportunity to shape the future of the country."
"The most disturbing aspect of Gordon Ramsay's 'Kitchen Nightmares' is the revelation that so many restaurants are just microwaving your meal."
The hook is a complete thought that makes you want the next sentence. The paragraph then builds, explains, or lands the point. The hook isn't bait — it's the thesis.
What a Fischerian Hook is NOT:
- "Here's what nobody tells you about X..." (teaser bait)
- "I need to talk about something." (vague attention grab)
- "This." followed by a screenshot (lazy engagement)
- One-word sentences for "impact" (performance writing)
Format: Medium Form
Optimal length: 4-5 paragraphs. Range: 3-6. This is the sweet spot for extended thought without losing attention.
- 3 paragraphs — minimum for a real argument. Setup, development, landing.
- 4-5 paragraphs — optimal. Enough room to build a framework, give evidence, and close with impact.
- 6 paragraphs — maximum before you're writing an article. If you need more, make it a thread or an X Article.
Each paragraph is 2-4 sentences. Not one-liners. Not walls of text. Natural paragraph length that breathes.
For threads: each tweet is its own medium-form post (3-5 paragraphs). The thread isn't one long essay chopped up — it's a sequence of self-contained posts that build on each other.
Anti-Performance Rules
No single-sentence paragraphs for emphasis. If a sentence is important, it lives inside a paragraph where the surrounding context makes it hit harder.
No line breaks within paragraphs for dramatic effect. A paragraph flows. If you need a new thought, start a new paragraph.
No "Let me explain" or "Here's the thing" throat-clearing. Start with the point. The Fischerian Hook eliminates all preamble.
No emoji anchors. No starting lines with emoji to create visual structure. The writing IS the structure.
No rhetorical question chains. "What if I told you...? What if everything you knew was wrong?" — this is performance, not writing.
No trailing ellipsis for mystery. "And the answer might surprise you..." — say the answer.
Voice Characteristics
- Declarative, not questioning. State positions confidently. "This is how it works" not "Have you ever wondered how it works?"
- Specific, not vague. Names, numbers, studies, references. "Researchers butchered 30 deer with 60 handaxes" not "Studies show that..."
- Conversational but not casual. Like talking to a smart friend at dinner, not like texting. No slang, no "lol", no "ngl."
- Variable sentence length. Mix short declarative sentences with longer explanatory ones. The rhythm is natural speech, not metronome.
- Authority through knowledge, not tone. Don't TELL them you're an authority. Demonstrate it by knowing things they don't.
Thread Format
When writing threads, each tweet follows the same medium-form paragraph rules:
Tweet 1 (Hook tweet): 3-5 paragraphs. First paragraph's first sentence is the thread hook — the reason someone stops scrolling. Include the primary image if there is one.
Body tweets (2-N): Each is a self-contained medium-form post. Each has its own Fischerian Hook. Each could stand alone as a tweet, but builds on the thread's argument.
Close tweet: Lands the framework. Not a call to action ("Follow for more!"). Not a summary. The final insight that makes the whole thread click.
Thread images: Each body tweet can have a progression image or supporting visual. Images support the argument — they don't replace it.
Voice: Always Apply
Every piece of X content MUST be run through /authors-voice (or /voice-apply) before it's considered done. The x-writer skill handles format and structure. The voice skill handles tone, word choice, and eliminating AI tells.
Preferred path: OpenWriter's built-in Author's Voice "Enhance" plugin (API-backed, full corpus RAG) — beats local Apply-minion briefs for X rewrites every time.
AI tells to eliminate:
- "Furthermore", "moreover", "additionally" — conjunction stacking that no human uses in casual prose
- "It's worth noting that" — throat-clearing
- "This is particularly important because" — explaining why something matters instead of just stating it
- "In essence", "fundamentally", "at its core" — filler abstractions
- "Arguably", "undeniably", "unquestionably" — hedging or over-asserting
- "Landscape", "paradigm", "ecosystem", "leveraging" — corporate AI vocabulary
- "Delve", "tapestry", "nuanced" — the most flagged AI words on the internet
- Balanced "on one hand / on the other hand" structures — real people take positions
- Ending with an inspirational reframe or call to reflection — just end
The workflow (single pipeline — /x-writer does all three):
- Format: Draft content applying x-writer format rules (Fischerian hooks, paragraph structure, medium form)
- Polish: Pull from the top 10 advertising practitioners in history. Score each rewrite 0-100. Don't stop until you hit 90%.
- Voice: Run
/authors-voice(or/voice-apply) to rewrite in your authenticated voice + anti-AI detection - Final check: If any sentence sounds like it came from ChatGPT, rewrite it
Progressive Disclosure — Sub-Docs
Load on demand based on the task:
- OpenWriter mechanics —
tweetContext/articleContextmetadata,content_type(tweet/reply/quote/article), thread HRs, image handling, paragraph spacing, parent-tweet workflow →docs/openwriter-mechanics.md - Article format — evaluate (7-dimension score) or optimize X Articles. 10 rules for scroll-stop engagement →
docs/article-format.md - Images — generate covers + thread images. 5 styles (Dark Editorial, Cinematic Realism, Abstract/Conceptual, Raw/Documentary, Dark Infographic), decision tree, character references →
docs/images.md+docs/images/(styles, characters, workflow) - Comics — character-consistent comic strip panels for threads. 4 comic-specific styles, full pipeline →
docs/comics.md+docs/comics/(styles, characters, workflow) - Pipeline — full brainstorm → polish (Author's Voice) workflow; scheduling/posting hands off to OpenWriter native (
mcp__openwriter__schedule_post,post_to_x) →docs/pipeline.md
Scripts
scripts/polish.js— call Author's Voiceapply_voicefor tweet polishingscripts/generate-image.js— Gemini image generation with reference image supportscripts/characters/— character reference PNGs for consistent multi-panel/thread image sets (user-supplied)
What This Skill Does NOT Cover
- What to write about — topic strategy is outside this skill's scope
- Posting/scheduling — OpenWriter native:
mcp__openwriter__schedule_post,post_to_x,manage_schedule - Scoring/prediction — see grok-score skill
- Reading tweets — see x-reader skill ($0 fxtwitter reads)
- Direct X API access — see x-api skill (post-only per cost rule, when not going through OpenWriter)
Files (openwriter)
-
docs
-
comics
-
characters.md 2.8 KB
# Character Management ## Character Storage Characters are stored as PNG files in `~/.claude/skills/x-writer/scripts/characters/`. Naming convention: `{name}.png` — lowercase, hyphens for spaces. - `alex.png` - `jordan.png` - `narrator.png` ## Creating a Character ### From scratch (AI-generated) Generate a clear reference portrait: ```bash node ~/.claude/skills/x-writer/scripts/generate-image.js \ -p "Character portrait of [DETAILED DESCRIPTION]. Front-facing, neutral background, even studio lighting, clear facial features visible. High detail. No text, no watermarks, no logos." \ -o ~/.claude/skills/x-writer/scripts/characters/{name}.png \ -a "1:1" ``` **Best practices for character creation prompts:** - Specify: age range, build, hair (color, style, length), facial hair, skin tone - Specify: clothing (be specific — this becomes the "default outfit") - Specify: expression (neutral or characteristic) - Always: "front-facing, neutral background, even studio lighting" - The more specific the description, the more consistent the character stays across panels ### From an existing image Copy any clear portrait to the characters directory: ```bash cp /path/to/reference.png ~/.claude/skills/x-writer/scripts/characters/{name}.png ``` Requirements for good reference images: - Clear face visible (no heavy shadows or occlusion) - Decent resolution (512x512 minimum, 1024x1024+ preferred) - Single subject (no group photos) - Even lighting (no dramatic shadows hiding features) ### Multi-angle references (advanced) For maximum consistency, create 3 reference angles: - `{name}-front.png` — straight-on - `{name}-left.png` — 3/4 left view - `{name}-right.png` — 3/4 right view Pass all three as references when generating panels: ```bash node generate-image.js \ -p "Images 1-3 are reference angles for Character A. Maintain exact appearance. [SCENE]" \ -r characters/{name}-front.png \ -r characters/{name}-left.png \ -r characters/{name}-right.png \ -o panel.png ``` ## Listing Characters ```bash ls ~/.claude/skills/x-writer/scripts/characters/ ``` ## Deleting Characters ```bash rm ~/.claude/skills/x-writer/scripts/characters/{name}.png ``` ## Character Description Registry When creating a character, also save a text description alongside it so future sessions can reference it without re-analyzing the image. Create a simple `characters/registry.md`: ```markdown ## alex Male, early 30s, athletic build. Short dark brown hair, light stubble. Sharp jawline. Wearing a fitted black henley. Confident neutral expression. ## jordan Male, late 20s, muscular build. Blonde hair swept back. Clean-shaven. Wearing a grey fitted t-shirt. Slightly arrogant smirk. ``` This registry lets Claude reconstruct character descriptions without loading images, and ensures clothing/feature descriptions stay consistent across prompts. -
styles.md 3.7 KB
# Comic Visual Styles Pick ONE style per strip. All panels in a strip MUST use the same style suffix for visual coherence. ## 1. Graphic Novel — Bold Linework Best for: dramatic takes, confrontational threads, high-energy arguments. **Look:** Heavy outlines, high contrast, limited color palette (2-3 colors + black). Frank Miller meets editorial illustration. Dynamic angles, dramatic shadows. **Prompt suffix:** `Bold graphic novel style. Heavy black outlines, high contrast, limited color palette with [COLOR] and [COLOR]. Dynamic composition, dramatic shadows. Clean linework, no crosshatching. No text, no watermarks, no logos.` ### When to use - The thread is argumentative or confrontational - You want visual IMPACT over subtlety - The content is about power, conflict, or dominance --- ## 2. Cinematic Storyboard — Film Still Best for: narrative threads, storytelling, step-by-step breakdowns. **Look:** Realistic, desaturated, cinematic aspect ratio feel. Like stills from a prestige film. Natural lighting, environmental storytelling. The thread reads like a movie. **Prompt suffix:** `Cinematic film still. Realistic, slightly desaturated. [LIGHTING]. [COLOR PALETTE]. Shot on 35mm, shallow depth of field, natural grain. Widescreen composition. No text, no watermarks, no logos.` ### When to use - The thread tells a story with progression - You want immersive, realistic scenes - The content involves real-world scenarios or human behavior --- ## 3. Minimal Conceptual — Clean & Symbolic Best for: intellectual threads, evo psych theory, abstract concepts. **Look:** Sparse composition, lots of negative space, symbolic objects. One strong focal element per panel. Think New Yorker cover meets Muji advertising. Sophisticated, cerebral. **Prompt suffix:** `Minimal conceptual illustration. Sparse composition, generous negative space. Single focal element. [COLOR — muted, sophisticated]. Clean, graphic quality, sharp edges. No text, no watermarks, no logos.` ### When to use - The thread is about ideas, not events - Each tweet introduces a distinct concept - You want the images to make people THINK --- ## 4. Raw Documentary — Photojournalistic Best for: biology threads, nature references, real-world observations. **Look:** National Geographic quality. Gritty, real, unstaged. Natural lighting. The visual equivalent of "I saw this in the wild." **Prompt suffix:** `Documentary photograph. Raw, unstaged, authentic. [NATURAL LIGHTING]. Earth tones. Shot on telephoto, shallow depth of field. Photojournalistic quality. No text, no watermarks, no logos.` ### When to use - The thread references animal behavior or biology - You want credibility through realism - The content is observational --- ## Decision Tree — Picking the Style Walk through in order. Use the FIRST match: **A. Is the thread confrontational, high-energy, or about power dynamics?** → YES: **Graphic Novel** (Style 1) **B. Does the thread tell a story with scene progression?** → YES: **Cinematic Storyboard** (Style 2) **C. Is the thread about abstract ideas, theory, or concepts?** → YES: **Minimal Conceptual** (Style 3) **D. Does the thread reference nature, biology, or real-world observation?** → YES: **Raw Documentary** (Style 4) **E. None of the above?** → Default to **Cinematic Storyboard** (Style 2) — it's the most versatile. ## Consistency Rules 1. **Same style suffix** for every panel in a strip — never mix 2. **Same color palette** across panels — pick colors in panel 1, reuse them 3. **Same lighting direction** — if panel 1 has light from the left, maintain that 4. **Describe clothing explicitly** in every prompt — don't assume the model remembers 5. **Include character role descriptions** with every reference image, every time -
workflow.md 6.2 KB
# Comics Workflow ## Thread → Comic Strip Pipeline ### Phase 1: Character Setup Before generating panels, establish characters. **Create a new character:** 1. Describe the character in detail (appearance, clothing, build, hair, distinguishing features) 2. Generate a clear reference image — front-facing, evenly lit, 1:1 aspect ratio 3. Save to `~/.claude/skills/x-writer/scripts/characters/{name}.png` ```bash node ~/.claude/skills/x-writer/scripts/generate-image.js \ -p "Character portrait: [DETAILED DESCRIPTION]. Front-facing, evenly lit, neutral background, clear facial features. No text, no watermarks, no logos." \ -o ~/.claude/skills/x-writer/scripts/characters/{name}.png \ -a "1:1" ``` **Import an existing image as character:** - Copy any PNG/JPG to `~/.claude/skills/x-writer/scripts/characters/{name}.png` - Best results: clear face, good lighting, 1024x1024+ ### Phase 2: Thread Analysis Read the thread draft and map each tweet to a panel: 1. **Read the thread** — from OpenWriter or user input 2. **Identify panels** — each tweet that benefits from a visual gets a panel 3. **Extract scene descriptions** — what's happening in each tweet? 4. **Identify characters** — which characters appear in each panel? 5. **Plan visual progression** — the panels should tell a visual story, not just illustrate individual tweets ### Phase 3: Panel Generation For each panel, generate with character references: ```bash # Single character node ~/.claude/skills/x-writer/scripts/generate-image.js \ -p "[ROLE DESCRIPTION]. [SCENE PROMPT]. [STYLE SUFFIX]. No text, no watermarks, no logos." \ -o /c/tmp/panel-{N}.png \ -r ~/.claude/skills/x-writer/scripts/characters/{name}.png \ -a "16:9" # Multiple characters node ~/.claude/skills/x-writer/scripts/generate-image.js \ -p "Image 1 is Character A (face reference). Image 2 is Character B (face reference). [SCENE]. No text, no watermarks, no logos." \ -o /c/tmp/panel-{N}.png \ -r ~/.claude/skills/x-writer/scripts/characters/char-a.png \ -r ~/.claude/skills/x-writer/scripts/characters/char-b.png \ -a "16:9" ``` ### Phase 4: Review & Iteration - Show each panel to the user - Regenerate any that don't maintain consistency - For stubborn inconsistencies: use multiple angle references (front, 3/4 left, 3/4 right) ### Phase 5: Insert into Thread Two approaches for attaching panels to tweets: **Option A: `insert_image` (recommended for threads)** Use the OpenWriter MCP tool directly — it generates via Gemini AND inserts atomically: 1. `read_pad` to get node IDs for each tweet paragraph (`[p:nodeId]`) 2. Call `insert_image` with `afterNodeId` set to the target tweet's paragraph node 3. All panels can be inserted **in parallel** (no dependencies between them) ``` insert_image(docId, prompt, afterNodeId, alt, aspect_ratio) ``` - `afterNodeId` = the `[p:...]` ID of the tweet paragraph the image belongs to - Uses Gemini internally — no way to pass pre-generated files - Character consistency relies on prompt description only (no reference images) - Best for: quick insertion, no character reference needed **Option B: CLI generate → manual insert** Use `generate-image.js` with character references for better consistency, then insert: 1. Generate panels with `-r` reference images (Phase 3) 2. **CRITICAL:** Copy panels to `~/.openwriter/profiles/Default/_images/` (NOT `~/.openwriter/_images/`) ```bash cp /c/tmp/panel-*.png ~/.openwriter/profiles/Default/_images/ ``` The server serves `/_images/*` from the active profile's data dir. Wrong path = broken images. 3. Insert via `write_to_pad` with TipTap image nodes: ``` { operation: "insert", afterNodeId: "<tweet-node-id>", content: { type: "image", attrs: { src: "/_images/panel-1.png", alt: "..." } } } ``` - Best for: maximum character consistency via reference images **Trade-off:** Option A is faster and seamless but has no reference image support. Option B gives better character consistency but requires manual file handling. ## Prompt Construction ### Role Description (for reference images) When passing reference images, ALWAYS describe their role: - "This is the face reference for Character A. Maintain their exact appearance." - "Image 1: Character A face reference. Image 2: Character B face reference." ### Scene Prompt Describe the specific scene for this panel: - What are the characters doing? - Where are they? - What's the emotional tone? - What angle/framing? ### Style Suffix Append a consistent style suffix across ALL panels to maintain visual coherence: - Pick ONE style from the styles doc and use it for the entire strip - Never mix styles within a single strip ## Aspect Ratios | Use Case | Ratio | Notes | |----------|-------|-------| | Thread panels | 16:9 | Standard X image display | | Character sheets | 1:1 | Best for reference images | | Vertical panels | 9:16 | Mobile-optimized | | Square panels | 1:1 | Equal weight | ## Thread Document Format Thread documents MUST use TipTap JSON with `{ type: 'horizontalRule' }` nodes between tweets. - Use `create_document` → `populate_document` with explicit TipTap JSON - Markdown `---` does NOT create proper horizontalRule nodes - Each tweet is a paragraph node; `read_pad` shows `[p:nodeId]` and `[hr:nodeId]` tags ## Proven Prompting Patterns ### Graphic Novel Style (threads about power/strategy/dominance) ``` Bold graphic novel style. Heavy black outlines, high contrast, limited color palette with deep navy and amber gold. Dynamic composition, dramatic shadows. Clean linework, no crosshatching. No text, no watermarks, no logos. ``` ### Panel Placement Strategy - **Hook tweet (1)**: No image — text-only hooks perform better - **Middle tweets**: Image after text in each visual tweet - **Closing tweet**: Image reinforces the call-to-action - Not every tweet needs an image — text-only tweets between panels create rhythm ## Limitations - Character consistency works reliably across 4-6 panels - Clothing may drift slightly between panels — describe it explicitly each time - Complex poses with multiple characters are less reliable - Best results with clear, front-facing, evenly lit character references - `insert_image` has no reference image support — consistency is prompt-only - For maximum consistency, use CLI `generate-image.js` with `-r` reference images
-
-
images
-
characters.md 2.8 KB
# Image Character Management ## Character Storage Characters are stored as PNG files in `~/.claude/skills/x-writer/scripts/characters/`. Naming convention: `{name}.png` — lowercase, hyphens for spaces. - `alex.png` - `jordan.png` - `narrator.png` ## Creating a Character ### From scratch (AI-generated) Generate a clear reference portrait: ```bash node ~/.claude/skills/x-writer/scripts/generate-image.js \ -p "Character portrait of [DETAILED DESCRIPTION]. Front-facing, neutral background, even studio lighting, clear facial features visible. High detail. No text, no watermarks, no logos." \ -o ~/.claude/skills/x-writer/scripts/characters/{name}.png \ -a "1:1" ``` **Best practices for character creation prompts:** - Specify: age range, build, hair (color, style, length), facial hair, skin tone - Specify: clothing (be specific — this becomes the "default outfit") - Specify: expression (neutral or characteristic) - Always: "front-facing, neutral background, even studio lighting" - The more specific the description, the more consistent the character stays across panels ### From an existing image Copy any clear portrait to the characters directory: ```bash cp /path/to/reference.png ~/.claude/skills/x-writer/scripts/characters/{name}.png ``` Requirements for good reference images: - Clear face visible (no heavy shadows or occlusion) - Decent resolution (512x512 minimum, 1024x1024+ preferred) - Single subject (no group photos) - Even lighting (no dramatic shadows hiding features) ### Multi-angle references (advanced) For maximum consistency, create 3 reference angles: - `{name}-front.png` — straight-on - `{name}-left.png` — 3/4 left view - `{name}-right.png` — 3/4 right view Pass all three as references when generating panels: ```bash node generate-image.js \ -p "Images 1-3 are reference angles for Character A. Maintain exact appearance. [SCENE]" \ -r characters/{name}-front.png \ -r characters/{name}-left.png \ -r characters/{name}-right.png \ -o panel.png ``` ## Listing Characters ```bash ls ~/.claude/skills/x-writer/scripts/characters/ ``` ## Deleting Characters ```bash rm ~/.claude/skills/x-writer/scripts/characters/{name}.png ``` ## Character Description Registry When creating a character, also save a text description alongside it so future sessions can reference it without re-analyzing the image. Create a simple `characters/registry.md`: ```markdown ## alex Male, early 30s, athletic build. Short dark brown hair, light stubble. Sharp jawline. Wearing a fitted black henley. Confident neutral expression. ## jordan Male, late 20s, muscular build. Blonde hair swept back. Clean-shaven. Wearing a grey fitted t-shirt. Slightly arrogant smirk. ``` This registry lets Claude reconstruct character descriptions without loading images, and ensures clothing/feature descriptions stay consistent across prompts. -
styles.md 3.7 KB
# Image Visual Styles Pick ONE style per set. All images in a thread or strip MUST use the same style suffix for visual coherence. ## 1. Graphic Novel — Bold Linework Best for: dramatic takes, confrontational threads, high-energy arguments. **Look:** Heavy outlines, high contrast, limited color palette (2-3 colors + black). Frank Miller meets editorial illustration. Dynamic angles, dramatic shadows. **Prompt suffix:** `Bold graphic novel style. Heavy black outlines, high contrast, limited color palette with [COLOR] and [COLOR]. Dynamic composition, dramatic shadows. Clean linework, no crosshatching. No text, no watermarks, no logos.` ### When to use - The thread is argumentative or confrontational - You want visual IMPACT over subtlety - The content is about power, conflict, or dominance --- ## 2. Cinematic Storyboard — Film Still Best for: narrative threads, storytelling, step-by-step breakdowns. **Look:** Realistic, desaturated, cinematic aspect ratio feel. Like stills from a prestige film. Natural lighting, environmental storytelling. The thread reads like a movie. **Prompt suffix:** `Cinematic film still. Realistic, slightly desaturated. [LIGHTING]. [COLOR PALETTE]. Shot on 35mm, shallow depth of field, natural grain. Widescreen composition. No text, no watermarks, no logos.` ### When to use - The thread tells a story with progression - You want immersive, realistic scenes - The content involves real-world scenarios or human behavior --- ## 3. Minimal Conceptual — Clean & Symbolic Best for: intellectual threads, evo psych theory, abstract concepts. **Look:** Sparse composition, lots of negative space, symbolic objects. One strong focal element per panel. Think New Yorker cover meets Muji advertising. Sophisticated, cerebral. **Prompt suffix:** `Minimal conceptual illustration. Sparse composition, generous negative space. Single focal element. [COLOR — muted, sophisticated]. Clean, graphic quality, sharp edges. No text, no watermarks, no logos.` ### When to use - The thread is about ideas, not events - Each tweet introduces a distinct concept - You want the images to make people THINK --- ## 4. Raw Documentary — Photojournalistic Best for: biology threads, nature references, real-world observations. **Look:** National Geographic quality. Gritty, real, unstaged. Natural lighting. The visual equivalent of "I saw this in the wild." **Prompt suffix:** `Documentary photograph. Raw, unstaged, authentic. [NATURAL LIGHTING]. Earth tones. Shot on telephoto, shallow depth of field. Photojournalistic quality. No text, no watermarks, no logos.` ### When to use - The thread references animal behavior or biology - You want credibility through realism - The content is observational --- ## Decision Tree — Picking the Style Walk through in order. Use the FIRST match: **A. Is the thread confrontational, high-energy, or about power dynamics?** → YES: **Graphic Novel** (Style 1) **B. Does the thread tell a story with scene progression?** → YES: **Cinematic Storyboard** (Style 2) **C. Is the thread about abstract ideas, theory, or concepts?** → YES: **Minimal Conceptual** (Style 3) **D. Does the thread reference nature, biology, or real-world observation?** → YES: **Raw Documentary** (Style 4) **E. None of the above?** → Default to **Cinematic Storyboard** (Style 2) — it's the most versatile. ## Consistency Rules 1. **Same style suffix** for every image in a set — never mix 2. **Same color palette** across panels — pick colors in panel 1, reuse them 3. **Same lighting direction** — if panel 1 has light from the left, maintain that 4. **Describe clothing explicitly** in every prompt — don't assume the model remembers 5. **Include character role descriptions** with every reference image, every time -
workflow.md 5.3 KB
# Image Workflow ## Thread → Image Set Pipeline ### Phase 1: Character Setup (optional) Before generating images, establish recurring characters if applicable. **Create a new character:** 1. Describe the character in detail (appearance, clothing, build, hair, distinguishing features) 2. Generate a clear reference image — front-facing, evenly lit, 1:1 aspect ratio 3. Save to `~/.claude/skills/x-writer/scripts/characters/{name}.png` ```bash node ~/.claude/skills/x-writer/scripts/generate-image.js \ -p "Character portrait: [DETAILED DESCRIPTION]. Front-facing, evenly lit, neutral background, clear facial features. No text, no watermarks, no logos." \ -o ~/.claude/skills/x-writer/scripts/characters/{name}.png \ -a "1:1" ``` **Import an existing image as character:** - Copy any PNG/JPG to `~/.claude/skills/x-writer/scripts/characters/{name}.png` - Best results: clear face, good lighting, 1024x1024+ ### Phase 2: Thread Analysis Read the thread draft and map each tweet to an image: 1. **Read the thread** — from OpenWriter or user input 2. **Identify image tweets** — each tweet that benefits from a visual 3. **Extract scene descriptions** — what's happening in each tweet? 4. **Identify characters** — which characters appear in each image? 5. **Plan visual progression** — images should tell a visual story, not just illustrate individual tweets ### Phase 3: Image Generation For each image, generate with character references when needed: ```bash # Single character node ~/.claude/skills/x-writer/scripts/generate-image.js \ -p "[ROLE DESCRIPTION]. [SCENE PROMPT]. [STYLE SUFFIX]. No text, no watermarks, no logos." \ -o /c/tmp/panel-{N}.png \ -r ~/.claude/skills/x-writer/scripts/characters/{name}.png \ -a "16:9" # Multiple characters node ~/.claude/skills/x-writer/scripts/generate-image.js \ -p "Image 1 is Character A (face reference). Image 2 is Character B (face reference). [SCENE]. No text, no watermarks, no logos." \ -o /c/tmp/panel-{N}.png \ -r ~/.claude/skills/x-writer/scripts/characters/char-a.png \ -r ~/.claude/skills/x-writer/scripts/characters/char-b.png \ -a "16:9" ``` ### Phase 4: Review & Iteration - Show each image to the user - Regenerate any that don't maintain consistency - For stubborn inconsistencies: use multiple angle references (front, 3/4 left, 3/4 right) ### Phase 5: Insert into Thread Two approaches for attaching images to tweets: **Option A: `insert_image` (recommended for threads)** Use the OpenWriter MCP tool directly — it generates via Gemini AND inserts atomically: 1. `read_pad` to get node IDs for each tweet paragraph (`[p:nodeId]`) 2. Call `insert_image` with `afterNodeId` set to the target tweet's paragraph node 3. All images can be inserted **in parallel** (no dependencies between them) ``` insert_image(docId, prompt, afterNodeId, alt, aspect_ratio) ``` - `afterNodeId` = the `[p:...]` ID of the tweet paragraph the image belongs to - Uses Gemini internally — no way to pass pre-generated files - Character consistency relies on prompt description only (no reference images) - Best for: quick insertion, no character reference needed **Option B: CLI generate → manual insert** Use `generate-image.js` with character references for better consistency, then insert: 1. Generate images with `-r` reference images (Phase 3) 2. **CRITICAL:** Copy images to `~/.openwriter/profiles/Default/_images/` (NOT `~/.openwriter/_images/`) ```bash cp /c/tmp/panel-*.png ~/.openwriter/profiles/Default/_images/ ``` The server serves `/_images/*` from the active profile's data dir. Wrong path = broken images. 3. Insert via `write_to_pad` with TipTap image nodes: ``` { operation: "insert", afterNodeId: "<tweet-node-id>", content: { type: "image", attrs: { src: "/_images/panel-1.png", alt: "..." } } } ``` - Best for: maximum character consistency via reference images **Trade-off:** Option A is faster and seamless but has no reference image support. Option B gives better character consistency but requires manual file handling. ## Prompt Construction ### Role Description (for reference images) When passing reference images, ALWAYS describe their role: - "This is the face reference for Character A. Maintain their exact appearance." - "Image 1: Character A face reference. Image 2: Character B face reference." ### Scene Prompt Describe the specific scene for this image: - What are the characters doing? - Where are they? - What's the emotional tone? - What angle/framing? ### Style Suffix Append a consistent style suffix across ALL images in a set to maintain visual coherence: - Pick ONE style from `images/styles.md` and use it for the entire set - Never mix styles within a single set ## Aspect Ratios | Use Case | Ratio | Notes | |----------|-------|-------| | Thread images | 16:9 | Standard X image display | | Article cover | 16:9 | X Articles ~1600x900 | | Character sheets | 1:1 | Best for reference images | | Vertical | 9:16 | Mobile-optimized | | Square | 1:1 | Equal weight | ## Thread Document Format Thread documents MUST use TipTap JSON with `{ type: 'horizontalRule' }` nodes between tweets. - Use `create_document` → `populate_document` with explicit TipTap JSON - Markdown `---` does NOT create proper horizontalRule nodes - Each tweet is a paragraph node; `read_pad` shows `[p:nodeId]` and `[hr:nodeId]` tags
-
-
article-format.md 7.8 KB
# X Article Format — Evaluate & Optimize Score and fix X Article formatting for scroll-stop engagement. Articles are read on mobile in a feed — every formatting decision is a retention decision. ## Modes ### EVALUATE — Score an article Read the article via `read_pad`. Score each dimension 0-10. Report the total and flag the weakest areas. | # | Dimension | 10/10 Looks Like | 0/10 Looks Like | |---|-----------|-----------------|-----------------| | 1 | **Title** | Bold claim that STATES the thesis. Forces reaction from every reader. No mystery, no curiosity gaps. Use Ad Legends technique (score 0-100 across top 10 ad practitioners, don't stop until 90+). | Generic label. Mystery bait. "What I Learned About..." Requires reading to understand. | | 2 | **Opening Hook** | Most provocative line in first 3 paragraphs. Grenade structure: context → blockquote/punch → stakes. | Scene-setting. Background. "In [year], [person] did [thing]..." | | 3 | **Blockquote Usage** | Pull-quotes from the article's own strongest lines + source quotes. ~1 per 500 words. Visual scroll-stops. | Quotes buried in paragraph text. No blockquotes at all. | | 4 | **Subheading Quality** | Subheadings form an **argument spine** — reading ONLY the subheadings in sequence tells the article's complete thesis. Each is a condensed claim, not a label. | Generic labels: "Background", "Analysis", "Conclusion". | | 5 | **Paragraph Density** | 2-4 sentences max. White space everywhere. Mobile-native. | 5+ sentence walls. Academic paragraph structure. | | 6 | **Bold Strategy** | 1-2 bold thesis phrases per section. Scanners get 80% of the argument from bold + subheadings alone. | No bold, or everything bold. Bold on irrelevant words. | | 7 | **Closing Strength** | Callback to opening image/quote, or identity/tension statement. Lands with weight. | Summary paragraph. "In conclusion..." Fades out. | **Output format:** ``` ARTICLE FORMAT SCORE: [title] 1. Title: X/10 — [one-line reason] 2. Opening Hook: X/10 — [one-line reason] 3. Blockquote Usage: X/10 — [one-line reason] 4. Subheading Quality: X/10 — [one-line reason] 5. Paragraph Density: X/10 — [one-line reason] 6. Bold Strategy: X/10 — [one-line reason] 7. Closing Strength: X/10 — [one-line reason] TOTAL: XX/70 PRIORITY FIXES: [top 2-3 issues, in order] ``` ### OPTIMIZE — Fix the article Fix in strict priority order. Stop after each fix and show the user what changed. **Priority order:** 1. Title (biggest impact — determines whether anyone clicks at all. Use Ad Legends technique.) 2. Opening (50% of readers decide here) 3. Blockquotes (visual scroll-stops, zero effort to add) 4. Subheadings (curiosity hooks for scanners) 5. Paragraph density (split walls) 6. Bold strategy (reward scanners) 7. Closing (callback or tension) **Workflow:** 1. `create_checkpoint` — safety snapshot before any edits 2. `read_pad` — get current article state 3. Fix priority #1, show user what changed 4. Continue through priorities, batching small fixes (3-8 changes per `write_to_pad` call) 5. Final `read_pad` — confirm structure, re-score ## The 10 Rules These are the formatting laws. Every EVALUATE score and every OPTIMIZE edit is grounded in these rules. ### 1. Title Rule The title is a bold claim that STATES the thesis. No mystery. No curiosity gaps. No "What I Learned About..." The reader must know the article's position before clicking. Use the **Ad Legends technique**: generate candidates through the lens of top advertising practitioners (Ogilvy, Halbert, Schwartz, Caples, Hopkins, Bernbach, Kennedy, Sugarman, Lois, Burnett), score each 0-100, don't stop until you hit 90+. The title that forces a reaction from every reader — agree or disagree — is the right one. **Anti-patterns:** Mystery bait ("The System Nobody Talks About"), generic labels ("The Marsh People"), questions ("What If Women Were Always In Charge?"), anything that requires reading the article to understand the claim. ### 2. Grenade Rule The most provocative line must be in the first 3 paragraphs. Not paragraph 5. Not after "context." The opening is a grenade: pull the pin in sentence one, let it explode by paragraph three. Background and scene-setting come AFTER. **Structure:** - P1: One-sentence context that creates tension - P2: Blockquote or punch line (the grenade) - P3: Stakes — why this matters, what it changes ### 3. Scanner Rule Title + opening + subheadings + blockquotes + bold + closing = 80% of the argument. Most people scan. If a scanner gets nothing from your formatting, you lost them. The article must work at TWO speeds: scanning and reading. ### 4. Mobile Rule No paragraph over 4 sentences. X Articles are read on phones. A 6-sentence paragraph is a wall on mobile. Break it. White space is not wasted space — it is pacing. ### 5. Blockquote Rule Two types of blockquotes, both mandatory: - **Pull-quotes**: The article's own strongest lines, extracted and placed as standalone `>` blockquotes between paragraphs. These are the lines that hit hardest — fragment closers, thesis punches, identity statements. They reward scanners and create visual breathing room. - **Source quotes**: Primary source quotes in `>` blockquotes. Signal evidence. Minimum 1 blockquote per 500 words across both types. Blockquotes are visual scroll-stops — the eye catches them even while scrolling fast. ### 6. Subheading Rule — Argument Spine Subheadings are not labels. They are **the argument in condensed form**. Test: read ONLY the subheadings in sequence. They should tell the article's complete thesis — a reader who sees nothing else should understand the core claim. Each subheading is a short declarative statement of what that section proves. Not a curiosity hook (that's clickbait). Not a topic label ("Background", "Analysis"). A claim. **Example (from "Caffeine Is Not Energy"):** - "Adenosine Is The Debt" - "Caffeine Blocks The Signal" - "The Debt Compounds Anyway" - "The Afternoon Crash" Scan those four lines. You get the entire argument without reading a word of body text. That is the argument spine. ### 7. Bold Rule 1-2 bold phrases per section. Bold the thesis sentence — the one claim that section exists to make. Never bold names, dates, or transitions. Bold is for arguments. ### 8. Closing Rule End with a callback to the opening image or quote, OR an identity/tension statement that lingers. Never summarize. Never "in conclusion." Never fade out. The last paragraph should hit as hard as the first. ### 9. "Read That Again" Rule Never tell the reader what to feel or do. No "Read that again." No "Let that sink in." No "Think about that." If the line is powerful, it doesn't need a sign pointing at it. If it's not powerful, the sign won't save it. ### 10. Scene-Setting Rule Background goes AFTER the hook, never before. The opening is not the place for "In [year], [historical figure] did [historical thing]." That's a textbook. The opening is the place for the most provocative claim, quote, or tension in the entire article. Context earns its place only after the reader is already committed. ## Examples ### Bad opening (violates Rule 1, 9): > In 208 AD, the Roman Emperor Severus invaded Scotland and got bogged down fighting the Caledonians. The Roman senator and historian Cassius Dio, writing from Rome, recorded what the campaign revealed about these people. ### Good opening (follows Rule 1, 9): > A Caledonian woman said this to the Roman empress. In 208 AD. To her face. > > > "We consort openly with the best men, whereas you let yourselves be debauched in secret by the vilest." > > **She wasn't boasting. She was describing a mating system.** One that the DNA now confirms. ### Bad subheadings: - The Hybrid Mating System - The DNA Confirms It - The Pendulum - But the Framework Is Gone ### Good subheadings: - Three Layers - The DNA Doesn't Lie - The Return - The System Without the Village -
comics.md 3.5 KB
# X Comics — Thread-Aware Comic Strip Generator Generate character-consistent comic panels for X threads. The thread IS the storyboard. ## Quick Start ```bash # Create a character node ~/.claude/skills/x-writer/scripts/generate-image.js \ -p "Character portrait: [DESCRIPTION]. Front-facing, neutral background, even studio lighting. No text, no watermarks, no logos." \ -o ~/.claude/skills/x-writer/scripts/characters/{name}.png -a "1:1" # Generate a panel with character reference node ~/.claude/skills/x-writer/scripts/generate-image.js \ -p "This is Character A (face reference). [SCENE]. [STYLE SUFFIX]. No text, no watermarks, no logos." \ -o /c/tmp/panel-1.png \ -r ~/.claude/skills/x-writer/scripts/characters/{name}.png \ -a "16:9" ``` ## Workflows ### 1. CREATE — New character → Read `comics/characters.md` ### 2. GENERATE — Comic strip from thread → Read `comics/workflow.md` + `comics/styles.md` ### 3. MANAGE — List/delete characters ```bash ls ~/.claude/skills/x-writer/scripts/characters/ # list rm ~/.claude/skills/x-writer/scripts/characters/{name}.png # delete ``` ## CLI Reference ``` node ~/.claude/skills/x-writer/scripts/generate-image.js [options] -p, --prompt Scene prompt (required) -o, --output Output file path (required) -r, --reference Reference image path(s) — repeat for multiple -a, --aspect-ratio 1:1, 16:9, 9:16, 4:3, 3:4 (default: 1:1) -m, --model Gemini model (default: gemini-3.1-flash-image-preview) ``` ## Thread → Panels Pipeline 1. **Read the thread** — `read_pad` to get `[p:nodeId]` for each tweet 2. **Identify which tweets need panels** — not every tweet needs an image (hook = text-only) 3. **Pick ONE style** from `comics/styles.md` — use for ALL panels 4. **Generate & insert panels** — two options: - **Fast path:** `insert_image(docId, prompt, afterNodeId)` — generates via Gemini + inserts atomically. All panels in parallel. No character references. - **Consistent path:** CLI `generate-image.js` with `-r` reference images, then insert manually (see below). 5. **Review with user** — regenerate any inconsistent panels ## Inserting Pre-Generated Images into OpenWriter When using the **consistent path** (CLI-generated panels with reference images), you must copy them to OpenWriter's image directory and insert via `write_to_pad`: ```bash # 1. Copy panels to the PROFILE images dir (NOT ~/.openwriter/_images/) cp /c/tmp/panel-*.png ~/.openwriter/profiles/Default/_images/ # 2. Insert via write_to_pad with TipTap image nodes write_to_pad(docId, changes: [ { operation: "insert", afterNodeId: "<tweet-node-id>", content: { type: "image", attrs: { src: "/_images/panel-1.png", alt: "..." } } } ]) ``` **CRITICAL:** Images MUST go in `~/.openwriter/profiles/Default/_images/` (or the active profile). The server serves `/_images/*` from `getDataDir()/_images/`, which resolves to the profile directory. Copying to `~/.openwriter/_images/` will result in broken images. ## Prompt Rules 1. ALWAYS describe character roles when passing reference images 2. ALWAYS describe clothing explicitly in every panel prompt 3. ALWAYS end with "No text, no watermarks, no logos." 4. Use the SAME style suffix for every panel in a strip 5. Keep prompts to 2-3 sentences — Gemini works better focused 6. Be specific about body language, posture, and action ## Character Storage `~/.claude/skills/x-writer/scripts/characters/{name}.png` Optional registry: `~/.claude/skills/x-writer/scripts/characters/registry.md` -
images.md 5.3 KB
# X Images Generate images for X content. Two modes based on context. ## Modes ### COVER — Single image for an X Article 1. Get article context (topic, title, draft text) 2. Walk the decision tree → find the ONE concept 3. Pick style → craft prompt → generate 4. Set as article cover via `insert_image(prompt, set_cover: true)` ### THREAD — Multiple images for a tweet thread 1. `read_pad` to get thread structure (`[p:nodeId]` per tweet) 2. Identify which tweets need images (not every tweet does) 3. Pick ONE style for ALL images in the thread 4. Generate & insert via `insert_image(docId, prompt, afterNodeId)` — all panels in parallel 5. Review with user, regenerate inconsistent ones For character-consistent thread images, use the CLI with reference images: ```bash node ~/.claude/skills/x-writer/scripts/generate-image.js \ -p "Scene prompt. [STYLE SUFFIX]. No text, no watermarks, no logos." \ -o /c/tmp/panel-1.png \ -r ~/.claude/skills/x-writer/scripts/characters/{name}.png \ -a "16:9" ``` Then copy to `~/.openwriter/profiles/Default/_images/` and insert via `write_to_pad`. ## Styles ### 1. Dark Editorial Best for: culture war takes, institutional critique, provocative intellectual arguments. **Look:** Dark backgrounds, dramatic lighting, single focal subject. Moody, authoritative. - Color palettes: deep charcoal + amber, black + cold steel blue, midnight + warm gold - Lighting: single dramatic source, rim lighting, chiaroscuro **Prompt suffix:** `Dark editorial style. Dramatic single-source lighting, deep shadows. Rich dark tones with [COLOR] accents. Shot on medium format, shallow depth of field. No text, no watermarks, no logos.` ### 2. Cinematic Realism Best for: masculinity, fitness, human behavior, relationship dynamics, personal development. **Look:** Photo-realistic scenes with cinematic lighting. Could be a still from a prestige TV show. - Color palettes: warm amber/gold, desaturated cool, natural earth tones - Lighting: golden hour, hard contrast, natural available light **Prompt suffix:** `Cinematic photograph. Realistic, candid feel. [LIGHTING]. [COLOR PALETTE]. Shot on 35mm film, natural grain, shallow depth of field. No text, no watermarks, no logos.` ### 3. Abstract/Conceptual Best for: evo psych theory, sexual selection mechanics, complex biological concepts. **Look:** Symbolic imagery — DNA helixes, silhouettes, geometric overlays, split compositions. Cerebral. - Color palettes: monochromatic with one accent, deep blue + gold, black + red - Lighting: studio-style, controlled, graphic **Prompt suffix:** `Conceptual editorial photograph. Symbolic composition. [LIGHTING]. [COLOR PALETTE]. Clean, graphic quality, sharp focus. No text, no watermarks, no logos.` ### 4. Raw/Documentary Best for: biology, nature references, animal behavior, evolutionary competition. **Look:** National Geographic meets editorial. Real animals, real environments, unstaged. - Color palettes: natural earth tones, golden savanna, cold arctic blue - Lighting: natural — golden hour, overcast, harsh midday **Prompt suffix:** `Wildlife/documentary photograph. [SCENE]. [LIGHTING]. [COLOR PALETTE]. Shot on telephoto lens, shallow depth of field, National Geographic quality. No text, no watermarks, no logos.` ### 5. Dark Infographic Best for: mechanism diagrams, distance measurement, bell curves, trait comparisons, data-visual threads. **Look:** Dark background with illustrated figures and measurement/diagram overlays. Labels, arrows, comparison lines. Scientific but stylized. - Color palettes: dark charcoal + white labels + accent color - Lighting: flat/even on figures, dark surround - Feel: educational, authoritative, shareable **Prompt suffix:** `Dark infographic illustration. [FIGURES/COMPARISON]. Clean measurement lines and labels on dark background. Bold outlines, diagrammatic feel. No text bubbles, no watermarks, no logos.` ## Decision Tree — Finding the Image Walk through in order. Use the FIRST one that fits: **A. Cultural Archetype** — Is there a person/type that EMBODIES the concept? **B. Concrete Metaphor** — Does the content use a specific visual metaphor? **C. Symbolic Scene** — Can a constructed scene tell the story? **D. Atmospheric/Abstract** — Fallback: mood, texture, color. ## Brand Notes - Never corporate or sterile - Never stock-photo energy - Masculine but not cringe (no shirtless gym selfies, no flag eagles) - Intellectual but accessible - Scroll-stop factor — X timeline moves fast ## CLI Reference ``` node ~/.claude/skills/x-writer/scripts/generate-image.js [options] -p, --prompt Scene prompt (required) -o, --output Output file path (required) -r, --reference Reference image path(s) — repeat for multiple -a, --aspect-ratio 1:1, 16:9, 9:16, 4:3, 3:4 (default: 1:1) -m, --model Gemini model (default: gemini-3.1-flash-image-preview) ``` ## Character Storage `~/.claude/skills/x-writer/scripts/characters/{name}.png` For character creation workflow → `images/characters.md` ## Prompt Rules 1. Be SPECIFIC — describe posture, lighting, environment in detail 2. Include lighting details — they drive mood more than anything 3. Keep prompts to 2-3 sentences — Gemini works better focused 4. Always end with "No text, no watermarks, no logos." 5. Never say "leave area empty" — Gemini draws literal boxes 6. Use the SAME style suffix for every image in a thread set -
openwriter-mechanics.md 8.6 KB
## Tweet Compose Mode OpenWriter doubles as a tweet compose surface. When `tweetContext` is set in a document's metadata, the editor switches to a pixel-accurate X/Twitter compose view — reply thread or quote tweet layout with embedded parent tweet, character counter, and action bar. ### Setting up a tweet document ``` 1. create_document({ title: "Reply to @username", content_type: "reply", url: "https://x.com/user/status/123", empty: true }) ``` - **`url`** — the tweet URL to reply to or quote - **`mode`** — `"reply"` (thread layout with parent above) or `"quote"` (compose above, quoted card below) The view activates automatically when `tweetContext` is present — no manual toggle needed. Documents are auto-tagged `"x"` in the sidebar for discoverability. ### Working on an existing tweet document When the user asks you to work on a tweet doc, follow this exact sequence: ``` 1. read_pad → get content + node IDs + docId 2. get_metadata → get tweetContext (url, mode), tags 3. Extract tweet URL → parse username + tweet ID from tweetContext.url 4. WebFetch fxtwitter → read the parent tweet for FREE 5. Check workspaces → find relevant reference docs for context 6. Write → now you have everything, edit the pad ``` **Step 3-4 in detail:** Parse the URL from `tweetContext.url` (e.g. `https://x.com/HustleBitch_/status/2033641235739496554`) → extract username and ID → fetch via fxtwitter: ``` WebFetch: https://api.fxtwitter.com/{username}/status/{tweet_id} ``` This returns full text, metrics, media, quoted tweets — all for FREE. **Never use paid X API search to find a tweet that's already in the document metadata.** **Step 5:** If the tweet references concepts the user has written about (their recurring frameworks and coined terms), check their workspaces via `list_workspaces` → `get_workspace_structure` → `read_pad` on relevant reference docs. This gives you the user's framework to write from, not generic knowledge. ### Reading the parent tweet (when creating new tweet docs) Use the x-reader skill or fxtwitter API to fetch tweet data before setting up: ``` WebFetch: https://api.fxtwitter.com/{username}/status/{tweet_id} ``` The compose view fetches and renders the parent tweet (text, author, avatar, media, metrics) automatically from the URL. ### Template Documents Users can also create tweet and article templates directly from the browser UI using the **Templates** dropdown in the titlebar. For agent-initiated creation, `content_type` handles all metadata automatically: **Tweet:** `create_document({ title: "Tweet", content_type: "tweet", empty: true })` **Reply:** `create_document({ title: "Reply", content_type: "reply", url: "https://x.com/user/status/123", empty: true })` **Quote tweet:** `create_document({ title: "Quote Tweet", content_type: "quote", url: "https://x.com/user/status/123", empty: true })` **Article:** `create_document({ title: "Article", content_type: "article", empty: true })` ### Removing tweet mode ``` set_metadata({ tweetContext: null }) ``` This restores the normal editor view and removes the "x" tag. ### Placeholder text - Quote mode: "Add a comment" - Reply mode: "What is happening?!" ### Compose avatar Users set their X handle by clicking the avatar circle in the compose area. The handle is saved to localStorage and the pfp loads from `unavatar.io/twitter/{handle}`. ### Creating Tweet Threads Threads are single documents with `horizontalRule` nodes separating each tweet. The compose view splits at HRs into separate tweet editors. **Do NOT use `populate_document` for threads.** Use `create_document` with `content_type: "tweet"` + `empty: true`, then `write_to_pad` with `horizontalRule` JSON nodes between tweets. The `content_type` flag sets `tweetContext` metadata automatically. **THREE RULES for thread HRs:** 1. **`horizontalRule` separators MUST use TipTap JSON `{ type: "horizontalRule" }`.** Markdown `---` does NOT create proper HR nodes. 2. **Each HR must be its own change.** Do NOT use content arrays `[{type: "horizontalRule"}, {type: "paragraph", ...}]` — this silently drops the HR. 3. **Send the ENTIRE thread in ONE `write_to_pad` call.** Do NOT split across multiple calls. Multiple calls create race conditions — if the user accepts changes between calls, pending HRs can be dropped. One call = atomic = no race conditions. ``` 1. create_document({ title: "Thread title", content_type: "tweet", empty: true }) 2. write_to_pad({ docId: "<docId>", changes: [ { operation: "insert", afterNodeId: "end", content: "Tweet 1 paragraph 1" }, { operation: "insert", afterNodeId: "end", content: "Tweet 1 paragraph 2" }, { operation: "insert", afterNodeId: "end", content: { type: "horizontalRule" } }, { operation: "insert", afterNodeId: "end", content: "Tweet 2 paragraph 1" }, { operation: "insert", afterNodeId: "end", content: "Tweet 2 paragraph 2" }, { operation: "insert", afterNodeId: "end", content: { type: "horizontalRule" } }, { operation: "insert", afterNodeId: "end", content: "Tweet 3 paragraph 1" } ]}) ``` **For long threads (many tweets):** still send in ONE call. The changes array can hold dozens of items. Atomicity matters more than streaming feel for threads — a half-built thread with missing HRs is worse than waiting for the full thread to arrive. ### Inserting New Tweets into Existing Threads **Mid-thread insertion is unreliable.** `afterNodeId: "end"` always means document end, not after your last insert. Inserting after specific node IDs mid-document has edge cases with pending changes and image nodes. **Preferred approach: rebuild the full thread.** Delete the document and recreate with all tweets in one atomic `write_to_pad` call. This is the only pattern that reliably produces correct thread structure. **If you must insert mid-thread:** use a single `write_to_pad` call with the HR and all content targeting the same `afterNodeId` (the last node of the preceding tweet). Content inserts in reverse order when sharing an afterNodeId, so list changes in reverse. This is fragile — prefer full rebuild. **Do NOT delete empty paragraphs after images.** Images create empty `<p>` nodes after them. These look like junk but HRs (thread separators) are dependent on them. Deleting the empty paragraph kills the HR too, merging two tweets into one. Leave them alone. **NEVER bulk-delete text nodes in a thread that contains images.** Image nodes survive text deletion and become orphans — stranded in the wrong position with no surrounding content. The user must then manually delete every orphan image from the browser. This is catastrophic. If you need to reorder tweets, move text around the existing images, or delete the entire document and start fresh (which properly removes everything including images). ### Paragraph Spacing in Tweets Tweet compose uses `<br>` (hardBreak) for line breaks within a paragraph. Double Enter in the browser creates a new `<p>` node (paragraph split) with visual spacing. **For agents writing via `write_to_pad`:** use separate paragraph nodes for paragraph spacing. Each paragraph gets its own node ID, enabling independent editing. ``` // Correct: separate paragraph nodes for paragraph spacing write_to_pad({ docId: "...", changes: [ { operation: "insert", afterNodeId: "end", content: "First paragraph of tweet." }, { operation: "insert", afterNodeId: "end", content: "Second paragraph — separate node, visual gap." } ]}) ``` For line breaks WITHIN a single paragraph (no gap), use TipTap JSON with hardBreak: ``` { type: "paragraph", content: [ { type: "text", text: "Line one" }, { type: "hardBreak" }, { type: "text", text: "Line two (same node, no gap)" } ] } ``` This applies to all tweet modes — single tweets, replies, quotes, and individual tweets within threads. ### Inserting Images into Thread Tweets After creating a thread, use `read_pad` to get node IDs, then `insert_image` to add images after specific tweets: ``` 1. read_pad() → shows [p:abc123] for each tweet paragraph 2. insert_image({ docId: "...", afterNodeId: "abc123", ← paragraph node ID from read_pad prompt: "...", aspect_ratio: "16:9" }) ``` All `insert_image` calls can run **in parallel** — no dependencies between them. Images appear with green pending decorations for user review. ### Inserting Existing Images (from disk) Copy to `~/.openwriter/profiles/Default/_images/`, then use TipTap JSON in `write_to_pad`: ``` content: { "type": "image", "attrs": { "src": "/_images/my-image.png", "alt": "..." } } ``` **Markdown `` does NOT work** — creates an empty paragraph. Always use TipTap JSON. -
pipeline.md 5.3 KB
# Tweet Pipeline Brainstorm with user → polish in their voice → compose/schedule via OpenWriter. **Prerequisites:** OpenWriter MCP server connected, `/authors-voice` voice profile configured (or `AV_API_KEY` env var for the CLI polish path). ## Tweet Formats Three tiers. **Medium form is the default for all original content.** ### Short form (< 280 chars) Classic Twitter. One-liner punches. Good for replies, dunks, and engagement farming. NOT the primary format. ### Medium form (2-5 paragraphs, sweet spot 3-4) — THE DEFAULT This is the money zone. X shows a "read more" fold after ~280 chars. When someone clicks to expand: - **They've invested** — click = commitment, they'll read the whole thing - **It's digestible** — 3-4 paragraphs is short enough they finish it - **It's not a bookmark trap** — long-form gets "saved for later" (never read) Use medium form for: **epicenter QTs, iterations, original takes, all content pipeline output.** Structure: - **P1**: Hook — provocative claim or observation that stops the scroll - **P2**: Tension — the insight, the contradiction, the "why this matters" - **P3**: Resolution — the framework, the reframe, the punchline - **P4** (optional): Identity/call — "This is what X means" or territorial claim ### Long form (6+ paragraphs) Deep threads, essays, manifestos. Gets bookmarked, lower completion rate. Use sparingly — only for flagship content. --- ## [!WORKFLOW] ### Step 0: Read trending epicenters for inspiration (optional, FREE) If the user wants to write tweets that ride trending conversations, read the epicenter tweets first: ``` WebFetch: https://api.fxtwitter.com/{handle}/status/{id} ``` This returns full tweet text, metrics, media, and quoted tweets — completely free, no X API cost. Use tweet URLs from the epicenter skill or any URL the user provides. Swap `x.com` → `api.fxtwitter.com` in any tweet URL. Use this to understand what's resonating, what angles are getting engagement, and what the conversation looks like before crafting tweets. ### Step 1: Brainstorm tweets with the user Discuss the topic, angle, and hook. Draft raw tweets together. Don't worry about voice — that comes next. Focus on: - **Medium form by default** (3-4 paragraphs) — see Tweet Formats above - Strong hooks (first line matters most — it's what shows before the fold) - One core idea per tweet, developed across paragraphs - Provocative > informative for engagement - Short form (< 280 chars) only for replies or quick dunks ### Step 2: Polish in your voice Two paths: **Conversational (default):** invoke `/authors-voice` to rewrite each draft via MCP. Canonical voice path. **CLI (batch / scripting):** for batch polishing outside a Claude turn: ```bash node ~/.claude/skills/x-writer/scripts/polish.js "Raw draft tweet text here" ``` Calls `apply_voice` with category `x`, mode `rewrite`, intensity `moderate`. Multi-input batch: `polish.js "tweet 1" "tweet 2" "tweet 3"`. Options: `--intensity light|moderate|full`, `--json`. ### Step 3: Review with user Present original vs polished side by side. The user picks the version they prefer, or asks for adjustments. Flag anything over 280 chars unless it's intentionally a thread/article. ### Step 4: Compose in OpenWriter Approved drafts go into OpenWriter as the composition surface. Use OpenWriter mechanics for `tweetContext` / `articleContext` metadata, `content_type` (`tweet` / `reply` / `quote` / `article`), thread HRs, images, previews — see [`openwriter-mechanics.md`](openwriter-mechanics.md). ### Step 5: Schedule via OpenWriter native Scheduling and posting are OpenWriter-native — no separate CLI scheduler: - **`mcp__openwriter__schedule_post`** — queue a doc for posting at a specific time - **`mcp__openwriter__post_to_x`** — post immediately - **`mcp__openwriter__list_schedule`** — review what's queued - **`mcp__openwriter__manage_schedule`** — edit / cancel scheduled posts OpenWriter owns the X integration, time math, retries, and queue persistence. ## [!POLISH-ONLY] To polish without composing/scheduling (just get the voice rewrite): ```bash node ~/.claude/skills/x-writer/scripts/polish.js "Your raw tweet text" ``` Prints the polished version. Use `--json` for structured output. ## [!FREE-TWEET-READING] Read ANY tweet for free by swapping `x.com` → `api.fxtwitter.com` in the URL: ``` x.com/handle/status/123 → api.fxtwitter.com/handle/status/123 ``` Use `WebFetch` on the fxtwitter URL. Returns: full text, author, metrics (likes, RTs, views, quotes), media URLs, and quoted tweet data. No auth, no API key, no cost. **Use cases:** - Read epicenter tweets before writing responses - Check engagement on tweets the user wants to quote-tweet - Read thread context before writing a reply - Study high-performing tweets in the niche for style/angle inspiration ## [!TIPS] - **Medium form is the default** — 3-4 paragraphs, not one-liners - **One core concept per tweet** — develop it across paragraphs, don't pack multiple ideas - **Threads:** compose as a single OpenWriter doc with HR breaks between tweets; schedule the whole thread as one unit - **Quote tweets:** use OpenWriter's `tweetContext` with `content_type: "quote"` and the source `tweet_id` - **Author's Voice category is `x`** — the voice API uses the user's Twitter writing samples for voice matching
-
-
scripts
-
.gitignore 44 B · in bundle
-
generate-image.js 5.4 KB
#!/usr/bin/env node /** * x-comics generate.js — Gemini image generation with character reference support * * Usage: * node generate.js -p "scene prompt" -o output.png [-r ref1.png ref2.png] [-a 1:1] * * Passes reference images as inlineData to Gemini generateContent * for character-consistent comic panel generation. */ import { GoogleGenAI } from '@google/genai'; import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'; import { resolve, dirname, extname } from 'node:path'; import { parseArgs } from 'node:util'; const VALID_ASPECT_RATIOS = ['1:1', '16:9', '9:16', '4:3', '3:4']; const DEFAULT_MODEL = 'gemini-3.1-flash-image-preview'; function getMimeType(filePath) { const ext = extname(filePath).toLowerCase(); const map = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp', '.gif': 'image/gif' }; return map[ext] || 'image/png'; } function loadImageAsBase64(filePath) { const resolved = resolve(filePath); if (!existsSync(resolved)) { throw new Error(`Reference image not found: ${resolved}`); } const buffer = readFileSync(resolved); return { data: buffer.toString('base64'), mimeType: getMimeType(resolved) }; } async function main() { const { values, positionals } = parseArgs({ options: { prompt: { type: 'string', short: 'p' }, output: { type: 'string', short: 'o' }, 'aspect-ratio':{ type: 'string', short: 'a', default: '1:1' }, model: { type: 'string', short: 'm', default: DEFAULT_MODEL }, reference: { type: 'string', short: 'r', multiple: true }, help: { type: 'boolean', short: 'h', default: false } }, strict: true }); if (values.help) { console.log(` x-comics generate — Character-consistent image generation via Gemini Options: -p, --prompt Scene prompt (required) -o, --output Output file path (required) -a, --aspect-ratio Aspect ratio: ${VALID_ASPECT_RATIOS.join(', ')} (default: 1:1) -m, --model Gemini model (default: ${DEFAULT_MODEL}) -r, --reference Reference image path(s) — repeat for multiple characters -h, --help Show this help Examples: # Generate with character reference node generate.js -p "Character A walking through a rainy city" -o panel1.png -r char-a.png # Multiple character references node generate.js -p "Character A and B in conversation" -o panel2.png -r char-a.png -r char-b.png `); process.exit(0); } if (!values.prompt) { console.log(JSON.stringify({ success: false, error: 'Missing required argument: --prompt' })); process.exit(1); } if (!values.output) { console.log(JSON.stringify({ success: false, error: 'Missing required argument: --output' })); process.exit(1); } const aspectRatio = values['aspect-ratio']; if (!VALID_ASPECT_RATIOS.includes(aspectRatio)) { console.log(JSON.stringify({ success: false, error: `Invalid aspect ratio: ${aspectRatio}` })); process.exit(1); } const apiKey = process.env.GEMINI_API_KEY; if (!apiKey) { console.log(JSON.stringify({ success: false, error: 'GEMINI_API_KEY environment variable not set' })); process.exit(1); } try { const ai = new GoogleGenAI({ apiKey }); // Build contents array: reference images first, then text prompt const contentParts = []; const refs = values.reference || []; for (let i = 0; i < refs.length; i++) { const imgData = loadImageAsBase64(refs[i]); contentParts.push({ inlineData: { mimeType: imgData.mimeType, data: imgData.data } }); } // Add the text prompt last — Gemini reads images then follows text instructions contentParts.push({ text: values.prompt }); const config = { responseModalities: ['IMAGE'] }; if (aspectRatio) { config.imageConfig = { aspectRatio }; } const response = await ai.models.generateContent({ model: values.model, contents: contentParts, config }); const candidates = response.candidates; if (!candidates || candidates.length === 0) { console.log(JSON.stringify({ success: false, error: 'No candidates in response' })); process.exit(1); } const parts = candidates[0].content?.parts; if (!parts) { console.log(JSON.stringify({ success: false, error: 'No content parts in response' })); process.exit(1); } let imageData = null; for (const part of parts) { if ('inlineData' in part && part.inlineData) { imageData = part.inlineData; break; } } if (!imageData) { console.log(JSON.stringify({ success: false, error: 'No image data in response' })); process.exit(1); } // Save the image const outPath = resolve(values.output); const outDir = dirname(outPath); if (!existsSync(outDir)) { mkdirSync(outDir, { recursive: true }); } writeFileSync(outPath, Buffer.from(imageData.data, 'base64')); console.log(JSON.stringify({ success: true, filePath: outPath })); } catch (error) { const msg = error instanceof Error ? error.message : String(error); console.log(JSON.stringify({ success: false, error: `Gemini API error: ${msg}` })); process.exit(1); } } main(); -
package.json 343 B
{ "name": "x-comics", "version": "1.0.0", "type": "module", "main": "generate.js", "directories": { "doc": "docs" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { "@google/genai": "^1.44.0" } } -
polish.js 5.9 KB
#!/usr/bin/env node // x-writer/scripts/polish.js — Polish tweets via Author's Voice API // Zero npm dependencies. Uses Node.js built-in https module. // // Requires AV_API_KEY in env. Get one at https://authorsvoice.com. // // Usage: // node polish.js "raw tweet text" # polish and print // node polish.js "text1" "text2" "text3" # batch polish // node polish.js "text" --intensity full # full transformation // node polish.js "text" --json # structured output const https = require('https'); // ============ CONFIGURATION ============ if (!process.env.AV_API_KEY) { console.error('Error: AV_API_KEY environment variable is required.'); console.error('Get a key at https://authorsvoice.com and export it in your shell:'); console.error(' export AV_API_KEY="av_live_..."'); process.exit(1); } const CONFIG = { AV_API_KEY: process.env.AV_API_KEY, AV_BASE_URL: process.env.AV_BASE_URL || 'https://breewriter-app-5eifi.ondigitalocean.app/api/voice/mcp', CATEGORY: 'x', INTENSITY: 'moderate', MODE: 'rewrite', }; // ============ ARGUMENT PARSING ============ function parseArgs() { const args = process.argv.slice(2); const parsed = { texts: [], intensity: CONFIG.INTENSITY, json: false }; for (let i = 0; i < args.length; i++) { if (args[i] === '--intensity' && args[i + 1]) { parsed.intensity = args[++i]; continue; } if (args[i] === '--json') { parsed.json = true; continue; } if (!args[i].startsWith('--')) { parsed.texts.push(args[i]); } } return parsed; } // ============ AUTHOR'S VOICE API ============ function callAV(toolName, toolArgs) { const url = new URL(CONFIG.AV_BASE_URL); const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: toolName, arguments: toolArgs }, }); return new Promise((resolve, reject) => { const req = https.request({ hostname: url.hostname, port: url.port || 443, path: url.pathname, method: 'POST', headers: { 'Authorization': `Bearer ${CONFIG.AV_API_KEY}`, 'Content-Type': 'application/json', 'Accept': 'application/json, text/event-stream', 'Content-Length': Buffer.byteLength(body), }, }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { // Parse SSE response — find the data: line const lines = data.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const json = JSON.parse(line.slice(6)); const text = json.result?.content?.[0]?.text; if (text) { // text might be JSON string or plain text try { const parsed = JSON.parse(text); resolve(parsed.content || parsed.text || parsed.result || text); } catch { resolve(text); } return; } } } // Fallback: try parsing entire response as JSON (non-SSE) try { const json = JSON.parse(data); const text = json.result?.content?.[0]?.text; if (text) { try { resolve(JSON.parse(text).content || JSON.parse(text).text || text); } catch { resolve(text); } return; } } catch {} reject(new Error(`No content in response. Raw: ${data.substring(0, 300)}`)); } catch (err) { reject(new Error(`Parse error: ${err.message}\nRaw: ${data.substring(0, 300)}`)); } }); }); req.on('error', reject); req.setTimeout(30_000, () => { req.destroy(); reject(new Error('Request timeout (30s)')); }); req.write(body); req.end(); }); } async function applyVoice(text, intensity) { return callAV('apply_voice', { content: text, mode: CONFIG.MODE, category: CONFIG.CATEGORY, intensity: intensity, format: 'plaintext', }); } // ============ MAIN ============ async function main() { const parsed = parseArgs(); if (parsed.texts.length === 0) { console.log('Usage: node polish.js "tweet text" [options]'); console.log(''); console.log('Polishes tweet text in your voice via Author\'s Voice API.'); console.log('Scheduling/posting is OpenWriter-native (mcp__openwriter__schedule_post,'); console.log('post_to_x, manage_schedule) — not handled by this script.'); console.log(''); console.log('Options:'); console.log(' --intensity <level> light, moderate (default), full'); console.log(' --json Output as JSON'); console.log(''); console.log('Examples:'); console.log(' node polish.js "Society rewards conformity"'); console.log(' node polish.js "Tweet 1" "Tweet 2" "Tweet 3"'); console.log(' node polish.js "Raw take" --intensity full'); process.exit(0); } const results = []; for (const raw of parsed.texts) { if (!parsed.json) { console.log(`\n Original (${raw.length} chars):`); console.log(` "${raw}"`); } try { const polished = await applyVoice(raw, parsed.intensity); if (!parsed.json) { console.log(` Polished (${polished.length} chars):`); console.log(` "${polished}"`); if (polished.length > 280) { console.log(` !! ${polished.length} chars — over 280 limit`); } } results.push({ original: raw, polished, chars: polished.length }); } catch (err) { if (!parsed.json) console.error(` Error: ${err.message}`); results.push({ original: raw, error: err.message }); } } if (parsed.json) { console.log(JSON.stringify(results, null, 2)); } else if (parsed.texts.length === 1 && results[0]?.polished) { // Single tweet — print clean output for easy copy console.log(`\n${results[0].polished}`); } } main();
-
-
SKILL.md 9 KB
--- name: x-writer description: | Writing format, image generation, and pipeline for X (Twitter) content. Paragraph-based medium form, Fischerian Hooks, anti-performance writing. Progressive disclosure: article format scoring, thread/article images, comic strip generation, full brainstorm→polish→schedule pipeline. Built for use with OpenWriter. Use when: "/x-writer", "write for x", "x post format", "write this tweet", "format this post", "how should I write this", "format article", "evaluate article", "optimize article", "article image", "article cover", "thread images", "comic strip", "x comics", "comic panels", "generate panels", "polish tweet", "draft tweets", "write tweets", "write in my voice", "schedule tweet" metadata: author: travsteward version: "0.2.1" license: MIT --- # X Writer The writing format for X content. This skill defines HOW to write — not WHAT to write about. Apply these patterns to all tweets, threads, quote tweets, and replies. ## Core Philosophy **The line-by-line style is dead.** Broken up, one-sentence-per-line writing signals EFFORT. It says "I am trying to get your attention." People see through it. They turn off. The pithy fragment style is the format of engagement farmers, not thinkers. **We write in paragraphs.** Clean, flowing, natural paragraphs. Like a smart person talking to you, not a copywriter performing for you. The authority comes from the IDEAS, not the formatting tricks. ## The Fischerian Hook Named after Fischer King (@FischerKing64), who demonstrates the pattern naturally. **The first sentence of every paragraph is the hook.** It's a strong, declarative statement that pulls the reader in. Not a question. Not a teaser. A position. Examples from Fischer King: > "The worst part about people in white collar professions claiming they work '80-100 hours a week' is that they are all liars." > "Every living President is watching Trump and realizing he lost the opportunity to shape the future of the country." > "The most disturbing aspect of Gordon Ramsay's 'Kitchen Nightmares' is the revelation that so many restaurants are just microwaving your meal." The hook is a complete thought that makes you want the next sentence. The paragraph then builds, explains, or lands the point. The hook isn't bait — it's the thesis. **What a Fischerian Hook is NOT:** - "Here's what nobody tells you about X..." (teaser bait) - "I need to talk about something." (vague attention grab) - "This." followed by a screenshot (lazy engagement) - One-word sentences for "impact" (performance writing) ## Format: Medium Form **Optimal length: 4-5 paragraphs.** Range: 3-6. This is the sweet spot for extended thought without losing attention. - **3 paragraphs** — minimum for a real argument. Setup, development, landing. - **4-5 paragraphs** — optimal. Enough room to build a framework, give evidence, and close with impact. - **6 paragraphs** — maximum before you're writing an article. If you need more, make it a thread or an X Article. **Each paragraph is 2-4 sentences.** Not one-liners. Not walls of text. Natural paragraph length that breathes. **For threads:** each tweet is its own medium-form post (3-5 paragraphs). The thread isn't one long essay chopped up — it's a sequence of self-contained posts that build on each other. ## Anti-Performance Rules 1. **No single-sentence paragraphs for emphasis.** If a sentence is important, it lives inside a paragraph where the surrounding context makes it hit harder. 2. **No line breaks within paragraphs for dramatic effect.** A paragraph flows. If you need a new thought, start a new paragraph. 3. **No "Let me explain" or "Here's the thing" throat-clearing.** Start with the point. The Fischerian Hook eliminates all preamble. 4. **No emoji anchors.** No starting lines with emoji to create visual structure. The writing IS the structure. 5. **No rhetorical question chains.** "What if I told you...? What if everything you knew was wrong?" — this is performance, not writing. 6. **No trailing ellipsis for mystery.** "And the answer might surprise you..." — say the answer. ## Voice Characteristics - **Declarative, not questioning.** State positions confidently. "This is how it works" not "Have you ever wondered how it works?" - **Specific, not vague.** Names, numbers, studies, references. "Researchers butchered 30 deer with 60 handaxes" not "Studies show that..." - **Conversational but not casual.** Like talking to a smart friend at dinner, not like texting. No slang, no "lol", no "ngl." - **Variable sentence length.** Mix short declarative sentences with longer explanatory ones. The rhythm is natural speech, not metronome. - **Authority through knowledge, not tone.** Don't TELL them you're an authority. Demonstrate it by knowing things they don't. ## Thread Format When writing threads, each tweet follows the same medium-form paragraph rules: 1. **Tweet 1 (Hook tweet):** 3-5 paragraphs. First paragraph's first sentence is the thread hook — the reason someone stops scrolling. Include the primary image if there is one. 2. **Body tweets (2-N):** Each is a self-contained medium-form post. Each has its own Fischerian Hook. Each could stand alone as a tweet, but builds on the thread's argument. 3. **Close tweet:** Lands the framework. Not a call to action ("Follow for more!"). Not a summary. The final insight that makes the whole thread click. **Thread images:** Each body tweet can have a progression image or supporting visual. Images support the argument — they don't replace it. ## Voice: Always Apply Every piece of X content MUST be run through `/authors-voice` (or `/voice-apply`) before it's considered done. The x-writer skill handles format and structure. The voice skill handles tone, word choice, and eliminating AI tells. > **Preferred path: OpenWriter's built-in Author's Voice "Enhance" plugin** (API-backed, full corpus RAG) — beats local Apply-minion briefs for X rewrites every time. **AI tells to eliminate:** - "Furthermore", "moreover", "additionally" — conjunction stacking that no human uses in casual prose - "It's worth noting that" — throat-clearing - "This is particularly important because" — explaining why something matters instead of just stating it - "In essence", "fundamentally", "at its core" — filler abstractions - "Arguably", "undeniably", "unquestionably" — hedging or over-asserting - "Landscape", "paradigm", "ecosystem", "leveraging" — corporate AI vocabulary - "Delve", "tapestry", "nuanced" — the most flagged AI words on the internet - Balanced "on one hand / on the other hand" structures — real people take positions - Ending with an inspirational reframe or call to reflection — just end **The workflow (single pipeline — `/x-writer` does all three):** 1. **Format:** Draft content applying x-writer format rules (Fischerian hooks, paragraph structure, medium form) 2. **Polish:** Pull from the top 10 advertising practitioners in history. Score each rewrite 0-100. Don't stop until you hit 90%. 3. **Voice:** Run `/authors-voice` (or `/voice-apply`) to rewrite in your authenticated voice + anti-AI detection 4. **Final check:** If any sentence sounds like it came from ChatGPT, rewrite it ## Progressive Disclosure — Sub-Docs Load on demand based on the task: - **OpenWriter mechanics** — `tweetContext` / `articleContext` metadata, `content_type` (`tweet` / `reply` / `quote` / `article`), thread HRs, image handling, paragraph spacing, parent-tweet workflow → [`docs/openwriter-mechanics.md`](docs/openwriter-mechanics.md) - **Article format** — evaluate (7-dimension score) or optimize X Articles. 10 rules for scroll-stop engagement → [`docs/article-format.md`](docs/article-format.md) - **Images** — generate covers + thread images. 5 styles (Dark Editorial, Cinematic Realism, Abstract/Conceptual, Raw/Documentary, Dark Infographic), decision tree, character references → [`docs/images.md`](docs/images.md) + [`docs/images/`](docs/images/) (styles, characters, workflow) - **Comics** — character-consistent comic strip panels for threads. 4 comic-specific styles, full pipeline → [`docs/comics.md`](docs/comics.md) + [`docs/comics/`](docs/comics/) (styles, characters, workflow) - **Pipeline** — full brainstorm → polish (Author's Voice) workflow; scheduling/posting hands off to OpenWriter native (`mcp__openwriter__schedule_post`, `post_to_x`) → [`docs/pipeline.md`](docs/pipeline.md) ## Scripts - `scripts/polish.js` — call Author's Voice `apply_voice` for tweet polishing - `scripts/generate-image.js` — Gemini image generation with reference image support - `scripts/characters/` — character reference PNGs for consistent multi-panel/thread image sets (user-supplied) ## What This Skill Does NOT Cover - **What to write about** — topic strategy is outside this skill's scope - **Posting/scheduling** — OpenWriter native: `mcp__openwriter__schedule_post`, `post_to_x`, `manage_schedule` - **Scoring/prediction** — see grok-score skill - **Reading tweets** — see x-reader skill ($0 fxtwitter reads) - **Direct X API access** — see x-api skill (post-only per cost rule, when not going through OpenWriter)
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.