# Post To X

> 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.

- Skill: `yzhao062/post-to-x` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add yzhao062/post-to-x`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yzhao062/post-to-x/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: yzhao062 (https://skillmd.com/u/yzhao062)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/yzhao062/post-to-x

---


# 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`

