A lightweight X/Twitter client that uses your real browser session tokens to call X's internal GraphQL API. No scraping, no Playwright, no official API keys — just direct HTTP calls.
clawuth tweet "Hello from clawuth!"
clawuth makes it easy to interact with X programmatically using the same requests your browser makes. You extract two cookie values from your browser (takes 30 seconds), and clawuth handles all the auth headers, GraphQL wiring, and response parsing.
No dependencies. Node 18+ built-ins only: fetch, fs, path, readline.
git clone https://github.com/yourname/clawuth
cd clawuth
npm link # makes the `clawuth` command available globallyclawuth setupThe wizard will:
- Tell you to open x.com in your browser
- Show you a DevTools snippet to copy your session tokens
- Ask for an account name and the pasted JSON
- Save everything to
~/.clawuth/accounts.json
clawuth tweet "Hello from clawuth! 🚀"clawuth <command> [args]
| Command | Description |
|---|---|
setup |
Interactive account setup wizard |
accounts list |
List all configured accounts |
accounts add <name> |
Add a new account by name |
accounts default <name> |
Set the default account |
tweet <text> |
Post a tweet |
tweet <text> --reply <id> |
Reply to a tweet |
like <tweetId> |
Like a tweet |
timeline |
Show your home timeline (last 10 tweets) |
search <query> |
Search for tweets |
whoami |
Show the current account info |
import { ClawuthClient } from 'clawuth';
const client = new ClawuthClient(); // uses default account
const client = new ClawuthClient('myacct'); // uses named account// Post a tweet
const tweet = await client.tweet('Hello world!');
// Reply to a tweet
await client.reply('1234567890', 'Great point!');
// Post with options
await client.tweet('Check this out!', {
replyToId: '1234567890', // reply to this tweet
quoteUrl: 'https://x.com/user/status/123', // quote tweet URL
mediaIds: ['media_id_1'], // pre-uploaded media IDs
});
// Delete a tweet
await client.deleteTweet('1234567890');
// Get a single tweet
const tweet = await client.getTweet('1234567890');await client.like('1234567890');
await client.unlike('1234567890');
await client.retweet('1234567890');
await client.unretweet('1234567890');// Home timeline (last 20 tweets)
const { tweets, nextCursor } = await client.timeline({ count: 20 });
// Load more using cursor
const { tweets: more } = await client.timeline({ count: 20, cursor: nextCursor });
// Notifications / mentions
const data = await client.notifications({ count: 20 });
const data = await client.mentions({ count: 20 });const { tweets, nextCursor } = await client.search('AI agents', {
count: 20,
filter: 'Latest', // 'Latest' | 'Top' | 'People' | 'Photos' | 'Videos'
});// By screen name (with or without @)
const user = await client.getUser('@myaccount');
const user = await client.getUser('myaccount');
// By numeric ID
const user = await client.getUser('12345678');
// Follow / unfollow
await client.follow('12345678');
await client.unfollow('12345678');import { addAccount, listAccounts, setDefault } from 'clawuth';
addAccount('myacct', authToken, ct0, 'myusername');
setDefault('myacct');
const accounts = listAccounts();You can configure as many accounts as you like:
clawuth accounts add personal
clawuth accounts add work
clawuth accounts list
clawuth accounts default workOr programmatically:
import { addAccount } from 'clawuth';
addAccount('personal', 'auth_token_1', 'ct0_1', 'myhandle');
addAccount('work', 'auth_token_2', 'ct0_2', 'workhandle');
const client = new ClawuthClient('work');All accounts are stored in ~/.clawuth/accounts.json.
Run clawuth setup and follow the prompts — it shows you exactly what to paste in DevTools.
For manual extraction, see scripts/extract-tokens.js for detailed instructions, including the manual cookie extraction method.
Tokens expire when you log out of x.com. If you see a 401 Auth error, your tokens have expired. Re-run clawuth setup (or clawuth accounts add <name>) to refresh them.
To avoid expiry: stay logged in to x.com in your browser. Tokens are session-based and tied to your browser login.
~/.clawuth/accounts.json looks like:
{
"__default": "myaccount",
"myaccount": {
"name": "myaccount",
"username": "myaccount",
"authToken": "...",
"ct0": "...",
"addedAt": "2024-01-01T00:00:00.000Z"
}
}clawuth ships a companion OpenClaw channel plugin that makes X a proper two-way messaging channel — @mentions and DMs arrive in any OpenClaw surface (Telegram, webchat, etc.), and replies post back to X automatically.
Copy (or symlink) the plugin into your OpenClaw extensions directory:
cp -r plugins/openclaw-x ~/.openclaw/extensions/xOr point OpenClaw at it via plugins.load.paths in your config.
Add to your OpenClaw config (~/.openclaw/config.json5):
{
channels: {
x: {
enabled: true,
accounts: {
default: {
authToken: "your_auth_token", // from DevTools → Application → Cookies → https://x.com
ct0: "your_ct0", // same place
username: "yourusername", // without @
watchMentions: true,
watchDMs: true,
pollIntervalMs: 60000 // check every 60s
}
}
}
}
}openclaw gateway restartX is now a channel. @mentions and DMs flow in; replies flow out.
clawuth can host a live X Space with AI-generated speech. Two TTS backends are supported:
Runs on your machine using Kokoro TTS. No API key needed.
# Start the Voice Lab server (one-time setup)
cd ~/voice-lab && python server.py
# Start a Space
clawuth space start "AI and the Future of Open Source"Available voices: af_nicole (default), af_sarah, af_bella, bm_george, bm_lewis, and more.
VOICE_LAB_VOICE=af_sarah clawuth space start "My Topic"export ELEVENLABS_API_KEY=your_key_here
clawuth space start "My Topic"Available voices: Sarah (default), Liam, Charlotte, Brian, Jessica.
ELEVENLABS_VOICE_ID=TX3LPaxmHKxFdv7VOQHJ clawuth space start "My Topic"clawuth auto-detects which backend to use:
- Voice Lab — if server is running at
localhost:8002 - ElevenLabs — if
ELEVENLABS_API_KEYis set - Error if neither is available
Force a specific backend:
TTS_BACKEND=elevenlabs clawuth space start "My Topic"
TTS_BACKEND=voicelab clawuth space start "My Topic"Also requires FFmpeg: brew install ffmpeg
Monitor any public Space for keywords. Transcribes live audio using Deepgram.
clawuth space listen https://x.com/i/spaces/XXXXX \
--keywords "openclaw,vibeclaw,agent" \
--transcript \
--alertOptions:
--keywords "a,b,c"— comma-separated keywords to watch for--transcript— print live transcript to stdout--alert— send OpenClaw notification on keyword match (default: on)--webhook <url>— POST to URL on keyword match--anon— join as anonymous listener (no account needed)--account <name>— use a specific account
# Show Space info without joining
clawuth space info https://x.com/i/spaces/XXXXX- Node.js 22+ (built-in
fetch+WebSocket) - An active x.com account
- FFmpeg (for Spaces):
brew install ffmpeg - For TTS: Voice Lab (local) OR
ELEVENLABS_API_KEY(cloud) - For transcription: Deepgram API key (
DEEPGRAM_API_KEY) - No npm dependencies
MIT