A fast, feature-complete command-line interface for Twitter/X, written in Go.
No API keys required — authenticates directly via browser cookies.
- Features
- Installation
- Quick Start
- Authentication
- Commands
- Output Formats
- MCP Server
- Configuration
- Endpoint Management
- Diagnostics & Status
- Development
- Zero API keys — cookie-based authentication extracted from your browser
- AI-ready — built-in MCP server compatible with Claude, Cursor, and any MCP client
- Multi-account — manage and switch between multiple Twitter/X accounts
- Structured output — JSON, YAML, and compact modes for scripting and agents
- Resilient endpoint discovery — authenticated recursive discovery of GraphQL operation IDs across modern X.com bundles
- Media support — post up to 4 images per tweet, download media from any tweet
- Full coverage — tweets, DMs, lists, bookmarks, jobs, trends, schedules, relationships, and more
- Community discovery — browse communities and continue through cursor-based pages
- Long-form publishing — publish Note Tweets from text or a file with explicit confirmation
- Endpoint-aware fallbacks — stale X operations are detected; quote search falls back to
SearchTimeline
Requires Go 1.24+:
go install github.com/benoitpetit/xsh@latestLinux/macOS:
curl -fsSL https://xsh.devbyben.fr/install | bashWindows (PowerShell as Administrator):
iwr -useb https://xsh.devbyben.fr/install | iexDownload from Releases:
# Linux (amd64)
curl -L https://github.com/benoitpetit/xsh/releases/latest/download/xsh-linux-amd64 -o xsh && \
chmod +x xsh && \
sudo mv xsh /usr/local/bin/To update an installed release binary:
xsh update --check # Check the latest compatible stable release
xsh update # Download, verify SHA-256, and install itThe updater selects the binary for your OS and architecture. It needs write
access to the installed executable; installations in protected directories may
require running xsh update with elevated privileges. xsh auto-update is a
separate command that refreshes X GraphQL endpoint IDs.
git clone https://github.com/benoitpetit/xsh
cd xsh
go build -o xsh .
sudo mv xsh /usr/local/bin/# 1. Authenticate (extracts cookies from your browser)
xsh auth login
# 2. View your timeline
xsh feed
# 3. Post a tweet
xsh tweet post "Hello from xsh!"
# 4. Search tweets
xsh search "golang" --pages 2
# 5. View a user profile
xsh user elonmusk# Auto-detect browser (Chrome, Firefox, Brave, Edge, Chromium)
xsh auth login
# Target a specific browser
xsh auth login --browser firefox
xsh auth login --browser brave
# Save under a named account
xsh auth login --account work# Import from Cookie Editor JSON export
xsh auth import cookies.json
xsh auth import cookies.json --account work
# Set credentials manually
xsh auth set
# Check authentication status
xsh auth status
# Show current user info
xsh auth whoami
# List all stored accounts
xsh auth accounts
# Switch default account
xsh auth switch work
# Remove stored credentials
xsh auth logoutShortcuts
xsh accounts,xsh switch <name>, andxsh import <file>are also available at root level.
These flags apply to every command:
| Flag | Short | Description |
|---|---|---|
--account <name> |
Use a specific stored account | |
--json |
Output as JSON | |
--yaml |
Output as YAML | |
--compact |
-c |
Compact output for AI agents |
--verbose |
-v |
Show HTTP requests (debug) |
--watch <seconds> |
-w |
Refresh output periodically |
# View a tweet (with optional thread tree)
xsh tweet view <id>
xsh tweet view <id> --thread
xsh tweet view <id> --count 50
xsh tweet get <id> # Fetch the tweet without its thread
xsh tweet view <id> --export article.json --export-format json
# Post a tweet
xsh tweet post "Hello world!"
# Publish a long-form Note Tweet (confirmation required)
xsh tweet note --file essay.txt
xsh tweet post "With image" --image photo.jpg
xsh tweet post "Multiple images" -i img1.jpg -i img2.jpg -i img3.jpg
xsh tweet post "Reply" --reply-to <tweet-id>
xsh tweet post "Quote" --quote https://x.com/user/status/<id>
# Delete your tweet
xsh tweet delete <id>
# Like / unlike
xsh tweet like <id>
xsh tweet unlike <id>
# Retweet / undo
xsh tweet retweet <id>
xsh tweet unretweet <id>
# Bookmark / remove
xsh tweet bookmark <id>
xsh tweet unbookmark <id>Shortcuts available at root level:
xsh unlike <id>
xsh unretweet <id>
xsh unbookmark <id># Home timeline
xsh feed
xsh feed --type following # Following timeline
xsh feed --count 50 --pages 3 # 150 tweets total
xsh feed --filter top --top 20 # Top 20 by engagement
xsh feed --filter score --threshold 100
xsh feed --cursor <cursor> # Paginate
xsh feed --page-json --json # Include next_cursor and has_more
# Search
xsh search "golang"
xsh search "golang" --type Latest # Types: Top, Latest, Photos, Videos
xsh search "golang" --count 50 --pages 2
xsh search "golang" --cursor <cursor>
xsh search "golang" --page-json --json # Include next_cursor and has_more# View profile
xsh user <handle>
# User's tweets
xsh user tweets <handle>
xsh user tweets <handle> --replies # Include replies
xsh user tweets <handle> --count 50
# Liked tweets
xsh user likes <handle> --count 30
# Followers / following
xsh user followers <handle> --count 50
xsh user following <handle> --count 50
xsh user followers <handle> --cursor <cursor>
# Media and relationship discovery
xsh user media <handle> --count 50
xsh user followers-you-know <handle> --count 50
xsh user blue-verified-followers <handle> --count 50
xsh user media <handle> --json --cursor <cursor># Discover communities and fetch another page from its cursor
xsh community explore --count 20
xsh community explore --json --cursor <cursor>
# Inspect a community, read its tweets, or join/leave it
xsh community view <community-id>
xsh community tweets <community-id>
xsh community join <community-id># Follow / unfollow
xsh follow <handle>
xsh unfollow <handle>
# Block / unblock
xsh block <handle>
xsh unblock <handle>
# Mute / unmute
xsh mute <handle>
xsh unmute <handle>
# Read relationship lists
xsh social blocked --count 50
xsh social muted --count 50
xsh social blocked --json --cursor <cursor># View all bookmarks
xsh bookmarks
xsh bookmarks --count 50
# List bookmark folders
xsh bookmarks-folders
# View tweets in a specific folder
xsh bookmarks-folder <folder-id># View inbox
xsh dm inbox
# Send a DM
xsh dm send <handle> "Your message"
# Delete a DM message
xsh dm delete <message-id># View all your lists
xsh lists
# Read list metadata and memberships
xsh lists info <list-id>
xsh lists memberships
# View tweets from a list
xsh lists view <list-id>
xsh lists view <list-id> --count 50
xsh lists view <list-id> --json --cursor <cursor>
# Create / delete a list
xsh lists create "List name"
xsh lists delete <list-id>
# Manage members
xsh lists members <list-id>
xsh lists members <list-id> --cursor <cursor>
xsh lists memberships --json --cursor <cursor>
xsh lists add-member <list-id> <handle>
xsh lists remove-member <list-id> <handle>
# Pin / unpin
xsh lists pin <list-id>
xsh lists unpin <list-id>
# Update metadata (requires confirmation; JSON requires --force)
xsh lists update <list-id> --name "New name"
xsh lists update <list-id> --private=false --force --json# Schedule a tweet
xsh schedule "My future tweet" --at "2026-04-01 09:00"
# List scheduled tweets
xsh scheduled
# Cancel a scheduled tweet
xsh unschedule <scheduled-tweet-id># Worldwide trends
xsh trends
# By location name
xsh trends --location "Paris"
xsh trends --location "France"
# By WOEID
xsh trends --woeid 1 # Worldwide
xsh trends --woeid 615702 # Paris# Search job listings
xsh jobs search "software engineer"
xsh jobs search "data engineer" --location "Paris"
xsh jobs search "devops" --location-type remote
xsh jobs search "backend" --employment-type full_time
xsh jobs search "intern" --seniority entry_level
xsh jobs search "manager" --company Google --pages 2
# Available filters:
# --location <city/country>
# --location-type remote | onsite | hybrid
# --employment-type full_time | part_time | contract | internship
# --seniority entry_level | mid_level | senior
# --company <name>
# --industry <sector>
# --count <n> (default 25)
# --pages <n> (default 1)
# View job details
xsh jobs view <job-id># Download all media from a tweet
xsh download <tweet-id>
xsh download <tweet-id> --output-dir ./media# Interactive mode (prompts for each tweet)
xsh compose
# From a text file (auto-splits into thread)
xsh compose --file thread.txt
# From stdin
cat thread.txt | xsh compose
# Preview without posting
xsh compose --file thread.txt --dry-runExport tweets to file in multiple formats: json, jsonl, csv, tsv, md.
# Export timeline
xsh export feed --format csv --output timeline.csv
xsh export feed --format jsonl --output tweets.jsonl --count 200
xsh export feed --type following --filter top
# Export search results
xsh export search "golang" --format md --output results.md
# Export bookmarks
xsh export bookmarks --format json --output bookmarks.json
# Write to stdout
xsh export feed --format jsonl --output -# Fetch multiple tweets by ID
xsh tweets <id1> <id2> <id3>
# Fetch multiple user profiles
xsh users <handle1> <handle2> <handle3>
# Fetch multiple profiles by numeric user ID
xsh users --id 44196397 --id 783214# Count characters in text
xsh count "My tweet draft"
# From a file
xsh count --file draft.txt
# From stdin
echo "My tweet" | xsh count
# Show formatted preview
xsh count "My tweet" --preview
xsh count --file draft.txt --preview --width 80Read commands are safe to exercise against a connected account. Commands that
publish, send, follow, block, mute, bookmark, schedule, delete, or update data
change remote state. lists update requires at least one explicit field and a
human confirmation; in JSON mode it also requires --force. tweet note
accepts text or --file, validates the 25,000-character limit, and uses the
same confirmation rule. Never use --force in an automated smoke test unless
the remote mutation is intentional.
All commands support structured output for scripting and AI integration:
# JSON
xsh feed --json
xsh user elonmusk --json
# YAML
xsh search "golang" --yaml
# Compact (essential fields only, ideal for AI agents)
xsh feed --compact
# Pipe auto-detection: JSON is used automatically when stdout is not a TTY
xsh feed | jq '.[].text'
# Cursor-enabled user/list commands expose an envelope
xsh user tweets ben | jq '.items[] | .text'
# Cursor-based commands return an envelope in structured modes:
# {"items": [...], "next_cursor": "...", "has_more": true}
# Pass next_cursor back with --cursor to fetch the next page.
# feed and search keep their historical flat output by default; add --page-json
# to include the pagination envelope in JSON, YAML, or compact output.
# Compact JSON is one line, suitable for pipes and agents
xsh user followers ben --compact | jq -c .
# Use a specific account
xsh feed --account workxsh includes a full Model Context Protocol server for use with Claude, Cursor, and other MCP-compatible AI clients.
xsh mcpAdd to your claude_desktop_config.json:
{
"mcpServers": {
"xsh": {
"command": "xsh",
"args": ["mcp"]
}
}
}| Category | Tools |
|---|---|
| Timeline | get_feed, get_home_latest_timeline |
| Search | search, search_bookmarks |
| Tweets | get_tweet, get_tweet_thread, post_tweet, delete_tweet, get_tweets_batch |
| Engagement | like, unlike, retweet, unretweet, bookmark, unbookmark |
| Users | get_user, get_users_batch, get_user_tweets, get_user_likes, get_user_media, get_followers, get_following, get_followers_you_know, get_blue_verified_followers |
| Relationships | get_blocked_accounts, get_muted_accounts |
| Social | follow, unfollow, block, unblock, mute, unmute |
| Bookmarks | list_bookmarks, get_bookmark_folders, get_bookmark_folder |
| Lists | get_user_lists, get_list_info, get_list_memberships, get_list_timeline, get_list_members, create_list, delete_list, add_list_member, remove_list_member, pin_list, unpin_list |
| DMs | dm_inbox, send_dm, delete_dm |
| Scheduled | schedule_tweet, get_scheduled_tweets, delete_scheduled_tweet |
| Trends | get_trending |
| Jobs | search_jobs, get_job |
| Media | download_media, upload_media |
| Compose | compose_thread |
| Utils | count_characters, export_tweets |
| Endpoints | get_endpoints, refresh_endpoints |
xsh resolves credentials in this order: explicit environment variables
(X_AUTH_TOKEN/X_CT0, then legacy TWITTER_AUTH_TOKEN/TWITTER_CT0), the
selected --account, the configured default account, and finally browser
extraction. Use XSH_CONFIG_DIR to select an isolated configuration root;
otherwise xsh follows the platform XDG/OS configuration directory.
Credentials and caches are written with private directory/file permissions and
atomic replacement. If a JSON file is corrupted, xsh preserves it as a .bak
file before returning the error. Remove or restore that backup only after
inspection.
Endpoint snapshots are bounded and versioned. Discovery fetches an authenticated homepage (10 MiB limit) and JavaScript bundles (5 MiB limit), follows imports with a bounded worker pool, and merges duplicate operations in bundle priority order. A stale operation is retried only for safe reads; write operations are never replayed automatically. A quarantined operation is not retried until discovery supplies a replacement endpoint.
--json, --yaml, and --compact are mutually exclusive. Structured output
is emitted on stdout, diagnostics on stderr, and credentials are redacted.
Long-running commands honor context cancellation and stop their refresh,
stream, and watch loops without writing partial machine-readable records.
Configuration is stored below the platform user configuration directory, or
under the directory selected by XSH_CONFIG_DIR. Use xsh config path to
display the exact file path on the current system:
# Show current configuration
xsh config
xsh config show
# Get a specific value
xsh config get filter.likes_weight
# Set a value
xsh config set display.theme dark
xsh config set request.timeout 60
# Open in editor
xsh config edit
# Show config file path
xsh config path
# Reset to defaults
xsh config resetdefault_count = 20
[display]
theme = "default"
show_engagement = true
show_timestamps = true
max_width = 100
[request]
delay = 1.5
timeout = 30
max_retries = 3
[filter]
likes_weight = 1.0
retweets_weight = 1.5
replies_weight = 0.5
bookmarks_weight = 2.0
views_log_weight = 0.3
min_score = 0request.delay controls the delay after read requests. Keep the default
1.5 for normal interactive use; set it to 0 for an explicitly fast local
workflow. request.timeout controls the HTTP client timeout in seconds.
The published Linux, Windows, and macOS binaries are built with CGO_ENABLED=0
and include the portable Chromium/Firefox cookie-database readers. Browser
profiles are discovered in the platform's standard locations, including
common Snap and Flatpak layouts on Linux. Chromium-based browsers may still
require access to the operating-system key store to decrypt protected cookies;
when that store is unavailable, xsh reports the affected authentication cookie
instead of silently using incomplete credentials.
xsh dynamically discovers GraphQL operation IDs from the authenticated X.com web client and caches them locally. Discovery reuses every stored session cookie, walks nested Vite/Rollup chunks, and understands persisted GraphQL URLs built at runtime.
# List all cached endpoints
xsh endpoints list
# Check status of a specific endpoint
xsh endpoints check HomeTimeline
# Refresh endpoints from the authenticated X.com session
xsh endpoints refresh
# Show endpoint system status
xsh endpoints status
# Manually update a single endpoint
xsh endpoints update <operation> <endpoint-id>
# Reset all endpoints to bundled defaults
xsh endpoints reset
# Auto-update obsolete endpoints (requires an authenticated session)
xsh auto-update
xsh auto-update --dry-run # Check only, don't update
xsh auto-update --force # Force refresh (ignore cache)endpoints status reports whether the dynamic cache is available and how many
operations were quarantined after a definitive 404. When discovery is
unavailable, xsh keeps the last valid cache and uses static IDs only for
operations that still have a verified fallback. A stale operation is refreshed
once; a confirmed 404 is isolated so repeated calls do not trigger discovery
storms.
Discovery refuses a logged-out x-web shell and never treats the public shell
as an authenticated endpoint source. If refresh fails with an authentication
or shell error, log in again and retry:
xsh auth status
xsh endpoints refresh --verboseSome operation names can remain visible in the endpoint inventory after X
removes them. TweetQuotes currently returns 404, so xsh quotes uses the
stable SearchTimeline query instead. CommunitiesMainPageTimeline is also
tracked as a candidate but is not exposed as a discovery command until X
serves it again. Followers has no verified static fallback and is therefore
not bundled; it becomes available only if the authenticated discovery finds a
current operation. Following and FollowersYouKnow remain separate
operations and are not interchangeable with Followers.
# System status: auth, endpoints, cache health
xsh status
xsh status --check # Validate cached endpoint metadata and required operations
xsh status --local # Skip connectivity and endpoint checks; use local state only
xsh status --json
# Full diagnostic report
xsh doctor
# Print version
xsh version# Clone
git clone https://github.com/benoitpetit/xsh
cd xsh
# Build
go build -o xsh .
# Run the complete test suite
go test ./...
# Build with version info
make build VERSION=v0.1.0core/
├── main.go # Entry point
├── cmd/ # Cobra CLI commands
├── core/ # API client, auth, config, endpoints
├── models/ # Tweet, User, DM, etc.
├── display/ # Terminal formatters
├── utils/ # Helpers (filter, validation, article, delay, hash)
├── browser/ # Browser cookie extraction
└── tests/ # Integration tests
- Go 1.24+
- A Twitter/X account
- A supported browser (for cookie extraction)
- Credentials stored locally with
0600permissions - No data sent to third parties — direct API calls to X/Twitter only
- TLS fingerprinting (uTLS) to avoid bot detection
- Cookie values sanitized per RFC 6265 before use
xsh is a complete rewrite in Go of the original clix Python project. This is not a fork — it is a ground-up reimplementation with a focus on performance, reliability, and modern Go practices.
Made with ❤️ and Go
