From hamel-tools
CLI for X (Twitter) API v2: fetch following lists, diff snapshots for follow changes, get likes/bookmarks/latest posts, screenshot tweets with Playwright.
npx claudepluginhub hamelsmu/hamel --plugin hamel-toolsThis skill uses the workspace's default tool permissions.
Use the `xapi` CLI tool to interact with X (Twitter) via the official API v2. No cookies or scraping required.
Interact with Twitter/X via twclaw CLI: read tweets/threads/replies/users/timelines, search/trending, post/reply/quote with media, like/retweet/bookmark/follow, manage lists.
Fetches recent tweets, likes, and media from X (Twitter) users by @username. Supports ID lookup, paginated timelines, and bulk extraction of full post/likes/media history.
Provides X/Twitter CLI access via bird for reading tweets/threads, timelines, search, bookmarks, likes, lists, and engagement using GraphQL + cookie auth.
Share bugs, ideas, or general feedback.
Use the xapi CLI tool to interact with X (Twitter) via the official API v2. No cookies or scraping required.
Backward-compatible alias: twitter also points to the same CLI.
Get one from the X Developer Portal:
export X_BEARER_TOKEN='your_token_here'
The likes and bookmarks endpoints require an OAuth 2.0 user access token — app-only Bearer Tokens are not supported for these.
http://localhost:3000/callback)like.read, bookmark.read, tweet.read, users.read, offline.accessexport X_ACCESS_TOKEN='your_user_access_token_here'
export X_REFRESH_TOKEN='your_refresh_token_here' # optional, for future token refresh support
| Command | Description |
|---|---|
xapi resolve <username> | Resolve username to user ID + profile info |
xapi fetch <username> | Fetch and snapshot the full following list |
xapi diff <snap_a> <snap_b> | Compare two snapshots for follows/unfollows |
xapi latest-post <username> | Fetch most recent original post for an account |
xapi likes [username] | Fetch liked tweets (default: HamelHusain, requires X_ACCESS_TOKEN) |
xapi bookmarks [username] | Fetch bookmarked tweets (default: HamelHusain, requires X_ACCESS_TOKEN) |
xapi screenshot <url> | Take a screenshot of a tweet (requires Playwright) |
xapi resolve elonmusk
# Auto-saves to ~/.x/<username>_<timestamp>.json
xapi fetch elonmusk
# Save to a specific file
xapi fetch elonmusk -o /tmp/elon_following.json
# Verbose mode shows progress
xapi fetch elonmusk -v
Compare two snapshots to detect new follows and unfollows:
# Basic diff
xapi diff snapshot_before.json snapshot_after.json
# Save diff output to file
xapi diff before.json after.json -o diff_result.json
# Also fetch the latest post for each unfollowed account
# (uses the newer snapshot's timestamp as cutoff)
xapi diff before.json after.json --fetch-posts -v
xapi latest-post someuser
# Get the latest post before a specific date
xapi latest-post someuser --before 2026-01-15T00:00:00Z
# Get the latest post before a specific post ID
xapi latest-post someuser --before-post 1234567890
# Save to file
xapi latest-post someuser -o latest.json
# Fetch liked tweets for the default user (HamelHusain)
xapi likes
# Fetch for a different user
xapi likes someuser
# Limit to 20 most recent likes
xapi likes --limit 20
# Save to file
xapi likes -o likes.json
# Fetch bookmarked tweets for the default user (HamelHusain)
xapi bookmarks
# Fetch for a different user (must be your own account)
xapi bookmarks yourusername
# Limit and save
xapi bookmarks --limit 50 -o bookmarks.json
# Screenshot a tweet (saves to tweet_<id>.png)
xapi screenshot https://x.com/user/status/1234567890
# Save to specific file
xapi screenshot https://x.com/user/status/1234567890 -o my_tweet.png
# Full page screenshot
xapi screenshot https://x.com/user/status/1234567890 --full-page
# Wait longer for slow loading tweets
xapi screenshot https://x.com/user/status/1234567890 --wait 5 -v
| Option | Short | Description |
|---|---|---|
--output | -o | Output file path (default: stdout or auto-generated) |
--dir | -d | Snapshot directory for fetch (default: ~/.x/) |
--verbose | -v | Show progress information |
--fetch-posts | -p | Fetch latest post for unfollowed accounts (diff only) |
--before | ISO 8601 datetime cutoff for latest-post | |
--before-post | Post ID cutoff for latest-post (returns posts older than this ID) | |
--limit | -n | Maximum number of tweets to fetch (likes/bookmarks) |
--full-page | -f | Capture full page (screenshot only) |
--wait | -w | Seconds to wait for page load (screenshot only) |
{
"watched_username": "elonmusk",
"watched_user_id": "44196397",
"fetch_timestamp": "2026-01-01T00:00:00+00:00",
"following_count": 500,
"following": [
{
"id": "12345",
"username": "someuser",
"name": "Some User",
"description": "Bio text",
"public_metrics": { "followers_count": 1000, "following_count": 200, "tweet_count": 5000 }
}
]
}
{
"snapshot_before": { "file": "...", "watched_username": "...", "fetch_timestamp": "...", "following_count": 500 },
"snapshot_after": { "file": "...", "watched_username": "...", "fetch_timestamp": "...", "following_count": 502 },
"new_follows_count": 3,
"unfollows_count": 1,
"new_follows": [ { "id": "...", "username": "...", "name": "..." } ],
"unfollows": [ { "id": "...", "username": "...", "name": "...", "latest_post": { "post_id": "...", "text": "...", "url": "..." } } ]
}
[
{
"id": "1234567890",
"text": "Tweet content here...",
"created_at": "2026-01-01T00:00:00Z",
"author": { "id": "987654321", "username": "example_user", "name": "Example User" },
"public_metrics": { "like_count": 100, "retweet_count": 10 },
"url": "https://x.com/example_user/status/1234567890"
}
]
X_BEARER_TOKEN — X API Bearer Token (required for resolve, fetch, diff, latest-post; also used by likes/bookmarks to resolve usernames)X_ACCESS_TOKEN — OAuth 2.0 user access token (required for likes and bookmarks)X_REFRESH_TOKEN — OAuth 2.0 refresh token (optional, for future token refresh support)X_DEFAULT_USERNAME — Override the default username for likes/bookmarks (optional, default: HamelHusain)If a required variable is missing, the CLI exits with a clear error explaining what is needed and how to set it up.
The likes and bookmarks commands default to HamelHusain if no username is provided.
Three ways to override (pick one):
Environment variable (recommended for skill installers): Set X_DEFAULT_USERNAME to change the default for all invocations without editing code:
export X_DEFAULT_USERNAME='yourusername'
Add this to your ~/.bashrc or ~/.zshrc to persist it.
Per-invocation: Pass a username argument to override just that call:
xapi likes otherusername
xapi bookmarks otherusername
Edit source code: Change _FALLBACK_USERNAME at the top of hamel/x_cli.py:
_FALLBACK_USERNAME = "YourUsername"
The hamel package must be installed: pip install hamel
For xapi screenshot: Playwright is included as a dependency, but you need to install browser binaries:
playwright install chromium
After installing and setting the environment variables, run these commands to confirm everything works:
# 1. Check the CLI is installed
xapi --help
# 2. Test API access (lightweight call — uses X_BEARER_TOKEN)
xapi resolve github
# 3. Test latest post
xapi latest-post someuser
# 4. Test likes (requires X_ACCESS_TOKEN, defaults to HamelHusain)
xapi likes --limit 5
# 5. Test bookmarks (requires X_ACCESS_TOKEN, defaults to HamelHusain)
xapi bookmarks --limit 5
If xapi resolve github returns a JSON object with "username": "github", your Bearer Token is working.
If xapi likes returns JSON, your OAuth 2.0 user access token is working.
X_ACCESS_TOKEN. These endpoints use OAuth 2.0 user-context auth and do not work with X_BEARER_TOKEN alone. You need to complete the OAuth 2.0 PKCE flow and set X_ACCESS_TOKEN. Required scopes: like.read (likes), bookmark.read (bookmarks), tweet.read, users.read.--before with older dates will return no results.The X API has rate limits per 15-minute window. The tool automatically handles rate limiting by waiting and retrying. For large following lists (>15k), fetches may take several minutes due to pagination and rate limits.
"X_BEARER_TOKEN environment variable is not set": Get a Bearer Token from the X Developer Portal and export it.
"API error 401": Your Bearer Token may be invalid or expired. Regenerate it in the Developer Portal.
"X_ACCESS_TOKEN environment variable is not set": Complete the OAuth 2.0 PKCE flow in the Developer Portal and set the token. See Setup section above.
"API error 403": For likes/bookmarks, ensure X_ACCESS_TOKEN is set (app-only Bearer Tokens are not supported). For other endpoints, check your app's access level in the Developer Portal.
"API error 429": Rate limited. The tool waits automatically, but if it persists, wait 15 minutes.
Empty following list: The account may be protected. App-only auth cannot access protected accounts' following lists.
playwright not installed: For screenshots, install with pip install playwright && playwright install chromium.
Monitor follows over time:
xapi fetch someuser -o ~/snapshots/day1.json
# ... later ...
xapi fetch someuser -o ~/snapshots/day2.json
xapi diff ~/snapshots/day1.json ~/snapshots/day2.json --fetch-posts -v
Download likes and analyze with AI:
xapi likes -o /tmp/likes.json
ai-gem "What topics am I most interested in based on my likes?" /tmp/likes.json
Export bookmarks:
xapi bookmarks -o ~/bookmarks-backup.json
Get the last post before a specific tweet:
xapi latest-post someuser --before-post 1234567890