npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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.

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/clawnx

Requires 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 tweet

Engagement

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 succeeded

Helpers

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