x-cli is a terminal-first CLI for personal X workflows.
It separates read and write surfaces:
- Read commands use official X API v2 endpoints with a user/app token.
- Canonical post commands use official X API v2 create Post and media upload endpoints.
- Live posting is gated by
X_ENABLE_LIVE_POST=1andX_CLI_LIVE_PROOF=1because X API access is pay-per-use and posting changes the account. - Account-changing actions default to dry-run and require
--liveplusX_ENABLE_LIVE_ACTION=1. - Browser/session cookies can be reported for diagnostics, but token values and cookie values are never printed.
Implemented and covered by focused tests:
x-cli auth-status --jsonx-cli read feed --limit 5 --jsonx-cli read profile --me --jsonx-cli read profile --username xdevelopers --jsonx-cli search "agent workflow" --limit 10 --jsonx-cli post text --text "..." --dry-run --jsonx-cli post text --text "..." --jsonx-cli post media --text "..." --media image.png --dry-run --jsonx-cli post media --text "..." --media image.png --jsonx-cli like|unlike|repost|unrepost|bookmark|unbookmark <post-id> --jsonx-cli comment <post-id> "reply text" --jsonsns-json-v1success/error envelopes--output/-oJSON file output for contract commands- Live post and live action safety gates
- This project is not affiliated with X Corp.
- Official posting uses
POST https://api.x.com/2/tweets. - Media posting uploads one image through
POST https://api.x.com/2/media/upload, then references the returned media id inPOST https://api.x.com/2/tweets. - Likes, reposts, and bookmarks use official user-context X API v2 endpoints.
- Programmatic replies are restricted by X policy and permissions. Treat
comment --liveas account-risky. - X API access may be pay-per-use. Review billing and pricing before enabling live posts.
- Do not use this project for spam, bulk automation, engagement loops, or anything that violates platform rules.
Official API commands resolve OAuth credentials from:
X_ACCESS_TOKENX_BEARER_TOKENX_OAUTH_FILE~/.config/x/oauth.json
Token file shape:
{
"access_token": "...",
"live_post_enabled": false,
"live_action_enabled": false
}Live post requires a token plus:
export X_ENABLE_LIVE_POST=1
export X_CLI_LIVE_PROOF=1live_post_enabled can also come from the token file, but X_CLI_LIVE_PROOF=1 must stay an explicit per-run environment variable:
{
"access_token": "...",
"live_post_enabled": true
}Live account actions require --live and one of:
export X_ENABLE_LIVE_ACTION=1or:
{
"access_token": "...",
"live_action_enabled": true
}Optional browser/session diagnostics:
export X_COOKIE_HEADER='auth_token=...; ct0=...'
# or write a private cookie header file:
mkdir -p ~/.config/x
chmod 700 ~/.config/x
printf '%s\n' 'auth_token=...; ct0=...' > ~/.config/x/cookies.txt
chmod 600 ~/.config/x/cookies.txtauth-status --json reports cookie names and count only, never cookie values.
uv run x-cli auth-status --json
uv run x-cli read feed --limit 5 --json
uv run x-cli read profile --me --json
uv run x-cli read profile --username xdevelopers --json
uv run x-cli search "agent workflow" --limit 10 --json
uv run x-cli post text --text "hello" --dry-run --json
uv run x-cli post text --text "hello" --dry-run --json --output tmp/x-post-text-dry-run.json
X_ENABLE_LIVE_POST=1 X_CLI_LIVE_PROOF=1 uv run x-cli post text --text "hello" --json
uv run x-cli post media --text "hello with image" --media tests/fixtures/sample.png --dry-run --json
uv run x-cli like 1234567890 --json
uv run x-cli like 1234567890 --live --json
uv run x-cli repost 1234567890 --json
uv run x-cli bookmark 1234567890 --json
uv run x-cli comment 1234567890 "reply text" --jsonDry-run examples make no network request:
uv run x-cli post text --text "hello" --dry-run --json
uv run x-cli post media --text "hello" --media tests/fixtures/sample.png --dry-run --json
uv run x-cli like 1234567890 --jsonFinal live proof shape:
uv run pytest -q
uv run ruff check .
uv run python -m compileall x_cli tests
uv run x-cli auth-status --json
uv run x-cli read feed --limit 3 --json
uv run x-cli read profile --me --json
uv run x-cli post text --text "x-cli dry-run verification" --dry-run --json
uv run x-cli post text --text "x-cli dry-run verification" --dry-run --json --output tmp/x-post-text-dry-run.json
uv run x-cli post media --text "x-cli media dry-run verification" --media tests/fixtures/sample.png --dry-run --json
X_ENABLE_LIVE_POST=1 X_CLI_LIVE_PROOF=1 uv run x-cli post text --text "x-cli live verification $(date -u +%Y%m%dT%H%M%SZ)" --jsonuv run pytest -q
uv run ruff check .
uv run python -m compileall x_cli tests