siluzan-tso-cli 1.1.44-beta.2 → 1.1.44-beta.4

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 (141) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +193 -165
  3. package/dist/skill/AGENTS.md +16 -16
  4. package/dist/skill/_meta.json +2 -2
  5. package/dist/skill/assets/campaign-create-template.md +46 -46
  6. package/dist/skill/assets/market-analysis-rules.md +4 -4
  7. package/dist/skill/assets/pmax-create-template.md +17 -17
  8. package/dist/skill/assets/pmax-lead-form-template.md +12 -12
  9. package/dist/skill/assets/scripts/assemble-campaign-from-plan.mjs +3 -1
  10. package/dist/skill/references/README.md +31 -31
  11. package/dist/skill/references/accounts/accounts-balance-stats.md +38 -36
  12. package/dist/skill/references/accounts/accounts-list.md +20 -20
  13. package/dist/skill/references/accounts/accounts-permissions.md +27 -27
  14. package/dist/skill/references/accounts/accounts.md +9 -9
  15. package/dist/skill/references/accounts/finance.md +0 -1
  16. package/dist/skill/references/accounts/open-account-by-media.md +57 -58
  17. package/dist/skill/references/accounts/open-account-google-ui.md +18 -18
  18. package/dist/skill/references/analytics/account-analytics.md +33 -33
  19. package/dist/skill/references/analytics/google-analysis-batch.md +0 -1
  20. package/dist/skill/references/analytics/keyword-planner-workflows.md +15 -13
  21. package/dist/skill/references/analytics/market-analysis-guide.md +4 -1
  22. package/dist/skill/references/analytics/rag.md +7 -7
  23. package/dist/skill/references/analytics/website-diagnosis-guide.md +6 -6
  24. package/dist/skill/references/core/agent-conventions.md +54 -54
  25. package/dist/skill/references/core/intent-routing.md +107 -108
  26. package/dist/skill/references/core/setup.md +1 -0
  27. package/dist/skill/references/core/tips.md +0 -1
  28. package/dist/skill/references/core/workflows.md +15 -16
  29. package/dist/skill/references/google-ads/google-ads-campaign-plan.md +55 -55
  30. package/dist/skill/references/google-ads/google-ads-read.md +27 -12
  31. package/dist/skill/references/google-ads/google-ads-write.md +35 -18
  32. package/dist/skill/references/google-ads/google-ads.md +13 -14
  33. package/dist/skill/references/google-ads/rules/README.md +17 -17
  34. package/dist/skill/references/google-ads/rules/google-ads-account-audit.md +6 -8
  35. package/dist/skill/references/google-ads/rules/google-ads-compliance.md +5 -7
  36. package/dist/skill/references/google-ads/rules/google-ads-keyword-strategy.md +9 -9
  37. package/dist/skill/references/google-ads/rules/google-ads-launch-plan-template.md +16 -15
  38. package/dist/skill/references/google-ads/rules/google-ads-plan-source-fidelity.md +27 -27
  39. package/dist/skill/references/google-ads/rules/google-ads-pmax-launch-plan-template.md +41 -41
  40. package/dist/skill/references/operations/guard.md +24 -24
  41. package/dist/skill/references/operations/hosted-automation-self-control.md +15 -16
  42. package/dist/skill/references/operations/hosted-automation-user-catalog.md +18 -18
  43. package/dist/skill/references/report-templates/README.md +1 -1
  44. package/dist/skill/references/report-templates/bing-period-report.md +16 -16
  45. package/dist/skill/references/report-templates/google-ads-diagnosis.md +32 -29
  46. package/dist/skill/references/report-templates/google-inquiry-analysis.md +1 -1
  47. package/dist/skill/references/report-templates/google-period-report.md +5 -5
  48. package/dist/skill/references/report-templates/meta-account-diagnosis-report.md +2 -2
  49. package/dist/skill/references/report-templates/okki-weekly-google-client.md +0 -1
  50. package/dist/skill/references/report-templates/tiktok-period-report.md +6 -6
  51. package/dist/skill/report-templates/GoogleAdsDiagnosisReport.html +328 -238
  52. package/dist/skill/report-templates/README.md +1 -1
  53. package/dist/skill/report-templates/bing-period-report.html +10 -7
  54. package/dist/skill/report-templates/bing-period-report.md +16 -16
  55. package/dist/skill/report-templates/google-ads-diagnosis.md +32 -29
  56. package/dist/skill/report-templates/google-inquiry-analysis.md +1 -1
  57. package/dist/skill/report-templates/google-period-report.md +5 -5
  58. package/dist/skill/report-templates/meta-account-diagnosis-report.md +2 -2
  59. package/dist/skill/report-templates/okki-weekly-google-client.md +0 -1
  60. package/dist/skill/report-templates/tiktok-period-report.md +6 -6
  61. package/dist/skill/scripts/install.ps1 +1 -1
  62. package/dist/skill/scripts/install.sh +1 -1
  63. package/eval/cases/accounts-entityid-vs-mediaccustomerid.scenario.json +2 -14
  64. package/eval/cases/accounts-mcc-bind-inquiry.scenario.json +1 -3
  65. package/eval/cases/accounts-single-balance-not-bulk.scenario.json +3 -14
  66. package/eval/cases/budget-display-not-raw-micros.scenario.json +1 -8
  67. package/eval/cases/clue-meta-leads-json.scenario.json +2 -14
  68. package/eval/cases/clue-tiktok-leads-json.scenario.json +2 -11
  69. package/eval/cases/destructive-account-delink-needs-confirm.scenario.json +3 -9
  70. package/eval/cases/destructive-forewarning-delete-needs-confirm.scenario.json +3 -9
  71. package/eval/cases/destructive-invoice-apply-needs-confirm.scenario.json +3 -9
  72. package/eval/cases/facebook-analysis-google-section-aliases.scenario.json +2 -11
  73. package/eval/cases/facebook-analysis-not-google-keywords.scenario.json +3 -10
  74. package/eval/cases/facebook-analysis-period-default.scenario.json +2 -14
  75. package/eval/cases/finance-invoice-info-list.scenario.json +3 -11
  76. package/eval/cases/forewarning-list-google.scenario.json +3 -14
  77. package/eval/cases/google-ads-no-structural-without-confirm.scenario.json +2 -6
  78. package/eval/cases/google-analysis-keywords-route.scenario.json +2 -14
  79. package/eval/cases/guard-budget-circuit-all-google.scenario.json +2 -12
  80. package/eval/cases/guard-zero-conv-empty-spend.scenario.json +2 -12
  81. package/eval/cases/human-p1-multiturn.scenario.json +1 -5
  82. package/eval/cases/ir-p1-vs-p8-account-vs-url.scenario.json +5 -17
  83. package/eval/cases/ir-p3-cpa-monitor-not-p7.scenario.json +2 -19
  84. package/eval/cases/ir-p3-zero-inquiry-not-p7.scenario.json +3 -16
  85. package/eval/cases/ir-p4-period-not-diagnosis.scenario.json +3 -16
  86. package/eval/cases/ir-p8-meets-google-ads-requirements-with-url.scenario.json +3 -28
  87. package/eval/cases/ir-p8-network-diagnosis-with-url.scenario.json +3 -25
  88. package/eval/cases/ir-p8-website-monitor-report-with-url.scenario.json +3 -28
  89. package/eval/cases/ir-p8-website-test-report-no-url.scenario.json +3 -14
  90. package/eval/cases/ir-p9-industry-not-p8.scenario.json +3 -19
  91. package/eval/cases/ir-vague-report-clarify.scenario.json +3 -14
  92. package/eval/cases/ir-w3-vs-p8-from-website.scenario.json +2 -8
  93. package/eval/cases/ir-w5-keyword-planner-url-core-terms.scenario.json +2 -22
  94. package/eval/cases/meta-single-balance-not-bulk.scenario.json +3 -17
  95. package/eval/cases/no-legacy-json-flag.scenario.json +2 -6
  96. package/eval/cases/open-account-google-noninteractive.scenario.json +1 -3
  97. package/eval/cases/open-account-tiktok-license-file.scenario.json +1 -3
  98. package/eval/cases/optimize-list-by-account.scenario.json +3 -11
  99. package/eval/cases/p1-single-account-profile.scenario.json +1 -11
  100. package/eval/cases/p2-balance-scan-bulk.scenario.json +2 -9
  101. package/eval/cases/p3-accounts-digest.scenario.json +1 -5
  102. package/eval/cases/p3-cpa-scan-max-cpa.scenario.json +2 -14
  103. package/eval/cases/p4-fb-meta-period-report.scenario.json +2 -12
  104. package/eval/cases/p4-period-report-window.scenario.json +1 -8
  105. package/eval/cases/pmax-asset-group-create-with-bg.scenario.json +2 -13
  106. package/eval/cases/pmax-brand-edit-routing.scenario.json +3 -16
  107. package/eval/cases/pmax-edit-not-campaign-edit.scenario.json +2 -12
  108. package/eval/cases/pmax-enable-brand-guidelines.scenario.json +2 -12
  109. package/eval/cases/pmax-no-assets-update-brand-fields.scenario.json +3 -14
  110. package/eval/cases/rag-before-keyword-expand.scenario.json +1 -11
  111. package/eval/cases/rag-list-then-query.scenario.json +2 -14
  112. package/eval/cases/report-list-google.scenario.json +2 -11
  113. package/eval/cases/report-push-list-google.scenario.json +2 -11
  114. package/eval/cases/setup-login-or-env.scenario.json +1 -3
  115. package/eval/cases/setup-siluzan-data-permission-env.scenario.json +1 -3
  116. package/eval/cases/tiktok-bc-bind-inquiry.scenario.json +2 -6
  117. package/eval/cases/time-range-user-delegates-default.scenario.json +1 -8
  118. package/eval/cases/tips-json-out-filtering.scenario.json +1 -3
  119. package/eval/cases/tips-large-json-pagination.scenario.json +1 -3
  120. package/eval/cases/uj-ad-campaign-validate-before-create-stub.scenario.json +2 -11
  121. package/eval/cases/uj-ad-outdoor-campgear-search-plan.scenario.json +1 -3
  122. package/eval/cases/uj-ad-website-search-plan-table-format.scenario.json +1 -3
  123. package/eval/cases/uj-analytics-30d-pdf-campaign-device-geo.scenario.json +6 -18
  124. package/eval/cases/uj-analytics-compare-google-tiktok-last-month-roi.scenario.json +1 -8
  125. package/eval/cases/uj-analytics-ecommerce-industry-report.scenario.json +2 -14
  126. package/eval/cases/uj-analytics-google-weekly-trends-campaigns-keywords.scenario.json +2 -11
  127. package/eval/cases/uj-analytics-report-push-weekly-email.scenario.json +1 -3
  128. package/eval/cases/uj-finance-invoice-records-this-month.scenario.json +2 -11
  129. package/eval/cases/uj-life-newbie-siluzan-google-end-to-end.scenario.json +1 -4
  130. package/eval/cases/uj-ops-google-accounts-list-normal.scenario.json +2 -17
  131. package/eval/cases/uj-ops-google-yesterday-spend-conversions.scenario.json +2 -14
  132. package/eval/cases/uj-ops-pause-worst-adgroup-confirm.scenario.json +2 -6
  133. package/eval/cases/uj-ops-tiktok-leads-last-week.scenario.json +3 -17
  134. package/eval/cases/uj-patrol-cpc-spike-adgroups-over-15.scenario.json +1 -5
  135. package/eval/cases/uj-patrol-forewarning-create-daily-cap-3000.scenario.json +1 -3
  136. package/eval/cases/uj-patrol-forewarning-trigger-records.scenario.json +3 -17
  137. package/eval/cases/uj-patrol-google-balances-low.scenario.json +2 -11
  138. package/eval/cases/uj-roi-optimize-records-then-execute-cautiously.scenario.json +3 -14
  139. package/eval/cases/uj-roi-search-terms-add-negative-keywords.scenario.json +2 -14
  140. package/eval/cases/yandex-stats-use-porg-mediaccustomerid.scenario.json +3 -21
  141. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5471,11 +5471,7 @@ function register10(program2) {
5471
5471
  "--max-cpa <n>",
5472
5472
  "\u8FC7\u6EE4\uFF1A\u4FDD\u7559 CPA > \u6B64\u503C\u7684\u8D26\u6237\uFF1B\u6709\u6D88\u8017\u4E14\u8F6C\u5316\u4E3A 0 \u89C6\u4E3A\u547D\u4E2D\uFF08CPA \u65E0\u7A77\uFF09",
5473
5473
  (v) => parseFloat(v)
5474
- ).option(
5475
- "--zero-conversions",
5476
- "\u8FC7\u6EE4\uFF1A\u4FDD\u7559\u533A\u95F4\u6D88\u8017 > 0 \u4E14\u8F6C\u5316 = 0 \u6216\u8F6C\u5316\u672A\u8FD4\u56DE\u7684\u8D26\u6237",
5477
- false
5478
- ).option("--page-size <n>", "\u8D26\u6237\u6E05\u5355\u5206\u9875\u5927\u5C0F\uFF08\u9ED8\u8BA4 200\uFF0C\u4E0A\u9650 500\uFF09", (v) => parseInt(v, 10)).option("--max-pages <n>", "\u6700\u591A\u626B\u63CF\u9875\u6570\uFF08\u9ED8\u8BA4 20\uFF0C\u4E0A\u9650 200\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option(
5474
+ ).option("--zero-conversions", "\u8FC7\u6EE4\uFF1A\u4FDD\u7559\u533A\u95F4\u6D88\u8017 > 0 \u4E14\u8F6C\u5316 = 0 \u6216\u8F6C\u5316\u672A\u8FD4\u56DE\u7684\u8D26\u6237", false).option("--page-size <n>", "\u8D26\u6237\u6E05\u5355\u5206\u9875\u5927\u5C0F\uFF08\u9ED8\u8BA4 200\uFF0C\u4E0A\u9650 500\uFF09", (v) => parseInt(v, 10)).option("--max-pages <n>", "\u6700\u591A\u626B\u63CF\u9875\u6570\uFF08\u9ED8\u8BA4 20\uFF0C\u4E0A\u9650 200\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option(
5479
5475
  "--json-out <path>",
5480
5476
  "\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",
5481
5477
  void 0
@@ -101976,7 +101972,9 @@ var init_translate_fields = __esm({
101976
101972
  function resolveDateRange(start, end) {
101977
101973
  if (start && end) return { startDate: start, endDate: end };
101978
101974
  if (!start && !end) return defaultChinaDateRange();
101979
- 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\uFF0CUTC+8\uFF09\u3002\n");
101975
+ console.error(
101976
+ "\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\uFF0CUTC+8\uFF09\u3002\n"
101977
+ );
101980
101978
  process.exit(1);
101981
101979
  }
101982
101980
  function buildSearchParams(def, start, end, extras) {
@@ -102513,9 +102511,7 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
102513
102511
  averageCpc: num2(dimensionSummaryRecord.averageCpc),
102514
102512
  costPerConversion: num2(dimensionSummaryRecord.costPerConversion),
102515
102513
  searchImpressionShare: num2(dimensionSummaryRecord.searchImpressionShare),
102516
- searchBudgetLostImpressionShare: num2(
102517
- dimensionSummaryRecord.searchBudgetLostImpressionShare
102518
- ),
102514
+ searchBudgetLostImpressionShare: num2(dimensionSummaryRecord.searchBudgetLostImpressionShare),
102519
102515
  searchRankLostImpressionShare: num2(dimensionSummaryRecord.searchRankLostImpressionShare)
102520
102516
  };
102521
102517
  }
@@ -104220,10 +104216,7 @@ function registerRunCommand() {
104220
104216
  ).option("-t, --token <token>", "Auth Token").option(
104221
104217
  "--start <date>",
104222
104218
  "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08google-analysis \u53EA\u4F20\u5E74\u6708\u65E5\uFF1B\u7701\u7565=\u8FD1 7 \u5929\u622A\u81F3\u6628\u5929\uFF0C\u9ED8\u8BA4\u6309 UTC+8 \u65E5\u5386\uFF1B\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\uFF09"
104223
- ).option(
104224
- "--end <date>",
104225
- "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD\uFF08\u53EA\u4F20\u5E74\u6708\u65E5\uFF1B\u7701\u7565=\u6628\u5929\uFF09"
104226
- ).option("--start-date <date>", "\u540C --start\uFF08\u6587\u6863/Playbook \u517C\u5BB9\u522B\u540D\uFF09").option("--end-date <date>", "\u540C --end\uFF08\u6587\u6863/Playbook \u517C\u5BB9\u522B\u540D\uFF09").option(
104219
+ ).option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD\uFF08\u53EA\u4F20\u5E74\u6708\u65E5\uFF1B\u7701\u7565=\u6628\u5929\uFF09").option("--start-date <date>", "\u540C --start\uFF08\u6587\u6863/Playbook \u517C\u5BB9\u522B\u540D\uFF09").option("--end-date <date>", "\u540C --end\uFF08\u6587\u6863/Playbook \u517C\u5BB9\u522B\u540D\uFF09").option(
104227
104220
  "--sections <list>",
104228
104221
  "\u4EC5\u6267\u884C\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u5982 overview,keywords,ads\uFF1B\u7701\u7565=\u5168\u90E8 25 \u4E2A"
104229
104222
  ).option(
@@ -107182,7 +107175,9 @@ async function runBalanceScan(opts) {
107182
107175
  \u274C balance-scan \u6570\u636E\u5F02\u5E38\uFF08${dataIssue.code}\uFF09\uFF1A${dataIssue.message}
107183
107176
  `);
107184
107177
  if (wroteJson) {
107185
- console.error(" \u5DF2\u5199\u5165 --json-out\uFF08ok=false\uFF0C\u89C1 meta.dataIssue\uFF09\uFF1B\u8BF7\u4FEE\u590D\u540E\u91CD\u8DD1\uFF0C\u52FF\u5F53\u4F5C\u300C\u65E0\u9884\u8B66\u547D\u4E2D\u300D\u3002\n");
107178
+ console.error(
107179
+ " \u5DF2\u5199\u5165 --json-out\uFF08ok=false\uFF0C\u89C1 meta.dataIssue\uFF09\uFF1B\u8BF7\u4FEE\u590D\u540E\u91CD\u8DD1\uFF0C\u52FF\u5F53\u4F5C\u300C\u65E0\u9884\u8B66\u547D\u4E2D\u300D\u3002\n"
107180
+ );
107186
107181
  }
107187
107182
  process.exit(2);
107188
107183
  }
@@ -107378,12 +107373,20 @@ function convertBatchMoneyTreeFromYuanInner(value) {
107378
107373
  }
107379
107374
  return value;
107380
107375
  }
107381
- function adgroupListUrl(googleApiUrl, account, startDate, endDate) {
107376
+ function adgroupListUrl(googleApiUrl, account, startDate, endDate, fresh) {
107382
107377
  const params = new URLSearchParams();
107383
107378
  params.set("startDate", toGoogleDate(startDate, -30));
107384
107379
  params.set("endDate", toGoogleDate(endDate, 0));
107380
+ if (fresh) params.set("newest", "true");
107385
107381
  return `${googleApiUrl}/adgroupnmanagement/v2/list/${account}?${params}`;
107386
107382
  }
107383
+ function campaignListUrl(googleApiUrl, account, startDate, endDate, fresh) {
107384
+ const params = new URLSearchParams();
107385
+ params.set("startDate", toGoogleDate(startDate, -30));
107386
+ params.set("endDate", toGoogleDate(endDate, 0));
107387
+ if (fresh) params.set("newest", "true");
107388
+ return `${googleApiUrl}/campaignmanagement/v2/list/${account}?${params}`;
107389
+ }
107387
107390
  function requireGoogleApi(config) {
107388
107391
  if (!config.googleApiUrl) {
107389
107392
  console.error(
@@ -107418,10 +107421,8 @@ function assertGoogleOnly(media) {
107418
107421
  async function resolveGuardAccounts(opts, config) {
107419
107422
  const filterIds = (opts.accounts ?? "").split(",").map((s) => s.trim()).filter(Boolean);
107420
107423
  if (filterIds.length > 0) {
107421
- process.stderr.write(
107422
- `\u23F3 [guard] \u5B50\u96C6\u6A21\u5F0F\uFF1A\u5BF9 -a \u6307\u5B9A\u7684 ${filterIds.length} \u4E2A\u8D26\u6237\u626B\u63CF\u3002
107423
- `
107424
- );
107424
+ process.stderr.write(`\u23F3 [guard] \u5B50\u96C6\u6A21\u5F0F\uFF1A\u5BF9 -a \u6307\u5B9A\u7684 ${filterIds.length} \u4E2A\u8D26\u6237\u626B\u63CF\u3002
107425
+ `);
107425
107426
  return filterIds.map((id) => ({ mediaCustomerId: id, name: null }));
107426
107427
  }
107427
107428
  const pageSize = Math.max(1, Math.min(500, opts.pageSize ?? 200));
@@ -107791,12 +107792,7 @@ async function runZeroConv(opts) {
107791
107792
  const skipped2 = [];
107792
107793
  let checked2 = 0;
107793
107794
  if (level === "adgroup" || level === "both") {
107794
- const groups = await fetchAdGroupsForDay(
107795
- config,
107796
- acc.mediaCustomerId,
107797
- date,
107798
- opts.verbose
107799
- );
107795
+ const groups = await fetchAdGroupsForDay(config, acc.mediaCustomerId, date, opts.verbose);
107800
107796
  for (const g of groups) {
107801
107797
  if (onlyEnabled && !isEnabledStatus(g.statusV2)) continue;
107802
107798
  checked2++;
@@ -107990,11 +107986,7 @@ function register11(program2) {
107990
107986
  ).requiredOption("-m, --media <type>", "\u76EE\u524D\u4EC5\u652F\u6301 Google").option(
107991
107987
  "-a, --accounts <ids>",
107992
107988
  "\u6307\u5B9A\u8D26\u6237 mediaCustomerId\uFF0C\u9017\u53F7\u5206\u9694\uFF1B\u7559\u7A7A\u5219\u626B\u63CF\u540D\u4E0B\u5168\u90E8 Google \u8D26\u6237"
107993
- ).option("--date <YYYY-MM-DD>", "\u7EDF\u8BA1\u65E5\uFF08Asia/Shanghai \u9ED8\u8BA4\u4ECA\u5929\uFF09\uFF1B\u5F3A\u5236 start=end=\u8BE5\u65E5").option("--ratio <n>", "\u9608\u503C\u7CFB\u6570\uFF1Aspend >= budget * ratio\uFF08\u9ED8\u8BA4 1.1\uFF09", (v) => parseFloat(v)).option(
107994
- "--status <statusV2>",
107995
- "\u53EA\u68C0\u67E5\u8BE5\u72B6\u6001\u7684\u7CFB\u5217\uFF08\u9ED8\u8BA4 Enabled\uFF1B\u4F20 * \u8868\u793A\u4E0D\u8FC7\u6EE4\uFF09",
107996
- "Enabled"
107997
- ).option("--apply", "\u5BF9\u547D\u4E2D\u7CFB\u5217\u6267\u884C Paused\uFF08\u987B\u914D\u5408\u5168\u5C40 --commit\uFF09", false).option("--page-size <n>", "\u8D26\u6237\u6E05\u5355\u5206\u9875\u5927\u5C0F\uFF08\u9ED8\u8BA4 200\uFF09", (v) => parseInt(v, 10)).option("--max-pages <n>", "\u6700\u591A\u626B\u63CF\u9875\u6570\uFF08\u9ED8\u8BA4 20\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF09").option(
107989
+ ).option("--date <YYYY-MM-DD>", "\u7EDF\u8BA1\u65E5\uFF08Asia/Shanghai \u9ED8\u8BA4\u4ECA\u5929\uFF09\uFF1B\u5F3A\u5236 start=end=\u8BE5\u65E5").option("--ratio <n>", "\u9608\u503C\u7CFB\u6570\uFF1Aspend >= budget * ratio\uFF08\u9ED8\u8BA4 1.1\uFF09", (v) => parseFloat(v)).option("--status <statusV2>", "\u53EA\u68C0\u67E5\u8BE5\u72B6\u6001\u7684\u7CFB\u5217\uFF08\u9ED8\u8BA4 Enabled\uFF1B\u4F20 * \u8868\u793A\u4E0D\u8FC7\u6EE4\uFF09", "Enabled").option("--apply", "\u5BF9\u547D\u4E2D\u7CFB\u5217\u6267\u884C Paused\uFF08\u987B\u914D\u5408\u5168\u5C40 --commit\uFF09", false).option("--page-size <n>", "\u8D26\u6237\u6E05\u5355\u5206\u9875\u5927\u5C0F\uFF08\u9ED8\u8BA4 200\uFF09", (v) => parseInt(v, 10)).option("--max-pages <n>", "\u6700\u591A\u626B\u63CF\u9875\u6570\uFF08\u9ED8\u8BA4 20\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF09").option(
107998
107990
  "--json-out <path>",
107999
107991
  "\u843D\u76D8\u76EE\u5F55\u6216 *.json\uFF1Bstdout \u4E00\u884C\u6458\u8981\uFF1B\u4EBA\u7C7B\u53EF\u8BFB\u8868\u59CB\u7EC8\u6253\u5370",
108000
107992
  void 0
@@ -108021,15 +108013,7 @@ function register11(program2) {
108021
108013
  ).requiredOption("-m, --media <type>", "\u76EE\u524D\u4EC5\u652F\u6301 Google").option(
108022
108014
  "-a, --accounts <ids>",
108023
108015
  "\u6307\u5B9A\u8D26\u6237 mediaCustomerId\uFF0C\u9017\u53F7\u5206\u9694\uFF1B\u7559\u7A7A\u5219\u626B\u63CF\u540D\u4E0B\u5168\u90E8 Google \u8D26\u6237"
108024
- ).option("--date <YYYY-MM-DD>", "\u7EDF\u8BA1\u65E5\uFF08Asia/Shanghai \u9ED8\u8BA4\u4ECA\u5929\uFF09").option(
108025
- "--cpa-multiple <n>",
108026
- "\u500D\u6570 N\uFF1Aspend >= targetCpa \xD7 N\uFF08\u9ED8\u8BA4 3\uFF09",
108027
- (v) => parseFloat(v)
108028
- ).option(
108029
- "--level <level>",
108030
- "\u626B\u63CF\u5C42\u7EA7\uFF1Aadgroup | ad | both\uFF08\u9ED8\u8BA4 adgroup\uFF09",
108031
- "adgroup"
108032
- ).option(
108016
+ ).option("--date <YYYY-MM-DD>", "\u7EDF\u8BA1\u65E5\uFF08Asia/Shanghai \u9ED8\u8BA4\u4ECA\u5929\uFF09").option("--cpa-multiple <n>", "\u500D\u6570 N\uFF1Aspend >= targetCpa \xD7 N\uFF08\u9ED8\u8BA4 3\uFF09", (v) => parseFloat(v)).option("--level <level>", "\u626B\u63CF\u5C42\u7EA7\uFF1Aadgroup | ad | both\uFF08\u9ED8\u8BA4 adgroup\uFF09", "adgroup").option(
108033
108017
  "--fallback-target-cpa <yuan>",
108034
108018
  "\u7EC4\u65E0 targetCpa\uFF08\u6216\u521B\u610F\u7EF4\uFF09\u65F6\u4F7F\u7528\u7684\u76EE\u6807 CPA\uFF08\u5143\uFF09",
108035
108019
  (v) => parseFloat(v)
@@ -108134,14 +108118,7 @@ async function fetchStatsWindow(media, ids, config, startDate, endDate, verbose)
108134
108118
  }
108135
108119
  return items2;
108136
108120
  }
108137
- const map = await fetchOverviewMap(
108138
- media,
108139
- ids,
108140
- config,
108141
- startDate,
108142
- endDate,
108143
- verbose
108144
- );
108121
+ const map = await fetchOverviewMap(media, ids, config, startDate, endDate, verbose);
108145
108122
  return [...map.values()];
108146
108123
  }
108147
108124
  async function runStats(opts) {
@@ -108185,14 +108162,7 @@ async function runStats(opts) {
108185
108162
  if (opts.verbose || days.length > 7) {
108186
108163
  console.error(` \u2192 [${i + 1}/${days.length}] ${day}`);
108187
108164
  }
108188
- const dayItems = await fetchStatsWindow(
108189
- opts.media,
108190
- ids,
108191
- config,
108192
- day,
108193
- day,
108194
- opts.verbose
108195
- );
108165
+ const dayItems = await fetchStatsWindow(opts.media, ids, config, day, day, opts.verbose);
108196
108166
  for (const row of dayItems) {
108197
108167
  items2.push({ ...row, date: day });
108198
108168
  }
@@ -111902,7 +111872,9 @@ function resolveKeywordTexts(opts) {
111902
111872
  const fromFile = opts.keywordsFile ? loadKeywordsFromFile(opts.keywordsFile) : [];
111903
111873
  const merged = [...fromCsv, ...fromFile];
111904
111874
  if (merged.length === 0) {
111905
- throw new Error("\u8BF7\u4F20 --keywords \u548C/\u6216 --keywords-file\uFF08\u591A\u8BCD\u77ED\u8BED\u76F4\u63A5\u5199\u7A7A\u683C\u5373\u53EF\uFF0C\u5982 how to make\uFF09");
111875
+ throw new Error(
111876
+ "\u8BF7\u4F20 --keywords \u548C/\u6216 --keywords-file\uFF08\u591A\u8BCD\u77ED\u8BED\u76F4\u63A5\u5199\u7A7A\u683C\u5373\u53EF\uFF0C\u5982 how to make\uFF09"
111877
+ );
111906
111878
  }
111907
111879
  if (!opts.matchType) return merged;
111908
111880
  return merged.map((k) => formatKeywordTextForMatchType(k, opts.matchType));
@@ -111930,6 +111902,9 @@ async function runAdKeywords(opts) {
111930
111902
  const params = new URLSearchParams();
111931
111903
  params.set("startDate", toGoogleDate(opts.startDate, -30));
111932
111904
  params.set("endDate", toGoogleDate(opts.endDate, 0));
111905
+ if (opts.negative && opts.level) {
111906
+ params.set("appliedlevel", opts.level);
111907
+ }
111933
111908
  const endpoint = opts.negative ? "negativekeywordmanagement" : "keywordmanagement";
111934
111909
  const url = `${googleApiUrl}/${endpoint}/v2/list/${opts.account}?${params}`;
111935
111910
  let data;
@@ -111977,7 +111952,8 @@ ${label}\uFF08\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672C
111977
111952
  items2.forEach((item) => {
111978
111953
  const kwText = Array.isArray(item["keywordText"]) ? item["keywordText"].join(", ") : String(item["text"] ?? item["keywordText"] ?? item["id"] ?? "\u2014");
111979
111954
  const matchType = item["matchTypeV2"] ?? "\u2014";
111980
- console.log(` [${matchType}] ${kwText} id: ${String(item["id"] ?? "")}`);
111955
+ const level = item["negativeLevel"] ?? "\u2014";
111956
+ console.log(` [${matchType}] ${kwText} id: ${String(item["id"] ?? "")} \u7EA7\u522B: ${level}`);
111981
111957
  });
111982
111958
  } else {
111983
111959
  const kwW = Math.min(
@@ -112080,6 +112056,30 @@ async function runAdKeywordCreate(opts) {
112080
112056
  \u2705 \u5DF2\u6DFB\u52A0 ${keywords.length} \u4E2A\u5173\u952E\u8BCD\u5230\u5E7F\u544A\u7EC4\u300C${opts.adgroupName}\u300D
112081
112057
  `);
112082
112058
  }
112059
+ function resolveNegativeKeywordLevel(opts) {
112060
+ if (opts.level) return opts.level;
112061
+ return opts.adgroupId ? "AdGroup" : "Campaign";
112062
+ }
112063
+ function buildNegativeKeywordCreateBody(level, keywords, opts) {
112064
+ if (level === "Account") {
112065
+ return { KeywordText: keywords, NegativeLevel: "Account" };
112066
+ }
112067
+ const body = {
112068
+ KeywordText: keywords,
112069
+ CampaignId: opts.campaignId,
112070
+ Campaign: opts.campaignName
112071
+ };
112072
+ if (level === "AdGroup") {
112073
+ body["AdGroupId"] = opts.adgroupId;
112074
+ body["AdGroup"] = opts.adgroupName ?? "";
112075
+ }
112076
+ return body;
112077
+ }
112078
+ var negativeKeywordLevelLabel = {
112079
+ Account: "\u8D26\u6237",
112080
+ Campaign: "\u5E7F\u544A\u7CFB\u5217",
112081
+ AdGroup: "\u5E7F\u544A\u7EC4"
112082
+ };
112083
112083
  async function runAdNegativeKeywordCreate(opts) {
112084
112084
  opts = withResolvedAdAccount(opts);
112085
112085
  const config = loadConfig(opts.token);
@@ -112097,16 +112097,27 @@ async function runAdNegativeKeywordCreate(opts) {
112097
112097
  `);
112098
112098
  process.exit(1);
112099
112099
  }
112100
- const body = {
112101
- KeywordText: keywords,
112102
- CampaignId: opts.campaignId,
112103
- Campaign: opts.campaignName
112104
- };
112105
- const level = opts.adgroupId ? "\u5E7F\u544A\u7EC4" : "\u5E7F\u544A\u7CFB\u5217";
112106
- if (opts.adgroupId) {
112107
- body["AdGroupId"] = opts.adgroupId;
112108
- body["AdGroup"] = opts.adgroupName ?? "";
112100
+ const level = resolveNegativeKeywordLevel(opts);
112101
+ if (level === "Account") {
112102
+ if (opts.campaignId || opts.adgroupId) {
112103
+ console.error(
112104
+ "\n\u274C --level Account \u65E0\u9700\u4E5F\u4E0D\u80FD\u4F20 --campaign-id / --adgroup-id\uFF08\u8D26\u6237\u7EA7\u5426\u8BCD\u76F4\u63A5\u751F\u6548\u4E8E\u6574\u4E2A\u8D26\u6237\uFF09\n"
112105
+ );
112106
+ process.exit(1);
112107
+ }
112108
+ } else {
112109
+ if (!opts.campaignId || !opts.campaignName) {
112110
+ console.error(`
112111
+ \u274C --level ${level} \u9700\u8981\u540C\u65F6\u4F20 --campaign-id \u4E0E --campaign-name
112112
+ `);
112113
+ process.exit(1);
112114
+ }
112115
+ if (level === "AdGroup" && !opts.adgroupId) {
112116
+ console.error("\n\u274C --level AdGroup \u9700\u8981\u4F20 --adgroup-id\n");
112117
+ process.exit(1);
112118
+ }
112109
112119
  }
112120
+ const body = buildNegativeKeywordCreateBody(level, keywords, opts);
112110
112121
  const url = `${googleApiUrl}/negativekeywordmanagement/negativekeyword/${opts.account}`;
112111
112122
  try {
112112
112123
  await apiFetch2(url, config, { method: "POST", body: JSON.stringify(body) }, opts.verbose);
@@ -112116,9 +112127,11 @@ async function runAdNegativeKeywordCreate(opts) {
112116
112127
  `);
112117
112128
  process.exit(1);
112118
112129
  }
112119
- console.log(`
112120
- \u2705 \u5DF2\u5728${level}\u5C42\u7EA7\u6DFB\u52A0 ${keywords.length} \u4E2A\u5426\u5B9A\u5173\u952E\u8BCD
112121
- `);
112130
+ console.log(
112131
+ `
112132
+ \u2705 \u5DF2\u5728${negativeKeywordLevelLabel[level]}\u5C42\u7EA7\u6DFB\u52A0 ${keywords.length} \u4E2A\u5426\u5B9A\u5173\u952E\u8BCD
112133
+ `
112134
+ );
112122
112135
  }
112123
112136
  async function runAdKeywordDelete(opts) {
112124
112137
  opts = withResolvedAdAccount(opts);
@@ -113506,10 +113519,7 @@ async function runAdCampaigns(opts) {
113506
113519
  opts = withResolvedAdAccount(opts);
113507
113520
  const config = loadConfig(opts.token);
113508
113521
  const googleApiUrl = requireGoogleApi(config);
113509
- const params = new URLSearchParams();
113510
- params.set("startDate", toGoogleDate(opts.startDate, -30));
113511
- params.set("endDate", toGoogleDate(opts.endDate, 0));
113512
- const url = `${googleApiUrl}/campaignmanagement/v2/list/${opts.account}?${params}`;
113522
+ const url = campaignListUrl(googleApiUrl, opts.account, opts.startDate, opts.endDate, opts.fresh);
113513
113523
  let data;
113514
113524
  try {
113515
113525
  data = await apiFetch2(url, config, {}, opts.verbose);
@@ -113939,10 +113949,7 @@ async function runAdGroups(opts) {
113939
113949
  opts = withResolvedAdAccount(opts);
113940
113950
  const config = loadConfig(opts.token);
113941
113951
  const googleApiUrl = requireGoogleApi(config);
113942
- const params = new URLSearchParams();
113943
- params.set("startDate", toGoogleDate(opts.startDate, -30));
113944
- params.set("endDate", toGoogleDate(opts.endDate, 0));
113945
- const url = `${googleApiUrl}/adgroupnmanagement/v2/list/${opts.account}?${params}`;
113952
+ const url = adgroupListUrl(googleApiUrl, opts.account, opts.startDate, opts.endDate, opts.fresh);
113946
113953
  let data;
113947
113954
  try {
113948
113955
  data = await apiFetch2(url, config, {}, opts.verbose);
@@ -115583,7 +115590,12 @@ async function runAdDeviceBidSet(opts) {
115583
115590
  // src/commands/ad/geo.ts
115584
115591
  async function fetchGeoLocationCandidates(googleApiUrl, account, query, config, verbose) {
115585
115592
  const url = `${googleApiUrl}/campaignmanagement/geolocations/${account}/${encodeURIComponent(query)}`;
115586
- const data = await apiFetch2(url, config, {}, verbose);
115593
+ const data = await apiFetch2(
115594
+ url,
115595
+ config,
115596
+ {},
115597
+ verbose
115598
+ );
115587
115599
  return Array.isArray(data) ? data : data.data ?? [];
115588
115600
  }
115589
115601
  function geoItemDisplayName(item) {
@@ -115732,9 +115744,7 @@ async function runAdGeoResolve(opts) {
115732
115744
  process.exit(1);
115733
115745
  }
115734
115746
  if (queries.length === 0) {
115735
- console.error(
115736
- '\n\u274C \u8BF7\u63D0\u4F9B --queries "Peru,Chile,..." \u6216 --from-file <names.txt|names.json>\n'
115737
- );
115747
+ console.error('\n\u274C \u8BF7\u63D0\u4F9B --queries "Peru,Chile,..." \u6216 --from-file <names.txt|names.json>\n');
115738
115748
  process.exit(1);
115739
115749
  }
115740
115750
  const config = loadConfig(opts.token);
@@ -115790,9 +115800,7 @@ async function runAdGeoResolve(opts) {
115790
115800
  /** 可直接写入 campaign-create JSON */
115791
115801
  locations,
115792
115802
  targetedLocations,
115793
- nameToId: Object.fromEntries(
115794
- rows.filter((r) => r.id).map((r) => [r.query, r.id])
115795
- )
115803
+ nameToId: Object.fromEntries(rows.filter((r) => r.id).map((r) => [r.query, r.id]))
115796
115804
  };
115797
115805
  if (await emitCliJsonOrSnapshot(
115798
115806
  { jsonOut: opts.jsonOut },
@@ -115807,9 +115815,11 @@ async function runAdGeoResolve(opts) {
115807
115815
  if (failed.length > 0 && !opts.allowPartial) process.exit(1);
115808
115816
  return;
115809
115817
  }
115810
- console.log(`
115818
+ console.log(
115819
+ `
115811
115820
  \u5730\u7406\u4F4D\u7F6E\u6279\u91CF\u89E3\u6790\uFF08${opts.account}\uFF09\uFF1A\u6210\u529F ${payload.resolved} / \u5931\u8D25 ${failed.length}
115812
- `);
115821
+ `
115822
+ );
115813
115823
  for (const r of rows) {
115814
115824
  if (r.id) {
115815
115825
  console.log(` \u2705 ${r.query} \u2192 id:${r.id} ${r.name ?? ""} [${r.targetType ?? ""}]`);
@@ -115818,11 +115828,9 @@ async function runAdGeoResolve(opts) {
115818
115828
  }
115819
115829
  }
115820
115830
  if (failed.length > 0) {
115821
- console.error(
115822
- `
115831
+ console.error(`
115823
115832
  \u274C ${failed.length} \u4E2A\u5730\u540D\u672A\u89E3\u6790\uFF1B\u8BF7\u6539\u540D\u540E\u91CD\u8BD5\uFF0C\u6216\u68C0\u67E5\u62FC\u5199\uFF08\u52FF\u7F16\u9020 id\uFF09\u3002
115824
- `
115825
- );
115833
+ `);
115826
115834
  if (!opts.allowPartial) process.exit(1);
115827
115835
  } else {
115828
115836
  console.log(
@@ -116089,13 +116097,7 @@ function buildExtensionRemediateCommandLine(opts) {
116089
116097
  if (type === "CALLOUT") {
116090
116098
  const text = pickString(props["Text"], props["CalloutText"]);
116091
116099
  if (!text) return null;
116092
- const parts = [
116093
- ...base,
116094
- "callout",
116095
- `-a ${accountId}`,
116096
- `--text ${shellArg(text)}`,
116097
- levelFlag
116098
- ];
116100
+ const parts = [...base, "callout", `-a ${accountId}`, `--text ${shellArg(text)}`, levelFlag];
116099
116101
  if (campaignFlag) parts.push(campaignFlag);
116100
116102
  parts.push(`--json-out ${jsonOut}`);
116101
116103
  parts.push(`--commit ${shellArg("batch-diff remediate extension")}`);
@@ -118885,7 +118887,9 @@ function formatPmaxExtensionsAttachErrors(result) {
118885
118887
  );
118886
118888
  }
118887
118889
  if (result.whatsapp && !result.whatsapp.ok) {
118888
- msgs.push(`WhatsApp\u300C${result.whatsapp.label}\u300D\u5931\u8D25\uFF1A${classifyExtensionAttachError(result.whatsapp.error)}`);
118890
+ msgs.push(
118891
+ `WhatsApp\u300C${result.whatsapp.label}\u300D\u5931\u8D25\uFF1A${classifyExtensionAttachError(result.whatsapp.error)}`
118892
+ );
118889
118893
  }
118890
118894
  return msgs;
118891
118895
  }
@@ -121160,7 +121164,11 @@ function register21(program2) {
121160
121164
  });
121161
121165
  }
121162
121166
  );
121163
- adCmd.command("campaigns").alias("campaign-list").description("\u67E5\u8BE2\u5E7F\u544A\u7CFB\u5217\u5217\u8868\uFF08\u65E5\u671F\u9ED8\u8BA4\u8FD1 30 \u5929\uFF1B\u522B\u540D campaign-list\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u9ED8\u8BA4 30 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u4ECA\u5929\uFF09").option("-t, --token <token>", "Auth Token").option(
121167
+ adCmd.command("campaigns").alias("campaign-list").description("\u67E5\u8BE2\u5E7F\u544A\u7CFB\u5217\u5217\u8868\uFF08\u65E5\u671F\u9ED8\u8BA4\u8FD1 30 \u5929\uFF1B\u522B\u540D campaign-list\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u9ED8\u8BA4 30 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u4ECA\u5929\uFF09").option(
121168
+ "--fresh",
121169
+ "\u8DF3\u8FC7\u7F51\u5173 60 \u79D2\u7F13\u5B58\uFF0C\u5F3A\u5236\u62C9\u53D6\u6700\u65B0\u6570\u636E\uFF08\u7F51\u5173 newest=true\uFF09\uFF1B\u521A\u521B\u5EFA/\u7F16\u8F91\u5B8C\u60F3\u7ACB\u523B\u770B\u5230\u7ED3\u679C\u65F6\u7528",
121170
+ false
121171
+ ).option("-t, --token <token>", "Auth Token").option(
121164
121172
  "--json-out <path>",
121165
121173
  "\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",
121166
121174
  void 0
@@ -121171,12 +121179,17 @@ function register21(program2) {
121171
121179
  account: opts.account,
121172
121180
  startDate: opts.start,
121173
121181
  endDate: opts.end,
121182
+ fresh: opts.fresh,
121174
121183
  jsonOut: opts.jsonOut,
121175
121184
  verbose: opts.verbose
121176
121185
  });
121177
121186
  }
121178
121187
  );
121179
- adCmd.command("groups").alias("group-list").description("\u67E5\u8BE2\u5E7F\u544A\u7EC4\u5217\u8868\uFF08\u65E5\u671F\u9ED8\u8BA4\u8FD1 30 \u5929\uFF1B\u522B\u540D group-list\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u9ED8\u8BA4 30 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u4ECA\u5929\uFF09").option("-t, --token <token>", "Auth Token").option(
121188
+ adCmd.command("groups").alias("group-list").description("\u67E5\u8BE2\u5E7F\u544A\u7EC4\u5217\u8868\uFF08\u65E5\u671F\u9ED8\u8BA4\u8FD1 30 \u5929\uFF1B\u522B\u540D group-list\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u9ED8\u8BA4 30 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u4ECA\u5929\uFF09").option(
121189
+ "--fresh",
121190
+ "\u8DF3\u8FC7\u7F51\u5173 60 \u79D2\u7F13\u5B58\uFF0C\u5F3A\u5236\u62C9\u53D6\u6700\u65B0\u6570\u636E\uFF08\u7F51\u5173 newest=true\uFF09\uFF1B\u521A\u521B\u5EFA/\u7F16\u8F91\u5B8C\u60F3\u7ACB\u523B\u770B\u5230\u7ED3\u679C\u65F6\u7528",
121191
+ false
121192
+ ).option("-t, --token <token>", "Auth Token").option(
121180
121193
  "--json-out <path>",
121181
121194
  "\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",
121182
121195
  void 0
@@ -121187,6 +121200,7 @@ function register21(program2) {
121187
121200
  account: opts.account,
121188
121201
  startDate: opts.start,
121189
121202
  endDate: opts.end,
121203
+ fresh: opts.fresh,
121190
121204
  jsonOut: opts.jsonOut,
121191
121205
  verbose: opts.verbose
121192
121206
  });
@@ -121209,16 +121223,26 @@ function register21(program2) {
121209
121223
  });
121210
121224
  }
121211
121225
  );
121212
- adCmd.command("keywords").alias("keyword-list").description("\u67E5\u8BE2\u5173\u952E\u8BCD\u5217\u8868\uFF08--negative \u67E5\u8BE2\u5426\u5B9A\u5173\u952E\u8BCD\uFF1B\u65E5\u671F\u9ED8\u8BA4\u8FD1 30 \u5929\uFF1B\u522B\u540D keyword-list\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option("--negative", "\u67E5\u8BE2\u5426\u5B9A\u5173\u952E\u8BCD", false).option("--start <date>", "\u5F00\u59CB\u65E5\u671F\uFF08YYYY-MM-DD\uFF0C\u9ED8\u8BA4 30 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F\uFF08YYYY-MM-DD\uFF0C\u9ED8\u8BA4\u4ECA\u5929\uFF09").option("-t, --token <token>", "Auth Token").option(
121226
+ adCmd.command("keywords").alias("keyword-list").description(
121227
+ "\u67E5\u8BE2\u5173\u952E\u8BCD\u5217\u8868\uFF08--negative \u67E5\u8BE2\u5426\u5B9A\u5173\u952E\u8BCD\uFF0C\u53EF\u7528 --level \u6309\u7CFB\u5217/\u7EC4/\u8D26\u6237\u8FC7\u6EE4\uFF1B\u65E5\u671F\u9ED8\u8BA4\u8FD1 30 \u5929\uFF1B\u522B\u540D keyword-list\uFF09"
121228
+ ).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option("--negative", "\u67E5\u8BE2\u5426\u5B9A\u5173\u952E\u8BCD", false).option(
121229
+ "--level <level>",
121230
+ "\u5426\u5B9A\u5173\u952E\u8BCD\u5C42\u7EA7\u8FC7\u6EE4\uFF1AAccount | Campaign | AdGroup\uFF08\u4EC5\u914D\u5408 --negative\uFF1B\u4E0D\u4F20\u5219\u4E09\u7EA7\u90FD\u663E\u793A\uFF09"
121231
+ ).option("--start <date>", "\u5F00\u59CB\u65E5\u671F\uFF08YYYY-MM-DD\uFF0C\u9ED8\u8BA4 30 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F\uFF08YYYY-MM-DD\uFF0C\u9ED8\u8BA4\u4ECA\u5929\uFF09").option("-t, --token <token>", "Auth Token").option(
121213
121232
  "--json-out <path>",
121214
121233
  "\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",
121215
121234
  void 0
121216
121235
  ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
121217
121236
  async (opts) => {
121237
+ if (opts.level && !["Account", "Campaign", "AdGroup"].includes(opts.level)) {
121238
+ console.error("\n\u274C --level \u53EA\u63A5\u53D7 Account | Campaign | AdGroup\n");
121239
+ process.exit(1);
121240
+ }
121218
121241
  await runAdKeywords({
121219
121242
  token: opts.token,
121220
121243
  account: opts.account,
121221
121244
  negative: opts.negative,
121245
+ level: opts.level,
121222
121246
  startDate: opts.start,
121223
121247
  endDate: opts.end,
121224
121248
  jsonOut: opts.jsonOut,
@@ -121394,8 +121418,17 @@ function register21(program2) {
121394
121418
  }
121395
121419
  );
121396
121420
  adCmd.command("keyword-negative-create").description(
121397
- "\u6DFB\u52A0\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u7CFB\u5217/\u7EC4\u7EA7\uFF1B\u652F\u6301\u7A7A\u683C\u591A\u8BCD\u77ED\u8BED\uFF0C\u5982 how to make\uFF1B\u53EF\u7528 --keywords-file\uFF09"
121398
- ).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--campaign-id <id>", "\u5E7F\u544A\u7CFB\u5217 ID").requiredOption("--campaign-name <name>", "\u5E7F\u544A\u7CFB\u5217\u540D\u79F0").option(
121421
+ "\u6DFB\u52A0\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u7CFB\u5217/\u7EC4/\u8D26\u6237\u4E09\u7EA7\uFF1B\u652F\u6301\u7A7A\u683C\u591A\u8BCD\u77ED\u8BED\uFF0C\u5982 how to make\uFF1B\u53EF\u7528 --keywords-file\uFF09\n\n --level Account\uFF1A\u8D26\u6237\u7EA7\u5426\u8BCD\uFF08Sammamish 2026-08 \u65B0\u589E\uFF0CAccount Settings \u2192 Negative keywords\uFF09\uFF0C\u65E0\u9700 --campaign-id/--adgroup-id\u3002\n \u4E0D\u4F20 --level\uFF1A\u6309\u662F\u5426\u4F20 --adgroup-id \u81EA\u52A8\u5224\u65AD Campaign / AdGroup\uFF08\u517C\u5BB9\u65E7\u7528\u6CD5\uFF09\u3002"
121422
+ ).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option(
121423
+ "--level <level>",
121424
+ "\u5C42\u7EA7\uFF1AAccount | Campaign | AdGroup\uFF08\u4E0D\u4F20\u5219\u6309 --adgroup-id \u81EA\u52A8\u5224\u65AD\uFF09"
121425
+ ).option(
121426
+ "--campaign-id <id>",
121427
+ "\u5E7F\u544A\u7CFB\u5217 ID\uFF08level=Campaign/AdGroup \u5FC5\u4F20\uFF1Blevel=Account \u4E0D\u53EF\u4F20\uFF09"
121428
+ ).option(
121429
+ "--campaign-name <name>",
121430
+ "\u5E7F\u544A\u7CFB\u5217\u540D\u79F0\uFF08level=Campaign/AdGroup \u5FC5\u4F20\uFF1Blevel=Account \u4E0D\u53EF\u4F20\uFF09"
121431
+ ).option(
121399
121432
  "--keywords <kw1,kw2,...>",
121400
121433
  "\u5426\u5B9A\u8BCD\u5217\u8868\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u7A7A\u683C\u591A\u8BCD\u5408\u6CD5\uFF09\uFF1B\u53EF\u4E0E --keywords-file \u5E76\u7528",
121401
121434
  (v) => parseKeywordsCsv(v)
@@ -121405,11 +121438,19 @@ function register21(program2) {
121405
121438
  ).option(
121406
121439
  "--match-type <Broad|Phrase|Exact>",
121407
121440
  "\u7EDF\u4E00\u5339\u914D\u7C7B\u578B\uFF08Broad \u65E0\u5305\u88C5 / Phrase \u52A0\u53CC\u5F15\u53F7 / Exact \u52A0\u65B9\u62EC\u53F7\uFF09"
121408
- ).option("--adgroup-id <id>", "\u5E7F\u544A\u7EC4 ID\uFF08\u6307\u5B9A\u540E\u4E3A\u5E7F\u544A\u7EC4\u5C42\u7EA7\u5426\u5B9A\uFF09").option("--adgroup-name <name>", "\u5E7F\u544A\u7EC4\u540D\u79F0").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
121441
+ ).option(
121442
+ "--adgroup-id <id>",
121443
+ "\u5E7F\u544A\u7EC4 ID\uFF08level=AdGroup \u5FC5\u4F20\uFF1B\u672A\u4F20 --level \u65F6\u6307\u5B9A\u5373\u89C6\u4E3A AdGroup \u5C42\u7EA7\uFF09"
121444
+ ).option("--adgroup-name <name>", "\u5E7F\u544A\u7EC4\u540D\u79F0").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
121409
121445
  async (opts) => {
121446
+ if (opts.level && !["Account", "Campaign", "AdGroup"].includes(opts.level)) {
121447
+ console.error("\n\u274C --level \u53EA\u63A5\u53D7 Account | Campaign | AdGroup\n");
121448
+ process.exit(1);
121449
+ }
121410
121450
  await runAdNegativeKeywordCreate({
121411
121451
  token: opts.token,
121412
121452
  account: opts.account,
121453
+ level: opts.level,
121413
121454
  campaignId: opts.campaignId,
121414
121455
  campaignName: opts.campaignName,
121415
121456
  adgroupId: opts.adgroupId,
@@ -121421,7 +121462,9 @@ function register21(program2) {
121421
121462
  });
121422
121463
  }
121423
121464
  );
121424
- adCmd.command("keyword-negative-delete").description("\u5220\u9664\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u9700\u5148\u901A\u8FC7 ad keywords --negative \u67E5\u5230 ID\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <keywordId>", "\u5426\u5B9A\u5173\u952E\u8BCD ID").option("--start <date>", "\u5217\u8868\u67E5\u8BE2\u8D77\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u6700\u8FD1 30 \u5929\uFF09").option("--end <date>", "\u5217\u8868\u67E5\u8BE2\u7ED3\u675F\u65E5\u671F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u4ECA\u5929\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
121465
+ adCmd.command("keyword-negative-delete").description(
121466
+ "\u5220\u9664\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u9700\u5148\u901A\u8FC7 ad keywords --negative \u67E5\u5230 ID\uFF1B\u7CFB\u5217/\u7EC4/\u8D26\u6237\u4E09\u7EA7\u901A\u7528\uFF0C\u8D26\u6237\u7EA7\u65E0\u9700\u989D\u5916\u53C2\u6570\uFF09"
121467
+ ).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <keywordId>", "\u5426\u5B9A\u5173\u952E\u8BCD ID").option("--start <date>", "\u5217\u8868\u67E5\u8BE2\u8D77\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u6700\u8FD1 30 \u5929\uFF09").option("--end <date>", "\u5217\u8868\u67E5\u8BE2\u7ED3\u675F\u65E5\u671F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u4ECA\u5929\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
121425
121468
  async (opts) => {
121426
121469
  await runAdNegativeKeywordDelete({
121427
121470
  token: opts.token,
@@ -121877,7 +121920,7 @@ function register21(program2) {
121877
121920
  });
121878
121921
  }
121879
121922
  );
121880
- adCmd.command("keyword-negative-edit").description("\u7F16\u8F91\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u6587\u672C\u6216\u5339\u914D\u7C7B\u578B\uFF0C\u81F3\u5C11\u4F20\u4E00\u4E2A\u4FEE\u6539\u9879\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption(
121923
+ adCmd.command("keyword-negative-edit").description("\u7F16\u8F91\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u6587\u672C\u6216\u5339\u914D\u7C7B\u578B\uFF0C\u81F3\u5C11\u4F20\u4E00\u4E2A\u4FEE\u6539\u9879\uFF1B\u7CFB\u5217/\u7EC4/\u8D26\u6237\u4E09\u7EA7\u901A\u7528\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption(
121881
121924
  "--id <keywordId>",
121882
121925
  "\u5426\u5B9A\u5173\u952E\u8BCD ID\uFF08\u6765\u81EA ad keywords --negative --json-out \u2192 id\uFF09"
121883
121926
  ).option("--text <text>", "\u65B0\u5173\u952E\u8BCD\u6587\u672C").option(
@@ -126965,7 +127008,9 @@ function buildDataAvailability(opts) {
126965
127008
  } else {
126966
127009
  parts.push("\u4F18\u5148\u4F9D\u636E signals.* \u5E03\u5C14/\u8BA1\u6570\u8BC1\u636E\u5199 issue\uFF1Bsuggestion \u987B\u542B\u53EF\u6267\u884C\u52A8\u4F5C\u4E0E\u9875\u9762\u4F4D\u7F6E\u3002");
126967
127010
  }
126968
- parts.push("\u5B8C\u6574\u6D41\u7A0B\uFF1A\u6309 signals + website-diagnosis-rules \u2192 \u5199 diagnosis.json \u2192 website-diagnosis render\u3002");
127011
+ parts.push(
127012
+ "\u5B8C\u6574\u6D41\u7A0B\uFF1A\u6309 signals + website-diagnosis-rules \u2192 \u5199 diagnosis.json \u2192 website-diagnosis render\u3002"
127013
+ );
126969
127014
  return {
126970
127015
  lighthouse,
126971
127016
  html,
@@ -126981,7 +127026,8 @@ function extractWebsiteDiagnosisSignals(opts) {
126981
127026
  if (/googletagmanager\.com|gtm\.js|GTM-[A-Z0-9]+/i.test(rawHtml)) analyticsHints.push("gtm");
126982
127027
  if (/google-analytics\.com|gtag\(|G-[A-Z0-9]+|UA-\d+/i.test(rawHtml)) analyticsHints.push("ga");
126983
127028
  if (/facebook\.net\/.*fbevents|fbq\s*\(/i.test(rawHtml)) analyticsHints.push("meta_pixel");
126984
- if (/hotjar\.com|clarity\.ms|linkedin\.com\/px/i.test(rawHtml)) analyticsHints.push("other_tracker");
127029
+ if (/hotjar\.com|clarity\.ms|linkedin\.com\/px/i.test(rawHtml))
127030
+ analyticsHints.push("other_tracker");
126985
127031
  const html = stripTags(rawHtml);
126986
127032
  const titleM = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
126987
127033
  const title = titleM?.[1]?.replace(/\s+/g, " ").trim() || null;
@@ -128508,9 +128554,7 @@ function googleAdsDiagnosisReportTemplatePath() {
128508
128554
  }
128509
128555
  function injectGoogleAdsDiagnosisData(html, payload) {
128510
128556
  if (!html.includes(GOOGLE_ADS_DIAGNOSIS_DATA_MARKER)) {
128511
- throw new Error(
128512
- "\u62A5\u544A\u6A21\u677F\u7F3A\u5C11 __REPORT_DATA__ \u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 GoogleAdsDiagnosisReport.html"
128513
- );
128557
+ throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11 __REPORT_DATA__ \u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 GoogleAdsDiagnosisReport.html");
128514
128558
  }
128515
128559
  const json = JSON.stringify(payload).replace(/</g, "\\u003c");
128516
128560
  return html.replace(GOOGLE_ADS_DIAGNOSIS_DATA_MARKER, json);
@@ -128576,11 +128620,7 @@ function buildGoogleAdsDiagnosisAgentBrief(input) {
128576
128620
  },
128577
128621
  comparisonTableItemCounts: input.comparisonTableItemCounts,
128578
128622
  agentRules: {
128579
- doNotModifyItemRows: [
128580
- "campaigns.items",
128581
- "geographic.items",
128582
- "keywords.items"
128583
- ],
128623
+ doNotModifyItemRows: ["campaigns.items", "geographic.items", "keywords.items"],
128584
128624
  renderRestoresComparisonItemsFromCollect: "render \u9ED8\u8BA4\u540C\u76EE\u5F55 google-ads-diagnosis-collect.json \u4F1A\u4EE5 collect \u8986\u76D6\u4E0A\u8FF0 items\uFF08Agent \u53EA\u586B analysis/suggestions\uFF09",
128585
128625
  mayEditItemFields: [
128586
128626
  "biddingStrategy.items[].recommendationReason",
@@ -128632,7 +128672,6 @@ function createDefaultDiagnosisReportData() {
128632
128672
  keywordCount: 0,
128633
128673
  adCount: 0,
128634
128674
  extensionCount: 0,
128635
- negativeKeywordCount: 0,
128636
128675
  countriesWithConversionsCount: 0
128637
128676
  },
128638
128677
  conversionGoals: { items: [] },
@@ -128887,7 +128926,14 @@ function toDailyConversionItem(row) {
128887
128926
  const rawDate = String(row.date ?? "");
128888
128927
  const date = rawDate.includes("T") ? rawDate.slice(0, 10) : rawDate.slice(0, 10);
128889
128928
  const cpa = conversions > 0 ? cost / conversions : num(row.costPerConversion ?? row.cpa);
128890
- return { date, cost, conversions, cpa, clicks: num(row.clicks), impressions: num(row.impressions) };
128929
+ return {
128930
+ date,
128931
+ cost,
128932
+ conversions,
128933
+ cpa,
128934
+ clicks: num(row.clicks),
128935
+ impressions: num(row.impressions)
128936
+ };
128891
128937
  }
128892
128938
  function loadCompanyNameFromSnap(snapDir) {
128893
128939
  const file = path23.join(snapDir, "list-accounts-google.json");
@@ -128965,7 +129011,7 @@ function buildStructureCounts(resourceRecord, campaignRows2, keywordRows, search
128965
129011
  keywordCount,
128966
129012
  adCount: num(resourceRecord?.adCount),
128967
129013
  extensionCount: num(resourceRecord?.extensionCount),
128968
- negativeKeywordCount: num(resourceRecord?.negativeKeywordCount),
129014
+ // collect 未拉取否词明细,不输出 negativeKeywordCount(避免报告展示 0 误导)
128969
129015
  countriesWithConversionsCount: num(resourceRecord?.countriesWithConversionsCount),
128970
129016
  searchTermCount: searchRows.length
128971
129017
  };
@@ -128988,9 +129034,7 @@ function buildKeywordShareItems(rows) {
128988
129034
  return {
128989
129035
  keyword: keywordText || "\u672A\u547D\u540D",
128990
129036
  cpc: num(row.averageCpc ?? row.cpc),
128991
- matchType: String(
128992
- row.keywordMatchTypeZh ?? row.keywordMatchType ?? row.matchType ?? ""
128993
- ),
129037
+ matchType: String(row.keywordMatchTypeZh ?? row.keywordMatchType ?? row.matchType ?? ""),
128994
129038
  impressions: num(row.impressions),
128995
129039
  clicks: num(row.clicks),
128996
129040
  cost,
@@ -129061,12 +129105,12 @@ function buildBudgetCompetitiveness(overviewRecord, dimensionRecord) {
129061
129105
  const current = asPeriodRecord(overviewRecord?.currentPeriod);
129062
129106
  const totalCost = num(overviewRecord?.totalCost ?? current?.spend);
129063
129107
  const activeDays = num(overviewRecord?.activeDays);
129064
- const avgDaily = num(overviewRecord?.averageDailyCost ?? (activeDays > 0 ? totalCost / activeDays : 0));
129108
+ const avgDaily = num(
129109
+ overviewRecord?.averageDailyCost ?? (activeDays > 0 ? totalCost / activeDays : 0)
129110
+ );
129065
129111
  const remainingBudget = num(overviewRecord?.remainingAccountBudget);
129066
129112
  const utilization = remainingBudget > 0 && avgDaily > 0 ? Math.min(100, avgDaily / (avgDaily + remainingBudget / Math.max(activeDays, 1)) * 100) : avgDaily > 0 ? 85 : 0;
129067
- const sis = num(
129068
- dimensionRecord?.searchImpressionShare ?? current?.searchImpressionShare
129069
- );
129113
+ const sis = num(dimensionRecord?.searchImpressionShare ?? current?.searchImpressionShare);
129070
129114
  const budgetLost = num(
129071
129115
  dimensionRecord?.searchBudgetLostImpressionShare ?? current?.searchBudgetLostImpressionShare
129072
129116
  );
@@ -129126,13 +129170,12 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
129126
129170
  loadSectionSnapshot(snapDir, "resource-counts", accountId, start, end)
129127
129171
  );
129128
129172
  const goldRecord = sectionRecord(loadSectionSnapshot(snapDir, "gold-account", accountId, start, end)) ?? {};
129173
+ delete goldRecord.adsNegativeQualified;
129129
129174
  const campaignCurrent = sectionItems(
129130
129175
  loadSectionSnapshot(snapDir, "campaigns", accountId, start, end)
129131
129176
  );
129132
129177
  const adRows = sectionItems(loadSectionSnapshot(snapDir, "ads", accountId, start, end));
129133
- const kwCurrent = sectionItems(
129134
- loadSectionSnapshot(snapDir, "keywords", accountId, start, end)
129135
- );
129178
+ const kwCurrent = sectionItems(loadSectionSnapshot(snapDir, "keywords", accountId, start, end));
129136
129179
  const searchRows = sectionItems(
129137
129180
  loadSectionSnapshot(snapDir, "search-terms", accountId, start, end)
129138
129181
  );
@@ -129195,7 +129238,6 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
129195
129238
  keywordCount: structureCounts.keywordCount,
129196
129239
  adCount: structureCounts.adCount,
129197
129240
  extensionCount: structureCounts.extensionCount,
129198
- negativeKeywordCount: structureCounts.negativeKeywordCount,
129199
129241
  countriesWithConversionsCount: structureCounts.countriesWithConversionsCount
129200
129242
  };
129201
129243
  data.conversionGoals = {
@@ -129261,9 +129303,7 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
129261
129303
  items: geoTargeting,
129262
129304
  ...emptyModuleNarrative()
129263
129305
  };
129264
- const deviceRows = sectionItems(
129265
- loadSectionSnapshot(snapDir, "devices", accountId, start, end)
129266
- );
129306
+ const deviceRows = sectionItems(loadSectionSnapshot(snapDir, "devices", accountId, start, end));
129267
129307
  const deviceItems = deviceRows.map(
129268
129308
  (row) => toTargetingItem(row, "deviceType", String(row.deviceType ?? "\u672A\u77E5\u8BBE\u5907"))
129269
129309
  );
@@ -129313,9 +129353,7 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
129313
129353
  items: buildAdCreativeItems(adRows),
129314
129354
  ...emptyModuleNarrative()
129315
129355
  };
129316
- const campaignTypes = sectionRecord(
129317
- loadSectionSnapshot(snapDir, "campaign-types", accountId)
129318
- );
129356
+ const campaignTypes = sectionRecord(loadSectionSnapshot(snapDir, "campaign-types", accountId));
129319
129357
  const nfItems = (Array.isArray(data.newFeatures.items) ? data.newFeatures.items : []).filter((item) => String(item.strategy ?? "") !== "AiMax");
129320
129358
  data.newFeatures = {
129321
129359
  items: nfItems.map((item) => {
@@ -129462,9 +129500,7 @@ function previousDateRange2(start, end) {
129462
129500
  }
129463
129501
  async function resolveWebsiteUrl(opts, snapDir, accountId) {
129464
129502
  if (opts.website?.trim()) return normalizeWebsiteUrl(opts.website);
129465
- const ads = sectionItems(
129466
- loadSectionSnapshot(snapDir, "ads", accountId, opts.start, opts.end)
129467
- );
129503
+ const ads = sectionItems(loadSectionSnapshot(snapDir, "ads", accountId, opts.start, opts.end));
129468
129504
  for (const ad of ads) {
129469
129505
  const url = String(ad.finalurl ?? ad.destinationUrl ?? "").trim();
129470
129506
  if (url) return normalizeWebsiteUrl(url);
@@ -129503,18 +129539,14 @@ async function runGoogleAdsDiagnosisCollect(opts) {
129503
129539
  }
129504
129540
  if (mainPartial) {
129505
129541
  console.error(main.stderr || main.stdout);
129506
- console.error(
129507
- "\n\u26A0\uFE0F google-analysis \u90E8\u5206\u7EF4\u5EA6\u62C9\u53D6\u5931\u8D25\uFF0C\u5C06\u7EE7\u7EED\u62C9\u53D6\u5BF9\u6BD4\u5468\u671F\u5E76\u751F\u6210 collect\u3002\n"
129508
- );
129542
+ console.error("\n\u26A0\uFE0F google-analysis \u90E8\u5206\u7EF4\u5EA6\u62C9\u53D6\u5931\u8D25\uFF0C\u5C06\u7EE7\u7EED\u62C9\u53D6\u5BF9\u6BD4\u5468\u671F\u5E76\u751F\u6210 collect\u3002\n");
129509
129543
  degraded = true;
129510
129544
  }
129511
129545
  if (opts.fetchPreviousPeriod !== false) {
129512
129546
  const prev = previousDateRange2(opts.start, opts.end);
129513
- console.error(
129514
- `
129547
+ console.error(`
129515
129548
  \u{1F4E5} \u62C9\u53D6\u5BF9\u6BD4\u5468\u671F campaigns/geographic/keywords\uFF1A${prev.start} ~ ${prev.end}
129516
- `
129517
- );
129549
+ `);
129518
129550
  const prevRun = invokeGoogleAnalysisCli({
129519
129551
  account: accountId,
129520
129552
  start: prev.start,
@@ -129668,17 +129700,13 @@ function validateAdCreativeItemSuggestions(data) {
129668
129700
  const row = asRecord7(items2[i]);
129669
129701
  const title = String(row?.adTitle ?? i);
129670
129702
  if (!hasNarrativeContent(row?.suggestion ?? row?.suggestions)) {
129671
- warnings.push(
129672
- `adCreativeOptimization.items[${title}].suggestion \u4E3A\u7A7A\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`
129673
- );
129703
+ warnings.push(`adCreativeOptimization.items[${title}].suggestion \u4E3A\u7A7A\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`);
129674
129704
  }
129675
129705
  }
129676
129706
  return warnings;
129677
129707
  }
129678
129708
  function validateGoogleAdsDiagnosisContent(data) {
129679
- const missingKeys = GOOGLE_ADS_DIAGNOSIS_TOP_LEVEL_KEYS.filter(
129680
- (key) => data[key] === void 0
129681
- );
129709
+ const missingKeys = GOOGLE_ADS_DIAGNOSIS_TOP_LEVEL_KEYS.filter((key) => data[key] === void 0);
129682
129710
  const warnings = [];
129683
129711
  const accountInfo = asRecord7(data.accountInfo);
129684
129712
  if (accountInfo && !accountInfo.companyName && !accountInfo.period) {
@@ -129930,7 +129958,7 @@ var NARRATIVE_CONSISTENCY_RULES = [
129930
129958
  id: "negative-keyword-zero-as-core-issue",
129931
129959
  textPattern: /否定(关键)?词.{0,40}(为\s*0|数量为\s*0|为0|缺少|没有)/,
129932
129960
  isContradicted: () => true,
129933
- hint: "\u7981\u6B62\u5C06\u300C\u5426\u5B9A\u5173\u952E\u8BCD\u6570\u91CF\u4E3A 0 / \u7F3A\u5C11\u5426\u8BCD\u300D\u5199\u5165 summary.keyIssues \u6216 diagnosisOverview.disadvantages\u3002structure.negativeKeywordCount \u53EF\u5728\u7ED3\u6784\u8868\u5C55\u793A\uFF0C\u4F46\u4E0D\u4F5C\u4E3A\u6838\u5FC3\u95EE\u9898\uFF1B\u5426\u8BCD\u5206\u6790\u653E\u5230\u5173\u952E\u8BCD/\u641C\u7D22\u8BCD\u6A21\u5757\u6309\u641C\u7D22\u8BCD\u6D6A\u8D39\u8BC1\u636E\u5199\u3002"
129961
+ hint: "\u672C\u62A5\u544A collect \u672A\u62C9\u53D6\u5426\u8BCD\u6570\u636E\uFF0C\u7981\u6B62\u5C06\u300C\u5426\u5B9A\u5173\u952E\u8BCD\u6570\u91CF\u4E3A 0 / \u7F3A\u5C11\u5426\u8BCD\u300D\u5199\u5165 summary.keyIssues \u6216 diagnosisOverview.disadvantages\u3002\u6709\u641C\u7D22\u8BCD\u6D6A\u8D39\u65F6\u4EC5\u5728 \xA708 \u6309\u5177\u4F53\u641C\u7D22\u8BCD\u5199\u4F18\u5316\u5EFA\u8BAE\uFF0C\u52FF\u4EE5\u5426\u8BCD\u8BA1\u6570\u4E0B\u7ED3\u8BBA\u3002"
129934
129962
  },
129935
129963
  {
129936
129964
  id: "landing-page-missing-as-core-issue",
@@ -130179,7 +130207,11 @@ function registerGoogleAdsDiagnosisCommands(program2) {
130179
130207
  ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684\u8BCA\u65AD JSON\uFF08\u4E0E MarkAI sectionData \u540C\u7ED3\u6784\uFF09").option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").option(
130180
130208
  "--collect <file>",
130181
130209
  "collect JSON \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\u7684 google-ads-diagnosis-collect.json\uFF0C\u7528\u4E8E\u6062\u590D\u5BF9\u6BD4\u8868 items\uFF09"
130182
- ).option("--no-merge-collect", "\u4E0D\u4ECE collect \u81EA\u52A8\u6062\u590D campaigns/geographic/keywords.items", false).option("--lenient", "\u7ED3\u6784\u8B66\u544A\uFF08\u5982\u7F3A analysis\uFF09\u65F6\u4E0D\u963B\u65AD\u6E32\u67D3", false).action(
130210
+ ).option(
130211
+ "--no-merge-collect",
130212
+ "\u4E0D\u4ECE collect \u81EA\u52A8\u6062\u590D campaigns/geographic/keywords.items",
130213
+ false
130214
+ ).option("--lenient", "\u7ED3\u6784\u8B66\u544A\uFF08\u5982\u7F3A analysis\uFF09\u65F6\u4E0D\u963B\u65AD\u6E32\u67D3", false).action(
130183
130215
  async (opts) => {
130184
130216
  await runGoogleAdsDiagnosisRender({
130185
130217
  dataFile: opts.data,
@@ -131477,12 +131509,7 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
131477
131509
  const tables = { ...payload.tables ?? {} };
131478
131510
  {
131479
131511
  const rows = asRows(sectionMap.get("ad-sets")?.adGroups).map(
131480
- (r) => pickMetricExtras(r, [
131481
- "adGroupId",
131482
- "adGroupName",
131483
- "campaignName",
131484
- "spendPercentage"
131485
- ])
131512
+ (r) => pickMetricExtras(r, ["adGroupId", "adGroupName", "campaignName", "spendPercentage"])
131486
131513
  );
131487
131514
  if (rows.length > 0) tables.adSets = rows;
131488
131515
  }
@@ -132307,10 +132334,7 @@ async function fetchBingOverviewPayload(config, id, startDate, endDate, verbose)
132307
132334
  endDate,
132308
132335
  verbose
132309
132336
  );
132310
- return normalizeBingSectionPayload(
132311
- syncOverviewBalanceFields(withPrevious),
132312
- "overview"
132313
- );
132337
+ return normalizeBingSectionPayload(syncOverviewBalanceFields(withPrevious), "overview");
132314
132338
  }
132315
132339
  async function enrichBingOverviewPreviousPeriod(config, accountId, record2, startDate, endDate, verbose) {
132316
132340
  if (!isBingPeriodBlockEmpty(record2.previousPeriod)) {
@@ -133520,7 +133544,11 @@ function mapAudienceMerged(payload) {
133520
133544
  return {
133521
133545
  audienceGender: mapAudienceList(data?.gender, "gender", "gender"),
133522
133546
  audienceAge: mapAudienceList(data?.age, "age", "ageRange"),
133523
- audienceInterest: mapAudienceList(data?.interest_category, "interest_category", "interestCategory")
133547
+ audienceInterest: mapAudienceList(
133548
+ data?.interest_category,
133549
+ "interest_category",
133550
+ "interestCategory"
133551
+ )
133524
133552
  };
133525
133553
  }
133526
133554
  async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {