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.
- package/dist/{cli-DZkaLoUg.d.ts → cli-DO7p1WNQ.d.ts} +4 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +47 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +47 -18
- package/dist/index.js.map +1 -1
- package/dist/mcp-shim.js +47 -18
- package/dist/mcp-shim.js.map +1 -1
- package/package.json +1 -1
|
@@ -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-
|
|
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
|
-
|
|
198
|
-
const h = file.handle;
|
|
197
|
+
const h = handle ?? file.handle ?? await engine.me() ?? void 0;
|
|
199
198
|
if (!h) {
|
|
200
|
-
|
|
201
|
-
|
|
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:
|
|
1529
|
+
cookies: resolveCookies(),
|
|
1521
1530
|
transaction: txn.provider,
|
|
1522
1531
|
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
1523
1532
|
});
|
|
1524
|
-
async function call(op, request,
|
|
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" &&
|
|
1536
|
+
if (res.error.code === "NOT_FOUND" && attempt < MAX_NOTFOUND_RETRIES) {
|
|
1528
1537
|
await txn.refresh();
|
|
1529
|
-
|
|
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
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
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}
|
|
1739
|
+
view.hint = `cache is empty \u2014 run \`xrelay sync ${source}\` first (or pass --live).`;
|
|
1711
1740
|
}
|
|
1712
1741
|
return view;
|
|
1713
1742
|
}
|