Polls, posts, and engages on X/Twitter via API v2. Supports multiple accounts with OAuth 2.0 PKCE.
| Script | Description |
|---|---|
poll-posts.ts |
Poll each account's own posts via X API v2 (Owned Read) |
poll-mentions.ts |
Poll mentions for each account |
poll-feed.ts |
Poll the home timeline — writes directly to feed/ directory |
poll-likes.ts |
Poll liked tweets for each account |
poll-bookmarks.ts |
Poll bookmarks for each account |
drain-queues.ts |
Drain all X runner queues to disk as JSON files |
post.ts |
Dispatch queued posts, replies, and quotes |
like.ts |
Process the like queue |
repost.ts |
Process the repost queue |
refresh-token.ts |
Refresh OAuth 2.0 access token (manual or scheduled) |
flowchart TD
subgraph Ingest
poll["poll-posts / mentions /\nfeed / likes / bookmarks"] --> enqueue["enqueue to runner queues\n(x-type-handle)"]
enqueue --> drain["drain-queues\nwrites JSON to disk"]
end
subgraph Publish
action["like / repost"] --> dequeue["dequeue from\nrunner queues"]
dequeue --> api["call X API v2\nendpoints"]
post["post"] --> files["read queue/*.json\n(account dir)"]
files --> api
api --> refresh["auto-refresh\ntoken on 401"]
end
poll-feed is the exception — it writes feed items directly to the account's feed/ directory instead of using the queue pattern.
x-<type>-<handle> (x-posts-, x-mentions-, x-feed-, x-likes-, x-bookmarks-); drain-queues writes each item to <X_ACCOUNTS[handle]>/<type>/<id>.json. like.ts and repost.ts dequeue from the runner queues x-like-<handle> and x-repost-<handle> (lib/drain-x-queue.ts): an item is marked done only when the action succeeds; an API error, or a handle that no longer resolves to an X user, fails the item in the runner and counts it as failed. post.ts does not use a runner queue: it reads JSON files ({ text, type?, targetTime?, replyToId?, quoteId? }) from <account dir>/queue/, posts those whose targetTime has passed, and moves each to queue/done/ or, after 3 failed attempts, queue/failed/.X_ACCOUNTS from constants(), built from integrations.x.accounts (each handle's silo and relativePath, default x/<handle> in the default silo): a map of account handle → that account's output directory. Empty by default.X_OAUTH_DIR ({CREDENTIALS_DIR}/oauth, i.e. /base/config/credentials/oauth), named x-{handle}-oauth2.json, containing clientId, clientSecret, access_token and refresh_token. These are secrets: keep them in that directory, never in the repo. Tokens come from the initial auth flow and are refreshed by refresh-token.ts (and automatically on a 401).| Job | Script | Schedule |
|---|---|---|
x-poll-posts |
poll-posts.ts |
Every 17 min |
x-poll-mentions |
poll-mentions.ts |
Every 19 min |
x-poll-feed |
poll-feed.ts |
Every 23 min |
x-poll-likes |
poll-likes.ts |
Every 29 min |
x-poll-bookmarks |
poll-bookmarks.ts |
Every 13 min |
x-drain-queues |
drain-queues.ts |
Every 11 min |
All entries in core's manifest jobs/x.json carry a non-null prerequisite (X_ACCOUNTS plus each handle's OAuth file). x-post, x-like, x-repost and x-refresh-token are registered without a manifest (kept in core, Decision 50: an instance that wants to publish, like or repost schedules them in its own jobs/*.json, see runnables); they take the handle as their first argument (run x-post <handle>).
Handles come from X_ACCOUNTS, the one list of the instance's X accounts. The manifest entries pass no arguments, so as registered:
poll-posts, poll-mentions, poll-feed, poll-likes, poll-bookmarks) polls every handle in integrations.x.accounts, one after another (lib/poll-handles.ts). A handle argument (node bin/jeeves-scripts.js run <poll id> <handle>) narrows a run to that handle. Only a first argument that is not an option is a handle: options (--count N, --queue NAME) follow the handle or stand alone, so run x-poll-posts --count 10 polls every handle. --queue replaces the queue name for every handle polled, so give it with a handle.X_ACCOUNTS (otherwise drain-queues would never drain its items) and its OAuth file exists; any other handle is logged as [skip] @<handle>: <reason>. With X_ACCOUNTS empty (the template default) the job logs [skip] and exits 0.poll-feed then fails the run; the queue pollers log the error and exit 0.drain-queues iterates over every handle in X_ACCOUNTS.post.ts, like.ts, repost.ts and refresh-token.ts act on one account and take its handle as their first argument; without one they log [skip] and exit 0.
src/x/lib/poll-handles.ts — which handles a poll run covers (integrations.x.accounts, or the handle argument)src/x/lib/ - X API client wrappers, OAuth token management, polling helperssrc/x/lib/x-client.ts - XClient: the subset of the @xdevplatform/xdk client the jobs call (user lookup, the timeline reads, like/repost, post creation). The SDK camel-cases response keys and snake-cases request bodies, so the jobs read createdAt/authorId and post reply.inReplyToTweetId/quoteTweetId; timeline reads request postFields: ['id', 'text', 'created_at'] with the author_id expansionlib/constants.ts — constants(): X_ACCOUNTS from integrations.x.accounts and X_OAUTH_DIR ({paths.credentialsDir}/oauth) in the instance configconfig/integrations.ts — integrations.x.accounts