A company posts its news on X and nowhere else. You want a story out of it. What you get instead is a fight with the platform: web_fetch returns an empty body every time, the actual mechanics are buried in post 3/ of a numbered thread, and the one link that matters — the blog post or docs page with the real detail — is hidden behind a t.co shortlink in the last post.
x-post-story handles that sourcing problem, then writes the story. Drop an x.com URL in chat and it renders the page through the Chrome connector, stitches the full thread out of the DOM, follows the outbound link to the fuller source, and drafts a 4–5 graf wire-service story under 200 words.
The writing rules are close to distro-release-rewrite. The sourcing is where the time goes, and it's where this skill earns its keep. It also refuses to invent the CEO quote that an X thread never gives you — a gap that is otherwise exactly where fabrication creeps in.
What you'll need
- Claude in Chrome (the browser connector). X is JavaScript-rendered; there is no fetch path. If Chrome isn't available the skill says so and asks you to paste the thread text.
- Distro Publisher MCP — only if you ask it to file the draft. Drafting in chat needs nothing but Chrome.
What it does
- Gets the whole thread, not just post 1/. Queries every
<article>on the page and filters to the original poster's handle. This is what stops the classic failure whereget_page_textstarts handing back unrelated promoted posts and a Robinhood ad ends up quoted in your Privy story. - Checks the date before writing a word. X shows a post from December identically to one from this morning. If the post is more than about a week old it stops and asks.
- Follows the link out and makes it the primary source. Three tweets give you three facts, and a story built on three facts reads like marketing copy with the verbs changed. The linked docs page is where the mechanics, the limits and the caveats live.
- Reports rather than relays. One middle graf the company didn't write — who else is in this space, what's conspicuously missing, whether this is actually new.
- Won't manufacture a quote. Real quote from the linked page, or a line of company copy that genuinely says something, or no quote at all. Never words put in a named executive's mouth.
- House style built in. Sentence-case headline around 60 characters, no em dashes, hyphenation rules, byline as the writer's name alone, and the
HOW AI WAS USED IN THE PRODUCTION OF THIS PIECEnote with the exact label. - Ends by telling you what it's unsure about. A short list of judgment calls — a source it couldn't reach, numbers that conflicted between thread and blog, a connection it suspected and left out.
- Stops at the draft. Filing to a newslode is a separate step you have to ask for.
Install the skill
Ask Claude:
"Please fetch the x-post-story skill from Distro Skills and give me a
.skillfile I can install."
Claude will pull this story, extract the SKILL.md from the fenced block below, and package it as x-post-story.skill. The file will appear in your chat with a Save skill button — click it to install directly. That registers the skill with your Claude app so it shows up in your skills list and triggers on matching requests.
Requires the Distro Reader or Distro Publisher MCP connected so Claude can pull the story.
Claude Code users: you have a direct path — ask Claude to drop the unzipped folder into ~/.claude/skills/x-post-story/ and restart the session. No packaging step needed.
No Distro MCP connected? Copy the SKILL.md contents from the fenced block below into a file called SKILL.md inside a folder called x-post-story, zip it (folder as zip root), and upload through Settings → Capabilities → Skills → Upload skill.
The skill source
This is the canonical SKILL.md:
---
name: x-post-story
description: >-
Use this skill when the user hands over an X (Twitter) URL from a company, project or executive
account and wants it turned into a news story. Triggers on "use the x-post-story skill on this,"
"write this up," "do a story on this one," "turn this thread into a story," or any request pairing
an x.com or twitter.com status URL with a story-writing goal, including when the user just drops
an X link with no instruction at all, since on a news desk that means "write it."
Handles the mechanics X makes annoying, namely that web_fetch returns nothing on x.com because the
page is JavaScript-rendered, that the news is usually spread across a numbered thread that has to
be stitched out of the DOM, and that the real source is often a blog or docs page linked from the
last post. Drafts a tight 4-5 graf wire-service story in chat for sign-off. Do NOT use for press
releases (use distro-release-rewrite), link-reposts (use distro-link-post), or podcast episodes
(use lodecast-publishing).
---
# X Post Story
Turn an official X post or thread from a company, project or executive into a short, original news story: 4–5 grafs, under 200 words, wire-service register.
Producing that draft in chat is the whole job. Filing it to a Distro newslode is a **separate, optional step** that only happens if the user asks (see "Filing" at the bottom).
## Why this is its own skill
The writing is nearly identical to distro-release-rewrite. The *sourcing* is not, and that's where the time goes:
- **X won't fetch.** `web_fetch` on an x.com URL returns an empty body. Every time. You have to render the page.
- **The thread hides the news.** Post 1/ is the hook. The mechanics — how it works, which chains, what the limits are — live in 2/ and 3/. A story written from 1/ alone has no reporting in it.
- **The thread is often just a pointer.** The last post frequently links to a blog post, docs page or formal release. *That* is the real source. The thread is the announcement peg.
- **There's no executive quote.** Press releases hand you one. Threads almost never do.
Everything downstream — voice, structure, hyphenation, byline, AI-disclosure note — follows Distro house rules, restated here so this skill stands alone.
---
## Step 1: Get the thread out of the DOM
Do not call `web_fetch` on the X URL. It returns nothing and you've burned a round trip.
Do not try mirror or proxy sites either (fxtwitter, vxtwitter, nitter, r.jina.ai, oembed endpoints). They either return empty or hang for three minutes, and routing around a blocked fetch isn't something to do anyway. Chrome is the supported path — use it.
**The reliable sequence:**
1. `navigate` to the status URL. Strip any `?s=20` tracking suffix.
2. Extract the whole thread with `javascript_tool` — query every `<article>` element on the page and filter to the original poster's handle.
Something along these lines, adapted as needed:
```js
[...document.querySelectorAll('article')]
.map(a => a.innerText)
.filter(t => t.includes('@privy_io')) // ← the OP's handle
```
**Why this and not the obvious approaches:**
- `get_page_text` returns only the root post's `<article>`. Scroll and call it again and it starts handing back *unrelated promoted posts* from the timeline — a Robinhood post in the middle of a Privy story, a Jeremy Allaire post in the middle of an Apify one. If you find yourself quoting a company that has nothing to do with your story, this is why. Filtering on the OP's handle is what prevents it.
- Reading the thread off screenshots works but is slow and lossy, and truncated posts stay truncated behind a "Show more" you then have to click.
Also grab, while you're in there:
- **The timestamp.** You need the day for the lede, and you need it for the staleness check below.
- **The account bio**, visible in the "Relevant people" panel. It usually gives you a serviceable company descriptor.
**Not every X post is a thread.** Plenty of announcements are one long standalone post with its own internal bullets. If there's no 2/, don't go hunting for one — the replies below it are strangers arguing about the token price, not your story.
If the browser tools aren't available, say so and ask the user to paste the thread text.
### Check the date before you write a word
X presents a post from December identically to a post from this morning. A stale link is easy for anyone to grab and embarrassing to write up as news.
Read the timestamp. If the post is more than about a week old, stop and tell the user before drafting: they may have grabbed an old link, or they may know something you don't and want it written anyway. Either way it's their call, and it costs one sentence to ask.
If you're unsure whether a rendered date is real, the post ID encodes its creation time — you can decode the snowflake to confirm.
## Step 2: Follow the link out
Scan the thread for an outbound link: a blog post, docs page, GitHub repo, formal release. Companies routinely tweet a three-post summary and put the actual substance behind a link in the last post.
**Finding the real link is fiddly.** The `t.co` shortlink visible in the page title is often the *image* attached to post 1, not the card you want. Get the actual hrefs with `read_page` using `filter: "interactive"`, or pull them out with `javascript_tool`.
**Fetching it: go through Chrome.** `web_fetch` frequently times out (180 seconds, silently) on exactly the kinds of pages you'll be fetching — Mintlify docs sites, company engineering blogs. Don't spend three minutes finding that out. `navigate` to the link and `get_page_text` it; the tab is usually already open.
**Then make it your primary source.** Write from the fuller page, using the thread as the news peg and date anchor.
This is the rule that most improves the story. Three tweets give you three facts, and a story built on three facts reads like the company's marketing copy with the verbs changed. The linked page is where the mechanics, the limits, the caveats and sometimes a real quote live. In testing, the Privy docs page is what produced the 402-to-signed-proof flow, the mandatory user-delegation step, the per-session spend caps and the supported AWS regions — none of which were in the thread at all.
If the linked page is paywalled, gated behind a wallet sign-in, or just a landing page with no detail, fall back to the thread and say nothing about it. Don't pad. Don't sign in to anything.
## Step 3: Report it, don't relay it
The single thing that separates a story from a repost is a graf the company didn't write.
You are not confined to the announcement. Prior coverage, the public record, and well-established facts about the company are all fair game, and using them is expected — attribute them, and prefer things you can point at. What's off-limits is *invention*: facts with no source, quotes nobody said, significance you assert on the company's behalf.
Concretely, ask what the announcement is *doing* in the world:
- Who else is already in this space, and is this a response to them?
- Is the company positioning against a named rival? (In testing, the AWS AgentCore connector type is literally named `StripePrivy`, which points at a Stripe-versus-Coinbase distribution fight inside a single Amazon product — an angle that appears nowhere in Privy's thread and is much more interesting than the thread.)
- Is this actually new, or a rebrand of something that already shipped?
- What's conspicuously missing? Pricing, dates, a named customer, a security model?
One graf of this is usually enough, and it belongs in the middle of the story. Don't force it — if the announcement is genuinely just a feature launch, say so plainly and move on.
## Step 4: Draft
### Length and shape
4–5 grafs, under 200 words in the body, excluding the AI-disclosure note. Inverted pyramid: lede → mechanics with attribution → context → the company's pitch → caveats and availability.
Hold the line on length. Without it, this kind of story drifts to 500 words of company copy.
### Headline
Sentence case. Concrete and specific — name the company and the news in plain English. Avoid colons as crutches. Roughly 60 characters.
Example: `Privy wallets let AI agents on AWS pay for APIs in USDC`
### Description / preview
Roughly 100–120 characters. Lead with the company name and a colon, then one sentence on what's new.
Example: `Privy: Agents built on AWS Bedrock AgentCore can now autonomously pay for APIs, MCP tools and web content in USDC.`
### Lede
Company + brief descriptor + "on [day] said/announced/unveiled" + the news, in one sentence.
Assume readers don't know the company. Always give it an epithet: "Privy, a crypto wallet infrastructure provider," "Apify, a marketplace of web scraping and automation tools." Pull it from the account bio or the linked page's boilerplate. Prefer a descriptor matching the actual architecture over the company's marketing framing — a centralized onramp is a "platform," not a "network."
Day of the week if the post is from the current week ("on Friday"); a date if it's older.
Company self-reported scale figures ("120M+ accounts") are unaudited marketing claims. Leave them out of the lede, or attribute them explicitly.
### Attribution
Not "according to a press release." Use what's true:
- Thread only: "...the company said in a thread posted on X."
- Thread plus linked page: "...according to a company blog post," or "...according to a Privy documentation page," with the thread cited for the announcement.
Put the hyperlink in **graf 2**, on the first concrete factual claim from the source. Keep the lede clean of links. Once the source is established in graf 2, later "the company said" is implicitly the same source — don't re-attribute every graf.
**If the thread and the linked page disagree** — and they do, surprisingly often, on numbers — go with the fuller, more formal source, and flag the discrepancy to the user at the end. Don't silently pick one.
### Quotes — don't manufacture one
Press releases hand you a CEO quote. X threads don't, and the reflex to fill that empty slot is exactly how fabrication happens.
In order of preference:
1. **A real quote from the linked blog post or docs**, attributed to the named person and title. Best case.
2. **A line of company copy that actually says something**, quoted and attributed to the company, not a person: `Apify tracks that spending off-chain, which it said "introduces a trust assumption in Apify's bookkeeping."` Note what makes that usable — it's a self-flagged caveat, not a slogan. "Now your agent can buy what it needs to finish the job" is a slogan. Leave slogans out.
3. **No quote at all.** Entirely acceptable, and usually right. A tight four-graf story with no quote beats a five-graf story padded with marketing language dressed up as news.
Never attribute words to a named executive unless you can see those exact words in the source.
For a real multi-sentence quote, split the attribution after the first sentence: `"First sentence," said Jane Doe, chief executive. "Second sentence. Third."`
### Closing graf
The practical fine print: custody, revocation, spend limits, geographic rollout, pricing, identity verification, test-network-versus-mainnet.
Above all, **what is actually shipping**. Companies announce things on X that are aspirational, in beta, or documentation-only. "The recipe is live now in our docs" means developers can integrate it — not that it ships to users. Write what's true and let the closing graf carry the distinction.
Keep it factual. Don't end on a flourish or an echo of the sales pitch.
## Voice
Overrides anything in brads-voice that conflicts.
- **Reporter, not stenographer.** Pull the facts; drop the marketing voice. Professional, neutral, informed. Skeptical but curious.
- **No em dashes.** Readers read them as an AI tell. Use commas, periods, parentheticals.
- **Spaced en dashes, rarely.** ` – ` (space, en dash, space) when a dash genuinely fits. Once or twice per piece at most. Never `—`, never `--`.
- **Hyphenation:** on-chain, off-chain, self-custodial, co-founder.
- **Attribute the company's claims to the company.** Novelty, importance, "first of its kind" are their assertions, not your findings.
- Active voice. Concrete verbs. Short sentences.
- **Don't** editorialize about whether the thing is good or transformative. **Don't** use "leverage," "utilize," "empower," "unlock" unless quoting. **Don't** pad with "in today's rapidly evolving landscape." **Don't** use bullets in the body.
## Byline
Default: **`[User's name]`** (for Brad, `Bradley Keoun`). The byline is the writer's name alone. Do **not** append "assisted by Claude" or any other AI-credit suffix. Transparency about AI's role is carried by the disclosure note at the bottom of the piece, which is where it belongs. If the user's name isn't established in the conversation, ask, don't assume.
## AI-disclosure note
At the very bottom, after a horizontal rule, in italics. Two format rules that are easy to get wrong:
- **The label is `HOW AI WAS USED IN THE PRODUCTION OF THIS PIECE`**, not "EDITOR'S NOTE."
- **No surrounding parentheses**, and **write it in the user's first person** ("I drafted...", "I checked..."). The note describes the tools the writer used, so it should sound like them describing their own workflow.
Describe the workflow that actually produced the piece. When drafted but not filed:
> *HOW AI WAS USED IN THE PRODUCTION OF THIS PIECE: I drafted this story on Claude Desktop using my custom x-post-story skill, which read the announcement on X through the Claude in Chrome browser connector. I checked every quote, figure and background fact against original sources before publication.*
If it's later filed, extend it to name the Distro Publisher MCP connector and the newslode, plus the DistroVerse built-in (non-AI) editing interface if the writer edited there.
Adapt the wording to what actually happened, but keep the label exact. This follows `distro-house-style` rule 6, which governs any Distro deliverable.
## Step 5: Stop, and tell the editor what you're unsure about
Show the user the headline, description, byline and body in chat. That's the deliverable. Don't ask which newslode to file to, and don't file anything.
Then flag your judgment calls in a short list underneath. This is the part that earns trust, so be specific: a quote pulled from company copy rather than a person, a linked source you couldn't reach, numbers that conflicted between thread and blog, a claim you couldn't verify, a connection you suspected but left out. The user is the editor. Give them what an editor would want to know before their name goes on it.
---
## Filing (only if the user asks)
1. **Resolve the newslode to a publication ID.** Call `distro_content_list` with `source: "publication"` and `limit: 50`. Each article carries a `publication` object with `id` and name. Match case-insensitively and fuzzily. **DeAI News is publication 109**, where most of these land. If the user gives a numeric ID, skip the lookup. A brand-new newslode with no articles won't appear — ask for the ID.
2. **File as a draft** with `distro_content_publish`:
```
distro_content_publish({
publicationId: <resolved ID>,
title: <sentence-case headline>,
preview: <~120-char description with the company-name prefix>,
content: <HTML body>,
author: "[User's name]",
status: "draft",
contentFormat: "html"
})
```
Leave `sourceName` and `sourceUrl` **empty**. This is an original story built from the announcement, not a link post. The X link belongs in the body.
3. **HTML, not markdown.** `distro_content_update` only handles HTML cleanly, so using it at publish time keeps the editing path consistent. Wrap each graf in `<p>`. After the last graf, `<hr>`, then the AI-disclosure note in `<em>` inside its own `<p>`. Links carry the same anchor attributes the Distro editor produces:
```html
<a target="_blank" rel="noopener noreferrer nofollow" class="tiptap-link" href="[URL]">thread posted on X</a>
```
4. **After filing**, report the content ID, note it's a draft, and offer the shortcut: "Want me to flip it to published, or do you want to review and edit in the Distro editor first?" If they say publish, call `distro_content_update` with `status: "published"`.
### Known quirks
- `distro_content_update` requires HTML. Send revisions as HTML to preserve paragraph breaks and links.
- `distro_content_delete` has a known server-side bug. If a draft needs to go, tell the user to delete it in the Distro UI.
When to use it
- A company, project or executive posts news on X and you want a story out of it
- A numbered thread announcing a product, integration or funding round
- Someone drops an x.com link in chat with no instruction — on a news desk that means "write it"
When not to use it
- A press release →
distro-release-rewrite - A company blog post →
distro-blog-post - A plain link repost with a hot take →
distro-link-post - A research report or analyst note →
distro-report-writeup - A podcast or video episode →
lodecast-publishing - Writing a promo tweet for a story you already published →
distro-story-tweet
Feedback
This is v1.0. Edge cases or suggestions → reply or ping Bradley Keoun.