194 lines
6.1 KiB
Markdown
194 lines
6.1 KiB
Markdown
# bird 🐦 — fast X CLI for tweeting, replying, and reading
|
||
|
||
`bird` is a fast X CLI for tweeting, replying, and reading via X/Twitter GraphQL (cookie auth).
|
||
|
||
## Disclaimer
|
||
|
||
This project uses X/Twitter’s **undocumented** web GraphQL API (and cookie auth). X can change endpoints, query IDs,
|
||
and anti-bot behavior at any time — **expect this to break without notice**.
|
||
|
||
## Install
|
||
|
||
```bash
|
||
npm install -g @steipete/bird
|
||
# or
|
||
pnpm add -g @steipete/bird
|
||
# or
|
||
bun add -g @steipete/bird
|
||
|
||
# one-shot (no install)
|
||
bunx @steipete/bird whoami
|
||
```
|
||
|
||
Homebrew (macOS, prebuilt Bun binary):
|
||
|
||
```bash
|
||
brew install steipete/tap/bird
|
||
```
|
||
|
||
## Quickstart
|
||
|
||
```bash
|
||
# Show the logged-in account
|
||
bird whoami
|
||
|
||
# Discover command help
|
||
bird help whoami
|
||
|
||
# Read a tweet (URL or ID)
|
||
bird read https://x.com/user/status/1234567890123456789
|
||
bird 1234567890123456789 --json
|
||
|
||
# Thread + replies
|
||
bird thread https://x.com/user/status/1234567890123456789
|
||
bird replies 1234567890123456789
|
||
|
||
# Search + mentions
|
||
bird search "from:steipete" -n 5
|
||
bird mentions -n 5
|
||
bird mentions --user @steipete -n 5
|
||
|
||
# Bookmarks
|
||
bird bookmarks -n 5
|
||
|
||
# Refresh GraphQL query IDs cache (no rebuild)
|
||
bird query-ids --fresh
|
||
```
|
||
|
||
## Commands
|
||
|
||
- `bird tweet "<text>"` — post a new tweet.
|
||
- `bird reply <tweet-id-or-url> "<text>"` — reply to a tweet using its ID or URL.
|
||
- `bird help [command]` — show help (or help for a subcommand).
|
||
- `bird query-ids [--fresh] [--json]` — inspect or refresh cached GraphQL query IDs.
|
||
- `bird read <tweet-id-or-url> [--json]` — fetch tweet content as text or JSON.
|
||
- `bird <tweet-id-or-url> [--json]` — shorthand for `read` when only a URL or ID is provided.
|
||
- `bird replies <tweet-id-or-url> [--json]` — list replies to a tweet.
|
||
- `bird thread <tweet-id-or-url> [--json]` — show the full conversation thread.
|
||
- `bird search "<query>" [-n count] [--json]` — search for tweets matching a query.
|
||
- `bird mentions [-n count] [--user @handle] [--json]` — find tweets mentioning a user (defaults to the authenticated user).
|
||
- `bird bookmarks [-n count] [--json]` — list your bookmarked tweets.
|
||
- `bird whoami` — print which Twitter account your cookies belong to.
|
||
- `bird check` — show which credentials are available and where they were sourced from.
|
||
|
||
Global options:
|
||
- `--timeout <ms>`: abort requests after the given timeout (milliseconds).
|
||
- `--plain`: stable output (no emoji, no color).
|
||
- `--no-emoji`: disable emoji output.
|
||
- `--no-color`: disable ANSI colors (or set `NO_COLOR=1`).
|
||
- `--cookie-source <safari|chrome|firefox>`: choose browser cookie source (repeatable; order matters).
|
||
|
||
## Authentication (GraphQL)
|
||
|
||
GraphQL mode uses your existing X/Twitter web session (no password prompt). It sends requests to internal
|
||
X endpoints and authenticates via cookies (`auth_token`, `ct0`).
|
||
|
||
Write operations:
|
||
- `tweet`/`reply` primarily use GraphQL (`CreateTweet`).
|
||
- If GraphQL returns error `226` (“automated request”), `bird` falls back to the legacy `statuses/update.json` endpoint.
|
||
|
||
`bird` resolves credentials in this order:
|
||
|
||
1. CLI flags: `--auth-token`, `--ct0`
|
||
2. Environment variables: `AUTH_TOKEN`, `CT0` (fallback: `TWITTER_AUTH_TOKEN`, `TWITTER_CT0`)
|
||
3. Browser cookies via `@steipete/sweet-cookie` (override via `--cookie-source` order)
|
||
|
||
Browser cookie sources:
|
||
- Safari: `~/Library/Cookies/Cookies.binarycookies` (fallback: `~/Library/Containers/com.apple.Safari/Data/Library/Cookies/Cookies.binarycookies`)
|
||
- Chrome: `~/Library/Application Support/Google/Chrome/<Profile>/Cookies`
|
||
- Firefox: `~/Library/Application Support/Firefox/Profiles/<profile>/cookies.sqlite`
|
||
|
||
## Config (JSON5)
|
||
|
||
Config precedence: CLI flags > env vars > project config > global config.
|
||
|
||
- Global: `~/.config/bird/config.json5`
|
||
- Project: `./.birdrc.json5`
|
||
|
||
Example `~/.config/bird/config.json5`:
|
||
|
||
```json5
|
||
{
|
||
// Cookie source order for browser extraction (string or array)
|
||
cookieSource: ["firefox", "safari"],
|
||
firefoxProfile: "default-release",
|
||
timeoutMs: 20000
|
||
}
|
||
```
|
||
|
||
Environment shortcuts:
|
||
- `BIRD_TIMEOUT_MS`
|
||
|
||
## Output
|
||
|
||
- `--json` prints raw tweet objects for read/replies/thread/search/mentions/bookmarks.
|
||
- `read` returns full text for Notes and Articles when present.
|
||
- Use `--plain` for stable, script-friendly output (no emoji, no color).
|
||
|
||
## Query IDs (GraphQL)
|
||
|
||
X rotates GraphQL “query IDs” frequently. Each GraphQL operation is addressed as:
|
||
|
||
- `operationName` (e.g. `TweetDetail`, `CreateTweet`)
|
||
- `queryId` (rotating ID baked into X’s web client bundles)
|
||
|
||
`bird` ships with a baseline mapping in `src/lib/query-ids.json` (copied into `dist/` on build). At runtime,
|
||
it can refresh that mapping by scraping X’s public web client bundles and caching the result on disk.
|
||
|
||
Runtime cache:
|
||
- Default path: `~/.config/bird/query-ids-cache.json`
|
||
- Override path: `BIRD_QUERY_IDS_CACHE=/path/to/file.json`
|
||
- TTL: 24h (stale cache is still used, but marked “not fresh”)
|
||
|
||
Auto-recovery:
|
||
- On GraphQL `404` (query ID invalid), `bird` forces a refresh once and retries.
|
||
- For `TweetDetail`/`SearchTimeline`, `bird` also rotates through a small set of known fallback IDs to reduce
|
||
breakage while refreshing.
|
||
|
||
Refresh on demand:
|
||
|
||
```bash
|
||
bird query-ids --fresh
|
||
```
|
||
|
||
Exit codes:
|
||
- `0`: success
|
||
- `1`: runtime error (network/auth/etc)
|
||
- `2`: invalid usage/validation (e.g. bad `--user` handle)
|
||
|
||
## Version
|
||
|
||
`bird --version` prints `package.json` version plus current git sha when available, e.g. `0.3.0 (3df7969b)`.
|
||
|
||
## Media uploads
|
||
|
||
- Attach media with `--media` (repeatable) and optional `--alt` per item.
|
||
- Up to 4 images/GIFs, or 1 video (no mixing). Supported: jpg, jpeg, png, webp, gif, mp4, mov.
|
||
- Images/GIFs + 1 video supported (uploads via Twitter legacy upload endpoint + cookies; video may take longer to process).
|
||
|
||
Example:
|
||
|
||
```bash
|
||
bird tweet "hi" --media img.png --alt "desc"
|
||
```
|
||
|
||
## Development
|
||
|
||
```bash
|
||
cd ~/Projects/bird
|
||
pnpm install
|
||
pnpm run build # dist/ + bun binary
|
||
pnpm run build:dist # dist/ only
|
||
pnpm run build:binary
|
||
|
||
pnpm run dev tweet "Test"
|
||
pnpm run dev -- --plain check
|
||
pnpm test
|
||
pnpm run lint
|
||
```
|
||
|
||
## Notes
|
||
|
||
- GraphQL uses internal X endpoints and can be rate limited (429).
|
||
- Query IDs rotate; refresh at runtime with `bird query-ids --fresh` (or update the baked baseline via `pnpm run graphql:update`).
|