Multi-account proxy for the bird CLI. Store multiple X/Twitter auth tokens and automatically rotate between accounts to reduce rate-limit risk.
curl -fsSL https://raw.githubusercontent.com/guzus/birdy/main/install.sh | bash && birdy account add main && birdy tuibirdy account add main will prompt you for auth_token and ct0 from your X/Twitter browser session.
Launch the full-screen terminal interface with AI-powered chat:
birdy tuiThe TUI features:
- Chat — Ask birdy to read your timeline, search tweets, post, and more via Claude
- Deep browsing — Say "dive deeper" and birdy will autonomously explore threads, replies, and user profiles
- Account management — Add, remove, and view accounts with
tab - Chat history — Conversations are saved as markdown in
~/.config/birdy/chats/(setBIRDY_TUI_HIDE_HISTORY=1to disable)
Run birdy as a browser-accessible terminal session:
birdy host --addr 0.0.0.0:8787birdy will print the local URL:
http://127.0.0.1:8787
Users must enter an invite code to connect.
Notes:
- The host runs the same
birdy tuisession in a web terminal. - Set invite code with
--invite-codeorBIRDY_HOST_INVITE_CODE. - For public deployments, set
BIRDY_READ_ONLY=1. - This is a shared session: everyone who knows the invite code can see/control the same TUI.
This repo now includes a Railway-ready container setup:
Dockerfilescripts/entrypoint-railway.sh.env.railway.example
Create a Railway service from this repo. Railway will detect and build the Dockerfile.
Set these in Railway Variables:
# Required: invite code users enter in the browser
BIRDY_HOST_INVITE_CODE=replace-with-long-random-secret
# Recommended for public deployments: disable write actions
BIRDY_READ_ONLY=1
# Optional: lock websocket origins to specific public domains
# BIRDY_HOST_ALLOWED_ORIGINS=https://your-domain.example,https://<railway-domain>
# Optional: hide/disable local chat history UI + persistence (recommended for public deploys)
BIRDY_TUI_HIDE_HISTORY=1
# Required: X/Twitter accounts as JSON (single line)
BIRDY_ACCOUNTS=[{"name":"main","auth_token":"x_auth_token_here","ct0":"x_ct0_here"}]
# Required for AI chat (pick one auth method)
CLAUDE_CODE_OAUTH_TOKEN=replace-with-claude-code-oauth-token
# or
# ANTHROPIC_API_KEY=replace-with-anthropic-api-key
# or
# ANTHROPIC_AUTH_TOKEN=replace-with-anthropic-auth-tokenMount a Railway volume at:
/data
This preserves:
~/.config/birdy/accounts.json~/.config/birdy/state.json~/.config/birdy/chats/
After deploy, open your Railway public URL and enter your invite code:
https://<your-service-domain>
- Keep this service at
1replica (session is shared and stateful). - The container uses Node 22 + Claude Code CLI + bundled bird CLI.
- Rotate
BIRDY_HOST_INVITE_CODEif it leaks.
birdy sits in front of the bird CLI. When you run a bird command through birdy, it:
- Picks an account from your stored credentials using a rotation strategy
- Injects the
AUTH_TOKENandCT0environment variables - Forwards the command to
bird - Tracks usage per account for smart rotation
curl -fsSL https://raw.githubusercontent.com/guzus/birdy/main/install.sh | bashRequires the GitHub CLI (gh). To install a specific version, pass it as an argument: ... | bash -s v0.2.0
# From source (requires Go)
go install github.com/guzus/birdy@latest
# Build locally
git clone https://github.com/guzus/birdy.git && cd birdy && make buildIf you install via go install, only the birdy binary is installed, so you still need bird available on your PATH (or set BIRDY_BIRD_PATH).
If you build from a git clone, bird is vendored under third_party/@steipete/bird/ (requires Node >= 22).
- The installer bundles the upstream bird CLI and installs it as
birdy-bird(birdy will auto-detect it). The bundled bird requires Node>= 22. - Claude Code (
claudeCLI) — required for the interactive TUI (birdy tui)
To force a specific bird binary, set BIRDY_BIRD_PATH=/path/to/bird.
# Add accounts (you'll be prompted for auth_token and ct0)
birdy account add personal
birdy account add work
birdy account add alt
# Or pass credentials directly
birdy account add bot --auth-token "xxx" --ct0 "yyy"
# Now use bird commands through birdy - accounts rotate automatically
birdy read 1234567890
birdy search "golang"
birdy home
birdy mentions
# See which account was used with --verbose
birdy -v home
# Use a specific account
birdy --account personal whoami
# Check rotation status
birdy status
# List accounts
birdy account listbirdy account add <name> # Add account (interactive or with --auth-token/--ct0)
birdy account list # List all accounts with usage stats
birdy account update <name> # Update credentials for an account
birdy account remove <name> # Remove an accountControl how birdy picks the next account with --strategy / -s:
| Strategy | Description |
|---|---|
round-robin (default) |
Cycles through accounts in order |
least-recently-used |
Picks the account used longest ago |
least-used |
Picks the account with the fewest total uses |
random |
Picks a random account |
birdy -s least-used search "rust"
birdy -s random homeYou need two cookies from an active X/Twitter web session:
- Open X/Twitter in your browser and log in
- Open Developer Tools (F12) > Application > Cookies >
https://x.com - Copy the values of
auth_tokenandct0
Repeat for each account you want to add.
In CI environments where there's no interactive terminal, set the BIRDY_ACCOUNTS env var with a JSON array of accounts:
export BIRDY_ACCOUNTS='[{"name":"bot1","auth_token":"xxx","ct0":"yyy"},{"name":"bot2","auth_token":"aaa","ct0":"bbb"}]'
birdy -v read 1234567890When BIRDY_ACCOUNTS is set and no accounts file exists on disk, birdy runs in ephemeral mode — accounts are loaded from the env var and nothing is written to disk.
If an accounts file also exists, env accounts are merged in (overriding any file account with the same name).
Store the JSON as a repository secret named BIRDY_ACCOUNTS, then use it in your workflow:
- name: Read a tweet
env:
BIRDY_ACCOUNTS: ${{ secrets.BIRDY_ACCOUNTS }}
run: birdy -v read 1234567890See .github/workflows/example.yml for a full workflow.
Accounts are stored in ~/.config/birdy/accounts.json with 0600 permissions (owner-only read/write). Rotation state is tracked in ~/.config/birdy/state.json.
MIT