Post to X
Compose and post announcement tweets from this repo via scripts/post_to_x.py. The account holder has X Premium, so single long posts (up to 25,000 chars) are the default; threads are opt-in.
When to use
- User asks to post, tweet, or announce something on X/Twitter
- New open-source release, paper, grant, award, talk, hiring, student news
- Follow-ups to previous announcements
Do not use for: drafting LinkedIn posts, email newsletters, or non-public announcements.
Prerequisites
Two gates. Drafting and previewing require less than posting; do not block the user on posting-only items when they just want to draft.
Gate 1: drafting and previewing (steps 1 to 4 of the workflow)
~/miniforge3/envs/py312/python.exe has tweepy and python-dotenv installed (pip install -r scripts/requirements-post.txt). tweepy is imported at module load even for --dry-run, so it must be present.
- Source data available:
data/open-source.json, data/publications.json, or user-provided text.
If Gate 1 fails, stop and report.
Gate 2: real post (step 5)
.env at repo root contains X_API_KEY, X_API_KEY_SECRET, X_ACCESS_TOKEN, X_ACCESS_TOKEN_SECRET. Do not ask the user to paste them into chat; they are gitignored by design.
- X Developer account has API credit balance (check at
console.x.com → Billing).
- User has reviewed and approved the dry-run preview for this specific post.
If Gate 2 fails, stop and report before posting. Drafting and previewing may continue.
Workflow
1. Identify source → open-source.json / publications.json / user-provided
2. Draft to file → scripts/drafts/<slug>.md (use references/draft-patterns.md)
For data/open-source.json entries, seed with:
python scripts/post_to_x.py --project <name>
That writes scripts/drafts/<slug>.md with TODO markers and exits.
3. Preview dry-run → python scripts/post_to_x.py --dry-run --draft <path>
4. Iterate → show preview, accept user edits, re-preview
5. Post → python scripts/post_to_x.py --yes --draft <path>
Attach images with --media <path> (repeatable, up to 4).
In thread mode, media attaches to the first tweet only.
6. Report URL → return https://x.com/i/web/status/<id>
Always run step 3 before step 5. No exceptions. --project seeds a draft but never posts directly; the script refuses to post any draft that still contains <!-- TODO markers.
Hard rules
- Lead with the one-line value prop that answers "what is different now?" For a release, that is the single biggest change, not a list of features. If the user says the core is X, do not bury X under other points.
- End with 3 to 5 hashtags selected from
references/hashtag-sets.md. Never more than 5; algorithm penalizes stuffing.
- Single long post is the default (Premium). Only split into a thread when the user explicitly asks, or when content has 3+ distinct beats that read awkwardly as one paragraph.
- Apply CLAUDE.md writing rules:
- No em dashes (
—) or en dashes (–) as casual sentence punctuation. Use commas, colons, semicolons, or parentheses instead.
- No banned words from
AGENTS.md / CLAUDE.md Writing Defaults.
- Prefer full forms (
it is) over contractions where natural.
- Avoid Unicode
U+202F.
- Verify every factual claim against the source JSON or the user. Star counts, download counts, version numbers, coauthor names, venue dates: do not fabricate or round up.
- Cost awareness: each post with a URL is $0.20, without URL is $0.015. Show total in dry-run output. Warn the user if a thread pushes above $1 for a single announcement.
Cost reference
| Scenario |
Cost |
| Single long post, no URL |
$0.015 |
| Single long post, with URL |
$0.20 |
| 3-part thread, all with URLs |
$0.60 |
| 5-part thread, all with URLs |
$1.00 |
Common mistakes (from observed failures)
| Mistake |
Fix |
| Leading with a side feature (e.g., "multi-modal") when the core change is elsewhere (e.g., "agentic") |
Ask the user what the core change is before drafting, do not infer from JSON alone |
| Drafting without hashtags |
Always append 3 to 5 from references/hashtag-sets.md |
Em dash slips into draft (—) |
Replace with : or , before preview; the guard hook will block Writes with banned style anyway |
| Defaulting to thread mode |
Premium user gets single long post by default; --thread only on explicit request |
| Posting without dry-run confirmation |
Always run --dry-run first, get user yes/no, then run with --yes |
| Fabricated social proof ("300+ companies") |
Only cite numbers from data/open-source.json, data/publications.json, or user-stated facts |
Quick reference
- Draft patterns (release, paper, grant, talk):
skills/post-to-x/references/draft-patterns.md
- Hashtag sets by topic and announcement type:
skills/post-to-x/references/hashtag-sets.md
- Known-good example posted 2026-04-17:
scripts/drafts/pyod-3.0.md (PyOD 3 release)
- Script source:
scripts/post_to_x.py
- Script flags: run with
--help
1---2name: post-to-x3description: Use when announcing a release, paper, grant, talk, award, or project update on X (Twitter) from this repo. Covers drafting, style compliance, cost preview, and posting via scripts/post_to_x.py.4---56# Post to X78Compose and post announcement tweets from this repo via `scripts/post_to_x.py`. The account holder has X Premium, so single long posts (up to 25,000 chars) are the default; threads are opt-in.910## When to use1112- User asks to post, tweet, or announce something on X/Twitter13- New open-source release, paper, grant, award, talk, hiring, student news14- Follow-ups to previous announcements1516Do **not** use for: drafting LinkedIn posts, email newsletters, or non-public announcements.1718## Prerequisites1920Two gates. Drafting and previewing require less than posting; do not block the user on posting-only items when they just want to draft.2122**Gate 1: drafting and previewing** (steps 1 to 4 of the workflow)23- `~/miniforge3/envs/py312/python.exe` has `tweepy` and `python-dotenv` installed (`pip install -r scripts/requirements-post.txt`). `tweepy` is imported at module load even for `--dry-run`, so it must be present.24- Source data available: `data/open-source.json`, `data/publications.json`, or user-provided text.2526If Gate 1 fails, stop and report.2728**Gate 2: real post** (step 5)29- `.env` at repo root contains `X_API_KEY`, `X_API_KEY_SECRET`, `X_ACCESS_TOKEN`, `X_ACCESS_TOKEN_SECRET`. Do not ask the user to paste them into chat; they are gitignored by design.30- X Developer account has API credit balance (check at `console.x.com` → Billing).31- User has reviewed and approved the dry-run preview for this specific post.3233If Gate 2 fails, stop and report **before posting**. Drafting and previewing may continue.3435## Workflow3637```381. Identify source → open-source.json / publications.json / user-provided392. Draft to file → scripts/drafts/<slug>.md (use references/draft-patterns.md)40 For data/open-source.json entries, seed with:41 python scripts/post_to_x.py --project <name>42 That writes scripts/drafts/<slug>.md with TODO markers and exits.433. Preview dry-run → python scripts/post_to_x.py --dry-run --draft <path>444. Iterate → show preview, accept user edits, re-preview455. Post → python scripts/post_to_x.py --yes --draft <path>46 Attach images with --media <path> (repeatable, up to 4).47 In thread mode, media attaches to the first tweet only.486. Report URL → return https://x.com/i/web/status/<id>49```5051Always run step 3 before step 5. No exceptions. `--project` seeds a draft but never posts directly; the script refuses to post any draft that still contains `<!-- TODO` markers.5253## Hard rules5455- **Lead with the one-line value prop** that answers "what is different now?" For a release, that is the single biggest change, not a list of features. If the user says the core is X, do not bury X under other points.56- **End with 3 to 5 hashtags** selected from `references/hashtag-sets.md`. Never more than 5; algorithm penalizes stuffing.57- **Single long post is the default** (Premium). Only split into a thread when the user explicitly asks, or when content has 3+ distinct beats that read awkwardly as one paragraph.58- **Apply CLAUDE.md writing rules**:59 - No em dashes (`—`) or en dashes (`–`) as casual sentence punctuation. Use commas, colons, semicolons, or parentheses instead.60 - No banned words from `AGENTS.md` / `CLAUDE.md` Writing Defaults.61 - Prefer full forms (`it is`) over contractions where natural.62 - Avoid Unicode `U+202F`.63- **Verify every factual claim** against the source JSON or the user. Star counts, download counts, version numbers, coauthor names, venue dates: do not fabricate or round up.64- **Cost awareness**: each post with a URL is $0.20, without URL is $0.015. Show total in dry-run output. Warn the user if a thread pushes above $1 for a single announcement.6566## Cost reference6768| Scenario | Cost |69|---|---|70| Single long post, no URL | $0.015 |71| Single long post, with URL | $0.20 |72| 3-part thread, all with URLs | $0.60 |73| 5-part thread, all with URLs | $1.00 |7475## Common mistakes (from observed failures)7677| Mistake | Fix |78|---|---|79| Leading with a side feature (e.g., "multi-modal") when the core change is elsewhere (e.g., "agentic") | Ask the user what the core change is before drafting, do not infer from JSON alone |80| Drafting without hashtags | Always append 3 to 5 from `references/hashtag-sets.md` |81| Em dash slips into draft (`—`) | Replace with `:` or `,` before preview; the guard hook will block Writes with banned style anyway |82| Defaulting to thread mode | Premium user gets single long post by default; `--thread` only on explicit request |83| Posting without dry-run confirmation | Always run `--dry-run` first, get user yes/no, then run with `--yes` |84| Fabricated social proof ("300+ companies") | Only cite numbers from `data/open-source.json`, `data/publications.json`, or user-stated facts |8586## Quick reference8788- Draft patterns (release, paper, grant, talk): `skills/post-to-x/references/draft-patterns.md`89- Hashtag sets by topic and announcement type: `skills/post-to-x/references/hashtag-sets.md`90- Known-good example posted 2026-04-17: `scripts/drafts/pyod-3.0.md` (PyOD 3 release)91- Script source: `scripts/post_to_x.py`92- Script flags: run with `--help`