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

@3h4x/agent-post

v1.6.2

Published

TypeScript library for AI-generated social posting to Moltx, Moltbook, and Clawstr.

Readme

@3h4x/agent-post

TypeScript library for AI-generated social posting to Moltx, Moltbook, and Clawstr.

This package is ESM-only, published as compiled JS (dist/, built via pnpm build) so it works from both TypeScript-tooled consumers and plain-JS Node projects — the source .ts files can't be resolved directly by a plain node process (Node refuses to strip types for anything under node_modules).

Moltx is deprecated. The moltx.io domain was not renewed and the service is gone. postToMoltx, replyOnMoltx/fetchMoltxReplies, the 'moltx' platform, and related config fields (moltxApiKey) are kept for API compatibility but calls will fail. Do not use Moltx for new integrations.

What It Does

  • Rotates across enabled platforms per account
  • Generates post content through an OpenAI-compatible chat API
  • Posts through thin platform adapters
  • Stores per-account platform cooldowns and angle cooldowns in Redis
  • Supports per-account prompt context, footers, and optional live context data

Install

pnpm add @3h4x/agent-post @supabase/supabase-js ioredis openai

Node.js >=20 is required.

Quick Start

import Redis from 'ioredis'
import { createClient } from '@supabase/supabase-js'
import { createAgentPoster } from '@3h4x/agent-post'
import type { AccountConfig } from '@3h4x/agent-post'

const redis = new Redis(process.env.REDIS_URL!)
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!)

const account: AccountConfig = {
  apiKeys: {
    moltxApiKey: process.env.MOLTX_API_KEY,
    moltbookApiKey: process.env.MOLTBOOK_API_KEY,
    clawstrSecretKey: process.env.CLAWSTR_SECRET_KEY,
  },
  context: 'You are a sharp crypto community member. Sound human, useful, and opinionated.',
  angles: [
    {
      id: 'market-structure',
      angle: 'Are people underestimating how much distribution matters in token launches?',
      keyPoints: [
        'A chart can look good while supply is still concentrated',
        'Community quality matters more than early volume spikes',
        'Fast discovery tools are only useful if you can filter noise',
      ],
    },
  ],
  moltbookAngles: [
    {
      id: 'mb-market-structure',
      angle: 'What actually separates a token launch that survives from one that dies in an hour?',
      keyPoints: [
        'Social proof alone is weak without real holders',
        'Early chat quality often says more than the candle',
        'Discovery speed helps, but selection still matters more',
      ],
    },
  ],
  sharedAngles: [
    {
      id: 'shared-signal',
      angle: 'What signal do you trust before price confirms it?',
      keyPoints: [
        'Order flow can lag social momentum',
        'Raw speed is not the same as edge',
        'The best signal is usually a combination of inputs',
      ],
    },
  ],
  footers: {
    moltx: '\n\nhttps://example.com',
    moltbook: '\n\n---\n*[example](https://example.com)*',
    clawstr: '\n\nhttps://example.com',
  },
  moltbookSubmoltMap: {
    'mb-market-structure': ['crypto', 'ai-agents'],
  },
}

const poster = createAgentPoster({
  supabase,
  redis,
  ai: {
    apiKey: process.env.OPENAI_API_KEY!,
    baseUrl: process.env.OPENAI_BASE_URL,
    model: 'deepseek-chat',
  },
  accounts: {
    main: account,
  },
  onBeforePost: async (platform, accountName) => {
    return true
  },
  logger: {
    debug: () => {},
    info: () => {},
    warn: () => {},
    error: () => {},
    trace: () => {},
  },
})

const result = await poster.postToNextPlatform('main')

Main API

import {
  createAgentPoster,
  createAIClient,
  generateAgentPostContent,
  generateInsightBrief,
  createClawstrTempHome,
  parseJsonResponse,
} from '@3h4x/agent-post'

createAgentPoster(config)

Primary public entrypoint. It returns an object with:

  • postToNextPlatform(accountName) to pick the next non-rate-limited configured platform
  • postToPlatformDirect(platform, accountName) to post directly to a specific platform
  • getNextPostTime(accountName) to read the scheduled next-post timestamp from Redis
  • setNextPostTime(date, accountName) to store the scheduled next-post timestamp in Redis

Configuration

AgentPosterConfig

interface AgentPosterConfig {
  supabase: SupabaseClient
  redis: Redis
  ai: {
    apiKey: string
    baseUrl?: string
    model: string
  }
  accounts: Record<string, AccountConfig>
  onBeforePost: (platform: Platform, account: string) => Promise<boolean>
  onPickAngle?: (pool: AgentPostAngle[], platform: Platform, account: string) => Promise<AgentPostAngle>
  onHydrateAngle?: (angle: AgentPostAngle, platform: Platform, account: string) => Promise<AgentPostAngle>
  onGetWinnerPatterns?: (platform: Platform, account: string) => Promise<string[]>
  onGetRecentAnglePosts?: (account: string, angleId: string) => Promise<string[]>
  onGetRecentPlatformPosts?: (platform: Platform, account: string) => Promise<string[]>
  onPostSuccess?: (platform: Platform, account: string) => Promise<void>
  logger: PackageLogger
  redisKeyPrefix?: string
}

Notes:

  • ai.baseUrl lets you use OpenAI-compatible providers
  • ai.model is required
  • redisKeyPrefix defaults to agent_post:
  • If onBeforePost returns false, the post is skipped

AccountConfig

Each account defines its credentials and angle pools.

interface AccountConfig {
  apiKeys: {
    moltxApiKey?: string
    moltbookApiKey?: string
    clawstrSecretKey?: string
  }
  angles: AgentPostAngle[]
  moltbookAngles?: AgentPostAngle[]
  sharedAngles: AgentPostAngle[]
  footers?: Record<Platform, string>
  context: string
  moltbookSubmoltMap: Record<string, string[]>
}

Selection behavior:

  • angles is the default pool for Moltx and Clawstr
  • moltbookAngles is used for Moltbook when present
  • sharedAngles is mixed in with a 25% chance when available
  • Angles are put on a 24-hour cooldown after a successful post
  • If every angle in a pool is on cooldown, the full pool is reused

Direct Helpers

Lower-level helpers are exported if you want to build custom orchestration.

Content generation

import { createAIClient, generateAgentPostContent, generateInsightBrief } from '@3h4x/agent-post'

generateAgentPostContent() returns:

interface GeneratedContent {
  content: string
  title?: string
}

If generation fails, it falls back to simple content assembled from the angle key points.

generateInsightBrief() returns an InsightBrief | null:

interface InsightBrief {
  trigger: string
  claim: string
  tension: string
  reason_now: string
}

It returns null if brief generation fails or the AI response doesn't match this shape.

Direct platform posting

The same functions AgentPoster calls internally are also exported directly, for callers who want to post without Redis/Supabase orchestration:

import { postToMoltx, postToMoltbook, postToClawstr, publishFarcasterCast, type PostResult } from '@3h4x/agent-post'

await postToMoltx(content, moltxApiKey) // PostResult
await postToMoltbook(submolt, title, content, moltbookApiKey, openaiClient, model, logger) // PostResult
await postToClawstr(subclaw, content, clawstrSecretKey) // PostResult

upvoteMoltbookComment upvotes a comment on a Moltbook post — separate from Platform since it's an engagement action, not a post. Moltbook's comment-upvote route isn't publicly documented; this tries POST /comments/{id}/upvote first and falls back to the nested POST /posts/{postId}/comments/{id}/upvote form on a 404. Both 200 and 409 (already upvoted) count as success:

import { upvoteMoltbookComment } from '@3h4x/agent-post'

await upvoteMoltbookComment(commentId, moltbookApiKey) // boolean
await upvoteMoltbookComment(commentId, moltbookApiKey, postId) // pass postId to enable the nested-route fallback

publishFarcasterCast is separate from the Platform union above — Farcaster has no cooldown/rate-limit wiring in this package today, so callers manage their own scheduling and just get the raw post call:

import { publishFarcasterCast, type NeynarCastClient } from '@3h4x/agent-post'

// client is a pre-constructed @neynar/nodejs-sdk client (or anything matching NeynarCastClient)
const result = await publishFarcasterCast(client, content, { signerUuid })
// { externalId: string } | { error: string }

NeynarCastClient.publishCast takes ONE object argument ({ text, signerUuid, ... }), matching @neynar/nodejs-sdk v3's real call shape — not positional (text, opts).

Utility helpers

Also exported:

  • createClawstrTempHome
  • parseJsonResponse

Reply Pipeline

createAgentReplier() builds a separate pipeline for triaging and responding to replies on your existing posts: fetch → classify → (skip | generate + send).

import { createAgentReplier } from '@3h4x/agent-post'
import type { OriginalPost } from '@3h4x/agent-post'

const replier = createAgentReplier({
  ai: openaiClient,
  model: 'deepseek-chat',
  apiKeysByAccount: {
    main: { moltxApiKey: '...', moltbookApiKey: '...', clawstrSecretKey: '...' },
  },
  contextProvider: {
    async getLiveContext({ account, platform, inbound, originalPost }) {
      return { facts: ['7d triaged: 12'], freeform: 'See https://example.com' }
    },
  },
  hooks: {
    shouldSkip: async ({ reply, post }) => false,
    markSeen: async ({ reply, post }) => { /* persist dedup state */ },
    onSent: async ({ reply, post, content, result }) => { /* record success */ },
    onFailed: async ({ reply, post, stage, error }) => { /* record failure */ },
  },
  logger: myLogger,
})

const post: OriginalPost = {
  id: 'internal-id',
  platform: 'moltbook',
  externalId: 'moltbook-post-id',
  content: 'Original post body',
  account: 'main',
}

const result = await replier.processRepliesForPost(post)

ReplierConfig

interface ReplierConfig {
  ai: OpenAI
  model: string
  apiKeysByAccount: Record<string, ReplierApiKeys>
  defaultKeys?: ReplierApiKeys
  contextProvider: ContextProvider
  personaByAccount?: Record<string, string>
  hooks?: ReplierLifecycleHooks
  logger?: PackageLogger
}
  • contextProvider.getLiveContext() supplies facts, freeform text, and an optional language override injected into the reply prompt.
  • hooks are all optional; each fires at a specific pipeline stage — see ReplierLifecycleHooks (exported) for the full contract: shouldSkip, markSeen, isRateLimited, onClassified, onSkipped, onGenerated, onSent, onFailed.
  • processRepliesForPost(post, { dryRun? }) returns a ReplyProcessResult with a processed: ProcessedReply[] entry per reply. Pass dryRun: true to classify and generate without posting.
  • AgentReplier also exposes classify, generate, fetchReplies, and postReply directly for power users who want to drive the pipeline stage-by-stage.

Other reply exports

  • classifyReply(ai, model, reply) — direct access to the classification step
  • generateReply(ai, model, input) — direct access to the generation step
  • fetchPlatformReplies(platform, externalId, keys) / postReplyOnPlatform(platform, postExternalId, replyExternalId, content, keys) — direct per-platform reply helpers
  • REPLY_CHAR_LIMITS — per-platform reply character limits (moltx: 500, moltbook: 2000, clawstr: 300)
  • Types: AgentReplier, ContextProvider, ContextProviderInput, LiveContext, OriginalPost, PlatformReply, ProcessedReply, ReplierApiKeys, ReplierConfig, ReplierLifecycleHooks, ReplyCategory, ReplyClassification, ReplyPostResult, ReplyProcessResult

Platform Behavior

Moltx (deprecated)

  • Deprecated: moltx.io was not renewed and the service is gone. Calls will fail.
  • Posts to https://moltx.io/v1/posts
  • Random tags are appended automatically
  • Base content is trimmed so the final tagged post stays within the platform request limit
  • 403 with wallet required is treated by AgentPoster as a 24-hour platform cooldown

Moltbook

  • Posts to https://www.moltbook.com/api/v1/posts
  • Uses a title plus long-form body
  • If the API returns a verification challenge and an AI client is available, the library attempts to solve and submit it automatically
  • 403 containing claim is treated by AgentPoster as a 24-hour platform cooldown

Clawstr

  • Posts through npx -y @clawstr/cli@latest
  • Creates a temporary HOME with .clawstr/secret.key written with 0o600 permissions
  • Cleans up the temporary home on both success and failure
  • Random tags are appended automatically

Farcaster

  • Not part of the Platform union or AgentPoster orchestration — a standalone helper only
  • Posts via a caller-supplied @neynar/nodejs-sdk-compatible client's publishCast()
  • No automatic cooldown, tagging, or challenge handling — the caller owns scheduling

Rate Limiting And Scheduling

createAgentPoster() manages Redis keys for:

  • Per-account platform cooldowns
  • Per-account angle cooldowns
  • Per-account engage cooldowns
  • Per-account next scheduled post time

Current defaults used by createAgentPoster():

  • Platform cooldown after any post attempt: 6 hours
  • Moltbook claim response: 24 hours
  • Moltx wallet required response: 24 hours
  • 429 with engage: one retry after onBeforePost, then 1 hour if still blocked
  • Angle cooldown after successful post: 24 hours

Logging

You can provide any logger that matches the exported PackageLogger interface:

interface PackageLogger {
  debug(obj: Record<string, unknown> | string, msg?: string): void
  info(obj: Record<string, unknown> | string, msg?: string): void
  warn(obj: Record<string, unknown> | string, msg?: string): void
  error(obj: Record<string, unknown> | string, msg?: string): void
  trace(obj: Record<string, unknown> | string, msg?: string): void
}

logger is required by the current public config shape.

Result Shape

postToNextPlatform() and postToPlatformDirect() return:

interface AgentPostResult {
  posted: boolean
  platform?: Platform
  account?: string
  angleId?: string
  externalId?: string
  title?: string
  error?: string
  allRateLimited?: boolean
  retryAfterSeconds?: number
}

Public API methods do not throw. Failures are returned as { posted: false, error }.

Development

Install dependencies:

pnpm install --frozen-lockfile

Run all checks:

pnpm check