siluzan-tso-cli 1.1.35-beta.2 → 1.1.35-beta.3

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.
Files changed (120) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +1679 -1633
  3. package/dist/skill/AGENTS.md +11 -10
  4. package/dist/skill/SKILL.md +1 -1
  5. package/dist/skill/_meta.json +2 -2
  6. package/dist/skill/assets/campaign-create-template.md +15 -15
  7. package/dist/skill/assets/market-analysis-rules.md +14 -14
  8. package/dist/skill/assets/pmax-create-template.md +6 -6
  9. package/dist/skill/assets/pmax-lead-form-template.md +3 -3
  10. package/dist/skill/references/README.md +21 -21
  11. package/dist/skill/references/accounts/accounts.md +42 -42
  12. package/dist/skill/references/analytics/account-analytics.md +46 -39
  13. package/dist/skill/references/analytics/keyword-planner-workflows.md +14 -13
  14. package/dist/skill/references/analytics/rag.md +7 -7
  15. package/dist/skill/references/analytics/reporting.md +1 -1
  16. package/dist/skill/references/core/agent-conventions.md +20 -20
  17. package/dist/skill/references/core/intent-routing.md +116 -117
  18. package/dist/skill/references/core/subagent-orchestration.md +1 -1
  19. package/dist/skill/references/core/workflows.md +12 -12
  20. package/dist/skill/references/google-ads/google-ads-campaign-plan.md +29 -31
  21. package/dist/skill/references/google-ads/google-ads.md +7 -7
  22. package/dist/skill/references/google-ads/rules/google-ads-keyword-taxonomy.md +14 -14
  23. package/dist/skill/references/operations/hosted-automation-optimize-index.md +2 -2
  24. package/dist/skill/references/operations/hosted-automation-scenarios.md +2 -2
  25. package/dist/skill/references/operations/hosted-automation-self-control.md +6 -6
  26. package/dist/skill/references/operations/hosted-automation-user-catalog.md +17 -17
  27. package/dist/skill/references/report-templates/README.md +68 -0
  28. package/dist/skill/references/report-templates/REPORT-WORKFLOW.md +2 -2
  29. package/dist/skill/references/report-templates/bing-period-report.md +53 -53
  30. package/dist/skill/references/report-templates/google-ads-diagnosis.md +28 -2
  31. package/dist/skill/references/report-templates/google-inquiry-analysis.md +8 -8
  32. package/dist/skill/references/report-templates/google-period-report-excel.md +2 -2
  33. package/dist/skill/references/report-templates/okki-weekly-google-client.md +55 -55
  34. package/dist/skill/references/report-templates/tiktok-period-report.md +22 -21
  35. package/dist/skill/report-templates/GoogleAdsDiagnosisReport.html +390 -892
  36. package/dist/skill/report-templates/REPORT-WORKFLOW.md +2 -2
  37. package/dist/skill/report-templates/bing-period-report.md +53 -53
  38. package/dist/skill/report-templates/google-ads-diagnosis.md +28 -2
  39. package/dist/skill/report-templates/google-inquiry-analysis.md +8 -8
  40. package/dist/skill/report-templates/google-period-report-excel.md +2 -2
  41. package/dist/skill/report-templates/meta-period-report.html +1 -4
  42. package/dist/skill/report-templates/okki-weekly-google-client.md +55 -55
  43. package/dist/skill/report-templates/report-template-academic.html +0 -2
  44. package/dist/skill/report-templates/report-template.html +0 -2
  45. package/dist/skill/report-templates/tiktok-period-report.md +22 -21
  46. package/dist/skill/report-templates/website-diagnosis-report.html +1 -5
  47. package/dist/skill/scripts/install.ps1 +1 -1
  48. package/dist/skill/scripts/install.sh +1 -1
  49. package/eval/cases/accounts-entityid-vs-mediaccustomerid.scenario.json +2 -14
  50. package/eval/cases/accounts-mcc-bind-inquiry.scenario.json +1 -3
  51. package/eval/cases/accounts-single-balance-not-bulk.scenario.json +3 -14
  52. package/eval/cases/budget-display-not-raw-micros.scenario.json +1 -8
  53. package/eval/cases/clue-meta-leads-json.scenario.json +2 -14
  54. package/eval/cases/clue-tiktok-leads-json.scenario.json +2 -11
  55. package/eval/cases/destructive-account-delink-needs-confirm.scenario.json +3 -9
  56. package/eval/cases/destructive-forewarning-delete-needs-confirm.scenario.json +3 -9
  57. package/eval/cases/destructive-invoice-apply-needs-confirm.scenario.json +3 -9
  58. package/eval/cases/facebook-analysis-google-section-aliases.scenario.json +2 -11
  59. package/eval/cases/facebook-analysis-not-google-keywords.scenario.json +3 -10
  60. package/eval/cases/facebook-analysis-period-default.scenario.json +2 -14
  61. package/eval/cases/finance-invoice-info-list.scenario.json +3 -11
  62. package/eval/cases/forewarning-list-google.scenario.json +3 -14
  63. package/eval/cases/google-ads-no-structural-without-confirm.scenario.json +2 -6
  64. package/eval/cases/google-analysis-keywords-route.scenario.json +2 -14
  65. package/eval/cases/human-p1-multiturn.scenario.json +1 -5
  66. package/eval/cases/ir-p1-vs-p8-account-vs-url.scenario.json +5 -17
  67. package/eval/cases/ir-p4-period-not-diagnosis.scenario.json +3 -16
  68. package/eval/cases/ir-p8-network-diagnosis-with-url.scenario.json +3 -25
  69. package/eval/cases/ir-p8-website-monitor-report-with-url.scenario.json +3 -28
  70. package/eval/cases/ir-p8-website-test-report-no-url.scenario.json +3 -14
  71. package/eval/cases/ir-p9-industry-not-p8.scenario.json +3 -19
  72. package/eval/cases/ir-vague-report-clarify.scenario.json +3 -14
  73. package/eval/cases/ir-w3-vs-p8-from-website.scenario.json +2 -8
  74. package/eval/cases/ir-w5-keyword-planner-url-core-terms.scenario.json +2 -22
  75. package/eval/cases/meta-single-balance-not-bulk.scenario.json +3 -17
  76. package/eval/cases/no-legacy-json-flag.scenario.json +2 -6
  77. package/eval/cases/open-account-google-noninteractive.scenario.json +1 -3
  78. package/eval/cases/open-account-tiktok-license-file.scenario.json +1 -3
  79. package/eval/cases/optimize-list-by-account.scenario.json +3 -11
  80. package/eval/cases/p1-single-account-profile.scenario.json +1 -11
  81. package/eval/cases/p2-balance-scan-bulk.scenario.json +2 -9
  82. package/eval/cases/p3-accounts-digest.scenario.json +1 -5
  83. package/eval/cases/p4-fb-meta-period-report.scenario.json +2 -12
  84. package/eval/cases/p4-period-report-window.scenario.json +1 -8
  85. package/eval/cases/pmax-asset-group-create-with-bg.scenario.json +2 -13
  86. package/eval/cases/pmax-brand-edit-routing.scenario.json +3 -16
  87. package/eval/cases/pmax-edit-not-campaign-edit.scenario.json +2 -12
  88. package/eval/cases/pmax-enable-brand-guidelines.scenario.json +2 -12
  89. package/eval/cases/pmax-no-assets-update-brand-fields.scenario.json +3 -14
  90. package/eval/cases/rag-before-keyword-expand.scenario.json +1 -11
  91. package/eval/cases/rag-list-then-query.scenario.json +2 -14
  92. package/eval/cases/report-list-google.scenario.json +2 -11
  93. package/eval/cases/report-push-list-google.scenario.json +2 -11
  94. package/eval/cases/setup-login-or-env.scenario.json +1 -3
  95. package/eval/cases/setup-siluzan-data-permission-env.scenario.json +1 -3
  96. package/eval/cases/tiktok-bc-bind-inquiry.scenario.json +2 -6
  97. package/eval/cases/time-range-user-delegates-default.scenario.json +1 -8
  98. package/eval/cases/tips-json-out-filtering.scenario.json +1 -3
  99. package/eval/cases/tips-large-json-pagination.scenario.json +1 -3
  100. package/eval/cases/uj-ad-campaign-validate-before-create-stub.scenario.json +2 -11
  101. package/eval/cases/uj-ad-outdoor-campgear-search-plan.scenario.json +1 -3
  102. package/eval/cases/uj-ad-website-search-plan-table-format.scenario.json +1 -3
  103. package/eval/cases/uj-analytics-30d-pdf-campaign-device-geo.scenario.json +6 -18
  104. package/eval/cases/uj-analytics-compare-google-tiktok-last-month-roi.scenario.json +1 -8
  105. package/eval/cases/uj-analytics-ecommerce-industry-report.scenario.json +2 -14
  106. package/eval/cases/uj-analytics-google-weekly-trends-campaigns-keywords.scenario.json +2 -11
  107. package/eval/cases/uj-analytics-report-push-weekly-email.scenario.json +1 -3
  108. package/eval/cases/uj-finance-invoice-records-this-month.scenario.json +2 -11
  109. package/eval/cases/uj-life-newbie-siluzan-google-end-to-end.scenario.json +1 -4
  110. package/eval/cases/uj-ops-google-accounts-list-normal.scenario.json +2 -17
  111. package/eval/cases/uj-ops-google-yesterday-spend-conversions.scenario.json +2 -14
  112. package/eval/cases/uj-ops-pause-worst-adgroup-confirm.scenario.json +2 -6
  113. package/eval/cases/uj-ops-tiktok-leads-last-week.scenario.json +3 -17
  114. package/eval/cases/uj-patrol-cpc-spike-adgroups-over-15.scenario.json +1 -5
  115. package/eval/cases/uj-patrol-forewarning-create-daily-cap-3000.scenario.json +1 -3
  116. package/eval/cases/uj-patrol-forewarning-trigger-records.scenario.json +3 -17
  117. package/eval/cases/uj-patrol-google-balances-low.scenario.json +2 -11
  118. package/eval/cases/uj-roi-optimize-records-then-execute-cautiously.scenario.json +3 -14
  119. package/eval/cases/uj-roi-search-terms-add-negative-keywords.scenario.json +2 -14
  120. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4357,7 +4357,14 @@ async function enrichOverviewRecordWithRealtimeBalance(media, accountId, config,
4357
4357
  if (!BALANCE_SUPPORTED_MEDIA.includes(media)) {
4358
4358
  return record;
4359
4359
  }
4360
- const balanceMap = await fetchBalanceMap(media, [accountId], config, void 0, void 0, verbose);
4360
+ const balanceMap = await fetchBalanceMap(
4361
+ media,
4362
+ [accountId],
4363
+ config,
4364
+ void 0,
4365
+ void 0,
4366
+ verbose
4367
+ );
4361
4368
  const bal = balanceMap.get(accountId);
4362
4369
  if (bal == null) return record;
4363
4370
  const out = { ...record };
@@ -4630,11 +4637,7 @@ async function fetchBalanceAndOverviewSequential(opts) {
4630
4637
  const ids = chunks[chunkIdx];
4631
4638
  const oneBased = chunkIdx + 1;
4632
4639
  opts.onChunkStart?.(oneBased, chunks.length, ids);
4633
- const routeGroups = groupAccountIdsByOverviewRoute(
4634
- media,
4635
- ids,
4636
- opts.accountMediaTypes
4637
- );
4640
+ const routeGroups = groupAccountIdsByOverviewRoute(media, ids, opts.accountMediaTypes);
4638
4641
  let chunkOverviewSize = 0;
4639
4642
  for (const [route, routeIds] of routeGroups) {
4640
4643
  const overviewChunk = await fetchOverviewMap(
@@ -4649,14 +4652,7 @@ async function fetchBalanceAndOverviewSequential(opts) {
4649
4652
  for (const [k, v] of overviewChunk) overviewMap.set(k, v);
4650
4653
  }
4651
4654
  opts.onOverviewDone?.(oneBased, chunks.length, ids, chunkOverviewSize);
4652
- const balanceChunk = await fetchBalanceMap(
4653
- media,
4654
- ids,
4655
- config,
4656
- startDate,
4657
- endDate,
4658
- verbose
4659
- );
4655
+ const balanceChunk = await fetchBalanceMap(media, ids, config, startDate, endDate, verbose);
4660
4656
  opts.onBalanceDone?.(oneBased, chunks.length, ids, balanceChunk.size);
4661
4657
  for (const [k, v] of balanceChunk) balanceMap.set(k, v);
4662
4658
  }
@@ -4986,20 +4982,16 @@ async function runAccountsDigest(opts) {
4986
4982
  verbose: opts.verbose,
4987
4983
  accountMediaTypes: buildAccountMediaTypesMap(accountsList),
4988
4984
  onChunkStart: (chunkIdx, totalChunks, ids) => {
4989
- process.stderr.write(
4990
- ` \u2192 [\u6D88\u8017] \u7B2C ${chunkIdx}/${totalChunks} \u6279\uFF08${ids.length} \u6237\uFF09\u2026
4991
- `
4992
- );
4985
+ process.stderr.write(` \u2192 [\u6D88\u8017] \u7B2C ${chunkIdx}/${totalChunks} \u6279\uFF08${ids.length} \u6237\uFF09\u2026
4986
+ `);
4993
4987
  },
4994
4988
  onOverviewDone: (chunkIdx, totalChunks, ids, size) => {
4995
4989
  process.stderr.write(
4996
4990
  ` \u2713 [\u6D88\u8017] \u7B2C ${chunkIdx}/${totalChunks} \u6279\u5B8C\u6210\uFF08${ids.length} \u6237 \u2192 ${size} \u6761\uFF09
4997
4991
  `
4998
4992
  );
4999
- process.stderr.write(
5000
- ` \u2192 [\u4F59\u989D] \u7B2C ${chunkIdx}/${totalChunks} \u6279\uFF08${ids.length} \u6237\uFF09\u2026
5001
- `
5002
- );
4993
+ process.stderr.write(` \u2192 [\u4F59\u989D] \u7B2C ${chunkIdx}/${totalChunks} \u6279\uFF08${ids.length} \u6237\uFF09\u2026
4994
+ `);
5003
4995
  },
5004
4996
  onBalanceDone: (chunkIdx, totalChunks, ids, size) => {
5005
4997
  process.stderr.write(
@@ -5773,14 +5765,14 @@ function computeBackoffMs(attempt, policy) {
5773
5765
  return capped + jitter;
5774
5766
  }
5775
5767
  function sleep3(ms, signal) {
5776
- return new Promise((resolve15, reject) => {
5768
+ return new Promise((resolve17, reject) => {
5777
5769
  if (signal?.aborted) {
5778
5770
  reject(new Error("retry sleep aborted"));
5779
5771
  return;
5780
5772
  }
5781
5773
  const timer = setTimeout(() => {
5782
5774
  signal?.removeEventListener("abort", onAbort);
5783
- resolve15();
5775
+ resolve17();
5784
5776
  }, ms);
5785
5777
  const onAbort = () => {
5786
5778
  clearTimeout(timer);
@@ -101574,7 +101566,7 @@ function defaultDateRange3() {
101574
101566
  const fmt2 = (d) => d.toISOString().slice(0, 10);
101575
101567
  return { startDate: fmt2(start), endDate: fmt2(end) };
101576
101568
  }
101577
- function resolveDateRange2(start, end) {
101569
+ function resolveDateRange(start, end) {
101578
101570
  if (start && end) return { startDate: start, endDate: end };
101579
101571
  if (!start && !end) return defaultDateRange3();
101580
101572
  console.error("\n\u274C --start \u4E0E --end \u987B\u540C\u65F6\u4F20\u5165\uFF0C\u6216\u540C\u65F6\u7701\u7565\u4EE5\u4F7F\u7528\u9ED8\u8BA4\u8FD1 7 \u5929\uFF08\u622A\u81F3\u6628\u5929\uFF09\u3002\n");
@@ -101583,7 +101575,7 @@ function resolveDateRange2(start, end) {
101583
101575
  function buildSearchParams(def, start, end, extras) {
101584
101576
  const params = new URLSearchParams();
101585
101577
  if (def.dateMode === "range") {
101586
- const { startDate, endDate } = resolveDateRange2(start, end);
101578
+ const { startDate, endDate } = resolveDateRange(start, end);
101587
101579
  params.set("startDate", startDate);
101588
101580
  params.set("endDate", endDate);
101589
101581
  }
@@ -101718,7 +101710,7 @@ function endpointHintFrom(def) {
101718
101710
  }
101719
101711
  function manifestDateRange(def, start, end) {
101720
101712
  if (def.dateMode === "none") return null;
101721
- const { startDate, endDate } = resolveDateRange2(start, end);
101713
+ const { startDate, endDate } = resolveDateRange(start, end);
101722
101714
  return { mode: "range", start: startDate, end: endDate };
101723
101715
  }
101724
101716
  async function fetchSectionPayload(def, opts, config, id, http2) {
@@ -101744,7 +101736,7 @@ async function fetchSectionPayload(def, opts, config, id, http2) {
101744
101736
  if (n(opts.conversionsGreater)) extras.conversionsGreater = opts.conversionsGreater;
101745
101737
  }
101746
101738
  if (def.name === "materials") {
101747
- const { startDate, endDate } = resolveDateRange2(opts.start, opts.end);
101739
+ const { startDate, endDate } = resolveDateRange(opts.start, opts.end);
101748
101740
  const q = `?${new URLSearchParams({ startDate, endDate }).toString()}`;
101749
101741
  const videoPath = `/reporting/media-account/${id}/Videos${q}`;
101750
101742
  const imagePath = `/reporting/media-account/${id}/CampaignAssetView${q}`;
@@ -101792,7 +101784,7 @@ async function fetchSectionPayload(def, opts, config, id, http2) {
101792
101784
  );
101793
101785
  }
101794
101786
  if (def.name === "daily-metrics") {
101795
- const { startDate, endDate } = resolveDateRange2(opts.start, opts.end);
101787
+ const { startDate, endDate } = resolveDateRange(opts.start, opts.end);
101796
101788
  const { startInstant, endInstant } = resolveDailyMetricsQueryTimes(startDate, endDate);
101797
101789
  const params = new URLSearchParams({
101798
101790
  mediaCustomerIds: id,
@@ -102360,8 +102352,8 @@ async function runBatch(opts) {
102360
102352
  return;
102361
102353
  }
102362
102354
  if (raw instanceof HttpRetryError) {
102363
- const next = raw.class === "permanent" ? "failed_permanent" : "failed_retryable";
102364
- await recordTaskTransition(opts.paths, inner, task, next, {
102355
+ const next2 = raw.class === "permanent" ? "failed_permanent" : "failed_retryable";
102356
+ await recordTaskTransition(opts.paths, inner, task, next2, {
102365
102357
  attempts: raw.attempts,
102366
102358
  error: raw.message,
102367
102359
  status: raw.status,
@@ -102371,12 +102363,28 @@ async function runBatch(opts) {
102371
102363
  await onTaskFinish(false);
102372
102364
  return;
102373
102365
  }
102374
- await recordTaskTransition(opts.paths, inner, task, "failed_permanent", {
102366
+ const classified = classifyError(err);
102367
+ if (classified.class === "abort") {
102368
+ tokenInvalidated = (classified.status ?? 0) === 401;
102369
+ await recordTaskTransition(opts.paths, inner, task, "failed_permanent", {
102370
+ attempts: 1,
102371
+ error: err.message,
102372
+ status: classified.status,
102373
+ durationMs
102374
+ });
102375
+ await writeTaskError(opts.paths, errorsDir, task, err, "abort");
102376
+ await onTaskFinish(false);
102377
+ aborterToken.abort();
102378
+ return;
102379
+ }
102380
+ const next = classified.class === "permanent" ? "failed_permanent" : "failed_retryable";
102381
+ await recordTaskTransition(opts.paths, inner, task, next, {
102375
102382
  attempts: 1,
102376
102383
  error: err.message,
102384
+ status: classified.status,
102377
102385
  durationMs
102378
102386
  });
102379
- await writeTaskError(opts.paths, errorsDir, task, err, "permanent");
102387
+ await writeTaskError(opts.paths, errorsDir, task, err, classified.class);
102380
102388
  await onTaskFinish(false);
102381
102389
  }
102382
102390
  },
@@ -103165,9 +103173,7 @@ async function runAllSections(opts) {
103165
103173
  );
103166
103174
  const cliVersion = getCurrentVersion2();
103167
103175
  const overallT0 = performance4.now();
103168
- const tasks = targets.map(
103169
- (def) => () => fetchAndWriteSection(def, opts, config, id, cliVersion)
103170
- );
103176
+ const tasks = targets.map((def) => () => fetchAndWriteSection(def, opts, config, id, cliVersion));
103171
103177
  let results = await runWithConcurrency(tasks, concurrency);
103172
103178
  const failedIndices = results.map((r, i) => r.ok ? -1 : i).filter((i) => i >= 0);
103173
103179
  if (failedIndices.length > 0) {
@@ -103415,9 +103421,9 @@ var init_google_analysis3 = __esm({
103415
103421
  init_dist();
103416
103422
  init_dist();
103417
103423
  import * as fs23 from "fs";
103418
- import * as path31 from "path";
103424
+ import * as path33 from "path";
103419
103425
  import { fileURLToPath as fileURLToPath7 } from "url";
103420
- import { Command as Command2 } from "commander";
103426
+ import { Command as Command4 } from "commander";
103421
103427
 
103422
103428
  // src/commands/login/urls.ts
103423
103429
  init_defaults();
@@ -103902,8 +103908,8 @@ function deriveWebUrl(apiBaseUrl) {
103902
103908
  }
103903
103909
  var TSO_UMI_ROUTE_PREFIX = "/v3umijs/tso";
103904
103910
  function buildTsoPageWebUrl(webUrl, tsoSubRoute, extraQuery) {
103905
- const path32 = tsoSubRoute.replace(/^\/+/, "");
103906
- const umiPath = `${TSO_UMI_ROUTE_PREFIX}/${path32}`;
103911
+ const path34 = tsoSubRoute.replace(/^\/+/, "");
103912
+ const umiPath = `${TSO_UMI_ROUTE_PREFIX}/${path34}`;
103907
103913
  const q = new URLSearchParams({ tso: umiPath });
103908
103914
  if (extraQuery) {
103909
103915
  for (const [key, value] of Object.entries(extraQuery)) {
@@ -103911,7 +103917,7 @@ function buildTsoPageWebUrl(webUrl, tsoSubRoute, extraQuery) {
103911
103917
  if (v) q.set(key, v);
103912
103918
  }
103913
103919
  }
103914
- return `${webUrl.replace(/\/+$/, "")}/v3/foreign_trade/tso/${path32}?${q.toString()}`;
103920
+ return `${webUrl.replace(/\/+$/, "")}/v3/foreign_trade/tso/${path34}?${q.toString()}`;
103915
103921
  }
103916
103922
  function cmdConfigShow() {
103917
103923
  const shared = readSharedConfig();
@@ -107382,1304 +107388,334 @@ async function runReportPushReceiveEmails(opts) {
107382
107388
  // src/commands/report/register-commander-types.ts
107383
107389
  var splitCsv = (v) => v.split(",").map((s) => s.trim()).filter(Boolean);
107384
107390
 
107385
- // src/commands/report-tiktok-analysis/_shared.ts
107386
- init_auth();
107387
- init_version();
107388
- var TIKTOK_TAKE_DEFAULT = 100;
107389
- function resolveTake(raw) {
107390
- const n = typeof raw === "number" ? raw : parseInt(String(raw), 10);
107391
- return Number.isFinite(n) && n > 0 ? n : TIKTOK_TAKE_DEFAULT;
107392
- }
107393
- function resolveDateRange(start, end) {
107394
- if (start && end) return { startDate: start, endDate: end };
107395
- if (!start && !end) {
107396
- const endD = /* @__PURE__ */ new Date();
107397
- endD.setDate(endD.getDate() - 1);
107398
- const startD = new Date(endD);
107399
- startD.setDate(startD.getDate() - 6);
107400
- const fmt2 = (d) => d.toISOString().slice(0, 10);
107401
- return { startDate: fmt2(startD), endDate: fmt2(endD) };
107402
- }
107403
- console.error("\n\u274C --start \u4E0E --end \u987B\u540C\u65F6\u4F20\u5165\uFF0C\u6216\u540C\u65F6\u7701\u7565\u4EE5\u4F7F\u7528\u9ED8\u8BA4\u8FD1 7 \u5929\uFF08\u622A\u81F3\u6628\u5929\uFF09\u3002\n");
107404
- process.exit(1);
107405
- }
107406
- function assertTikTokAccountId(id) {
107407
- const t = id.trim();
107408
- if (!t) {
107409
- console.error("\n\u274C --account \u4E0D\u80FD\u4E3A\u7A7A\u3002\n");
107410
- process.exit(1);
107411
- }
107412
- if (!/^\d+$/.test(t)) {
107413
- console.error(
107414
- "\n\u274C --account \u987B\u4E3A TikTok \u5E7F\u544A\u4E3B ID\uFF08\u6570\u5B57 mediaCustomerId\uFF0C\u4E0E list-accounts -m TikTok \u4E00\u81F4\uFF09\u3002\n"
107415
- );
107416
- process.exit(1);
107417
- }
107418
- return t;
107419
- }
107420
- function reportingUrl(config, accountId, segment, query) {
107421
- const q = query ? query.startsWith("?") ? query : `?${query}` : "";
107422
- return `${config.apiBaseUrl}/reporting/media-account/TikTok/${accountId}/${segment}${q}`;
107423
- }
107424
- var AUDIENCE_DIMENSIONS = [
107425
- "gender",
107426
- "age",
107427
- "interest_category",
107428
- "country_code",
107429
- "platform",
107430
- "language"
107431
- ];
107432
- async function emitTiktokSnapshot(opts, section, accountId, startDate, endDate, data) {
107433
- const summary = await writeReportAnalysisSnapshot({
107434
- snapshotDir: opts.jsonOut,
107435
- section,
107436
- accountId,
107437
- dateRange: { start: startDate, end: endDate },
107438
- payload: data,
107439
- cliVersion: getCurrentVersion2()
107391
+ // src/commands/report/register.ts
107392
+ function register13(program2) {
107393
+ const reportCmd = program2.command("report").description("\u4F18\u5316\u62A5\u544A\u4E0E\u63A8\u9001\u7BA1\u7406");
107394
+ reportCmd.command("list").description("\u67E5\u8BE2\u4F18\u5316\u62A5\u544A\u5217\u8868").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").option("-t, --type <type>", "\u62A5\u544A\u7C7B\u578B\uFF08\u5982 Daily / Hourly\uFF09").option("-s, --status <ready>", "\u751F\u6210\u72B6\u6001\uFF08true/false\uFF09").option("-k, --keyword <text>", "\u62A5\u544A\u540D\u79F0\u5173\u952E\u5B57").option("--start <date>", "\u5F00\u59CB\u65E5\u671F").option("--end <date>", "\u7ED3\u675F\u65E5\u671F").option("-p, --page <n>", "\u9875\u7801", parseInt).option("--page-size <n>", "\u6BCF\u9875\u6570\u91CF", parseInt).option("--token <token>", "Auth Token").option(
107395
+ "--json-out <path>",
107396
+ "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
107397
+ void 0
107398
+ ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107399
+ await runReportList({
107400
+ token: opts.token,
107401
+ media: opts.media,
107402
+ type: opts.type,
107403
+ status: opts.status,
107404
+ keyword: opts.keyword,
107405
+ startDate: opts.start,
107406
+ endDate: opts.end,
107407
+ page: opts.page,
107408
+ pageSize: opts.pageSize,
107409
+ jsonOut: opts.jsonOut,
107410
+ verbose: opts.verbose
107411
+ });
107412
+ });
107413
+ reportCmd.command("create").description("\u6279\u91CF\u751F\u6210\u4F18\u5316\u62A5\u544A").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").requiredOption("-a, --accounts <ids>", "\u8D26\u6237 ID\uFF0C\u9017\u53F7\u5206\u9694").requiredOption("-t, --type <type>", "\u62A5\u544A\u7C7B\u578B\uFF0C\u5982 Daily").requiredOption("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD").requiredOption("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("--token <token>", "Auth Token").option(
107414
+ "--json-out <path>",
107415
+ "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
107416
+ void 0
107417
+ ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107418
+ await runReportCreate({
107419
+ token: opts.token,
107420
+ media: opts.media,
107421
+ accounts: opts.accounts.split(",").map((s) => s.trim()),
107422
+ type: opts.type,
107423
+ startDate: opts.start,
107424
+ endDate: opts.end,
107425
+ jsonOut: opts.jsonOut,
107426
+ verbose: opts.verbose
107427
+ });
107428
+ });
107429
+ reportCmd.command("delete").description("\u5220\u9664\u4F18\u5316\u62A5\u544A\uFF08--id \u6216 --ids \u81F3\u5C11\u4F20\u4E00\u4E2A\uFF09").option("--id <id>", "\u5355\u6761\u62A5\u544A entityId\uFF08\u4E0E --ids \u4E8C\u9009\u4E00\uFF09").option("--ids <ids>", "\u6279\u91CF\u5220\u9664\uFF0C\u9017\u53F7\u5206\u9694 entityId\uFF08\u4E0E --id \u4E8C\u9009\u4E00\uFF09").option("--token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107430
+ await runReportDelete({
107431
+ token: opts.token,
107432
+ id: opts.id,
107433
+ ids: opts.ids ? opts.ids.split(",").map((s) => s.trim()) : void 0,
107434
+ verbose: opts.verbose
107435
+ });
107436
+ });
107437
+ reportCmd.command("meta-overview").description(
107438
+ "Meta \u8D26\u6237\u5206\u6790\u603B\u89C8\uFF08OverviewSectionData\uFF1BFacebookAds \u6388\u6743\u6237\u8D70 FacebookAds/act_\uFF0CMetaAd \u5F00\u6237\u8D70 MetaAd\uFF09"
107439
+ ).requiredOption("-a, --account <id>", "Meta \u5E7F\u544A\u8D26\u6237 mediaCustomerId\uFF08\u6570\u5B57\u6216 act_<\u6570\u5B57>\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\u7565\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("--token <token>", "Auth Token").option(
107440
+ "--json-out <path>",
107441
+ "\u843D\u76D8\u76EE\u5F55\u5E76\u66F4\u65B0 report-manifest[-<accountId>].json\uFF1B\u6587\u4EF6\u540D\u4E3A `<section>[-<accountId>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09"
107442
+ ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107443
+ await runReportMetaOverview({
107444
+ token: opts.token,
107445
+ account: opts.account,
107446
+ startDate: opts.start,
107447
+ endDate: opts.end,
107448
+ jsonOut: opts.jsonOut,
107449
+ verbose: opts.verbose
107450
+ });
107451
+ });
107452
+ const pushCmd = reportCmd.command("push").description("\u63A8\u9001\u7BA1\u7406\uFF08\u90AE\u4EF6\u62A5\u544A\u63A8\u9001\u4EFB\u52A1\uFF09");
107453
+ pushCmd.command("list").description("\u67E5\u8BE2\u63A8\u9001\u4EFB\u52A1\u5217\u8868").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").option("-k, --keyword <text>", "\u63A8\u9001\u540D\u79F0\u5173\u952E\u5B57").option("-t, --type <type>", "\u62A5\u544A\u7C7B\u578B").option("--stopped", "\u53EA\u663E\u793A\u5DF2\u505C\u7528\u7684\u63A8\u9001", false).option("--start <date>", "\u5F00\u59CB\u65E5\u671F").option("--end <date>", "\u7ED3\u675F\u65E5\u671F").option("-p, --page <n>", "\u9875\u7801", parseInt).option("--page-size <n>", "\u6BCF\u9875\u6570\u91CF", parseInt).option("--token <token>", "Auth Token").option(
107454
+ "--json-out <path>",
107455
+ "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
107456
+ void 0
107457
+ ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107458
+ await runReportPushList({
107459
+ token: opts.token,
107460
+ media: opts.media,
107461
+ keyword: opts.keyword,
107462
+ type: opts.type,
107463
+ stopped: opts.stopped,
107464
+ startDate: opts.start,
107465
+ endDate: opts.end,
107466
+ page: opts.page,
107467
+ pageSize: opts.pageSize,
107468
+ jsonOut: opts.jsonOut,
107469
+ verbose: opts.verbose
107470
+ });
107471
+ });
107472
+ pushCmd.command("create").description(
107473
+ "\u65B0\u5EFA\u63A8\u9001\u914D\u7F6E\uFF08POST /command/report-push/settings/{\u5A92\u4F53}\uFF0C\u4E0E\u7F51\u9875\u300C\u6DFB\u52A0\u63A8\u9001\u300D\u4E00\u81F4\uFF09"
107474
+ ).requiredOption("-m, --media <type>", "\u5A92\u4F53\uFF1AGoogle | TikTok").requiredOption("--name <name>", "\u63A8\u9001\u540D\u79F0\uFF08\u5BF9\u5E94 body.Name\uFF09").requiredOption(
107475
+ "--media-accounts <ids>",
107476
+ "\u5E7F\u544A\u8D26\u6237 entityId\uFF0C\u9017\u53F7\u5206\u9694\uFF08list-accounts --json-out \u2192 ma.entityId\uFF09",
107477
+ splitCsv
107478
+ ).requiredOption("--emails <list>", "\u6536\u4EF6\u90AE\u7BB1\uFF0C\u9017\u53F7\u5206\u9694", splitCsv).requiredOption("--types <list>", "\u62A5\u544A\u7C7B\u578B\uFF0C\u9017\u53F7\u5206\u9694\uFF1ADaily | Weekly | Monthly", splitCsv).option("--title <text>", "\u90AE\u4EF6\u6807\u9898\uFF08ReceiveTitle\uFF0C\u53EF\u7A7A\uFF09", "").option("--ai-suggest", "\u4EC5 Google\uFF1AAiSuggest=true", false).option("--token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107479
+ await runReportPushCreate({
107480
+ token: opts.token,
107481
+ media: opts.media,
107482
+ name: opts.name,
107483
+ mediaAccounts: opts.mediaAccounts,
107484
+ receiveEmails: opts.emails,
107485
+ reportTypes: opts.types,
107486
+ receiveTitle: opts.title ?? "",
107487
+ aiSuggest: opts.aiSuggest,
107488
+ verbose: opts.verbose
107489
+ });
107490
+ });
107491
+ pushCmd.command("update").description(
107492
+ "\u7F16\u8F91\u63A8\u9001\u914D\u7F6E\uFF08PUT /command/report-push/settings/{\u5A92\u4F53}/{id}\uFF1Bid \u4E0E\u7F51\u9875\u7F16\u8F91\u7528 row.id \u4E00\u81F4\uFF0C\u901A\u5E38\u540C entityId\uFF09"
107493
+ ).requiredOption("-m, --media <type>", "\u5A92\u4F53\uFF1AGoogle | TikTok").requiredOption("--id <id>", "\u63A8\u9001\u914D\u7F6E id\uFF08push list --json-out \u2192 items[].id \u6216 entityId\uFF09").requiredOption("--name <name>", "\u63A8\u9001\u540D\u79F0").requiredOption("--media-accounts <ids>", "\u5E7F\u544A\u8D26\u6237 entityId\uFF0C\u9017\u53F7\u5206\u9694", splitCsv).requiredOption("--emails <list>", "\u6536\u4EF6\u90AE\u7BB1\uFF0C\u9017\u53F7\u5206\u9694", splitCsv).requiredOption("--types <list>", "\u62A5\u544A\u7C7B\u578B\uFF0C\u9017\u53F7\u5206\u9694\uFF1ADaily | Weekly | Monthly", splitCsv).option("--title <text>", "\u90AE\u4EF6\u6807\u9898", "").option("--ai-suggest", "\u4EC5 Google\uFF1AAiSuggest=true", false).option("--token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107494
+ await runReportPushUpdate({
107495
+ token: opts.token,
107496
+ media: opts.media,
107497
+ id: opts.id,
107498
+ name: opts.name,
107499
+ mediaAccounts: opts.mediaAccounts,
107500
+ receiveEmails: opts.emails,
107501
+ reportTypes: opts.types,
107502
+ receiveTitle: opts.title ?? "",
107503
+ aiSuggest: opts.aiSuggest,
107504
+ verbose: opts.verbose
107505
+ });
107506
+ });
107507
+ pushCmd.command("history").description(
107508
+ "\u63A8\u9001\u8BB0\u5F55\u5217\u8868\uFF08GET /query/report-push/settings/{\u5A92\u4F53}/{settingId}/push-history\uFF0C\u5BF9\u5E94\u7F51\u9875 /dataReport/pushRecord\uFF09"
107509
+ ).requiredOption("-m, --media <type>", "\u5A92\u4F53\uFF1AGoogle | TikTok").requiredOption(
107510
+ "--setting-id <id>",
107511
+ "\u63A8\u9001\u89C4\u5219 id\uFF08\u4E0E push list \u7684 entityId \u4E00\u81F4\uFF1B\u7F51\u9875\u8DEF\u7531 entityId\uFF09"
107512
+ ).option("-p, --page <n>", "\u9875\u7801", parseInt).option("--page-size <n>", "\u6BCF\u9875\u6570\u91CF", parseInt).option("--start <date>", "\u7B5B\u9009\u5F00\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u7B5B\u9009\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("--receive-email <email>", "\u7B5B\u9009\u6536\u4EF6\u90AE\u7BB1").option("--report-type <type>", "\u7B5B\u9009\u62A5\u544A\u7C7B\u578B").option("--media-account-id <entityId>", "\u7B5B\u9009\u8D26\u6237\uFF08\u4F20\u8D26\u6237 entityId\uFF09").option("--token <token>", "Auth Token").option(
107513
+ "--json-out <path>",
107514
+ "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
107515
+ void 0
107516
+ ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107517
+ await runReportPushHistory({
107518
+ token: opts.token,
107519
+ media: opts.media,
107520
+ settingId: opts.settingId,
107521
+ page: opts.page,
107522
+ pageSize: opts.pageSize,
107523
+ startDate: opts.start,
107524
+ endDate: opts.end,
107525
+ receiveEmail: opts.receiveEmail,
107526
+ reportType: opts.reportType,
107527
+ mediaAccountId: opts.mediaAccountId,
107528
+ jsonOut: opts.jsonOut,
107529
+ verbose: opts.verbose
107530
+ });
107531
+ });
107532
+ pushCmd.command("receive-emails").description(
107533
+ "\u67E5\u8BE2\u63A8\u9001\u66FE\u7528\u8FC7\u7684\u6536\u4EF6\u90AE\u7BB1\u5217\u8868\uFF08GET /query/report-push/settings/{\u5A92\u4F53}/all/receive-emails\uFF0C\u4E0E\u7F51\u9875\u4E0B\u62C9\u4E00\u81F4\uFF09"
107534
+ ).requiredOption("-m, --media <type>", "\u5A92\u4F53\uFF1AGoogle | TikTok").option("--token <token>", "Auth Token").option(
107535
+ "--json-out <path>",
107536
+ "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
107537
+ void 0
107538
+ ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107539
+ await runReportPushReceiveEmails({
107540
+ token: opts.token,
107541
+ media: opts.media,
107542
+ jsonOut: opts.jsonOut,
107543
+ verbose: opts.verbose
107544
+ });
107545
+ });
107546
+ pushCmd.command("start").description("\u542F\u52A8\u63A8\u9001\u4EFB\u52A1").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").requiredOption("--id <id>", "\u63A8\u9001\u4EFB\u52A1 entityId").option("--token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107547
+ await runReportPushStart({
107548
+ token: opts.token,
107549
+ media: opts.media,
107550
+ id: opts.id,
107551
+ verbose: opts.verbose
107552
+ });
107553
+ });
107554
+ pushCmd.command("stop").description("\u505C\u6B62\u63A8\u9001\u4EFB\u52A1").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").requiredOption("--id <id>", "\u63A8\u9001\u4EFB\u52A1 entityId").option("--token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107555
+ await runReportPushStop({
107556
+ token: opts.token,
107557
+ media: opts.media,
107558
+ id: opts.id,
107559
+ verbose: opts.verbose
107560
+ });
107561
+ });
107562
+ pushCmd.command("delete").description("\u5220\u9664\u63A8\u9001\u4EFB\u52A1").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").requiredOption("--id <id>", "\u63A8\u9001\u4EFB\u52A1 entityId").option("--token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107563
+ await runReportPushDelete({
107564
+ token: opts.token,
107565
+ media: opts.media,
107566
+ id: opts.id,
107567
+ verbose: opts.verbose
107568
+ });
107440
107569
  });
107441
- console.log(JSON.stringify(summary));
107442
- }
107443
- async function fetchTikTokJson(config, url, verbose) {
107444
- return apiFetch2(url, config, {}, verbose);
107445
107570
  }
107446
107571
 
107447
- // src/commands/report-tiktok-analysis/run.ts
107572
+ // src/commands/transfer.ts
107448
107573
  init_auth();
107449
- init_version();
107450
- async function runReportTikTokOverview(opts) {
107451
- const id = assertTikTokAccountId(opts.account);
107452
- const { startDate, endDate } = resolveDateRange(opts.start, opts.end);
107453
- const config = loadConfig(opts.token);
107454
- const params = new URLSearchParams({ startDate, endDate });
107455
- const url = reportingUrl(config, id, "OverviewSectionData", params.toString());
107456
- try {
107457
- const data = await fetchTikTokJson(config, url, !!opts.verbose);
107458
- if (opts.jsonOut) {
107459
- await emitTiktokSnapshot(opts, "tiktok-overview", id, startDate, endDate, data);
107460
- return;
107461
- }
107462
- console.log(`
107463
- \u2705 TikTok \u8D26\u6237\u603B\u89C8 ${id} ${startDate} ~ ${endDate}`);
107464
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
107465
- } catch (err) {
107466
- console.error(`
107467
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
107468
- `);
107469
- process.exit(1);
107470
- }
107574
+ init_cli_json_snapshot();
107575
+ init_cli_table();
107576
+ var VALID_MEDIA_TYPES6 = ["Google", "TikTok", "MetaAd", "BingV2"];
107577
+ function mediaToPath(media) {
107578
+ return media.toLowerCase();
107471
107579
  }
107472
- async function runReportTikTokCampaigns(opts) {
107473
- const id = assertTikTokAccountId(opts.account);
107474
- const { startDate, endDate } = resolveDateRange(opts.start, opts.end);
107475
- const take = resolveTake(opts.take);
107476
- const config = loadConfig(opts.token);
107477
- const params = new URLSearchParams({
107478
- startDate,
107479
- endDate,
107480
- take: String(take)
107481
- });
107482
- const url = reportingUrl(config, id, "CampaignSectionData", params.toString());
107483
- try {
107484
- const data = await fetchTikTokJson(config, url, !!opts.verbose);
107485
- if (opts.jsonOut) {
107486
- await emitTiktokSnapshot(opts, "tiktok-campaigns", id, startDate, endDate, data);
107487
- return;
107488
- }
107489
- console.log(`
107490
- \u2705 TikTok \u5E7F\u544A\u7CFB\u5217\u5206\u6BB5 ${id} take=${take}`);
107491
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
107492
- } catch (err) {
107493
- console.error(`
107494
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
107495
- `);
107580
+ async function runTransferCreate(opts) {
107581
+ if (!VALID_MEDIA_TYPES6.includes(opts.media)) {
107582
+ console.error(
107583
+ `
107584
+ \u274C \u4E0D\u652F\u6301\u7684\u5A92\u4F53\u7C7B\u578B\uFF1A${opts.media}
107585
+ \u53EF\u9009\uFF1A${VALID_MEDIA_TYPES6.join(" | ")}
107586
+ `
107587
+ );
107496
107588
  process.exit(1);
107497
107589
  }
107498
- }
107499
- async function runReportTikTokAdGroups(opts) {
107500
- const id = assertTikTokAccountId(opts.account);
107501
- const { startDate, endDate } = resolveDateRange(opts.start, opts.end);
107502
- const take = resolveTake(opts.take);
107503
107590
  const config = loadConfig(opts.token);
107504
- const params = new URLSearchParams({
107505
- startDate,
107506
- endDate,
107507
- take: String(take)
107508
- });
107509
- const url = reportingUrl(config, id, "AdGroupReport", params.toString());
107510
- try {
107511
- const data = await fetchTikTokJson(config, url, !!opts.verbose);
107512
- if (opts.jsonOut) {
107513
- await emitTiktokSnapshot(opts, "tiktok-ad-groups", id, startDate, endDate, data);
107514
- return;
107591
+ const url = `${config.apiBaseUrl}/command/accounttransfer/${mediaToPath(opts.media)}/createaccounttransfers`;
107592
+ const body = [
107593
+ {
107594
+ exchangeRate: 1,
107595
+ accountTransferOutData: {
107596
+ mediaCustomerId: opts.outAccount,
107597
+ shortId: null,
107598
+ transferOutAmounts: opts.amount
107599
+ },
107600
+ accountTransferInData: {
107601
+ mediaCustomerId: opts.inAccount,
107602
+ shortId: null
107603
+ },
107604
+ customerName: opts.customerName ?? ""
107515
107605
  }
107516
- console.log(`
107517
- \u2705 TikTok \u5E7F\u544A\u7EC4\u62A5\u8868 ${id} take=${take}`);
107518
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
107519
- } catch (err) {
107520
- console.error(`
107521
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
107522
- `);
107523
- process.exit(1);
107524
- }
107525
- }
107526
- async function runReportTikTokAds(opts) {
107527
- const id = assertTikTokAccountId(opts.account);
107528
- const { startDate, endDate } = resolveDateRange(opts.start, opts.end);
107529
- const take = resolveTake(opts.take);
107530
- const config = loadConfig(opts.token);
107531
- const params = new URLSearchParams({
107532
- startDate,
107533
- endDate,
107534
- take: String(take)
107535
- });
107536
- const url = reportingUrl(config, id, "AdReport", params.toString());
107606
+ ];
107607
+ let result;
107537
107608
  try {
107538
- const data = await fetchTikTokJson(config, url, !!opts.verbose);
107539
- if (opts.jsonOut) {
107540
- await emitTiktokSnapshot(opts, "tiktok-ads", id, startDate, endDate, data);
107541
- return;
107542
- }
107543
- console.log(`
107544
- \u2705 TikTok \u5E7F\u544A\u62A5\u8868 ${id} take=${take}`);
107545
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
107609
+ result = await apiFetch2(
107610
+ url,
107611
+ config,
107612
+ { method: "POST", body: JSON.stringify(body) },
107613
+ opts.verbose
107614
+ );
107546
107615
  } catch (err) {
107547
107616
  console.error(`
107548
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
107617
+ \u274C \u8F6C\u8D26\u7533\u8BF7\u63D0\u4EA4\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
107549
107618
  `);
107550
107619
  process.exit(1);
107551
107620
  }
107552
- }
107553
- async function runReportTikTokAudience(opts) {
107554
- const id = assertTikTokAccountId(opts.account);
107555
- const { startDate, endDate } = resolveDateRange(opts.start, opts.end);
107556
- const take = resolveTake(opts.take);
107557
- const config = loadConfig(opts.token);
107558
- const params = new URLSearchParams({
107559
- startDate,
107560
- endDate,
107561
- take: String(take),
107562
- dimensions: opts.dimension
107563
- });
107564
- const url = reportingUrl(config, id, "AudienceReport", params.toString());
107565
- try {
107566
- const data = await fetchTikTokJson(config, url, !!opts.verbose);
107567
- if (opts.jsonOut) {
107568
- await emitTiktokSnapshot(
107569
- opts,
107570
- `tiktok-audience-${opts.dimension}`,
107571
- id,
107572
- startDate,
107573
- endDate,
107574
- data
107575
- );
107576
- return;
107577
- }
107578
- console.log(`
107579
- \u2705 TikTok \u53D7\u4F17\u62A5\u8868 ${id} dimensions=${opts.dimension} take=${take}`);
107580
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
107581
- } catch (err) {
107582
- console.error(`
107583
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
107584
- `);
107585
- process.exit(1);
107621
+ if (await emitCliJsonOrSnapshot(opts, {
107622
+ section: `transfer-create-${mediaToPath(opts.media)}`,
107623
+ commandLabel: "transfer create",
107624
+ commandHint: opts.media,
107625
+ payload: result,
107626
+ idSuffix: opts.media
107627
+ })) {
107628
+ return;
107586
107629
  }
107630
+ console.log(
107631
+ `
107632
+ \u2705 \u8F6C\u8D26\u7533\u8BF7\u5DF2\u63D0\u4EA4\uFF1A${opts.outAccount} \u2192 ${opts.inAccount}\uFF0C\u91D1\u989D ${opts.amount}\u3002
107633
+ `
107634
+ );
107635
+ console.log(" \u4F7F\u7528 transfer list \u67E5\u770B\u8F6C\u8D26\u8BB0\u5F55\u53CA\u5230\u8D26\u72B6\u6001\u3002\n");
107587
107636
  }
107588
- async function runReportTikTokAudienceMerged(opts) {
107589
- const id = assertTikTokAccountId(opts.account);
107590
- const { startDate, endDate } = resolveDateRange(opts.start, opts.end);
107591
- const take = resolveTake(opts.take);
107592
- const config = loadConfig(opts.token);
107593
- const dims = ["gender", "age", "interest_category"];
107594
- try {
107595
- const parts = {};
107596
- for (const dimension of dims) {
107597
- const params = new URLSearchParams({
107598
- startDate,
107599
- endDate,
107600
- take: String(take),
107601
- dimensions: dimension
107602
- });
107603
- const url = reportingUrl(config, id, "AudienceReport", params.toString());
107604
- parts[dimension] = await fetchTikTokJson(config, url, !!opts.verbose);
107605
- }
107606
- const merged = { mergedDimensions: dims, startDate, endDate, account: id, data: parts };
107607
- if (opts.jsonOut) {
107608
- await emitTiktokSnapshot(opts, "tiktok-audience-merged", id, startDate, endDate, merged);
107609
- return;
107610
- }
107611
- console.log(JSON.stringify(merged, null, 2));
107612
- } catch (err) {
107613
- console.error(`
107614
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
107615
- `);
107637
+ async function runTransferList(opts) {
107638
+ if (!VALID_MEDIA_TYPES6.includes(opts.media)) {
107639
+ console.error(
107640
+ `
107641
+ \u274C \u4E0D\u652F\u6301\u7684\u5A92\u4F53\u7C7B\u578B\uFF1A${opts.media}
107642
+ \u53EF\u9009\uFF1A${VALID_MEDIA_TYPES6.join(" | ")}
107643
+ `
107644
+ );
107616
107645
  process.exit(1);
107617
107646
  }
107618
- }
107619
- async function runReportTikTokVideos(opts) {
107620
- const id = assertTikTokAccountId(opts.account);
107621
- const { startDate, endDate } = resolveDateRange(opts.start, opts.end);
107622
- const take = resolveTake(opts.take);
107623
107647
  const config = loadConfig(opts.token);
107624
- const params = new URLSearchParams({
107625
- startDate,
107626
- endDate,
107627
- take: String(take)
107628
- });
107629
- const url = reportingUrl(config, id, "VideoReport", params.toString());
107648
+ const params = new URLSearchParams();
107649
+ params.set("pageNumber", String(opts.page ?? 1));
107650
+ params.set("pageSize", String(opts.pageSize ?? 20));
107651
+ if (opts.number) params.set("transferNumber", opts.number);
107652
+ if (opts.status) params.set("transferStatus", opts.status);
107653
+ if (opts.startDate) params.set("startDate", opts.startDate);
107654
+ if (opts.endDate) params.set("endDate", opts.endDate);
107655
+ const url = `${config.apiBaseUrl}/query/accounttransfer/${opts.media}/search?${params}`;
107656
+ let data;
107630
107657
  try {
107631
- const data = await fetchTikTokJson(config, url, !!opts.verbose);
107632
- if (opts.jsonOut) {
107633
- await emitTiktokSnapshot(opts, "tiktok-videos", id, startDate, endDate, data);
107634
- return;
107635
- }
107636
- console.log(`
107637
- \u2705 TikTok \u89C6\u9891\u7D20\u6750\u62A5\u8868 ${id} take=${take}`);
107638
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
107658
+ data = await apiFetch2(url, config, {}, opts.verbose);
107639
107659
  } catch (err) {
107640
107660
  console.error(`
107641
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
107661
+ \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
107642
107662
  `);
107643
107663
  process.exit(1);
107644
107664
  }
107645
- }
107646
- async function runReportTikTokAreacode(opts) {
107647
- const config = loadConfig(opts.token);
107648
- const base = config.mainApiUrl?.trim();
107649
- if (!base) {
107650
- console.error("\n\u274C \u65E0\u6CD5\u63A8\u5BFC mainApiUrl\uFF0C\u8BF7\u68C0\u67E5 tsoApiBaseUrl \u914D\u7F6E\u3002\n");
107651
- process.exit(1);
107665
+ const items = data.list ?? data.results ?? [];
107666
+ const total = data.total ?? data.totalResultCount ?? 0;
107667
+ const page = opts.page ?? 1;
107668
+ const pageSize = opts.pageSize ?? 20;
107669
+ const listPayload = wrapListJson({ page, pageSize, total, items });
107670
+ if (await emitCliJsonOrSnapshot(opts, {
107671
+ section: `transfer-list-${mediaToPath(opts.media)}`,
107672
+ commandLabel: "transfer list",
107673
+ commandHint: opts.media,
107674
+ payload: listPayload,
107675
+ idSuffix: opts.media
107676
+ })) {
107677
+ return;
107652
107678
  }
107653
- const url = `${base}/query/media-account/tiktok/TikTokAreacode/Read`;
107654
- try {
107655
- const data = await fetchTikTokJson(config, url, !!opts.verbose);
107656
- if (opts.jsonOut) {
107657
- const summary = await writeReportAnalysisSnapshot({
107658
- snapshotDir: opts.jsonOut,
107659
- section: "tiktok-areacode",
107660
- dateRange: null,
107661
- payload: data,
107662
- cliVersion: getCurrentVersion2()
107663
- });
107664
- console.log(JSON.stringify(summary));
107665
- return;
107666
- }
107667
- console.log("\n\u2705 TikTok \u5730\u533A\u4EE3\u7801\u8868\uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
107668
- } catch (err) {
107669
- console.error(`
107670
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
107671
- `);
107672
- process.exit(1);
107679
+ console.log(
107680
+ `
107681
+ \u8F6C\u8D26\u8BB0\u5F55\uFF08\u7B2C ${page}/${Math.max(1, Math.ceil(total / pageSize))} \u9875\uFF0C\u672C\u9875 ${items.length} \u6761\uFF0C\u5171 ${total} \u6761\uFF09
107682
+ `
107683
+ );
107684
+ if (items.length === 0) {
107685
+ console.log(" \u6682\u65E0\u6570\u636E\u3002\n");
107686
+ return;
107673
107687
  }
107674
- }
107675
- async function runReportTikTokInterestList(opts) {
107676
- const id = assertTikTokAccountId(opts.account);
107677
- const config = loadConfig(opts.token);
107678
- const tt = config.tiktokApiUrl?.trim();
107679
- if (!tt) {
107680
- console.error("\n\u274C \u672A\u914D\u7F6E tiktokApiUrl\u3002\n");
107681
- process.exit(1);
107682
- }
107683
- const params = new URLSearchParams({ mediaCustomerId: id });
107684
- const url = `${tt}/tiktokadgroupmanagement/adgroup/tiktok/GetInterestList?${params}`;
107685
- try {
107686
- const data = await fetchTikTokJson(config, url, !!opts.verbose);
107687
- if (opts.jsonOut) {
107688
- const summary = await writeReportAnalysisSnapshot({
107689
- snapshotDir: opts.jsonOut,
107690
- section: "tiktok-interest-list",
107691
- accountId: id,
107692
- dateRange: null,
107693
- payload: data,
107694
- cliVersion: getCurrentVersion2()
107695
- });
107696
- console.log(JSON.stringify(summary));
107697
- return;
107698
- }
107699
- console.log(`
107700
- \u2705 TikTok \u5174\u8DA3\u7C7B\u76EE ${id}\uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09
107701
- `);
107702
- } catch (err) {
107703
- console.error(`
107704
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
107705
- `);
107706
- process.exit(1);
107707
- }
107708
- }
107709
-
107710
- // src/commands/report-tiktok-analysis/_register.ts
107711
- function addDateAndFetchOptions(c) {
107712
- return c.requiredOption("-a, --account <id>", "TikTok \u5E7F\u544A\u4E3B mediaCustomerId\uFF08\u6570\u5B57\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\u7565\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("--take <n>", "\u6D88\u8017/\u6761\u6570\u4E0A\u9650\uFF08\u9ED8\u8BA4 100\uFF0C\u4E0E\u524D\u7AEF take \u4E00\u81F4\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Auth Token").option(
107713
- "--json-out <dir>",
107714
- "\u5C06 JSON \u5199\u5165\u76EE\u5F55\uFF08\u6587\u4EF6\u540D\u5E26 accountId \u540E\u7F00\uFF09\u5E76\u66F4\u65B0 report-manifest-<accountId>.json\uFF1Bstdout \u4EC5\u4E00\u884C\u6458\u8981 JSON"
107715
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false);
107716
- }
107717
- function registerReportTiktokCommands(reportCmd) {
107718
- const desc = "TikTok \u8D26\u6237\u5206\u6790\uFF08TSO reporting/media-account/TikTok/...\uFF0C\u4E0E\u5468\u671F\u62A5\u544A\u6570\u636E\u5757\u4E00\u81F4\uFF09";
107719
- reportCmd.command("tiktok-overview").description(`${desc} \u2014 \u603B\u89C8 OverviewSectionData`).requiredOption("-a, --account <id>", "TikTok mediaCustomerId").option("--start <date>", "\u5F00\u59CB\u65E5\u671F").option("--end <date>", "\u7ED3\u675F\u65E5\u671F").option("-t, --token <token>", "Auth Token").option(
107720
- "--json-out <dir>",
107721
- "\u5C06 JSON \u5199\u5165\u76EE\u5F55\uFF08\u6587\u4EF6\u540D\u5E26 accountId \u540E\u7F00\uFF09\u5E76\u66F4\u65B0 report-manifest-<accountId>.json\uFF1Bstdout \u4EC5\u4E00\u884C\u6458\u8981 JSON"
107722
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107723
- await runReportTikTokOverview(opts);
107724
- });
107725
- const bindTake = (name2, segmentLabel, runner) => {
107726
- const cmd = reportCmd.command(name2).description(`${desc} \u2014 ${segmentLabel}`);
107727
- addDateAndFetchOptions(cmd);
107728
- cmd.action(async (opts) => {
107729
- await runner(opts);
107730
- });
107731
- };
107732
- bindTake("tiktok-campaigns", "CampaignSectionData", runReportTikTokCampaigns);
107733
- bindTake("tiktok-ad-groups", "AdGroupReport", runReportTikTokAdGroups);
107734
- bindTake("tiktok-ads", "AdReport", runReportTikTokAds);
107735
- bindTake("tiktok-videos", "VideoReport", runReportTikTokVideos);
107736
- reportCmd.command("tiktok-audience").description(`${desc} \u2014 AudienceReport\uFF08\u5355\u7EF4\u5EA6 dimensions\uFF09`).requiredOption("-a, --account <id>", "TikTok mediaCustomerId").requiredOption("-d, --dimension <dim>", `\u7EF4\u5EA6\uFF1A${AUDIENCE_DIMENSIONS.join(" | ")}`).option("--start <date>", "\u5F00\u59CB\u65E5\u671F").option("--end <date>", "\u7ED3\u675F\u65E5\u671F").option("--take <n>", "\u6761\u6570\u4E0A\u9650\uFF08\u9ED8\u8BA4 100\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Auth Token").option(
107737
- "--json-out <dir>",
107738
- "\u5C06 JSON \u5199\u5165\u76EE\u5F55\uFF08\u6587\u4EF6\u540D\u5E26 accountId \u540E\u7F00\uFF09\u5E76\u66F4\u65B0 report-manifest-<accountId>.json\uFF1Bstdout \u4EC5\u4E00\u884C\u6458\u8981 JSON"
107739
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107740
- const d = opts.dimension;
107741
- if (!AUDIENCE_DIMENSIONS.includes(d)) {
107742
- console.error(`
107743
- \u274C --dimension \u975E\u6CD5\uFF0C\u53EF\u9009\uFF1A${AUDIENCE_DIMENSIONS.join(" | ")}
107744
- `);
107745
- process.exit(1);
107746
- }
107747
- await runReportTikTokAudience({ ...opts, dimension: d });
107748
- });
107749
- reportCmd.command("tiktok-audience-merged").description(
107750
- `${desc} \u2014 \u5408\u5E76 gender + age + interest_category \u4E09\u6B21 AudienceReport\uFF08\u9ED8\u8BA4 stdout \u4E3A JSON\uFF09`
107751
- ).requiredOption("-a, --account <id>", "TikTok mediaCustomerId").option("--start <date>", "\u5F00\u59CB\u65E5\u671F").option("--end <date>", "\u7ED3\u675F\u65E5\u671F").option("--take <n>", "\u6761\u6570\u4E0A\u9650\uFF08\u9ED8\u8BA4 100\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Auth Token").option(
107752
- "--json-out <dir>",
107753
- "\u5C06\u5408\u5E76 JSON \u5199\u5165\u76EE\u5F55\uFF08\u6587\u4EF6\u540D\u5E26 accountId \u540E\u7F00\uFF09\u5E76\u66F4\u65B0 report-manifest-<accountId>.json\uFF1Bstdout \u4EC5\u4E00\u884C\u6458\u8981 JSON"
107754
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
107755
- await runReportTikTokAudienceMerged(opts);
107756
- });
107757
- reportCmd.command("tiktok-areacode").description("TikTok \u5730\u533A\u4EE3\u7801\u679A\u4E3E\uFF08\u4E3B\u5E73\u53F0 query/media-account/tiktok/TikTokAreacode/Read\uFF09").option("-t, --token <token>", "Auth Token").option(
107758
- "--json-out <dir>",
107759
- "\u5C06 JSON \u5199\u5165\u76EE\u5F55\uFF08\u6587\u4EF6\u540D\u5E26 accountId \u540E\u7F00\uFF09\u5E76\u66F4\u65B0 report-manifest-<accountId>.json\uFF1Bstdout \u4EC5\u4E00\u884C\u6458\u8981 JSON"
107760
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
107761
- async (opts) => {
107762
- await runReportTikTokAreacode(opts);
107763
- }
107764
- );
107765
- reportCmd.command("tiktok-interest-list").description(
107766
- "TikTok \u5174\u8DA3\u7C7B\u76EE\uFF08tiktokApiUrl GetInterestList\uFF0C\u89E3\u6790 country_code/interest \u62A5\u8868\u65F6\u5E38\u7528\uFF09"
107767
- ).requiredOption("-a, --account <id>", "TikTok mediaCustomerId").option("-t, --token <token>", "Auth Token").option(
107768
- "--json-out <dir>",
107769
- "\u5C06 JSON \u5199\u5165\u76EE\u5F55\uFF08\u6587\u4EF6\u540D\u5E26 accountId \u540E\u7F00\uFF09\u5E76\u66F4\u65B0 report-manifest-<accountId>.json\uFF1Bstdout \u4EC5\u4E00\u884C\u6458\u8981 JSON"
107770
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
107771
- async (opts) => {
107772
- await runReportTikTokInterestList(opts);
107773
- }
107774
- );
107775
- }
107776
-
107777
- // src/commands/report-bing-analysis/_shared.ts
107778
- init_auth();
107779
- init_version();
107780
- var BING_KEYWORD_LIMIT_DEFAULT = 100;
107781
- function stripLocalDate(d) {
107782
- return new Date(d.getFullYear(), d.getMonth(), d.getDate());
107783
- }
107784
- function parseLocalYmd(ymd) {
107785
- const parts = ymd.trim().split("-").map((x) => parseInt(x, 10));
107786
- if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) {
107787
- return /* @__PURE__ */ new Date(NaN);
107788
- }
107789
- const [y, m, day] = parts;
107790
- return new Date(y, m - 1, day);
107791
- }
107792
- function formatLocalYmd(d) {
107793
- const y = d.getFullYear();
107794
- const m = String(d.getMonth() + 1).padStart(2, "0");
107795
- const day = String(d.getDate()).padStart(2, "0");
107796
- return `${y}-${m}-${day}`;
107797
- }
107798
- function bingRangeTouchesTodayOrYesterday(startDate, endDate) {
107799
- const s = parseLocalYmd(startDate);
107800
- const e = parseLocalYmd(endDate);
107801
- if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime()) || s > e) {
107802
- return true;
107803
- }
107804
- const today = stripLocalDate(/* @__PURE__ */ new Date());
107805
- const yesterday = new Date(today);
107806
- yesterday.setDate(yesterday.getDate() - 1);
107807
- for (let cur = new Date(s); cur <= e; cur.setDate(cur.getDate() + 1)) {
107808
- const c = stripLocalDate(cur);
107809
- if (c.getTime() === today.getTime() || c.getTime() === yesterday.getTime()) {
107810
- return true;
107811
- }
107812
- }
107813
- return false;
107814
- }
107815
- function resolveBingDateRange(start, end) {
107816
- if (start && end) {
107817
- const s = parseLocalYmd(start);
107818
- const e = parseLocalYmd(end);
107819
- if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime())) {
107820
- console.error("\n\u274C --start / --end \u987B\u4E3A\u6709\u6548\u65E5\u671F YYYY-MM-DD\u3002\n");
107821
- process.exit(1);
107822
- }
107823
- if (s > e) {
107824
- console.error("\n\u274C \u5F00\u59CB\u65E5\u671F\u4E0D\u80FD\u665A\u4E8E\u7ED3\u675F\u65E5\u671F\u3002\n");
107825
- process.exit(1);
107826
- }
107827
- if (bingRangeTouchesTodayOrYesterday(start, end)) {
107828
- console.error(
107829
- "\n\u274C Bing \u62A5\u8868\u65F6\u95F4\u8303\u56F4\u4E0D\u80FD\u5305\u542B\u300C\u4ECA\u5929\u300D\u6216\u300C\u6628\u5929\u300D\uFF08\u63A5\u53E3\u9650\u5236\uFF09\u3002\u8BF7\u6539\u7528\u66F4\u65E9\u7684\u7ED3\u675F\u65E5\u671F\uFF0C\u4F8B\u5982\u524D\u5929\u53CA\u4EE5\u524D\u3002\n"
107830
- );
107831
- process.exit(1);
107832
- }
107833
- return { startDate: start, endDate: end };
107834
- }
107835
- if (!start && !end) {
107836
- const endD = stripLocalDate(/* @__PURE__ */ new Date());
107837
- endD.setDate(endD.getDate() - 2);
107838
- const startD = new Date(endD);
107839
- startD.setDate(startD.getDate() - 6);
107840
- return { startDate: formatLocalYmd(startD), endDate: formatLocalYmd(endD) };
107841
- }
107842
- console.error(
107843
- "\n\u274C --start \u4E0E --end \u987B\u540C\u65F6\u4F20\u5165\uFF0C\u6216\u540C\u65F6\u7701\u7565\u4EE5\u4F7F\u7528\u9ED8\u8BA4\u533A\u95F4\uFF08\u622A\u81F3\u524D\u5929\u5171 7 \u5929\uFF09\u3002\n"
107844
- );
107845
- process.exit(1);
107846
- }
107847
- function assertBingAccountId(id) {
107848
- const t = id.trim();
107849
- if (!t) {
107850
- console.error("\n\u274C --account \u4E0D\u80FD\u4E3A\u7A7A\u3002\n");
107851
- process.exit(1);
107852
- }
107853
- if (!/^\d+$/.test(t)) {
107854
- console.error("\n\u274C --account \u987B\u4E3A\u6570\u5B57 mediaCustomerId\uFF08\u4E0E list-accounts -m BingV2 \u4E00\u81F4\uFF09\u3002\n");
107855
- process.exit(1);
107856
- }
107857
- return t;
107858
- }
107859
- function reportingUrl2(config, accountId, segment, query) {
107860
- const q = query ? query.startsWith("?") ? query : `?${query}` : "";
107861
- return `${config.apiBaseUrl}/reporting/media-account/BingV2/${accountId}/${segment}${q}`;
107862
- }
107863
- function resolveLimit(raw) {
107864
- const n = typeof raw === "number" ? raw : parseInt(String(raw), 10);
107865
- return Number.isFinite(n) && n > 0 ? n : BING_KEYWORD_LIMIT_DEFAULT;
107866
- }
107867
- async function emitBingSnapshot(opts, section, accountId, startDate, endDate, data) {
107868
- const summary = await writeReportAnalysisSnapshot({
107869
- snapshotDir: opts.jsonOut,
107870
- section,
107871
- accountId,
107872
- dateRange: { start: startDate, end: endDate },
107873
- payload: data,
107874
- cliVersion: getCurrentVersion2()
107875
- });
107876
- console.log(JSON.stringify(summary));
107877
- }
107878
- async function fetchBingJson(config, url, verbose) {
107879
- return apiFetch2(url, config, {}, verbose);
107880
- }
107881
-
107882
- // src/commands/report-bing-analysis/run.ts
107883
- init_auth();
107884
- init_balance();
107885
-
107886
- // src/commands/report-bing-analysis/normalize-bing-overview.ts
107887
- function num(v) {
107888
- return typeof v === "number" && Number.isFinite(v) ? v : void 0;
107889
- }
107890
- function roundMoney(v) {
107891
- return Math.round(v * 100) / 100;
107892
- }
107893
- function countInclusiveCalendarDays(startDate, endDate) {
107894
- const parts = (ymd) => ymd.trim().split("-").map((x) => parseInt(x, 10));
107895
- const sp = parts(startDate);
107896
- const ep = parts(endDate);
107897
- if (sp.length !== 3 || ep.length !== 3 || sp.some((n) => !Number.isFinite(n)) || ep.some((n) => !Number.isFinite(n))) {
107898
- return 0;
107899
- }
107900
- const s = new Date(sp[0], sp[1] - 1, sp[2]);
107901
- const e = new Date(ep[0], ep[1] - 1, ep[2]);
107902
- if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime()) || s > e) return 0;
107903
- const msPerDay = 24 * 60 * 60 * 1e3;
107904
- return Math.floor((e.getTime() - s.getTime()) / msPerDay) + 1;
107905
- }
107906
- function periodSpend(period) {
107907
- if (!period || typeof period !== "object") return void 0;
107908
- return num(period.spend);
107909
- }
107910
- function enrichBingOverviewDerivedMetrics(record, startDate, endDate) {
107911
- const out = { ...record };
107912
- const currentSpend = periodSpend(out.currentPeriod);
107913
- let totalCost = num(out.totalCost);
107914
- if (totalCost == null || totalCost <= 0) {
107915
- if (currentSpend != null && currentSpend > 0) {
107916
- out.totalCost = currentSpend;
107917
- totalCost = currentSpend;
107918
- }
107919
- }
107920
- let activeDays = num(out.activeDays);
107921
- if (activeDays == null || activeDays <= 0) {
107922
- const calendarDays = countInclusiveCalendarDays(startDate, endDate);
107923
- if (calendarDays > 0) {
107924
- out.activeDays = calendarDays;
107925
- activeDays = calendarDays;
107926
- }
107927
- }
107928
- const averageDailyCost = num(out.averageDailyCost);
107929
- if ((averageDailyCost == null || averageDailyCost <= 0) && totalCost != null && totalCost > 0 && activeDays != null && activeDays > 0) {
107930
- out.averageDailyCost = roundMoney(totalCost / activeDays);
107931
- }
107932
- return out;
107933
- }
107934
- function parseYmd(ymd) {
107935
- const parts = ymd.trim().split("-").map((x) => parseInt(x, 10));
107936
- if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) return null;
107937
- const d = new Date(parts[0], parts[1] - 1, parts[2]);
107938
- return Number.isNaN(d.getTime()) ? null : d;
107939
- }
107940
- function formatYmd(d) {
107941
- const y = d.getFullYear();
107942
- const m = String(d.getMonth() + 1).padStart(2, "0");
107943
- const day = String(d.getDate()).padStart(2, "0");
107944
- return `${y}-${m}-${day}`;
107945
- }
107946
- function calcBingPreviousPeriodRange(startDate, endDate) {
107947
- const diffDays = countInclusiveCalendarDays(startDate, endDate);
107948
- if (diffDays <= 0) return null;
107949
- const start = parseYmd(startDate);
107950
- if (!start) return null;
107951
- const prevEnd = new Date(start);
107952
- prevEnd.setDate(prevEnd.getDate() - 1);
107953
- const prevStart = new Date(prevEnd);
107954
- prevStart.setDate(prevStart.getDate() - (diffDays - 1));
107955
- return { startDate: formatYmd(prevStart), endDate: formatYmd(prevEnd) };
107956
- }
107957
- function isBingPeriodBlockEmpty(period) {
107958
- if (!period || typeof period !== "object") return true;
107959
- const p = period;
107960
- const spend = num(p.spend) ?? 0;
107961
- const impressions = num(p.impressions) ?? 0;
107962
- const clicks = num(p.clicks) ?? 0;
107963
- return spend <= 0 && impressions <= 0 && clicks <= 0;
107964
- }
107965
- function syncOverviewBalanceFields(record) {
107966
- const out = { ...record };
107967
- const budget = num(out.remainingAccountBudget);
107968
- if (budget != null && budget >= 0) {
107969
- const balanceField = num(out.balance);
107970
- if (balanceField == null || balanceField <= 0 && budget > 0) {
107971
- out.balance = budget;
107972
- }
107973
- }
107974
- return out;
107975
- }
107976
-
107977
- // src/commands/report-bing-analysis/run.ts
107978
- async function enrichBingOverviewPreviousPeriod(config, accountId, record, startDate, endDate, verbose) {
107979
- if (!isBingPeriodBlockEmpty(record.previousPeriod)) {
107980
- return record;
107981
- }
107982
- const prevRange = calcBingPreviousPeriodRange(startDate, endDate);
107983
- if (!prevRange) return record;
107984
- if (bingRangeTouchesTodayOrYesterday(prevRange.startDate, prevRange.endDate)) {
107985
- return record;
107986
- }
107987
- try {
107988
- const params = new URLSearchParams({
107989
- startDate: prevRange.startDate,
107990
- endDate: prevRange.endDate
107991
- });
107992
- const url = reportingUrl2(config, accountId, "OverviewSectionData", params.toString());
107993
- const prevRaw = await fetchBingJson(config, url, verbose);
107994
- if (!prevRaw || typeof prevRaw !== "object" || Array.isArray(prevRaw)) {
107995
- return record;
107996
- }
107997
- const prevRecord = prevRaw;
107998
- const prevPeriod = prevRecord.currentPeriod;
107999
- if (isBingPeriodBlockEmpty(prevPeriod)) {
108000
- return record;
108001
- }
108002
- return {
108003
- ...record,
108004
- previousPeriod: prevPeriod,
108005
- previousPeriodDateRange: prevRange,
108006
- _previousPeriodSource: "OverviewSectionData-second-fetch"
108007
- };
108008
- } catch {
108009
- return record;
108010
- }
108011
- }
108012
- async function runReportBingOverview(opts) {
108013
- const id = assertBingAccountId(opts.account);
108014
- const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
108015
- const config = loadConfig(opts.token);
108016
- const params = new URLSearchParams({ startDate, endDate });
108017
- const url = reportingUrl2(config, id, "OverviewSectionData", params.toString());
108018
- try {
108019
- const raw = await fetchBingJson(config, url, !!opts.verbose);
108020
- let data = raw;
108021
- if (raw && typeof raw === "object" && !Array.isArray(raw)) {
108022
- const withBalance = await enrichOverviewRecordWithRealtimeBalance(
108023
- "BingV2",
108024
- id,
108025
- config,
108026
- raw,
108027
- opts.verbose
108028
- );
108029
- const withDerived = enrichBingOverviewDerivedMetrics(withBalance, startDate, endDate);
108030
- const withPrevious = await enrichBingOverviewPreviousPeriod(
108031
- config,
108032
- id,
108033
- withDerived,
108034
- startDate,
108035
- endDate,
108036
- !!opts.verbose
108037
- );
108038
- data = syncOverviewBalanceFields(withPrevious);
108039
- }
108040
- if (opts.jsonOut) {
108041
- await emitBingSnapshot(opts, "bing-overview", id, startDate, endDate, data);
108042
- return;
108043
- }
108044
- console.log(`
108045
- \u2705 Bing \u8D26\u6237\u603B\u89C8 ${id} ${startDate} ~ ${endDate}`);
108046
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF1BBing \u533A\u95F4\u4E0D\u53EF\u542B\u4ECA\u5929/\u6628\u5929\uFF09\n");
108047
- } catch (err) {
108048
- console.error(`
108049
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
108050
- `);
108051
- process.exit(1);
108052
- }
108053
- }
108054
- async function runReportBingDevice(opts) {
108055
- const id = assertBingAccountId(opts.account);
108056
- const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
108057
- const config = loadConfig(opts.token);
108058
- const params = new URLSearchParams({ startDate, endDate });
108059
- const url = reportingUrl2(config, id, "DeviceSectionData", params.toString());
108060
- try {
108061
- const data = await fetchBingJson(config, url, !!opts.verbose);
108062
- if (opts.jsonOut) {
108063
- await emitBingSnapshot(opts, "bing-device", id, startDate, endDate, data);
108064
- return;
108065
- }
108066
- console.log(`
108067
- \u2705 Bing \u8BBE\u5907\u5206\u5E03 ${id} ${startDate} ~ ${endDate}`);
108068
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
108069
- } catch (err) {
108070
- console.error(`
108071
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
108072
- `);
108073
- process.exit(1);
108074
- }
108075
- }
108076
- async function runReportBingGeographic(opts) {
108077
- const id = assertBingAccountId(opts.account);
108078
- const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
108079
- const config = loadConfig(opts.token);
108080
- const params = new URLSearchParams({ startDate, endDate });
108081
- const url = reportingUrl2(config, id, "GeographicSectionData", params.toString());
108082
- try {
108083
- const data = await fetchBingJson(config, url, !!opts.verbose);
108084
- if (opts.jsonOut) {
108085
- await emitBingSnapshot(opts, "bing-geographic", id, startDate, endDate, data);
108086
- return;
108087
- }
108088
- console.log(`
108089
- \u2705 Bing \u5730\u57DF\u5206\u5E03 ${id} ${startDate} ~ ${endDate}`);
108090
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
108091
- } catch (err) {
108092
- console.error(`
108093
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
108094
- `);
108095
- process.exit(1);
108096
- }
108097
- }
108098
- async function runReportBingAgeAudience(opts) {
108099
- const id = assertBingAccountId(opts.account);
108100
- const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
108101
- const config = loadConfig(opts.token);
108102
- const params = new URLSearchParams({ startDate, endDate });
108103
- const url = reportingUrl2(config, id, "AgeAudienceData", params.toString());
108104
- try {
108105
- const data = await fetchBingJson(config, url, !!opts.verbose);
108106
- if (opts.jsonOut) {
108107
- await emitBingSnapshot(opts, "bing-age-audience", id, startDate, endDate, data);
108108
- return;
108109
- }
108110
- console.log(`
108111
- \u2705 Bing \u5E74\u9F84\u53D7\u4F17 ${id} ${startDate} ~ ${endDate}`);
108112
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
108113
- } catch (err) {
108114
- console.error(`
108115
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
108116
- `);
108117
- process.exit(1);
108118
- }
108119
- }
108120
- async function runReportBingGenderAudience(opts) {
108121
- const id = assertBingAccountId(opts.account);
108122
- const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
108123
- const config = loadConfig(opts.token);
108124
- const params = new URLSearchParams({ startDate, endDate });
108125
- const url = reportingUrl2(config, id, "GenderAudienceData", params.toString());
108126
- try {
108127
- const data = await fetchBingJson(config, url, !!opts.verbose);
108128
- if (opts.jsonOut) {
108129
- await emitBingSnapshot(opts, "bing-gender-audience", id, startDate, endDate, data);
108130
- return;
108131
- }
108132
- console.log(`
108133
- \u2705 Bing \u6027\u522B\u53D7\u4F17 ${id} ${startDate} ~ ${endDate}`);
108134
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
108135
- } catch (err) {
108136
- console.error(`
108137
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
108138
- `);
108139
- process.exit(1);
108140
- }
108141
- }
108142
- async function runReportBingAudienceMerged(opts) {
108143
- const id = assertBingAccountId(opts.account);
108144
- const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
108145
- const config = loadConfig(opts.token);
108146
- const params = new URLSearchParams({ startDate, endDate });
108147
- try {
108148
- const ageUrl = reportingUrl2(config, id, "AgeAudienceData", params.toString());
108149
- const genderUrl = reportingUrl2(config, id, "GenderAudienceData", params.toString());
108150
- const [ageData, genderData] = await Promise.all([
108151
- fetchBingJson(config, ageUrl, !!opts.verbose),
108152
- fetchBingJson(config, genderUrl, !!opts.verbose)
108153
- ]);
108154
- const merged = {
108155
- mergedParts: ["AgeAudienceData", "GenderAudienceData"],
108156
- startDate,
108157
- endDate,
108158
- account: id,
108159
- data: { ageAudience: ageData, genderAudience: genderData }
108160
- };
108161
- if (opts.jsonOut) {
108162
- await emitBingSnapshot(opts, "bing-audience-merged", id, startDate, endDate, merged);
108163
- return;
108164
- }
108165
- console.log(JSON.stringify(merged, null, 2));
108166
- } catch (err) {
108167
- console.error(`
108168
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
108169
- `);
108170
- process.exit(1);
108171
- }
108172
- }
108173
- async function runReportBingCampaigns(opts) {
108174
- const id = assertBingAccountId(opts.account);
108175
- const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
108176
- const config = loadConfig(opts.token);
108177
- const params = new URLSearchParams({ startDate, endDate });
108178
- const url = reportingUrl2(config, id, "CampaignReport", params.toString());
108179
- try {
108180
- const data = await fetchBingJson(config, url, !!opts.verbose);
108181
- if (opts.jsonOut) {
108182
- await emitBingSnapshot(opts, "bing-campaigns", id, startDate, endDate, data);
108183
- return;
108184
- }
108185
- console.log(`
108186
- \u2705 Bing \u5E7F\u544A\u7CFB\u5217\u62A5\u8868 ${id} ${startDate} ~ ${endDate}`);
108187
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
108188
- } catch (err) {
108189
- console.error(`
108190
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
108191
- `);
108192
- process.exit(1);
108193
- }
108194
- }
108195
- async function runReportBingAdGroups(opts) {
108196
- const id = assertBingAccountId(opts.account);
108197
- const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
108198
- const config = loadConfig(opts.token);
108199
- const params = new URLSearchParams({ startDate, endDate });
108200
- const url = reportingUrl2(config, id, "AdGroupReport", params.toString());
108201
- try {
108202
- const data = await fetchBingJson(config, url, !!opts.verbose);
108203
- if (opts.jsonOut) {
108204
- await emitBingSnapshot(opts, "bing-ad-groups", id, startDate, endDate, data);
108205
- return;
108206
- }
108207
- console.log(`
108208
- \u2705 Bing \u5E7F\u544A\u7EC4\u62A5\u8868 ${id} ${startDate} ~ ${endDate}`);
108209
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
108210
- } catch (err) {
108211
- console.error(`
108212
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
108213
- `);
108214
- process.exit(1);
108215
- }
108216
- }
108217
- async function runReportBingAds(opts) {
108218
- const id = assertBingAccountId(opts.account);
108219
- const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
108220
- const config = loadConfig(opts.token);
108221
- const params = new URLSearchParams({ startDate, endDate });
108222
- const url = reportingUrl2(config, id, "AdReport", params.toString());
108223
- try {
108224
- const data = await fetchBingJson(config, url, !!opts.verbose);
108225
- if (opts.jsonOut) {
108226
- await emitBingSnapshot(opts, "bing-ads", id, startDate, endDate, data);
108227
- return;
108228
- }
108229
- console.log(`
108230
- \u2705 Bing \u5E7F\u544A\u62A5\u8868 ${id} ${startDate} ~ ${endDate}`);
108231
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
108232
- } catch (err) {
108233
- console.error(`
108234
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
108235
- `);
108236
- process.exit(1);
108237
- }
108238
- }
108239
- async function runReportBingKeywords(opts) {
108240
- const id = assertBingAccountId(opts.account);
108241
- const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
108242
- const limit = resolveLimit(opts.limit);
108243
- const config = loadConfig(opts.token);
108244
- const params = new URLSearchParams({
108245
- startDate,
108246
- endDate,
108247
- limit: String(limit),
108248
- orderByCost: "true"
108249
- });
108250
- const url = reportingUrl2(config, id, "KeywordReport", params.toString());
108251
- try {
108252
- const data = await fetchBingJson(config, url, !!opts.verbose);
108253
- if (opts.jsonOut) {
108254
- await emitBingSnapshot(opts, "bing-keywords", id, startDate, endDate, data);
108255
- return;
108256
- }
108257
- console.log(`
108258
- \u2705 Bing \u5173\u952E\u8BCD\u62A5\u8868 ${id} limit=${limit} orderByCost=true`);
108259
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
108260
- } catch (err) {
108261
- console.error(`
108262
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
108263
- `);
108264
- process.exit(1);
108265
- }
108266
- }
108267
- async function runReportBingSearchTerms(opts) {
108268
- const id = assertBingAccountId(opts.account);
108269
- const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
108270
- const limit = resolveLimit(opts.limit);
108271
- const config = loadConfig(opts.token);
108272
- const params = new URLSearchParams({
108273
- startDate,
108274
- endDate,
108275
- limit: String(limit),
108276
- orderByCost: "true"
108277
- });
108278
- const url = reportingUrl2(config, id, "SearchQueryReport", params.toString());
108279
- try {
108280
- const data = await fetchBingJson(config, url, !!opts.verbose);
108281
- if (opts.jsonOut) {
108282
- await emitBingSnapshot(opts, "bing-search-terms", id, startDate, endDate, data);
108283
- return;
108284
- }
108285
- console.log(`
108286
- \u2705 Bing \u641C\u7D22\u5B57\u8BCD\u62A5\u8868 ${id} limit=${limit} orderByCost=true`);
108287
- console.log(" \uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
108288
- } catch (err) {
108289
- console.error(`
108290
- \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
108291
- `);
108292
- process.exit(1);
108293
- }
108294
- }
108295
-
108296
- // src/commands/report-bing-analysis/_register.ts
108297
- function addBingDateOptions(c) {
108298
- return c.requiredOption(
108299
- "-a, --account <id>",
108300
- "Bing mediaCustomerId\uFF08\u6570\u5B57\uFF0C\u4E0E list-accounts -m BingV2 \u4E00\u81F4\uFF09"
108301
- ).option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\u7565\uFF1B\u4E0D\u53EF\u542B\u4ECA\u5929/\u6628\u5929\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-t, --token <token>", "Auth Token").option(
108302
- "--json-out <dir>",
108303
- "\u5C06 JSON \u5199\u5165\u76EE\u5F55\uFF08\u6587\u4EF6\u540D\u5E26 accountId \u540E\u7F00\uFF09\u5E76\u66F4\u65B0 report-manifest-<accountId>.json\uFF1Bstdout \u4EC5\u4E00\u884C\u6458\u8981 JSON"
108304
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false);
108305
- }
108306
- function registerReportBingCommands(reportCmd) {
108307
- const desc = "Bing\uFF08BingV2\uFF09\u8D26\u6237\u5206\u6790\uFF08TSO reporting/media-account/BingV2/...\uFF1B\u65E5\u671F\u4E0D\u53EF\u542B\u4ECA\u5929/\u6628\u5929\uFF09";
108308
- const bindSimple = (name2, label, runner) => {
108309
- const cmd = reportCmd.command(name2).description(`${desc} \u2014 ${label}`);
108310
- addBingDateOptions(cmd);
108311
- cmd.action(async (opts) => {
108312
- await runner(opts);
108313
- });
108314
- };
108315
- bindSimple("bing-overview", "\u603B\u89C8 OverviewSectionData", runReportBingOverview);
108316
- bindSimple("bing-device", "\u8BBE\u5907 DeviceSectionData", runReportBingDevice);
108317
- bindSimple("bing-geographic", "\u5730\u57DF GeographicSectionData", runReportBingGeographic);
108318
- bindSimple("bing-age-audience", "\u5E74\u9F84\u53D7\u4F17 AgeAudienceData", runReportBingAgeAudience);
108319
- bindSimple("bing-gender-audience", "\u6027\u522B\u53D7\u4F17 GenderAudienceData", runReportBingGenderAudience);
108320
- bindSimple("bing-campaigns", "\u5E7F\u544A\u7CFB\u5217 CampaignReport", runReportBingCampaigns);
108321
- bindSimple("bing-ad-groups", "\u5E7F\u544A\u7EC4 AdGroupReport", runReportBingAdGroups);
108322
- bindSimple("bing-ads", "\u5E7F\u544A AdReport", runReportBingAds);
108323
- reportCmd.command("bing-audience-merged").description(`${desc} \u2014 \u5408\u5E76 AgeAudienceData + GenderAudienceData\uFF08\u9ED8\u8BA4 stdout \u4E3A JSON\uFF09`).requiredOption("-a, --account <id>", "Bing mediaCustomerId").option("--start <date>", "\u5F00\u59CB\u65E5\u671F").option("--end <date>", "\u7ED3\u675F\u65E5\u671F").option("-t, --token <token>", "Auth Token").option(
108324
- "--json-out <dir>",
108325
- "\u5C06\u5408\u5E76 JSON \u5199\u5165\u76EE\u5F55\uFF08\u6587\u4EF6\u540D\u5E26 accountId \u540E\u7F00\uFF09\u5E76\u66F4\u65B0 report-manifest-<accountId>.json\uFF1Bstdout \u4EC5\u4E00\u884C\u6458\u8981 JSON"
108326
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
108327
- await runReportBingAudienceMerged(opts);
108328
- });
108329
- const bindLimit = (name2, label, runner) => {
108330
- const cmd = reportCmd.command(name2).description(`${desc} \u2014 ${label}`);
108331
- addBingDateOptions(cmd);
108332
- cmd.option(
108333
- "--limit <n>",
108334
- "\u6761\u6570\u4E0A\u9650\uFF08\u9ED8\u8BA4 100\uFF0C\u4E0E\u524D\u7AEF KeywordReport/SearchQueryReport \u4E00\u81F4\uFF09",
108335
- (v) => parseInt(v, 10)
108336
- );
108337
- cmd.action(async (opts) => {
108338
- await runner(opts);
108339
- });
108340
- };
108341
- bindLimit(
108342
- "bing-keywords",
108343
- "\u5173\u952E\u8BCD KeywordReport\uFF08limit + orderByCost=true\uFF09",
108344
- runReportBingKeywords
108345
- );
108346
- bindLimit(
108347
- "bing-search-terms",
108348
- "\u641C\u7D22\u5B57\u8BCD SearchQueryReport\uFF08limit + orderByCost=true\uFF09",
108349
- runReportBingSearchTerms
108350
- );
108351
- }
108352
-
108353
- // src/commands/report/register.ts
108354
- function register13(program2) {
108355
- const reportCmd = program2.command("report").description("\u4F18\u5316\u62A5\u544A\u4E0E\u63A8\u9001\u7BA1\u7406");
108356
- reportCmd.command("list").description("\u67E5\u8BE2\u4F18\u5316\u62A5\u544A\u5217\u8868").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").option("-t, --type <type>", "\u62A5\u544A\u7C7B\u578B\uFF08\u5982 Daily / Hourly\uFF09").option("-s, --status <ready>", "\u751F\u6210\u72B6\u6001\uFF08true/false\uFF09").option("-k, --keyword <text>", "\u62A5\u544A\u540D\u79F0\u5173\u952E\u5B57").option("--start <date>", "\u5F00\u59CB\u65E5\u671F").option("--end <date>", "\u7ED3\u675F\u65E5\u671F").option("-p, --page <n>", "\u9875\u7801", parseInt).option("--page-size <n>", "\u6BCF\u9875\u6570\u91CF", parseInt).option("--token <token>", "Auth Token").option(
108357
- "--json-out <path>",
108358
- "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
108359
- void 0
108360
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
108361
- await runReportList({
108362
- token: opts.token,
108363
- media: opts.media,
108364
- type: opts.type,
108365
- status: opts.status,
108366
- keyword: opts.keyword,
108367
- startDate: opts.start,
108368
- endDate: opts.end,
108369
- page: opts.page,
108370
- pageSize: opts.pageSize,
108371
- jsonOut: opts.jsonOut,
108372
- verbose: opts.verbose
108373
- });
108374
- });
108375
- reportCmd.command("create").description("\u6279\u91CF\u751F\u6210\u4F18\u5316\u62A5\u544A").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").requiredOption("-a, --accounts <ids>", "\u8D26\u6237 ID\uFF0C\u9017\u53F7\u5206\u9694").requiredOption("-t, --type <type>", "\u62A5\u544A\u7C7B\u578B\uFF0C\u5982 Daily").requiredOption("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD").requiredOption("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("--token <token>", "Auth Token").option(
108376
- "--json-out <path>",
108377
- "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
108378
- void 0
108379
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
108380
- await runReportCreate({
108381
- token: opts.token,
108382
- media: opts.media,
108383
- accounts: opts.accounts.split(",").map((s) => s.trim()),
108384
- type: opts.type,
108385
- startDate: opts.start,
108386
- endDate: opts.end,
108387
- jsonOut: opts.jsonOut,
108388
- verbose: opts.verbose
108389
- });
108390
- });
108391
- reportCmd.command("delete").description("\u5220\u9664\u4F18\u5316\u62A5\u544A\uFF08--id \u6216 --ids \u81F3\u5C11\u4F20\u4E00\u4E2A\uFF09").option("--id <id>", "\u5355\u6761\u62A5\u544A entityId\uFF08\u4E0E --ids \u4E8C\u9009\u4E00\uFF09").option("--ids <ids>", "\u6279\u91CF\u5220\u9664\uFF0C\u9017\u53F7\u5206\u9694 entityId\uFF08\u4E0E --id \u4E8C\u9009\u4E00\uFF09").option("--token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
108392
- await runReportDelete({
108393
- token: opts.token,
108394
- id: opts.id,
108395
- ids: opts.ids ? opts.ids.split(",").map((s) => s.trim()) : void 0,
108396
- verbose: opts.verbose
108397
- });
108398
- });
108399
- reportCmd.command("meta-overview").description(
108400
- "Meta \u8D26\u6237\u5206\u6790\u603B\u89C8\uFF08OverviewSectionData\uFF1BFacebookAds \u6388\u6743\u6237\u8D70 FacebookAds/act_\uFF0CMetaAd \u5F00\u6237\u8D70 MetaAd\uFF09"
108401
- ).requiredOption("-a, --account <id>", "Meta \u5E7F\u544A\u8D26\u6237 mediaCustomerId\uFF08\u6570\u5B57\u6216 act_<\u6570\u5B57>\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\u7565\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("--token <token>", "Auth Token").option(
108402
- "--json-out <path>",
108403
- "\u843D\u76D8\u76EE\u5F55\u5E76\u66F4\u65B0 report-manifest[-<accountId>].json\uFF1B\u6587\u4EF6\u540D\u4E3A `<section>[-<accountId>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09"
108404
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
108405
- await runReportMetaOverview({
108406
- token: opts.token,
108407
- account: opts.account,
108408
- startDate: opts.start,
108409
- endDate: opts.end,
108410
- jsonOut: opts.jsonOut,
108411
- verbose: opts.verbose
108412
- });
108413
- });
108414
- registerReportTiktokCommands(reportCmd);
108415
- registerReportBingCommands(reportCmd);
108416
- const pushCmd = reportCmd.command("push").description("\u63A8\u9001\u7BA1\u7406\uFF08\u90AE\u4EF6\u62A5\u544A\u63A8\u9001\u4EFB\u52A1\uFF09");
108417
- pushCmd.command("list").description("\u67E5\u8BE2\u63A8\u9001\u4EFB\u52A1\u5217\u8868").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").option("-k, --keyword <text>", "\u63A8\u9001\u540D\u79F0\u5173\u952E\u5B57").option("-t, --type <type>", "\u62A5\u544A\u7C7B\u578B").option("--stopped", "\u53EA\u663E\u793A\u5DF2\u505C\u7528\u7684\u63A8\u9001", false).option("--start <date>", "\u5F00\u59CB\u65E5\u671F").option("--end <date>", "\u7ED3\u675F\u65E5\u671F").option("-p, --page <n>", "\u9875\u7801", parseInt).option("--page-size <n>", "\u6BCF\u9875\u6570\u91CF", parseInt).option("--token <token>", "Auth Token").option(
108418
- "--json-out <path>",
108419
- "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
108420
- void 0
108421
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
108422
- await runReportPushList({
108423
- token: opts.token,
108424
- media: opts.media,
108425
- keyword: opts.keyword,
108426
- type: opts.type,
108427
- stopped: opts.stopped,
108428
- startDate: opts.start,
108429
- endDate: opts.end,
108430
- page: opts.page,
108431
- pageSize: opts.pageSize,
108432
- jsonOut: opts.jsonOut,
108433
- verbose: opts.verbose
108434
- });
108435
- });
108436
- pushCmd.command("create").description(
108437
- "\u65B0\u5EFA\u63A8\u9001\u914D\u7F6E\uFF08POST /command/report-push/settings/{\u5A92\u4F53}\uFF0C\u4E0E\u7F51\u9875\u300C\u6DFB\u52A0\u63A8\u9001\u300D\u4E00\u81F4\uFF09"
108438
- ).requiredOption("-m, --media <type>", "\u5A92\u4F53\uFF1AGoogle | TikTok").requiredOption("--name <name>", "\u63A8\u9001\u540D\u79F0\uFF08\u5BF9\u5E94 body.Name\uFF09").requiredOption(
108439
- "--media-accounts <ids>",
108440
- "\u5E7F\u544A\u8D26\u6237 entityId\uFF0C\u9017\u53F7\u5206\u9694\uFF08list-accounts --json-out \u2192 ma.entityId\uFF09",
108441
- splitCsv
108442
- ).requiredOption("--emails <list>", "\u6536\u4EF6\u90AE\u7BB1\uFF0C\u9017\u53F7\u5206\u9694", splitCsv).requiredOption("--types <list>", "\u62A5\u544A\u7C7B\u578B\uFF0C\u9017\u53F7\u5206\u9694\uFF1ADaily | Weekly | Monthly", splitCsv).option("--title <text>", "\u90AE\u4EF6\u6807\u9898\uFF08ReceiveTitle\uFF0C\u53EF\u7A7A\uFF09", "").option("--ai-suggest", "\u4EC5 Google\uFF1AAiSuggest=true", false).option("--token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
108443
- await runReportPushCreate({
108444
- token: opts.token,
108445
- media: opts.media,
108446
- name: opts.name,
108447
- mediaAccounts: opts.mediaAccounts,
108448
- receiveEmails: opts.emails,
108449
- reportTypes: opts.types,
108450
- receiveTitle: opts.title ?? "",
108451
- aiSuggest: opts.aiSuggest,
108452
- verbose: opts.verbose
108453
- });
108454
- });
108455
- pushCmd.command("update").description(
108456
- "\u7F16\u8F91\u63A8\u9001\u914D\u7F6E\uFF08PUT /command/report-push/settings/{\u5A92\u4F53}/{id}\uFF1Bid \u4E0E\u7F51\u9875\u7F16\u8F91\u7528 row.id \u4E00\u81F4\uFF0C\u901A\u5E38\u540C entityId\uFF09"
108457
- ).requiredOption("-m, --media <type>", "\u5A92\u4F53\uFF1AGoogle | TikTok").requiredOption("--id <id>", "\u63A8\u9001\u914D\u7F6E id\uFF08push list --json-out \u2192 items[].id \u6216 entityId\uFF09").requiredOption("--name <name>", "\u63A8\u9001\u540D\u79F0").requiredOption("--media-accounts <ids>", "\u5E7F\u544A\u8D26\u6237 entityId\uFF0C\u9017\u53F7\u5206\u9694", splitCsv).requiredOption("--emails <list>", "\u6536\u4EF6\u90AE\u7BB1\uFF0C\u9017\u53F7\u5206\u9694", splitCsv).requiredOption("--types <list>", "\u62A5\u544A\u7C7B\u578B\uFF0C\u9017\u53F7\u5206\u9694\uFF1ADaily | Weekly | Monthly", splitCsv).option("--title <text>", "\u90AE\u4EF6\u6807\u9898", "").option("--ai-suggest", "\u4EC5 Google\uFF1AAiSuggest=true", false).option("--token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
108458
- await runReportPushUpdate({
108459
- token: opts.token,
108460
- media: opts.media,
108461
- id: opts.id,
108462
- name: opts.name,
108463
- mediaAccounts: opts.mediaAccounts,
108464
- receiveEmails: opts.emails,
108465
- reportTypes: opts.types,
108466
- receiveTitle: opts.title ?? "",
108467
- aiSuggest: opts.aiSuggest,
108468
- verbose: opts.verbose
108469
- });
108470
- });
108471
- pushCmd.command("history").description(
108472
- "\u63A8\u9001\u8BB0\u5F55\u5217\u8868\uFF08GET /query/report-push/settings/{\u5A92\u4F53}/{settingId}/push-history\uFF0C\u5BF9\u5E94\u7F51\u9875 /dataReport/pushRecord\uFF09"
108473
- ).requiredOption("-m, --media <type>", "\u5A92\u4F53\uFF1AGoogle | TikTok").requiredOption(
108474
- "--setting-id <id>",
108475
- "\u63A8\u9001\u89C4\u5219 id\uFF08\u4E0E push list \u7684 entityId \u4E00\u81F4\uFF1B\u7F51\u9875\u8DEF\u7531 entityId\uFF09"
108476
- ).option("-p, --page <n>", "\u9875\u7801", parseInt).option("--page-size <n>", "\u6BCF\u9875\u6570\u91CF", parseInt).option("--start <date>", "\u7B5B\u9009\u5F00\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u7B5B\u9009\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("--receive-email <email>", "\u7B5B\u9009\u6536\u4EF6\u90AE\u7BB1").option("--report-type <type>", "\u7B5B\u9009\u62A5\u544A\u7C7B\u578B").option("--media-account-id <entityId>", "\u7B5B\u9009\u8D26\u6237\uFF08\u4F20\u8D26\u6237 entityId\uFF09").option("--token <token>", "Auth Token").option(
108477
- "--json-out <path>",
108478
- "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
108479
- void 0
108480
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
108481
- await runReportPushHistory({
108482
- token: opts.token,
108483
- media: opts.media,
108484
- settingId: opts.settingId,
108485
- page: opts.page,
108486
- pageSize: opts.pageSize,
108487
- startDate: opts.start,
108488
- endDate: opts.end,
108489
- receiveEmail: opts.receiveEmail,
108490
- reportType: opts.reportType,
108491
- mediaAccountId: opts.mediaAccountId,
108492
- jsonOut: opts.jsonOut,
108493
- verbose: opts.verbose
108494
- });
108495
- });
108496
- pushCmd.command("receive-emails").description(
108497
- "\u67E5\u8BE2\u63A8\u9001\u66FE\u7528\u8FC7\u7684\u6536\u4EF6\u90AE\u7BB1\u5217\u8868\uFF08GET /query/report-push/settings/{\u5A92\u4F53}/all/receive-emails\uFF0C\u4E0E\u7F51\u9875\u4E0B\u62C9\u4E00\u81F4\uFF09"
108498
- ).requiredOption("-m, --media <type>", "\u5A92\u4F53\uFF1AGoogle | TikTok").option("--token <token>", "Auth Token").option(
108499
- "--json-out <path>",
108500
- "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
108501
- void 0
108502
- ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
108503
- await runReportPushReceiveEmails({
108504
- token: opts.token,
108505
- media: opts.media,
108506
- jsonOut: opts.jsonOut,
108507
- verbose: opts.verbose
108508
- });
108509
- });
108510
- pushCmd.command("start").description("\u542F\u52A8\u63A8\u9001\u4EFB\u52A1").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").requiredOption("--id <id>", "\u63A8\u9001\u4EFB\u52A1 entityId").option("--token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
108511
- await runReportPushStart({
108512
- token: opts.token,
108513
- media: opts.media,
108514
- id: opts.id,
108515
- verbose: opts.verbose
108516
- });
108517
- });
108518
- pushCmd.command("stop").description("\u505C\u6B62\u63A8\u9001\u4EFB\u52A1").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").requiredOption("--id <id>", "\u63A8\u9001\u4EFB\u52A1 entityId").option("--token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
108519
- await runReportPushStop({
108520
- token: opts.token,
108521
- media: opts.media,
108522
- id: opts.id,
108523
- verbose: opts.verbose
108524
- });
108525
- });
108526
- pushCmd.command("delete").description("\u5220\u9664\u63A8\u9001\u4EFB\u52A1").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").requiredOption("--id <id>", "\u63A8\u9001\u4EFB\u52A1 entityId").option("--token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
108527
- await runReportPushDelete({
108528
- token: opts.token,
108529
- media: opts.media,
108530
- id: opts.id,
108531
- verbose: opts.verbose
108532
- });
108533
- });
108534
- }
108535
-
108536
- // src/commands/transfer.ts
108537
- init_auth();
108538
- init_cli_json_snapshot();
108539
- init_cli_table();
108540
- var VALID_MEDIA_TYPES6 = ["Google", "TikTok", "MetaAd", "BingV2"];
108541
- function mediaToPath(media) {
108542
- return media.toLowerCase();
108543
- }
108544
- async function runTransferCreate(opts) {
108545
- if (!VALID_MEDIA_TYPES6.includes(opts.media)) {
108546
- console.error(
108547
- `
108548
- \u274C \u4E0D\u652F\u6301\u7684\u5A92\u4F53\u7C7B\u578B\uFF1A${opts.media}
108549
- \u53EF\u9009\uFF1A${VALID_MEDIA_TYPES6.join(" | ")}
108550
- `
108551
- );
108552
- process.exit(1);
108553
- }
108554
- const config = loadConfig(opts.token);
108555
- const url = `${config.apiBaseUrl}/command/accounttransfer/${mediaToPath(opts.media)}/createaccounttransfers`;
108556
- const body = [
108557
- {
108558
- exchangeRate: 1,
108559
- accountTransferOutData: {
108560
- mediaCustomerId: opts.outAccount,
108561
- shortId: null,
108562
- transferOutAmounts: opts.amount
108563
- },
108564
- accountTransferInData: {
108565
- mediaCustomerId: opts.inAccount,
108566
- shortId: null
108567
- },
108568
- customerName: opts.customerName ?? ""
108569
- }
108570
- ];
108571
- let result;
108572
- try {
108573
- result = await apiFetch2(
108574
- url,
108575
- config,
108576
- { method: "POST", body: JSON.stringify(body) },
108577
- opts.verbose
108578
- );
108579
- } catch (err) {
108580
- console.error(`
108581
- \u274C \u8F6C\u8D26\u7533\u8BF7\u63D0\u4EA4\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
108582
- `);
108583
- process.exit(1);
108584
- }
108585
- if (await emitCliJsonOrSnapshot(opts, {
108586
- section: `transfer-create-${mediaToPath(opts.media)}`,
108587
- commandLabel: "transfer create",
108588
- commandHint: opts.media,
108589
- payload: result,
108590
- idSuffix: opts.media
108591
- })) {
108592
- return;
108593
- }
108594
- console.log(
108595
- `
108596
- \u2705 \u8F6C\u8D26\u7533\u8BF7\u5DF2\u63D0\u4EA4\uFF1A${opts.outAccount} \u2192 ${opts.inAccount}\uFF0C\u91D1\u989D ${opts.amount}\u3002
108597
- `
108598
- );
108599
- console.log(" \u4F7F\u7528 transfer list \u67E5\u770B\u8F6C\u8D26\u8BB0\u5F55\u53CA\u5230\u8D26\u72B6\u6001\u3002\n");
108600
- }
108601
- async function runTransferList(opts) {
108602
- if (!VALID_MEDIA_TYPES6.includes(opts.media)) {
108603
- console.error(
108604
- `
108605
- \u274C \u4E0D\u652F\u6301\u7684\u5A92\u4F53\u7C7B\u578B\uFF1A${opts.media}
108606
- \u53EF\u9009\uFF1A${VALID_MEDIA_TYPES6.join(" | ")}
108607
- `
108608
- );
108609
- process.exit(1);
108610
- }
108611
- const config = loadConfig(opts.token);
108612
- const params = new URLSearchParams();
108613
- params.set("pageNumber", String(opts.page ?? 1));
108614
- params.set("pageSize", String(opts.pageSize ?? 20));
108615
- if (opts.number) params.set("transferNumber", opts.number);
108616
- if (opts.status) params.set("transferStatus", opts.status);
108617
- if (opts.startDate) params.set("startDate", opts.startDate);
108618
- if (opts.endDate) params.set("endDate", opts.endDate);
108619
- const url = `${config.apiBaseUrl}/query/accounttransfer/${opts.media}/search?${params}`;
108620
- let data;
108621
- try {
108622
- data = await apiFetch2(url, config, {}, opts.verbose);
108623
- } catch (err) {
108624
- console.error(`
108625
- \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
108626
- `);
108627
- process.exit(1);
108628
- }
108629
- const items = data.list ?? data.results ?? [];
108630
- const total = data.total ?? data.totalResultCount ?? 0;
108631
- const page = opts.page ?? 1;
108632
- const pageSize = opts.pageSize ?? 20;
108633
- const listPayload = wrapListJson({ page, pageSize, total, items });
108634
- if (await emitCliJsonOrSnapshot(opts, {
108635
- section: `transfer-list-${mediaToPath(opts.media)}`,
108636
- commandLabel: "transfer list",
108637
- commandHint: opts.media,
108638
- payload: listPayload,
108639
- idSuffix: opts.media
108640
- })) {
108641
- return;
108642
- }
108643
- console.log(
108644
- `
108645
- \u8F6C\u8D26\u8BB0\u5F55\uFF08\u7B2C ${page}/${Math.max(1, Math.ceil(total / pageSize))} \u9875\uFF0C\u672C\u9875 ${items.length} \u6761\uFF0C\u5171 ${total} \u6761\uFF09
108646
- `
108647
- );
108648
- if (items.length === 0) {
108649
- console.log(" \u6682\u65E0\u6570\u636E\u3002\n");
108650
- return;
108651
- }
108652
- const cols = [
108653
- { key: "transferNumber", header: "\u8BA2\u5355\u53F7" },
108654
- { key: "outAcct", header: "\u8F6C\u51FA\u8D26\u6237" },
108655
- { key: "inAcct", header: "\u8F6C\u5165\u8D26\u6237" },
108656
- { key: "amount", header: "\u91D1\u989D" },
108657
- { key: "currency", header: "\u5E01\u79CD" },
108658
- { key: "customerName", header: "\u5BA2\u6237\u540D\u79F0" },
108659
- { key: "transferStatus", header: "\u8BA2\u5355\u72B6\u6001" },
108660
- { key: "created", header: "\u521B\u5EFA\u65F6\u95F4" }
108661
- ];
108662
- const rows = items.map((item) => {
108663
- const out = item.accountTransferOutData;
108664
- const inp = item.accountTransferInData;
108665
- const outAcct = out ? `${out.mediaCustomerId ?? ""}(${out.mediaCustomerName ?? "\u2014"})` : "\u2014";
108666
- const inAcct = inp ? `${inp.mediaCustomerId ?? ""}(${inp.mediaCustomerName ?? "\u2014"})` : "\u2014";
108667
- const amount = out?.transferOutAmounts ?? inp?.transferInAmounts ?? "\u2014";
108668
- const currency = out?.currencyCode ?? "";
108669
- const created = item.createdDateTime ? String(item.createdDateTime).slice(0, 10) : "\u2014";
108670
- return {
108671
- transferNumber: item.transferNumber ?? "",
108672
- outAcct: outAcct.slice(0, 22),
108673
- inAcct: inAcct.slice(0, 22),
108674
- amount: String(amount),
108675
- currency,
108676
- customerName: (item.customerName ?? "").slice(0, 14),
108677
- transferStatus: item.transferStatus ?? "",
108678
- created
108679
- };
108680
- });
108681
- printCliTable(rows, cols);
108682
- console.log();
107688
+ const cols = [
107689
+ { key: "transferNumber", header: "\u8BA2\u5355\u53F7" },
107690
+ { key: "outAcct", header: "\u8F6C\u51FA\u8D26\u6237" },
107691
+ { key: "inAcct", header: "\u8F6C\u5165\u8D26\u6237" },
107692
+ { key: "amount", header: "\u91D1\u989D" },
107693
+ { key: "currency", header: "\u5E01\u79CD" },
107694
+ { key: "customerName", header: "\u5BA2\u6237\u540D\u79F0" },
107695
+ { key: "transferStatus", header: "\u8BA2\u5355\u72B6\u6001" },
107696
+ { key: "created", header: "\u521B\u5EFA\u65F6\u95F4" }
107697
+ ];
107698
+ const rows = items.map((item) => {
107699
+ const out = item.accountTransferOutData;
107700
+ const inp = item.accountTransferInData;
107701
+ const outAcct = out ? `${out.mediaCustomerId ?? ""}(${out.mediaCustomerName ?? "\u2014"})` : "\u2014";
107702
+ const inAcct = inp ? `${inp.mediaCustomerId ?? ""}(${inp.mediaCustomerName ?? "\u2014"})` : "\u2014";
107703
+ const amount = out?.transferOutAmounts ?? inp?.transferInAmounts ?? "\u2014";
107704
+ const currency = out?.currencyCode ?? "";
107705
+ const created = item.createdDateTime ? String(item.createdDateTime).slice(0, 10) : "\u2014";
107706
+ return {
107707
+ transferNumber: item.transferNumber ?? "",
107708
+ outAcct: outAcct.slice(0, 22),
107709
+ inAcct: inAcct.slice(0, 22),
107710
+ amount: String(amount),
107711
+ currency,
107712
+ customerName: (item.customerName ?? "").slice(0, 14),
107713
+ transferStatus: item.transferStatus ?? "",
107714
+ created
107715
+ };
107716
+ });
107717
+ printCliTable(rows, cols);
107718
+ console.log();
108683
107719
  }
108684
107720
  function register14(program2) {
108685
107721
  const transferCmd = program2.command("transfer").description("\u5A92\u4F53\u8D26\u6237\u8F6C\u8D26\uFF1A\u63D0\u4EA4\u8F6C\u8D26\u7533\u8BF7\u6216\u67E5\u8BE2\u8F6C\u8D26\u8BB0\u5F55");
@@ -111341,7 +110377,7 @@ function cloneKeywordBlockShell(block) {
111341
110377
  delete shell["matchTypeV2"];
111342
110378
  return shell;
111343
110379
  }
111344
- function splitKeywordsForBatchJobBlockIfMixed(block, path32, warnings) {
110380
+ function splitKeywordsForBatchJobBlockIfMixed(block, path34, warnings) {
111345
110381
  const declaredUi = matchTypeV2ToUi(readBlockMatchTypeRaw(block));
111346
110382
  const entries = collectKeywordEntriesFromBlock(block);
111347
110383
  if (entries.length === 0) return [block];
@@ -111360,7 +110396,7 @@ function splitKeywordsForBatchJobBlockIfMixed(block, path32, warnings) {
111360
110396
  (ui) => matchTypeUiToV2(ui)
111361
110397
  );
111362
110398
  warnings.push(
111363
- `${path32} \u542B\u591A\u79CD\u5339\u914D\u7C7B\u578B\uFF0C\u5DF2\u81EA\u52A8\u62C6\u5206\u4E3A ${groups.size} \u4E2A KeywordsForBatchJob \u5757\uFF08${labels.join("\u3001")}\uFF09`
110399
+ `${path34} \u542B\u591A\u79CD\u5339\u914D\u7C7B\u578B\uFF0C\u5DF2\u81EA\u52A8\u62C6\u5206\u4E3A ${groups.size} \u4E2A KeywordsForBatchJob \u5757\uFF08${labels.join("\u3001")}\uFF09`
111364
110400
  );
111365
110401
  const shell = cloneKeywordBlockShell(block);
111366
110402
  const splitBlocks = [];
@@ -111380,16 +110416,16 @@ function splitKeywordsForBatchJobBlockIfMixed(block, path32, warnings) {
111380
110416
  }
111381
110417
  return splitBlocks;
111382
110418
  }
111383
- function validateKeywordCore(core, path32, errors, lengthViolations) {
110419
+ function validateKeywordCore(core, path34, errors, lengthViolations) {
111384
110420
  const trimmed = core.trim();
111385
110421
  if (!trimmed) {
111386
- errors.push(`${path32} \u5173\u952E\u8BCD\u8BCD\u5E72\u4E0D\u80FD\u4E3A\u7A7A`);
110422
+ errors.push(`${path34} \u5173\u952E\u8BCD\u8BCD\u5E72\u4E0D\u80FD\u4E3A\u7A7A`);
111387
110423
  return false;
111388
110424
  }
111389
110425
  if (trimmed.length > GOOGLE_KEYWORD_MAX_CORE_LENGTH) {
111390
110426
  if (lengthViolations) {
111391
110427
  pushLengthViolation(lengthViolations, {
111392
- path: path32,
110428
+ path: path34,
111393
110429
  field: "KeywordText",
111394
110430
  kind: "keyword_core",
111395
110431
  limit: GOOGLE_KEYWORD_MAX_CORE_LENGTH,
@@ -111399,13 +110435,13 @@ function validateKeywordCore(core, path32, errors, lengthViolations) {
111399
110435
  });
111400
110436
  }
111401
110437
  errors.push(
111402
- `${path32} \u5173\u952E\u8BCD\u8BCD\u5E72\u8D85\u8FC7 ${GOOGLE_KEYWORD_MAX_CORE_LENGTH} \u5B57\u7B26\uFF08\u5F53\u524D ${trimmed.length}\uFF09\uFF1A"${trimmed}"`
110438
+ `${path34} \u5173\u952E\u8BCD\u8BCD\u5E72\u8D85\u8FC7 ${GOOGLE_KEYWORD_MAX_CORE_LENGTH} \u5B57\u7B26\uFF08\u5F53\u524D ${trimmed.length}\uFF09\uFF1A"${trimmed}"`
111403
110439
  );
111404
110440
  return false;
111405
110441
  }
111406
110442
  if (!VALID_CORE_REGEX.test(trimmed)) {
111407
110443
  errors.push(
111408
- `${path32} \u542B Google \u4E0D\u5141\u8BB8\u7684\u5B57\u7B26\uFF08\u8BCD\u5E72\u4EC5\u5141\u8BB8\u5B57\u6BCD/\u6570\u5B57/\u7A7A\u683C/\u8FDE\u5B57\u7B26/\u53E5\u70B9\uFF09\uFF1A"${trimmed.slice(0, 40)}${trimmed.length > 40 ? "\u2026" : ""}"`
110444
+ `${path34} \u542B Google \u4E0D\u5141\u8BB8\u7684\u5B57\u7B26\uFF08\u8BCD\u5E72\u4EC5\u5141\u8BB8\u5B57\u6BCD/\u6570\u5B57/\u7A7A\u683C/\u8FDE\u5B57\u7B26/\u53E5\u70B9\uFF09\uFF1A"${trimmed.slice(0, 40)}${trimmed.length > 40 ? "\u2026" : ""}"`
111409
110445
  );
111410
110446
  return false;
111411
110447
  }
@@ -111462,24 +110498,24 @@ function canonicalizeKeywordBatchBlock(block, resolvedMatchV2, normalizedTexts,
111462
110498
  }
111463
110499
  }
111464
110500
  }
111465
- function pushKeywordAutoFixWarning(warnings, path32, fieldLabel, trimmedRaw, formatted, declaredUi, beforeUi, matchType, inferredMatchType) {
110501
+ function pushKeywordAutoFixWarning(warnings, path34, fieldLabel, trimmedRaw, formatted, declaredUi, beforeUi, matchType, inferredMatchType) {
111466
110502
  if (inferredMatchType) {
111467
110503
  warnings.push(
111468
- `${path32}.${fieldLabel} \u672A\u6307\u5B9A MatchTypeV2\uFF0C\u5DF2\u6309\u8BCD\u9762\u63A8\u65AD\u4E3A ${matchTypeUiToV2(matchType)}`
110504
+ `${path34}.${fieldLabel} \u672A\u6307\u5B9A MatchTypeV2\uFF0C\u5DF2\u6309\u8BCD\u9762\u63A8\u65AD\u4E3A ${matchTypeUiToV2(matchType)}`
111469
110505
  );
111470
110506
  return;
111471
110507
  }
111472
110508
  if (declaredUi && beforeUi !== declaredUi) {
111473
110509
  warnings.push(
111474
- `${path32}.${fieldLabel} MatchTypeV2=${matchTypeUiToV2(declaredUi)} \u4E0E\u8BCD\u9762\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u4FEE\u590D\u4E3A\uFF1A${formatted}`
110510
+ `${path34}.${fieldLabel} MatchTypeV2=${matchTypeUiToV2(declaredUi)} \u4E0E\u8BCD\u9762\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u4FEE\u590D\u4E3A\uFF1A${formatted}`
111475
110511
  );
111476
110512
  return;
111477
110513
  }
111478
110514
  if (formatted !== trimmedRaw) {
111479
- warnings.push(`${path32}.${fieldLabel} \u5DF2\u81EA\u52A8\u4FEE\u590D\u8BCD\u9762\uFF1A${trimmedRaw} \u2192 ${formatted}`);
110515
+ warnings.push(`${path34}.${fieldLabel} \u5DF2\u81EA\u52A8\u4FEE\u590D\u8BCD\u9762\uFF1A${trimmedRaw} \u2192 ${formatted}`);
111480
110516
  }
111481
110517
  }
111482
- function normalizeKeywordTextList(texts, matchTypeRaw, path32, fieldLabel, errors, warnings, lengthViolations) {
110518
+ function normalizeKeywordTextList(texts, matchTypeRaw, path34, fieldLabel, errors, warnings, lengthViolations) {
111483
110519
  const declaredUi = matchTypeV2ToUi(matchTypeRaw);
111484
110520
  const normalized = [];
111485
110521
  const seen = /* @__PURE__ */ new Set();
@@ -111487,7 +110523,7 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path32, fieldLabel, error
111487
110523
  for (let k = 0; k < texts.length; k++) {
111488
110524
  const raw = texts[k];
111489
110525
  if (typeof raw !== "string" || !raw.trim()) {
111490
- errors.push(`${path32}.${fieldLabel}[${k}] \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
110526
+ errors.push(`${path34}.${fieldLabel}[${k}] \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
111491
110527
  continue;
111492
110528
  }
111493
110529
  const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
@@ -111498,7 +110534,7 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path32, fieldLabel, error
111498
110534
  );
111499
110535
  pushKeywordAutoFixWarning(
111500
110536
  warnings,
111501
- path32,
110537
+ path34,
111502
110538
  fieldLabel,
111503
110539
  trimmedRaw,
111504
110540
  formatted,
@@ -111508,11 +110544,11 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path32, fieldLabel, error
111508
110544
  inferredMatchType
111509
110545
  );
111510
110546
  const core = unwrapKeywordDisplayTextForEdit(formatted);
111511
- if (!validateKeywordCore(core, `${path32}.${fieldLabel}[${k}]`, errors, lengthViolations))
110547
+ if (!validateKeywordCore(core, `${path34}.${fieldLabel}[${k}]`, errors, lengthViolations))
111512
110548
  continue;
111513
110549
  const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
111514
110550
  if (seen.has(dedupeKey)) {
111515
- warnings.push(`${path32}.${fieldLabel}[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
110551
+ warnings.push(`${path34}.${fieldLabel}[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
111516
110552
  continue;
111517
110553
  }
111518
110554
  seen.add(dedupeKey);
@@ -111521,12 +110557,12 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path32, fieldLabel, error
111521
110557
  }
111522
110558
  return { normalized, resolvedUi };
111523
110559
  }
111524
- function normalizeKeywordsForBatchJobBlock(block, path32, errors, warnings, lengthViolations) {
110560
+ function normalizeKeywordsForBatchJobBlock(block, path34, errors, warnings, lengthViolations) {
111525
110561
  const matchTypeRaw = readBlockMatchTypeRaw(block);
111526
110562
  const declaredUi = matchTypeV2ToUi(matchTypeRaw);
111527
110563
  if (matchTypeRaw !== void 0 && declaredUi === null) {
111528
110564
  errors.push(
111529
- `${path32}.MatchTypeV2 \u65E0\u6548\uFF08${String(matchTypeRaw)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1ABROAD | PHRASE | EXACT`
110565
+ `${path34}.MatchTypeV2 \u65E0\u6548\uFF08${String(matchTypeRaw)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1ABROAD | PHRASE | EXACT`
111530
110566
  );
111531
110567
  return;
111532
110568
  }
@@ -111541,7 +110577,7 @@ function normalizeKeywordsForBatchJobBlock(block, path32, errors, warnings, leng
111541
110577
  const result = normalizeKeywordTextList(
111542
110578
  texts,
111543
110579
  matchTypeRaw,
111544
- path32,
110580
+ path34,
111545
110581
  "KeywordText",
111546
110582
  errors,
111547
110583
  warnings,
@@ -111550,7 +110586,7 @@ function normalizeKeywordsForBatchJobBlock(block, path32, errors, warnings, leng
111550
110586
  normalizedTexts = result.normalized;
111551
110587
  if (result.resolvedUi) resolvedUi = result.resolvedUi;
111552
110588
  if (normalizedTexts.length === 0 && texts.length > 0) {
111553
- errors.push(`${path32}.KeywordText \u7ECF\u6821\u9A8C\u540E\u65E0\u6709\u6548\u5173\u952E\u8BCD\uFF0C\u8BF7\u4FEE\u6B63\u8BCD\u9762\u6216 MatchTypeV2`);
110589
+ errors.push(`${path34}.KeywordText \u7ECF\u6821\u9A8C\u540E\u65E0\u6709\u6548\u5173\u952E\u8BCD\uFF0C\u8BF7\u4FEE\u6B63\u8BCD\u9762\u6216 MatchTypeV2`);
111554
110590
  }
111555
110591
  }
111556
110592
  if (hasItems && items) {
@@ -111558,12 +110594,12 @@ function normalizeKeywordsForBatchJobBlock(block, path32, errors, warnings, leng
111558
110594
  for (let k = 0; k < items.length; k++) {
111559
110595
  const item = items[k];
111560
110596
  if (!item || typeof item !== "object") {
111561
- errors.push(`${path32}.Items[${k}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
110597
+ errors.push(`${path34}.Items[${k}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
111562
110598
  continue;
111563
110599
  }
111564
110600
  const raw = readItemText(item);
111565
110601
  if (raw === null || !raw.trim()) {
111566
- errors.push(`${path32}.Items[${k}].Text \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
110602
+ errors.push(`${path34}.Items[${k}].Text \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
111567
110603
  continue;
111568
110604
  }
111569
110605
  const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
@@ -111574,7 +110610,7 @@ function normalizeKeywordsForBatchJobBlock(block, path32, errors, warnings, leng
111574
110610
  );
111575
110611
  pushKeywordAutoFixWarning(
111576
110612
  warnings,
111577
- path32,
110613
+ path34,
111578
110614
  `Items[${k}].Text`,
111579
110615
  trimmedRaw,
111580
110616
  formatted,
@@ -111584,11 +110620,11 @@ function normalizeKeywordsForBatchJobBlock(block, path32, errors, warnings, leng
111584
110620
  inferredMatchType
111585
110621
  );
111586
110622
  const core = unwrapKeywordDisplayTextForEdit(formatted);
111587
- if (!validateKeywordCore(core, `${path32}.Items[${k}].Text`, errors, lengthViolations))
110623
+ if (!validateKeywordCore(core, `${path34}.Items[${k}].Text`, errors, lengthViolations))
111588
110624
  continue;
111589
110625
  const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
111590
110626
  if (seen.has(dedupeKey)) {
111591
- warnings.push(`${path32}.Items[${k}].Text \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
110627
+ warnings.push(`${path34}.Items[${k}].Text \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
111592
110628
  continue;
111593
110629
  }
111594
110630
  seen.add(dedupeKey);
@@ -111733,16 +110769,16 @@ function warnIfNegativeKeywordsInPositiveKeywords(campaign, warnings) {
111733
110769
  const raw = texts[ki];
111734
110770
  if (typeof raw !== "string" || !raw.trim()) continue;
111735
110771
  const core = normalizeKeywordCoreForNegativeCheck(raw);
111736
- const path32 = `campaign.AdGroupsForBatchJob[${gi}].KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
110772
+ const path34 = `campaign.AdGroupsForBatchJob[${gi}].KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
111737
110773
  if (plannedNegCores.has(core)) {
111738
110774
  warnings.push(
111739
- `${path32} \u8BCD\u9762\u300C${raw}\u300D\u5DF2\u51FA\u73B0\u5728 campaign.NegativeKeywordsForBatchJob\uFF0C\u4E0D\u5E94\u91CD\u590D\u5199\u5165\u6B63\u5411 KeywordsForBatchJob\uFF1B\u5426\u8BCD\u53EA\u653E NegativeKeywordsForBatchJob`
110775
+ `${path34} \u8BCD\u9762\u300C${raw}\u300D\u5DF2\u51FA\u73B0\u5728 campaign.NegativeKeywordsForBatchJob\uFF0C\u4E0D\u5E94\u91CD\u590D\u5199\u5165\u6B63\u5411 KeywordsForBatchJob\uFF1B\u5426\u8BCD\u53EA\u653E NegativeKeywordsForBatchJob`
111740
110776
  );
111741
110777
  continue;
111742
110778
  }
111743
110779
  if (looksLikeStandaloneNegativeKeyword(core)) {
111744
110780
  warnings.push(
111745
- `${path32} \u8BCD\u9762\u300C${raw}\u300D\u7591\u4F3C\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u5E38\u89C1\u5426\u8BCD\u8BCD\u6839\uFF09\uFF0C\u5E94\u5199\u5165 campaign.NegativeKeywordsForBatchJob\uFF08MatchTypeV2: BROAD\uFF09\uFF0C\u52FF\u653E\u5165 KeywordsForBatchJob`
110781
+ `${path34} \u8BCD\u9762\u300C${raw}\u300D\u7591\u4F3C\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u5E38\u89C1\u5426\u8BCD\u8BCD\u6839\uFF09\uFF0C\u5E94\u5199\u5165 campaign.NegativeKeywordsForBatchJob\uFF08MatchTypeV2: BROAD\uFF09\uFF0C\u52FF\u653E\u5165 KeywordsForBatchJob`
111746
110782
  );
111747
110783
  }
111748
110784
  }
@@ -114768,12 +113804,12 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
114768
113804
  for (let ki = 0; ki < texts.length; ki++) {
114769
113805
  const t = texts[ki];
114770
113806
  if (typeof t !== "string" || !t.trim()) continue;
114771
- const path32 = `${groupPath}.KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
113807
+ const path34 = `${groupPath}.KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
114772
113808
  const key = keywordKey(t, matchTypeV2);
114773
113809
  if (!liveKwKeys.has(key)) {
114774
113810
  missing.push({
114775
113811
  layer: "keyword",
114776
- path: path32,
113812
+ path: path34,
114777
113813
  adGroupName: groupName,
114778
113814
  plannedContent: `\u8BCD\u9762: ${t} | \u5339\u914D: ${matchTypeV2}`,
114779
113815
  liveNote: `\u540C\u7EC4\u5DF2\u6709 ${liveKwInGroup.length} \u6761\u5173\u952E\u8BCD\uFF0C\u65E0\u952E ${key}`,
@@ -114791,12 +113827,12 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
114791
113827
  for (let ai = 0; ai < ads.length; ai++) {
114792
113828
  const ad = asRecord2(ads[ai]);
114793
113829
  if (!ad) continue;
114794
- const path32 = `${groupPath}.AdsForBatchJob[${ai}]`;
113830
+ const path34 = `${groupPath}.AdsForBatchJob[${ai}]`;
114795
113831
  const primary = pickString(ad["headlinePart1"], ad["AdTitle"]);
114796
113832
  if (!primary) {
114797
113833
  missing.push({
114798
113834
  layer: "ad",
114799
- path: path32,
113835
+ path: path34,
114800
113836
  adGroupName: groupName,
114801
113837
  plannedContent: `AdsForBatchJob[${ai}]\uFF08\u7F3A\u5C11 headlinePart1\uFF0C\u65E0\u6CD5\u6BD4\u5BF9\uFF09`,
114802
113838
  liveNote: `\u540C\u7EC4\u5DF2\u6709 ${liveAdsInGroup.length} \u6761\u521B\u610F`,
@@ -114809,7 +113845,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
114809
113845
  if (!found) {
114810
113846
  missing.push({
114811
113847
  layer: "ad",
114812
- path: path32,
113848
+ path: path34,
114813
113849
  adGroupName: groupName,
114814
113850
  plannedContent: `RSA \u9996\u6807\u9898: ${primary}${finalUrl ? ` | \u843D\u5730\u9875: ${finalUrl}` : ""}`,
114815
113851
  liveNote: `\u540C\u7EC4\u5DF2\u6709 ${liveAdsInGroup.length} \u6761 RSA\uFF0C\u65E0\u6B64\u9996\u6807\u9898`,
@@ -116056,8 +115092,8 @@ function pmaxChannelTypesUrl(googleApiUrl) {
116056
115092
  }
116057
115093
  function pmaxCampaignUrl(googleApiUrl, accountId, campaignId) {
116058
115094
  const base = googleApiUrl.replace(/\/$/, "");
116059
- const path32 = `${base}/accounts/${accountId}/campaign/pmax`;
116060
- return campaignId ? `${path32}/${campaignId}` : path32;
115095
+ const path34 = `${base}/accounts/${accountId}/campaign/pmax`;
115096
+ return campaignId ? `${path34}/${campaignId}` : path34;
116061
115097
  }
116062
115098
  function pmaxBrandAssetsUrl(googleApiUrl, accountId, campaignId) {
116063
115099
  return `${pmaxCampaignUrl(googleApiUrl, accountId, campaignId)}/brand-assets`;
@@ -116067,8 +115103,8 @@ function pmaxBrandGuidelinesEnableUrl(googleApiUrl, accountId, campaignId) {
116067
115103
  }
116068
115104
  function pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, suffix) {
116069
115105
  const base = googleApiUrl.replace(/\/$/, "");
116070
- const path32 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
116071
- return suffix ? `${path32}/${suffix.replace(/^\//, "")}` : path32;
115106
+ const path34 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
115107
+ return suffix ? `${path34}/${suffix.replace(/^\//, "")}` : path34;
116072
115108
  }
116073
115109
  function pmaxCampaignAssetGroupUrl(googleApiUrl, accountId, campaignId) {
116074
115110
  const base = googleApiUrl.replace(/\/$/, "");
@@ -118723,8 +117759,8 @@ async function runAdPmaxImageConvert(opts) {
118723
117759
  }
118724
117760
 
118725
117761
  // src/commands/ad/pmax-brand-mgmt.ts
118726
- function loadBrandConfig(path32, accountId, campaignId) {
118727
- const cfg = loadPmaxJsonFile(path32);
117762
+ function loadBrandConfig(path34, accountId, campaignId) {
117763
+ const cfg = loadPmaxJsonFile(path34);
118728
117764
  return {
118729
117765
  ...cfg,
118730
117766
  account: String(cfg["account"] ?? accountId).trim(),
@@ -121236,7 +120272,9 @@ function assertGoogleMediaCustomerId(raw) {
121236
120272
  process.exit(1);
121237
120273
  }
121238
120274
  if (!/^\d+$/.test(id)) {
121239
- console.error("\n\u274C --account \u987B\u4E3A Google mediaCustomerId\uFF08\u7EAF\u6570\u5B57\uFF0C\u4E0E list-accounts -m Google \u4E00\u81F4\uFF09\u3002\n");
120275
+ console.error(
120276
+ "\n\u274C --account \u987B\u4E3A Google mediaCustomerId\uFF08\u7EAF\u6570\u5B57\uFF0C\u4E0E list-accounts -m Google \u4E00\u81F4\uFF09\u3002\n"
120277
+ );
121240
120278
  process.exit(1);
121241
120279
  }
121242
120280
  return id;
@@ -121344,11 +120382,7 @@ async function fetchGoogleCustomerAccessPermission(config, mediaCustomerId, verb
121344
120382
  async function runAccountCheckAccess(opts) {
121345
120383
  const mediaCustomerId = assertGoogleMediaCustomerId(opts.account);
121346
120384
  const config = loadConfig(opts.token);
121347
- const payload = await fetchGoogleCustomerAccessPermission(
121348
- config,
121349
- mediaCustomerId,
121350
- opts.verbose
121351
- );
120385
+ const payload = await fetchGoogleCustomerAccessPermission(config, mediaCustomerId, opts.verbose);
121352
120386
  if (await emitCliJsonOrSnapshot(opts, {
121353
120387
  section: "account-check-access",
121354
120388
  commandLabel: "account check-access",
@@ -125166,9 +124200,7 @@ function countCompetitorMentions(html) {
125166
124200
  }
125167
124201
  }
125168
124202
  const globalPlain = stripHtmlTags(html);
125169
- const named = globalPlain.match(
125170
- /(?:竞品|竞争对手)[::][^\n。;;]{10,400}/
125171
- )?.[0] ?? "";
124203
+ const named = globalPlain.match(/(?:竞品|竞争对手)[::][^\n。;;]{10,400}/)?.[0] ?? "";
125172
124204
  if (named) {
125173
124205
  const parts = named.replace(/^[^::]+[::]/, "").split(/[、,,;;|]/).map((s) => s.trim()).filter((s) => s.length >= 2);
125174
124206
  if (parts.length >= 3) return parts.length;
@@ -125755,12 +124787,16 @@ function sectionRecord(env) {
125755
124787
  }
125756
124788
 
125757
124789
  // src/commands/google-ads-diagnosis/build-report-data.ts
125758
- function num2(value) {
124790
+ function num(value) {
125759
124791
  const n = Number(value);
125760
124792
  return Number.isFinite(n) ? n : 0;
125761
124793
  }
125762
124794
  function rowCost(row) {
125763
- return num2(row.cost ?? row.spend);
124795
+ return num(row.cost ?? row.spend);
124796
+ }
124797
+ function cvrOf(row, clicks) {
124798
+ if (clicks > 0) return num(row.conversions) / clicks;
124799
+ return num(row.conversionRate ?? row.cvr);
125764
124800
  }
125765
124801
  function pct(value) {
125766
124802
  return `${(value * 100).toFixed(2)}%`;
@@ -125791,11 +124827,11 @@ function asPeriodRecord(value) {
125791
124827
  }
125792
124828
  function toKeyMetricRow(title, current, previous) {
125793
124829
  const currentCost = rowCost(current);
125794
- const currentClicks = num2(current.clicks);
125795
- const currentCvr = num2(current.conversionRate ?? current.cvr);
124830
+ const currentClicks = num(current.clicks);
124831
+ const currentCvr = cvrOf(current, currentClicks);
125796
124832
  const previousCost = previous ? rowCost(previous) : null;
125797
- const previousClicks = previous ? num2(previous.clicks) : null;
125798
- const previousCvr = previous ? num2(previous.conversionRate ?? previous.cvr) : null;
124833
+ const previousClicks = previous ? num(previous.clicks) : null;
124834
+ const previousCvr = previous ? cvrOf(previous, num(previous.clicks)) : null;
125799
124835
  return {
125800
124836
  title,
125801
124837
  currentCost,
@@ -125825,13 +124861,13 @@ function buildComparisonItems(currentRows, previousRows, titleFn, keyFn) {
125825
124861
  }
125826
124862
  function toTargetingItem(row, labelKey, label) {
125827
124863
  const cost = rowCost(row);
125828
- const clicks = num2(row.clicks);
125829
- const impressions = num2(row.impressions);
125830
- const conversions = num2(row.conversions);
125831
- const ctr = num2(row.ctr);
125832
- const cvr = num2(row.conversionRate ?? row.cvr);
125833
- const cpc = num2(row.averageCpc ?? row.cpc);
125834
- const cpa = conversions > 0 ? cost / conversions : num2(row.costPerConversion ?? row.cpa);
124864
+ const clicks = num(row.clicks);
124865
+ const impressions = num(row.impressions);
124866
+ const conversions = num(row.conversions);
124867
+ const ctr = num(row.ctr);
124868
+ const cvr = cvrOf(row, clicks);
124869
+ const cpc = num(row.averageCpc ?? row.cpc);
124870
+ const cpa = conversions > 0 ? cost / conversions : num(row.costPerConversion ?? row.cpa);
125835
124871
  return {
125836
124872
  [labelKey]: label,
125837
124873
  cost,
@@ -125846,11 +124882,11 @@ function toTargetingItem(row, labelKey, label) {
125846
124882
  }
125847
124883
  function toDailyConversionItem(row) {
125848
124884
  const cost = rowCost(row);
125849
- const conversions = num2(row.conversions);
124885
+ const conversions = num(row.conversions);
125850
124886
  const rawDate = String(row.date ?? "");
125851
124887
  const date = rawDate.includes("T") ? rawDate.slice(0, 10) : rawDate.slice(0, 10);
125852
- const cpa = conversions > 0 ? cost / conversions : num2(row.costPerConversion ?? row.cpa);
125853
- return { date, cost, conversions, cpa, clicks: num2(row.clicks), impressions: num2(row.impressions) };
124888
+ const cpa = conversions > 0 ? cost / conversions : num(row.costPerConversion ?? row.cpa);
124889
+ return { date, cost, conversions, cpa, clicks: num(row.clicks), impressions: num(row.impressions) };
125854
124890
  }
125855
124891
  function loadCompanyNameFromSnap(snapDir) {
125856
124892
  const file = path21.join(snapDir, "list-accounts-google.json");
@@ -125887,8 +124923,8 @@ function inferBusinessProfile(campaignRows, adRows, website) {
125887
124923
  };
125888
124924
  }
125889
124925
  function primaryConversionLabel(rows) {
125890
- const sorted = [...rows].sort((a, b) => num2(b.allConversions) - num2(a.allConversions));
125891
- const top = sorted.find((r) => num2(r.allConversions) > 0 && String(r.status) === "Enabled");
124926
+ const sorted = [...rows].sort((a, b) => num(b.allConversions) - num(a.allConversions));
124927
+ const top = sorted.find((r) => num(r.allConversions) > 0 && String(r.status) === "Enabled");
125892
124928
  if (top) return String(top.name ?? "\u8BE2\u76D8/\u8F6C\u5316");
125893
124929
  const enabled = rows.find((r) => String(r.status) === "Enabled");
125894
124930
  return enabled ? String(enabled.name ?? "\u8BE2\u76D8/\u8F6C\u5316") : "\u8BE2\u76D8/\u8F6C\u5316";
@@ -125907,7 +124943,7 @@ function buildBiddingStrategyItems(campaignRows, endDate, gold) {
125907
124943
  const strategy = String(row.biddingStrategyTypeV2 ?? row.biddingStrategyType ?? "UNKNOWN");
125908
124944
  const status = String(row.campaignStatus ?? row.campaignStatusV2 ?? "");
125909
124945
  const isPaused = /paused/i.test(status);
125910
- const recommended = num2(row.conversions) > 0 ? "MAXIMIZE_CONVERSIONS" : "TARGET_SPEND";
124946
+ const recommended = num(row.conversions) > 0 ? "MAXIMIZE_CONVERSIONS" : "TARGET_SPEND";
125911
124947
  const isCorrect = policyOk && !isPaused && strategy === recommended;
125912
124948
  return {
125913
124949
  campaignName: String(row.campaignName ?? "\u672A\u547D\u540D\u7CFB\u5217"),
@@ -125920,16 +124956,16 @@ function buildBiddingStrategyItems(campaignRows, endDate, gold) {
125920
124956
  });
125921
124957
  }
125922
124958
  function buildStructureCounts(resourceRecord, campaignRows, keywordRows, searchRows) {
125923
- const campaignCount = Math.max(num2(resourceRecord?.campaignCount), campaignRows.length);
125924
- const keywordCount = Math.max(num2(resourceRecord?.keywordCount), keywordRows.length);
124959
+ const campaignCount = Math.max(num(resourceRecord?.campaignCount), campaignRows.length);
124960
+ const keywordCount = Math.max(num(resourceRecord?.keywordCount), keywordRows.length);
125925
124961
  return {
125926
124962
  campaignCount,
125927
- adGroupCount: num2(resourceRecord?.adGroupCount),
124963
+ adGroupCount: num(resourceRecord?.adGroupCount),
125928
124964
  keywordCount,
125929
- adCount: num2(resourceRecord?.adCount),
125930
- extensionCount: num2(resourceRecord?.extensionCount),
125931
- negativeKeywordCount: num2(resourceRecord?.negativeKeywordCount),
125932
- countriesWithConversionsCount: num2(resourceRecord?.countriesWithConversionsCount),
124965
+ adCount: num(resourceRecord?.adCount),
124966
+ extensionCount: num(resourceRecord?.extensionCount),
124967
+ negativeKeywordCount: num(resourceRecord?.negativeKeywordCount),
124968
+ countriesWithConversionsCount: num(resourceRecord?.countriesWithConversionsCount),
125933
124969
  searchTermCount: searchRows.length
125934
124970
  };
125935
124971
  }
@@ -125943,26 +124979,26 @@ function loadWebsiteFromAds(snapDir, accountId, start, end) {
125943
124979
  }
125944
124980
  function buildKeywordShareItems(rows) {
125945
124981
  const totalCost = rows.reduce((sum, row) => sum + rowCost(row), 0);
125946
- const totalConversions = rows.reduce((sum, row) => sum + num2(row.conversions), 0);
124982
+ const totalConversions = rows.reduce((sum, row) => sum + num(row.conversions), 0);
125947
124983
  return rows.map((row) => {
125948
124984
  const cost = rowCost(row);
125949
- const conversions = num2(row.conversions);
124985
+ const conversions = num(row.conversions);
125950
124986
  const keywordText = String(row.keywordText ?? row.keyword ?? "").trim();
125951
124987
  return {
125952
124988
  keyword: keywordText || "\u672A\u547D\u540D",
125953
- cpc: num2(row.averageCpc ?? row.cpc),
124989
+ cpc: num(row.averageCpc ?? row.cpc),
125954
124990
  matchType: String(
125955
124991
  row.keywordMatchTypeZh ?? row.keywordMatchType ?? row.matchType ?? ""
125956
124992
  ),
125957
- impressions: num2(row.impressions),
125958
- clicks: num2(row.clicks),
124993
+ impressions: num(row.impressions),
124994
+ clicks: num(row.clicks),
125959
124995
  cost,
125960
124996
  conversions,
125961
124997
  costShare: totalCost > 0 ? cost / totalCost * 100 : 0,
125962
124998
  conversionsShare: totalConversions > 0 ? conversions / totalConversions * 100 : 0,
125963
124999
  cpa: conversions > 0 ? cost / conversions : 0,
125964
- ctr: num2(row.ctr),
125965
- cvr: num2(row.conversionRate ?? row.cvr)
125000
+ ctr: num(row.ctr),
125001
+ cvr: cvrOf(row, num(row.clicks))
125966
125002
  };
125967
125003
  });
125968
125004
  }
@@ -125970,17 +125006,17 @@ function buildSearchTermItems(rows) {
125970
125006
  const sorted = [...rows].sort((a, b) => rowCost(b) - rowCost(a));
125971
125007
  return sorted.slice(0, 50).map((row) => {
125972
125008
  const cost = rowCost(row);
125973
- const conversions = num2(row.conversions);
125974
- const cpa = conversions > 0 ? num2(row.costPerConversion) || cost / conversions : void 0;
125009
+ const conversions = num(row.conversions);
125010
+ const cpa = conversions > 0 ? num(row.costPerConversion) || cost / conversions : void 0;
125975
125011
  return {
125976
125012
  searchTerm: String(row.searchTermText ?? row.searchTerm ?? "\u672A\u547D\u540D"),
125977
125013
  keyword: String(row.keywordText ?? row.keyword ?? ""),
125978
125014
  cost,
125979
125015
  conversions,
125980
- clicks: num2(row.clicks),
125981
- impressions: num2(row.impressions),
125982
- ctr: num2(row.ctr),
125983
- cvr: num2(row.conversionRate ?? row.cvr),
125016
+ clicks: num(row.clicks),
125017
+ impressions: num(row.impressions),
125018
+ ctr: num(row.ctr),
125019
+ cvr: cvrOf(row, num(row.clicks)),
125984
125020
  cpa
125985
125021
  };
125986
125022
  });
@@ -126022,18 +125058,18 @@ function buildAdCreativeItems(rows) {
126022
125058
  }
126023
125059
  function buildBudgetCompetitiveness(overviewRecord, dimensionRecord) {
126024
125060
  const current = asPeriodRecord(overviewRecord?.currentPeriod);
126025
- const totalCost = num2(overviewRecord?.totalCost ?? current?.spend);
126026
- const activeDays = num2(overviewRecord?.activeDays);
126027
- const avgDaily = num2(overviewRecord?.averageDailyCost ?? (activeDays > 0 ? totalCost / activeDays : 0));
126028
- const remainingBudget = num2(overviewRecord?.remainingAccountBudget);
125061
+ const totalCost = num(overviewRecord?.totalCost ?? current?.spend);
125062
+ const activeDays = num(overviewRecord?.activeDays);
125063
+ const avgDaily = num(overviewRecord?.averageDailyCost ?? (activeDays > 0 ? totalCost / activeDays : 0));
125064
+ const remainingBudget = num(overviewRecord?.remainingAccountBudget);
126029
125065
  const utilization = remainingBudget > 0 && avgDaily > 0 ? Math.min(100, avgDaily / (avgDaily + remainingBudget / Math.max(activeDays, 1)) * 100) : avgDaily > 0 ? 85 : 0;
126030
- const sis = num2(
125066
+ const sis = num(
126031
125067
  dimensionRecord?.searchImpressionShare ?? current?.searchImpressionShare
126032
125068
  );
126033
- const budgetLost = num2(
125069
+ const budgetLost = num(
126034
125070
  dimensionRecord?.searchBudgetLostImpressionShare ?? current?.searchBudgetLostImpressionShare
126035
125071
  );
126036
- const rankLost = num2(
125072
+ const rankLost = num(
126037
125073
  dimensionRecord?.searchRankLostImpressionShare ?? current?.searchRankLostImpressionShare
126038
125074
  );
126039
125075
  return [
@@ -126119,17 +125155,17 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
126119
125155
  };
126120
125156
  data.reportDate = end;
126121
125157
  const cost = rowCost(currentPeriod ?? overviewRecord ?? {});
126122
- const impressions = num2(currentPeriod?.impressions);
126123
- const clicks = num2(currentPeriod?.clicks);
126124
- const conversions = num2(currentPeriod?.conversions);
126125
- const ctr = num2(currentPeriod?.ctr);
126126
- const cvr = num2(currentPeriod?.conversionRate ?? currentPeriod?.cvr);
126127
- const cpa = conversions > 0 ? cost / conversions : num2(currentPeriod?.costPerConversion);
125158
+ const impressions = num(currentPeriod?.impressions);
125159
+ const clicks = num(currentPeriod?.clicks);
125160
+ const conversions = num(currentPeriod?.conversions);
125161
+ const ctr = num(currentPeriod?.ctr);
125162
+ const cvr = cvrOf(currentPeriod ?? {}, clicks);
125163
+ const cpa = conversions > 0 ? cost / conversions : num(currentPeriod?.costPerConversion);
126128
125164
  const prevCost = previousPeriod ? rowCost(previousPeriod) : 0;
126129
- const prevConversions = previousPeriod ? num2(previousPeriod.conversions) : 0;
126130
- const prevCtr = previousPeriod ? num2(previousPeriod.ctr) : 0;
126131
- const prevCvr = previousPeriod ? num2(previousPeriod.conversionRate ?? previousPeriod.cvr) : 0;
126132
- const prevCpa = prevConversions > 0 ? rowCost(previousPeriod) / prevConversions : num2(previousPeriod?.costPerConversion);
125165
+ const prevConversions = previousPeriod ? num(previousPeriod.conversions) : 0;
125166
+ const prevCtr = previousPeriod ? num(previousPeriod.ctr) : 0;
125167
+ const prevCvr = previousPeriod ? cvrOf(previousPeriod, num(previousPeriod.clicks)) : 0;
125168
+ const prevCpa = prevConversions > 0 ? rowCost(previousPeriod) / prevConversions : num(previousPeriod?.costPerConversion);
126133
125169
  data.metrics = {
126134
125170
  cost,
126135
125171
  impressions,
@@ -126165,8 +125201,8 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
126165
125201
  items: conversionRows.map((row) => ({
126166
125202
  name: String(row.name ?? row.eventName ?? "\u672A\u77E5\u4E8B\u4EF6"),
126167
125203
  status: String(row.status ?? ""),
126168
- allConversions: num2(row.allConversions),
126169
- allConversionsValue: num2(row.allConversionsValue)
125204
+ allConversions: num(row.allConversions),
125205
+ allConversionsValue: num(row.allConversionsValue)
126170
125206
  }))
126171
125207
  };
126172
125208
  const campaignPrevious = sectionItems(
@@ -126250,16 +125286,16 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
126250
125286
  const lighthouse = opts.landingPage ?? {};
126251
125287
  data.landingPageAnalysis = {
126252
125288
  desktop: {
126253
- score: num2(lighthouse.desktop?.score),
126254
- firstContentfulPaint: num2(lighthouse.desktop?.firstContentfulPaint),
126255
- firstMeaningfulPaint: num2(lighthouse.desktop?.firstMeaningfulPaint),
126256
- speedIndex: num2(lighthouse.desktop?.speedIndex)
125289
+ score: num(lighthouse.desktop?.score),
125290
+ firstContentfulPaint: num(lighthouse.desktop?.firstContentfulPaint),
125291
+ firstMeaningfulPaint: num(lighthouse.desktop?.firstMeaningfulPaint),
125292
+ speedIndex: num(lighthouse.desktop?.speedIndex)
126257
125293
  },
126258
125294
  mobile: {
126259
- score: num2(lighthouse.mobile?.score),
126260
- firstContentfulPaint: num2(lighthouse.mobile?.firstContentfulPaint),
126261
- firstMeaningfulPaint: num2(lighthouse.mobile?.firstMeaningfulPaint),
126262
- speedIndex: num2(lighthouse.mobile?.speedIndex)
125295
+ score: num(lighthouse.mobile?.score),
125296
+ firstContentfulPaint: num(lighthouse.mobile?.firstContentfulPaint),
125297
+ firstMeaningfulPaint: num(lighthouse.mobile?.firstMeaningfulPaint),
125298
+ speedIndex: num(lighthouse.mobile?.speedIndex)
126263
125299
  }
126264
125300
  };
126265
125301
  const keywordShareItems = buildKeywordShareItems(kwCurrent);
@@ -126293,7 +125329,7 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
126293
125329
  data.newFeatures = {
126294
125330
  items: nfItems.map((item) => {
126295
125331
  const strategy = String(item.strategy ?? "");
126296
- const enabled = strategy === "PerformanceMax" ? num2(campaignTypes?.PerformanceMax) > 0 : strategy === "KwMatch" ? num2(campaignTypes?.Search) > 0 : strategy === "DemandGen" ? num2(campaignTypes?.DemandGen) > 0 : strategy === "Youtube" ? num2(campaignTypes?.Video) > 0 || num2(campaignTypes?.Youtube) > 0 : false;
125332
+ const enabled = strategy === "PerformanceMax" ? num(campaignTypes?.PerformanceMax) > 0 : strategy === "KwMatch" ? num(campaignTypes?.Search) > 0 : strategy === "DemandGen" ? num(campaignTypes?.DemandGen) > 0 : strategy === "Youtube" ? num(campaignTypes?.Video) > 0 || num(campaignTypes?.Youtube) > 0 : false;
126297
125333
  return {
126298
125334
  ...item,
126299
125335
  accountStatus: enabled,
@@ -126303,7 +125339,7 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
126303
125339
  ...emptyModuleNarrative()
126304
125340
  };
126305
125341
  data.summary = emptySummary();
126306
- const zeroConvKw = kwCurrent.filter((r) => rowCost(r) > 50 && num2(r.conversions) === 0);
125342
+ const zeroConvKw = kwCurrent.filter((r) => rowCost(r) > 50 && num(r.conversions) === 0);
126307
125343
  const agentBrief = buildGoogleAdsDiagnosisAgentBrief({
126308
125344
  accountId,
126309
125345
  period: formatPeriod(start, end),
@@ -126327,9 +125363,9 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
126327
125363
  prevCpa
126328
125364
  },
126329
125365
  dimension: {
126330
- searchImpressionShare: num2(dimensionRecord?.searchImpressionShare),
126331
- searchBudgetLostImpressionShare: num2(dimensionRecord?.searchBudgetLostImpressionShare),
126332
- searchRankLostImpressionShare: num2(dimensionRecord?.searchRankLostImpressionShare)
125366
+ searchImpressionShare: num(dimensionRecord?.searchImpressionShare),
125367
+ searchBudgetLostImpressionShare: num(dimensionRecord?.searchBudgetLostImpressionShare),
125368
+ searchRankLostImpressionShare: num(dimensionRecord?.searchRankLostImpressionShare)
126333
125369
  },
126334
125370
  structure: structureCounts,
126335
125371
  goldAccount: goldRecord,
@@ -126537,9 +125573,9 @@ function asRecord5(value) {
126537
125573
  }
126538
125574
  return null;
126539
125575
  }
126540
- function getNestedValue(root, path32) {
125576
+ function getNestedValue(root, path34) {
126541
125577
  let cur = root;
126542
- for (const key of path32) {
125578
+ for (const key of path34) {
126543
125579
  const rec = asRecord5(cur);
126544
125580
  if (!rec) return void 0;
126545
125581
  cur = rec[key];
@@ -126719,6 +125755,35 @@ function getFactItemsErrors(data) {
126719
125755
  }
126720
125756
  return errors;
126721
125757
  }
125758
+ var GOOGLE_ADS_DIAGNOSIS_LANDING_PAGE_MODULE = "landingPageAnalysis";
125759
+ var LANDING_PAGE_SPEED_KEYS = [
125760
+ "score",
125761
+ "firstContentfulPaint",
125762
+ "firstMeaningfulPaint",
125763
+ "speedIndex"
125764
+ ];
125765
+ function isLandingPageSpeedValid(value) {
125766
+ const rec = asRecord6(value);
125767
+ if (!rec) return false;
125768
+ return LANDING_PAGE_SPEED_KEYS.every(
125769
+ (key) => typeof rec[key] === "number" && Number.isFinite(rec[key])
125770
+ );
125771
+ }
125772
+ function restoreLandingPageSpeedFromCollect(finalData, collectReportData) {
125773
+ const finalBlock = asRecord6(finalData[GOOGLE_ADS_DIAGNOSIS_LANDING_PAGE_MODULE]);
125774
+ const collectBlock = asRecord6(collectReportData[GOOGLE_ADS_DIAGNOSIS_LANDING_PAGE_MODULE]);
125775
+ if (!collectBlock) return { data: finalData, restored: false };
125776
+ const needsDesktop = !isLandingPageSpeedValid(finalBlock?.desktop) && isLandingPageSpeedValid(collectBlock.desktop);
125777
+ const needsMobile = !isLandingPageSpeedValid(finalBlock?.mobile) && isLandingPageSpeedValid(collectBlock.mobile);
125778
+ if (!needsDesktop && !needsMobile) return { data: finalData, restored: false };
125779
+ const data = { ...finalData };
125780
+ data[GOOGLE_ADS_DIAGNOSIS_LANDING_PAGE_MODULE] = {
125781
+ ...finalBlock ?? {},
125782
+ desktop: needsDesktop ? collectBlock.desktop : finalBlock?.desktop,
125783
+ mobile: needsMobile ? collectBlock.mobile : finalBlock?.mobile
125784
+ };
125785
+ return { data, restored: true };
125786
+ }
126722
125787
  function mergeComparisonItemsFromCollect(finalData, collectReportData) {
126723
125788
  const data = { ...finalData };
126724
125789
  const restoredModules = [];
@@ -126739,8 +125804,74 @@ function mergeComparisonItemsFromCollect(finalData, collectReportData) {
126739
125804
  return { data, restoredModules };
126740
125805
  }
126741
125806
 
126742
- // src/commands/google-ads-diagnosis/render-report.ts
125807
+ // src/commands/google-ads-diagnosis/narrative-consistency-check.ts
126743
125808
  function asRecord7(value) {
125809
+ if (value && typeof value === "object" && !Array.isArray(value)) {
125810
+ return value;
125811
+ }
125812
+ return null;
125813
+ }
125814
+ function collectNarrativeTexts(data) {
125815
+ const texts = [];
125816
+ const summary = asRecord7(data.summary);
125817
+ if (Array.isArray(summary?.keyIssues)) {
125818
+ for (const item of summary.keyIssues) texts.push(String(item ?? ""));
125819
+ }
125820
+ const overview = asRecord7(data.diagnosisOverview);
125821
+ if (Array.isArray(overview?.disadvantages)) {
125822
+ for (const item of overview.disadvantages) {
125823
+ const rec = asRecord7(item);
125824
+ if (rec) {
125825
+ texts.push(String(rec.title ?? ""), String(rec.description ?? ""));
125826
+ } else {
125827
+ texts.push(String(item ?? ""));
125828
+ }
125829
+ }
125830
+ }
125831
+ return texts.filter((t) => t.trim().length > 0);
125832
+ }
125833
+ function newFeatureAccountStatus(data, strategy) {
125834
+ const nf = asRecord7(data.newFeatures);
125835
+ const items = nf?.items;
125836
+ if (!Array.isArray(items)) return null;
125837
+ const row = items.find((it) => asRecord7(it)?.strategy === strategy);
125838
+ const rec = asRecord7(row);
125839
+ if (!rec || typeof rec.accountStatus !== "boolean") return null;
125840
+ return rec.accountStatus;
125841
+ }
125842
+ function goldAccountFlag(data, key) {
125843
+ const gold = asRecord7(data.goldAccount);
125844
+ const value = gold?.[key];
125845
+ return typeof value === "boolean" ? value : null;
125846
+ }
125847
+ var NARRATIVE_CONSISTENCY_RULES = [
125848
+ {
125849
+ id: "keyword-strategy-missing",
125850
+ textPattern: /关键词.{0,8}(完全缺失|策略缺失|从未投放|从未做过|一直没有|尚未开启)/,
125851
+ isContradicted: (data) => newFeatureAccountStatus(data, "KwMatch") === true,
125852
+ hint: 'newFeatures \u91CC KwMatch.accountStatus=true\uFF0C\u8BF4\u660E\u8D26\u6237\u5386\u53F2\u4E0A\u6295\u653E\u8FC7 Search/\u5173\u952E\u8BCD\u5E7F\u6CDB\u5339\u914D\uFF08\u53EA\u662F\u5F53\u524D\u7CFB\u5217\u5DF2\u6682\u505C\uFF09\uFF0C\u4E0D\u662F"\u4ECE\u672A\u6295\u653E"\u3002\u8BF7\u6838\u5B9E\u5386\u53F2\u7CFB\u5217\uFF08channelTypeV2=SEARCH \u4E14\u5DF2 Paused\uFF09\u540E\u6539\u5199\u4E3A"\u5173\u952E\u8BCD\u7B56\u7565\u5DF2\u88AB\u6401\u7F6E/\u8FC7\u5EA6\u4F9D\u8D56 PMax \u7B49\u5176\u4ED6\u7CFB\u5217\u7C7B\u578B"\uFF0C\u5E76\u7ED9\u51FA\u5177\u4F53\u5386\u53F2\u7CFB\u5217\u540D\u79F0\u4E0E\u82B1\u8D39/\u8F6C\u5316\u6570\u636E\u3002'
125853
+ },
125854
+ {
125855
+ id: "conversion-tracking-incomplete",
125856
+ textPattern: /转化跟踪.{0,8}(不完整|不完善|未完善|不到位|缺失)/,
125857
+ isContradicted: (data) => goldAccountFlag(data, "conversionTrackingQualified") === true,
125858
+ hint: "goldAccount.conversionTrackingQualified=true \u8868\u793A\u8BE5\u9879\u5DF2\u8FBE\u6807\uFF0C\u4E0E\u6587\u6848\u7ED3\u8BBA\u76F8\u53CD\uFF08true=\u5DF2\u8FBE\u6807\u3001false=\u672A\u8FBE\u6807\uFF0C\u4E0D\u53EF\u98A0\u5012\uFF09\u3002\u8BF7\u91CD\u65B0\u6838\u5BF9\u5177\u4F53\u662F\u54EA\u4E2A\u8F6C\u5316\u52A8\u4F5C/\u54EA\u4E2A\u5E73\u53F0\u7684\u95EE\u9898\uFF0C\u6216\u5220\u9664\u8BE5\u6761\u7ED3\u8BBA\u3002"
125859
+ }
125860
+ ];
125861
+ function getNarrativeConsistencyErrors(data) {
125862
+ const texts = collectNarrativeTexts(data);
125863
+ const errors = [];
125864
+ for (const rule of NARRATIVE_CONSISTENCY_RULES) {
125865
+ const hitText = texts.find((t) => rule.textPattern.test(t));
125866
+ if (hitText && rule.isContradicted(data)) {
125867
+ errors.push(`\u53D9\u4E8B\u77DB\u76FE\uFF08${rule.id}\uFF09\uFF1A\u300C${hitText}\u300D\u2014\u2014 ${rule.hint}`);
125868
+ }
125869
+ }
125870
+ return errors;
125871
+ }
125872
+
125873
+ // src/commands/google-ads-diagnosis/render-report.ts
125874
+ function asRecord8(value) {
126744
125875
  if (value && typeof value === "object" && !Array.isArray(value)) {
126745
125876
  return value;
126746
125877
  }
@@ -126749,8 +125880,8 @@ function asRecord7(value) {
126749
125880
  async function tryLoadCollectReportData(collectPath) {
126750
125881
  try {
126751
125882
  const raw = JSON.parse(await fs18.readFile(collectPath, "utf8"));
126752
- const root = asRecord7(raw);
126753
- const reportData = asRecord7(root?.reportData);
125883
+ const root = asRecord8(raw);
125884
+ const reportData = asRecord8(root?.reportData);
126754
125885
  return reportData ?? root;
126755
125886
  } catch {
126756
125887
  return null;
@@ -126767,7 +125898,7 @@ async function runGoogleAdsDiagnosisRender(opts) {
126767
125898
  `);
126768
125899
  process.exit(1);
126769
125900
  }
126770
- let data = asRecord7(dataRaw);
125901
+ let data = asRecord8(dataRaw);
126771
125902
  if (!opts.noMergeCollect) {
126772
125903
  const collectPath = path23.resolve(
126773
125904
  opts.collectFile ?? path23.join(path23.dirname(dataPath), GOOGLE_ADS_DIAGNOSIS_COLLECT_BASENAME)
@@ -126784,11 +125915,32 @@ async function runGoogleAdsDiagnosisRender(opts) {
126784
125915
  `
126785
125916
  \u2139\uFE0F \u5DF2\u4ECE collect \u6062\u590D\u4E8B\u5B9E items\uFF08${restoredModules.join("\u3001")}\uFF09\uFF1BAgent \u53EA\u5E94\u586B\u5199 analysis/suggestions\uFF0C\u52FF\u6539\u5199 items \u884C\uFF08\u542B biddingStrategy.duration \u5929\u6570\uFF09\u3002
126786
125917
  \u6765\u6E90\uFF1A${collectPath}
125918
+ `
125919
+ );
125920
+ }
125921
+ const { data: withLandingPage, restored: landingPageRestored } = restoreLandingPageSpeedFromCollect(data, collectReportData);
125922
+ data = withLandingPage;
125923
+ if (landingPageRestored) {
125924
+ console.log(
125925
+ `
125926
+ \u2139\uFE0F \u5DF2\u4ECE collect \u6062\u590D landingPageAnalysis.desktop/mobile \u6D4B\u901F\u6570\u636E\uFF08Agent \u53EA\u5E94\u8FFD\u52A0 analysis/suggestions\uFF0C\u7981\u6B62\u6574\u4F53\u8986\u76D6\u8BE5\u5BF9\u8C61\uFF09\u3002
125927
+ \u6765\u6E90\uFF1A${collectPath}
126787
125928
  `
126788
125929
  );
126789
125930
  }
126790
125931
  }
126791
125932
  }
125933
+ const narrativeErrors = getNarrativeConsistencyErrors(data);
125934
+ if (narrativeErrors.length > 0) {
125935
+ console.error("\n\u274C \u53D9\u4E8B\u6587\u6848\u4E0E\u4E8B\u5B9E\u5B57\u6BB5\u77DB\u76FE\uFF0C\u7981\u6B62\u6E32\u67D3\uFF1A\n");
125936
+ for (const err of narrativeErrors) {
125937
+ console.error(` \xB7 ${err}`);
125938
+ }
125939
+ console.error(
125940
+ "\n \u8BF7\u4FEE\u6B63 summary.keyIssues / diagnosisOverview.disadvantages \u540E\u91CD\u65B0 render\u3002\n"
125941
+ );
125942
+ process.exit(1);
125943
+ }
126792
125944
  const comparisonErrors = getFactItemsErrors(data);
126793
125945
  if (comparisonErrors.length > 0) {
126794
125946
  console.error("\n\u274C \u4E8B\u5B9E items \u4E0D\u5B8C\u6574\uFF0CHTML \u4E2D\u90E8\u5206\u8868\u683C\u4F1A\u663E\u793A\u4E3A\u7A7A\uFF1A\n");
@@ -126853,7 +126005,7 @@ async function runGoogleAdsDiagnosisRender(opts) {
126853
126005
  `);
126854
126006
  }
126855
126007
  console.log(
126856
- "\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u5B8C\u6574\u62A5\u544A\uFF08\u6837\u5F0F\u4E0E MarkAI GoogleAdsDiagnosisReport.html \u4E00\u81F4\uFF1B\u56FE\u8868\uFF1AChart.js CDN\uFF09\u3002\n"
126008
+ "\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u5B8C\u6574\u62A5\u544A\uFF08\u6837\u5F0F\u4E0E MarkAI GoogleAdsDiagnosisReport.html \u4E00\u81F4\uFF1B\u56FE\u8868\uFF1AECharts CDN\uFF09\u3002\n"
126857
126009
  );
126858
126010
  }
126859
126011
 
@@ -126983,7 +126135,7 @@ var PHASE_LABEL = {
126983
126135
  "find-winner": "\u627E\u8D62\u5BB6",
126984
126136
  scale: "\u653E\u91CF"
126985
126137
  };
126986
- function num3(value) {
126138
+ function num2(value) {
126987
126139
  const n = Number(value);
126988
126140
  return Number.isFinite(n) ? n : void 0;
126989
126141
  }
@@ -126994,35 +126146,35 @@ function money(value, currency = "USD") {
126994
126146
  }
126995
126147
  function adSetsWithSpend(tables) {
126996
126148
  const rows = Array.isArray(tables?.adSets) ? tables.adSets : [];
126997
- return rows.filter((r) => (num3(r.spend) ?? 0) > 0);
126149
+ return rows.filter((r) => (num2(r.spend) ?? 0) > 0);
126998
126150
  }
126999
126151
  function topAdSetConcentration(adSets) {
127000
126152
  if (!adSets.length) return null;
127001
- const totalSpend = adSets.reduce((s, r) => s + (num3(r.spend) ?? 0), 0);
127002
- const totalResults = adSets.reduce((s, r) => s + (num3(r.results) ?? 0), 0);
126153
+ const totalSpend = adSets.reduce((s, r) => s + (num2(r.spend) ?? 0), 0);
126154
+ const totalResults = adSets.reduce((s, r) => s + (num2(r.results) ?? 0), 0);
127003
126155
  let best = adSets[0];
127004
- let bestSpend = num3(best.spend) ?? 0;
126156
+ let bestSpend = num2(best.spend) ?? 0;
127005
126157
  for (const r of adSets) {
127006
- const sp = num3(r.spend) ?? 0;
126158
+ const sp = num2(r.spend) ?? 0;
127007
126159
  if (sp > bestSpend) {
127008
126160
  best = r;
127009
126161
  bestSpend = sp;
127010
126162
  }
127011
126163
  }
127012
- const results = num3(best.results) ?? 0;
126164
+ const results = num2(best.results) ?? 0;
127013
126165
  return {
127014
126166
  name: String(best.adGroupName ?? best.name ?? "\u2014"),
127015
126167
  spendShare: totalSpend > 0 ? bestSpend / totalSpend : 0,
127016
126168
  resultsShare: totalResults > 0 ? results / totalResults : 0,
127017
- cpl: num3(best.costPerResult)
126169
+ cpl: num2(best.costPerResult)
127018
126170
  };
127019
126171
  }
127020
126172
  function winnerSpendShare(adSets, avgCpl, totalSpend) {
127021
126173
  if (totalSpend <= 0 || avgCpl <= 0) return 0;
127022
126174
  const winnerSpend = adSets.filter((r) => {
127023
- const cpl = num3(r.costPerResult);
126175
+ const cpl = num2(r.costPerResult);
127024
126176
  return cpl != null && cpl <= avgCpl * 0.9;
127025
- }).reduce((s, r) => s + (num3(r.spend) ?? 0), 0);
126177
+ }).reduce((s, r) => s + (num2(r.spend) ?? 0), 0);
127026
126178
  return winnerSpend / totalSpend;
127027
126179
  }
127028
126180
  function countActiveDimensions(charts, tables) {
@@ -127039,7 +126191,7 @@ function asArray(value) {
127039
126191
  }
127040
126192
  function inferLifecyclePhase(input) {
127041
126193
  const { avgCpl, currency = "USD" } = input;
127042
- const spend = num3(input.spend) ?? 0;
126194
+ const spend = num2(input.spend) ?? 0;
127043
126195
  const adSets = adSetsWithSpend(input.tables);
127044
126196
  const activeDims = countActiveDimensions(input.charts, input.tables);
127045
126197
  const winShare = winnerSpendShare(adSets, avgCpl, spend);
@@ -127098,7 +126250,7 @@ function ensureLifecycleFromSnapshot(healthDiagnosis, merged) {
127098
126250
  }
127099
126251
 
127100
126252
  // src/commands/facebook-analysis/scorecard-autofill.ts
127101
- function num4(value) {
126253
+ function num3(value) {
127102
126254
  const n = Number(value);
127103
126255
  return Number.isFinite(n) ? n : void 0;
127104
126256
  }
@@ -127138,8 +126290,8 @@ function buildScorecardFromMergedData(input) {
127138
126290
  if (pLabels.length) {
127139
126291
  const indexed = pLabels.map((label, i) => ({
127140
126292
  label: String(label),
127141
- cpl: num4(pCpl[i]),
127142
- spend: num4(pSpend[i]) ?? 0
126293
+ cpl: num3(pCpl[i]),
126294
+ spend: num3(pSpend[i]) ?? 0
127143
126295
  })).filter((x) => x.spend > 0 || x.cpl != null && x.cpl > 0);
127144
126296
  const sorted = [...indexed].sort((a, b) => (a.cpl ?? Infinity) - (b.cpl ?? Infinity));
127145
126297
  if (sorted[0]) {
@@ -127164,7 +126316,7 @@ function buildScorecardFromMergedData(input) {
127164
126316
  const cLabels = Array.isArray(country?.labels) ? country.labels : [];
127165
126317
  const cCpl = Array.isArray(country?.cpl) ? country.cpl : [];
127166
126318
  if (cLabels.length) {
127167
- const indexed = cLabels.map((label, i) => ({ label: String(label), cpl: num4(cCpl[i]) })).filter((x) => x.cpl != null && x.cpl > 0);
126319
+ const indexed = cLabels.map((label, i) => ({ label: String(label), cpl: num3(cCpl[i]) })).filter((x) => x.cpl != null && x.cpl > 0);
127168
126320
  const sorted = [...indexed].sort((a, b) => (a.cpl ?? 0) - (b.cpl ?? 0));
127169
126321
  if (sorted[0]) {
127170
126322
  const s = cplSignal(sorted[0].cpl, avgCpl);
@@ -127185,24 +126337,24 @@ function buildScorecardFromMergedData(input) {
127185
126337
  }
127186
126338
  }
127187
126339
  const adSets = Array.isArray(tables.adSets) ? tables.adSets : [];
127188
- const adWithSpend = adSets.filter((g) => (num4(g.spend) ?? 0) > 0);
126340
+ const adWithSpend = adSets.filter((g) => (num3(g.spend) ?? 0) > 0);
127189
126341
  if (adWithSpend.length) {
127190
126342
  const sorted = [...adWithSpend].sort(
127191
- (a, b) => (num4(a.costPerResult) ?? Infinity) - (num4(b.costPerResult) ?? Infinity)
126343
+ (a, b) => (num3(a.costPerResult) ?? Infinity) - (num3(b.costPerResult) ?? Infinity)
127192
126344
  );
127193
126345
  const best = sorted[0];
127194
- const sBest = cplSignal(num4(best.costPerResult), avgCpl);
126346
+ const sBest = cplSignal(num3(best.costPerResult), avgCpl);
127195
126347
  pushRow(rows, seen, {
127196
126348
  item: String(best.name ?? "\u5E7F\u544A\u7EC4"),
127197
- data: `CPL ${money2(num4(best.costPerResult), currency)}`,
126349
+ data: `CPL ${money2(num3(best.costPerResult), currency)}`,
127198
126350
  ...sBest
127199
126351
  });
127200
126352
  if (sorted.length > 1) {
127201
126353
  const worst = sorted[sorted.length - 1];
127202
- const sWorst = cplSignal(num4(worst.costPerResult), avgCpl);
126354
+ const sWorst = cplSignal(num3(worst.costPerResult), avgCpl);
127203
126355
  pushRow(rows, seen, {
127204
126356
  item: String(worst.name ?? "\u5E7F\u544A\u7EC4"),
127205
- data: `CPL ${money2(num4(worst.costPerResult), currency)}`,
126357
+ data: `CPL ${money2(num3(worst.costPerResult), currency)}`,
127206
126358
  ...sWorst
127207
126359
  });
127208
126360
  }
@@ -127210,18 +126362,18 @@ function buildScorecardFromMergedData(input) {
127210
126362
  const topAud = Array.isArray(tables.audienceTop) ? tables.audienceTop : [];
127211
126363
  const bottomAud = Array.isArray(tables.audienceBottom) ? tables.audienceBottom : [];
127212
126364
  if (topAud[0]) {
127213
- const s = cplSignal(num4(topAud[0].costPerResult), avgCpl);
126365
+ const s = cplSignal(num3(topAud[0].costPerResult), avgCpl);
127214
126366
  pushRow(rows, seen, {
127215
126367
  item: `\u53D7\u4F17\xB7${String(topAud[0].label ?? "Top")}`,
127216
- data: `CPL ${money2(num4(topAud[0].costPerResult), currency)}`,
126368
+ data: `CPL ${money2(num3(topAud[0].costPerResult), currency)}`,
127217
126369
  ...s
127218
126370
  });
127219
126371
  }
127220
126372
  if (bottomAud[0]) {
127221
- const s = cplSignal(num4(bottomAud[0].costPerResult), avgCpl);
126373
+ const s = cplSignal(num3(bottomAud[0].costPerResult), avgCpl);
127222
126374
  pushRow(rows, seen, {
127223
126375
  item: `\u53D7\u4F17\xB7${String(bottomAud[0].label ?? "Bottom")}`,
127224
- data: `CPL ${money2(num4(bottomAud[0].costPerResult), currency)}`,
126376
+ data: `CPL ${money2(num3(bottomAud[0].costPerResult), currency)}`,
127225
126377
  ...s
127226
126378
  });
127227
126379
  }
@@ -127259,13 +126411,13 @@ function ensureScorecardFromSnapshot(healthDiagnosis, merged) {
127259
126411
  }
127260
126412
 
127261
126413
  // src/commands/facebook-analysis/merge-snapshot.ts
127262
- function asRecord8(value) {
126414
+ function asRecord9(value) {
127263
126415
  if (value && typeof value === "object" && !Array.isArray(value)) {
127264
126416
  return value;
127265
126417
  }
127266
126418
  return null;
127267
126419
  }
127268
- function num5(value) {
126420
+ function num4(value) {
127269
126421
  const n = Number(value);
127270
126422
  return Number.isFinite(n) ? n : void 0;
127271
126423
  }
@@ -127308,7 +126460,7 @@ async function readManifest(snapshotDir) {
127308
126460
  async function loadSectionFile(snapshotDir, file) {
127309
126461
  try {
127310
126462
  const raw = await fs21.readFile(path28.join(snapshotDir, file), "utf8");
127311
- return asRecord8(JSON.parse(raw));
126463
+ return asRecord9(JSON.parse(raw));
127312
126464
  } catch {
127313
126465
  return null;
127314
126466
  }
@@ -127319,8 +126471,8 @@ function aggregatePlatform(networks) {
127319
126471
  const platform = String(row.publisherPlatform ?? row.network ?? "unknown");
127320
126472
  const prev = map.get(platform) ?? { spend: 0, results: 0 };
127321
126473
  map.set(platform, {
127322
- spend: prev.spend + (num5(row.spend) ?? 0),
127323
- results: prev.results + (num5(row.results) ?? 0)
126474
+ spend: prev.spend + (num4(row.spend) ?? 0),
126475
+ results: prev.results + (num4(row.results) ?? 0)
127324
126476
  });
127325
126477
  }
127326
126478
  const labels = [];
@@ -127336,10 +126488,10 @@ function aggregatePlatform(networks) {
127336
126488
  function buildAudienceRows(audiences) {
127337
126489
  return audiences.map((a) => ({
127338
126490
  label: audienceLabel(a.age, a.gender),
127339
- spend: num5(a.spend) ?? 0,
127340
- results: num5(a.results) ?? 0,
127341
- costPerResult: num5(a.costPerResult) ?? (num5(a.results) ? (num5(a.spend) ?? 0) / (num5(a.results) ?? 1) : 0),
127342
- frequency: num5(a.frequency)
126491
+ spend: num4(a.spend) ?? 0,
126492
+ results: num4(a.results) ?? 0,
126493
+ costPerResult: num4(a.costPerResult) ?? (num4(a.results) ? (num4(a.spend) ?? 0) / (num4(a.results) ?? 1) : 0),
126494
+ frequency: num4(a.frequency)
127343
126495
  })).filter((r) => r.spend > 0 || r.results > 0);
127344
126496
  }
127345
126497
  async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
@@ -127356,7 +126508,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
127356
126508
  if (data) sectionMap.set(art.section, data);
127357
126509
  }
127358
126510
  const overview = sectionMap.get("overview");
127359
- const current = asRecord8(overview?.currentPeriod);
126511
+ const current = asRecord9(overview?.currentPeriod);
127360
126512
  const accountName = typeof overview?.accountName === "string" ? overview.accountName : void 0;
127361
126513
  const accountId = manifest.accountId ?? (typeof overview?.accountId === "string" ? overview.accountId : void 0);
127362
126514
  const kpis = { ...payload.kpis ?? {} };
@@ -127378,7 +126530,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
127378
126530
  }
127379
126531
  if (!meta.resultType && current?.resultType) meta.resultType = String(current.resultType);
127380
126532
  const charts = { ...payload.charts ?? {} };
127381
- const avgCpl = num5(kpis.costPerResult) ?? 0;
126533
+ const avgCpl = num4(kpis.costPerResult) ?? 0;
127382
126534
  if (!charts.platform) {
127383
126535
  const platform = sectionMap.get("platform");
127384
126536
  const networks = Array.isArray(platform?.networks) ? platform.networks : [];
@@ -127388,10 +126540,10 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
127388
126540
  const country = sectionMap.get("country");
127389
126541
  const countries = Array.isArray(country?.countries) ? country.countries : [];
127390
126542
  if (countries.length) {
127391
- const sorted = [...countries].sort((a, b) => (num5(b.spend) ?? 0) - (num5(a.spend) ?? 0));
126543
+ const sorted = [...countries].sort((a, b) => (num4(b.spend) ?? 0) - (num4(a.spend) ?? 0));
127392
126544
  charts.country = {
127393
126545
  labels: sorted.map((c) => String(c.countryOrRegion ?? "\u2014")),
127394
- cpl: sorted.map((c) => num5(c.costPerResult) ?? 0)
126546
+ cpl: sorted.map((c) => num4(c.costPerResult) ?? 0)
127395
126547
  };
127396
126548
  }
127397
126549
  }
@@ -127407,7 +126559,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
127407
126559
  };
127408
126560
  }
127409
126561
  }
127410
- if (!charts.funnel && num5(kpis.reach) != null) {
126562
+ if (!charts.funnel && num4(kpis.reach) != null) {
127411
126563
  charts.funnel = { reach: kpis.reach, results: kpis.results ?? 0 };
127412
126564
  }
127413
126565
  const tables = { ...payload.tables ?? {} };
@@ -127416,14 +126568,14 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
127416
126568
  const groups = Array.isArray(adSets?.adGroups) ? adSets.adGroups : [];
127417
126569
  if (groups.length) {
127418
126570
  tables.adSets = groups.map((g) => {
127419
- const cpl = num5(g.costPerResult);
127420
- const freq = num5(g.frequency);
126571
+ const cpl = num4(g.costPerResult);
126572
+ const freq = num4(g.frequency);
127421
126573
  const fatigue = fatigueFromFrequency(freq);
127422
126574
  const status = statusFromCpl(cpl, avgCpl);
127423
126575
  return {
127424
126576
  name: String(g.adGroupName ?? g.campaignName ?? "\u2014"),
127425
- spend: num5(g.spend) ?? 0,
127426
- results: num5(g.results) ?? 0,
126577
+ spend: num4(g.spend) ?? 0,
126578
+ results: num4(g.results) ?? 0,
127427
126579
  costPerResult: cpl ?? 0,
127428
126580
  frequency: freq,
127429
126581
  fatigueLevel: fatigue.level,
@@ -127447,7 +126599,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
127447
126599
  }
127448
126600
  const currency = typeof meta.currency === "string" ? meta.currency : "USD";
127449
126601
  const mergeCtx = {
127450
- spend: num5(kpis.spend),
126602
+ spend: num4(kpis.spend),
127451
126603
  avgCpl,
127452
126604
  currency,
127453
126605
  charts,
@@ -127478,7 +126630,7 @@ function isFiniteNumber(value) {
127478
126630
  function asArray2(value) {
127479
126631
  return Array.isArray(value) ? value : [];
127480
126632
  }
127481
- function asRecord9(value) {
126633
+ function asRecord10(value) {
127482
126634
  if (value && typeof value === "object" && !Array.isArray(value)) {
127483
126635
  return value;
127484
126636
  }
@@ -127490,26 +126642,26 @@ function pushMissing(missing, id, chapter, dimension, hint) {
127490
126642
  function countAdSetsWithSpend(tables) {
127491
126643
  const rows = asArray2(tables?.adSets);
127492
126644
  return rows.filter((row) => {
127493
- const r = asRecord9(row);
126645
+ const r = asRecord10(row);
127494
126646
  const spend = r?.spend;
127495
126647
  return isFiniteNumber(spend) && spend > 0;
127496
126648
  }).length;
127497
126649
  }
127498
126650
  function chartLabels(chart) {
127499
- const c = asRecord9(chart);
126651
+ const c = asRecord10(chart);
127500
126652
  return asArray2(c?.labels).length;
127501
126653
  }
127502
126654
  function validateMetaPeriodReportContent(data) {
127503
126655
  const missing = [];
127504
- const meta = asRecord9(data.meta) ?? {};
127505
- const kpis = asRecord9(data.kpis) ?? {};
127506
- const narrative = asRecord9(data.narrative) ?? {};
127507
- const tables = asRecord9(data.tables);
127508
- const charts = asRecord9(data.charts);
127509
- const sections = asRecord9(data.sections);
127510
- const health = asRecord9(data.healthDiagnosis);
127511
- const priorityPlan = asRecord9(data.priorityPlan);
127512
- const actionChecklist = asRecord9(data.actionChecklist);
126656
+ const meta = asRecord10(data.meta) ?? {};
126657
+ const kpis = asRecord10(data.kpis) ?? {};
126658
+ const narrative = asRecord10(data.narrative) ?? {};
126659
+ const tables = asRecord10(data.tables);
126660
+ const charts = asRecord10(data.charts);
126661
+ const sections = asRecord10(data.sections);
126662
+ const health = asRecord10(data.healthDiagnosis);
126663
+ const priorityPlan = asRecord10(data.priorityPlan);
126664
+ const actionChecklist = asRecord10(data.actionChecklist);
127513
126665
  const adSetsWithSpend2 = countAdSetsWithSpend(tables ?? void 0);
127514
126666
  const regionalNarratives = asArray2(narrative.regional).length;
127515
126667
  const recommendations = asArray2(narrative.recommendations);
@@ -127581,7 +126733,7 @@ function validateMetaPeriodReportContent(data) {
127581
126733
  "charts.audience.labels \u81F3\u5C11 1 \u9879"
127582
126734
  );
127583
126735
  }
127584
- const funnel = asRecord9(charts?.funnel);
126736
+ const funnel = asRecord10(charts?.funnel);
127585
126737
  if (!isFiniteNumber(funnel?.reach)) {
127586
126738
  pushMissing(
127587
126739
  missing,
@@ -127645,7 +126797,7 @@ function validateMetaPeriodReportContent(data) {
127645
126797
  );
127646
126798
  } else {
127647
126799
  for (let i = 0; i < regionalNarratives; i++) {
127648
- const row = asRecord9(asArray2(narrative.regional)[i]);
126800
+ const row = asRecord10(asArray2(narrative.regional)[i]);
127649
126801
  if (textLen2(row?.text) < 80) {
127650
126802
  pushMissing(
127651
126803
  missing,
@@ -127675,7 +126827,7 @@ function validateMetaPeriodReportContent(data) {
127675
126827
  "\u5FC5\u987B\u6070\u597D 4 \u6761\uFF08\u7B80\u5316\u8868\u5355/\u533A\u57DF\u8C03\u6574/\u9884\u7B97\u91CD\u6784/\u7D20\u6750\u5EFA\u8BAE\uFF09"
127676
126828
  );
127677
126829
  } else {
127678
- const titles = recommendations.map((r) => asRecord9(r)?.title);
126830
+ const titles = recommendations.map((r) => asRecord10(r)?.title);
127679
126831
  for (const title of RECOMMENDATION_TITLES) {
127680
126832
  if (!titles.includes(title)) {
127681
126833
  pushMissing(
@@ -127688,7 +126840,7 @@ function validateMetaPeriodReportContent(data) {
127688
126840
  }
127689
126841
  }
127690
126842
  for (let i = 0; i < recommendations.length; i++) {
127691
- const rec = asRecord9(recommendations[i]);
126843
+ const rec = asRecord10(recommendations[i]);
127692
126844
  if (textLen2(rec?.content) < 150) {
127693
126845
  pushMissing(
127694
126846
  missing,
@@ -127710,7 +126862,7 @@ function validateMetaPeriodReportContent(data) {
127710
126862
  );
127711
126863
  } else {
127712
126864
  for (let i = 0; i < supplementary.length; i++) {
127713
- const row = asRecord9(supplementary[i]);
126865
+ const row = asRecord10(supplementary[i]);
127714
126866
  if (!textLen2(row?.dimension) || !textLen2(row?.issue)) {
127715
126867
  pushMissing(
127716
126868
  missing,
@@ -127792,7 +126944,7 @@ function validateMetaPeriodReportContent(data) {
127792
126944
  );
127793
126945
  } else {
127794
126946
  for (let i = 0; i < fourQuestions.length; i++) {
127795
- const q = asRecord9(fourQuestions[i]);
126947
+ const q = asRecord10(fourQuestions[i]);
127796
126948
  const evidence = asArray2(q?.evidence).filter((e) => textLen2(e) > 0);
127797
126949
  if (evidence.length < 2) {
127798
126950
  pushMissing(
@@ -127816,7 +126968,7 @@ function validateMetaPeriodReportContent(data) {
127816
126968
  }
127817
126969
  let scorecardValidRows = 0;
127818
126970
  for (let i = 0; i < scorecard.length; i++) {
127819
- const row = asRecord9(scorecard[i]);
126971
+ const row = asRecord10(scorecard[i]);
127820
126972
  const itemOk = textLen2(row?.item) > 0;
127821
126973
  const dataOk = textLen2(row?.data) > 0;
127822
126974
  const signalOk = SCORECARD_SIGNALS.includes(row?.signal);
@@ -127858,7 +127010,7 @@ function validateMetaPeriodReportContent(data) {
127858
127010
  );
127859
127011
  }
127860
127012
  for (const key of ["platform", "country", "adSets"]) {
127861
- const insight = textLen2(asRecord9(sections?.[key])?.insight);
127013
+ const insight = textLen2(asRecord10(sections?.[key])?.insight);
127862
127014
  if (insight < 200) {
127863
127015
  pushMissing(
127864
127016
  missing,
@@ -127869,7 +127021,7 @@ function validateMetaPeriodReportContent(data) {
127869
127021
  );
127870
127022
  }
127871
127023
  }
127872
- const audience = asRecord9(sections?.audience);
127024
+ const audience = asRecord10(sections?.audience);
127873
127025
  if (asArray2(audience?.goldenProfile).filter((s) => textLen2(s) > 0).length < 3) {
127874
127026
  pushMissing(
127875
127027
  missing,
@@ -127897,7 +127049,7 @@ function validateMetaPeriodReportContent(data) {
127897
127049
  "\u2265150 \u5B57"
127898
127050
  );
127899
127051
  }
127900
- const landingRows = asArray2(asRecord9(sections?.landingPage)?.rows);
127052
+ const landingRows = asArray2(asRecord10(sections?.landingPage)?.rows);
127901
127053
  if (landingRows.length < 3) {
127902
127054
  pushMissing(
127903
127055
  missing,
@@ -127966,133 +127118,971 @@ function resolveSkillTemplatePath3(basename11) {
127966
127118
  for (const p of candidates) {
127967
127119
  if (fs22.existsSync(p)) return p;
127968
127120
  }
127969
- return candidates[0];
127121
+ return candidates[0];
127122
+ }
127123
+ function metaPeriodReportTemplatePath() {
127124
+ return resolveSkillTemplatePath3(TEMPLATE_BASENAMES2.html);
127125
+ }
127126
+ function metaPeriodReportRuntimePath() {
127127
+ return resolveSkillTemplatePath3(TEMPLATE_BASENAMES2.runtime);
127128
+ }
127129
+ async function readJsonFile2(filePath) {
127130
+ const text = await fsPromises2.readFile(filePath, "utf8");
127131
+ try {
127132
+ return JSON.parse(text);
127133
+ } catch {
127134
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
127135
+ }
127136
+ }
127137
+ function asRecord11(value) {
127138
+ if (value && typeof value === "object" && !Array.isArray(value)) {
127139
+ return value;
127140
+ }
127141
+ throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
127142
+ }
127143
+ function injectReportData2(html, payload) {
127144
+ const json = JSON.stringify(payload).replace(/</g, "\\u003c");
127145
+ if (!html.includes(INJECT_MARKER)) {
127146
+ throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 meta-period-report.html");
127147
+ }
127148
+ return html.replace(INJECT_MARKER, `window.__META_PERIOD_REPORT__ = ${json};`);
127149
+ }
127150
+ async function runFacebookAnalysisRender(opts) {
127151
+ const dataPath = path29.resolve(opts.dataFile);
127152
+ let data = asRecord11(await readJsonFile2(dataPath));
127153
+ if (opts.snapshotDir?.trim()) {
127154
+ data = await mergeFacebookSnapshotIntoReport(data, path29.resolve(opts.snapshotDir));
127155
+ }
127156
+ const contentCheck = validateMetaPeriodReportContent(data);
127157
+ if (!contentCheck.ok) {
127158
+ console.error(`
127159
+ \u274C ${formatMetaPeriodReportErrors(contentCheck)}
127160
+ `);
127161
+ const { stats: stats2 } = contentCheck;
127162
+ console.error(
127163
+ ` \u7EDF\u8BA1\uFF1A\u5EFA\u8BAE ${stats2.recommendationCount}/4\uFF5C\u8865\u5145 ${stats2.supplementaryCount}/7\uFF5C\u56DB\u95EE ${stats2.fourQuestionsCount}/4\uFF5C\u9636\u6BB5 ${stats2.lifecyclePhase ?? "\u2014"}${stats2.lifecyclePhaseInferred ? `\uFF08\u63A8\u65AD ${stats2.lifecyclePhaseInferred}\uFF09` : ""}\uFF5C\u7EA2\u7EFF\u706F ${stats2.scorecardValidRows}/${stats2.scorecardRows} \u6709\u6548\u884C\uFF5C\u533A\u57DF\u53D9\u4E8B ${stats2.regionalNarratives}/${stats2.adSetsWithSpend}
127164
+ `
127165
+ );
127166
+ process.exit(1);
127167
+ }
127168
+ const templatePath = metaPeriodReportTemplatePath();
127169
+ let html;
127170
+ try {
127171
+ html = await fsPromises2.readFile(templatePath, "utf8");
127172
+ } catch {
127173
+ console.error(`
127174
+ \u274C \u672A\u627E\u5230\u62A5\u544A\u6A21\u677F\uFF1A${templatePath}
127175
+ \u8BF7\u5148\u6267\u884C npm run build
127176
+ `);
127177
+ process.exit(1);
127178
+ }
127179
+ const outPath = path29.resolve(
127180
+ opts.out ?? path29.join(path29.dirname(dataPath), "meta-period-report.html")
127181
+ );
127182
+ const outDir = path29.dirname(outPath);
127183
+ await fsPromises2.mkdir(outDir, { recursive: true });
127184
+ await fsPromises2.writeFile(outPath, injectReportData2(html, data), "utf8");
127185
+ const runtimeSrc = metaPeriodReportRuntimePath();
127186
+ const runtimeOut = path29.join(outDir, TEMPLATE_BASENAMES2.runtime);
127187
+ let runtimeCopied = false;
127188
+ try {
127189
+ await fsPromises2.copyFile(runtimeSrc, runtimeOut);
127190
+ runtimeCopied = true;
127191
+ } catch {
127192
+ }
127193
+ const { stats } = contentCheck;
127194
+ console.log(`
127195
+ \u2705 Meta/Facebook \u5468\u671F\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
127196
+ `);
127197
+ console.log(
127198
+ ` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u5EFA\u8BAE ${stats.recommendationCount}\uFF5C\u8865\u5145 ${stats.supplementaryCount}\uFF5C\u56DB\u95EE ${stats.fourQuestionsCount}\uFF5C\u9636\u6BB5 ${stats.lifecyclePhase ?? "\u2014"}\uFF5C\u7EA2\u7EFF\u706F ${stats.scorecardValidRows} \u6709\u6548\u884C
127199
+ `
127200
+ );
127201
+ if (runtimeCopied) {
127202
+ console.log(` \u8FD0\u884C\u65F6\u811A\u672C\uFF1A${runtimeOut}
127203
+ `);
127204
+ }
127205
+ console.log(
127206
+ "\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u56FE\u8868\u4E0E\u5B8C\u6574\u7AE0\u8282\uFF08ECharts\uFF1Astaticpn.siluzan.com/homeCDN/echarts.js\uFF09\u3002\n"
127207
+ );
127208
+ }
127209
+
127210
+ // src/commands/facebook-analysis/run-batch.ts
127211
+ init_auth();
127212
+ import * as path30 from "path";
127213
+ import { performance as performance5 } from "perf_hooks";
127214
+ init_version();
127215
+
127216
+ // src/commands/facebook-analysis/resolve-sections.ts
127217
+ function normalizeSectionToken(raw) {
127218
+ const t = raw.trim();
127219
+ if (!t) return null;
127220
+ if (FACEBOOK_SECTION_NAMES.includes(t)) {
127221
+ return t;
127222
+ }
127223
+ return FACEBOOK_SECTION_ALIASES[t] ?? null;
127224
+ }
127225
+ function resolveSectionList2(sections, exclude) {
127226
+ const splitClean = (s) => (s ?? "").split(",").map((x) => x.trim()).filter(Boolean);
127227
+ const include = splitClean(sections);
127228
+ const dropRaw = splitClean(exclude);
127229
+ const unknown = [...include, ...dropRaw].filter((n) => normalizeSectionToken(n) === null);
127230
+ if (unknown.length > 0) {
127231
+ console.error(
127232
+ `
127233
+ \u274C \u672A\u77E5\u7684 section \u540D\u79F0\uFF1A${unknown.join(", ")}
127234
+ \u53EF\u9009\uFF1A${FACEBOOK_SECTION_NAMES.join(", ")}
127235
+ Google \u522B\u540D\uFF1A${Object.keys(FACEBOOK_SECTION_ALIASES).join(", ")}
127236
+ `
127237
+ );
127238
+ process.exit(1);
127239
+ }
127240
+ const includeCanonical = new Set(include.map((n) => normalizeSectionToken(n)).filter(Boolean));
127241
+ const dropCanonical = new Set(dropRaw.map((n) => normalizeSectionToken(n)).filter(Boolean));
127242
+ const base = include.length > 0 ? FACEBOOK_SECTIONS.filter((s) => includeCanonical.has(s.name)) : [...FACEBOOK_SECTIONS];
127243
+ return base.filter((s) => !dropCanonical.has(s.name));
127244
+ }
127245
+
127246
+ // src/commands/facebook-analysis/run-batch.ts
127247
+ async function runWithConcurrency2(tasks, concurrency) {
127248
+ const results = new Array(tasks.length);
127249
+ let cursor = 0;
127250
+ const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
127251
+ while (true) {
127252
+ const i = cursor++;
127253
+ if (i >= tasks.length) return;
127254
+ results[i] = await tasks[i]();
127255
+ }
127256
+ });
127257
+ await Promise.all(workers);
127258
+ return results;
127259
+ }
127260
+ async function runAllFacebookSections(opts) {
127261
+ const { apiId, manifestId } = normalizeFacebookAccountId(opts.account);
127262
+ const config = loadConfig(opts.token);
127263
+ if (!opts.jsonOut?.trim()) {
127264
+ console.error("\n\u274C \u5FC5\u987B\u4F20 --json-out <\u76EE\u5F55>\uFF08\u5404\u7EF4\u5EA6\u843D\u76D8\u540E\u7531 report-manifest \u7D22\u5F15\uFF09\u3002\n");
127265
+ process.exit(1);
127266
+ }
127267
+ const targets = resolveSectionList2(opts.sections, opts.exclude);
127268
+ if (targets.length === 0) {
127269
+ console.error("\n\u274C \u6CA1\u6709\u8981\u6267\u884C\u7684 section\uFF08\u8BF7\u68C0\u67E5 --sections / --exclude\uFF09\u3002\n");
127270
+ process.exit(1);
127271
+ }
127272
+ const { startDate, endDate } = resolveFacebookDateRange(opts.start, opts.end);
127273
+ const concurrency = Math.max(
127274
+ 1,
127275
+ Math.min(typeof opts.concurrency === "number" ? opts.concurrency : 5, 16)
127276
+ );
127277
+ const cliVersion = getCurrentVersion2();
127278
+ const overallT0 = performance5.now();
127279
+ const tasks = targets.map(
127280
+ (def) => async () => {
127281
+ const t0 = performance5.now();
127282
+ const hint = endpointHintForFacebookSection(def, apiId);
127283
+ try {
127284
+ const payload = await fetchFacebookSectionPayload(def, opts, config, apiId);
127285
+ const summary2 = await writeReportAnalysisSnapshot({
127286
+ snapshotDir: opts.jsonOut,
127287
+ section: def.name,
127288
+ accountId: manifestId,
127289
+ dateRange: { start: startDate, end: endDate },
127290
+ payload,
127291
+ cliVersion
127292
+ });
127293
+ return {
127294
+ section: def.name,
127295
+ ok: true,
127296
+ elapsedMs: performance5.now() - t0,
127297
+ file: summary2.writtenFiles[0],
127298
+ endpointHint: hint
127299
+ };
127300
+ } catch (err) {
127301
+ return {
127302
+ section: def.name,
127303
+ ok: false,
127304
+ elapsedMs: performance5.now() - t0,
127305
+ error: err instanceof Error ? err.message : String(err),
127306
+ endpointHint: hint
127307
+ };
127308
+ }
127309
+ }
127310
+ );
127311
+ const results = await runWithConcurrency2(tasks, concurrency);
127312
+ const succeeded = results.filter((r) => r.ok).length;
127313
+ const failed = results.length - succeeded;
127314
+ const absoluteSnapshotDir = path30.resolve(opts.jsonOut);
127315
+ const summary = {
127316
+ kind: "siluzan-tso-facebook-analysis-snapshot-batch",
127317
+ absoluteSnapshotDir,
127318
+ manifestFile: reportManifestFile(manifestId),
127319
+ accountId: manifestId,
127320
+ totalSections: results.length,
127321
+ succeeded,
127322
+ failed,
127323
+ concurrency,
127324
+ elapsedMs: performance5.now() - overallT0,
127325
+ results
127326
+ };
127327
+ console.log(JSON.stringify(summary));
127328
+ if (failed > 0) process.exit(2);
127329
+ }
127330
+
127331
+ // src/commands/facebook-analysis/register-cli.ts
127332
+ function registerBatchCommand(sectionHelp) {
127333
+ return new Command("run").description(
127334
+ "\u6279\u91CF\u62C9\u53D6 TSO reporting/media-account/FacebookAds Section \u6570\u636E\uFF08\u7701\u7565\u5B50\u547D\u4EE4\u540D\u65F6\u9ED8\u8BA4\u6267\u884C\u672C\u547D\u4EE4\uFF09"
127335
+ ).requiredOption("-a, --account <id>", "Facebook \u5E7F\u544A\u8D26\u6237 mediaCustomerId\uFF08\u6570\u5B57\u6216 act_<\u6570\u5B57>\uFF09").requiredOption(
127336
+ "--json-out <dir>",
127337
+ "\u7ED3\u679C\u843D\u76D8\u76EE\u5F55\uFF1B\u6BCF\u7EF4\u5EA6 `<section>-<accountId>.json` + `report-manifest-<accountId>.json`"
127338
+ ).option("-t, --token <token>", "Auth Token").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\u7565\uFF1B\u7701\u7565=\u8FD1 7 \u5929\u622A\u81F3\u6628\u5929\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option(
127339
+ "--sections <list>",
127340
+ `\u4EC5\u6267\u884C\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u5982 overview,ad-sets,platform\uFF1B\u7701\u7565=\u5168\u90E8 7 \u4E2A\u3002\u53EF\u9009\uFF1A${sectionHelp}\uFF1BGoogle \u522B\u540D\uFF1Acampaigns,geographic,devices,ads,materials`
127341
+ ).option("--exclude <list>", "\u6392\u9664\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u4E0E --sections \u53EF\u53E0\u52A0").option(
127342
+ "--limit <n>",
127343
+ "\u4EC5 country \u7EF4\u5EA6\uFF1A\u6309 spend \u964D\u5E8F\u53D6\u524D N \u6761\uFF08\u4E0D\u4F20\u5219\u8FD4\u56DE\u5168\u90E8\u56FD\u5BB6\uFF09",
127344
+ (v) => parseInt(v, 10)
127345
+ ).option("--concurrency <n>", "\u5E76\u53D1\u6570\uFF0C\u9ED8\u8BA4 5\uFF0C\u4E0A\u9650 16", (v) => parseInt(v, 10)).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
127346
+ await runAllFacebookSections(opts);
127347
+ });
127348
+ }
127349
+ function registerFacebookAnalysisCommands(program2) {
127350
+ const sectionHelp = FACEBOOK_SECTION_NAMES.join(", ");
127351
+ const root = program2.command("facebook-analysis").description(
127352
+ "Facebook Ads \u8D26\u6237\u5206\u6790\uFF1A\u6279\u91CF\u62C9\u53D6 Section \u6570\u636E\uFF08\u9ED8\u8BA4\uFF09\uFF0C\u6216\u7531 render \u751F\u6210 HTML \u5468\u671F\u62A5\u544A"
127353
+ );
127354
+ root.addCommand(registerBatchCommand(sectionHelp), { isDefault: true });
127355
+ root.command("render").description(
127356
+ "\u6839\u636E Agent \u64B0\u5199\u7684\u62A5\u544A JSON \u751F\u6210 Meta/Facebook \u5468\u671F\u5206\u6790 HTML \u7EC8\u7A3F\uFF08\u6821\u9A8C\u5FC5\u542B\u5B57\u6BB5\uFF0C\u7F3A\u9879\u62D2\u7EDD\u6E32\u67D3\uFF09"
127357
+ ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 meta-period-report.json").option(
127358
+ "--snapshot-dir <dir>",
127359
+ "\u53EF\u9009\uFF1Afacebook-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u5408\u5E76 KPI / \u56FE\u8868 / \u8868\u683C"
127360
+ ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
127361
+ await runFacebookAnalysisRender({
127362
+ dataFile: opts.data,
127363
+ snapshotDir: opts.snapshotDir,
127364
+ out: opts.out
127365
+ });
127366
+ });
127367
+ }
127368
+
127369
+ // src/commands/bing-analysis/sections.ts
127370
+ var BING_SECTIONS = [
127371
+ {
127372
+ name: "overview",
127373
+ description: "\u8D26\u6237\u603B\u89C8 OverviewSectionData\uFF08\u542B\u4F59\u989D\u5B9E\u65F6\u6821\u6B63\u3001\u65E5\u5747\u6D88\u8017\u56DE\u586B\u3001\u4E0A\u4E00\u5468\u671F\u73AF\u6BD4\u4E8C\u6B21\u62C9\u6570\uFF09",
127374
+ segment: "OverviewSectionData"
127375
+ },
127376
+ {
127377
+ name: "device",
127378
+ description: "\u8BBE\u5907\u5206\u5E03 DeviceSectionData",
127379
+ segment: "DeviceSectionData"
127380
+ },
127381
+ {
127382
+ name: "geographic",
127383
+ description: "\u5730\u57DF\u5206\u5E03 GeographicSectionData",
127384
+ segment: "GeographicSectionData"
127385
+ },
127386
+ {
127387
+ name: "age-audience",
127388
+ description: "\u5E74\u9F84\u53D7\u4F17 AgeAudienceData",
127389
+ segment: "AgeAudienceData"
127390
+ },
127391
+ {
127392
+ name: "gender-audience",
127393
+ description: "\u6027\u522B\u53D7\u4F17 GenderAudienceData",
127394
+ segment: "GenderAudienceData"
127395
+ },
127396
+ {
127397
+ name: "audience-merged",
127398
+ description: "\u53D7\u4F17\u5408\u5E76\uFF08AgeAudienceData + GenderAudienceData \u5E76\u884C\u62C9\u53D6\u5408\u5E76\uFF0C\u4E0D\u505A\u524D\u7AEF filterAudienceData\uFF09",
127399
+ segment: "AgeAudienceData+GenderAudienceData"
127400
+ },
127401
+ {
127402
+ name: "campaigns",
127403
+ description: "\u5E7F\u544A\u7CFB\u5217\u62A5\u8868 CampaignReport",
127404
+ segment: "CampaignReport"
127405
+ },
127406
+ {
127407
+ name: "ad-groups",
127408
+ description: "\u5E7F\u544A\u7EC4\u62A5\u8868 AdGroupReport",
127409
+ segment: "AdGroupReport"
127410
+ },
127411
+ {
127412
+ name: "ads",
127413
+ description: "\u5E7F\u544A\u62A5\u8868 AdReport",
127414
+ segment: "AdReport"
127415
+ },
127416
+ {
127417
+ name: "keywords",
127418
+ description: "\u5173\u952E\u8BCD\u62A5\u8868 KeywordReport\uFF08\u9ED8\u8BA4 limit=100\u3001orderByCost=true\uFF09",
127419
+ segment: "KeywordReport",
127420
+ limitOption: true
127421
+ },
127422
+ {
127423
+ name: "search-terms",
127424
+ description: "\u641C\u7D22\u5B57\u8BCD\u62A5\u8868 SearchQueryReport\uFF08\u9ED8\u8BA4 limit=100\u3001orderByCost=true\uFF09",
127425
+ segment: "SearchQueryReport",
127426
+ limitOption: true
127427
+ }
127428
+ ];
127429
+ var BING_SECTION_NAMES = BING_SECTIONS.map((s) => s.name);
127430
+ var BING_SECTION_ALIASES = {
127431
+ devices: "device",
127432
+ geo: "geographic",
127433
+ "search-queries": "search-terms"
127434
+ };
127435
+
127436
+ // src/commands/bing-analysis/resolve-sections.ts
127437
+ function normalizeSectionToken2(raw) {
127438
+ const t = raw.trim();
127439
+ if (!t) return null;
127440
+ if (BING_SECTION_NAMES.includes(t)) {
127441
+ return t;
127442
+ }
127443
+ return BING_SECTION_ALIASES[t] ?? null;
127444
+ }
127445
+ function resolveSectionList3(sections, exclude) {
127446
+ const splitClean = (s) => (s ?? "").split(",").map((x) => x.trim()).filter(Boolean);
127447
+ const include = splitClean(sections);
127448
+ const dropRaw = splitClean(exclude);
127449
+ const unknown = [...include, ...dropRaw].filter((n) => normalizeSectionToken2(n) === null);
127450
+ if (unknown.length > 0) {
127451
+ console.error(
127452
+ `
127453
+ \u274C \u672A\u77E5\u7684 section \u540D\u79F0\uFF1A${unknown.join(", ")}
127454
+ \u53EF\u9009\uFF1A${BING_SECTION_NAMES.join(", ")}
127455
+ \u522B\u540D\uFF1A${Object.keys(BING_SECTION_ALIASES).join(", ")}
127456
+ `
127457
+ );
127458
+ process.exit(1);
127459
+ }
127460
+ const includeCanonical = new Set(include.map((n) => normalizeSectionToken2(n)).filter(Boolean));
127461
+ const dropCanonical = new Set(dropRaw.map((n) => normalizeSectionToken2(n)).filter(Boolean));
127462
+ const base = include.length > 0 ? BING_SECTIONS.filter((s) => includeCanonical.has(s.name)) : [...BING_SECTIONS];
127463
+ return base.filter((s) => !dropCanonical.has(s.name));
127464
+ }
127465
+
127466
+ // src/commands/bing-analysis/fetch.ts
127467
+ init_auth();
127468
+ init_balance();
127469
+
127470
+ // src/commands/bing-analysis/normalize-bing-overview.ts
127471
+ function num5(v) {
127472
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
127473
+ }
127474
+ function roundMoney(v) {
127475
+ return Math.round(v * 100) / 100;
127476
+ }
127477
+ function countInclusiveCalendarDays(startDate, endDate) {
127478
+ const parts = (ymd) => ymd.trim().split("-").map((x) => parseInt(x, 10));
127479
+ const sp = parts(startDate);
127480
+ const ep = parts(endDate);
127481
+ if (sp.length !== 3 || ep.length !== 3 || sp.some((n) => !Number.isFinite(n)) || ep.some((n) => !Number.isFinite(n))) {
127482
+ return 0;
127483
+ }
127484
+ const s = new Date(sp[0], sp[1] - 1, sp[2]);
127485
+ const e = new Date(ep[0], ep[1] - 1, ep[2]);
127486
+ if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime()) || s > e) return 0;
127487
+ const msPerDay = 24 * 60 * 60 * 1e3;
127488
+ return Math.floor((e.getTime() - s.getTime()) / msPerDay) + 1;
127489
+ }
127490
+ function periodSpend(period) {
127491
+ if (!period || typeof period !== "object") return void 0;
127492
+ return num5(period.spend);
127493
+ }
127494
+ function enrichBingOverviewDerivedMetrics(record, startDate, endDate) {
127495
+ const out = { ...record };
127496
+ const currentSpend = periodSpend(out.currentPeriod);
127497
+ let totalCost = num5(out.totalCost);
127498
+ if (totalCost == null || totalCost <= 0) {
127499
+ if (currentSpend != null && currentSpend > 0) {
127500
+ out.totalCost = currentSpend;
127501
+ totalCost = currentSpend;
127502
+ }
127503
+ }
127504
+ let activeDays = num5(out.activeDays);
127505
+ if (activeDays == null || activeDays <= 0) {
127506
+ const calendarDays = countInclusiveCalendarDays(startDate, endDate);
127507
+ if (calendarDays > 0) {
127508
+ out.activeDays = calendarDays;
127509
+ activeDays = calendarDays;
127510
+ }
127511
+ }
127512
+ const averageDailyCost = num5(out.averageDailyCost);
127513
+ if ((averageDailyCost == null || averageDailyCost <= 0) && totalCost != null && totalCost > 0 && activeDays != null && activeDays > 0) {
127514
+ out.averageDailyCost = roundMoney(totalCost / activeDays);
127515
+ }
127516
+ return out;
127970
127517
  }
127971
- function metaPeriodReportTemplatePath() {
127972
- return resolveSkillTemplatePath3(TEMPLATE_BASENAMES2.html);
127518
+ function parseYmd(ymd) {
127519
+ const parts = ymd.trim().split("-").map((x) => parseInt(x, 10));
127520
+ if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) return null;
127521
+ const d = new Date(parts[0], parts[1] - 1, parts[2]);
127522
+ return Number.isNaN(d.getTime()) ? null : d;
127973
127523
  }
127974
- function metaPeriodReportRuntimePath() {
127975
- return resolveSkillTemplatePath3(TEMPLATE_BASENAMES2.runtime);
127524
+ function formatYmd(d) {
127525
+ const y = d.getFullYear();
127526
+ const m = String(d.getMonth() + 1).padStart(2, "0");
127527
+ const day = String(d.getDate()).padStart(2, "0");
127528
+ return `${y}-${m}-${day}`;
127976
127529
  }
127977
- async function readJsonFile2(filePath) {
127978
- const text = await fsPromises2.readFile(filePath, "utf8");
127979
- try {
127980
- return JSON.parse(text);
127981
- } catch {
127982
- throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
127530
+ function calcBingPreviousPeriodRange(startDate, endDate) {
127531
+ const diffDays = countInclusiveCalendarDays(startDate, endDate);
127532
+ if (diffDays <= 0) return null;
127533
+ const start = parseYmd(startDate);
127534
+ if (!start) return null;
127535
+ const prevEnd = new Date(start);
127536
+ prevEnd.setDate(prevEnd.getDate() - 1);
127537
+ const prevStart = new Date(prevEnd);
127538
+ prevStart.setDate(prevStart.getDate() - (diffDays - 1));
127539
+ return { startDate: formatYmd(prevStart), endDate: formatYmd(prevEnd) };
127540
+ }
127541
+ function isBingPeriodBlockEmpty(period) {
127542
+ if (!period || typeof period !== "object") return true;
127543
+ const p = period;
127544
+ const spend = num5(p.spend) ?? 0;
127545
+ const impressions = num5(p.impressions) ?? 0;
127546
+ const clicks = num5(p.clicks) ?? 0;
127547
+ return spend <= 0 && impressions <= 0 && clicks <= 0;
127548
+ }
127549
+ function syncOverviewBalanceFields(record) {
127550
+ const out = { ...record };
127551
+ const budget = num5(out.remainingAccountBudget);
127552
+ if (budget != null && budget >= 0) {
127553
+ const balanceField = num5(out.balance);
127554
+ if (balanceField == null || balanceField <= 0 && budget > 0) {
127555
+ out.balance = budget;
127556
+ }
127983
127557
  }
127558
+ return out;
127984
127559
  }
127985
- function asRecord10(value) {
127986
- if (value && typeof value === "object" && !Array.isArray(value)) {
127987
- return value;
127560
+
127561
+ // src/commands/bing-analysis/fetch.ts
127562
+ var BING_KEYWORD_LIMIT_DEFAULT = 100;
127563
+ function stripLocalDate(d) {
127564
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
127565
+ }
127566
+ function parseLocalYmd(ymd) {
127567
+ const parts = ymd.trim().split("-").map((x) => parseInt(x, 10));
127568
+ if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) {
127569
+ return /* @__PURE__ */ new Date(NaN);
127988
127570
  }
127989
- throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
127571
+ const [y, m, day] = parts;
127572
+ return new Date(y, m - 1, day);
127990
127573
  }
127991
- function injectReportData2(html, payload) {
127992
- const json = JSON.stringify(payload).replace(/</g, "\\u003c");
127993
- if (!html.includes(INJECT_MARKER)) {
127994
- throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 meta-period-report.html");
127574
+ function formatLocalYmd(d) {
127575
+ const y = d.getFullYear();
127576
+ const m = String(d.getMonth() + 1).padStart(2, "0");
127577
+ const day = String(d.getDate()).padStart(2, "0");
127578
+ return `${y}-${m}-${day}`;
127579
+ }
127580
+ function bingRangeTouchesTodayOrYesterday(startDate, endDate) {
127581
+ const s = parseLocalYmd(startDate);
127582
+ const e = parseLocalYmd(endDate);
127583
+ if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime()) || s > e) {
127584
+ return true;
127995
127585
  }
127996
- return html.replace(INJECT_MARKER, `window.__META_PERIOD_REPORT__ = ${json};`);
127586
+ const today = stripLocalDate(/* @__PURE__ */ new Date());
127587
+ const yesterday = new Date(today);
127588
+ yesterday.setDate(yesterday.getDate() - 1);
127589
+ for (let cur = new Date(s); cur <= e; cur.setDate(cur.getDate() + 1)) {
127590
+ const c = stripLocalDate(cur);
127591
+ if (c.getTime() === today.getTime() || c.getTime() === yesterday.getTime()) {
127592
+ return true;
127593
+ }
127594
+ }
127595
+ return false;
127997
127596
  }
127998
- async function runFacebookAnalysisRender(opts) {
127999
- const dataPath = path29.resolve(opts.dataFile);
128000
- let data = asRecord10(await readJsonFile2(dataPath));
128001
- if (opts.snapshotDir?.trim()) {
128002
- data = await mergeFacebookSnapshotIntoReport(data, path29.resolve(opts.snapshotDir));
127597
+ function resolveBingDateRange(start, end) {
127598
+ if (start && end) {
127599
+ const s = parseLocalYmd(start);
127600
+ const e = parseLocalYmd(end);
127601
+ if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime())) {
127602
+ console.error("\n\u274C --start / --end \u987B\u4E3A\u6709\u6548\u65E5\u671F YYYY-MM-DD\u3002\n");
127603
+ process.exit(1);
127604
+ }
127605
+ if (s > e) {
127606
+ console.error("\n\u274C \u5F00\u59CB\u65E5\u671F\u4E0D\u80FD\u665A\u4E8E\u7ED3\u675F\u65E5\u671F\u3002\n");
127607
+ process.exit(1);
127608
+ }
127609
+ if (bingRangeTouchesTodayOrYesterday(start, end)) {
127610
+ console.error(
127611
+ "\n\u274C Bing \u62A5\u8868\u65F6\u95F4\u8303\u56F4\u4E0D\u80FD\u5305\u542B\u300C\u4ECA\u5929\u300D\u6216\u300C\u6628\u5929\u300D\uFF08\u63A5\u53E3\u9650\u5236\uFF09\u3002\u8BF7\u6539\u7528\u66F4\u65E9\u7684\u7ED3\u675F\u65E5\u671F\uFF0C\u4F8B\u5982\u524D\u5929\u53CA\u4EE5\u524D\u3002\n"
127612
+ );
127613
+ process.exit(1);
127614
+ }
127615
+ return { startDate: start, endDate: end };
128003
127616
  }
128004
- const contentCheck = validateMetaPeriodReportContent(data);
128005
- if (!contentCheck.ok) {
128006
- console.error(`
128007
- \u274C ${formatMetaPeriodReportErrors(contentCheck)}
128008
- `);
128009
- const { stats: stats2 } = contentCheck;
128010
- console.error(
128011
- ` \u7EDF\u8BA1\uFF1A\u5EFA\u8BAE ${stats2.recommendationCount}/4\uFF5C\u8865\u5145 ${stats2.supplementaryCount}/7\uFF5C\u56DB\u95EE ${stats2.fourQuestionsCount}/4\uFF5C\u9636\u6BB5 ${stats2.lifecyclePhase ?? "\u2014"}${stats2.lifecyclePhaseInferred ? `\uFF08\u63A8\u65AD ${stats2.lifecyclePhaseInferred}\uFF09` : ""}\uFF5C\u7EA2\u7EFF\u706F ${stats2.scorecardValidRows}/${stats2.scorecardRows} \u6709\u6548\u884C\uFF5C\u533A\u57DF\u53D9\u4E8B ${stats2.regionalNarratives}/${stats2.adSetsWithSpend}
128012
- `
128013
- );
127617
+ if (!start && !end) {
127618
+ const endD = stripLocalDate(/* @__PURE__ */ new Date());
127619
+ endD.setDate(endD.getDate() - 2);
127620
+ const startD = new Date(endD);
127621
+ startD.setDate(startD.getDate() - 6);
127622
+ return { startDate: formatLocalYmd(startD), endDate: formatLocalYmd(endD) };
127623
+ }
127624
+ console.error(
127625
+ "\n\u274C --start \u4E0E --end \u987B\u540C\u65F6\u4F20\u5165\uFF0C\u6216\u540C\u65F6\u7701\u7565\u4EE5\u4F7F\u7528\u9ED8\u8BA4\u533A\u95F4\uFF08\u622A\u81F3\u524D\u5929\u5171 7 \u5929\uFF09\u3002\n"
127626
+ );
127627
+ process.exit(1);
127628
+ }
127629
+ function assertBingAccountId(id) {
127630
+ const t = id.trim();
127631
+ if (!t) {
127632
+ console.error("\n\u274C --account \u4E0D\u80FD\u4E3A\u7A7A\u3002\n");
128014
127633
  process.exit(1);
128015
127634
  }
128016
- const templatePath = metaPeriodReportTemplatePath();
128017
- let html;
128018
- try {
128019
- html = await fsPromises2.readFile(templatePath, "utf8");
128020
- } catch {
128021
- console.error(`
128022
- \u274C \u672A\u627E\u5230\u62A5\u544A\u6A21\u677F\uFF1A${templatePath}
128023
- \u8BF7\u5148\u6267\u884C npm run build
128024
- `);
127635
+ if (!/^\d+$/.test(t)) {
127636
+ console.error("\n\u274C --account \u987B\u4E3A\u6570\u5B57 mediaCustomerId\uFF08\u4E0E list-accounts -m BingV2 \u4E00\u81F4\uFF09\u3002\n");
128025
127637
  process.exit(1);
128026
127638
  }
128027
- const outPath = path29.resolve(
128028
- opts.out ?? path29.join(path29.dirname(dataPath), "meta-period-report.html")
127639
+ return t;
127640
+ }
127641
+ function reportingUrl(config, accountId, segment, query) {
127642
+ const q = query ? query.startsWith("?") ? query : `?${query}` : "";
127643
+ return `${config.apiBaseUrl}/reporting/media-account/BingV2/${accountId}/${segment}${q}`;
127644
+ }
127645
+ function resolveLimit(raw) {
127646
+ const n = typeof raw === "number" ? raw : parseInt(String(raw), 10);
127647
+ return Number.isFinite(n) && n > 0 ? n : BING_KEYWORD_LIMIT_DEFAULT;
127648
+ }
127649
+ async function fetchBingJson(config, url, verbose) {
127650
+ return apiFetch2(url, config, {}, verbose);
127651
+ }
127652
+ async function fetchBingOverviewPayload(config, id, startDate, endDate, verbose) {
127653
+ const params = new URLSearchParams({ startDate, endDate });
127654
+ const url = reportingUrl(config, id, "OverviewSectionData", params.toString());
127655
+ const raw = await fetchBingJson(config, url, verbose);
127656
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return raw;
127657
+ const withBalance = await enrichOverviewRecordWithRealtimeBalance(
127658
+ "BingV2",
127659
+ id,
127660
+ config,
127661
+ raw,
127662
+ verbose
128029
127663
  );
128030
- const outDir = path29.dirname(outPath);
128031
- await fsPromises2.mkdir(outDir, { recursive: true });
128032
- await fsPromises2.writeFile(outPath, injectReportData2(html, data), "utf8");
128033
- const runtimeSrc = metaPeriodReportRuntimePath();
128034
- const runtimeOut = path29.join(outDir, TEMPLATE_BASENAMES2.runtime);
128035
- let runtimeCopied = false;
127664
+ const withDerived = enrichBingOverviewDerivedMetrics(withBalance, startDate, endDate);
127665
+ const withPrevious = await enrichBingOverviewPreviousPeriod(
127666
+ config,
127667
+ id,
127668
+ withDerived,
127669
+ startDate,
127670
+ endDate,
127671
+ verbose
127672
+ );
127673
+ return syncOverviewBalanceFields(withPrevious);
127674
+ }
127675
+ async function enrichBingOverviewPreviousPeriod(config, accountId, record, startDate, endDate, verbose) {
127676
+ if (!isBingPeriodBlockEmpty(record.previousPeriod)) {
127677
+ return record;
127678
+ }
127679
+ const prevRange = calcBingPreviousPeriodRange(startDate, endDate);
127680
+ if (!prevRange) return record;
127681
+ if (bingRangeTouchesTodayOrYesterday(prevRange.startDate, prevRange.endDate)) {
127682
+ return record;
127683
+ }
128036
127684
  try {
128037
- await fsPromises2.copyFile(runtimeSrc, runtimeOut);
128038
- runtimeCopied = true;
127685
+ const params = new URLSearchParams({
127686
+ startDate: prevRange.startDate,
127687
+ endDate: prevRange.endDate
127688
+ });
127689
+ const url = reportingUrl(config, accountId, "OverviewSectionData", params.toString());
127690
+ const prevRaw = await fetchBingJson(config, url, verbose);
127691
+ if (!prevRaw || typeof prevRaw !== "object" || Array.isArray(prevRaw)) {
127692
+ return record;
127693
+ }
127694
+ const prevRecord = prevRaw;
127695
+ const prevPeriod = prevRecord.currentPeriod;
127696
+ if (isBingPeriodBlockEmpty(prevPeriod)) {
127697
+ return record;
127698
+ }
127699
+ return {
127700
+ ...record,
127701
+ previousPeriod: prevPeriod,
127702
+ previousPeriodDateRange: prevRange,
127703
+ _previousPeriodSource: "OverviewSectionData-second-fetch"
127704
+ };
128039
127705
  } catch {
127706
+ return record;
128040
127707
  }
128041
- const { stats } = contentCheck;
128042
- console.log(`
128043
- \u2705 Meta/Facebook \u5468\u671F\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
128044
- `);
128045
- console.log(
128046
- ` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u5EFA\u8BAE ${stats.recommendationCount}\uFF5C\u8865\u5145 ${stats.supplementaryCount}\uFF5C\u56DB\u95EE ${stats.fourQuestionsCount}\uFF5C\u9636\u6BB5 ${stats.lifecyclePhase ?? "\u2014"}\uFF5C\u7EA2\u7EFF\u706F ${stats.scorecardValidRows} \u6709\u6548\u884C
128047
- `
128048
- );
128049
- if (runtimeCopied) {
128050
- console.log(` \u8FD0\u884C\u65F6\u811A\u672C\uFF1A${runtimeOut}
128051
- `);
127708
+ }
127709
+ async function fetchBingAudienceMergedPayload(config, id, startDate, endDate, verbose) {
127710
+ const params = new URLSearchParams({ startDate, endDate });
127711
+ const ageUrl = reportingUrl(config, id, "AgeAudienceData", params.toString());
127712
+ const genderUrl = reportingUrl(config, id, "GenderAudienceData", params.toString());
127713
+ const [ageData, genderData] = await Promise.all([
127714
+ fetchBingJson(config, ageUrl, verbose),
127715
+ fetchBingJson(config, genderUrl, verbose)
127716
+ ]);
127717
+ return {
127718
+ mergedParts: ["AgeAudienceData", "GenderAudienceData"],
127719
+ startDate,
127720
+ endDate,
127721
+ account: id,
127722
+ data: { ageAudience: ageData, genderAudience: genderData }
127723
+ };
127724
+ }
127725
+ async function fetchBingSectionPayload(def, opts, config, id) {
127726
+ const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
127727
+ const verbose = !!opts.verbose;
127728
+ if (def.name === "overview") {
127729
+ return fetchBingOverviewPayload(config, id, startDate, endDate, verbose);
128052
127730
  }
128053
- console.log(
128054
- "\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u56FE\u8868\u4E0E\u5B8C\u6574\u7AE0\u8282\uFF08ECharts\uFF1Astaticpn.siluzan.com/homeCDN/echarts.js\uFF09\u3002\n"
128055
- );
127731
+ if (def.name === "audience-merged") {
127732
+ return fetchBingAudienceMergedPayload(config, id, startDate, endDate, verbose);
127733
+ }
127734
+ const params = new URLSearchParams({ startDate, endDate });
127735
+ if (def.limitOption) {
127736
+ params.set("limit", String(resolveLimit(opts.limit)));
127737
+ params.set("orderByCost", "true");
127738
+ }
127739
+ const url = reportingUrl(config, id, def.segment, params.toString());
127740
+ return fetchBingJson(config, url, verbose);
127741
+ }
127742
+ function endpointHintForBingSection(def) {
127743
+ return `GET \u2026/BingV2/{id}/${def.segment}`;
128056
127744
  }
128057
127745
 
128058
- // src/commands/facebook-analysis/run-batch.ts
127746
+ // src/commands/bing-analysis/run-batch.ts
128059
127747
  init_auth();
128060
- import * as path30 from "path";
128061
- import { performance as performance5 } from "perf_hooks";
127748
+ import * as path31 from "path";
127749
+ import { performance as performance6 } from "perf_hooks";
128062
127750
  init_version();
127751
+ async function runWithConcurrency3(tasks, concurrency) {
127752
+ const results = new Array(tasks.length);
127753
+ let cursor = 0;
127754
+ const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
127755
+ while (true) {
127756
+ const i = cursor++;
127757
+ if (i >= tasks.length) return;
127758
+ results[i] = await tasks[i]();
127759
+ }
127760
+ });
127761
+ await Promise.all(workers);
127762
+ return results;
127763
+ }
127764
+ async function runAllBingSections(opts) {
127765
+ const id = assertBingAccountId(opts.account);
127766
+ const config = loadConfig(opts.token);
127767
+ if (!opts.jsonOut?.trim()) {
127768
+ console.error("\n\u274C \u5FC5\u987B\u4F20 --json-out <\u76EE\u5F55>\uFF08\u5404\u7EF4\u5EA6\u843D\u76D8\u540E\u7531 report-manifest \u7D22\u5F15\uFF09\u3002\n");
127769
+ process.exit(1);
127770
+ }
127771
+ const targets = resolveSectionList3(opts.sections, opts.exclude);
127772
+ if (targets.length === 0) {
127773
+ console.error("\n\u274C \u6CA1\u6709\u8981\u6267\u884C\u7684 section\uFF08\u8BF7\u68C0\u67E5 --sections / --exclude\uFF09\u3002\n");
127774
+ process.exit(1);
127775
+ }
127776
+ const { startDate, endDate } = resolveBingDateRange(opts.start, opts.end);
127777
+ const concurrency = Math.max(
127778
+ 1,
127779
+ Math.min(typeof opts.concurrency === "number" ? opts.concurrency : 5, 16)
127780
+ );
127781
+ const cliVersion = getCurrentVersion2();
127782
+ const overallT0 = performance6.now();
127783
+ const tasks = targets.map((def) => async () => {
127784
+ const t0 = performance6.now();
127785
+ const hint = endpointHintForBingSection(def);
127786
+ try {
127787
+ const payload = await fetchBingSectionPayload(
127788
+ def,
127789
+ { ...opts, start: startDate, end: endDate },
127790
+ config,
127791
+ id
127792
+ );
127793
+ const summary2 = await writeReportAnalysisSnapshot({
127794
+ snapshotDir: opts.jsonOut,
127795
+ section: def.name,
127796
+ accountId: id,
127797
+ dateRange: { start: startDate, end: endDate },
127798
+ payload,
127799
+ cliVersion
127800
+ });
127801
+ return {
127802
+ section: def.name,
127803
+ ok: true,
127804
+ elapsedMs: performance6.now() - t0,
127805
+ file: summary2.writtenFiles[0],
127806
+ endpointHint: hint
127807
+ };
127808
+ } catch (err) {
127809
+ return {
127810
+ section: def.name,
127811
+ ok: false,
127812
+ elapsedMs: performance6.now() - t0,
127813
+ error: err instanceof Error ? err.message : String(err),
127814
+ endpointHint: hint
127815
+ };
127816
+ }
127817
+ });
127818
+ const results = await runWithConcurrency3(tasks, concurrency);
127819
+ const succeeded = results.filter((r) => r.ok).length;
127820
+ const failed = results.length - succeeded;
127821
+ const absoluteSnapshotDir = path31.resolve(opts.jsonOut);
127822
+ const summary = {
127823
+ kind: "siluzan-tso-bing-analysis-snapshot-batch",
127824
+ absoluteSnapshotDir,
127825
+ manifestFile: reportManifestFile(id),
127826
+ accountId: id,
127827
+ totalSections: results.length,
127828
+ succeeded,
127829
+ failed,
127830
+ concurrency,
127831
+ elapsedMs: performance6.now() - overallT0,
127832
+ results
127833
+ };
127834
+ console.log(JSON.stringify(summary));
127835
+ if (failed > 0) process.exit(2);
127836
+ }
128063
127837
 
128064
- // src/commands/facebook-analysis/resolve-sections.ts
128065
- function normalizeSectionToken(raw) {
127838
+ // src/commands/bing-analysis/register-cli.ts
127839
+ import { Command as Command2 } from "commander";
127840
+ function registerBatchCommand2(sectionHelp, aliasHelp) {
127841
+ return new Command2("run").description(
127842
+ "\u6279\u91CF\u62C9\u53D6 TSO reporting/media-account/BingV2 Section \u6570\u636E\uFF08\u7701\u7565\u5B50\u547D\u4EE4\u540D\u65F6\u9ED8\u8BA4\u6267\u884C\u672C\u547D\u4EE4\uFF09"
127843
+ ).requiredOption(
127844
+ "-a, --account <id>",
127845
+ "Bing mediaCustomerId\uFF08\u6570\u5B57\uFF0C\u4E0E list-accounts -m BingV2 \u4E00\u81F4\uFF09"
127846
+ ).requiredOption(
127847
+ "--json-out <dir>",
127848
+ "\u7ED3\u679C\u843D\u76D8\u76EE\u5F55\uFF1B\u6BCF\u7EF4\u5EA6 `<section>-<accountId>.json` + `report-manifest-<accountId>.json`"
127849
+ ).option("-t, --token <token>", "Auth Token").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\u7565\uFF1B\u4E0D\u53EF\u542B\u4ECA\u5929/\u6628\u5929\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option(
127850
+ "--sections <list>",
127851
+ `\u4EC5\u6267\u884C\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u5982 overview,campaigns,keywords\uFF1B\u7701\u7565=\u5168\u90E8 11 \u4E2A\u3002\u53EF\u9009\uFF1A${sectionHelp}\uFF1B\u522B\u540D\uFF1A${aliasHelp}`
127852
+ ).option("--exclude <list>", "\u6392\u9664\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u4E0E --sections \u53EF\u53E0\u52A0").option(
127853
+ "--limit <n>",
127854
+ "\u4EC5 keywords / search-terms\uFF1A\u6761\u6570\u4E0A\u9650\uFF08\u9ED8\u8BA4 100\uFF0C\u4E0E\u524D\u7AEF KeywordReport/SearchQueryReport \u4E00\u81F4\uFF09",
127855
+ (v) => parseInt(v, 10)
127856
+ ).option("--concurrency <n>", "\u5E76\u53D1\u6570\uFF0C\u9ED8\u8BA4 5\uFF0C\u4E0A\u9650 16", (v) => parseInt(v, 10)).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
127857
+ await runAllBingSections(opts);
127858
+ });
127859
+ }
127860
+ function registerBingAnalysisCommands(program2) {
127861
+ const sectionHelp = BING_SECTION_NAMES.join(", ");
127862
+ const aliasHelp = Object.keys(BING_SECTION_ALIASES).join(", ");
127863
+ const root = program2.command("bing-analysis").description("Bing\uFF08BingV2\uFF09\u8D26\u6237\u5206\u6790\uFF1A\u6279\u91CF\u62C9\u53D6 Section \u6570\u636E\uFF08\u9ED8\u8BA4\uFF09\uFF1B\u65E5\u671F\u4E0D\u53EF\u542B\u4ECA\u5929/\u6628\u5929");
127864
+ root.addCommand(registerBatchCommand2(sectionHelp, aliasHelp), { isDefault: true });
127865
+ }
127866
+
127867
+ // src/commands/tiktok-analysis/sections.ts
127868
+ var TIKTOK_SECTIONS = [
127869
+ {
127870
+ name: "overview",
127871
+ description: "\u8D26\u6237\u603B\u89C8 OverviewSectionData",
127872
+ segment: "OverviewSectionData"
127873
+ },
127874
+ {
127875
+ name: "campaigns",
127876
+ description: "\u5E7F\u544A\u7CFB\u5217\u5206\u6BB5 CampaignSectionData\uFF08\u9ED8\u8BA4 take=100\uFF09",
127877
+ segment: "CampaignSectionData",
127878
+ takeOption: true
127879
+ },
127880
+ {
127881
+ name: "ad-groups",
127882
+ description: "\u5E7F\u544A\u7EC4\u62A5\u8868 AdGroupReport",
127883
+ segment: "AdGroupReport",
127884
+ takeOption: true
127885
+ },
127886
+ {
127887
+ name: "ads",
127888
+ description: "\u5E7F\u544A\u62A5\u8868 AdReport",
127889
+ segment: "AdReport",
127890
+ takeOption: true
127891
+ },
127892
+ {
127893
+ name: "videos",
127894
+ description: "\u89C6\u9891\u7D20\u6750\u62A5\u8868 VideoReport",
127895
+ segment: "VideoReport",
127896
+ takeOption: true
127897
+ },
127898
+ {
127899
+ name: "audience-gender",
127900
+ description: "\u53D7\u4F17\uFF08\u6027\u522B\uFF09AudienceReport dimensions=gender",
127901
+ segment: "AudienceReport",
127902
+ audienceDimension: "gender",
127903
+ takeOption: true
127904
+ },
127905
+ {
127906
+ name: "audience-age",
127907
+ description: "\u53D7\u4F17\uFF08\u5E74\u9F84\uFF09AudienceReport dimensions=age",
127908
+ segment: "AudienceReport",
127909
+ audienceDimension: "age",
127910
+ takeOption: true
127911
+ },
127912
+ {
127913
+ name: "audience-interest-category",
127914
+ description: "\u53D7\u4F17\uFF08\u5174\u8DA3\u7C7B\u76EE\uFF09AudienceReport dimensions=interest_category",
127915
+ segment: "AudienceReport",
127916
+ audienceDimension: "interest_category",
127917
+ takeOption: true
127918
+ },
127919
+ {
127920
+ name: "audience-country-code",
127921
+ description: "\u53D7\u4F17\uFF08\u56FD\u5BB6/\u5730\u533A\uFF09AudienceReport dimensions=country_code",
127922
+ segment: "AudienceReport",
127923
+ audienceDimension: "country_code",
127924
+ takeOption: true
127925
+ },
127926
+ {
127927
+ name: "audience-platform",
127928
+ description: "\u53D7\u4F17\uFF08\u5E73\u53F0\uFF09AudienceReport dimensions=platform",
127929
+ segment: "AudienceReport",
127930
+ audienceDimension: "platform",
127931
+ takeOption: true
127932
+ },
127933
+ {
127934
+ name: "audience-language",
127935
+ description: "\u53D7\u4F17\uFF08\u8BED\u8A00\uFF09AudienceReport dimensions=language",
127936
+ segment: "AudienceReport",
127937
+ audienceDimension: "language",
127938
+ takeOption: true
127939
+ },
127940
+ {
127941
+ name: "audience-merged",
127942
+ description: "\u53D7\u4F17\u5408\u5E76\uFF08gender + age + interest_category \u4E09\u6B21 AudienceReport \u5408\u5E76\uFF0C\u4E0E getTikTokAudienceApi \u4E00\u81F4\uFF09",
127943
+ segment: "AudienceReport(gender+age+interest_category)",
127944
+ takeOption: true
127945
+ }
127946
+ ];
127947
+ var TIKTOK_SECTION_NAMES = TIKTOK_SECTIONS.map((s) => s.name);
127948
+ var TIKTOK_SECTION_ALIASES = {
127949
+ "ad-groups-report": "ad-groups",
127950
+ audience: "audience-merged"
127951
+ };
127952
+
127953
+ // src/commands/tiktok-analysis/resolve-sections.ts
127954
+ function normalizeSectionToken3(raw) {
128066
127955
  const t = raw.trim();
128067
127956
  if (!t) return null;
128068
- if (FACEBOOK_SECTION_NAMES.includes(t)) {
127957
+ if (TIKTOK_SECTION_NAMES.includes(t)) {
128069
127958
  return t;
128070
127959
  }
128071
- return FACEBOOK_SECTION_ALIASES[t] ?? null;
127960
+ return TIKTOK_SECTION_ALIASES[t] ?? null;
128072
127961
  }
128073
- function resolveSectionList2(sections, exclude) {
127962
+ function resolveSectionList4(sections, exclude) {
128074
127963
  const splitClean = (s) => (s ?? "").split(",").map((x) => x.trim()).filter(Boolean);
128075
127964
  const include = splitClean(sections);
128076
127965
  const dropRaw = splitClean(exclude);
128077
- const unknown = [...include, ...dropRaw].filter((n) => normalizeSectionToken(n) === null);
127966
+ const unknown = [...include, ...dropRaw].filter((n) => normalizeSectionToken3(n) === null);
128078
127967
  if (unknown.length > 0) {
128079
127968
  console.error(
128080
127969
  `
128081
127970
  \u274C \u672A\u77E5\u7684 section \u540D\u79F0\uFF1A${unknown.join(", ")}
128082
- \u53EF\u9009\uFF1A${FACEBOOK_SECTION_NAMES.join(", ")}
128083
- Google \u522B\u540D\uFF1A${Object.keys(FACEBOOK_SECTION_ALIASES).join(", ")}
127971
+ \u53EF\u9009\uFF1A${TIKTOK_SECTION_NAMES.join(", ")}
127972
+ \u522B\u540D\uFF1A${Object.keys(TIKTOK_SECTION_ALIASES).join(", ")}
128084
127973
  `
128085
127974
  );
128086
127975
  process.exit(1);
128087
127976
  }
128088
- const includeCanonical = new Set(include.map((n) => normalizeSectionToken(n)).filter(Boolean));
128089
- const dropCanonical = new Set(dropRaw.map((n) => normalizeSectionToken(n)).filter(Boolean));
128090
- const base = include.length > 0 ? FACEBOOK_SECTIONS.filter((s) => includeCanonical.has(s.name)) : [...FACEBOOK_SECTIONS];
127977
+ const includeCanonical = new Set(include.map((n) => normalizeSectionToken3(n)).filter(Boolean));
127978
+ const dropCanonical = new Set(dropRaw.map((n) => normalizeSectionToken3(n)).filter(Boolean));
127979
+ const base = include.length > 0 ? TIKTOK_SECTIONS.filter((s) => includeCanonical.has(s.name)) : [...TIKTOK_SECTIONS];
128091
127980
  return base.filter((s) => !dropCanonical.has(s.name));
128092
127981
  }
128093
127982
 
128094
- // src/commands/facebook-analysis/run-batch.ts
128095
- async function runWithConcurrency2(tasks, concurrency) {
127983
+ // src/commands/tiktok-analysis/fetch.ts
127984
+ init_auth();
127985
+ var TIKTOK_TAKE_DEFAULT = 100;
127986
+ function resolveTake(raw) {
127987
+ const n = typeof raw === "number" ? raw : parseInt(String(raw), 10);
127988
+ return Number.isFinite(n) && n > 0 ? n : TIKTOK_TAKE_DEFAULT;
127989
+ }
127990
+ function resolveDateRange2(start, end) {
127991
+ if (start && end) return { startDate: start, endDate: end };
127992
+ if (!start && !end) {
127993
+ const endD = /* @__PURE__ */ new Date();
127994
+ endD.setDate(endD.getDate() - 1);
127995
+ const startD = new Date(endD);
127996
+ startD.setDate(startD.getDate() - 6);
127997
+ const fmt2 = (d) => d.toISOString().slice(0, 10);
127998
+ return { startDate: fmt2(startD), endDate: fmt2(endD) };
127999
+ }
128000
+ console.error("\n\u274C --start \u4E0E --end \u987B\u540C\u65F6\u4F20\u5165\uFF0C\u6216\u540C\u65F6\u7701\u7565\u4EE5\u4F7F\u7528\u9ED8\u8BA4\u8FD1 7 \u5929\uFF08\u622A\u81F3\u6628\u5929\uFF09\u3002\n");
128001
+ process.exit(1);
128002
+ }
128003
+ function assertTikTokAccountId(id) {
128004
+ const t = id.trim();
128005
+ if (!t) {
128006
+ console.error("\n\u274C --account \u4E0D\u80FD\u4E3A\u7A7A\u3002\n");
128007
+ process.exit(1);
128008
+ }
128009
+ if (!/^\d+$/.test(t)) {
128010
+ console.error(
128011
+ "\n\u274C --account \u987B\u4E3A TikTok \u5E7F\u544A\u4E3B ID\uFF08\u6570\u5B57 mediaCustomerId\uFF0C\u4E0E list-accounts -m TikTok \u4E00\u81F4\uFF09\u3002\n"
128012
+ );
128013
+ process.exit(1);
128014
+ }
128015
+ return t;
128016
+ }
128017
+ function reportingUrl2(config, accountId, segment, query) {
128018
+ const q = query ? query.startsWith("?") ? query : `?${query}` : "";
128019
+ return `${config.apiBaseUrl}/reporting/media-account/TikTok/${accountId}/${segment}${q}`;
128020
+ }
128021
+ async function fetchTikTokJson(config, url, verbose) {
128022
+ return apiFetch2(url, config, {}, verbose);
128023
+ }
128024
+ async function fetchTikTokAudienceMergedPayload(config, id, startDate, endDate, take, verbose) {
128025
+ const dims = ["gender", "age", "interest_category"];
128026
+ const parts = {};
128027
+ for (const dimension of dims) {
128028
+ const params = new URLSearchParams({
128029
+ startDate,
128030
+ endDate,
128031
+ take: String(take),
128032
+ dimensions: dimension
128033
+ });
128034
+ const url = reportingUrl2(config, id, "AudienceReport", params.toString());
128035
+ parts[dimension] = await fetchTikTokJson(config, url, verbose);
128036
+ }
128037
+ return { mergedDimensions: dims, startDate, endDate, account: id, data: parts };
128038
+ }
128039
+ async function fetchTikTokSectionPayload(def, opts, config, id) {
128040
+ const { startDate, endDate } = resolveDateRange2(opts.start, opts.end);
128041
+ const verbose = !!opts.verbose;
128042
+ const take = resolveTake(opts.take);
128043
+ if (def.name === "audience-merged") {
128044
+ return fetchTikTokAudienceMergedPayload(config, id, startDate, endDate, take, verbose);
128045
+ }
128046
+ const params = new URLSearchParams({ startDate, endDate });
128047
+ if (def.takeOption) params.set("take", String(take));
128048
+ if (def.audienceDimension) params.set("dimensions", def.audienceDimension);
128049
+ const url = reportingUrl2(config, id, def.segment, params.toString());
128050
+ return fetchTikTokJson(config, url, verbose);
128051
+ }
128052
+ function endpointHintForTikTokSection(def) {
128053
+ return `GET \u2026/TikTok/{id}/${def.segment}`;
128054
+ }
128055
+
128056
+ // src/commands/tiktok-analysis/auxiliary.ts
128057
+ init_auth();
128058
+ async function fetchTikTokAreacode(config, verbose) {
128059
+ const base = config.mainApiUrl?.trim();
128060
+ if (!base) {
128061
+ console.error("\n\u274C \u65E0\u6CD5\u63A8\u5BFC mainApiUrl\uFF0C\u8BF7\u68C0\u67E5 tsoApiBaseUrl \u914D\u7F6E\u3002\n");
128062
+ process.exit(1);
128063
+ }
128064
+ const url = `${base}/query/media-account/tiktok/TikTokAreacode/Read`;
128065
+ return apiFetch2(url, config, {}, verbose);
128066
+ }
128067
+ async function fetchTikTokInterestList(config, account, verbose) {
128068
+ const id = assertTikTokAccountId(account);
128069
+ const tt = config.tiktokApiUrl?.trim();
128070
+ if (!tt) {
128071
+ console.error("\n\u274C \u672A\u914D\u7F6E tiktokApiUrl\u3002\n");
128072
+ process.exit(1);
128073
+ }
128074
+ const params = new URLSearchParams({ mediaCustomerId: id });
128075
+ const url = `${tt}/tiktokadgroupmanagement/adgroup/tiktok/GetInterestList?${params}`;
128076
+ const data = await apiFetch2(url, config, {}, verbose);
128077
+ return { id, data };
128078
+ }
128079
+
128080
+ // src/commands/tiktok-analysis/run-batch.ts
128081
+ init_auth();
128082
+ import * as path32 from "path";
128083
+ import { performance as performance7 } from "perf_hooks";
128084
+ init_version();
128085
+ async function runWithConcurrency4(tasks, concurrency) {
128096
128086
  const results = new Array(tasks.length);
128097
128087
  let cursor = 0;
128098
128088
  const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
@@ -128105,35 +128095,40 @@ async function runWithConcurrency2(tasks, concurrency) {
128105
128095
  await Promise.all(workers);
128106
128096
  return results;
128107
128097
  }
128108
- async function runAllFacebookSections(opts) {
128109
- const { apiId, manifestId } = normalizeFacebookAccountId(opts.account);
128098
+ async function runAllTikTokSections(opts) {
128099
+ const id = assertTikTokAccountId(opts.account);
128110
128100
  const config = loadConfig(opts.token);
128111
128101
  if (!opts.jsonOut?.trim()) {
128112
128102
  console.error("\n\u274C \u5FC5\u987B\u4F20 --json-out <\u76EE\u5F55>\uFF08\u5404\u7EF4\u5EA6\u843D\u76D8\u540E\u7531 report-manifest \u7D22\u5F15\uFF09\u3002\n");
128113
128103
  process.exit(1);
128114
128104
  }
128115
- const targets = resolveSectionList2(opts.sections, opts.exclude);
128105
+ const targets = resolveSectionList4(opts.sections, opts.exclude);
128116
128106
  if (targets.length === 0) {
128117
128107
  console.error("\n\u274C \u6CA1\u6709\u8981\u6267\u884C\u7684 section\uFF08\u8BF7\u68C0\u67E5 --sections / --exclude\uFF09\u3002\n");
128118
128108
  process.exit(1);
128119
128109
  }
128120
- const { startDate, endDate } = resolveFacebookDateRange(opts.start, opts.end);
128110
+ const { startDate, endDate } = resolveDateRange2(opts.start, opts.end);
128121
128111
  const concurrency = Math.max(
128122
128112
  1,
128123
128113
  Math.min(typeof opts.concurrency === "number" ? opts.concurrency : 5, 16)
128124
128114
  );
128125
128115
  const cliVersion = getCurrentVersion2();
128126
- const overallT0 = performance5.now();
128116
+ const overallT0 = performance7.now();
128127
128117
  const tasks = targets.map(
128128
128118
  (def) => async () => {
128129
- const t0 = performance5.now();
128130
- const hint = endpointHintForFacebookSection(def, apiId);
128119
+ const t0 = performance7.now();
128120
+ const hint = endpointHintForTikTokSection(def);
128131
128121
  try {
128132
- const payload = await fetchFacebookSectionPayload(def, opts, config, apiId);
128122
+ const payload = await fetchTikTokSectionPayload(
128123
+ def,
128124
+ { ...opts, start: startDate, end: endDate },
128125
+ config,
128126
+ id
128127
+ );
128133
128128
  const summary2 = await writeReportAnalysisSnapshot({
128134
128129
  snapshotDir: opts.jsonOut,
128135
128130
  section: def.name,
128136
- accountId: manifestId,
128131
+ accountId: id,
128137
128132
  dateRange: { start: startDate, end: endDate },
128138
128133
  payload,
128139
128134
  cliVersion
@@ -128141,7 +128136,7 @@ async function runAllFacebookSections(opts) {
128141
128136
  return {
128142
128137
  section: def.name,
128143
128138
  ok: true,
128144
- elapsedMs: performance5.now() - t0,
128139
+ elapsedMs: performance7.now() - t0,
128145
128140
  file: summary2.writtenFiles[0],
128146
128141
  endpointHint: hint
128147
128142
  };
@@ -128149,69 +128144,118 @@ async function runAllFacebookSections(opts) {
128149
128144
  return {
128150
128145
  section: def.name,
128151
128146
  ok: false,
128152
- elapsedMs: performance5.now() - t0,
128147
+ elapsedMs: performance7.now() - t0,
128153
128148
  error: err instanceof Error ? err.message : String(err),
128154
128149
  endpointHint: hint
128155
128150
  };
128156
128151
  }
128157
128152
  }
128158
128153
  );
128159
- const results = await runWithConcurrency2(tasks, concurrency);
128154
+ const results = await runWithConcurrency4(tasks, concurrency);
128160
128155
  const succeeded = results.filter((r) => r.ok).length;
128161
128156
  const failed = results.length - succeeded;
128162
- const absoluteSnapshotDir = path30.resolve(opts.jsonOut);
128157
+ const absoluteSnapshotDir = path32.resolve(opts.jsonOut);
128163
128158
  const summary = {
128164
- kind: "siluzan-tso-facebook-analysis-snapshot-batch",
128159
+ kind: "siluzan-tso-tiktok-analysis-snapshot-batch",
128165
128160
  absoluteSnapshotDir,
128166
- manifestFile: reportManifestFile(manifestId),
128167
- accountId: manifestId,
128161
+ manifestFile: reportManifestFile(id),
128162
+ accountId: id,
128168
128163
  totalSections: results.length,
128169
128164
  succeeded,
128170
128165
  failed,
128171
128166
  concurrency,
128172
- elapsedMs: performance5.now() - overallT0,
128167
+ elapsedMs: performance7.now() - overallT0,
128173
128168
  results
128174
128169
  };
128175
128170
  console.log(JSON.stringify(summary));
128176
128171
  if (failed > 0) process.exit(2);
128177
128172
  }
128178
128173
 
128179
- // src/commands/facebook-analysis/register-cli.ts
128180
- function registerBatchCommand(sectionHelp) {
128181
- return new Command("run").description(
128182
- "\u6279\u91CF\u62C9\u53D6 TSO reporting/media-account/FacebookAds Section \u6570\u636E\uFF08\u7701\u7565\u5B50\u547D\u4EE4\u540D\u65F6\u9ED8\u8BA4\u6267\u884C\u672C\u547D\u4EE4\uFF09"
128183
- ).requiredOption("-a, --account <id>", "Facebook \u5E7F\u544A\u8D26\u6237 mediaCustomerId\uFF08\u6570\u5B57\u6216 act_<\u6570\u5B57>\uFF09").requiredOption(
128174
+ // src/commands/tiktok-analysis/register-cli.ts
128175
+ init_version();
128176
+ init_auth();
128177
+ import { Command as Command3 } from "commander";
128178
+ function registerBatchCommand3(sectionHelp, aliasHelp) {
128179
+ return new Command3("run").description(
128180
+ "\u6279\u91CF\u62C9\u53D6 TSO reporting/media-account/TikTok Section \u6570\u636E\uFF08\u7701\u7565\u5B50\u547D\u4EE4\u540D\u65F6\u9ED8\u8BA4\u6267\u884C\u672C\u547D\u4EE4\uFF09"
128181
+ ).requiredOption("-a, --account <id>", "TikTok \u5E7F\u544A\u4E3B mediaCustomerId\uFF08\u6570\u5B57\uFF09").requiredOption(
128184
128182
  "--json-out <dir>",
128185
128183
  "\u7ED3\u679C\u843D\u76D8\u76EE\u5F55\uFF1B\u6BCF\u7EF4\u5EA6 `<section>-<accountId>.json` + `report-manifest-<accountId>.json`"
128186
128184
  ).option("-t, --token <token>", "Auth Token").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\u7565\uFF1B\u7701\u7565=\u8FD1 7 \u5929\u622A\u81F3\u6628\u5929\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option(
128187
128185
  "--sections <list>",
128188
- `\u4EC5\u6267\u884C\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u5982 overview,ad-sets,platform\uFF1B\u7701\u7565=\u5168\u90E8 7 \u4E2A\u3002\u53EF\u9009\uFF1A${sectionHelp}\uFF1BGoogle \u522B\u540D\uFF1Acampaigns,geographic,devices,ads,materials`
128186
+ `\u4EC5\u6267\u884C\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u5982 overview,campaigns,audience-merged\uFF1B\u7701\u7565=\u5168\u90E8 12 \u4E2A\u3002\u53EF\u9009\uFF1A${sectionHelp}\uFF1B\u522B\u540D\uFF1A${aliasHelp}`
128189
128187
  ).option("--exclude <list>", "\u6392\u9664\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u4E0E --sections \u53EF\u53E0\u52A0").option(
128190
- "--limit <n>",
128191
- "\u4EC5 country \u7EF4\u5EA6\uFF1A\u6309 spend \u964D\u5E8F\u53D6\u524D N \u6761\uFF08\u4E0D\u4F20\u5219\u8FD4\u56DE\u5168\u90E8\u56FD\u5BB6\uFF09",
128188
+ "--take <n>",
128189
+ "\u6D88\u8017/\u6761\u6570\u4E0A\u9650\uFF08\u9ED8\u8BA4 100\uFF0C\u4E0E\u524D\u7AEF take \u4E00\u81F4\uFF1Boverview \u4E0D\u9002\u7528\uFF09",
128192
128190
  (v) => parseInt(v, 10)
128193
128191
  ).option("--concurrency <n>", "\u5E76\u53D1\u6570\uFF0C\u9ED8\u8BA4 5\uFF0C\u4E0A\u9650 16", (v) => parseInt(v, 10)).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
128194
- await runAllFacebookSections(opts);
128192
+ await runAllTikTokSections(opts);
128195
128193
  });
128196
128194
  }
128197
- function registerFacebookAnalysisCommands(program2) {
128198
- const sectionHelp = FACEBOOK_SECTION_NAMES.join(", ");
128199
- const root = program2.command("facebook-analysis").description(
128200
- "Facebook Ads \u8D26\u6237\u5206\u6790\uFF1A\u6279\u91CF\u62C9\u53D6 Section \u6570\u636E\uFF08\u9ED8\u8BA4\uFF09\uFF0C\u6216\u7531 render \u751F\u6210 HTML \u5468\u671F\u62A5\u544A"
128201
- );
128202
- root.addCommand(registerBatchCommand(sectionHelp), { isDefault: true });
128203
- root.command("render").description(
128204
- "\u6839\u636E Agent \u64B0\u5199\u7684\u62A5\u544A JSON \u751F\u6210 Meta/Facebook \u5468\u671F\u5206\u6790 HTML \u7EC8\u7A3F\uFF08\u6821\u9A8C\u5FC5\u542B\u5B57\u6BB5\uFF0C\u7F3A\u9879\u62D2\u7EDD\u6E32\u67D3\uFF09"
128205
- ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 meta-period-report.json").option(
128206
- "--snapshot-dir <dir>",
128207
- "\u53EF\u9009\uFF1Afacebook-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u5408\u5E76 KPI / \u56FE\u8868 / \u8868\u683C"
128208
- ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
128209
- await runFacebookAnalysisRender({
128210
- dataFile: opts.data,
128211
- snapshotDir: opts.snapshotDir,
128212
- out: opts.out
128213
- });
128195
+ function registerTikTokAnalysisCommands(program2) {
128196
+ const sectionHelp = TIKTOK_SECTION_NAMES.join(", ");
128197
+ const aliasHelp = Object.keys(TIKTOK_SECTION_ALIASES).join(", ");
128198
+ const root = program2.command("tiktok-analysis").description("TikTok \u8D26\u6237\u5206\u6790\uFF1A\u6279\u91CF\u62C9\u53D6 Section \u6570\u636E\uFF08\u9ED8\u8BA4\uFF09\uFF0C\u6216\u67E5\u8BE2\u5730\u533A\u4EE3\u7801/\u5174\u8DA3\u7C7B\u76EE");
128199
+ root.addCommand(registerBatchCommand3(sectionHelp, aliasHelp), { isDefault: true });
128200
+ root.command("areacode").description("TikTok \u5730\u533A\u4EE3\u7801\u679A\u4E3E\uFF08\u4E3B\u5E73\u53F0 query/media-account/tiktok/TikTokAreacode/Read\uFF09").option("-t, --token <token>", "Auth Token").option(
128201
+ "--json-out <dir>",
128202
+ "\u5C06 JSON \u5199\u5165\u76EE\u5F55\u5E76\u66F4\u65B0 report-manifest.json\uFF1Bstdout \u4EC5\u4E00\u884C\u6458\u8981 JSON"
128203
+ ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
128204
+ const config = loadConfig(opts.token);
128205
+ try {
128206
+ const data = await fetchTikTokAreacode(config, !!opts.verbose);
128207
+ if (opts.jsonOut) {
128208
+ const summary = await writeReportAnalysisSnapshot({
128209
+ snapshotDir: opts.jsonOut,
128210
+ section: "areacode",
128211
+ dateRange: null,
128212
+ payload: data,
128213
+ cliVersion: getCurrentVersion2()
128214
+ });
128215
+ console.log(JSON.stringify(summary));
128216
+ return;
128217
+ }
128218
+ console.log("\n\u2705 TikTok \u5730\u533A\u4EE3\u7801\u8868\uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09\n");
128219
+ } catch (err) {
128220
+ console.error(`
128221
+ \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
128222
+ `);
128223
+ process.exit(1);
128224
+ }
128214
128225
  });
128226
+ root.command("interest-list").description(
128227
+ "TikTok \u5174\u8DA3\u7C7B\u76EE\uFF08tiktokApiUrl GetInterestList\uFF0C\u89E3\u6790 country_code/interest \u62A5\u8868\u65F6\u5E38\u7528\uFF09"
128228
+ ).requiredOption("-a, --account <id>", "TikTok mediaCustomerId").option("-t, --token <token>", "Auth Token").option(
128229
+ "--json-out <dir>",
128230
+ "\u5C06 JSON \u5199\u5165\u76EE\u5F55\u5E76\u66F4\u65B0 report-manifest-<accountId>.json\uFF1Bstdout \u4EC5\u4E00\u884C\u6458\u8981 JSON"
128231
+ ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
128232
+ async (opts) => {
128233
+ const config = loadConfig(opts.token);
128234
+ try {
128235
+ const { id, data } = await fetchTikTokInterestList(config, opts.account, !!opts.verbose);
128236
+ if (opts.jsonOut) {
128237
+ const summary = await writeReportAnalysisSnapshot({
128238
+ snapshotDir: opts.jsonOut,
128239
+ section: "interest-list",
128240
+ accountId: id,
128241
+ dateRange: null,
128242
+ payload: data,
128243
+ cliVersion: getCurrentVersion2()
128244
+ });
128245
+ console.log(JSON.stringify(summary));
128246
+ return;
128247
+ }
128248
+ console.log(`
128249
+ \u2705 TikTok \u5174\u8DA3\u7C7B\u76EE ${id}\uFF08\u5B8C\u6574\u6570\u636E\u8BF7\u52A0 --json-out <\u76EE\u5F55>\uFF09
128250
+ `);
128251
+ } catch (err) {
128252
+ console.error(`
128253
+ \u274C \u8BF7\u6C42\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
128254
+ `);
128255
+ process.exit(1);
128256
+ }
128257
+ }
128258
+ );
128215
128259
  }
128216
128260
 
128217
128261
  // src/index.ts
@@ -128220,8 +128264,8 @@ init_cli_json_snapshot();
128220
128264
  installProcessHandlers();
128221
128265
  function getVersion() {
128222
128266
  try {
128223
- const __dirname3 = path31.dirname(fileURLToPath7(import.meta.url));
128224
- const pkgPath = path31.join(__dirname3, "..", "package.json");
128267
+ const __dirname3 = path33.dirname(fileURLToPath7(import.meta.url));
128268
+ const pkgPath = path33.join(__dirname3, "..", "package.json");
128225
128269
  const pkg = JSON.parse(fs23.readFileSync(pkgPath, "utf8"));
128226
128270
  return pkg.version ?? "0.0.0";
128227
128271
  } catch {
@@ -128237,7 +128281,7 @@ configureCliJsonSnapshot({
128237
128281
  "references/accounts/accounts.md"
128238
128282
  ]
128239
128283
  });
128240
- var program = new Command2();
128284
+ var program = new Command4();
128241
128285
  program.name("siluzan-tso").description(
128242
128286
  "Siluzan \u5E7F\u544A\u8D26\u6237\u7BA1\u7406\uFF1A\u8D26\u6237\u67E5\u8BE2\u3001\u4F59\u989D\u3001\u6295\u653E\u6570\u636E\u3001\u5F00\u6237\u7533\u8BF7\uFF08Google/TikTok/Yandex/Bing/Kwai\uFF09\u3001\n\u8D26\u53F7\u5206\u4EAB/\u89E3\u7ED1\u3001\u4F18\u5316\u62A5\u544A\u3001Google \u8D26\u6237\u5206\u6790\u7F51\u5173\uFF08google-analysis\uFF09\u3001Google \u5E7F\u544A\u8BCA\u65AD\u62A5\u544A\uFF08google-ads-diagnosis render\uFF09\u3001\u7F51\u7AD9\u8BCA\u65AD\uFF08website-diagnosis\uFF09\u3001\u6218\u7565\u5E02\u573A\u5206\u6790\uFF08market-analysis\uFF0CAgent \u751F\u6210\u62A5\u544A\uFF09\u3001\u5145\u503C\u8F6C\u8D26\u3001\u5F00\u7968\u3001\u667A\u80FD\u9884\u8B66\u3001Google \u5E7F\u544A\u7BA1\u7406\uFF08\u542B\u5F02\u6B65\u6279\u91CF\uFF09\u3002"
128243
128287
  ).version(getVersion());
@@ -128271,6 +128315,8 @@ var REGISTRARS = [
128271
128315
  registerGoogleAnalysisCommands,
128272
128316
  registerGoogleAnalysisBatchCommands,
128273
128317
  registerFacebookAnalysisCommands,
128318
+ registerBingAnalysisCommands,
128319
+ registerTikTokAnalysisCommands,
128274
128320
  register14,
128275
128321
  register15,
128276
128322
  register16,