@clawnch/clawnx
v1.0.0
Published
Zero-dependency X/Twitter API v2 client for AI agents. OAuth 1.0a writes, Bearer reads, streaming, media, action chaining.
Maintainers
Readme
@clawnch/clawnx
Standalone X/Twitter API v2 client for TypeScript. Zero npm dependencies — only Node crypto built-in and global fetch.
53 actions covering tweets, engagement, social graph, DMs, lists, media upload, filtered streaming, and action chaining.
Installation
npm install @clawnch/clawnxRequires Node 18+.
Setup
Get credentials from the X Developer Portal. Free tier works.
import { ClawnX } from '@clawnch/clawnx';
// Option 1: Environment variables
// Set X_API_KEY, X_API_SECRET, X_ACCESS_TOKEN, X_ACCESS_TOKEN_SECRET, X_BEARER_TOKEN
const x = new ClawnX();
// Option 2: Explicit credentials
const x = new ClawnX({
credentials: {
apiKey: '...',
apiSecret: '...',
accessToken: '...',
accessTokenSecret: '...',
bearerToken: '...',
},
});Tweets
// Post
await x.postTweet({ text: 'hello world' });
// Reply
await x.postTweet({ text: 'nice!', replyTo: '123456789' });
// Quote
await x.postTweet({ text: 'check this', quoteTweetId: '123456789' });
// Poll
await x.postTweet({
text: 'Which chain?',
pollOptions: ['Base', 'Ethereum', 'Solana'],
pollDurationMinutes: 60,
});
// Get (accepts ID or URL)
const tweet = await x.getTweet('https://x.com/user/status/123456');
// Search
const results = await x.searchTweets({ query: '$TOKEN', maxResults: 20 });
// Delete
await x.deleteTweet('123456789');
// Detailed metrics
const metrics = await x.getTweetMetrics('123456789');Threads
const thread = await x.postThread([
{ text: '1/ Thread starts here' },
{ text: '2/ Second tweet' },
{ text: '3/ Final tweet' },
]);
console.log(thread.urls); // URL for each tweetEngagement
await x.likeTweet('123');
await x.unlikeTweet('123');
await x.retweet('123');
await x.unretweet('123');
await x.bookmarkTweet('123');
await x.unbookmarkTweet('123');Users
const user = await x.getUser('@clawnch');
const timeline = await x.getUserTimeline('clawnch', { maxResults: 20 });
const followers = await x.getFollowers('clawnch');
const following = await x.getFollowing('clawnch');
await x.followUser('clawnch');
await x.unfollowUser('clawnch');
await x.blockUser('spammer');
await x.unblockUser('spammer');
await x.muteUser('noisy');
await x.unmuteUser('noisy');
const me = await x.getMyProfile();
const home = await x.getHomeTimeline();
const mentions = await x.getMentions();
const bookmarks = await x.getBookmarks();Media Upload
import { readFileSync } from 'fs';
// From buffer
const buf = readFileSync('chart.png');
const media = await x.uploadMedia(buf, 'image/png');
await x.postTweet({ text: 'Daily chart', mediaIds: [media.media_id_string] });
// From URL (fetch + upload in one call)
const media2 = await x.uploadMediaFromUrl({ url: 'https://example.com/image.png' });
await x.postTweet({ text: 'From URL', mediaIds: [media2.media_id_string] });Lists
const list = await x.createList({ name: 'Agents', description: 'AI agents' });
await x.addListMember(list.data.id, 'clawnch');
const tweets = await x.getListTweets(list.data.id);
await x.removeListMember(list.data.id, 'clawnch');
await x.deleteList(list.data.id);Direct Messages
await x.sendDM('friend', { text: 'hey' });
const inbox = await x.getDMEvents();Filtered Streaming
// Set rules first
await x.setStreamRules([
{ value: '$TOKEN', tag: 'token-mentions' },
{ value: 'from:clawnch', tag: 'official' },
]);
// Stream for 60 seconds, collect up to 50 tweets
const tweets = await x.streamFiltered({ durationMs: 60_000, maxTweets: 50 });
// Stop early
x.stopStream();
// Check active rules
const rules = await x.getStreamRules();Action Chaining
Execute sequential actions with data flow between steps. Use "PREV_TWEET_ID" as a placeholder to reference the tweet ID from the previous step.
const result = await x.executeChain([
{ action: 'postTweet', text: 'Announcing v2!' },
{ action: 'likeTweet', idOrUrl: 'PREV_TWEET_ID' },
{ action: 'retweet', idOrUrl: 'PREV_TWEET_ID' },
]);
// result.steps — per-step outcomes
// result.success — true if all steps succeededHelpers
import { parseTweetId, stripAt } from '@clawnch/clawnx';
parseTweetId('https://x.com/user/status/123'); // '123'
parseTweetId('123'); // '123'
stripAt('@clawnch'); // 'clawnch'Error Handling
import { ClawnX, ClawnXError } from '@clawnch/clawnx';
try {
await x.postTweet({ text: 'hello' });
} catch (err) {
if (err instanceof ClawnXError) {
console.log(err.status); // HTTP status
console.log(err.message); // Error description
}
}Why Not xurl
| | ClawnX | xurl | |---|---|---| | Actions | 53 | 43 | | Dependencies | 0 | 12 | | Thread posting | Atomic (one call) | Manual chaining | | Media from URL | Built-in | Not available | | Filtered streaming | Built-in | Not available | | Action chaining | Built-in | Not available | | Agent-native | Yes (structured returns) | CLI subprocess |
License
MIT
