Skip to content

StanleyMasinde/twitter

Twitter CLI

Tweet without going to twitter.com

GitHub Downloads (all assets, all releases) Build and test

What it does

I love creating content on Twitter but twitter.com leads to doomscrolling. This is my way of fighting that.

Simple CLI for posting to Twitter using their API v2.

Installation

Unix/Linux

Quick install

Install the latest version with the following command. If you prefer, review the script first.

curl -fsSL https://raw.githubusercontent.com/StanleyMasinde/twitter/main/install.sh | sh

This will automatically

  • Detect your operating system and architecture
  • Download the correct binary
  • Verify the SHA256 checksum for security
  • Install to /usr/local/bin
  • Clean up temporary files

Review the installer before running:

curl -fsSL https://raw.githubusercontent.com/StanleyMasinde/twitter/main/install.sh

Install a specific version

curl -fsSL https://raw.githubusercontent.com/StanleyMasinde/twitter/main/install.sh | sh -s v1.5.0

Custom install location

curl -fsSL https://raw.githubusercontent.com/StanleyMasinde/twitter/main/install.sh | TWITTER_INSTALL=~/.local/bin sh

Updating

sudo twitter update

twitter update downloads the latest release, writes a temporary binary in the install directory, then atomically replaces the current executable.
If twitter is installed in /usr/local/bin (default install location), that directory is root-owned, so updating requires elevated privileges.

If you installed to a user-writable location (for example with TWITTER_INSTALL=~/.local/bin), sudo is not required.

ArchLinux

ArchLinux users can install the community maintained AUR binary package using yay or any other AUR helper:

yay -S twitter-cli

Warning

The AUR package is managed by the community. Please use the install script for other Unix based systems.

After installation the executable is available as twitter

You can also download the appropriate binary for your machine from releases:

Windows

Via PowerShell

Administrator privileges are only needed when writing to a protected directory. Works in both Windows PowerShell 5.1 (powershell) and PowerShell 7+ (pwsh).

Install latest version:

& ([scriptblock]::Create((irm "https://raw.githubusercontent.com/StanleyMasinde/twitter/main/install.ps1")))

Install a specific version:

& ([scriptblock]::Create((irm "https://raw.githubusercontent.com/StanleyMasinde/twitter/main/install.ps1"))) v1.5.0

Custom install location:

$env:TWITTER_INSTALL = "$env:USERPROFILE\bin"
& ([scriptblock]::Create((irm "https://raw.githubusercontent.com/StanleyMasinde/twitter/main/install.ps1")))

Updating

twitter update

twitter update schedules replacement of the current twitter.exe after the running process exits. Run an elevated terminal only if the executable is in a protected directory (for example C:\Program Files).

Note

For the CLI to run on Windows, ensure you have installed the latest C++ redistributable runtime for your architecture. After that installation, open the Windows Terminal and use Twitter CLI by typing twitter.

Build from source on Windows

Use PowerShell:

git clone https://github.com/StanleyMasinde/twitter.git
cd twitter

# Install Rust if needed
winget install --id Rustlang.Rustup -e

# Install and bootstrap vcpkg
# If vcpkg is already installed, skip clone/bootstrap and set VCPKG_ROOT accordingly
git clone https://github.com/microsoft/vcpkg
cd vcpkg
.\bootstrap-vcpkg.bat

# Install native dependencies
$env:VCPKG_ROOT = (Get-Location).Path
[Environment]::SetEnvironmentVariable("VCPKG_ROOT", $env:VCPKG_ROOT, "User")

$oldPath = [Environment]::GetEnvironmentVariable("Path", "User")
if ($oldPath -notlike "*$env:VCPKG_ROOT*") {
  [Environment]::SetEnvironmentVariable("Path", "$oldPath;$env:VCPKG_ROOT", "User")
}
$env:PATH = "$env:VCPKG_ROOT;$env:PATH"

vcpkg install sqlite3 curl
cd ..

# Build
cargo build --release

# If linker errors persist after installing dependencies
cargo clean
cargo build --release

# Install binary to a user PATH location
$dest = "$env:USERPROFILE\bin"
New-Item -ItemType Directory -Force -Path $dest | Out-Null
Copy-Item -Force ".\target\release\twitter.exe" "$dest\twitter.exe"

# Add that directory to PATH (persistent, per-user)
$old = [Environment]::GetEnvironmentVariable("Path","User")
if ($old -notlike "*$dest*") {
  [Environment]::SetEnvironmentVariable("Path","$old;$dest","User")
}

# Make it available in this session
$env:Path += ";$dest"

Configuration

  1. Create a Twitter developer account at developer.twitter.com
  2. Create a new app and get your API credentials

Interactive Setup (Recommended)

Warning

This will override your existing config. Only run it on setup.

twitter config --init

Quick Edit

twitter config --edit

Opens your default editor ($EDITOR) with the config file. Creates ~/.config/twitter_cli/config.toml if it doesn't exist.

Expected format:

# The account that will be used to tweet
# Please note, current account uses 0 based index.
# This means the first account is 0
current_account = 0

# Account 1
[[accounts]]
consumer_key = "your_consumer_key"
consumer_secret = "your_consumer_secret"
access_token = "your_access_token"
access_secret = "your_access_secret"
bearer_token = "your_bearer_token"
client_id = "your_oauth2_client_id"
client_secret = "your_oauth2_client_secret"

# Account 2
[[accounts]]
consumer_key = "your_consumer_key"
consumer_secret = "your_consumer_secret"
access_token = "your_access_token"
access_secret = "your_access_secret"
bearer_token = "your_bearer_token"
client_id = "your_oauth2_client_id"
client_secret = "your_oauth2_client_secret"

On main (not yet released), client_id and client_secret are required config fields for each account. Keep them populated even if you are not using OAuth 2.0-backed commands yet.

Manual Configuration

Create config file at ~/.config/twitter_cli/config.toml with the format above. Keep this file private since it contains API secrets.

Validation

twitter config --show # Visual preview
twitter config --validate # Check for issues

Update App Permissions

If you face a 403 error when tweeting:

  1. In the Twitter Developer Portal, go to your App → User authentication settings
  2. Set App permissions to Read and write
  3. Save changes, then regenerate your Access Token & Secret
  4. Update your config with the new values

NB: Regenerate tokens after updating permissions, otherwise old tokens remain read-only.

Service Unavailable (503) when posting

If media upload works but POST /2/tweets fails with:

{"title":"Service Unavailable","detail":"Service Unavailable","type":"about:blank","status":503}

check the following:

  1. In the X Developer Portal (developer.x.com / console.x.com), confirm your app has active billing and available credits.
  2. Ensure your app permissions are still Read and write, then regenerate Access Token and Secret after permission changes.
  3. Retry a minimal text-only tweet first:
    twitter tweet --body "test"
  4. If text-only works, retry with media:
    twitter tweet --body "test with image" --image ~/path/to/image.png
  5. Check your X API usage dashboard and logs to confirm write calls are not blocked by billing, limits, or temporary platform incidents.

Note: Some 503 responses are transient. If configuration and billing are correct, wait a few minutes and retry.

Usage

Tweet in CLI Mode

Tweet

twitter tweet --body "Building something cool today"

Piped input

echo "I love CLIs" | twitter tweet

From text files

cat drafts.txt | twitter tweet

Edit tweet in an editor

Omit --body and it will launch your default terminal editor. Like Vim or Nano.

twitter tweet

Add Media

The parameter is image because I do not see the point of uploading video from the terminal.

twitter tweet --image ~/Downloads/image.png

Tweet a thread

Threads are created whenever input contains --- separators, regardless of input mode.

twitter tweet --editor

This will launch your default terminal editor. Separate your threads with --- like this.

This is a very long thread. This being the intro.
---
The three dashes indicate the boundary between tweets so this will be tweet 2.
---
This will be tweet 3

Thread via stdin

Pipe thread content with --- separators.

cat thread.txt | twitter tweet

Schedule tweets

Add a scheduled tweet

Use either --on, --at or --in to specify the time. Please note that these are aliases.

twitter schedule new --body "Ship update at 5:06pm" --at "17:06"
twitter schedule new --body "Ship update on Tuesday" --on "Tuesday"
twitter schedule new --body "Ship update in 30 minutes" --in "30 minutes"

List scheduled tweets

twitter schedule list
twitter schedule list --filter failed
twitter schedule list --filter sent

If no rows match your filter, the CLI prints:

No scheduled tweets were found.

Run pending scheduled tweets

twitter schedule run

If none are due, the CLI prints:

No pending scheduled tweets to run.

Pair with your OS scheduler

schedule run is intended to be executed regularly by your OS scheduler.

Run scheduler jobs as the same user who ran twitter config --init. First get your installed binary path:

command -v twitter

Linux (cron):

crontab -e
* * * * * /usr/local/bin/twitter schedule run >> /tmp/twitter-schedule.log 2>&1

If your binary is not in /usr/local/bin/twitter, replace that path with the output of command -v twitter.

macOS (launchd):

<!-- ~/Library/LaunchAgents/com.twitter.schedule.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key><string>com.twitter.schedule</string>
    <key>ProgramArguments</key>
    <array>
      <string>/usr/local/bin/twitter</string>
      <string>schedule</string>
      <string>run</string>
    </array>
    <key>StartInterval</key><integer>60</integer>
    <key>StandardOutPath</key><string>/tmp/twitter-schedule.log</string>
    <key>StandardErrorPath</key><string>/tmp/twitter-schedule.log</string>
  </dict>
</plist>

If your binary is not in /usr/local/bin/twitter, replace that path with the output of command -v twitter. Load it (current user):

launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.twitter.schedule.plist
launchctl kickstart -k gui/$(id -u)/com.twitter.schedule

Windows (Task Scheduler): Use where twitter in PowerShell to find the full path to twitter.exe.

Create a task that runs every minute with:

schtasks /Create /SC MINUTE /MO 1 /TN "TwitterScheduleRunner" /TR "\"C:\Users\<you>\bin\twitter.exe\" schedule run" /F

If your binary is not in C:\Users\<you>\bin\twitter.exe, replace that path with the output of where twitter.

Clear scheduled tweets

twitter schedule clear

The CLI prints how many records were removed, for example:

Cleared 3 scheduled tweets.

Lists

Show a list by id

twitter lists by-id --list-id 1234567890

Create a list

twitter lists create --name "CLI builders"
twitter lists create --name "CLI builders" --description "People building CLI tools"
twitter lists create --name "Private list" --private true

Show the lists you own

User-scoped list commands use the current authenticated user automatically.

twitter lists owned
twitter lists owned --max-results 25

Delete a list

twitter lists delete --list-id 1234567890

Update a list

twitter lists update --list-id 1234567890 --name "Updated name"
twitter lists update --list-id 1234567890 --description "Updated description"
twitter lists update --list-id 1234567890 --private true

Show the members of a list

twitter lists members --list-id 1234567890
twitter lists members --list-id 1234567890 --max-results 25

Add a user to a list

User-scoped list commands use the current authenticated user automatically.

twitter lists add-member --list-id 1234567890 --user-id 987654321

Show the tweets in a list

twitter lists tweets --list-id 1234567890
twitter lists tweets --list-id 1234567890 --max-results 25

Remove yourself from a list

User-scoped list commands use the current authenticated user automatically.

twitter lists remove-member --list-id 1234567890

Show the lists you belong to

The CLI resolves the current authenticated user automatically for user-scoped list endpoints.

twitter lists memberships
twitter lists memberships --max-results 25

Retweets

Show who retweeted a tweet

twitter retweets by --tweet-id 1234567890
twitter retweets by --tweet-id 1234567890 --max-results 25

Retweet a tweet

User-scoped retweet commands use the current authenticated user automatically.

twitter retweets create --tweet-id 1234567890

Remove your retweet

twitter retweets delete --tweet-id 1234567890

DMs

Show DM events for a conversation

twitter dms conversation-events --conversation-id 1234567890-987654321
twitter dms conversation-events --conversation-id 1234567890-987654321 --max-results 25

Show your DM events

twitter dms events
twitter dms events --max-results 25

Show DM events with a participant

twitter dms with --participant-id 123456
twitter dms with --participant-id 123456 --max-results 25

Create a DM conversation

twitter dms create --participant-ids 123456,987654 --text "hello from the CLI"

Send a message by participant id

twitter dms send-with --participant-id 123456 --text "hello from the CLI"

Send a message to an existing conversation

twitter dms send --conversation-id 1234567890-987654321 --text "hello from the CLI"

Mutes

Mute a user

User-scoped mute commands use the current authenticated user automatically.

twitter mutes create --target-user-id 1234567890

Show muted users

User-scoped mute commands use the current authenticated user automatically.

twitter mutes list
twitter mutes list --max-results 25

Unmute a user

User-scoped mute commands use the current authenticated user automatically.

twitter mutes delete --target-user-id 1234567890

Blocks

Block a user

User-scoped block commands use the current authenticated user automatically.

twitter blocks create --target-user-id 1234567890

Show blocked users

User-scoped block commands use the current authenticated user automatically.

twitter blocks list
twitter blocks list --max-results 25

twitter blocks list uses OAuth 2.0 (Authorization Code + PKCE) and reuses cached tokens after first authorization.

Unblock a user

User-scoped block commands use the current authenticated user automatically.

twitter blocks delete --target-user-id 1234567890

Timeline

Fetch your reverse-chronological home timeline.

twitter timeline reverse

Tweet lookup

Fetch a specific tweet by id.

twitter tweets by-id 2006409743426818416

User tweets

Fetch tweets from a user by id.

twitter tweets user --id 2244994945

Recent tweet search

Search recent tweets by query.

twitter tweets recent --query "rustlang"

Full-archive tweet search

Search all tweets by query.

twitter tweets all --query "rustlang"

Mentions

Fetch tweets that mention the currently authenticated user.

twitter mentions

Likes

Show who liked a tweet.

twitter likes by --tweet-id 1234567890
twitter likes by --tweet-id 1234567890 --max-results 25

Like a tweet for the currently authenticated user.

twitter likes create --tweet-id 1234567890

Remove a like for the currently authenticated user.

twitter likes delete --tweet-id 1234567890

Fetch tweets liked by the currently authenticated user.

twitter likes tweets

Users

Fetch a single user by id or username.

twitter users by-id --id 2244994945
twitter users by-username --username jack

Fetch multiple users by ids or usernames.

twitter users by-ids --ids 2244994945,6253282
twitter users by-usernames --usernames jack,openai

Fetch follow relationships.

twitter users following --id 2244994945
twitter users followers --id 2244994945

Manage follows for the currently authenticated user.

twitter users follow --target-user-id 6253282
twitter users unfollow --target-user-id 6253282

Bookmarks

Fetch bookmarks and manage them for the current authenticated user.

twitter bookmarks list
twitter bookmarks create --tweet-id 1234567890
twitter bookmarks delete --tweet-id 1234567890

OAuth behavior:

  • twitter bookmarks list uses OAuth 2.0 (Authorization Code + PKCE) and stores access/refresh tokens in the local cache database.
  • twitter blocks list uses OAuth 2.0 (Authorization Code + PKCE) and shares the same local token cache.
  • twitter bookmarks create, twitter bookmarks delete, twitter bookmarks folders, and twitter bookmarks folder currently use OAuth 1.0a user tokens from your config.

First OAuth 2.0 key exchange for bookmarks list or blocks list (on main, unreleased):

  1. Add client_id and client_secret to your account in ~/.config/twitter_cli/config.toml.
  2. In the X/Twitter Developer Dashboard (App settings), set the callback/redirect URL to http://127.0.0.1:3000.
  3. Run twitter bookmarks list or twitter blocks list.
  4. Open the printed authorization URL in your browser.
  5. After consent, the browser redirect may show an error (for example, server not found). That is expected because no local dev server is required (works for VPS/embedded environments too).
  6. Copy the full callback URL from the browser address bar, paste it in the CLI prompt, then press Enter.
  7. The CLI exchanges the code for tokens and caches them; later runs auto-refresh with the refresh token.

List bookmark folders and fetch the tweets in a folder.

twitter bookmarks folders
twitter bookmarks folder --folder-id 1234567890

Streams

List and manage filtered stream rules.

twitter streams rules list
twitter streams rules add --value "rustlang has:links" --tag "rust"
twitter streams rules delete --ids 1234567890

Connect to the filtered stream.

twitter streams connect
twitter streams connect --backfill-minutes 5

Current user

Fetch details for the currently authenticated user.

twitter me

Example output:

User Id: 123456789
Name: Jane Doe
Username: @janedoe

API Response:

Tweet Id: 2006409743426818416
Tweet body: Hello, world

Show usage

You can show the API usage via the usage subcommand.

twitter usage

The above command will show the API usage like the one below.

Daily project usage: 0/100

Tech Stack

  • Rust
  • Twitter API v2

About

Tweet without going to twitter.com

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

Contributors

Languages