arizuko › components › twitd
twitd
What it is
In plain terms, twitd is the connector that plugs X (Twitter) into arizuko, so the agent can read mentions and DMs and reply.
twitd is the X / Twitter channel adapter. It wraps agent-twitter-client (the ai16z fork of @the-convocation/twitter-scraper) to drive X via browser emulation — cookies, not an API key. TypeScript / Bun, JID prefix twitter:.
Why it exists
X’s official v2 API is paywalled past the point of being useful for a hobbyist bot: writing tweets, mentioning users, posting DMs — none of it is available on the free tier. agent-twitter-client drives the same web surface a logged-in user sees, which is the only path that works for arizuko deployments. Account suspensions are common; the README documents an account-loss runbook.
How it fits
X (twitter.com web surface)
| agent-twitter-client poll (mentions, DM convs)
v
twitd (LISTEN_ADDR=:8080, Bun runtime)
| POST /v1/messages (signed by CHANNEL_SECRET)
v
routd
| POST /send (callback to twitd)
v
twitd (sendTweet | sendDirectMessage | favorite | ...)
|
v
X
JID forms: twitter:home for the timeline / mentions, twitter:tweet/<id> for a specific tweet, twitter:dm/<conv_id> for a DM thread, twitter:user/<handle> for a profile.
Wired verbs (twitd/src/verbs.ts): send (DM via sendDirectMessage), post (tweet), reply (sendTweet(text, replyToId)), repost (retweet), quote (quote-tweet; empty body returns hint to repost), like (favorite — binary, no emoji), delete (own tweets), send_file (image / video for tweets; DM attachments degrade to text-only). forward, dislike, edit are 501-with-hint — X has no downvote and X Premium edit is not exposed by the library.
No streaming. twitd polls mentions on TWITTER_POLL_INTERVAL (default 90 s).
Auth (3 paths, priority order)
- Cookie file at
$TWITTER_AUTH_DIR/cookies.json. Export from a logged-in browser. Recommended — skips 2FA. - Username + password via
TWITTER_USERNAME,TWITTER_PASSWORD,TWITTER_EMAIL,TWITTER_2FA_SECRET. Used only when cookies are missing or invalid; on success cookies are persisted. - Pair mode:
bun dist/main.js --pairperforms login with env creds, persists cookies, exits. Useful for warm-up.
Cookies are atomically rotated to cookies.json.bak on every save.
Standalone usage
export ROUTER_URL=http://routd:8080
export CHANNEL_SECRET=$(grep ^CHANNEL_SECRET .env | cut -d= -f2)
export LISTEN_ADDR=:8080
export TWITTER_AUTH_DIR=/srv/data/store/twitter-auth
export TWITTER_POLL_INTERVAL=90
# either: drop a cookies.json into TWITTER_AUTH_DIR, or:
export TWITTER_USERNAME=... TWITTER_PASSWORD=... TWITTER_EMAIL=...
bun dist/main.js
GET /health returns 503 with {status:"disconnected"} while unauthenticated, and 503 {status:"stale"} if no inbound has flowed in 5 minutes. Triage: cookies expired → drop a new cookies.json, restart twitd. 2FA challenge during password login → switch to the cookie path.
The library (agent-twitter-client@0.0.18) is solo-maintained; pin the version so X-side breakage stays out of the dep graph until the operator chooses to upgrade.
Go deeper
- components/channels — cross-adapter verb matrix.
twitd/README.md— verb table, account-loss runbook, env table.specs/2/k-twitter-adapter.md— adapter spec.