siluzan-tso-cli 1.1.44-beta.3 → 1.1.44

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 (143) hide show
  1. package/README.md +1 -2
  2. package/dist/index.js +209 -163
  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 +21 -21
  13. package/dist/skill/references/accounts/accounts-permissions.md +29 -29
  14. package/dist/skill/references/accounts/accounts.md +9 -9
  15. package/dist/skill/references/accounts/finance.md +1 -2
  16. package/dist/skill/references/accounts/open-account-by-media.md +58 -59
  17. package/dist/skill/references/accounts/open-account-google-ui.md +19 -19
  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 +8 -8
  23. package/dist/skill/references/analytics/reporting.md +5 -5
  24. package/dist/skill/references/analytics/website-diagnosis-guide.md +6 -6
  25. package/dist/skill/references/core/agent-conventions.md +54 -54
  26. package/dist/skill/references/core/intent-routing.md +107 -108
  27. package/dist/skill/references/core/setup.md +6 -5
  28. package/dist/skill/references/core/tips.md +0 -1
  29. package/dist/skill/references/core/workflows.md +15 -16
  30. package/dist/skill/references/google-ads/google-ads-campaign-plan.md +55 -55
  31. package/dist/skill/references/google-ads/google-ads-read.md +30 -12
  32. package/dist/skill/references/google-ads/google-ads-write.md +35 -18
  33. package/dist/skill/references/google-ads/google-ads.md +13 -14
  34. package/dist/skill/references/google-ads/rules/README.md +17 -17
  35. package/dist/skill/references/google-ads/rules/google-ads-account-audit.md +6 -8
  36. package/dist/skill/references/google-ads/rules/google-ads-compliance.md +5 -7
  37. package/dist/skill/references/google-ads/rules/google-ads-keyword-strategy.md +9 -9
  38. package/dist/skill/references/google-ads/rules/google-ads-launch-plan-template.md +16 -15
  39. package/dist/skill/references/google-ads/rules/google-ads-plan-source-fidelity.md +27 -27
  40. package/dist/skill/references/google-ads/rules/google-ads-pmax-launch-plan-template.md +41 -41
  41. package/dist/skill/references/misc/tso-home.md +2 -2
  42. package/dist/skill/references/operations/guard.md +24 -24
  43. package/dist/skill/references/operations/hosted-automation-self-control.md +15 -16
  44. package/dist/skill/references/operations/hosted-automation-user-catalog.md +18 -18
  45. package/dist/skill/references/report-templates/README.md +1 -1
  46. package/dist/skill/references/report-templates/bing-period-report.md +16 -16
  47. package/dist/skill/references/report-templates/google-ads-diagnosis.md +26 -23
  48. package/dist/skill/references/report-templates/google-inquiry-analysis.md +1 -1
  49. package/dist/skill/references/report-templates/google-period-report.md +5 -5
  50. package/dist/skill/references/report-templates/meta-account-diagnosis-report.md +2 -2
  51. package/dist/skill/references/report-templates/okki-weekly-google-client.md +0 -1
  52. package/dist/skill/references/report-templates/tiktok-period-report.md +6 -6
  53. package/dist/skill/report-templates/GoogleAdsDiagnosisReport.html +42 -13
  54. package/dist/skill/report-templates/README.md +1 -1
  55. package/dist/skill/report-templates/bing-period-report.html +10 -7
  56. package/dist/skill/report-templates/bing-period-report.md +16 -16
  57. package/dist/skill/report-templates/google-ads-diagnosis.md +26 -23
  58. package/dist/skill/report-templates/google-inquiry-analysis.md +1 -1
  59. package/dist/skill/report-templates/google-period-report.md +5 -5
  60. package/dist/skill/report-templates/meta-account-diagnosis-report.md +2 -2
  61. package/dist/skill/report-templates/okki-weekly-google-client.md +0 -1
  62. package/dist/skill/report-templates/tiktok-period-report.md +6 -6
  63. package/dist/skill/scripts/install.ps1 +3 -3
  64. package/dist/skill/scripts/install.sh +3 -3
  65. package/eval/cases/accounts-entityid-vs-mediaccustomerid.scenario.json +2 -14
  66. package/eval/cases/accounts-mcc-bind-inquiry.scenario.json +1 -3
  67. package/eval/cases/accounts-single-balance-not-bulk.scenario.json +3 -14
  68. package/eval/cases/budget-display-not-raw-micros.scenario.json +1 -8
  69. package/eval/cases/clue-meta-leads-json.scenario.json +2 -14
  70. package/eval/cases/clue-tiktok-leads-json.scenario.json +2 -11
  71. package/eval/cases/destructive-account-delink-needs-confirm.scenario.json +3 -9
  72. package/eval/cases/destructive-forewarning-delete-needs-confirm.scenario.json +3 -9
  73. package/eval/cases/destructive-invoice-apply-needs-confirm.scenario.json +3 -9
  74. package/eval/cases/facebook-analysis-google-section-aliases.scenario.json +2 -11
  75. package/eval/cases/facebook-analysis-not-google-keywords.scenario.json +3 -10
  76. package/eval/cases/facebook-analysis-period-default.scenario.json +2 -14
  77. package/eval/cases/finance-invoice-info-list.scenario.json +3 -11
  78. package/eval/cases/forewarning-list-google.scenario.json +3 -14
  79. package/eval/cases/google-ads-no-structural-without-confirm.scenario.json +2 -6
  80. package/eval/cases/google-analysis-keywords-route.scenario.json +2 -14
  81. package/eval/cases/guard-budget-circuit-all-google.scenario.json +2 -12
  82. package/eval/cases/guard-zero-conv-empty-spend.scenario.json +2 -12
  83. package/eval/cases/human-p1-multiturn.scenario.json +1 -5
  84. package/eval/cases/ir-p1-vs-p8-account-vs-url.scenario.json +5 -17
  85. package/eval/cases/ir-p3-cpa-monitor-not-p7.scenario.json +2 -19
  86. package/eval/cases/ir-p3-zero-inquiry-not-p7.scenario.json +3 -16
  87. package/eval/cases/ir-p4-period-not-diagnosis.scenario.json +3 -16
  88. package/eval/cases/ir-p8-meets-google-ads-requirements-with-url.scenario.json +3 -28
  89. package/eval/cases/ir-p8-network-diagnosis-with-url.scenario.json +3 -25
  90. package/eval/cases/ir-p8-website-monitor-report-with-url.scenario.json +3 -28
  91. package/eval/cases/ir-p8-website-test-report-no-url.scenario.json +3 -14
  92. package/eval/cases/ir-p9-industry-not-p8.scenario.json +3 -19
  93. package/eval/cases/ir-vague-report-clarify.scenario.json +3 -14
  94. package/eval/cases/ir-w3-vs-p8-from-website.scenario.json +2 -8
  95. package/eval/cases/ir-w5-keyword-planner-url-core-terms.scenario.json +2 -22
  96. package/eval/cases/meta-single-balance-not-bulk.scenario.json +3 -17
  97. package/eval/cases/no-legacy-json-flag.scenario.json +2 -6
  98. package/eval/cases/open-account-google-noninteractive.scenario.json +1 -3
  99. package/eval/cases/open-account-tiktok-license-file.scenario.json +1 -3
  100. package/eval/cases/optimize-list-by-account.scenario.json +3 -11
  101. package/eval/cases/p1-single-account-profile.scenario.json +1 -11
  102. package/eval/cases/p2-balance-scan-bulk.scenario.json +2 -9
  103. package/eval/cases/p3-accounts-digest.scenario.json +1 -5
  104. package/eval/cases/p3-cpa-scan-max-cpa.scenario.json +2 -14
  105. package/eval/cases/p4-fb-meta-period-report.scenario.json +2 -12
  106. package/eval/cases/p4-period-report-window.scenario.json +1 -8
  107. package/eval/cases/pmax-asset-group-create-with-bg.scenario.json +2 -13
  108. package/eval/cases/pmax-brand-edit-routing.scenario.json +3 -16
  109. package/eval/cases/pmax-edit-not-campaign-edit.scenario.json +2 -12
  110. package/eval/cases/pmax-enable-brand-guidelines.scenario.json +2 -12
  111. package/eval/cases/pmax-no-assets-update-brand-fields.scenario.json +3 -14
  112. package/eval/cases/rag-before-keyword-expand.scenario.json +1 -11
  113. package/eval/cases/rag-list-then-query.scenario.json +2 -14
  114. package/eval/cases/report-list-google.scenario.json +2 -11
  115. package/eval/cases/report-push-list-google.scenario.json +2 -11
  116. package/eval/cases/setup-login-or-env.scenario.json +1 -3
  117. package/eval/cases/setup-siluzan-data-permission-env.scenario.json +1 -3
  118. package/eval/cases/tiktok-bc-bind-inquiry.scenario.json +2 -6
  119. package/eval/cases/time-range-user-delegates-default.scenario.json +1 -8
  120. package/eval/cases/tips-json-out-filtering.scenario.json +1 -3
  121. package/eval/cases/tips-large-json-pagination.scenario.json +1 -3
  122. package/eval/cases/uj-ad-campaign-validate-before-create-stub.scenario.json +2 -11
  123. package/eval/cases/uj-ad-outdoor-campgear-search-plan.scenario.json +1 -3
  124. package/eval/cases/uj-ad-website-search-plan-table-format.scenario.json +1 -3
  125. package/eval/cases/uj-analytics-30d-pdf-campaign-device-geo.scenario.json +6 -18
  126. package/eval/cases/uj-analytics-compare-google-tiktok-last-month-roi.scenario.json +1 -8
  127. package/eval/cases/uj-analytics-ecommerce-industry-report.scenario.json +2 -14
  128. package/eval/cases/uj-analytics-google-weekly-trends-campaigns-keywords.scenario.json +2 -11
  129. package/eval/cases/uj-analytics-report-push-weekly-email.scenario.json +1 -3
  130. package/eval/cases/uj-finance-invoice-records-this-month.scenario.json +2 -11
  131. package/eval/cases/uj-life-newbie-siluzan-google-end-to-end.scenario.json +1 -4
  132. package/eval/cases/uj-ops-google-accounts-list-normal.scenario.json +2 -17
  133. package/eval/cases/uj-ops-google-yesterday-spend-conversions.scenario.json +2 -14
  134. package/eval/cases/uj-ops-pause-worst-adgroup-confirm.scenario.json +2 -6
  135. package/eval/cases/uj-ops-tiktok-leads-last-week.scenario.json +3 -17
  136. package/eval/cases/uj-patrol-cpc-spike-adgroups-over-15.scenario.json +1 -5
  137. package/eval/cases/uj-patrol-forewarning-create-daily-cap-3000.scenario.json +1 -3
  138. package/eval/cases/uj-patrol-forewarning-trigger-records.scenario.json +3 -17
  139. package/eval/cases/uj-patrol-google-balances-low.scenario.json +2 -11
  140. package/eval/cases/uj-roi-optimize-records-then-execute-cautiously.scenario.json +3 -14
  141. package/eval/cases/uj-roi-search-terms-add-negative-keywords.scenario.json +2 -14
  142. package/eval/cases/yandex-stats-use-porg-mediaccustomerid.scenario.json +3 -21
  143. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4026,7 +4026,7 @@ var DEFAULT_API_BASE;
4026
4026
  var init_defaults = __esm({
4027
4027
  "src/config/defaults.ts"() {
4028
4028
  "use strict";
4029
- DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
4029
+ DEFAULT_API_BASE = "https://tso-api.siluzan.com";
4030
4030
  }
4031
4031
  });
4032
4032
 
@@ -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));
@@ -111918,6 +111890,9 @@ function firstKeywordTextFromRecord(k) {
111918
111890
  }
111919
111891
  return "";
111920
111892
  }
111893
+ function toNegativeKeywordAppliedLevelQuery(level) {
111894
+ return level === "AdGroup" ? "Ad group" : level;
111895
+ }
111921
111896
  function keywordRowToYuan(item) {
111922
111897
  const { maxCPC: rawMaxCpc, ...rest } = item;
111923
111898
  if (rawMaxCpc == null) return item;
@@ -111930,6 +111905,9 @@ async function runAdKeywords(opts) {
111930
111905
  const params = new URLSearchParams();
111931
111906
  params.set("startDate", toGoogleDate(opts.startDate, -30));
111932
111907
  params.set("endDate", toGoogleDate(opts.endDate, 0));
111908
+ if (opts.negative && opts.level) {
111909
+ params.set("appliedlevel", toNegativeKeywordAppliedLevelQuery(opts.level));
111910
+ }
111933
111911
  const endpoint = opts.negative ? "negativekeywordmanagement" : "keywordmanagement";
111934
111912
  const url = `${googleApiUrl}/${endpoint}/v2/list/${opts.account}?${params}`;
111935
111913
  let data;
@@ -111977,7 +111955,8 @@ ${label}\uFF08\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672C
111977
111955
  items2.forEach((item) => {
111978
111956
  const kwText = Array.isArray(item["keywordText"]) ? item["keywordText"].join(", ") : String(item["text"] ?? item["keywordText"] ?? item["id"] ?? "\u2014");
111979
111957
  const matchType = item["matchTypeV2"] ?? "\u2014";
111980
- console.log(` [${matchType}] ${kwText} id: ${String(item["id"] ?? "")}`);
111958
+ const level = item["negativeLevel"] ?? "\u2014";
111959
+ console.log(` [${matchType}] ${kwText} id: ${String(item["id"] ?? "")} \u7EA7\u522B: ${level}`);
111981
111960
  });
111982
111961
  } else {
111983
111962
  const kwW = Math.min(
@@ -112080,6 +112059,30 @@ async function runAdKeywordCreate(opts) {
112080
112059
  \u2705 \u5DF2\u6DFB\u52A0 ${keywords.length} \u4E2A\u5173\u952E\u8BCD\u5230\u5E7F\u544A\u7EC4\u300C${opts.adgroupName}\u300D
112081
112060
  `);
112082
112061
  }
112062
+ function resolveNegativeKeywordLevel(opts) {
112063
+ if (opts.level) return opts.level;
112064
+ return opts.adgroupId ? "AdGroup" : "Campaign";
112065
+ }
112066
+ function buildNegativeKeywordCreateBody(level, keywords, opts) {
112067
+ if (level === "Account") {
112068
+ return { KeywordText: keywords, NegativeLevel: "Account" };
112069
+ }
112070
+ const body = {
112071
+ KeywordText: keywords,
112072
+ CampaignId: opts.campaignId,
112073
+ Campaign: opts.campaignName
112074
+ };
112075
+ if (level === "AdGroup") {
112076
+ body["AdGroupId"] = opts.adgroupId;
112077
+ body["AdGroup"] = opts.adgroupName ?? "";
112078
+ }
112079
+ return body;
112080
+ }
112081
+ var negativeKeywordLevelLabel = {
112082
+ Account: "\u8D26\u6237",
112083
+ Campaign: "\u5E7F\u544A\u7CFB\u5217",
112084
+ AdGroup: "\u5E7F\u544A\u7EC4"
112085
+ };
112083
112086
  async function runAdNegativeKeywordCreate(opts) {
112084
112087
  opts = withResolvedAdAccount(opts);
112085
112088
  const config = loadConfig(opts.token);
@@ -112097,16 +112100,27 @@ async function runAdNegativeKeywordCreate(opts) {
112097
112100
  `);
112098
112101
  process.exit(1);
112099
112102
  }
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 ?? "";
112103
+ const level = resolveNegativeKeywordLevel(opts);
112104
+ if (level === "Account") {
112105
+ if (opts.campaignId || opts.adgroupId) {
112106
+ console.error(
112107
+ "\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"
112108
+ );
112109
+ process.exit(1);
112110
+ }
112111
+ } else {
112112
+ if (!opts.campaignId || !opts.campaignName) {
112113
+ console.error(`
112114
+ \u274C --level ${level} \u9700\u8981\u540C\u65F6\u4F20 --campaign-id \u4E0E --campaign-name
112115
+ `);
112116
+ process.exit(1);
112117
+ }
112118
+ if (level === "AdGroup" && !opts.adgroupId) {
112119
+ console.error("\n\u274C --level AdGroup \u9700\u8981\u4F20 --adgroup-id\n");
112120
+ process.exit(1);
112121
+ }
112109
112122
  }
112123
+ const body = buildNegativeKeywordCreateBody(level, keywords, opts);
112110
112124
  const url = `${googleApiUrl}/negativekeywordmanagement/negativekeyword/${opts.account}`;
112111
112125
  try {
112112
112126
  await apiFetch2(url, config, { method: "POST", body: JSON.stringify(body) }, opts.verbose);
@@ -112116,9 +112130,11 @@ async function runAdNegativeKeywordCreate(opts) {
112116
112130
  `);
112117
112131
  process.exit(1);
112118
112132
  }
112119
- console.log(`
112120
- \u2705 \u5DF2\u5728${level}\u5C42\u7EA7\u6DFB\u52A0 ${keywords.length} \u4E2A\u5426\u5B9A\u5173\u952E\u8BCD
112121
- `);
112133
+ console.log(
112134
+ `
112135
+ \u2705 \u5DF2\u5728${negativeKeywordLevelLabel[level]}\u5C42\u7EA7\u6DFB\u52A0 ${keywords.length} \u4E2A\u5426\u5B9A\u5173\u952E\u8BCD
112136
+ `
112137
+ );
112122
112138
  }
112123
112139
  async function runAdKeywordDelete(opts) {
112124
112140
  opts = withResolvedAdAccount(opts);
@@ -112271,7 +112287,21 @@ async function runAdNegativeKeywordEdit(opts) {
112271
112287
  }
112272
112288
  const url = `${googleApiUrl}/negativekeywordmanagement/negativekeyword/${opts.account}/${opts.id}`;
112273
112289
  try {
112274
- await apiFetch2(url, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
112290
+ const raw = await apiFetch2(
112291
+ url,
112292
+ config,
112293
+ { method: "PUT", body: JSON.stringify(body) },
112294
+ opts.verbose
112295
+ );
112296
+ const returned = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : Array.isArray(raw) && raw[0] && typeof raw[0] === "object" ? raw[0] : null;
112297
+ const returnedId = returned && typeof returned["id"] === "string" ? returned["id"] : void 0;
112298
+ if (returnedId && returnedId !== opts.id) {
112299
+ console.log(
112300
+ `
112301
+ \u2139\uFE0F \u7F51\u5173\u8FD4\u56DE\u7684\u5426\u5B9A\u5173\u952E\u8BCD id \u5DF2\u53D8\u66F4\u4E3A ${returnedId}\uFF08\u8BF7\u6C42\u65F6 id \u4E3A ${opts.id}\uFF09\uFF0C\u540E\u7EED\u67E5\u8BE2/\u5220\u9664\u8BF7\u4F7F\u7528\u65B0 id\u3002
112302
+ `
112303
+ );
112304
+ }
112275
112305
  } catch (err) {
112276
112306
  console.error(`
112277
112307
  \u274C \u7F16\u8F91\u5426\u5B9A\u5173\u952E\u8BCD\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
@@ -113506,10 +113536,7 @@ async function runAdCampaigns(opts) {
113506
113536
  opts = withResolvedAdAccount(opts);
113507
113537
  const config = loadConfig(opts.token);
113508
113538
  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}`;
113539
+ const url = campaignListUrl(googleApiUrl, opts.account, opts.startDate, opts.endDate, opts.fresh);
113513
113540
  let data;
113514
113541
  try {
113515
113542
  data = await apiFetch2(url, config, {}, opts.verbose);
@@ -113939,10 +113966,7 @@ async function runAdGroups(opts) {
113939
113966
  opts = withResolvedAdAccount(opts);
113940
113967
  const config = loadConfig(opts.token);
113941
113968
  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}`;
113969
+ const url = adgroupListUrl(googleApiUrl, opts.account, opts.startDate, opts.endDate, opts.fresh);
113946
113970
  let data;
113947
113971
  try {
113948
113972
  data = await apiFetch2(url, config, {}, opts.verbose);
@@ -115583,7 +115607,12 @@ async function runAdDeviceBidSet(opts) {
115583
115607
  // src/commands/ad/geo.ts
115584
115608
  async function fetchGeoLocationCandidates(googleApiUrl, account, query, config, verbose) {
115585
115609
  const url = `${googleApiUrl}/campaignmanagement/geolocations/${account}/${encodeURIComponent(query)}`;
115586
- const data = await apiFetch2(url, config, {}, verbose);
115610
+ const data = await apiFetch2(
115611
+ url,
115612
+ config,
115613
+ {},
115614
+ verbose
115615
+ );
115587
115616
  return Array.isArray(data) ? data : data.data ?? [];
115588
115617
  }
115589
115618
  function geoItemDisplayName(item) {
@@ -115732,9 +115761,7 @@ async function runAdGeoResolve(opts) {
115732
115761
  process.exit(1);
115733
115762
  }
115734
115763
  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
- );
115764
+ console.error('\n\u274C \u8BF7\u63D0\u4F9B --queries "Peru,Chile,..." \u6216 --from-file <names.txt|names.json>\n');
115738
115765
  process.exit(1);
115739
115766
  }
115740
115767
  const config = loadConfig(opts.token);
@@ -115790,9 +115817,7 @@ async function runAdGeoResolve(opts) {
115790
115817
  /** 可直接写入 campaign-create JSON */
115791
115818
  locations,
115792
115819
  targetedLocations,
115793
- nameToId: Object.fromEntries(
115794
- rows.filter((r) => r.id).map((r) => [r.query, r.id])
115795
- )
115820
+ nameToId: Object.fromEntries(rows.filter((r) => r.id).map((r) => [r.query, r.id]))
115796
115821
  };
115797
115822
  if (await emitCliJsonOrSnapshot(
115798
115823
  { jsonOut: opts.jsonOut },
@@ -115807,9 +115832,11 @@ async function runAdGeoResolve(opts) {
115807
115832
  if (failed.length > 0 && !opts.allowPartial) process.exit(1);
115808
115833
  return;
115809
115834
  }
115810
- console.log(`
115835
+ console.log(
115836
+ `
115811
115837
  \u5730\u7406\u4F4D\u7F6E\u6279\u91CF\u89E3\u6790\uFF08${opts.account}\uFF09\uFF1A\u6210\u529F ${payload.resolved} / \u5931\u8D25 ${failed.length}
115812
- `);
115838
+ `
115839
+ );
115813
115840
  for (const r of rows) {
115814
115841
  if (r.id) {
115815
115842
  console.log(` \u2705 ${r.query} \u2192 id:${r.id} ${r.name ?? ""} [${r.targetType ?? ""}]`);
@@ -115818,11 +115845,9 @@ async function runAdGeoResolve(opts) {
115818
115845
  }
115819
115846
  }
115820
115847
  if (failed.length > 0) {
115821
- console.error(
115822
- `
115848
+ console.error(`
115823
115849
  \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
- );
115850
+ `);
115826
115851
  if (!opts.allowPartial) process.exit(1);
115827
115852
  } else {
115828
115853
  console.log(
@@ -116089,13 +116114,7 @@ function buildExtensionRemediateCommandLine(opts) {
116089
116114
  if (type === "CALLOUT") {
116090
116115
  const text = pickString(props["Text"], props["CalloutText"]);
116091
116116
  if (!text) return null;
116092
- const parts = [
116093
- ...base,
116094
- "callout",
116095
- `-a ${accountId}`,
116096
- `--text ${shellArg(text)}`,
116097
- levelFlag
116098
- ];
116117
+ const parts = [...base, "callout", `-a ${accountId}`, `--text ${shellArg(text)}`, levelFlag];
116099
116118
  if (campaignFlag) parts.push(campaignFlag);
116100
116119
  parts.push(`--json-out ${jsonOut}`);
116101
116120
  parts.push(`--commit ${shellArg("batch-diff remediate extension")}`);
@@ -118885,7 +118904,9 @@ function formatPmaxExtensionsAttachErrors(result) {
118885
118904
  );
118886
118905
  }
118887
118906
  if (result.whatsapp && !result.whatsapp.ok) {
118888
- msgs.push(`WhatsApp\u300C${result.whatsapp.label}\u300D\u5931\u8D25\uFF1A${classifyExtensionAttachError(result.whatsapp.error)}`);
118907
+ msgs.push(
118908
+ `WhatsApp\u300C${result.whatsapp.label}\u300D\u5931\u8D25\uFF1A${classifyExtensionAttachError(result.whatsapp.error)}`
118909
+ );
118889
118910
  }
118890
118911
  return msgs;
118891
118912
  }
@@ -121160,7 +121181,11 @@ function register21(program2) {
121160
121181
  });
121161
121182
  }
121162
121183
  );
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(
121184
+ 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(
121185
+ "--fresh",
121186
+ "\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",
121187
+ false
121188
+ ).option("-t, --token <token>", "Auth Token").option(
121164
121189
  "--json-out <path>",
121165
121190
  "\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
121191
  void 0
@@ -121171,12 +121196,17 @@ function register21(program2) {
121171
121196
  account: opts.account,
121172
121197
  startDate: opts.start,
121173
121198
  endDate: opts.end,
121199
+ fresh: opts.fresh,
121174
121200
  jsonOut: opts.jsonOut,
121175
121201
  verbose: opts.verbose
121176
121202
  });
121177
121203
  }
121178
121204
  );
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(
121205
+ 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(
121206
+ "--fresh",
121207
+ "\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",
121208
+ false
121209
+ ).option("-t, --token <token>", "Auth Token").option(
121180
121210
  "--json-out <path>",
121181
121211
  "\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
121212
  void 0
@@ -121187,6 +121217,7 @@ function register21(program2) {
121187
121217
  account: opts.account,
121188
121218
  startDate: opts.start,
121189
121219
  endDate: opts.end,
121220
+ fresh: opts.fresh,
121190
121221
  jsonOut: opts.jsonOut,
121191
121222
  verbose: opts.verbose
121192
121223
  });
@@ -121209,16 +121240,26 @@ function register21(program2) {
121209
121240
  });
121210
121241
  }
121211
121242
  );
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(
121243
+ adCmd.command("keywords").alias("keyword-list").description(
121244
+ "\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"
121245
+ ).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option("--negative", "\u67E5\u8BE2\u5426\u5B9A\u5173\u952E\u8BCD", false).option(
121246
+ "--level <level>",
121247
+ "\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"
121248
+ ).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
121249
  "--json-out <path>",
121214
121250
  "\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
121251
  void 0
121216
121252
  ).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
121217
121253
  async (opts) => {
121254
+ if (opts.level && !["Account", "Campaign", "AdGroup"].includes(opts.level)) {
121255
+ console.error("\n\u274C --level \u53EA\u63A5\u53D7 Account | Campaign | AdGroup\n");
121256
+ process.exit(1);
121257
+ }
121218
121258
  await runAdKeywords({
121219
121259
  token: opts.token,
121220
121260
  account: opts.account,
121221
121261
  negative: opts.negative,
121262
+ level: opts.level,
121222
121263
  startDate: opts.start,
121223
121264
  endDate: opts.end,
121224
121265
  jsonOut: opts.jsonOut,
@@ -121394,8 +121435,17 @@ function register21(program2) {
121394
121435
  }
121395
121436
  );
121396
121437
  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(
121438
+ "\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"
121439
+ ).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option(
121440
+ "--level <level>",
121441
+ "\u5C42\u7EA7\uFF1AAccount | Campaign | AdGroup\uFF08\u4E0D\u4F20\u5219\u6309 --adgroup-id \u81EA\u52A8\u5224\u65AD\uFF09"
121442
+ ).option(
121443
+ "--campaign-id <id>",
121444
+ "\u5E7F\u544A\u7CFB\u5217 ID\uFF08level=Campaign/AdGroup \u5FC5\u4F20\uFF1Blevel=Account \u4E0D\u53EF\u4F20\uFF09"
121445
+ ).option(
121446
+ "--campaign-name <name>",
121447
+ "\u5E7F\u544A\u7CFB\u5217\u540D\u79F0\uFF08level=Campaign/AdGroup \u5FC5\u4F20\uFF1Blevel=Account \u4E0D\u53EF\u4F20\uFF09"
121448
+ ).option(
121399
121449
  "--keywords <kw1,kw2,...>",
121400
121450
  "\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
121451
  (v) => parseKeywordsCsv(v)
@@ -121405,11 +121455,19 @@ function register21(program2) {
121405
121455
  ).option(
121406
121456
  "--match-type <Broad|Phrase|Exact>",
121407
121457
  "\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(
121458
+ ).option(
121459
+ "--adgroup-id <id>",
121460
+ "\u5E7F\u544A\u7EC4 ID\uFF08level=AdGroup \u5FC5\u4F20\uFF1B\u672A\u4F20 --level \u65F6\u6307\u5B9A\u5373\u89C6\u4E3A AdGroup \u5C42\u7EA7\uFF09"
121461
+ ).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
121462
  async (opts) => {
121463
+ if (opts.level && !["Account", "Campaign", "AdGroup"].includes(opts.level)) {
121464
+ console.error("\n\u274C --level \u53EA\u63A5\u53D7 Account | Campaign | AdGroup\n");
121465
+ process.exit(1);
121466
+ }
121410
121467
  await runAdNegativeKeywordCreate({
121411
121468
  token: opts.token,
121412
121469
  account: opts.account,
121470
+ level: opts.level,
121413
121471
  campaignId: opts.campaignId,
121414
121472
  campaignName: opts.campaignName,
121415
121473
  adgroupId: opts.adgroupId,
@@ -121421,7 +121479,9 @@ function register21(program2) {
121421
121479
  });
121422
121480
  }
121423
121481
  );
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(
121482
+ adCmd.command("keyword-negative-delete").description(
121483
+ "\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"
121484
+ ).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
121485
  async (opts) => {
121426
121486
  await runAdNegativeKeywordDelete({
121427
121487
  token: opts.token,
@@ -121877,7 +121937,7 @@ function register21(program2) {
121877
121937
  });
121878
121938
  }
121879
121939
  );
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(
121940
+ 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
121941
  "--id <keywordId>",
121882
121942
  "\u5426\u5B9A\u5173\u952E\u8BCD ID\uFF08\u6765\u81EA ad keywords --negative --json-out \u2192 id\uFF09"
121883
121943
  ).option("--text <text>", "\u65B0\u5173\u952E\u8BCD\u6587\u672C").option(
@@ -126965,7 +127025,9 @@ function buildDataAvailability(opts) {
126965
127025
  } else {
126966
127026
  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
127027
  }
126968
- parts.push("\u5B8C\u6574\u6D41\u7A0B\uFF1A\u6309 signals + website-diagnosis-rules \u2192 \u5199 diagnosis.json \u2192 website-diagnosis render\u3002");
127028
+ parts.push(
127029
+ "\u5B8C\u6574\u6D41\u7A0B\uFF1A\u6309 signals + website-diagnosis-rules \u2192 \u5199 diagnosis.json \u2192 website-diagnosis render\u3002"
127030
+ );
126969
127031
  return {
126970
127032
  lighthouse,
126971
127033
  html,
@@ -126981,7 +127043,8 @@ function extractWebsiteDiagnosisSignals(opts) {
126981
127043
  if (/googletagmanager\.com|gtm\.js|GTM-[A-Z0-9]+/i.test(rawHtml)) analyticsHints.push("gtm");
126982
127044
  if (/google-analytics\.com|gtag\(|G-[A-Z0-9]+|UA-\d+/i.test(rawHtml)) analyticsHints.push("ga");
126983
127045
  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");
127046
+ if (/hotjar\.com|clarity\.ms|linkedin\.com\/px/i.test(rawHtml))
127047
+ analyticsHints.push("other_tracker");
126985
127048
  const html = stripTags(rawHtml);
126986
127049
  const titleM = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
126987
127050
  const title = titleM?.[1]?.replace(/\s+/g, " ").trim() || null;
@@ -128508,9 +128571,7 @@ function googleAdsDiagnosisReportTemplatePath() {
128508
128571
  }
128509
128572
  function injectGoogleAdsDiagnosisData(html, payload) {
128510
128573
  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
- );
128574
+ throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11 __REPORT_DATA__ \u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 GoogleAdsDiagnosisReport.html");
128514
128575
  }
128515
128576
  const json = JSON.stringify(payload).replace(/</g, "\\u003c");
128516
128577
  return html.replace(GOOGLE_ADS_DIAGNOSIS_DATA_MARKER, json);
@@ -128576,11 +128637,7 @@ function buildGoogleAdsDiagnosisAgentBrief(input) {
128576
128637
  },
128577
128638
  comparisonTableItemCounts: input.comparisonTableItemCounts,
128578
128639
  agentRules: {
128579
- doNotModifyItemRows: [
128580
- "campaigns.items",
128581
- "geographic.items",
128582
- "keywords.items"
128583
- ],
128640
+ doNotModifyItemRows: ["campaigns.items", "geographic.items", "keywords.items"],
128584
128641
  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
128642
  mayEditItemFields: [
128586
128643
  "biddingStrategy.items[].recommendationReason",
@@ -128886,7 +128943,14 @@ function toDailyConversionItem(row) {
128886
128943
  const rawDate = String(row.date ?? "");
128887
128944
  const date = rawDate.includes("T") ? rawDate.slice(0, 10) : rawDate.slice(0, 10);
128888
128945
  const cpa = conversions > 0 ? cost / conversions : num(row.costPerConversion ?? row.cpa);
128889
- return { date, cost, conversions, cpa, clicks: num(row.clicks), impressions: num(row.impressions) };
128946
+ return {
128947
+ date,
128948
+ cost,
128949
+ conversions,
128950
+ cpa,
128951
+ clicks: num(row.clicks),
128952
+ impressions: num(row.impressions)
128953
+ };
128890
128954
  }
128891
128955
  function loadCompanyNameFromSnap(snapDir) {
128892
128956
  const file = path23.join(snapDir, "list-accounts-google.json");
@@ -128987,9 +129051,7 @@ function buildKeywordShareItems(rows) {
128987
129051
  return {
128988
129052
  keyword: keywordText || "\u672A\u547D\u540D",
128989
129053
  cpc: num(row.averageCpc ?? row.cpc),
128990
- matchType: String(
128991
- row.keywordMatchTypeZh ?? row.keywordMatchType ?? row.matchType ?? ""
128992
- ),
129054
+ matchType: String(row.keywordMatchTypeZh ?? row.keywordMatchType ?? row.matchType ?? ""),
128993
129055
  impressions: num(row.impressions),
128994
129056
  clicks: num(row.clicks),
128995
129057
  cost,
@@ -129060,12 +129122,12 @@ function buildBudgetCompetitiveness(overviewRecord, dimensionRecord) {
129060
129122
  const current = asPeriodRecord(overviewRecord?.currentPeriod);
129061
129123
  const totalCost = num(overviewRecord?.totalCost ?? current?.spend);
129062
129124
  const activeDays = num(overviewRecord?.activeDays);
129063
- const avgDaily = num(overviewRecord?.averageDailyCost ?? (activeDays > 0 ? totalCost / activeDays : 0));
129125
+ const avgDaily = num(
129126
+ overviewRecord?.averageDailyCost ?? (activeDays > 0 ? totalCost / activeDays : 0)
129127
+ );
129064
129128
  const remainingBudget = num(overviewRecord?.remainingAccountBudget);
129065
129129
  const utilization = remainingBudget > 0 && avgDaily > 0 ? Math.min(100, avgDaily / (avgDaily + remainingBudget / Math.max(activeDays, 1)) * 100) : avgDaily > 0 ? 85 : 0;
129066
- const sis = num(
129067
- dimensionRecord?.searchImpressionShare ?? current?.searchImpressionShare
129068
- );
129130
+ const sis = num(dimensionRecord?.searchImpressionShare ?? current?.searchImpressionShare);
129069
129131
  const budgetLost = num(
129070
129132
  dimensionRecord?.searchBudgetLostImpressionShare ?? current?.searchBudgetLostImpressionShare
129071
129133
  );
@@ -129130,9 +129192,7 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
129130
129192
  loadSectionSnapshot(snapDir, "campaigns", accountId, start, end)
129131
129193
  );
129132
129194
  const adRows = sectionItems(loadSectionSnapshot(snapDir, "ads", accountId, start, end));
129133
- const kwCurrent = sectionItems(
129134
- loadSectionSnapshot(snapDir, "keywords", accountId, start, end)
129135
- );
129195
+ const kwCurrent = sectionItems(loadSectionSnapshot(snapDir, "keywords", accountId, start, end));
129136
129196
  const searchRows = sectionItems(
129137
129197
  loadSectionSnapshot(snapDir, "search-terms", accountId, start, end)
129138
129198
  );
@@ -129260,9 +129320,7 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
129260
129320
  items: geoTargeting,
129261
129321
  ...emptyModuleNarrative()
129262
129322
  };
129263
- const deviceRows = sectionItems(
129264
- loadSectionSnapshot(snapDir, "devices", accountId, start, end)
129265
- );
129323
+ const deviceRows = sectionItems(loadSectionSnapshot(snapDir, "devices", accountId, start, end));
129266
129324
  const deviceItems = deviceRows.map(
129267
129325
  (row) => toTargetingItem(row, "deviceType", String(row.deviceType ?? "\u672A\u77E5\u8BBE\u5907"))
129268
129326
  );
@@ -129312,9 +129370,7 @@ function buildGoogleAdsDiagnosisCollectPayload(opts) {
129312
129370
  items: buildAdCreativeItems(adRows),
129313
129371
  ...emptyModuleNarrative()
129314
129372
  };
129315
- const campaignTypes = sectionRecord(
129316
- loadSectionSnapshot(snapDir, "campaign-types", accountId)
129317
- );
129373
+ const campaignTypes = sectionRecord(loadSectionSnapshot(snapDir, "campaign-types", accountId));
129318
129374
  const nfItems = (Array.isArray(data.newFeatures.items) ? data.newFeatures.items : []).filter((item) => String(item.strategy ?? "") !== "AiMax");
129319
129375
  data.newFeatures = {
129320
129376
  items: nfItems.map((item) => {
@@ -129461,9 +129517,7 @@ function previousDateRange2(start, end) {
129461
129517
  }
129462
129518
  async function resolveWebsiteUrl(opts, snapDir, accountId) {
129463
129519
  if (opts.website?.trim()) return normalizeWebsiteUrl(opts.website);
129464
- const ads = sectionItems(
129465
- loadSectionSnapshot(snapDir, "ads", accountId, opts.start, opts.end)
129466
- );
129520
+ const ads = sectionItems(loadSectionSnapshot(snapDir, "ads", accountId, opts.start, opts.end));
129467
129521
  for (const ad of ads) {
129468
129522
  const url = String(ad.finalurl ?? ad.destinationUrl ?? "").trim();
129469
129523
  if (url) return normalizeWebsiteUrl(url);
@@ -129502,18 +129556,14 @@ async function runGoogleAdsDiagnosisCollect(opts) {
129502
129556
  }
129503
129557
  if (mainPartial) {
129504
129558
  console.error(main.stderr || main.stdout);
129505
- console.error(
129506
- "\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"
129507
- );
129559
+ 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");
129508
129560
  degraded = true;
129509
129561
  }
129510
129562
  if (opts.fetchPreviousPeriod !== false) {
129511
129563
  const prev = previousDateRange2(opts.start, opts.end);
129512
- console.error(
129513
- `
129564
+ console.error(`
129514
129565
  \u{1F4E5} \u62C9\u53D6\u5BF9\u6BD4\u5468\u671F campaigns/geographic/keywords\uFF1A${prev.start} ~ ${prev.end}
129515
- `
129516
- );
129566
+ `);
129517
129567
  const prevRun = invokeGoogleAnalysisCli({
129518
129568
  account: accountId,
129519
129569
  start: prev.start,
@@ -129667,17 +129717,13 @@ function validateAdCreativeItemSuggestions(data) {
129667
129717
  const row = asRecord7(items2[i]);
129668
129718
  const title = String(row?.adTitle ?? i);
129669
129719
  if (!hasNarrativeContent(row?.suggestion ?? row?.suggestions)) {
129670
- warnings.push(
129671
- `adCreativeOptimization.items[${title}].suggestion \u4E3A\u7A7A\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`
129672
- );
129720
+ warnings.push(`adCreativeOptimization.items[${title}].suggestion \u4E3A\u7A7A\uFF08\u987B\u7531 Agent \u586B\u5199\uFF09`);
129673
129721
  }
129674
129722
  }
129675
129723
  return warnings;
129676
129724
  }
129677
129725
  function validateGoogleAdsDiagnosisContent(data) {
129678
- const missingKeys = GOOGLE_ADS_DIAGNOSIS_TOP_LEVEL_KEYS.filter(
129679
- (key) => data[key] === void 0
129680
- );
129726
+ const missingKeys = GOOGLE_ADS_DIAGNOSIS_TOP_LEVEL_KEYS.filter((key) => data[key] === void 0);
129681
129727
  const warnings = [];
129682
129728
  const accountInfo = asRecord7(data.accountInfo);
129683
129729
  if (accountInfo && !accountInfo.companyName && !accountInfo.period) {
@@ -130178,7 +130224,11 @@ function registerGoogleAdsDiagnosisCommands(program2) {
130178
130224
  ).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(
130179
130225
  "--collect <file>",
130180
130226
  "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"
130181
- ).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(
130227
+ ).option(
130228
+ "--no-merge-collect",
130229
+ "\u4E0D\u4ECE collect \u81EA\u52A8\u6062\u590D campaigns/geographic/keywords.items",
130230
+ false
130231
+ ).option("--lenient", "\u7ED3\u6784\u8B66\u544A\uFF08\u5982\u7F3A analysis\uFF09\u65F6\u4E0D\u963B\u65AD\u6E32\u67D3", false).action(
130182
130232
  async (opts) => {
130183
130233
  await runGoogleAdsDiagnosisRender({
130184
130234
  dataFile: opts.data,
@@ -131476,12 +131526,7 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
131476
131526
  const tables = { ...payload.tables ?? {} };
131477
131527
  {
131478
131528
  const rows = asRows(sectionMap.get("ad-sets")?.adGroups).map(
131479
- (r) => pickMetricExtras(r, [
131480
- "adGroupId",
131481
- "adGroupName",
131482
- "campaignName",
131483
- "spendPercentage"
131484
- ])
131529
+ (r) => pickMetricExtras(r, ["adGroupId", "adGroupName", "campaignName", "spendPercentage"])
131485
131530
  );
131486
131531
  if (rows.length > 0) tables.adSets = rows;
131487
131532
  }
@@ -132306,10 +132351,7 @@ async function fetchBingOverviewPayload(config, id, startDate, endDate, verbose)
132306
132351
  endDate,
132307
132352
  verbose
132308
132353
  );
132309
- return normalizeBingSectionPayload(
132310
- syncOverviewBalanceFields(withPrevious),
132311
- "overview"
132312
- );
132354
+ return normalizeBingSectionPayload(syncOverviewBalanceFields(withPrevious), "overview");
132313
132355
  }
132314
132356
  async function enrichBingOverviewPreviousPeriod(config, accountId, record2, startDate, endDate, verbose) {
132315
132357
  if (!isBingPeriodBlockEmpty(record2.previousPeriod)) {
@@ -133519,7 +133561,11 @@ function mapAudienceMerged(payload) {
133519
133561
  return {
133520
133562
  audienceGender: mapAudienceList(data?.gender, "gender", "gender"),
133521
133563
  audienceAge: mapAudienceList(data?.age, "age", "ageRange"),
133522
- audienceInterest: mapAudienceList(data?.interest_category, "interest_category", "interestCategory")
133564
+ audienceInterest: mapAudienceList(
133565
+ data?.interest_category,
133566
+ "interest_category",
133567
+ "interestCategory"
133568
+ )
133523
133569
  };
133524
133570
  }
133525
133571
  async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {