Drive the user's authenticated X/Twitter via the browser skill: read timelines/profiles, who-liked/retweeted (Favoriters/Retweeters), search, and gated posts/likes/DMs through X's internal GraphQL API. Use for any 'do X on Twitter/X as me' or build-the-X-graph task.
Install
npx skillscat add agentproto/ts/x-twitter Install via the SkillsCat registry.
X / Twitter — recon plan
✅ SHIPPED on the Camofox stealth session (X flags CDP-Chromium → use
Camofox, not the local-browser chain). Helpers:
scripts/lib/camofox.ts(createCamofox) — Camofox-service REST client
(tabs/navigate/click/type/evaluate +fetchInPage). baseUrl viaCAMOFOX_URL(⚠️ use127.0.0.1, notlocalhost— node/undici picks IPv6
::1 and fails).- X adapter —
SOCIAL_PLATFORMS.xin@agstudio/browser-social
(sites/x/adapter.ts): profile · authored · engagement-given/received ·
connections, as aSocialSourcePort. queryIds read live from X's JS
bundle (drift-proof);featuresflags self-heal on 400s. The capture
recipe lives in the registry, not a script-local helper.- Graph ingest:
scripts/profile.ts x <handle> --graph-only [--depth quick]→ registry
capture →landFootprintgraph sink (platform"x"): profile→SocialPerson,
posts→AUTHORED, connections→FOLLOWS. Validated live: @romanbuildsaas → 73
records · 74 graph ops.- Login done via Camofox (cookie-banner dismissed via
/evaluateJS, then
username→password→Continue). ⚠️ Followers list often 404s for
large/protected accounts; Favoriters/Retweeters load lazily →captureMore()after opening the likes/retweets modal. Gecko: no CDP, but/evaluateworks (used for the API calls).Legacy note: the local-browser cloned profile is NOT logged into X and X flags
CDP anyway — the Camofox path above supersedes it.
Internal API: GraphQL athttps://x.com/i/api/graphql/<queryId>/<OperationName> (+ some RESThttps://x.com/i/api/1.1/ and /2/). Same shape as Voyager: the web app calls
its own GraphQL; we drive that in-page.
Auth (capture from any /i/api/graphql request header)
authorization: Bearer <web-app bearer>— a constant embedded in X's JS
(same for all web users; longAAAA…). Capture once.x-csrf-token: <ct0 cookie>— per-session; read fromdocument.cookie(ct0).- cookies (
auth_token,ct0) ride automatically (same-origin). - also:
x-twitter-active-user: yes,x-twitter-auth-type: OAuth2Session,x-twitter-client-language: en.
In-page fetch:
;async () => {
const ct0 = (document.cookie.match(/ct0=([^;]+)/) || [])[1]
const r = await fetch(URL, {
headers: {
authorization: BEARER,
"x-csrf-token": ct0,
"x-twitter-active-user": "yes",
"x-twitter-auth-type": "OAuth2Session",
"content-type": "application/json",
},
})
return await r.json()
}Operations to capture + test (queryId + features/variables DRIFT)
| Purpose | Operation |
|---|---|
| Feed | HomeTimeline / HomeLatestTimeline |
| Profile → rest_id | UserByScreenName(screen_name) |
| A user's tweets | UserTweets / UserTweetsAndReplies(userId) |
| Tweet + replies | TweetDetail(focalTweetId) |
| Who liked | Favoriters(tweetId) |
| Who retweeted | Retweeters(tweetId) |
| Following / Followers | Following / Followers(userId) |
| Search | SearchTimeline(rawQuery, product) |
| WRITE: tweet | CreateTweet |
| WRITE: like | FavoriteTweet |
| WRITE: retweet | CreateRetweet |
| WRITE: DM | REST /1.1/dm/new2.json or /i/api/1.1/dm/conversation |
Entity model
User{rest_id, screen_name, name, followers_count, …} ·Tweet{id, full_text, author:User, favorite_count, retweet_count, reply_count, bookmark_count}.
Responses are instructions[].entries[] timeline trees → walk totweet_results.result.
Capture method
Open x.com → list_network_requests → find /i/api/graphql/ → read queryId +
OperationName + grab authorization (bearer) + the variables/features
objects from the request. Reuse for the session.
Actions to test (read-first; writes gated)
- UserByScreenName → rest_id for a target.
- UserTweets → their recent tweets + counts.
- Favoriters/Retweeters on one tweet → who engaged (→ persona/graph).
- SearchTimeline → discover accounts → score → dossier.
- (gated) FavoriteTweet / CreateTweet / DM — explicit confirm, rate-limited.
Gotchas
- GraphQL needs a
featuresobject (toggles) that DRIFTS — capture live or the
request 400s. - queryIds rotate per deploy. ct0 must match the cookie exactly.
x.comvstwitter.comorigin — use whichever the tab is on (cookies are
per-domain).