Twitter/X CLI for searching posts, reading timelines, bookmarks and replies, and publishing original posts or replies from the terminal. Inspired by Bird CLI.
Uses your own X/Twitter cookies. One user, one account. No API keys required.
- Search tweets by keyword or advanced query (
from:,to:,filter:, etc.) - Read a user's timeline
- Read a single tweet by ID or URL
- Read replies to any tweet
- Read your bookmarks (with
--allfor full export) - Look up user profiles by handle
- Publish an original post or reply with up to 4 photos, 1 GIF, or 1 video
- Stream video uploads in 5 MiB chunks and wait for X processing to finish
- Guard writes twice: CLI permission flags plus an opt-in client API
- JSON output for piping into other tools (
jq, scripts, etc.) - Auto-discovers X's rotating GraphQL query IDs (no manual updates needed)
- Zero config beyond two browser cookies
- No Twitter API keys or developer account needed
- Read-focused: only original posts and replies can write (no likes, retweets, follows, or deletes)
- Works with X's current GraphQL endpoints
- Lightweight: single dependency (Commander)
- Scriptable: JSON output for automation and data pipelines
npm install -g x-readergit clone https://github.com/DjinnFoundry/x-reader.git
cd x-reader
npm install
npm run build
npm link# 1. Set up authentication (interactive)
x-reader setup
# 2. Search tweets
x-reader search "machine learning"
# 3. Read a user's timeline
x-reader user-tweets @naval -n 10
# 4. Export your bookmarks as JSON
x-reader bookmarks --all --format json > bookmarks.jsonYou need two cookies from x.com: auth_token and ct0.
x-reader setupexport AUTH_TOKEN="your_auth_token"
export CT0="your_ct0"x-reader search "query" --auth-token xxx --ct0 yyy- Go to x.com and log in
- Open DevTools (F12) -> Application -> Cookies -> x.com
- Copy
auth_tokenandct0values
Config is saved to ~/.config/x-reader/config.json. setup enforces mode 600 on the file and 700 on its directory.
x-reader search "machine learning"
x-reader search "from:elonmusk" -n 5
x-reader search "AI safety" --format jsonx-reader user-tweets @steipete
x-reader user-tweets elonmusk -n 10
x-reader user-tweets @naval --format jsonx-reader read 1234567890
x-reader read https://x.com/user/status/1234567890
x-reader read https://x.com/user/status/1234567890 --format jsonx-reader replies 1234567890
x-reader replies https://x.com/user/status/1234567890 --format jsonX_READER_ENABLE_POST=true x-reader post "A deliberately approved post"
X_READER_ENABLE_POST=true x-reader post "An approved image post" --media cover.png
X_READER_ENABLE_POST=true x-reader post "An approved video post" --media clip.mp4X_READER_ENABLE_REPLY=true x-reader reply 1234567890 "Thanks for sharing!"
X_READER_ENABLE_REPLY=true x-reader reply https://x.com/user/status/1234567890 "Here's some context" --media chart.png
X_READER_ENABLE_REPLY=true x-reader reply 1234567890 "Done" --format jsonpost and reply are disabled by default. Prefer an operation-specific variable on the same command invocation:
X_READER_ENABLE_POST=trueenables onlypost.X_READER_ENABLE_REPLY=trueenables onlyreply.X_READER_ENABLE_WRITE=trueenables both and should be used only when that wider scope is intentional.
Do not export these variables globally. Enabling the capability is not approval for a particular remote write.
Media rules:
- Up to 4 photos (
jpg,jpeg,png, orwebp), each no larger than 5 MiB;--mediais repeatable. - Or exactly 1 GIF no larger than 15 MiB, without other media.
- Or exactly 1 video (
mp4,m4v, ormov). A video cannot be mixed with images. - Every attachment must exist, be a regular non-empty file, and pass validation before the first upload starts.
- Video is streamed from disk in 5 MiB segments using
INIT → APPEND → FINALIZE → STATUS; the CLI reports upload and processing progress on stderr. - X enforces account-specific upload and post limits separately. MP4 with H.264 video and AAC audio is the safest production target.
- Post/reply mutations are sent once and are never automatically retried after an ambiguous response, avoiding accidental duplicates.
- Threads, polls, likes, retweets, follows, and deletes are not implemented.
See X's chunked media upload and media best practices. x-reader uses X's undocumented web/cookie authentication and legacy upload endpoint, so X can change compatibility without notice.
x-reader bookmarks
x-reader bookmarks -n 50
x-reader bookmarks --all --format jsonx-reader user-lookup @steipete
x-reader user-lookup naval --format json# Show cached query IDs
x-reader query-ids
# Force refresh from x.com (when IDs rotate)
x-reader query-ids --refresh- text (default) - human-readable
- json - machine-readable, pipe to
jqor save to file
# Pipe to jq
x-reader search "typescript" --format json | jq '.tweets[].text'
# Save to file
x-reader bookmarks --all --format json > my-bookmarks.jsonimport { XReaderClient } from 'x-reader';
const client = new XReaderClient({
cookies: { authToken: '...', ct0: '...' },
enableWrites: true, // explicit opt-in; omit for read-only use
});
const result = await client.search('hello world', 10);
console.log(result.tweets);
// Publish an original post
const original = await client.createTweet('An explicitly approved post');
// Upload one video, then attach its media ID to a post
const video = await client.uploadMedia('clip.mp4', {
onProgress: (progress) => console.error(progress),
});
const videoPost = await client.createTweet('An approved video', {
mediaIds: video.mediaId ? [video.mediaId] : [],
});
// Post a reply (optionally with an uploaded image)
const up = await client.uploadMedia('chart.png');
const posted = await client.reply('1234567890', 'Here is the data', {
mediaIds: up.mediaId ? [up.mediaId] : [],
});
console.log(posted.url);X uses GraphQL endpoints with rotating query IDs embedded in their client-side JavaScript bundles. x-reader auto-discovers these IDs by scraping the JS bundles, caching them locally with a 24-hour TTL. No manual ID updates needed.
If you get 404 errors, force a refresh:
x-reader query-ids --refreshsrc/
├── api/
│ ├── client.ts # Main API client (reads + opt-in posts/replies)
│ ├── constants.ts # Bearer token, URLs, default query IDs
│ ├── features.ts # GraphQL feature flags per operation
│ ├── parser.ts # Response parsing (raw JSON -> Tweet/User)
│ ├── query-ids.ts # Auto-discovery of query IDs from x.com JS
│ └── types.ts # TypeScript interfaces
├── cli/
│ └── index.ts # CLI entry point (commander)
├── utils/
│ ├── auth.ts # Cookie resolution (env, config, bird compat)
│ ├── media.ts # Attachment validation and progress formatting
│ └── format.ts # Output formatting
└── index.ts # Library exports
MIT