原始内容
name: xurl description: A curl-like CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, send or read end-to-end encrypted XChat messages, upload media, or interact with any X API v2 endpoint. Supports multiple apps, OAuth 2.0, OAuth 1.0a, and app-only auth.
xurl — Agent Skill Reference
xurl is a CLI tool for the X API. It supports both shortcut commands (human/agent‑friendly one‑liners) and raw curl‑style access to any v2 endpoint. All commands return JSON to stdout.
Prerequisites
This skill requires the xurl CLI utility: https://github.com/xdevplatform/xurl.
Before using any command you must be authenticated. Run xurl auth status to check.
Secret Safety (Mandatory)
- Never read, print, parse, summarize, upload, or send anything under
~/.xurl/(or copies of it) to the LLM context. ~/.xurl/keys.ymlcontains XChat private encryption keys — the strictest no-read rule applies.- Never pass
--pininline in agent/LLM sessions (xurl chat keys restore --pin ...leaks the recovery PIN to context and shell history). Runxurl chat keys restorewithout the flag so the PIN is prompted without echo, or have the user run it manually. - Never ask the user to paste credentials/tokens into chat.
- The user must fill
~/.xurl/auth.ymlwith required secrets manually on their own machine. - Do not recommend or execute auth commands with inline secrets in agent/LLM sessions.
- Warn that using CLI secret options in agent sessions can leak credentials (prompt/context, logs, shell history).
- Never use
--verbose/-vin agent/LLM sessions; it can expose sensitive headers/tokens in output. - Never run
xurl tokenin agent/LLM sessions: it prints a live OAuth2 access token to stdout, which is a credential and must not enter the LLM context. xurl mcpis for configuring an MCP client (it bridges stdio↔HTTP and injects the bearer token); it is not something to invoke directly from an agent/LLM session.- Sensitive flags that must never be used in agent commands:
--bearer-token,--consumer-key,--consumer-secret,--access-token,--token-secret,--client-id,--client-secret. - To verify whether at least one app with credentials is already registered, run:
xurl auth status.
Register an app (recommended)
App credential registration must be done manually by the user outside the agent/LLM session. After credentials are registered, authenticate against the app that holds those credentials:
xurl auth oauth2 --app APP_NAME
You can also run xurl auth default APP_NAME first and then use xurl auth oauth2.
On a remote/headless machine (no reachable browser callback), add --headless: xurl auth oauth2 --app APP_NAME --headless prints the authorization URL and reads the pasted redirect URL (or code) back, so no localhost callback is needed.
For multiple pre-configured apps, switch between them:
xurl auth default prod-app # set default app
xurl auth default prod-app alice # set default app + user
xurl --app dev-app /2/users/me # one-off override
xurl auth apps redirect-uri get prod-app
xurl auth apps redirect-uri set prod-app http://localhost:8080/callback
Other auth methods
Examples with inline secret flags are intentionally omitted. If OAuth1 or app-only auth is needed, the user must run those commands manually outside agent/LLM context.
Tokens are persisted to ~/.xurl/auth.yml in YAML format (a legacy single-file ~/.xurl is migrated automatically). Each app has its own isolated tokens and may also store a redirect_uri. REDIRECT_URI in the environment still takes precedence over the stored app value. Do not read this file (or anything under ~/.xurl/) through the agent/LLM. Once authenticated, every command below will auto‑attach the right Authorization header.
Quick Reference
| Action | Command |
|---|---|
| Post | xurl post "Hello world!" |
| Reply | xurl reply POST_ID "Nice post!" |
| Quote | xurl quote POST_ID "My take" |
| Delete a post | xurl delete POST_ID |
| Read a post | xurl read POST_ID |
| Search posts | xurl search "QUERY" -n 10 |
| Who am I | xurl whoami |
| Look up a user | xurl user @handle |
| List a user's posts | xurl posts @handle -n 10 |
| Home timeline | xurl timeline -n 20 |
| Mentions | xurl mentions -n 10 |
| Like | xurl like POST_ID |
| Unlike | xurl unlike POST_ID |
| Repost | xurl repost POST_ID |
| Undo repost | xurl unrepost POST_ID |
| Bookmark | xurl bookmark POST_ID |
| Remove bookmark | xurl unbookmark POST_ID |
| List bookmarks | xurl bookmarks -n 10 |
| List likes | xurl likes -n 10 |
| Follow | xurl follow @handle |
| Unfollow | xurl unfollow @handle |
| List following | xurl following -n 20 |
| List followers | xurl followers -n 20 |
| Block | xurl block @handle |
| Unblock | xurl unblock @handle |
| Mute | xurl mute @handle |
| Unmute | xurl unmute @handle |
| Send DM | xurl dm @handle "message" |
| List DMs | xurl dms -n 10 |
| Upload media | xurl media upload path/to/file.mp4 |
| Media status | xurl media status MEDIA_ID |
| Encrypted Chat (XChat) | |
| Chat key status | xurl chat keys status |
| Restore chat keys | xurl chat keys restore (PIN prompted; never pass --pin in agent sessions) |
| Import chat keys | xurl chat keys import (blob prompted; avoid passing it as an argument) |
| List chat inbox | xurl chat conversations |
| Read a conversation | xurl chat read @handle -n 50 |
| Send encrypted message | xurl chat send @handle "message" |
| Listen for new messages | xurl chat listen @handle |
| Rotate a conversation key | xurl chat rotate CONV --yes (write op — see notes) |
| Send with an attachment | xurl chat send CONV "text" --file path/to/img.png |
| Reply to a message | xurl chat send CONV "text" --reply-to SEQUENCE_ID |
| Download an attachment | xurl chat download CONV MEDIA_HASH_KEY -o out.png |
| Add group members | xurl chat add-members GROUP @user --yes (write op) |
| Mark read (explicit) | xurl chat mark-read CONV |
| Typing indicator (explicit) | xurl chat typing CONV |
| App Management | |
| Register app | Manual, outside agent (do not pass secrets via agent) |
| List apps | xurl auth apps list |
| Update app config | Manual, outside agent (do not pass secrets via agent) |
| View app redirect URI | xurl auth apps redirect-uri get [NAME] |
| Set app redirect URI | xurl auth apps redirect-uri set NAME URI |
| Remove app | xurl auth apps remove NAME |
| Set default (interactive) | xurl auth default |
| Set default (command) | xurl auth default APP_NAME [USERNAME] |
| Use app per-request | xurl --app NAME /2/users/me |
| Auth status | xurl auth status |
Post IDs vs URLs: Anywhere
POST_IDappears above you can also paste a full post URL (e.g.https://x.com/user/status/1234567890) — xurl extracts the ID automatically.
Usernames: Leading
@is optional.@elonmuskandelonmuskboth work.
Command Details
Posting
# Simple post
xurl post "Hello world!"
# Post with media (upload first, then attach)
xurl media upload photo.jpg # → note the media_id from response
xurl post "Check this out" --media-id MEDIA_ID
# Multiple media
xurl post "Thread pics" --media-id 111 --media-id 222
# Reply to a post (by ID or URL)
xurl reply 1234567890 "Great point!"
xurl reply https://x.com/user/status/1234567890 "Agreed!"
# Reply with media
xurl reply 1234567890 "Look at this" --media-id MEDIA_ID
# Quote a post
xurl quote 1234567890 "Adding my thoughts"
# Delete your own post
xurl delete 1234567890
Reading
# Read a single post (returns author, text, metrics, entities)
xurl read 1234567890
xurl read https://x.com/user/status/1234567890
# Search recent posts (default 10 results)
xurl search "golang"
xurl search "from:elonmusk" -n 20
xurl search "#buildinpublic lang:en" -n 15
User Info
# Your own profile
xurl whoami
# Look up any user
xurl user elonmusk
xurl user @XDevelopers
# List a user's recent posts (by @username)
xurl posts elonmusk
xurl posts @XDevelopers -n 25
Timelines & Mentions
# Home timeline (reverse chronological)
xurl timeline
xurl timeline -n 25
# Your mentions
xurl mentions
xurl mentions -n 20
Engagement
# Like / unlike
xurl like 1234567890
xurl unlike 1234567890
# Repost / undo
xurl repost 1234567890
xurl unrepost 1234567890
# Bookmark / remove
xurl bookmark 1234567890
xurl unbookmark 1234567890
# List your bookmarks / likes
xurl bookmarks -n 20
xurl likes -n 20
Social Graph
# Follow / unfollow
xurl follow @XDevelopers
xurl unfollow @XDevelopers
# List who you follow / your followers
xurl following -n 50
xurl followers -n 50
# List another user's following/followers
xurl following --of elonmusk -n 20
xurl followers --of elonmusk -n 20
# Block / unblock
xurl block @spammer
xurl unblock @spammer
# Mute / unmute
xurl mute @annoying
xurl unmute @annoying
Direct Messages
# Send a DM
xurl dm @someuser "Hey, saw your post!"
# List recent DM events
xurl dms
xurl dms -n 25
Encrypted Chat (XChat)
xurl chat is an end-to-end encrypted XChat client: encryption and decryption happen locally via the chat-xdk crypto library, so the server only sees ciphertext. Requires OAuth2 user auth with dm.read + dm.write scopes, and is available on macOS (Intel/Apple Silicon) and Linux amd64 when built with cgo (prebuilt release binaries ship a stub that says so).
Keys come from another XChat client — xurl never generates or registers encryption keys. The account must already have keys (e.g. from the X app); bring them to this machine once with restore (Juicebox PIN recovery) or import (an exported key blob). Private keys are stored in ~/.xurl/keys.yml (mode 600) — never read that file into LLM context.
A conversation is addressed by @username, a bare user id, or a conversation id (1:1 ids look like 123-456; group ids look like g123). Every command accepts -u USERNAME to act as a specific authenticated account.
# 1. Keys — one-time setup (xurl never generates/registers keys)
xurl chat keys status # local key presence/fingerprint + registered versions
xurl chat keys restore # recover from Juicebox; prompts for the PIN (no echo)
xurl chat keys import # paste an exported private-key blob (no echo)
# 2. Browse the inbox
xurl chat conversations # pretty list; decrypts group names when keys are present
xurl chat conversations --json # raw JSON
# 3. Read history (oldest first; auto-marks the conversation read)
xurl chat read @someuser
xurl chat read g1234567890 -n 50 # -n = how many events to fetch (max 100)
xurl chat read @someuser --json # decrypted events as JSON (each has id, sequence_id, content)
xurl chat read @someuser --no-mark-read # read without sending a read receipt
# 4. Send (a new 1:1 sets up its key automatically; both sides need keys)
xurl chat send @someuser "hey, encrypted!"
xurl chat send @someuser "look" --file ./photo.png # attach an encrypted file
xurl chat send @someuser "agreed" --reply-to SEQUENCE_ID # threaded reply (id from `read --json`)
# send auto-sends a typing indicator first and marks read after;
# suppress with --no-typing / --no-mark-read
# 5. Attachments — inbound messages show "📎 attachment <media_hash_key>"
xurl chat download @someuser MEDIA_HASH_KEY -o out.png # download + decrypt
# 6. Live tail (poll loop; Ctrl-C to stop; auto-marks new messages read)
xurl chat listen @someuser
xurl chat listen g1234567890 --interval 5
# 7. Read receipts / typing (also happen automatically on read/send)
xurl chat mark-read @someuser # mark read up to the newest message
xurl chat typing @someuser # send a typing indicator
# 8. Group key management (writes visible to all participants)
xurl chat add-members g123 @newuser # add a member (rotates the key; prompts, or --yes)
xurl chat rotate g123 # rotate the conversation key; prompts, or --yes
# Rotate when a key may be exposed, or to grant a member whose keys were
# registered after the last rotation access going forward. Future messages
# only — old history stays readable only to holders of the old key versions.
Notes for agents:
- Messages whose authorship signature cannot be verified are rejected by default and surface as stderr decrypt warnings; unsigned messages that still render carry a red
[unverified]marker — treat those with suspicion. - Messages with attachments render a
📎 attachment <media_hash_key>marker; pass that hash key toxurl chat download CONV <media_hash_key>to fetch and decrypt the file. Replies show a↩prefix. readandlistenmark the conversation read automatically (a read receipt visible to other participants);sendalso marks read and sends a typing indicator first. These are writes — pass--no-mark-read/--no-typingto suppress them (e.g. to read without signaling). The standalonemark-readandtypingcommands remain for scripted/explicit use.- Decrypt warnings for individual events go to stderr and are non-fatal; the rest of the conversation still renders.
- If a command reports missing keys, do not attempt to generate or register any — tell the user to run
xurl chat keys restore(orimport) themselves. chat rotateandchat add-membersare writes visible to every participant's clients; never run them without explicit user intent, and prefer letting the user confirm the prompt over passing--yes.
Media Upload
# Upload a file (auto‑detects type for images/videos)
xurl media upload photo.jpg
xurl media upload video.mp4
# Specify type and category explicitly
xurl media upload --media-type image/jpeg --category tweet_image photo.jpg
# Check processing status (videos need server‑side processing)
xurl media status MEDIA_ID
xurl media status --wait MEDIA_ID # poll until done
# Full workflow: upload then post
xurl media upload meme.png # response includes media id
xurl post "lol" --media-id MEDIA_ID
Global Flags
These flags work on every command:
| Flag | Short | Description |
|---|---|---|
--app |
Use a specific registered app for this request (overrides default) | |
--auth |
Force auth type: oauth1, oauth2, or app |
|
--username |
-u |
Which OAuth2 account to use (if you have multiple) |
--verbose |
-v |
Forbidden in agent/LLM sessions (can leak auth headers/tokens) |
Raw API Access
The shortcut commands cover the most common operations. For anything else, use xurl's raw curl‑style mode — it works with any X API v2 endpoint:
# GET request (default)
xurl /2/users/me
# POST with JSON body
xurl -X POST /2/tweets -d '{"text":"Hello world!"}'
# PUT, PATCH, DELETE
xurl -X DELETE /2/tweets/1234567890
# Custom headers
xurl -H "Content-Type: application/json" /2/some/endpoint
# Force streaming mode
xurl -s /2/tweets/search/stream
# Full URLs also work
xurl https://api.x.com/2/users/me
Streaming
Streaming endpoints are auto‑detected. Known streaming endpoints include:
/2/tweets/search/stream/2/tweets/sample/stream/2/tweets/sample10/stream
You can force streaming on any endpoint with -s:
xurl -s /2/some/endpoint
Output Format
All commands return JSON to stdout, pretty‑printed with syntax highlighting. The output structure matches the X API v2 response format. A typical response looks like:
{
"data": {
"id": "1234567890",
"text": "Hello world!"
}
}
Errors are also returned as JSON:
{
"errors": [
{
"message": "Not authorized",
"code": 403
}
]
}
Common Workflows
Post with an image
# 1. Upload the image
xurl media upload photo.jpg
# 2. Copy the media_id from the response, then post
xurl post "Check out this photo!" --media-id MEDIA_ID
Reply to a conversation
# 1. Read the post to understand context
xurl read https://x.com/user/status/1234567890
# 2. Reply
xurl reply 1234567890 "Here are my thoughts..."
Search and engage
# 1. Search for relevant posts
xurl search "topic of interest" -n 10
# 2. Like an interesting one
xurl like POST_ID_FROM_RESULTS
# 3. Reply to it
xurl reply POST_ID_FROM_RESULTS "Great point!"
Check your activity
# See who you are
xurl whoami
# Check your mentions
xurl mentions -n 20
# Check your timeline
xurl timeline -n 20
Set up multiple apps
# App credentials must already be configured manually outside agent/LLM context.
# Authenticate users on each pre-configured app
xurl auth default prod
xurl auth oauth2 # authenticates on prod app
xurl auth default staging
xurl auth oauth2 # authenticates on staging app
# Switch between them
xurl auth default prod alice # prod app, alice user
xurl --app staging /2/users/me # one-off request against staging
Error Handling
- Non‑zero exit code on any error.
- API errors are printed as JSON to stdout (so you can still parse them).
- Auth errors suggest re‑running
xurl auth oauth2or checking your tokens. - If a command requires your user ID (like, repost, bookmark, follow, etc.), xurl will automatically fetch it via
/2/users/me. When that endpoint is unreliable, use--username USERNAMEor authenticate withxurl auth oauth2 --app APP_NAME USERNAMEso xurl can fall back to username lookup. - If X returns
client-forbidden/client-not-enrolledafter successful auth, check the app’s X developer-console package and environment. In current testing, moving the app toPay-per-useandProductionfixed/2/*read failures without changing localxurlauth data.
Notes
- Rate limits: The X API enforces rate limits per endpoint. If you get a 429 error, wait and retry. Write endpoints (post, reply, like, repost) have stricter limits than read endpoints.
- Scopes: OAuth 2.0 tokens are requested with broad scopes. If you get a 403 on a specific action, your token may lack the required scope — re‑run
xurl auth oauth2to get a fresh token. - Token refresh: OAuth 2.0 tokens auto‑refresh when expired. No manual intervention needed.
- Multiple apps: Each app has its own isolated credentials, tokens, and optional stored
redirect_uri. Configure credentials manually outside agent/LLM context, then switch withxurl auth defaultor--app. - Redirect URI precedence: The effective redirect URI resolves from
REDIRECT_URIin the environment first, then the app's storedredirect_uriin~/.xurl/auth.yml, then the built-in default. - Redirect URI management: Use
xurl auth apps redirect-uri get [NAME],xurl auth apps redirect-uri set NAME URI, orxurl auth apps update NAME --redirect-uri URIto inspect and manage the stored per-app callback value. - X platform enrollment: A successful OAuth callback does not guarantee
/2/*reads will work. If you seeclient-not-enrolled, verify the app is in the correct X package/environment. Current confirmed fix:Apps->Manage apps->Move to package-> choosePay-per-use, then move the app toProduction. - Multiple accounts: You can authenticate multiple OAuth 2.0 accounts per app and switch between them with
--username/-uor set a default withxurl auth default APP USER. - Default user: When no
-uflag is given, xurl uses the default user for the active app (set viaxurl auth default). If no default user is set, it uses the first available token. - Token storage:
~/.xurlis a directory;~/.xurl/auth.ymlholds each app's credentials and tokens. Never read or send anything under~/.xurl/to LLM context. - Chat key storage:
~/.xurl/keys.ymlholds XChat private encryption keys per user (mode 600). Losing it means losing the ability to decrypt on this machine (recoverable viaxurl chat keys restoreif a Juicebox PIN backup exists). Never read or send this file to LLM context. - Chat key registration: xurl performs none — no public-key registration and no Juicebox writes. Only keys already registered by another XChat client can be restored or imported; unregistered keys are rejected.
- Access tokens:
xurl tokenprints a valid (refreshed) OAuth2 access token for the active app to stdout, refreshing and persisting it if expired. It never opens a browser. The output is a secret — use it only in the user's own scripts, never in agent/LLM sessions. - MCP bridge:
xurl mcp [URL]bridges a stdio MCP client to a remote Streamable HTTP MCP server (defaulthttps://api.x.com/mcp), injectingAuthorization: Bearer <token>and refreshing the token automatically. On first run with no cached token it opens the browser for a one-time OAuth2 login using theCLIENT_ID/CLIENT_SECRETfrom its environment (the handshake waits for it, so set a generousstartup_timeout_sec); on a headless host, authenticate out-of-band first withxurl auth oauth2 --headless. Configure it in an MCP client via the npm launcher:{"command":"npx","args":["-y","@xdevplatform/xurl","mcp","https://api.x.com/mcp"],"env":{"CLIENT_ID":"...","CLIENT_SECRET":"..."},"startup_timeout_sec":300}.