Autonomous X/Twitter posting pipeline for your AI agent. No API key, no paid scrapers: researches your niche, writes in your voice, publishes through a real browser, and verifies its own posts.
# Add to your Claude Code skills
git clone https://github.com/vedantdhande04/tweetytweetsSee how tweetytweets compares with popular alternatives.
tweetytweets is an open-source ai agents skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by vedantdhande04. Autonomous X/Twitter posting pipeline for your AI agent. No API key, no paid scrapers: researches your niche, writes in your voice, publishes through a real browser, and verifies its own posts. It has 51 GitHub stars.
tweetytweets's catalog security scan is still queued. You can run an instant dependency and prompt-injection check now with the "Scan for vulnerabilities" button above.
Clone the repository with "git clone https://github.com/vedantdhande04/tweetytweets" and add it to your Claude Code skills directory (see the Installation section above). tweetytweets ships a SKILL.md manifest, so compatible agents can discover and load it automatically.
tweetytweets is primarily written in Python. It is open-source under vedantdhande04 on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other AI Agents skills you can browse and compare side by side. Open the AI Agents category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh tweetytweets against similar tools.
No comments yet. Be the first to share your thoughts!
⚠️ Third-Party Software Notice
This skill is third-party open-source software developed and hosted independently on GitHub. SkillsLLM is an informational directory and does not control or maintain the underlying repository.
Any security checks, ratings, or warnings displayed by SkillsLLM are automated and limited in scope. They do not constitute a security certification or guarantee that the software is safe, error-free, or free from malicious code, vulnerabilities, compromised dependencies, or prompt-injection risks.
Review the source code, permissions, dependencies, and configuration before installing or running any third-party skill. Use is at your own risk. To the maximum extent permitted by applicable law, SkillsLLM is not liable for losses arising from third-party software.
The deep catalog scan for this skill is still queued. Run an instant dependency check now instead.
An autonomous X/Twitter posting pipeline: it finds the day's material itself, writes posts in your voice, publishes them through a real logged-in browser, and proves each one actually went live.
No X API key. No developer account. No paid scraper. One Python dependency.
research -> plan -> schedule -> publish -> verify
Nothing here posts anything until you finish Step 0 and explicitly turn the schedule on. Do not skip the interview - the whole point is to sound like the user, not like a content bot.
Run this as a conversation. Ask the questions, wait for real answers, then write
them into config.json. Do not invent answers, and do not proceed on defaults
the user never saw.
This is the most important step. Ask, in these words or close to them:
"Give me 5-20 of your best-performing posts — ideally ones with 100k+ impressions, or simply the ones you're proudest of. Paste them raw, include the line breaks, and separate each post with a line of
---. If you have them with impression counts, include those too.I'm not copying them. I'm measuring how you actually write: how you open, how long you go, whether you use numbers or questions, what you talk about. Without this, anything I write will sound like a generic AI account."
Also ask, if they are willing:
"Name 2-3 accounts in your space whose posts you admire. Paste 3-5 of their posts too — I'll treat those as structure references, never as voice."
Then:
scratch/my_top_tweets.txt (one post per block,
--- between them).python scripts/voice_profile.py --in scratch/my_top_tweets.txt.references/voice-profile.local.md (git-ignored) containing:
Every draft from now on is written against that file. If it is missing, stop and ask for the posts again rather than guessing at a voice.
| Ask | Why it matters | Goes to |
|---|---|---|
| "What's your handle?" | the account guard refuses to post as anyone else | handle |
| "Is this account on X Premium, or free? (Not sure is fine.)" | The ceiling decides what content is possible and it differs per account — free caps at 280, Premium does not. Ask, then verify by measuring: python scripts/post.py measure --save. Never hardcode a number. |
premium, max_chars, max_chars_verified |
| "What do you post about, in your own words?" | drives research queries and topics | x_queries, subreddits, feeds |
| "What timezone are you in, and when do you want to post?" | slots are local time | timezone, slots |
| "How many posts a day?" | more is not better — a second post in the same feed only counts 62% | slots |
| "Do you want a daily news roundup post?" | the one repeating format; gets its own counter | ai_update_* |
| "Anything you never want to say or do?" | bait, politics, emoji, DMs — record it in the voice profile | do-not list |
| "Do you use X on your phone as well?" | replies from mobile raise the post count and will confuse a naive verifier | note it |
cp config.example.json config.json # then fill it from the interview
pip install -r requirements.txt
python scripts/doctor.py # fix anything MISSING before continuing
python scripts/doctor.py --live # network + browser check
python scripts/browser.py launch # starts the automation browser
# -> sign in to x.com IN THAT WINDOW. Use "Email or username", not
# "Continue with Google", which can silently create a SECOND account.
python scripts/post.py check # must print your handle
python scripts/post.py measure --save # measures THIS account's ceiling and stores it
python scripts/post.py limits # confirm: shows the limit + whether it's verified
Do one supervised post before scheduling anything:
python scripts/post.py compose --text "your first real post" --dry-run
# look at shots/compose.png, confirm the text and the account
python scripts/post.py post --text-file scratch/first.txt
python scripts/post.py shot --what profile # then LOOK at shots/profile.png
Only after a real post has gone out and been verified should you set up the schedule (see README.md → Scheduling).
These are not style preferences. Breaking them gets posts ignored, or the account actioned.
python scripts/post.py measure --save — it types into the composer and
reads the Post button's state; it never clicks Post, so it cannot publishmax_chars + premium + max_chars_verified into config.json
post.py limits reports what is configured and whether it is verified;
settings.limit() returns verified: false when it is not, and both due.py
and doctor.py will warn rather than quietly assume. Until it is measured the
tool falls back to the free-tier minimum, so a long draft gets trimmed instead
of a post failing at publish time.
1b. A high ceiling is permission, not instruction. Length is not the goal and
never fill the limit: if one sharp idea fits in two sentences, ship two
sentences - that is a finished post. Vary length deliberately across posts.
Longer is justified by more substance, never more words.post.py day, never hand-counted. It
advances only when that post verifies, so a missed day burns no number.Worth reading before writing anything. Current weights (from X's published source):
python scripts/research.py collect --hours 36 → swipe/<date>.jsonweb_extract, or curl the page).references/voice-profile.local.md, then check the
length. Rule of thumb: one idea, short lines, blank line between thoughts,
hook in the first 6-8 words, end on the payoff.python scripts/due.py fill --slot 13:00 --file scratch/x1.txt for each slot
(it rejects anything over the limit, so you learn now rather than at post time)python scripts/post.py shot --what profile, then read shots/profile.png
with a vision tool and confirm the topmost post is the text you wrote.Do not relearn these the hard way.
about:blank then Page.navigate drops the
websocket ("no close frame received or sent") on heavy pages like X and
Reddit. Create the target with the final URL.Input.insertText is the only way to fill the composer. Setting innerText
leaves React state stale and the Post button disabled forever.state.json for counts.scripts/ after a Python stdlib module — the script
directory is on sys.path, so inspect.py or queue.py silently breaks imports.scripts/ settings.py browser.py research.py post.py due.py voice_profile.py doctor.py
config.json your install (git-ignored); config.example.json is the template
state.json the ledger: every post, its URL, whether it verified
swipe/ drafts/ shots/ scratch/
references/voice-profile.local.md your measured voice (git-ignored)
README.md full setup, scheduling and troubleshooting
Full documentation — including how to wire the schedule with a monitor so idle ticks cost nothing, and the cost model — is in README.md.
An autonomous X/Twitter posting pipeline for your AI agent.
It researches the day's news in your niche, writes posts in your voice, publishes them through a real logged-in browser, and proves each one actually went live.
websockets. Everything else is the Python standard library. ┌──────────┐ ┌────────┐ ┌───────────┐ ┌─────────┐ ┌────────┐
11:00 │ RESEARCH │ → │ PLAN │ → │ SCHEDULE │ → │ PUBLISH │ → │ VERIFY │
└──────────┘ └────────┘ └───────────┘ └─────────┘ └────────┘
HN, RSS, agent cron gate real read the
Reddit, X writes 4 wakes only browser profile
~150 items drafts when due composer back
Every day, unattended:
| Stage | What happens |
|---|---|
| Research | Pulls ~150 candidate stories from Hacker News (Algolia API), RSS feeds, Reddit (6 subreddits) and X search (8 queries), then ranks them by engagement and recency. |
| Plan | Your agent reads the swipe file, picks stories, opens the actual source article, and writes one post per slot — including a daily news roundup with its own day counter. |
| Schedule | Each slot gets a random minute inside its window. A monitor script decides when something is due. When nothing is due it prints IDLE and the model is never called — idle time is free. |
| Publish | Types into the real X composer over Chrome DevTools Protocol, respecting the account's true character limit, then clicks Post. |
| Verify | Polls the profile until the new post appears, screenshots it, and has a vision model read the text back. Only then is the slot closed. |
git clone <this repo> tweetytweets
cd tweetytweets
pip install -r requirements.txt
cp config.example.json config.json # fill this in during the interview
python scripts/doctor.py # tells you exactly what's missing
Then start the automation browser and sign in once:
python scripts/browser.py launch
# a separate Chrome window opens with its own profile.
# Sign in to x.com there.
# Use "Email or username" — NOT "Continue with Google", which can
# silently create a second account instead of logging into yours.
Verify, and measure your real character ceiling:
python scripts/post.py check # should print your handle
python scripts/post.py measure --save # measure THIS account's ceiling, then store it
Use a dedicated browser profile. That is what the launch command gives you. Driving your everyday browser risks posting as the wrong account.
Point your agent at SKILL.md and let it interview you. The single most
important question it will ask is for your best-performing posts — ideally
ones with 100,000+ impressions.
"Give me 5-20 of your best posts. Paste them raw, keep the line breaks, separate each with a line of
---. I'm not copying them — I'm measuring how you open, how long you go, whether you use numbers or questions, what you actually talk about."
The agent saves them and runs:
python scripts/voice_profile.py --in scratch/my_top_tweets.txt
which reports, measured rather than guessed:
LENGTH
min 132 | median 138 | mean 161 | max 216 chars
within the account's 25000 -char ceiling: 5/5 -> all posts fit the account's ceiling
STRUCTURE
beats per post (blank-line separated): median 3
uses blank-line breaks: 5/5
DEVICES (how many posts use each)
numbers 4/5
questions 0/5
links 0/5
hashtags 0/5 posts, 0 total
emoji 0/5 posts, 0 total
HOOK SHAPE — how they open
2x directive / addressed to the reader
1x statement / claim
1x number / specific figure
1x question
THE ACTUAL FIRST 6 WORDS (pattern-match these):
1. An open-weight model just made exploits
2. Stop writing prompts. Write evals.
3. Everyone is wasting Claude on mid
4. I analyzed 200 viral dev posts
5. How I cut a 6-hour review
That output becomes references/voice-profile.local.md, plus a do-not list
(words and formats you never use), and every draft is written against it.
It is git-ignored on purpose — it is your voice, not part of the shared tool.
You can also feed it posts you admire from other accounts. Treat those as structural references (shape, hook construction) — never as voice.
# 1. gather the day's material
python scripts/research.py collect --hours 36
python scripts/research.py sources # per-feed health
# 2. have your agent write 4 drafts against the voice profile, then:
python scripts/due.py make-plan # random due times, empty slots
python scripts/due.py fill --slot 13:00 --file scratch/x1.txt
python scripts/due.py fill --slot 16:00 --file scratch/xai.txt
python scripts/due.py show # what's ready, what's posted
# 3. publish (or let the schedule do it)
python scripts/post.py post --text-file scratch/x1.txt
python scripts/post.py post --text-file scratch/xai.txt --kind ai_update
# 4. verify by looking
python scripts/post.py shot --what profile # -> shots/profile.png
python scripts/post.py verify # newest posts, read back
Useful extras:
python scripts/post.py compose --text "..." --dry-run # fills the box, posts nothing
python scripts/post.py day # next roundup number
python scripts/due.py check # what the cron monitor sees
python scripts/doctor.py --live # network + browser health
due.py is designed to be the thing a scheduler monitors, not the thing it
runs blindly:
IDLE, byte-identical every tick, so a monitor can
suppress the agent run entirely → idle ticks cost nothingTwo cron jobs:
x-plan every day at 11am (agent): research + write + fill the 4 slots
x-autopost every 10m, monitor=x_autopost_due.py (agent): publish whatever is due
The monitor shim is ~20 lines: it runs due.py check and prints its stdout.
See examples/ for both job definitions and a plain-cron equivalent.
0 11 * * * cd /path/to/tweetytweets && python scripts/research.py collect --hours 36
*/10 * * * * cd /path/to/tweetytweets && python scripts/cron_gate.py
cron_gate.py runs due.py check; when the output is not IDLE it hands the
draft to your agent. Wire it to whatever you use to invoke a model.
Timing note: a slot publishes on the first tick after it comes due, so a post can land 0–20 minutes after its scheduled minute. That is a feature, not a bug — it is part of not looking like a metronome.
config.json (copy from config.example.json). Everything is optional except
handle.
| Key | Meaning |
|---|---|
handle |
Your handle, no @. The publisher refuses to post as anyone else. |
premium |
Whether the account can post above 280 chars. Ask, never assume — post.py measure --save fills this in from a real measurement. |
max_chars |
Hard ceiling. due.py fill rejects drafts over it before the scheduled time. |
timezone |
"local" or an IANA name like "Asia/Kolkata". Slots are interpreted here. |
slots |
Anchor times, e.g. ["13:00","16:00","20:00","00:00"]. A 00:00 slot belongs to the previous day's run. |
jitter_minutes |
Each slot lands within ± this many minutes of its anchor. |
min_gap_hours |
Minimum spacing enforced between slots. |
ai_update_enabled |
Whether one slot is a repeating "Daily … | Day N" roundup. |
ai_update_slot |
Which ancho |