x-relay-mcp 1.0.0 → 1.1.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.
@@ -206,11 +206,15 @@ interface Engine {
206
206
  userTweets(handle: string, opts?: UserTweetsOpts): Promise<TweetPage>;
207
207
  bookmarks(opts?: PageOpts): Promise<TweetPage>;
208
208
  thread(id: string): Promise<ThreadResult>;
209
+ /** The authenticated user's own @handle (from the session), or null. Memoized. */
210
+ me(): Promise<string | null>;
209
211
  }
210
212
  interface EngineDeps {
211
213
  /** Cookies for auth. Omit to auto-extract from the local browser (getCookies). */
212
214
  cookies?: Cookies;
213
215
  fetchImpl?: typeof fetch;
216
+ /** Backoff between cold-start retries. Injectable (tests pass a no-op). */
217
+ sleep?: (ms: number) => Promise<void>;
214
218
  /** Injectable transport (tests). Defaults to a real client over the X API. */
215
219
  client?: EngineClient;
216
220
  /** Injectable transaction provider (tests). Defaults to the xctid generator. */
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-DZkaLoUg.js';
2
+ export { o as ParsedArgs, m as dispatch, p as parseArgs, r as run } from './cli-DO7p1WNQ.js';
package/dist/cli.js CHANGED
@@ -192,15 +192,15 @@ function syncBookmarks(engine, opts = {}) {
192
192
  opts
193
193
  );
194
194
  }
195
- function syncPosts(engine, handle, opts = {}) {
195
+ async function syncPosts(engine, handle, opts = {}) {
196
196
  const file = loadCache("posts", opts.dir);
197
- if (handle) file.handle = handle;
198
- const h = file.handle;
197
+ const h = handle ?? file.handle ?? await engine.me() ?? void 0;
199
198
  if (!h) {
200
- return Promise.reject(
201
- new Error("posts sync needs your handle once: `xrelay sync posts --handle <you>`")
199
+ throw new Error(
200
+ "couldn't determine your handle \u2014 pass `--handle <you>` (auto-detect needs a valid session)"
202
201
  );
203
202
  }
203
+ file.handle = h;
204
204
  return syncInto(
205
205
  file,
206
206
  (limit, stopAtId) => engine.userTweets(h, { limit, ...stopAtId !== void 0 ? { stopAtId } : {} }),
@@ -1465,6 +1465,8 @@ function idLte(a, b) {
1465
1465
  return a.length === b.length ? a <= b : a.length < b.length;
1466
1466
  }
1467
1467
  }
1468
+ var MAX_NOTFOUND_RETRIES = 2;
1469
+ var RETRY_BACKOFF_MS = 400;
1468
1470
  function createTransactionProvider(fetchImpl) {
1469
1471
  let ctPromise;
1470
1472
  const init = async () => ClientTransaction.create(await handleXMigration(fetchImpl));
@@ -1514,28 +1516,56 @@ async function paginate(fetchPage, limit, stopAtId) {
1514
1516
  return out;
1515
1517
  }
1516
1518
  function createEngine(deps) {
1519
+ const fetchImpl = deps.fetchImpl ?? fetch;
1520
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
1517
1521
  const txn = deps.transaction ? { provider: deps.transaction, refresh: async () => {
1518
1522
  } } : createTransactionProvider(deps.fetchImpl);
1523
+ let cookiesCache = deps.cookies;
1524
+ const resolveCookies = () => {
1525
+ if (cookiesCache === void 0) cookiesCache = getCookies();
1526
+ return cookiesCache;
1527
+ };
1519
1528
  const client = deps.client ?? createClient({
1520
- cookies: deps.cookies ?? getCookies(),
1529
+ cookies: resolveCookies(),
1521
1530
  transaction: txn.provider,
1522
1531
  ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
1523
1532
  });
1524
- async function call(op, request, retried = false) {
1533
+ async function call(op, request, attempt = 0) {
1525
1534
  const res = await client.get(op, request);
1526
1535
  if (res.ok) return res.value;
1527
- if (res.error.code === "NOT_FOUND" && !retried) {
1536
+ if (res.error.code === "NOT_FOUND" && attempt < MAX_NOTFOUND_RETRIES) {
1528
1537
  await txn.refresh();
1529
- return call(op, request, true);
1538
+ await sleep(RETRY_BACKOFF_MS * (attempt + 1));
1539
+ return call(op, request, attempt + 1);
1530
1540
  }
1531
1541
  throw new EngineError(res.error.code, res.error.message, res.error.status);
1532
1542
  }
1543
+ let mePromise;
1544
+ async function fetchMe() {
1545
+ const path = "/1.1/account/settings.json";
1546
+ try {
1547
+ const txid = await txn.provider("GET", path);
1548
+ const res = await fetchImpl(`https://api.x.com${path}`, {
1549
+ headers: buildHeaders({ cookies: resolveCookies(), transactionId: txid })
1550
+ });
1551
+ if (!res.ok) return null;
1552
+ const data = await res.json();
1553
+ return data.screen_name ?? null;
1554
+ } catch {
1555
+ return null;
1556
+ }
1557
+ }
1558
+ const me = () => {
1559
+ if (mePromise === void 0) mePromise = fetchMe();
1560
+ return mePromise;
1561
+ };
1533
1562
  async function getUser(handle) {
1534
1563
  const value = await call("UserByScreenName", userByScreenNameRequest({ screenName: handle }));
1535
1564
  const result = findDict(value, "result", true)[0];
1536
1565
  return parseUserResult(result);
1537
1566
  }
1538
1567
  return {
1568
+ me,
1539
1569
  async search(query, opts) {
1540
1570
  const product = opts?.product ?? "Top";
1541
1571
  const limit = opts?.limit ?? DEFAULT_LIMIT2;
@@ -1677,15 +1707,14 @@ function runBookmarks(engine, opts = {}) {
1677
1707
  }
1678
1708
  function runMyPosts(engine, opts = {}) {
1679
1709
  if (opts.live) {
1680
- if (!opts.handle) {
1681
- return Promise.resolve(err("my-posts", "INVALID_INPUT", "--live needs --handle <you>"));
1682
- }
1683
- return guard(
1684
- "my-posts",
1685
- () => engine.userTweets(opts.handle, {
1710
+ return guard("my-posts", async () => {
1711
+ const handle = opts.handle ?? await engine.me();
1712
+ if (!handle)
1713
+ throw new EngineError("INVALID_INPUT", "could not determine your handle \u2014 pass --handle");
1714
+ return engine.userTweets(handle, {
1686
1715
  ...opts.limit !== void 0 ? { limit: opts.limit } : {}
1687
- })
1688
- );
1716
+ });
1717
+ });
1689
1718
  }
1690
1719
  return guard("my-posts", async () => {
1691
1720
  let added;
@@ -1707,7 +1736,7 @@ function viewCache(source, opts, added) {
1707
1736
  if (file.syncedAt !== void 0) view.syncedAt = file.syncedAt;
1708
1737
  if (added !== void 0) view.added = added;
1709
1738
  if (total === 0) {
1710
- view.hint = `cache is empty \u2014 run \`xrelay sync ${source}${source === "posts" ? " --handle <you>" : ""}\` first (or pass --live).`;
1739
+ view.hint = `cache is empty \u2014 run \`xrelay sync ${source}\` first (or pass --live).`;
1711
1740
  }
1712
1741
  return view;
1713
1742
  }