x-relay-mcp 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -100,7 +100,40 @@ xrelay sync bookmarks|posts|all [--handle <you>] [--repair] [--max N]
100
100
  ```
101
101
  - Pulls ONLY tweets newer than the last sync (snowflake-id watermark + newest-first early-break) — never a
102
102
  full refetch. `--repair` refetches everything and patches records; `--max N` caps a run (good for a first
103
- sync). `posts` needs your `--handle` once (it's remembered). Returns `{ source, added, total, watermark }`.
103
+ sync). `posts` auto-detects your handle from the session (override/remember with `--handle`). Returns
104
+ `{ source, added, total, watermark }`.
105
+
106
+ ### More endpoints
107
+
108
+ ```
109
+ xrelay list <list-id> [--limit N] # tweets from a Twitter List (curated sources)
110
+ xrelay user-media <handle> [--limit N] # a user's images/videos only (visual evidence)
111
+ xrelay followers <handle> [--limit N] # a user's followers (network mapping)
112
+ xrelay following <handle> [--limit N] # who a user follows
113
+ xrelay retweeters <id|url> [--limit N] # who retweeted a tweet (amplification graph)
114
+ xrelay likers <id|url> [--limit N] # who liked a tweet (engagement graph)
115
+ xrelay quoters <id|url> [--limit N] # tweets quoting a tweet (reactions; recency-windowed)
116
+ xrelay trends [--woeid N] [--limit N] # what's hot now (woeid 1 = worldwide, default)
117
+ xrelay article <id|url> # a long-form X Article → Markdown
118
+ xrelay media <id|url> [--out <dir>] # a tweet's image/video URLs; --out downloads the files
119
+ xrelay community <community-id> [--limit N] # a community's tweet feed (topical, moderated sub-network)
120
+ xrelay community-info <community-id> # community metadata: name, members, rules, topic, creator
121
+ ```
122
+
123
+ - **`retweeters`/`followers`/`following`** return `{ users:[<profile>...], nextCursor }` — the
124
+ amplification/audience graph. Use them to answer "who is paying attention to / amplifying X".
125
+ - **`likers`** also returns `{ users }`, but X made likes private in 2024, so it's usually **empty** for
126
+ tweets you don't own (only the like *count* is public — read that from the tweet's `metrics.likes`).
127
+ - **`quoters`** is search-based (`quoted_tweet_id:`), so it's recency-windowed, not the full historical set.
128
+ - **`trends`** is a cheap zoomed-out entry point before drilling into `search`.
129
+ - **`article`** returns `{ id, title, markdown, url }` — the full long-form read for a finalist.
130
+ - **`media`** returns `{ tweetId, media:[{type,url,...}], files? }`; `--out <dir>` saves the actual
131
+ image/video files (for OCR / transcription / multimodal analysis).
132
+ - **`community`** returns the community's feed as a normal `{ tweets[], nextCursor }` — a focused,
133
+ on-topic corpus that's often higher-signal than open search for a niche. **`community-info`** returns
134
+ `{ name, description, memberCount, moderatorCount, rules[], topic, tags[], creator, url }`. Get the
135
+ `community-id` from a community URL (`x.com/i/communities/<id>`). Note: X exposes no stable endpoint for
136
+ a full member roster or within-community search, so those aren't provided — use the feed + `search`.
104
137
 
105
138
  ---
106
139
 
@@ -128,3 +161,28 @@ npm i -g x-relay-mcp
128
161
  ```
129
162
  Cookies are read automatically from your logged-in browser (macOS Keychain). The first run may show a one-time
130
163
  Keychain "Always Allow" prompt. Assumes a residential IP (run locally); datacenter IPs are blocked by X.
164
+
165
+ ### Account pool + proxy (optional — for heavy/sustained use)
166
+
167
+ X rate-limits per account and blocks datacenter IPs. For deep sweeps, give the tool **several sessions, each
168
+ behind its own residential proxy**; it transparently fails over to the next session when one hits a rate-limit
169
+ (429) or its cookies expire — so a long research run survives a single account getting throttled. All optional;
170
+ the default single-browser-session path needs none of this.
171
+
172
+ ```
173
+ # Several accounts, each pinned to its own proxy (JSON array — the robust form):
174
+ export XRELAY_ACCOUNTS='[
175
+ {"cookies":"auth_token=..; ct0=..","proxy":"http://user:pass@host1:port","label":"main"},
176
+ {"cookies":"auth_token=..; ct0=..","proxy":"socks5://user:pass@host2:port"}
177
+ ]'
178
+
179
+ # Or a simpler newline list of cookie strings, with proxies round-robined onto them:
180
+ export XRELAY_ACCOUNTS=$'auth_token=..; ct0=..\nauth_token=..; ct0=..'
181
+ export XRELAY_PROXIES='http://host1:port, http://host2:port'
182
+
183
+ # Single browser session, just routed through one proxy:
184
+ export XRELAY_PROXY='http://user:pass@host:port'
185
+ ```
186
+
187
+ Rotation triggers on `RATE_LIMITED` / `AUTH_FAILED` only; other errors fail fast. Pair an equal number of
188
+ accounts and proxies for a clean 1:1 mapping. http(s) and socks proxies are both supported.
@@ -84,6 +84,55 @@ type ThreadResult = {
84
84
  nextCursor?: string;
85
85
  };
86
86
  type SearchProduct$1 = 'Top' | 'Latest' | 'Media' | 'People';
87
+ /** A paginated set of users (followers / following / retweeters / likers). */
88
+ type UserPage = {
89
+ users: UserProfile[];
90
+ nextCursor?: string;
91
+ };
92
+ /** A trending topic. */
93
+ type Trend = {
94
+ name: string;
95
+ rank?: number;
96
+ /** Volume blurb as X reports it, e.g. "42.1K posts". */
97
+ volume?: string;
98
+ url?: string;
99
+ };
100
+ /** A downloadable media asset attached to a tweet. */
101
+ type MediaItem = {
102
+ type: MediaKind;
103
+ url: string;
104
+ thumbnail?: string;
105
+ width?: number;
106
+ height?: number;
107
+ durationMs?: number;
108
+ bitrate?: number;
109
+ };
110
+ /** A long-form X Article rendered to Markdown. */
111
+ type Article = {
112
+ id: string;
113
+ title: string;
114
+ markdown: string;
115
+ url: string;
116
+ author?: Author;
117
+ };
118
+ /** An X Community's metadata (the `community-info` command). */
119
+ type Community = {
120
+ id: string;
121
+ name: string;
122
+ description?: string;
123
+ memberCount?: number;
124
+ moderatorCount?: number;
125
+ /** ISO timestamp (X reports created_at as epoch ms). */
126
+ createdAt?: string;
127
+ /** The viewer's role in the community (Member / Moderator / Admin / NonMember). */
128
+ role?: string;
129
+ joinPolicy?: string;
130
+ topic?: string;
131
+ rules?: string[];
132
+ tags?: string[];
133
+ creator?: Author;
134
+ url: string;
135
+ };
87
136
 
88
137
  /** The two load-bearing cookies, plus any others the jar carries. */
89
138
  type Cookies = {
@@ -150,6 +199,30 @@ declare const OPS: {
150
199
  readonly queryId: "7UuJsFvnWuZo0HmxrzU42Q";
151
200
  readonly operationName: "ListLatestTweetsTimeline";
152
201
  };
202
+ readonly Retweeters: {
203
+ readonly queryId: "TZsWuSj7vGmncVnq7KWDUQ";
204
+ readonly operationName: "Retweeters";
205
+ };
206
+ readonly Favoriters: {
207
+ readonly queryId: "LLkw5EcVutJL6y-2gkz22A";
208
+ readonly operationName: "Favoriters";
209
+ };
210
+ readonly GenericTimelineById: {
211
+ readonly queryId: "_dGVIf1cY6xFanFNPsAzPQ";
212
+ readonly operationName: "GenericTimelineById";
213
+ };
214
+ readonly TweetResultByRestId: {
215
+ readonly queryId: "Xl5pC_lBk_gcO2ItU39DQw";
216
+ readonly operationName: "TweetResultByRestId";
217
+ };
218
+ readonly CommunityByRestId: {
219
+ readonly queryId: "vLS7mhOqMLtGZdXqFP1DEg";
220
+ readonly operationName: "CommunityByRestId";
221
+ };
222
+ readonly CommunityTweetsTimeline: {
223
+ readonly queryId: "pXYASW5kVylF3YMrGJovLg";
224
+ readonly operationName: "CommunityTweetsTimeline";
225
+ };
153
226
  };
154
227
  type OpName = keyof typeof OPS;
155
228
  type Vars = Record<string, unknown>;
@@ -204,8 +277,23 @@ interface Engine {
204
277
  search(query: string, opts?: SearchOpts): Promise<SearchResult>;
205
278
  user(handle: string): Promise<UserProfile | null>;
206
279
  userTweets(handle: string, opts?: UserTweetsOpts): Promise<TweetPage>;
280
+ userMedia(handle: string, opts?: PageOpts): Promise<TweetPage>;
207
281
  bookmarks(opts?: PageOpts): Promise<TweetPage>;
208
282
  thread(id: string): Promise<ThreadResult>;
283
+ list(listId: string, opts?: PageOpts): Promise<TweetPage>;
284
+ followers(handle: string, opts?: PageOpts): Promise<UserPage>;
285
+ following(handle: string, opts?: PageOpts): Promise<UserPage>;
286
+ retweeters(tweetId: string, opts?: PageOpts): Promise<UserPage>;
287
+ likers(tweetId: string, opts?: PageOpts): Promise<UserPage>;
288
+ quoters(tweetId: string, opts?: SearchOpts): Promise<SearchResult>;
289
+ trends(opts?: {
290
+ woeid?: number;
291
+ limit?: number;
292
+ }): Promise<Trend[]>;
293
+ article(tweetId: string): Promise<Article | null>;
294
+ media(tweetId: string): Promise<MediaItem[]>;
295
+ community(communityId: string, opts?: PageOpts): Promise<TweetPage>;
296
+ communityInfo(communityId: string): Promise<Community | null>;
209
297
  /** The authenticated user's own @handle (from the session), or null. Memoized. */
210
298
  me(): Promise<string | null>;
211
299
  }
@@ -217,6 +305,8 @@ interface EngineDeps {
217
305
  sleep?: (ms: number) => Promise<void>;
218
306
  /** Injectable transport (tests). Defaults to a real client over the X API. */
219
307
  client?: EngineClient;
308
+ /** Injectable transport lanes (tests) — drives account-pool rotation. Overrides `client`. */
309
+ clients?: EngineClient[];
220
310
  /** Injectable transaction provider (tests). Defaults to the xctid generator. */
221
311
  transaction?: TransactionProvider;
222
312
  }
@@ -233,4 +323,4 @@ declare function parseArgs(argv: string[]): ParsedArgs;
233
323
  declare function dispatch(parsed: ParsedArgs, engine: Engine): Promise<Envelope<unknown>>;
234
324
  declare function run(argv: string[], engine?: Engine): Promise<number>;
235
325
 
236
- export { type Author as A, type Cookies as C, type Err as E, type MediaKind as M, type Ok as O, type PageOpts as P, type SearchProduct as S, type Tweet as T, type UserProfile as U, type Engine as a, type Envelope as b, type TweetPage as c, type SearchResult as d, type ThreadResult as e, type EngineDeps as f, EngineError as g, type Metrics as h, type SearchOpts as i, type SearchProduct$1 as j, type UserTweetsOpts as k, createEngine as l, dispatch as m, parseCookies as n, type ParsedArgs as o, parseArgs as p, run as r };
326
+ export { type Article as A, type Cookies as C, type Err as E, type MediaItem as M, type Ok as O, type PageOpts as P, type SearchResult as S, type Tweet as T, type UserPage as U, type Engine as a, type Envelope as b, type TweetPage as c, type SearchProduct as d, type ThreadResult as e, type Trend as f, type UserProfile as g, type Author as h, type Community as i, type EngineDeps as j, EngineError as k, type MediaKind as l, type Metrics as m, type SearchOpts as n, type SearchProduct$1 as o, type UserTweetsOpts as p, createEngine as q, dispatch as r, parseArgs as s, parseCookies as t, run as u, type ParsedArgs as v };
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export { o as ParsedArgs, m as dispatch, p as parseArgs, r as run } from './cli-DO7p1WNQ.js';
2
+ export { v as ParsedArgs, r as dispatch, s as parseArgs, u as run } from './cli-DDbxyp9v.js';