@qcplay/cli 1.0.21 → 1.0.22

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/bin/qcplay.js CHANGED
@@ -3111,6 +3111,10 @@ async function runWithErrorBanner(title, action) {
3111
3111
  try {
3112
3112
  await action();
3113
3113
  } catch (err) {
3114
+ if (err.authorization?.action === "platform_authorization_required") {
3115
+ console.log(JSON.stringify(err.authorization));
3116
+ return;
3117
+ }
3114
3118
  console.error("");
3115
3119
  console.error(chalk.red(`${title}:`));
3116
3120
  console.error(err.message || err);
@@ -2,6 +2,7 @@ import { spawn } from "child_process";
2
2
 
3
3
  import * as cheerio from "cheerio";
4
4
 
5
+ import { larkCliExecutable } from "./lark-cli-command.js";
5
6
  import { normalizeArticleColor, sanitizeArticleRichHtml } from "./wechat-article.js";
6
7
 
7
8
  const LARK_DOCUMENT_PATH = /^\/(?:wiki|docx)\/([A-Za-z0-9_-]+)\/?$/;
@@ -59,7 +60,7 @@ function larkError(output, fallback) {
59
60
 
60
61
  function runLarkJson(args) {
61
62
  return new Promise((resolve, reject) => {
62
- const executable = process.platform === "win32" ? "lark-cli.cmd" : "lark-cli";
63
+ const executable = larkCliExecutable();
63
64
  const child = spawn(executable, args, {
64
65
  windowsHide: true,
65
66
  shell: process.platform === "win32",
@@ -0,0 +1,16 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+
5
+ const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
6
+
7
+ export function larkCliExecutable() {
8
+ const configured = String(process.env.QCPLAY_LARK_CLI || "").trim();
9
+ if (configured) {
10
+ return configured;
11
+ }
12
+
13
+ const executableName = process.platform === "win32" ? "lark-cli.cmd" : "lark-cli";
14
+ const localExecutable = path.resolve(moduleDirectory, "..", "node_modules", ".bin", executableName);
15
+ return fs.existsSync(localExecutable) ? localExecutable : executableName;
16
+ }
@@ -9,6 +9,7 @@ import readline from "readline";
9
9
 
10
10
  import * as cheerio from "cheerio";
11
11
  import { applyContentRules } from "./content-rules.js";
12
+ import { larkCliExecutable } from "./lark-cli-command.js";
12
13
  import { stripWechatGuidanceHtml } from "./wechat-article.js";
13
14
 
14
15
  const PLATFORM_CONFIG_URL = "https://t4blw8ys5w.feishu.cn/wiki/OqH7wkx9PiBaTYkKeWNc35SonGf";
@@ -484,7 +485,11 @@ export function normalizePlatformRows(rows) {
484
485
 
485
486
  function runJson(command, args) {
486
487
  return new Promise((resolve, reject) => {
487
- const executable = process.platform === "win32" ? `${command}.cmd` : command;
488
+ const executable = command === "lark-cli"
489
+ ? larkCliExecutable()
490
+ : process.platform === "win32"
491
+ ? `${command}.cmd`
492
+ : command;
488
493
  const child = spawn(executable, args, {
489
494
  windowsHide: true,
490
495
  shell: process.platform === "win32",
@@ -498,19 +503,95 @@ function runJson(command, args) {
498
503
  child.once("close", code => {
499
504
  const output = Buffer.concat(stdout).toString("utf8").trim();
500
505
  const diagnostics = Buffer.concat(stderr).toString("utf8").trim();
506
+ const parsed = parseJsonEnvelope(output) || parseJsonEnvelope(diagnostics);
501
507
  if (code !== 0) {
502
- reject(new Error(diagnostics || output || `${command} 执行失败 (${code})`));
508
+ const error = new Error(diagnostics || output || `${command} 执行失败 (${code})`);
509
+ error.exitCode = code;
510
+ error.payload = parsed;
511
+ reject(error);
503
512
  return;
504
513
  }
505
- try {
506
- resolve(JSON.parse(output));
507
- } catch {
514
+ if (parsed) {
515
+ resolve(parsed);
516
+ } else {
508
517
  reject(new Error(`${command} 没有返回有效 JSON`));
509
518
  }
510
519
  });
511
520
  });
512
521
  }
513
522
 
523
+ function parseJsonEnvelope(value) {
524
+ if (!value) return null;
525
+ try {
526
+ return JSON.parse(value);
527
+ } catch {
528
+ return null;
529
+ }
530
+ }
531
+
532
+ function isAuthFailure(error) {
533
+ const payload = error?.payload;
534
+ const serialized = JSON.stringify(payload || {}) + "\n" + String(error?.message || "");
535
+ return /token_invalid|invalid access token|authentication/i.test(serialized);
536
+ }
537
+
538
+ async function beginPlatformAuthorization(commandRunner = runJson) {
539
+ try {
540
+ const result = await commandRunner("lark-cli", ["auth", "login", "--recommend", "--no-wait", "--json"]);
541
+ const verificationUrl = normalizeText(result?.verification_url || result?.verification_uri_complete);
542
+ if (verificationUrl) {
543
+ return { verificationUrl, deviceCode: normalizeText(result?.device_code) };
544
+ }
545
+ return {
546
+ platformManaged: result?.action === "platform_authorization_required",
547
+ message: normalizeText(result?.message)
548
+ };
549
+ } catch (error) {
550
+ const verificationUrl = normalizeText(error?.payload?.verification_url || error?.payload?.verification_uri_complete);
551
+ return {
552
+ verificationUrl,
553
+ deviceCode: normalizeText(error?.payload?.device_code),
554
+ platformManaged: error?.payload?.action === "platform_authorization_required",
555
+ message: normalizeText(error?.payload?.message || error?.message)
556
+ };
557
+ }
558
+ }
559
+
560
+ async function throwPlatformAuthorizationError(error, commandRunner = runJson) {
561
+ if (!isAuthFailure(error)) {
562
+ throw error;
563
+ }
564
+ const authorization = await beginPlatformAuthorization(commandRunner);
565
+ if (authorization.verificationUrl) {
566
+ const authorizationError = new Error(
567
+ `飞书授权需要完成,请打开以下地址并完成授权:\n${authorization.verificationUrl}`
568
+ );
569
+ authorizationError.authorization = {
570
+ ok: true,
571
+ action: "platform_authorization_required",
572
+ provider: "lark",
573
+ verification_url: authorization.verificationUrl,
574
+ ...(authorization.deviceCode ? { device_code: authorization.deviceCode } : {}),
575
+ message: "请完成飞书授权后重试原命令"
576
+ };
577
+ throw authorizationError;
578
+ }
579
+ if (authorization.platformManaged) {
580
+ const authorizationError = new Error(
581
+ "飞书访问令牌已失效,但当前 lark-cli 由平台托管凭证,无法生成本地授权地址。请在当前平台的飞书/Lark 连接器中重新授权后重试。"
582
+ );
583
+ authorizationError.authorization = {
584
+ ok: true,
585
+ action: "platform_authorization_required",
586
+ provider: "lark",
587
+ managed_by_platform: true,
588
+ message: "飞书访问令牌已失效,请在当前平台重新授权飞书/Lark 连接器后重试"
589
+ };
590
+ throw authorizationError;
591
+ }
592
+ throw new Error(`飞书访问令牌已失效,授权启动失败:${authorization.message || "未知错误"}`);
593
+ }
594
+
514
595
  function domesticPlatformRows() {
515
596
  return normalizePlatformRows([...DOMESTIC_PLATFORM_ROWS, ...DOMESTIC_STANDARD_PLATFORM_ROWS]);
516
597
  }
@@ -530,41 +611,52 @@ export async function loadPlatformEntries(options = {}) {
530
611
  }
531
612
 
532
613
  const domesticEntries = domesticPlatformRows();
614
+ const commandRunner = options.runJson || runJson;
533
615
  const requestedRegion = normalizeKey(options.region || "");
534
616
  const requestedProject = normalizeProjectKey(options.project || "");
535
617
  if ((!requestedRegion || requestedRegion === normalizeKey(DOMESTIC_REGION)) && (!requestedProject || entriesForProject(domesticEntries, options.project).length > 0)) {
536
618
  return domesticEntries;
537
619
  }
538
620
 
539
- const workbook = await runJson("lark-cli", [
540
- "sheets",
541
- "+workbook-info",
542
- "--url",
543
- PLATFORM_CONFIG_URL,
544
- "--as",
545
- "user",
546
- "--format",
547
- "json"
548
- ]);
621
+ let workbook;
622
+ try {
623
+ workbook = await commandRunner("lark-cli", [
624
+ "sheets",
625
+ "+workbook-info",
626
+ "--url",
627
+ PLATFORM_CONFIG_URL,
628
+ "--as",
629
+ "user",
630
+ "--format",
631
+ "json"
632
+ ]);
633
+ } catch (error) {
634
+ await throwPlatformAuthorizationError(error, commandRunner);
635
+ }
549
636
  const sheet = workbook?.data?.sheets?.find(candidate => !candidate.is_hidden) || workbook?.data?.sheets?.[0];
550
637
  if (!sheet?.sheet_id || !Number.isInteger(sheet.row_count) || sheet.row_count < 1) {
551
638
  throw new Error("飞书平台配置没有可读取的工作表");
552
639
  }
553
- const value = await runJson("lark-cli", [
554
- "sheets",
555
- "+csv-get",
556
- "--url",
557
- PLATFORM_CONFIG_URL,
558
- "--sheet-id",
559
- sheet.sheet_id,
560
- "--range",
561
- `A1:I${sheet.row_count}`,
562
- "--include-row-prefix=false",
563
- "--as",
564
- "user",
565
- "--format",
566
- "json"
567
- ]);
640
+ let value;
641
+ try {
642
+ value = await commandRunner("lark-cli", [
643
+ "sheets",
644
+ "+csv-get",
645
+ "--url",
646
+ PLATFORM_CONFIG_URL,
647
+ "--sheet-id",
648
+ sheet.sheet_id,
649
+ "--range",
650
+ `A1:I${sheet.row_count}`,
651
+ "--include-row-prefix=false",
652
+ "--as",
653
+ "user",
654
+ "--format",
655
+ "json"
656
+ ]);
657
+ } catch (error) {
658
+ await throwPlatformAuthorizationError(error, commandRunner);
659
+ }
568
660
  return normalizePlatformRows(rowsFromCsvEnvelope(value));
569
661
  }
570
662
 
@@ -904,7 +996,7 @@ function plainTextForPlatform(article, platformKey) {
904
996
  $("img").each((_, element) => {
905
997
  const image = $(element);
906
998
  const source = image.attr("src") || image.attr("data-qcplay-src") || image.attr("data-src") || image.attr("data-original");
907
- image.replaceWith(source ? `\n${source}\n` : "");
999
+ image.replaceWith(platformKey === "meta" ? "\n" : source ? `\n${source}\n` : "");
908
1000
  });
909
1001
  const markdown = normalizeText($.root().text() || article.markdown)
910
1002
  .replace(/[ \t]+\n/g, "\n")
@@ -2105,7 +2197,7 @@ export function browserPlatformPageUrl(entry, type = "topic") {
2105
2197
  case "x":
2106
2198
  return "https://x.com/compose/post";
2107
2199
  case "meta":
2108
- return (
2200
+ return metaComposerUrl(
2109
2201
  configuredUrl(entry.url, url => url.hostname.toLowerCase() === "business.facebook.com") ||
2110
2202
  configuredUrl(entry.url)
2111
2203
  );
@@ -2133,6 +2225,10 @@ function imageSourcesFromArticle(article) {
2133
2225
  .filter(Boolean);
2134
2226
  }
2135
2227
 
2228
+ export function resolveMetaPublishingOptions(article = {}) {
2229
+ return { images: imageSourcesFromArticle(article) };
2230
+ }
2231
+
2136
2232
  function splitImageSources(value) {
2137
2233
  return normalizeText(value)
2138
2234
  .split(/[,,\r\n]+/)
@@ -6229,6 +6325,114 @@ async function clearWeiboArticleBody(page, bodyInput) {
6229
6325
  }
6230
6326
  }
6231
6327
 
6328
+ function metaComposerUrl(value) {
6329
+ const configured = normalizeText(value);
6330
+ if (!configured) return "";
6331
+ try {
6332
+ const source = new URL(configured);
6333
+ if (source.hostname.toLowerCase() !== "business.facebook.com" || !/\/latest\/composer\/?$/i.test(source.pathname)) {
6334
+ return source.toString();
6335
+ }
6336
+ const target = new URL("/latest/composer/", source.origin);
6337
+ for (const name of ["asset_id", "business_id"]) {
6338
+ const parameter = source.searchParams.get(name);
6339
+ if (parameter) target.searchParams.set(name, parameter);
6340
+ }
6341
+ return target.toString();
6342
+ } catch {
6343
+ return configured;
6344
+ }
6345
+ }
6346
+
6347
+ function genericMetaComposerUrl(value) {
6348
+ try {
6349
+ const url = new URL(value);
6350
+ return new URL("/latest/composer/", url.origin).toString();
6351
+ } catch {
6352
+ return "https://business.facebook.com/latest/composer/";
6353
+ }
6354
+ }
6355
+
6356
+ export function isMetaUnavailableContent(value) {
6357
+ return /很抱歉.{0,24}(?:无法显示内容|内容不可用)|链接可能已过期|(?:sorry,?\s*)?(?:this|the) content isn['’]t available|content isn['’]t available right now|page may only be visible to an audience/i.test(
6358
+ normalizeText(value)
6359
+ );
6360
+ }
6361
+
6362
+ async function metaUnavailablePage(page) {
6363
+ if (typeof page?.locator !== "function") return false;
6364
+ const text = await page.locator("body").innerText().catch(() => "");
6365
+ return isMetaUnavailableContent(text);
6366
+ }
6367
+
6368
+ async function metaUploadedPhotoCount(page) {
6369
+ if (typeof page?.getByText !== "function") return 0;
6370
+ return retryAcrossNavigation(page, () =>
6371
+ page.getByText(/^(?:移除|删除)(?:照片|图片)$|^Remove photo$|^Delete photo$/i, { exact: true }).count()
6372
+ );
6373
+ }
6374
+
6375
+ async function waitForMetaPhotoUploads(page, expectedCount, timeoutMs = 180000) {
6376
+ const deadline = Date.now() + timeoutMs;
6377
+ let stableChecks = 0;
6378
+ do {
6379
+ const text = await page.locator("body").innerText().catch(() => "");
6380
+ if (/上传失败|无法上传|照片.{0,12}(?:不受支持|格式错误)|处理失败|upload failed|couldn['’]t upload|unsupported/i.test(text)) {
6381
+ throw new Error("Facebook 图片上传失败,请检查页面提示或图片格式");
6382
+ }
6383
+ const uploaded = await metaUploadedPhotoCount(page);
6384
+ const pending = await pendingUploadCount(page);
6385
+ if (uploaded >= expectedCount && pending === 0) {
6386
+ stableChecks += 1;
6387
+ if (stableChecks >= 3) return uploaded;
6388
+ } else {
6389
+ stableChecks = 0;
6390
+ }
6391
+ await new Promise(resolve => setTimeout(resolve, 300));
6392
+ } while (Date.now() < deadline);
6393
+ const uploaded = await metaUploadedPhotoCount(page).catch(() => 0);
6394
+ throw new Error(`Facebook 图片上传未完成,预期 ${expectedCount} 张,页面确认 ${uploaded} 张`);
6395
+ }
6396
+
6397
+ export async function uploadMetaImages(page, article, options = {}) {
6398
+ const publishing = resolveMetaPublishingOptions(article);
6399
+ if (publishing.images.length === 0) return { images: 0 };
6400
+ if (typeof page?.waitForEvent !== "function") {
6401
+ throw new Error("Facebook 页面不支持文件选择器事件,无法上传正文图片");
6402
+ }
6403
+
6404
+ const payloads = [];
6405
+ for (let index = 0; index < publishing.images.length; index += 1) {
6406
+ payloads.push(await imageUploadPayload(publishing.images[index], article.articleFile, index, "Facebook"));
6407
+ if ((index + 1) % 5 === 0 || index + 1 === publishing.images.length) {
6408
+ options.onProgress?.(`已准备 Facebook 图片 ${index + 1}/${publishing.images.length}`);
6409
+ }
6410
+ }
6411
+
6412
+ const trigger =
6413
+ (await waitForVisibleButton(page, [/添加照片\s*\/\s*视频/i, /Add photos?\s*\/\s*videos?/i], 8000)) ||
6414
+ (await waitForVisibleTextAcrossFrames(page, /添加照片\s*\/\s*视频|Add photos?\s*\/\s*videos?/i, 3000, false));
6415
+ if (!trigger) {
6416
+ throw new Error("Facebook 发布页未找到“添加照片/视频”控件");
6417
+ }
6418
+
6419
+ const chooserPromise = page.waitForEvent("filechooser", { timeout: 10000 }).catch(() => null);
6420
+ await trigger.click();
6421
+ const chooser = await chooserPromise;
6422
+ if (!chooser) {
6423
+ throw new Error("Facebook 点击“添加照片/视频”后未打开文件选择器");
6424
+ }
6425
+ if (payloads.length > 1 && typeof chooser.isMultiple === "function" && !chooser.isMultiple()) {
6426
+ throw new Error(`Facebook 文件选择器不支持多选,无法一次上传 ${payloads.length} 张图片`);
6427
+ }
6428
+
6429
+ options.onProgress?.(`正在上传 Facebook 图片 ${payloads.length} 张`);
6430
+ await chooser.setFiles(payloads);
6431
+ const uploaded = await waitForMetaPhotoUploads(page, payloads.length, options.uploadTimeoutMs);
6432
+ options.onProgress?.(`Facebook 图片上传完成,共 ${uploaded} 张`);
6433
+ return { images: uploaded };
6434
+ }
6435
+
6232
6436
  export async function insertWeiboArticleRichContent(page, bodyInput, article, spec, fallbackText, options = {}) {
6233
6437
  if (typeof bodyInput.evaluate !== "function" || typeof bodyInput.locator !== "function") {
6234
6438
  await fillLocator(bodyInput, fallbackText);
@@ -7780,14 +7984,14 @@ async function launchBrowserContext(entry, browserChannel, options = {}) {
7780
7984
  const profileDir = browserProfileDir(entry);
7781
7985
  await fs.promises.mkdir(profileDir, { recursive: true, mode: 0o700 });
7782
7986
  const channel = browserChannel || process.env.QCPLAY_BROWSER_CHANNEL || (process.platform === "win32" ? "msedge" : "chrome");
7783
- if (options.awaitAIAction) {
7784
- return launchDetachedBrowserContext(profileDir, channel);
7785
- }
7786
7987
  const detachedContext = await reconnectDetachedBrowserContext(profileDir, chromium);
7787
7988
  if (detachedContext) {
7788
7989
  options.onProgress?.(`已复用当前 ${entry.platform} 授权编辑页,继续执行已确认的动作`);
7789
7990
  return detachedContext;
7790
7991
  }
7992
+ if (options.awaitAIAction) {
7993
+ return launchDetachedBrowserContext(profileDir, channel);
7994
+ }
7791
7995
  return chromium.launchPersistentContext(profileDir, {
7792
7996
  channel,
7793
7997
  headless: false,
@@ -7971,7 +8175,11 @@ export async function publishWithBrowser(entry, article, options = {}) {
7971
8175
  const ownsContext = !options.context;
7972
8176
  const context = options.context || (await launchBrowserContext(entry, options.browserChannel, options));
7973
8177
  const pages = context.pages();
7974
- const page = pages[0] || (await context.newPage());
8178
+ // A detached Edge session may briefly restore and then close its first tab.
8179
+ // Use a fresh page for AI-action flows so navigation keeps a stable target.
8180
+ let page = options.awaitAIAction && typeof context.newPage === "function"
8181
+ ? await context.newPage()
8182
+ : pages[0] || (await context.newPage());
7975
8183
  const promptUser = options.waitForUser || (message => waitForUser(message, options.streams));
7976
8184
  let haoyouEditorOpened = false;
7977
8185
  let preserveEditorOnFailure = false;
@@ -7991,13 +8199,29 @@ export async function publishWithBrowser(entry, article, options = {}) {
7991
8199
  if (entry.platformKey === "xiaohongshu") {
7992
8200
  return await publishXiaohongshu(page, entry, preparedArticle, spec, options, promptUser);
7993
8201
  }
7994
- const editorUrl = browserPlatformPageUrl(entry);
8202
+ let editorUrl = browserPlatformPageUrl(entry);
8203
+ let metaFallbackTried = false;
7995
8204
  let editorReady = false;
7996
8205
  let loginHandled = false;
7997
- const attempts = entry.platformKey === "haoyou" ? 3 : 2;
8206
+ const attempts = entry.platformKey === "haoyou" || entry.platformKey === "meta" ? 3 : 2;
7998
8207
  for (let attempt = 0; attempt < attempts; attempt += 1) {
8208
+ if (typeof page.isClosed === "function" && page.isClosed()) {
8209
+ const livePages = context.pages().filter(candidate => !(typeof candidate.isClosed === "function" && candidate.isClosed()));
8210
+ page = livePages.at(-1) || (await context.newPage());
8211
+ }
7999
8212
  await page.goto(editorUrl, { waitUntil: "domcontentloaded", timeout: 60000 });
8000
8213
  await afterNavigation(page);
8214
+ if (entry.platformKey === "meta" && (await metaUnavailablePage(page))) {
8215
+ if (!metaFallbackTried) {
8216
+ metaFallbackTried = true;
8217
+ editorUrl = genericMetaComposerUrl(editorUrl);
8218
+ options.onProgress?.(`${entry.platform} 资产专用链接不可用,正在尝试 Meta 通用发布入口`);
8219
+ continue;
8220
+ }
8221
+ throw new Error(
8222
+ `${entry.platform} 当前账号无法访问配置中的 Meta 资产,请确认该账号已加入 business_id 对应的商务管理平台并拥有 asset_id 对应主页的内容发布权限`
8223
+ );
8224
+ }
8001
8225
  const readyTimeout = attempt === 0 && spec.loginPage ? 3000 : 15000;
8002
8226
  const ready = entry.platformKey === "haoyou"
8003
8227
  ? await waitForHaoyouTargetEditor(page, editorUrl, spec, readyTimeout)
@@ -8039,6 +8263,7 @@ export async function publishWithBrowser(entry, article, options = {}) {
8039
8263
  throw new Error(`${entry.platform} 页面中未找到正文编辑器,页面结构可能已更新`);
8040
8264
  }
8041
8265
  let richContent = null;
8266
+ let platformMedia = null;
8042
8267
  if (entry.platformKey === "haoyou") {
8043
8268
  if (options.previewTextOnly) {
8044
8269
  await fillStableHaoyouValue(page, bodyInput, body, "正文");
@@ -8055,6 +8280,9 @@ export async function publishWithBrowser(entry, article, options = {}) {
8055
8280
  } else {
8056
8281
  await fillLocator(bodyInput, body);
8057
8282
  }
8283
+ if (entry.platformKey === "meta") {
8284
+ platformMedia = await uploadMetaImages(page, preparedArticle, options);
8285
+ }
8058
8286
 
8059
8287
  let submitButton = await waitForVisible(page, spec.submitSelectors, 5000);
8060
8288
  submitButton ||= await waitForVisibleButton(page, spec.submit, 5000);
@@ -8066,7 +8294,7 @@ export async function publishWithBrowser(entry, article, options = {}) {
8066
8294
  }
8067
8295
  const review = await reviewPreparedDraft(page, entry, options);
8068
8296
  if (review) {
8069
- return { ...review, ...richContent };
8297
+ return { ...review, ...richContent, ...platformMedia };
8070
8298
  }
8071
8299
  if (options.draftOnly) {
8072
8300
  return {
@@ -8075,7 +8303,8 @@ export async function publishWithBrowser(entry, article, options = {}) {
8075
8303
  draftOnly: true,
8076
8304
  approvalRequired: true,
8077
8305
  approvalActions: ["publish", "cancel"],
8078
- ...richContent
8306
+ ...richContent,
8307
+ ...platformMedia
8079
8308
  };
8080
8309
  }
8081
8310
  if (entry.platformKey === "haoyou") {
@@ -8096,7 +8325,7 @@ export async function publishWithBrowser(entry, article, options = {}) {
8096
8325
  if (options.keepOpen) {
8097
8326
  await promptUser(`${entry.platform} 已提交,浏览器保持打开供检查`);
8098
8327
  }
8099
- return { url: page.url() };
8328
+ return { url: page.url(), ...platformMedia };
8100
8329
  } catch (error) {
8101
8330
  if ((entry.platformKey === "haoyou" && haoyouEditorOpened) || entry.platformKey === "xiaohongshu") {
8102
8331
  if (!ownsContext) throw error;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qcplay/cli",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "description": "QCPlay CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,6 +38,7 @@
38
38
  },
39
39
  "license": "MIT",
40
40
  "dependencies": {
41
+ "@larksuite/cli": "1.0.96",
41
42
  "cheerio": "^1.0.0-rc.12",
42
43
  "playwright-core": "1.40.1"
43
44
  }