xurl-rs (xr)
xr is a Rust CLI for the X (Twitter) API. It ships 27 high-level shortcut commands, a raw curl-style mode for any
/2/... endpoint, OAuth1 / OAuth2-PKCE / Bearer auth with a multi-app token store at ~/.xurl, chunked media upload,
streaming, typed JSON-schema responses, and typed error envelopes with a next_step an agent can act on.
The binary self-introspects. Treat it as the source of truth: this skill routes you to the binary's helpers and provides
the workflow patterns that the binary can't describe on its own.
Hard guardrail: production credentials
The xr binary on the user's machine is configured against real X API credentials, not a sandbox. Every write
operation (post / reply / quote / delete / like / unlike / repost / unrepost / bookmark / unbookmark / follow / unfollow
/ mute / unmute / dm / media upload) hits production state.
Before any write op:
- Use
--dry-run first to surface input validation errors and confirm intent. Every write verb emits a typed status: "dry_run" envelope when --output json and --dry-run are both set; check would_succeed: true and exit_code: 0. Dry-run validates inputs only: not credentials, not the filesystem, and not the verb's own confirmation gate.
Only the three verbs with no inverse (delete, auth clear, auth apps remove) gate themselves and need
--force even for the preflight when there is no TTY; every other write verb, mute and unfollow included,
takes no --force (passing it is invalid-args).
- Confirm scope with the user before issuing the live call when the action is destructive (
delete, unfollow,
mute, dm, post to anything besides a test thread the user already named).
- Prefer
--output json with --no-interactive so failures arrive as structured envelopes you can act on.
Read ops (read, search, whoami, user, timeline, mentions, bookmarks, likes, following, followers,
dms, usage, usage credits, media status, auth status, schema, validate, examples, version) ignore
--dry-run and are safe to run without confirmation.
Quick start: let the binary teach you
The binary ships five self-introspection commands. Reach for them before reading anything in references/:
xr examples # curated invocation gallery, ~130 lines, every major workflow
xr <command> --help # 3-5 examples per command + full flag matrix
xr schema --list # 35 typed response shapes, one per command
xr schema post --output json # JSON Schema for a single response type
xr schema --envelope --output json # the ok / dry_run / error envelope variants and every error key
xr auth status --output json # a bare array of registered apps; read it through .[]
For full read-only-probes-are-always-safe rules, see
references/self-introspection.md.
Read the exit code, then the document
Under --output json, a success is the X API document itself on stdout (data, plus meta / includes / errors
when the API sent them) with no status key; only local verbs (auth …, validate, skill …) add status: "ok".
A failure is a status: "error" envelope on stderr with a non-zero exit, a closed-set kebab-case reason, an
exit_code, and, when a recovery exists, a next_step object. Exit 77 means no usable credential; its
next_step.action is one of register-app / sign-in / select-app / inspect-store / enroll-app, and a command
is safe to run verbatim while a template needs values only the user has:
xr auth status --output json # [ {...}, ... ]; each entry carries client_id_hint and bearer
xr whoami --output json 2>&1 >/dev/null # the failure itself, carrying next_step
Branch on the exit code first, then on reason and next_step.action; never guess a credential fix, and never treat a
missing status on a 0 exit as an error. Full contract, catalog, and the exit-77 recipe:
references/output-envelope.md.
Deterministic helpers (scripts/)
The bundle ships two shellcheck-clean scripts that encode the rules the references describe. Prefer them when you can:
they enforce mechanically what the prose only requests.
| Script |
Use for |
scripts/dry-run-gate.sh [--yes] -- xr <write-verb> [args] |
Every write op. Runs --dry-run preflight, refuses on would_succeed: false or an error envelope (naming its reason), prompts on TTY or honors --yes, then execs the live call. |
scripts/paginate.sh [--max-pages N] -- xr <list-verb> |
Every cursor-paginated read (search, timeline, mentions, bookmarks, likes, following, followers, dms). Streams .data[]? as JSONL, follows meta.next_token. |
Both auto-detect jaq (preferred) or jq. When neither is installed, they emit a PM-aware install advice ranked by
what's already on the system. Install path after xr skill install claude_code (or the host equivalent) is
~/.claude/skills/xurl-rs/scripts/. Full contract: scripts/README.md.
Routing table
| Task |
First action |
| User wants to authenticate |
templates/oauth2-setup.md |
| User wants to post / reply / thread |
scripts/dry-run-gate.sh + templates/post-reply-thread.md |
| User wants to search and pipe to a tool |
scripts/paginate.sh + templates/search-and-process.md |
| User wants to attach media |
templates/media-upload.md |
| Pick an auth mode for a one-off |
references/auth-modes.md |
| Pick output format / pagination / dry-run |
references/agent-flags.md |
| Parse a response or an error |
references/output-envelope.md |
| Look up X API endpoints / scopes / billing |
references/x-api-essentials.md |
Don't know what xr can do |
references/self-introspection.md |
| Stuck, what next? |
references/escalation.md |
Iron rules
- Never invent X API endpoints, scopes, billing tiers, or rate-limit numbers. They change. See
references/escalation.md for the lookup order.
- Never run a live write op without confirming scope with the user first, OR without a successful
--dry-run pass
against the exact same flags first.
- Never paste credentials into chat, commits, PR bodies, or shell history. Pass secrets through env vars
(
XURL_BEARER_TOKEN, --client-secret "$(op read op://...)"); never inline them.
- Read-only probes are always fine:
xr --help, xr <cmd> --help, xr examples, xr schema ..., xr validate < file.json, xr auth status, xr version, xr usage, xr usage credits. No confirmation needed.
Common flag patterns to apply across calls
--output json (or XURL_OUTPUT=json): machine-readable on every command. --output jsonl prints the same whole
document, not one record per line; get per-record lines with jaq -c '.data[]?' (or scripts/paginate.sh).
--no-interactive: fail with a structured envelope instead of prompting.
--no-pager: documented no-op, safe to always pass.
--quiet: suppress human-only banners (errors still go to stderr).
--timeout 30 (the default): bump for streaming / slow networks.
-n <1..100> / --limit: page size for list verbs (search floors at 10, the API's minimum); --cursor <token>
from meta.next_token for the next page.
Full agent-flag matrix and env-var precedence: references/agent-flags.md.
Verifying the install
xr version # prints "xr 3.2.0"; this bundle describes the 3.2.0 release
xr --help # full surface
xr auth status --output json | jaq -r '.[].name' # which apps are registered
A build from the development branch ahead of the 3.2.0 release may add commands and reshape local-verb documents;
xr --help is authoritative for the surface, and the bundle published beside that release describes its contract.
If xr is not on $PATH, install it from https://github.com/brettdavies/xurl-rs/releases, or refresh this bundle
with xr skill update claude_code (or whichever host; xr skill update --all refreshes every known host, cloning into
the ones that had no installation).
Reference index
- references/escalation.md: when stuck, lookup order, iron rules, halt-vs-continue, worked
examples.
- references/self-introspection.md: let the binary teach you (
examples, schema,
validate, usage).
- references/auth-modes.md: OAuth2 PKCE (browser + headless), OAuth1, Bearer, multi-app
token store, the
auth status apps shape, what to do on exit 77.
- references/agent-flags.md: output formats (and what
jsonl really emits), pagination and
clamps, dry-run, verbose, env-var precedence, exit codes.
- references/output-envelope.md: the four document kinds (API document /
ok /
dry_run / error), the closed-set reason catalog, next_step, the exit-77 recipe, exit-code matrix, which schema
validates which document.
- references/x-api-essentials.md: drift-resistant pointers into the X API (auth
scopes, tiers, rate limits, per-category doc URLs).
Templates
- templates/oauth2-setup.md: first-time OAuth2 (browser or headless), verify with
xr auth status.
- templates/post-reply-thread.md: compose, capture id, thread; leads with
scripts/dry-run-gate.sh.
- templates/search-and-process.md:
xr search --output json | jaq -c '.data[]';
leads with scripts/paginate.sh.
- templates/media-upload.md: chunked upload, attach
--media-id via the gate.
Scripts
- scripts/dry-run-gate.sh: preflight → confirm → live wrapper for every
xr write op.
- scripts/paginate.sh: cursor-pagination loop for any
xr list-style verb.
- scripts/README.md: full contract, exit codes, invocation patterns, jaq/jq fallback notes.
Producer-side notes
This file is the consumer entry point and is loaded into the agent's context when the skill activates. Producer-side
notes for agents working on this bundle (release flow, branch model, CI, the contract harness that verifies every
claim above against a real binary) live in AGENTS.md. Don't conflate the two.
1---2name: xurl-rs3description: Drive the X (Twitter) API from the command line via `xr`, the xurl-rs CLI. Use when the user wants to post or thread, reply, quote, delete, like, repost, bookmark, follow, mute, send DMs, search recent posts, read a timeline or mentions, look up a user, upload media, stream filtered tweets, hit a raw `/2/...` endpoint, manage OAuth2 / OAuth1 / Bearer auth, register multiple X apps, inspect token state, check API usage or credits, or validate a tweet/user JSON payload against the typed response schema. Triggers on "post to X", "post to Twitter", "tweet from CLI", "X API call", "X CLI", "OAuth2 X", "xurl", "xr command", "search tweets", "send DM", "follow on X", "mute on X".4---56# xurl-rs (`xr`)78`xr` is a Rust CLI for the X (Twitter) API. It ships 27 high-level shortcut commands, a raw curl-style mode for any9`/2/...` endpoint, OAuth1 / OAuth2-PKCE / Bearer auth with a multi-app token store at `~/.xurl`, chunked media upload,10streaming, typed JSON-schema responses, and typed error envelopes with a `next_step` an agent can act on.1112The binary self-introspects. Treat it as the source of truth: this skill routes you to the binary's helpers and provides13the workflow patterns that the binary can't describe on its own.1415## Hard guardrail: production credentials1617The `xr` binary on the user's machine is configured against **real X API credentials**, not a sandbox. Every write18operation (post / reply / quote / delete / like / unlike / repost / unrepost / bookmark / unbookmark / follow / unfollow19/ mute / unmute / dm / media upload) hits production state.2021Before any write op:22231. Use `--dry-run` first to surface input validation errors and confirm intent. Every write verb emits a typed `status:24 "dry_run"` envelope when `--output json` and `--dry-run` are both set; check `would_succeed: true` and `exit_code:25 0`. Dry-run validates inputs only: not credentials, not the filesystem, and not the verb's own confirmation gate.26 Only the three verbs with no inverse (`delete`, `auth clear`, `auth apps remove`) gate themselves and need27 `--force` even for the preflight when there is no TTY; every other write verb, `mute` and `unfollow` included,28 takes no `--force` (passing it is `invalid-args`).292. Confirm scope with the user before issuing the live call when the action is destructive (`delete`, `unfollow`,30 `mute`, `dm`, `post` to anything besides a test thread the user already named).313. Prefer `--output json` with `--no-interactive` so failures arrive as structured envelopes you can act on.3233Read ops (`read`, `search`, `whoami`, `user`, `timeline`, `mentions`, `bookmarks`, `likes`, `following`, `followers`,34`dms`, `usage`, `usage credits`, `media status`, `auth status`, `schema`, `validate`, `examples`, `version`) ignore35`--dry-run` and are safe to run without confirmation.3637## Quick start: let the binary teach you3839The binary ships five self-introspection commands. Reach for them before reading anything in `references/`:4041```bash42xr examples # curated invocation gallery, ~130 lines, every major workflow43xr <command> --help # 3-5 examples per command + full flag matrix44xr schema --list # 35 typed response shapes, one per command45xr schema post --output json # JSON Schema for a single response type46xr schema --envelope --output json # the ok / dry_run / error envelope variants and every error key47xr auth status --output json # a bare array of registered apps; read it through .[]48```4950For full read-only-probes-are-always-safe rules, see51[references/self-introspection.md](references/self-introspection.md).5253## Read the exit code, then the document5455Under `--output json`, a **success is the X API document itself** on stdout (`data`, plus `meta` / `includes` / `errors`56when the API sent them) with **no `status` key**; only local verbs (`auth …`, `validate`, `skill …`) add `status: "ok"`.57A **failure** is a `status: "error"` envelope on **stderr** with a non-zero exit, a closed-set kebab-case `reason`, an58`exit_code`, and, when a recovery exists, a `next_step` object. Exit `77` means no usable credential; its59`next_step.action` is one of `register-app` / `sign-in` / `select-app` / `inspect-store` / `enroll-app`, and a `command`60is safe to run verbatim while a `template` needs values only the user has:6162```bash63xr auth status --output json # [ {...}, ... ]; each entry carries client_id_hint and bearer64xr whoami --output json 2>&1 >/dev/null # the failure itself, carrying next_step65```6667Branch on the exit code first, then on `reason` and `next_step.action`; never guess a credential fix, and never treat a68missing `status` on a `0` exit as an error. Full contract, catalog, and the exit-77 recipe:69[references/output-envelope.md](references/output-envelope.md).7071## Deterministic helpers (`scripts/`)7273The bundle ships two shellcheck-clean scripts that encode the rules the references describe. Prefer them when you can:74they enforce mechanically what the prose only requests.7576| Script | Use for |77| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |78| `scripts/dry-run-gate.sh [--yes] -- xr <write-verb> [args]` | Every write op. Runs `--dry-run` preflight, refuses on `would_succeed: false` or an error envelope (naming its `reason`), prompts on TTY or honors `--yes`, then `exec`s the live call. |79| `scripts/paginate.sh [--max-pages N] -- xr <list-verb>` | Every cursor-paginated read (`search`, `timeline`, `mentions`, `bookmarks`, `likes`, `following`, `followers`, `dms`). Streams `.data[]?` as JSONL, follows `meta.next_token`. |8081Both auto-detect `jaq` (preferred) or `jq`. When neither is installed, they emit a PM-aware install advice ranked by82what's already on the system. Install path after `xr skill install claude_code` (or the host equivalent) is83`~/.claude/skills/xurl-rs/scripts/`. Full contract: [scripts/README.md](scripts/README.md).8485## Routing table8687| Task | First action |88| ------------------------------------------ | --------------------------------------------------------------------------------------------- |89| User wants to authenticate | [templates/oauth2-setup.md](templates/oauth2-setup.md) |90| User wants to post / reply / thread | `scripts/dry-run-gate.sh` + [templates/post-reply-thread.md](templates/post-reply-thread.md) |91| User wants to search and pipe to a tool | `scripts/paginate.sh` + [templates/search-and-process.md](templates/search-and-process.md) |92| User wants to attach media | [templates/media-upload.md](templates/media-upload.md) |93| Pick an auth mode for a one-off | [references/auth-modes.md](references/auth-modes.md) |94| Pick output format / pagination / dry-run | [references/agent-flags.md](references/agent-flags.md) |95| Parse a response or an error | [references/output-envelope.md](references/output-envelope.md) |96| Look up X API endpoints / scopes / billing | [references/x-api-essentials.md](references/x-api-essentials.md) |97| Don't know what `xr` can do | [references/self-introspection.md](references/self-introspection.md) |98| Stuck, what next? | [references/escalation.md](references/escalation.md) |99100## Iron rules1011021. **Never invent X API endpoints, scopes, billing tiers, or rate-limit numbers.** They change. See103 [references/escalation.md](references/escalation.md) for the lookup order.1042. **Never run a live write op without confirming scope with the user first**, OR without a successful `--dry-run` pass105 against the exact same flags first.1063. **Never paste credentials into chat, commits, PR bodies, or shell history.** Pass secrets through env vars107 (`XURL_BEARER_TOKEN`, `--client-secret "$(op read op://...)"`); never inline them.1084. **Read-only probes are always fine**: `xr --help`, `xr <cmd> --help`, `xr examples`, `xr schema ...`, `xr validate <109 file.json`, `xr auth status`, `xr version`, `xr usage`, `xr usage credits`. No confirmation needed.110111## Common flag patterns to apply across calls112113- `--output json` (or `XURL_OUTPUT=json`): machine-readable on every command. `--output jsonl` prints the same whole114 document, not one record per line; get per-record lines with `jaq -c '.data[]?'` (or `scripts/paginate.sh`).115- `--no-interactive`: fail with a structured envelope instead of prompting.116- `--no-pager`: documented no-op, safe to always pass.117- `--quiet`: suppress human-only banners (errors still go to stderr).118- `--timeout 30` (the default): bump for streaming / slow networks.119- `-n <1..100>` / `--limit`: page size for list verbs (`search` floors at 10, the API's minimum); `--cursor <token>`120 from `meta.next_token` for the next page.121122Full agent-flag matrix and env-var precedence: [references/agent-flags.md](references/agent-flags.md).123124## Verifying the install125126```bash127xr version # prints "xr 3.2.0"; this bundle describes the 3.2.0 release128xr --help # full surface129xr auth status --output json | jaq -r '.[].name' # which apps are registered130```131132A build from the development branch ahead of the 3.2.0 release may add commands and reshape local-verb documents;133`xr --help` is authoritative for the surface, and the bundle published beside that release describes its contract.134135If `xr` is not on `$PATH`, install it from <https://github.com/brettdavies/xurl-rs/releases>, or refresh this bundle136with `xr skill update claude_code` (or whichever host; `xr skill update --all` refreshes every known host, cloning into137the ones that had no installation).138139## Reference index140141- [references/escalation.md](references/escalation.md): when stuck, lookup order, iron rules, halt-vs-continue, worked142 examples.143- [references/self-introspection.md](references/self-introspection.md): let the binary teach you (`examples`, `schema`,144 `validate`, `usage`).145- [references/auth-modes.md](references/auth-modes.md): OAuth2 PKCE (browser + headless), OAuth1, Bearer, multi-app146 token store, the `auth status` `apps` shape, what to do on exit 77.147- [references/agent-flags.md](references/agent-flags.md): output formats (and what `jsonl` really emits), pagination and148 clamps, dry-run, verbose, env-var precedence, exit codes.149- [references/output-envelope.md](references/output-envelope.md): the four document kinds (API document / `ok` /150 `dry_run` / `error`), the closed-set reason catalog, `next_step`, the exit-77 recipe, exit-code matrix, which schema151 validates which document.152- [references/x-api-essentials.md](references/x-api-essentials.md): drift-resistant pointers into the X API (auth153 scopes, tiers, rate limits, per-category doc URLs).154155## Templates156157- [templates/oauth2-setup.md](templates/oauth2-setup.md): first-time OAuth2 (browser or headless), verify with `xr auth158 status`.159- [templates/post-reply-thread.md](templates/post-reply-thread.md): compose, capture id, thread; leads with160 `scripts/dry-run-gate.sh`.161- [templates/search-and-process.md](templates/search-and-process.md): `xr search --output json | jaq -c '.data[]'`;162 leads with `scripts/paginate.sh`.163- [templates/media-upload.md](templates/media-upload.md): chunked upload, attach `--media-id` via the gate.164165## Scripts166167- [scripts/dry-run-gate.sh](scripts/dry-run-gate.sh): preflight → confirm → live wrapper for every `xr` write op.168- [scripts/paginate.sh](scripts/paginate.sh): cursor-pagination loop for any `xr` list-style verb.169- [scripts/README.md](scripts/README.md): full contract, exit codes, invocation patterns, jaq/jq fallback notes.170171## Producer-side notes172173This file is the *consumer* entry point and is loaded into the agent's context when the skill activates. *Producer-side*174notes for agents working **on** this bundle (release flow, branch model, CI, the contract harness that verifies every175claim above against a real binary) live in [AGENTS.md](AGENTS.md). Don't conflate the two.