@andypai/x
v0.1.0
Published
Read-only X (Twitter) API CLI.
Readme
x CLI
Read-only X API client for the terminal, built on Bun.
Why
X launched pay-as-you-go API pricing. The $200/mo Pro tier was excessive for personal use, but per-request pricing makes the API viable for lightweight workflows.
I wanted quick terminal access to timelines, bookmarks, and search without opening a browser. Puppeteer/Playwright scraping is fragile and breaks constantly, and it risks your account. This uses the official API only — no scraping, no ToS violations. It also gives tools like OpenClaw a structured way to check interests and do research.
Install
Global install from npm registry:
bun install -g @andypai/xFor local development, clone the repo and symlink the executable:
bun install
chmod +x src/index.ts
ln -sf "$(pwd)/src/index.ts" ~/.bun/bin/xGetting Started
1. Create a project in the X Developer Portal
Go to the X Developer Portal and create a project + app. You'll need:
- OAuth 1.0a keys (API Key, API Secret, Access Token, Access Token Secret) — used by
following - OAuth 2.0 client credentials (Client ID, Client Secret) — used by
bookmarks,timelines - Bearer Token — used by
search
Under your app's "User authentication settings", enable OAuth 2.0 with type Confidential client and add http://127.0.0.1:3000/callback as a redirect URI.
2. Run interactive setup
x setupThis walks you through pasting each credential and runs the OAuth 2.0 authorization flow to mint a user token. If matching env vars already exist, setup uses them as hidden defaults so you can press Enter without re-pasting secrets.
Skip OAuth 2.0 if you only need following and search:
x setup --no-oauth2Or bootstrap non-interactively from existing env vars:
x setup --no-inputCredentials
The resulting credential file (.env or ~/.config/x-cli/.env) contains:
| Variable | Auth Flow | Used By |
| ----------------------- | ---------------------- | ------------------------ |
| X_API_KEY | OAuth 1.0a | following |
| X_API_SECRET | OAuth 1.0a | following |
| X_ACCESS_TOKEN | OAuth 1.0a | following |
| X_ACCESS_TOKEN_SECRET | OAuth 1.0a | following |
| X_BEARER_TOKEN | App-Only (OAuth 2.0) | search |
| X_USER_ACCESS_TOKEN | OAuth 2.0 User Context | bookmarks, timelines |
| X_USER_REFRESH_TOKEN | OAuth 2.0 refresh | automatic token renewal |
| X_CLIENT_ID | OAuth 2.0 | token refresh, setup |
| X_CLIENT_SECRET | OAuth 2.0 | token refresh, setup |
Note:
X_ACCESS_TOKEN(OAuth 1.0a) andX_USER_ACCESS_TOKEN(OAuth 2.0 User Context) are different credentials — don't confuse them.
Refresh tokens: If
X_USER_REFRESH_TOKENandX_CLIENT_IDare present, the CLI will attempt one automatic refresh on 401. Read-only scopes used:tweet.read users.read bookmark.read follows.read offline.access.
Commands
x following [--max-results 25]
x timelines [--max-results 25] [--all] [--pretty] [--images auto|on|off] [--image-width 40] [--max-images 4]
x bookmarks [--max-results 25] [--pretty]
x search "openai bun" [--max-results 25] [--pretty]
x setup [--no-input] [--overwrite] [--oauth2|--no-oauth2] [--from-env]--max-results supports 5-100.
timelines requests a ranking-friendly payload including:
expansions=author_id,referenced_tweets.id,referenced_tweets.id.author_id,attachments.media_keystweet.fields=id,text,author_id,created_at,lang,entities,context_annotations,public_metrics,note_tweet,referenced_tweets,possibly_sensitiveuser.fields=id,username,name,description,entities,verified,public_metricsmedia.fields=type,url,preview_image_url,duration_ms,alt_text,public_metrics
For human review in terminal:
x timelines -m 5 --pretty
x bookmarks -m 5 --pretty
x search "long running agents" -m 5 --prettyFor Ghostty/Kitty inline previews:
x timelines -m 5 --pretty --images autoNotes:
--images autorenders inline previews beside each timeline entry when Kitty-compatible support is detected.--images onforces Kitty rendering attempts, while--images offdisables inline previews.- Previews are fetched from
pbs.twimg.commedia and automatically retried withformat=pngwhen needed.
Scripts
bun run dev # run with watch mode
bun run start # run once (CLI entry)
bun run lint # eslint
bun run format # prettier write
bun run typecheck # tsc --noEmit
bun run test # bun test
bun run test:watch # bun test --watch
bun run build # bun build ./src/index.ts --target bun --outdir ./dist
bun run check # lint + typecheck