@webmcp-bridge/adapter-x 0.5.3 → 0.7.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.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAwB,MAAM,2BAA2B,CAAC;AAuHnF,MAAM,MAAM,qBAAqB,GAAG;IAClC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AA4xNF,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,WAAW,CA2jB3E"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAwB,MAAM,2BAA2B,CAAC;AAuHnF,MAAM,MAAM,qBAAqB,GAAG;IAClC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAk3NF,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,WAAW,CA+jB3E"}
package/dist/adapter.js CHANGED
@@ -537,7 +537,7 @@ const TOOL_DEFINITIONS = [
537
537
  },
538
538
  coverImagePath: {
539
539
  type: "string",
540
- description: "Optional absolute local image path for the article cover image.",
540
+ description: "Optional absolute local image path for the article cover image. X article covers work best when pre-cropped close to the editor's 5:2 aspect ratio.",
541
541
  minLength: 1,
542
542
  "x-uxc-kind": "file-path",
543
543
  },
@@ -576,7 +576,7 @@ const TOOL_DEFINITIONS = [
576
576
  },
577
577
  coverImagePath: {
578
578
  type: "string",
579
- description: "Optional absolute local image path for the article cover image when creating a new draft.",
579
+ description: "Optional absolute local image path for the article cover image when creating a new draft. X article covers work best when pre-cropped close to the editor's 5:2 aspect ratio.",
580
580
  minLength: 1,
581
581
  "x-uxc-kind": "file-path",
582
582
  },
@@ -605,7 +605,7 @@ const TOOL_DEFINITIONS = [
605
605
  },
606
606
  coverImagePath: {
607
607
  type: "string",
608
- description: "Optional absolute local image path for the article cover image.",
608
+ description: "Optional absolute local image path for the article cover image. X article covers work best when pre-cropped close to the editor's 5:2 aspect ratio.",
609
609
  minLength: 1,
610
610
  "x-uxc-kind": "file-path",
611
611
  },
@@ -658,7 +658,7 @@ const TOOL_DEFINITIONS = [
658
658
  },
659
659
  coverImagePath: {
660
660
  type: "string",
661
- description: "Absolute local image path for the article cover image.",
661
+ description: "Absolute local image path for the article cover image. X article covers work best when pre-cropped close to the editor's 5:2 aspect ratio.",
662
662
  minLength: 1,
663
663
  "x-uxc-kind": "file-path",
664
664
  },
@@ -2329,6 +2329,7 @@ async function withEphemeralPage(page, url, run) {
2329
2329
  const context = page.context();
2330
2330
  const ephemeralPage = await context.newPage();
2331
2331
  try {
2332
+ await installEphemeralBridgeStubs(page, ephemeralPage);
2332
2333
  await ensureNetworkCaptureInstalled(ephemeralPage);
2333
2334
  await ephemeralPage.goto(url, { waitUntil: "domcontentloaded", timeout: 60_000 });
2334
2335
  return await run(ephemeralPage);
@@ -2337,6 +2338,34 @@ async function withEphemeralPage(page, url, run) {
2337
2338
  await ephemeralPage.close().catch(() => { });
2338
2339
  }
2339
2340
  }
2341
+ async function installEphemeralBridgeStubs(ownerPage, targetPage) {
2342
+ const names = (await ownerPage
2343
+ .evaluate(() => {
2344
+ const globalAny = window;
2345
+ return {
2346
+ callName: typeof globalAny.__WEBMCP_BRIDGE_CALL_NAME__ === "string" ? globalAny.__WEBMCP_BRIDGE_CALL_NAME__ : "",
2347
+ resourceUpdatedName: typeof globalAny.__WEBMCP_BRIDGE_NOTIFY_RESOURCE_UPDATED_NAME__ === "string"
2348
+ ? globalAny.__WEBMCP_BRIDGE_NOTIFY_RESOURCE_UPDATED_NAME__
2349
+ : "",
2350
+ };
2351
+ })
2352
+ .catch(() => ({ callName: "", resourceUpdatedName: "" })));
2353
+ const callName = typeof names?.callName === "string" ? names.callName : "";
2354
+ const resourceUpdatedName = typeof names?.resourceUpdatedName === "string" ? names.resourceUpdatedName : "";
2355
+ if (callName) {
2356
+ await targetPage
2357
+ .exposeFunction(callName, async () => ({
2358
+ error: {
2359
+ code: "NOT_SUPPORTED",
2360
+ message: "bridge call is unavailable on ephemeral pages",
2361
+ },
2362
+ }))
2363
+ .catch(() => { });
2364
+ }
2365
+ if (resourceUpdatedName) {
2366
+ await targetPage.exposeFunction(resourceUpdatedName, async () => undefined).catch(() => { });
2367
+ }
2368
+ }
2340
2369
  function getReadPageCacheState(page) {
2341
2370
  let state = READ_PAGE_CACHE.get(page);
2342
2371
  if (!state) {
@@ -3418,12 +3447,17 @@ async function waitForArticleDraftPersisted(page, articleId, title) {
3418
3447
  await page.keyboard.press("Escape").catch(() => { });
3419
3448
  return await page
3420
3449
  .waitForFunction(({ targetId, expectedTitle }) => {
3421
- const anchors = Array.from(document.querySelectorAll("a[href]"));
3422
- const draftAnchor = anchors.find((anchor) => anchor.href.includes(`/compose/articles/edit/${targetId}`));
3423
- const containerText = draftAnchor?.closest("article, li, div")?.textContent || draftAnchor?.textContent || "";
3424
- const normalized = containerText.replace(/\s+/g, " ").trim();
3425
- return normalized.includes(expectedTitle) && !normalized.includes("(Needs title)");
3426
- }, { targetId: articleId, expectedTitle: title.trim() }, { timeout: 20_000 })
3450
+ const normalize = (value) => value.replace(/\s+/g, " ").trim();
3451
+ const currentUrl = window.location.href;
3452
+ const titleValue = document.querySelector("textarea[placeholder='Add a title']")?.value ??
3453
+ document.querySelector("textarea")?.value ??
3454
+ "";
3455
+ const bodyText = normalize(document.body?.innerText || "");
3456
+ const onExpectedEditor = currentUrl.includes(`/compose/articles/edit/${targetId}`);
3457
+ const titleMatches = normalize(titleValue) === expectedTitle;
3458
+ const autosaveVisible = bodyText.includes("Last saved") || bodyText.includes("Saved") || bodyText.includes("Just now");
3459
+ return onExpectedEditor && titleMatches && autosaveVisible;
3460
+ }, { targetId: articleId, expectedTitle: title.trim() }, { timeout: 4_000 })
3427
3461
  .then(() => true)
3428
3462
  .catch(() => false);
3429
3463
  }
@@ -3852,6 +3886,9 @@ function buildArticleEditUrl(articleId) {
3852
3886
  function buildArticlePreviewUrl(articleId) {
3853
3887
  return `https://x.com/i/articles/${encodeURIComponent(articleId)}/preview`;
3854
3888
  }
3889
+ function buildArticlePublicUrl(articleId) {
3890
+ return `https://x.com/i/articles/${encodeURIComponent(articleId)}`;
3891
+ }
3855
3892
  function isArticlePreviewUrl(url) {
3856
3893
  try {
3857
3894
  const parsed = new URL(url, "https://x.com");
@@ -4953,6 +4990,10 @@ async function readArticleByUrl(page, targetUrl, authorHandle) {
4953
4990
  return parseArticleReadErrorCode(ownerResult) ? publicResult : ownerResult;
4954
4991
  }
4955
4992
  async function publishArticleEditor(page, timeoutMs) {
4993
+ const isTimeoutError = (error) => {
4994
+ const message = error instanceof Error ? error.message : String(error ?? "");
4995
+ return /timeout/i.test(message);
4996
+ };
4956
4997
  const editUrl = page.url();
4957
4998
  await page
4958
4999
  .evaluate(() => {
@@ -5029,6 +5070,7 @@ async function publishArticleEditor(page, timeoutMs) {
5029
5070
  }
5030
5071
  await page.waitForTimeout(1_000);
5031
5072
  await clickPrimaryPublish().catch(() => false);
5073
+ let publishTimedOut = false;
5032
5074
  try {
5033
5075
  await page.waitForFunction(({ previousUrl }) => {
5034
5076
  const currentUrl = window.location.href;
@@ -5041,8 +5083,17 @@ async function publishArticleEditor(page, timeoutMs) {
5041
5083
  return (document.body?.innerText || "").includes("Published");
5042
5084
  }, { previousUrl: editUrl }, { timeout: timeoutMs });
5043
5085
  }
5044
- catch {
5045
- return { ok: false, reason: "publish_not_confirmed" };
5086
+ catch (error) {
5087
+ if (!isTimeoutError(error)) {
5088
+ return {
5089
+ ok: false,
5090
+ reason: "publish_wait_failed",
5091
+ details: {
5092
+ message: error instanceof Error ? error.message : String(error ?? ""),
5093
+ },
5094
+ };
5095
+ }
5096
+ publishTimedOut = true;
5046
5097
  }
5047
5098
  const details = await page.evaluate(({ op }) => {
5048
5099
  if (op !== "article_collect_publish_details") {
@@ -5065,11 +5116,30 @@ async function publishArticleEditor(page, timeoutMs) {
5065
5116
  const articleId = parseArticleIdFromUrl(details.currentUrl) ??
5066
5117
  (typeof details.editUrl === "string" ? parseArticleIdFromUrl(details.editUrl) : undefined) ??
5067
5118
  undefined;
5068
- const articleUrl = typeof details.publicUrl === "string" && details.publicUrl.length > 0
5119
+ let articleUrl = typeof details.publicUrl === "string" && details.publicUrl.length > 0
5069
5120
  ? details.publicUrl
5070
5121
  : !details.currentUrl.includes("/compose/articles/edit/")
5071
5122
  ? details.currentUrl
5072
5123
  : undefined;
5124
+ if (!articleUrl && articleId) {
5125
+ const publicUrl = buildArticlePublicUrl(articleId);
5126
+ const readback = await readArticleByUrl(page, publicUrl);
5127
+ if (!parseArticleReadErrorCode(readback)) {
5128
+ articleUrl = publicUrl;
5129
+ }
5130
+ }
5131
+ if (publishTimedOut && !articleUrl) {
5132
+ return {
5133
+ ok: false,
5134
+ reason: "publish_not_confirmed",
5135
+ details: {
5136
+ currentUrl: details.currentUrl,
5137
+ ...(typeof details.editUrl === "string" ? { editUrl: details.editUrl } : {}),
5138
+ ...(typeof details.publicUrl === "string" ? { publicUrl: details.publicUrl } : {}),
5139
+ ...(articleId ? { articleId } : {}),
5140
+ },
5141
+ };
5142
+ }
5073
5143
  const output = {
5074
5144
  ok: true,
5075
5145
  editUrl: typeof details.editUrl === "string" && details.editUrl.length > 0 ? details.editUrl : editUrl,
@@ -5227,6 +5297,7 @@ async function draftArticleMarkdown(page, markdownPath, explicitTitle, coverImag
5227
5297
  const articlePage = await page.context().newPage();
5228
5298
  let shouldClose = true;
5229
5299
  try {
5300
+ await installEphemeralBridgeStubs(page, articlePage);
5230
5301
  await ensureNetworkCaptureInstalled(articlePage);
5231
5302
  await articlePage.goto("https://x.com/compose/articles", { waitUntil: "domcontentloaded", timeout: 60_000 });
5232
5303
  const started = await openNewArticleEditor(articlePage);
@@ -6490,7 +6561,12 @@ export function createXAdapter(options) {
6490
6561
  }
6491
6562
  const explicitTitle = typeof args.title === "string" ? args.title.trim() : "";
6492
6563
  const coverImagePath = typeof args.coverImagePath === "string" ? args.coverImagePath.trim() : "";
6493
- return await draftArticleMarkdown(page, markdownPath, explicitTitle || undefined, coverImagePath || undefined);
6564
+ try {
6565
+ return await draftArticleMarkdown(page, markdownPath, explicitTitle || undefined, coverImagePath || undefined);
6566
+ }
6567
+ catch (error) {
6568
+ return errorResult("EXECUTION_FAILED", error instanceof Error ? error.message : String(error ?? ""));
6569
+ }
6494
6570
  }
6495
6571
  if (name === "article.upsertDraftMarkdown") {
6496
6572
  const authCheck = await requireAuthenticated(page);