siluzan-tso-cli 1.1.13 → 1.1.14-beta.10

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 (114) hide show
  1. package/README.md +2 -3
  2. package/assets/siluzan-ads/references/hosted-automation-user-catalog.md +40 -0
  3. package/dist/index.js +371 -55
  4. package/dist/skill/SKILL.md +78 -37
  5. package/dist/skill/_meta.json +2 -2
  6. package/dist/skill/references/account-analytics.md +69 -2
  7. package/dist/skill/references/accounts.md +8 -5
  8. package/dist/skill/references/clue.md +5 -0
  9. package/dist/skill/references/finance.md +9 -5
  10. package/dist/skill/references/forewarning.md +7 -0
  11. package/dist/skill/references/google-ads-rules/google-ads-campaign-optimization.md +3 -2
  12. package/dist/skill/references/google-ads.md +123 -101
  13. package/dist/skill/references/hosted-automation-monitoring-json.md +94 -0
  14. package/dist/skill/references/hosted-automation-optimize-ab-winner.md +69 -0
  15. package/dist/skill/references/hosted-automation-optimize-index.md +32 -0
  16. package/dist/skill/references/hosted-automation-optimize-scale.md +99 -0
  17. package/dist/skill/references/hosted-automation-optimize-weak-downbid.md +94 -0
  18. package/dist/skill/references/hosted-automation-scenarios.md +23 -0
  19. package/dist/skill/references/hosted-automation-self-control.md +216 -0
  20. package/dist/skill/references/hosted-automation-user-catalog.md +40 -0
  21. package/dist/skill/references/open-account-google-ui.md +0 -6
  22. package/dist/skill/references/reporting.md +4 -2
  23. package/dist/skill/references/setup.md +5 -5
  24. package/dist/skill/references/tips.md +6 -0
  25. package/dist/skill/references/tso-home.md +1 -1
  26. package/dist/skill/references/workflows.md +14 -1
  27. package/dist/skill/report-templates/google-account-diagnosis-report.md +1 -1
  28. package/dist/skill/report-templates/google-period-report.md +1 -0
  29. package/dist/skill/scripts/install.ps1 +2 -2
  30. package/dist/skill/scripts/install.sh +2 -2
  31. package/eval/cases/accounts-entityid-vs-mediaccustomerid.scenario.json +23 -0
  32. package/eval/cases/accounts-mcc-bind-inquiry.scenario.json +12 -0
  33. package/eval/cases/accounts-single-balance-not-bulk.scenario.json +23 -0
  34. package/eval/cases/budget-display-not-raw-micros.scenario.json +17 -0
  35. package/eval/cases/clue-meta-leads-json.scenario.json +23 -0
  36. package/eval/cases/clue-tiktok-leads-json.scenario.json +20 -0
  37. package/eval/cases/destructive-account-delink-needs-confirm.scenario.json +15 -0
  38. package/eval/cases/destructive-forewarning-delete-needs-confirm.scenario.json +15 -0
  39. package/eval/cases/destructive-invoice-apply-needs-confirm.scenario.json +15 -0
  40. package/eval/cases/destructive-unshare-needs-confirm.scenario.json +9 -0
  41. package/eval/cases/finance-invoice-info-list.scenario.json +17 -0
  42. package/eval/cases/forewarning-list-google.scenario.json +20 -0
  43. package/eval/cases/google-ads-no-structural-without-confirm.scenario.json +12 -0
  44. package/eval/cases/google-analysis-keywords-route.scenario.json +23 -0
  45. package/eval/cases/hosted-sop-cpa-spike-downbid.scenario.json +14 -0
  46. package/eval/cases/hosted-sop-daily-budget-circuit-breaker.scenario.json +13 -0
  47. package/eval/cases/hosted-sop-empty-spend-pause-p1.scenario.json +14 -0
  48. package/eval/cases/human-p1-multiturn.scenario.json +17 -0
  49. package/eval/cases/meta-single-balance-not-bulk.scenario.json +26 -0
  50. package/eval/cases/open-account-bing-noninteractive.scenario.json +13 -0
  51. package/eval/cases/open-account-google-noninteractive.scenario.json +12 -0
  52. package/eval/cases/open-account-tiktok-license-file.scenario.json +12 -0
  53. package/eval/cases/optimize-list-by-account.scenario.json +17 -0
  54. package/eval/cases/p1-single-account-profile.scenario.json +20 -0
  55. package/eval/cases/p2-balance-scan-bulk.scenario.json +18 -0
  56. package/eval/cases/p3-accounts-digest.scenario.json +20 -0
  57. package/eval/cases/p4-period-report-window.scenario.json +17 -0
  58. package/eval/cases/report-list-google.scenario.json +20 -0
  59. package/eval/cases/report-push-list-google.scenario.json +20 -0
  60. package/eval/cases/reporting-vs-account-analytics-routing.scenario.json +13 -0
  61. package/eval/cases/setup-login-or-env.scenario.json +12 -0
  62. package/eval/cases/setup-siluzan-data-permission-env.scenario.json +19 -0
  63. package/eval/cases/skill-async-poll-guidance.scenario.json +9 -0
  64. package/eval/cases/skill-optimize-vs-google-ads-distinction.scenario.json +13 -0
  65. package/eval/cases/tiktok-bc-bind-inquiry.scenario.json +12 -0
  66. package/eval/cases/time-range-must-ask.scenario.json +16 -0
  67. package/eval/cases/time-range-user-delegates-default.scenario.json +17 -0
  68. package/eval/cases/tips-json-filtering.scenario.json +12 -0
  69. package/eval/cases/tips-large-json-pagination.scenario.json +19 -0
  70. package/eval/cases/uj-ad-bluetooth-keywords-exclude-cheap-free.scenario.json +9 -0
  71. package/eval/cases/uj-ad-keywords-camping-tent-outdoor-plan.scenario.json +9 -0
  72. package/eval/cases/uj-ad-outdoor-campgear-search-plan.scenario.json +12 -0
  73. package/eval/cases/uj-analytics-30d-pdf-campaign-device-geo.scenario.json +29 -0
  74. package/eval/cases/uj-analytics-compare-google-tiktok-last-month-roi.scenario.json +17 -0
  75. package/eval/cases/uj-analytics-google-weekly-trends-campaigns-keywords.scenario.json +20 -0
  76. package/eval/cases/uj-analytics-report-push-weekly-email.scenario.json +12 -0
  77. package/eval/cases/uj-finance-invoice-records-this-month.scenario.json +20 -0
  78. package/eval/cases/uj-life-newbie-siluzan-google-end-to-end.scenario.json +13 -0
  79. package/eval/cases/uj-ops-google-accounts-list-normal.scenario.json +23 -0
  80. package/eval/cases/uj-ops-google-yesterday-spend-conversions.scenario.json +23 -0
  81. package/eval/cases/uj-ops-open-google-b2c-usd-shenzhen.scenario.json +13 -0
  82. package/eval/cases/uj-ops-pause-worst-adgroup-confirm.scenario.json +12 -0
  83. package/eval/cases/uj-ops-tiktok-leads-last-week.scenario.json +23 -0
  84. package/eval/cases/uj-patrol-all-media-balance-stats-forewarning.scenario.json +9 -0
  85. package/eval/cases/uj-patrol-cpc-spike-adgroups-over-15.scenario.json +18 -0
  86. package/eval/cases/uj-patrol-forewarning-create-daily-cap-3000.scenario.json +12 -0
  87. package/eval/cases/uj-patrol-forewarning-trigger-records.scenario.json +23 -0
  88. package/eval/cases/uj-patrol-google-balances-low.scenario.json +20 -0
  89. package/eval/cases/uj-roi-full-google-account-diagnosis.scenario.json +9 -0
  90. package/eval/cases/uj-roi-keywords-high-cpa-low-cvr-triage.scenario.json +9 -0
  91. package/eval/cases/uj-roi-optimize-records-then-execute-cautiously.scenario.json +20 -0
  92. package/eval/cases/uj-roi-search-terms-add-negative-keywords.scenario.json +23 -0
  93. package/eval/stub-fixtures/accounts-digest.json +33 -0
  94. package/eval/stub-fixtures/ad-campaigns.json +14 -0
  95. package/eval/stub-fixtures/balance-meta.json +6 -0
  96. package/eval/stub-fixtures/balance-scan.json +21 -0
  97. package/eval/stub-fixtures/balance.json +6 -0
  98. package/eval/stub-fixtures/clue-meta.json +7 -0
  99. package/eval/stub-fixtures/clue-tiktok.json +7 -0
  100. package/eval/stub-fixtures/forewarning-create-ok.json +1 -0
  101. package/eval/stub-fixtures/forewarning-records.json +7 -0
  102. package/eval/stub-fixtures/generic-ok.json +1 -0
  103. package/eval/stub-fixtures/google-analysis-search-terms.json +10 -0
  104. package/eval/stub-fixtures/google-analysis.json +7 -0
  105. package/eval/stub-fixtures/invoice-billable.json +7 -0
  106. package/eval/stub-fixtures/invoice-info-list.json +13 -0
  107. package/eval/stub-fixtures/invoice-list.json +10 -0
  108. package/eval/stub-fixtures/list-accounts.json +13 -0
  109. package/eval/stub-fixtures/optimize-list.json +7 -0
  110. package/eval/stub-fixtures/report-push-list.json +7 -0
  111. package/eval/stub-fixtures/stats.json +9 -0
  112. package/package.json +7 -2
  113. package/scripts/postinstall.mjs +27 -2
  114. package/dist/skill/references/open-account-by-media.md +0 -61
package/dist/index.js CHANGED
@@ -1962,7 +1962,7 @@ import { fileURLToPath as fileURLToPath4 } from "url";
1962
1962
  import { Command } from "commander";
1963
1963
 
1964
1964
  // src/config/defaults.ts
1965
- var DEFAULT_API_BASE = "https://tso-api.siluzan.com";
1965
+ var DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
1966
1966
 
1967
1967
  // ../common/dist/index.js
1968
1968
  import * as fs from "fs";
@@ -2441,19 +2441,19 @@ import * as fs3 from "fs/promises";
2441
2441
  import * as path3 from "path";
2442
2442
  async function getSkillFiles(skillDir) {
2443
2443
  const out = {};
2444
- async function walk(dir, prefix) {
2444
+ async function walk2(dir, prefix) {
2445
2445
  const entries = await fs3.readdir(dir, { withFileTypes: true });
2446
2446
  for (const ent of entries) {
2447
2447
  const rel = prefix ? `${prefix}/${ent.name}` : ent.name;
2448
2448
  const full = path3.join(dir, ent.name);
2449
2449
  if (ent.isDirectory()) {
2450
- await walk(full, rel);
2450
+ await walk2(full, rel);
2451
2451
  } else {
2452
2452
  out[rel] = await fs3.readFile(full, "utf8");
2453
2453
  }
2454
2454
  }
2455
2455
  }
2456
- await walk(skillDir, "");
2456
+ await walk2(skillDir, "");
2457
2457
  return out;
2458
2458
  }
2459
2459
 
@@ -2872,8 +2872,9 @@ function fmt(d) {
2872
2872
  }
2873
2873
  function defaultDateRange() {
2874
2874
  const end = /* @__PURE__ */ new Date();
2875
- const start = /* @__PURE__ */ new Date();
2876
- start.setDate(start.getDate() - 7);
2875
+ end.setDate(end.getDate() - 1);
2876
+ const start = new Date(end);
2877
+ start.setDate(start.getDate() - 6);
2877
2878
  return { startDate: fmt(start), endDate: fmt(end) };
2878
2879
  }
2879
2880
  async function fetchBalanceMap(media, accountIds, config, startDate, endDate, verbose) {
@@ -3676,18 +3677,25 @@ var VALID_MEDIA_TYPES2 = ["Google", "TikTok", "Yandex", "MetaAd", "BingV2", "Kwa
3676
3677
  async function runBalance(opts) {
3677
3678
  const config = loadConfig(opts.token);
3678
3679
  if (!VALID_MEDIA_TYPES2.includes(opts.media)) {
3679
- console.error(
3680
- `
3681
- \u274C \u4E0D\u652F\u6301\u7684\u5A92\u4F53\u7C7B\u578B\uFF1A${opts.media}
3682
- \u53EF\u9009\u503C\uFF1A${VALID_MEDIA_TYPES2.join(" | ")}
3683
- `
3684
- );
3680
+ const msg = `\u4E0D\u652F\u6301\u7684\u5A92\u4F53\u7C7B\u578B\uFF1A${opts.media}\uFF08\u53EF\u9009\uFF1A${VALID_MEDIA_TYPES2.join(" | ")}\uFF09`;
3681
+ if (opts.json) {
3682
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
3683
+ process.exit(1);
3684
+ }
3685
+ console.error(`
3686
+ \u274C ${msg}
3687
+ `);
3685
3688
  process.exit(1);
3686
3689
  }
3687
3690
  const media = opts.media;
3688
3691
  if (!BALANCE_SUPPORTED_MEDIA.includes(media)) {
3692
+ const msg = `${media} \u6682\u4E0D\u652F\u6301\u4F59\u989D\u67E5\u8BE2`;
3693
+ if (opts.json) {
3694
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
3695
+ process.exit(1);
3696
+ }
3689
3697
  console.error(`
3690
- \u26A0\uFE0F ${media} \u6682\u4E0D\u652F\u6301\u4F59\u989D\u67E5\u8BE2
3698
+ \u26A0\uFE0F ${msg}
3691
3699
  `);
3692
3700
  process.exit(1);
3693
3701
  }
@@ -3707,8 +3715,13 @@ async function runBalance(opts) {
3707
3715
  opts.verbose
3708
3716
  );
3709
3717
  } catch (err) {
3718
+ const message = err instanceof Error ? err.message : String(err);
3719
+ if (opts.json) {
3720
+ console.log(JSON.stringify({ ok: false, error: message }, null, 2));
3721
+ process.exit(1);
3722
+ }
3710
3723
  console.error(`
3711
- \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
3724
+ \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${message}
3712
3725
  `);
3713
3726
  process.exit(1);
3714
3727
  }
@@ -3948,18 +3961,54 @@ async function runBalanceScan(opts) {
3948
3961
  const CHUNK = 100;
3949
3962
  const chunks = [];
3950
3963
  for (let i = 0; i < validIds.length; i += CHUNK) chunks.push(validIds.slice(i, i + CHUNK));
3964
+ process.stderr.write(
3965
+ `\u23F3 [balance-scan] \u6709\u6548\u8D26\u6237 ${validIds.length} \u4E2A\uFF0C\u5206 ${chunks.length} \u6279\uFF1B\u4F59\u989D\u4E0E\u8FD1 7 \u65E5\u6D88\u8017\u5E76\u884C\u8BF7\u6C42\uFF08\u5355\u8BF7\u6C42\u6700\u957F\u7EA6 10 \u5206\u949F\uFF09\u3002
3966
+ `
3967
+ );
3968
+ const logBalanceChunk = (idx, ids, m) => {
3969
+ process.stderr.write(
3970
+ ` \u2713 [\u4F59\u989D] \u7B2C ${idx + 1}/${chunks.length} \u6279\u5B8C\u6210\uFF08${ids.length} \u6237 \u2192 ${m.size} \u6761\uFF09
3971
+ `
3972
+ );
3973
+ };
3974
+ const logOverviewChunk = (idx, ids, m) => {
3975
+ process.stderr.write(
3976
+ ` \u2713 [\u8FD17\u65E5\u6D88\u8017] \u7B2C ${idx + 1}/${chunks.length} \u6279\u5B8C\u6210\uFF08${ids.length} \u6237 \u2192 ${m.size} \u6761\uFF09
3977
+ `
3978
+ );
3979
+ };
3951
3980
  const [bMaps, oMaps] = await Promise.all([
3952
3981
  Promise.all(
3953
- chunks.map(
3954
- (ids) => fetchBalanceMap(media, ids, config, void 0, void 0, opts.verbose)
3955
- )
3982
+ chunks.map((ids, chunkIdx) => {
3983
+ process.stderr.write(
3984
+ ` \u2192 [\u4F59\u989D] \u7B2C ${chunkIdx + 1}/${chunks.length} \u6279\u8BF7\u6C42\u4E2D\uFF08${ids.length} \u6237\uFF09\u2026
3985
+ `
3986
+ );
3987
+ return fetchBalanceMap(media, ids, config, void 0, void 0, opts.verbose).then(
3988
+ (m) => {
3989
+ logBalanceChunk(chunkIdx, ids, m);
3990
+ return m;
3991
+ }
3992
+ );
3993
+ })
3956
3994
  ),
3957
3995
  Promise.all(
3958
- chunks.map(
3959
- (ids) => fetchOverviewMap(media, ids, config, void 0, void 0, opts.verbose)
3960
- )
3996
+ chunks.map((ids, chunkIdx) => {
3997
+ process.stderr.write(
3998
+ ` \u2192 [\u8FD17\u65E5\u6D88\u8017] \u7B2C ${chunkIdx + 1}/${chunks.length} \u6279\u8BF7\u6C42\u4E2D\uFF08${ids.length} \u6237\uFF09\u2026
3999
+ `
4000
+ );
4001
+ return fetchOverviewMap(media, ids, config, void 0, void 0, opts.verbose).then(
4002
+ (m) => {
4003
+ logOverviewChunk(chunkIdx, ids, m);
4004
+ return m;
4005
+ }
4006
+ );
4007
+ })
3961
4008
  )
3962
4009
  ]);
4010
+ process.stderr.write(`\u23F3 [balance-scan] \u4F59\u989D\u4E0E\u6D88\u8017\u5DF2\u9F50\uFF0C\u6B63\u5728\u6309\u9608\u503C\u7B5B\u9009\u2026
4011
+ `);
3963
4012
  for (const m of bMaps) for (const [k, v] of m) balanceMap.set(k, v);
3964
4013
  for (const m of oMaps) for (const [k, v] of m) overviewMap.set(k, v);
3965
4014
  }
@@ -4382,8 +4431,13 @@ async function runStats(opts) {
4382
4431
  try {
4383
4432
  raw = await apiFetch2(url, config, {}, opts.verbose);
4384
4433
  } catch (err) {
4434
+ const message = err instanceof Error ? err.message : String(err);
4435
+ if (opts.json) {
4436
+ console.log(JSON.stringify({ ok: false, error: message }, null, 2));
4437
+ process.exit(1);
4438
+ }
4385
4439
  console.error(`
4386
- \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
4440
+ \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${message}
4387
4441
  `);
4388
4442
  process.exit(1);
4389
4443
  }
@@ -5070,6 +5124,37 @@ async function runReportPushReceiveEmails(opts) {
5070
5124
  console.log();
5071
5125
  }
5072
5126
 
5127
+ // src/utils/strip-legacy-google-fields.ts
5128
+ var LEGACY_WHEN_V2_PRESENT = [
5129
+ ["status", "statusV2"],
5130
+ ["channelType", "channelTypeV2"],
5131
+ ["subChannelType", "subChannelTypeV2"],
5132
+ ["biddingStrategyType", "biddingStrategyTypeV2"],
5133
+ ["campaignStatus", "campaignStatusV2"],
5134
+ ["adGroupStatus", "adGroupStatusV2"],
5135
+ ["matchType", "matchTypeV2"],
5136
+ ["type", "typeV2"]
5137
+ ];
5138
+ function stripLegacyGoogleFieldsIfV2Present(value) {
5139
+ return walk(value);
5140
+ }
5141
+ function walk(obj) {
5142
+ if (Array.isArray(obj)) return obj.map(walk);
5143
+ if (obj !== null && typeof obj === "object") {
5144
+ const o = { ...obj };
5145
+ for (const [legacy, modern] of LEGACY_WHEN_V2_PRESENT) {
5146
+ if (modern in o && legacy in o) {
5147
+ delete o[legacy];
5148
+ }
5149
+ }
5150
+ for (const k of Object.keys(o)) {
5151
+ o[k] = walk(o[k]);
5152
+ }
5153
+ return o;
5154
+ }
5155
+ return obj;
5156
+ }
5157
+
5073
5158
  // src/commands/google-analysis.ts
5074
5159
  var SECTIONS = [
5075
5160
  {
@@ -5100,6 +5185,12 @@ var SECTIONS = [
5100
5185
  dateMode: "range",
5101
5186
  path: (id) => `/reporting/media-account/${id}/CampaignSectionData`
5102
5187
  },
5188
+ {
5189
+ name: "campaign-hour",
5190
+ description: "\u7CFB\u5217\u6309\u5C0F\u65F6 campaign-hour\uFF08Query\uFF1AstartDate\u3001endDate\uFF09",
5191
+ dateMode: "range",
5192
+ path: (id) => `/reporting/media-account/${id}/campaign-hour`
5193
+ },
5103
5194
  {
5104
5195
  name: "ads",
5105
5196
  description: "\u5E7F\u544A\u7EA7\u5217\u8868 admanagement/v2/list",
@@ -5235,6 +5326,55 @@ async function fetchJson(config, pathWithQuery, verbose) {
5235
5326
  const url = `${config.googleApiUrl}${pathWithQuery}`;
5236
5327
  return apiFetch2(url, config, {}, verbose);
5237
5328
  }
5329
+ function assertNever(x, ctx) {
5330
+ throw new Error(`${ctx}\uFF1A\u672A\u5904\u7406\u7684\u5206\u652F ${String(x)}`);
5331
+ }
5332
+ async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name) {
5333
+ switch (name) {
5334
+ case "overview":
5335
+ return fetchJson(config, fullPath, verbose);
5336
+ case "keywords":
5337
+ return fetchJson(config, fullPath, verbose);
5338
+ case "search-terms":
5339
+ return fetchJson(config, fullPath, verbose);
5340
+ case "campaigns":
5341
+ return fetchJson(config, fullPath, verbose);
5342
+ case "campaign-hour":
5343
+ return fetchJson(config, fullPath, verbose);
5344
+ case "ads":
5345
+ return fetchJson(config, fullPath, verbose);
5346
+ case "extensions":
5347
+ return fetchJson(config, fullPath, verbose);
5348
+ case "devices":
5349
+ return fetchJson(config, fullPath, verbose);
5350
+ case "geographic":
5351
+ return fetchJson(config, fullPath, verbose);
5352
+ case "audience":
5353
+ return fetchJson(config, fullPath, verbose);
5354
+ case "asset-images":
5355
+ return fetchJson(config, fullPath, verbose);
5356
+ case "videos":
5357
+ return fetchJson(config, fullPath, verbose);
5358
+ case "resource-counts":
5359
+ return fetchJson(config, fullPath, verbose);
5360
+ case "conversion-actions":
5361
+ return fetchJson(config, fullPath, verbose);
5362
+ case "daily-metrics":
5363
+ return fetchJson(config, fullPath, verbose);
5364
+ case "gold-account":
5365
+ return fetchJson(config, fullPath, verbose);
5366
+ case "ads-index":
5367
+ return fetchJson(config, fullPath, verbose);
5368
+ case "final-urls":
5369
+ return fetchJson(config, fullPath, verbose);
5370
+ case "dimension-summary":
5371
+ return fetchJson(config, fullPath, verbose);
5372
+ case "campaign-types":
5373
+ return fetchJson(config, fullPath, verbose);
5374
+ default:
5375
+ return assertNever(name, "google-analysis");
5376
+ }
5377
+ }
5238
5378
  function summarizeHuman(section, data) {
5239
5379
  if (data === null || data === void 0) return "\u65E0\u6570\u636E";
5240
5380
  if (Array.isArray(data)) return `\u6570\u7EC4\uFF0C\u5171 ${data.length} \u6761`;
@@ -5292,7 +5432,7 @@ async function runOneSection(def, opts) {
5292
5432
  ]);
5293
5433
  const merged = { images, videos };
5294
5434
  if (opts.json) {
5295
- console.log(JSON.stringify(merged, null, 2));
5435
+ console.log(JSON.stringify(stripLegacyGoogleFieldsIfV2Present(merged), null, 2));
5296
5436
  return;
5297
5437
  }
5298
5438
  const iLen = Array.isArray(images) ? images.length : 0;
@@ -5305,9 +5445,14 @@ async function runOneSection(def, opts) {
5305
5445
  );
5306
5446
  return;
5307
5447
  }
5308
- const data = await fetchJson(config, fullPath, !!opts.verbose);
5448
+ const data = await fetchGoogleAnalysisSectionJson(
5449
+ config,
5450
+ fullPath,
5451
+ !!opts.verbose,
5452
+ def.name
5453
+ );
5309
5454
  if (opts.json) {
5310
- console.log(JSON.stringify(data, null, 2));
5455
+ console.log(JSON.stringify(stripLegacyGoogleFieldsIfV2Present(data), null, 2));
5311
5456
  return;
5312
5457
  }
5313
5458
  console.log(
@@ -6791,6 +6936,12 @@ function toDisplayMoney(raw) {
6791
6936
  if (!Number.isFinite(n)) return null;
6792
6937
  return n / 100;
6793
6938
  }
6939
+ function toCentAmount(displayValue) {
6940
+ if (!Number.isFinite(displayValue)) {
6941
+ throw new Error(`toCentAmount: \u8F93\u5165\u5FC5\u987B\u662F\u6709\u9650\u6570\u5B57\uFF0C\u6536\u5230 ${displayValue}`);
6942
+ }
6943
+ return Math.round(displayValue * 100);
6944
+ }
6794
6945
  function adgroupListUrl(googleApiUrl, account, startDate, endDate) {
6795
6946
  const params = new URLSearchParams();
6796
6947
  params.set("startDate", toGoogleDate(startDate, -30));
@@ -6806,6 +6957,39 @@ function requireGoogleApi(config) {
6806
6957
  }
6807
6958
  return config.googleApiUrl;
6808
6959
  }
6960
+ function formatGoogleCampaignListStatus(row) {
6961
+ let result = "-";
6962
+ const raw = row.statusV2;
6963
+ if (raw == null || String(raw).trim() === "") return result;
6964
+ const statusV2 = String(raw).toUpperCase();
6965
+ const start = parseCampaignTimeMs(row.startTime);
6966
+ const end = parseCampaignTimeMs(row.endTime);
6967
+ if (start == null || end == null) return result;
6968
+ const now = Date.now();
6969
+ if (statusV2 === "PAUSED") {
6970
+ if (now > start && now < end) {
6971
+ result = "\u5DF2\u6682\u505C";
6972
+ } else if (now > end) {
6973
+ result = "\u5DF2\u7ED3\u675F\u4F7F\u7528";
6974
+ } else if (now < start) {
6975
+ result = "\u672A\u6295\u653E";
6976
+ }
6977
+ } else if (statusV2 === "ENABLED") {
6978
+ if (now > start && now < end) {
6979
+ result = "\u6709\u6548";
6980
+ } else if (now > end) {
6981
+ result = "\u5DF2\u7ED3\u675F\u4F7F\u7528";
6982
+ } else if (now < start) {
6983
+ result = "\u672A\u6295\u653E";
6984
+ }
6985
+ }
6986
+ return result;
6987
+ }
6988
+ function parseCampaignTimeMs(v) {
6989
+ if (v == null || v === "") return null;
6990
+ const t = new Date(v).getTime();
6991
+ return Number.isFinite(t) ? t : null;
6992
+ }
6809
6993
  async function runAdCampaigns(opts) {
6810
6994
  const config = loadConfig(opts.token);
6811
6995
  const googleApiUrl = requireGoogleApi(config);
@@ -6828,18 +7012,19 @@ async function runAdCampaigns(opts) {
6828
7012
  return {
6829
7013
  ...item,
6830
7014
  budgetDisplay,
6831
- budgetUnit: "display"
7015
+ budgetUnit: "display",
7016
+ statusDisplay: formatGoogleCampaignListStatus(item)
6832
7017
  };
6833
7018
  });
6834
7019
  const n = items.length;
6835
7020
  if (opts.json) {
6836
7021
  console.log(
6837
7022
  JSON.stringify(
6838
- {
7023
+ stripLegacyGoogleFieldsIfV2Present({
6839
7024
  ...wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items }),
6840
7025
  code: data.code ?? null,
6841
7026
  message: data.message ?? null
6842
- },
7027
+ }),
6843
7028
  null,
6844
7029
  2
6845
7030
  )
@@ -6874,7 +7059,7 @@ async function runAdCampaigns(opts) {
6874
7059
  const budget = item.budgetDisplay != null ? item.budgetDisplay.toFixed(2) : "\u2014";
6875
7060
  return {
6876
7061
  name: (item.name ?? "").slice(0, nameW),
6877
- status: item.statusV2 ?? "",
7062
+ status: item.statusDisplay ?? formatGoogleCampaignListStatus(item),
6878
7063
  channelType: item.channelTypeV2 ?? "",
6879
7064
  bidding: String(item.biddingStrategyTypeV2 ?? ""),
6880
7065
  budget,
@@ -6917,10 +7102,10 @@ async function runAdGroups(opts) {
6917
7102
  if (opts.json) {
6918
7103
  console.log(
6919
7104
  JSON.stringify(
6920
- {
7105
+ stripLegacyGoogleFieldsIfV2Present({
6921
7106
  ...wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items }),
6922
7107
  code: data.code ?? null
6923
- },
7108
+ }),
6924
7109
  null,
6925
7110
  2
6926
7111
  )
@@ -6976,6 +7161,9 @@ async function runAdList(opts) {
6976
7161
  const params = new URLSearchParams();
6977
7162
  params.set("startDate", toGoogleDate(opts.startDate, -30));
6978
7163
  params.set("endDate", toGoogleDate(opts.endDate, 0));
7164
+ if (opts.includeDeleted) {
7165
+ params.set("readDeleted", "true");
7166
+ }
6979
7167
  const url = `${googleApiUrl}/admanagement/v2/list/${opts.account}?${params}`;
6980
7168
  let data;
6981
7169
  try {
@@ -6990,7 +7178,13 @@ async function runAdList(opts) {
6990
7178
  const n = items.length;
6991
7179
  if (opts.json) {
6992
7180
  console.log(
6993
- JSON.stringify(wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items }), null, 2)
7181
+ JSON.stringify(
7182
+ stripLegacyGoogleFieldsIfV2Present(
7183
+ wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
7184
+ ),
7185
+ null,
7186
+ 2
7187
+ )
6994
7188
  );
6995
7189
  return;
6996
7190
  }
@@ -7027,7 +7221,7 @@ async function runAdList(opts) {
7027
7221
  campaign: String(item["campaign"] ?? "").slice(0, campW),
7028
7222
  adGroup: String(item["adGroup"] ?? "").slice(0, grpW),
7029
7223
  status: String(item["statusV2"]),
7030
- type: String(item["typeV2"] ?? item["type"] ?? ""),
7224
+ type: String(item["typeV2"] ?? ""),
7031
7225
  impressions: String(item["impressions"] ?? 0),
7032
7226
  clicks: String(item["clicks"] ?? 0),
7033
7227
  ctr,
@@ -7066,7 +7260,13 @@ async function runAdKeywords(opts) {
7066
7260
  const n = items.length;
7067
7261
  if (opts.json) {
7068
7262
  console.log(
7069
- JSON.stringify(wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items }), null, 2)
7263
+ JSON.stringify(
7264
+ stripLegacyGoogleFieldsIfV2Present(
7265
+ wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
7266
+ ),
7267
+ null,
7268
+ 2
7269
+ )
7070
7270
  );
7071
7271
  return;
7072
7272
  }
@@ -7082,7 +7282,7 @@ ${label}\uFF08\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672C
7082
7282
  if (opts.negative) {
7083
7283
  items.forEach((item) => {
7084
7284
  const kwText = Array.isArray(item["keywordText"]) ? item["keywordText"].join(", ") : String(item["text"] ?? item["keywordText"] ?? item["id"] ?? "\u2014");
7085
- const matchType = item["matchTypeV2"] ?? item["matchType"] ?? "\u2014";
7285
+ const matchType = item["matchTypeV2"] ?? "\u2014";
7086
7286
  console.log(` [${matchType}] ${kwText} id: ${String(item["id"] ?? "")}`);
7087
7287
  });
7088
7288
  } else {
@@ -7113,8 +7313,8 @@ ${label}\uFF08\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672C
7113
7313
  const kwText = String(item["text"] ?? item["keywordText"] ?? "\u2014");
7114
7314
  const campaign = String(item["campaignName"] ?? item["campaign"] ?? "");
7115
7315
  const adGroup = String(item["adGroupName"] ?? item["adGroup"] ?? "");
7116
- const status = String(item["status"] ?? item["userStatus"] ?? "");
7117
- const matchType = String(item["matchTypeV2"] ?? item["matchType"] ?? "");
7316
+ const status = String(item["userStatus"] ?? "");
7317
+ const matchType = String(item["matchTypeV2"] ?? "");
7118
7318
  const ctr = item["ctr"] != null ? (Number(item["ctr"]) * 100).toFixed(2) + "%" : "\u2014";
7119
7319
  const spend = item["spend"] != null ? Number(item["spend"]).toFixed(2) : "\u2014";
7120
7320
  return {
@@ -7211,7 +7411,8 @@ async function runAdGroupCreate(opts) {
7211
7411
  campaignId: opts.campaignId,
7212
7412
  campaignName: opts.campaignName,
7213
7413
  manualCpc_EnhancedCpcEnabled: false,
7214
- maxCPCAmount: opts.maxCpc,
7414
+ // 用户传主币种金额,统一 toCentAmount 转「分」(与前端、adgroup-edit 一致)
7415
+ maxCPCAmount: toCentAmount(opts.maxCpc),
7215
7416
  name: opts.name,
7216
7417
  rotationMode: 1,
7217
7418
  statusV2: opts.status ?? "ENABLED",
@@ -7914,6 +8115,53 @@ async function runAdGroupRename(opts) {
7914
8115
  \u2705 \u5E7F\u544A\u7EC4 ${opts.id} \u5DF2\u6539\u540D\u4E3A\u300C${opts.name}\u300D
7915
8116
  `);
7916
8117
  }
8118
+ async function runAdGroupEdit(opts) {
8119
+ const hasName = opts.name !== void 0 && opts.name.trim() !== "";
8120
+ const hasMax = opts.maxCPCAmount !== void 0;
8121
+ const hasTcpa = opts.targetCpaAmount !== void 0;
8122
+ if (!hasName && !hasMax && !hasTcpa) {
8123
+ console.error(
8124
+ "\n\u274C \u8BF7\u81F3\u5C11\u6307\u5B9A\u4E00\u9879\u4FEE\u6539\uFF1A--name / --max-cpc / --target-cpa\n"
8125
+ );
8126
+ process.exit(1);
8127
+ }
8128
+ if (hasMax && (!Number.isFinite(opts.maxCPCAmount) || opts.maxCPCAmount < 0)) {
8129
+ console.error("\n\u274C --max-cpc \u987B\u4E3A\u975E\u8D1F\u6570\u5B57\n");
8130
+ process.exit(1);
8131
+ }
8132
+ if (hasTcpa && (!Number.isFinite(opts.targetCpaAmount) || opts.targetCpaAmount < 0)) {
8133
+ console.error("\n\u274C --target-cpa \u987B\u4E3A\u975E\u8D1F\u6570\u5B57\n");
8134
+ process.exit(1);
8135
+ }
8136
+ const config = loadConfig(opts.token);
8137
+ const googleApiUrl = requireGoogleApi(config);
8138
+ const listUrl = adgroupListUrl(googleApiUrl, opts.account, opts.startDate, opts.endDate);
8139
+ let adgroup;
8140
+ try {
8141
+ adgroup = await findItemInList(listUrl, config, opts.id, opts.verbose);
8142
+ } catch (err) {
8143
+ console.error(`
8144
+ \u274C \u67E5\u8BE2\u5E7F\u544A\u7EC4\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
8145
+ `);
8146
+ process.exit(1);
8147
+ }
8148
+ const body = { ...adgroup };
8149
+ if (hasName) body["name"] = opts.name.trim();
8150
+ if (hasMax) body["maxCPCAmount"] = toCentAmount(opts.maxCPCAmount);
8151
+ if (hasTcpa) body["targetCpaAmount"] = toCentAmount(opts.targetCpaAmount);
8152
+ const putUrl = `${googleApiUrl}/adgroupnmanagement/adgroup/${opts.account}/${opts.id}`;
8153
+ try {
8154
+ await apiFetch2(putUrl, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
8155
+ } catch (err) {
8156
+ console.error(`
8157
+ \u274C \u7F16\u8F91\u5E7F\u544A\u7EC4\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
8158
+ `);
8159
+ process.exit(1);
8160
+ }
8161
+ console.log(`
8162
+ \u2705 \u5E7F\u544A\u7EC4 ${opts.id} \u5DF2\u66F4\u65B0
8163
+ `);
8164
+ }
7917
8165
  async function runAdEdit(opts) {
7918
8166
  const config = await ensureDataPermission(loadConfig(opts.token));
7919
8167
  const googleApiUrl = requireGoogleApi(config);
@@ -8101,11 +8349,12 @@ async function runAdCampaignEdit(opts) {
8101
8349
  }
8102
8350
  const body = { ...campaign };
8103
8351
  if (opts.name !== void 0) body["name"] = opts.name;
8104
- if (opts.budget !== void 0) body["budget"] = opts.budget;
8352
+ if (opts.budget !== void 0) body["budget"] = toCentAmount(opts.budget);
8105
8353
  if (opts.biddingStrategy !== void 0) body["biddingStrategyTypeV2"] = opts.biddingStrategy;
8106
8354
  if (opts.targetSpendBidCeiling !== void 0)
8107
- body["targetSpend_BidCeilingAmount"] = opts.targetSpendBidCeiling;
8108
- if (opts.targetCpa !== void 0) body["targetCpa_BidingAmount"] = opts.targetCpa;
8355
+ body["targetSpend_BidCeilingAmount"] = toCentAmount(opts.targetSpendBidCeiling);
8356
+ if (opts.targetCpa !== void 0)
8357
+ body["targetCpa_BidingAmount"] = toCentAmount(opts.targetCpa);
8109
8358
  if (opts.targetSearchNetwork !== void 0)
8110
8359
  body["targetSearchNetwork"] = opts.targetSearchNetwork;
8111
8360
  if (opts.targetContentNetwork !== void 0)
@@ -8145,7 +8394,13 @@ async function runAdExtensionList(opts) {
8145
8394
  const n = items.length;
8146
8395
  if (opts.json) {
8147
8396
  console.log(
8148
- JSON.stringify(wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items }), null, 2)
8397
+ JSON.stringify(
8398
+ stripLegacyGoogleFieldsIfV2Present(
8399
+ wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
8400
+ ),
8401
+ null,
8402
+ 2
8403
+ )
8149
8404
  );
8150
8405
  return;
8151
8406
  }
@@ -8278,7 +8533,13 @@ async function runAdSearchTerms(opts) {
8278
8533
  const n = items.length;
8279
8534
  if (opts.json) {
8280
8535
  console.log(
8281
- JSON.stringify(wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items }), null, 2)
8536
+ JSON.stringify(
8537
+ stripLegacyGoogleFieldsIfV2Present(
8538
+ wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
8539
+ ),
8540
+ null,
8541
+ 2
8542
+ )
8282
8543
  );
8283
8544
  return;
8284
8545
  }
@@ -8321,7 +8582,7 @@ async function runAdSearchTerms(opts) {
8321
8582
  term: term.slice(0, termW),
8322
8583
  campaign: String(item["campaignName"] ?? "").slice(0, campW),
8323
8584
  adGroup: String(item["adGroupName"] ?? "").slice(0, grpW),
8324
- matchType: String(item["matchType"] ?? ""),
8585
+ matchType: String(item["matchTypeV2"] ?? ""),
8325
8586
  impressions: String(item["impressions"] ?? 0),
8326
8587
  clicks: String(item["clicks"] ?? 0),
8327
8588
  ctr,
@@ -8355,7 +8616,7 @@ async function runAdGeoSearch(opts) {
8355
8616
  for (const item of items) {
8356
8617
  const id = String(item["id"] ?? "");
8357
8618
  const name = String(item["locationName"] ?? item["canonicalName"] ?? item["name"] ?? "");
8358
- const type = String(item["targetType"] ?? item["typeV2"] ?? item["type"] ?? "");
8619
+ const type = String(item["targetType"] ?? item["typeV2"] ?? "");
8359
8620
  console.log(` id:${id} ${name} [${type}]`);
8360
8621
  }
8361
8622
  console.log();
@@ -8391,7 +8652,13 @@ async function runAdGeoList(opts) {
8391
8652
  const n = items.length;
8392
8653
  if (opts.json) {
8393
8654
  console.log(
8394
- JSON.stringify(wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items }), null, 2)
8655
+ JSON.stringify(
8656
+ stripLegacyGoogleFieldsIfV2Present(
8657
+ wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
8658
+ ),
8659
+ null,
8660
+ 2
8661
+ )
8395
8662
  );
8396
8663
  return;
8397
8664
  }
@@ -9415,8 +9682,13 @@ async function fetchTikTokClues(opts, config) {
9415
9682
  );
9416
9683
  leads = Array.isArray(res) ? res : res.data ?? [];
9417
9684
  } catch (err) {
9685
+ const message = err instanceof Error ? err.message : String(err);
9686
+ if (opts.json) {
9687
+ console.log(JSON.stringify({ ok: false, error: message, items: [] }, null, 2));
9688
+ process.exit(1);
9689
+ }
9418
9690
  console.error(`
9419
- \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
9691
+ \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${message}
9420
9692
  `);
9421
9693
  process.exit(1);
9422
9694
  }
@@ -9463,8 +9735,13 @@ async function fetchMetaClues(opts, config) {
9463
9735
  try {
9464
9736
  raw = await apiFetch2(url, config, {}, opts.verbose);
9465
9737
  } catch (err) {
9738
+ const message = err instanceof Error ? err.message : String(err);
9739
+ if (opts.json) {
9740
+ console.log(JSON.stringify({ ok: false, error: message, items: [] }, null, 2));
9741
+ process.exit(1);
9742
+ }
9466
9743
  console.error(`
9467
- \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
9744
+ \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${message}
9468
9745
  `);
9469
9746
  process.exit(1);
9470
9747
  }
@@ -12121,14 +12398,14 @@ program.command("accounts-digest").description(
12121
12398
  program.command("stats").description("\u67E5\u8BE2\u5E7F\u544A\u6D88\u8017\u3001\u70B9\u51FB\u3001\u8F6C\u5316\u7B49\u6295\u653E\u6570\u636E\uFF08\u9ED8\u8BA4\u8FD1 7 \u5929\uFF0C\u4E0D\u542B\u4ECA\u5929\uFF09").requiredOption(
12122
12399
  "-m, --media <type>",
12123
12400
  "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai"
12124
- ).option("-a, --accounts <ids>", "\u8D26\u6237 ID\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF08\u7559\u7A7A\u5219\u67E5\u5168\u90E8\u8D26\u6237\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4 7 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u6628\u5929\uFF09").option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u539F\u59CB\u54CD\u5E94", false).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
12401
+ ).option("-a, --accounts <ids>", "\u8D26\u6237 ID\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF08\u7559\u7A7A\u5219\u67E5\u5168\u90E8\u8D26\u6237\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4 7 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4\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("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u539F\u59CB\u54CD\u5E94", false).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
12125
12402
  async (opts) => {
12126
12403
  await runStats({
12127
12404
  token: opts.token,
12128
12405
  media: opts.media,
12129
12406
  accounts: opts.accounts,
12130
- startDate: opts.start,
12131
- endDate: opts.end,
12407
+ startDate: opts.start ?? opts.startDate,
12408
+ endDate: opts.end ?? opts.endDate,
12132
12409
  json: opts.json,
12133
12410
  verbose: opts.verbose
12134
12411
  });
@@ -12757,13 +13034,14 @@ adCmd.command("groups").description("\u67E5\u8BE2\u5E7F\u544A\u7EC4\u5217\u8868\
12757
13034
  });
12758
13035
  }
12759
13036
  );
12760
- adCmd.command("list").description("\u67E5\u8BE2\u5E7F\u544A\uFF08\u521B\u610F\uFF09\u5217\u8868\uFF08\u65E5\u671F\u9ED8\u8BA4\u8FD1 30 \u5929\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("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
13037
+ adCmd.command("list").description("\u67E5\u8BE2\u5E7F\u544A\uFF08\u521B\u610F\uFF09\u5217\u8868\uFF08\u65E5\u671F\u9ED8\u8BA4\u8FD1 30 \u5929\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("--include-deleted", "\u5305\u542B\u5DF2\u5220\u9664\u7684\u5E7F\u544A\uFF08\u7F51\u5173 readDeleted=true\uFF09", false).option("-t, --token <token>", "Auth Token").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
12761
13038
  async (opts) => {
12762
13039
  await runAdList({
12763
13040
  token: opts.token,
12764
13041
  account: opts.account,
12765
13042
  startDate: opts.start,
12766
13043
  endDate: opts.end,
13044
+ includeDeleted: opts.includeDeleted,
12767
13045
  json: opts.json,
12768
13046
  verbose: opts.verbose
12769
13047
  });
@@ -12807,8 +13085,8 @@ adCmd.command("campaign-delete").description("\u5220\u9664\u5E7F\u544A\u7CFB\u52
12807
13085
  });
12808
13086
  adCmd.command("adgroup-create").description("\u5728\u5E7F\u544A\u7CFB\u5217\u4E0B\u521B\u5EFA\u641C\u7D22\u5E7F\u544A\u7EC4").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--campaign-id <id>", "\u6240\u5C5E\u5E7F\u544A\u7CFB\u5217 ID").requiredOption("--campaign-name <name>", "\u6240\u5C5E\u5E7F\u544A\u7CFB\u5217\u540D\u79F0").requiredOption("--name <name>", "\u5E7F\u544A\u7EC4\u540D\u79F0").requiredOption(
12809
13087
  "--max-cpc <amount>",
12810
- "\u6700\u9AD8\u5355\u6B21\u70B9\u51FB\u8D39\u7528\uFF08\u6700\u5C0F\u8D27\u5E01\u5355\u4F4D\uFF0C\u5982 100000 = 1 USD\uFF09",
12811
- parseInt
13088
+ "\u6700\u9AD8\u5355\u6B21\u70B9\u51FB\u8D39\u7528\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982 1.5 \u8868\u793A \xA51.50\uFF1BCLI \u5185\u90E8 \xD7100 \u5199\u5165\u300C\u5206\u300D\uFF09",
13089
+ parseFloat
12812
13090
  ).option("--status <status>", "\u72B6\u6001\uFF1AENABLED | PAUSED\uFF08\u9ED8\u8BA4 ENABLED\uFF09", "ENABLED").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
12813
13091
  async (opts) => {
12814
13092
  await runAdGroupCreate({
@@ -13118,7 +13396,19 @@ adCmd.command("campaign-create").description(
13118
13396
  });
13119
13397
  }
13120
13398
  );
13121
- adCmd.command("campaign-edit").description("\u7F16\u8F91\u5E7F\u544A\u7CFB\u5217\uFF08\u540D\u79F0/\u9884\u7B97/\u51FA\u4EF7\u7B56\u7565/\u6295\u653E\u7F51\u7EDC\uFF0C\u81F3\u5C11\u4F20\u4E00\u4E2A\u4FEE\u6539\u9879\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").option("--name <name>", "\u65B0\u540D\u79F0").option("--budget <amount>", "\u65B0\u9884\u7B97\uFF08\u6700\u5C0F\u8D27\u5E01\u5355\u4F4D\uFF0C\u5982 100000 = 1 USD\uFF09", parseInt).option("--bidding <strategy>", "\u51FA\u4EF7\u7B56\u7565\uFF1ATARGET_SPEND | TARGET_CPA | TARGET_ROAS | MANUAL_CPC").option("--bid-ceiling <amount>", "TARGET_SPEND \u51FA\u4EF7\u4E0A\u9650\uFF08\u6700\u5C0F\u8D27\u5E01\u5355\u4F4D\uFF0C0=\u4E0D\u9650\uFF09", parseInt).option("--target-cpa <amount>", "TARGET_CPA \u76EE\u6807 CPA\uFF08\u6700\u5C0F\u8D27\u5E01\u5355\u4F4D\uFF09", parseInt).option(
13399
+ adCmd.command("campaign-edit").description("\u7F16\u8F91\u5E7F\u544A\u7CFB\u5217\uFF08\u540D\u79F0/\u9884\u7B97/\u51FA\u4EF7\u7B56\u7565/\u6295\u653E\u7F51\u7EDC\uFF0C\u81F3\u5C11\u4F20\u4E00\u4E2A\u4FEE\u6539\u9879\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").option("--name <name>", "\u65B0\u540D\u79F0").option(
13400
+ "--budget <amount>",
13401
+ "\u65B0\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982 100 \u8868\u793A \xA5100\uFF1B\u652F\u6301\u5C0F\u6570\uFF1BCLI \u5185\u90E8\u81EA\u52A8 \xD7100 \u5199\u5165\u300C\u5206\u300D\u5B57\u6BB5\uFF09",
13402
+ parseFloat
13403
+ ).option("--bidding <strategy>", "\u51FA\u4EF7\u7B56\u7565\uFF1ATARGET_SPEND | TARGET_CPA | TARGET_ROAS | MANUAL_CPC").option(
13404
+ "--bid-ceiling <amount>",
13405
+ "TARGET_SPEND \u51FA\u4EF7\u4E0A\u9650\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF080 = \u4E0D\u9650\uFF1BCLI \u5185\u90E8 \xD7100 \u5199\u5165\uFF09",
13406
+ parseFloat
13407
+ ).option(
13408
+ "--target-cpa <amount>",
13409
+ "TARGET_CPA \u76EE\u6807 CPA\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08CLI \u5185\u90E8 \xD7100 \u5199\u5165\uFF09",
13410
+ parseFloat
13411
+ ).option(
13122
13412
  "--search-network <bool>",
13123
13413
  "\u6295\u653E\u5230 Google \u641C\u7D22\uFF1Atrue | false",
13124
13414
  (v) => v === "true"
@@ -13152,6 +13442,29 @@ adCmd.command("adgroup-rename").description("\u4FEE\u6539\u5E7F\u544A\u7EC4\u540
13152
13442
  });
13153
13443
  }
13154
13444
  );
13445
+ adCmd.command("adgroup-edit").description("\u7F16\u8F91\u5E7F\u544A\u7EC4\uFF08\u540D\u79F0\u3001\u6700\u9AD8 CPC\u3001\u7EC4\u7EA7\u76EE\u6807 CPA\uFF1B\u81F3\u5C11\u4F20\u4E00\u9879\uFF1B\u4E0E\u7F51\u5173 PUT \u4E00\u81F4\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <adgroupId>", "\u5E7F\u544A\u7EC4 ID").option("--name <name>", "\u65B0\u540D\u79F0").option(
13446
+ "--max-cpc <n>",
13447
+ "\u6700\u9AD8 CPC\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982 1.5 \u8868\u793A \xA51.50\uFF1BCLI \u5185\u90E8 \xD7100 \u5199\u5165\u300C\u5206\u300D\u5B57\u6BB5\uFF09",
13448
+ parseFloat
13449
+ ).option(
13450
+ "--target-cpa <n>",
13451
+ "\u7EC4\u7EA7\u76EE\u6807 CPA\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08CLI \u5185\u90E8 \xD7100 \u5199\u5165\u300C\u5206\u300D\u5B57\u6BB5\uFF09",
13452
+ parseFloat
13453
+ ).option("--start <date>", "\u5217\u8868\u67E5\u8BE2\u8D77\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u5217\u8868\u67E5\u8BE2\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
13454
+ async (opts) => {
13455
+ await runAdGroupEdit({
13456
+ token: opts.token,
13457
+ account: opts.account,
13458
+ id: opts.id,
13459
+ name: opts.name,
13460
+ maxCPCAmount: opts.maxCpc,
13461
+ targetCpaAmount: opts.targetCpa,
13462
+ startDate: opts.start,
13463
+ endDate: opts.end,
13464
+ verbose: opts.verbose
13465
+ });
13466
+ }
13467
+ );
13155
13468
  adCmd.command("ad-edit").description(
13156
13469
  "\u7F16\u8F91\u81EA\u9002\u5E94\u641C\u7D22\u5E7F\u544A\uFF08RESPONSIVE_SEARCH_AD\uFF09\uFF1A\u6807\u9898/\u63CF\u8FF0/\u843D\u5730\u9875/\u8DEF\u5F84/\u72B6\u6001\uFF1B\u4E0E\u7F51\u9875 PUT admanagement/campaign \u884C\u4E3A\u4E00\u81F4"
13157
13470
  ).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <adId>", "\u5E7F\u544A ID").option(
@@ -13195,7 +13508,10 @@ adCmd.command("keyword-delete").description("\u5220\u9664\u641C\u7D22\u5173\u952
13195
13508
  });
13196
13509
  }
13197
13510
  );
13198
- adCmd.command("keyword-edit").description("\u7F16\u8F91\u641C\u7D22\u5173\u952E\u8BCD\uFF08\u6570\u7EC4 body\uFF0C\u5148 list \u518D\u5408\u5E76\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <keywordId>", "\u5173\u952E\u8BCD ID\uFF08\u6765\u81EA ad keywords --json \u2192 id\uFF09").option("--text <text>", "\u65B0\u5173\u952E\u8BCD\u6587\u672C\uFF08\u5199\u5165 keywordText \u6570\u7EC4\uFF09").option("--match-type <type>", "\u65B0\u5339\u914D\u7C7B\u578B\uFF1ABroad | Phrase | Exact\uFF08\u5199\u5165 matchTypeV2\uFF09").option("--max-cpc <n>", "\u6700\u9AD8\u6BCF\u6B21\u70B9\u51FB\u8D39\u7528 maxCPC\uFF08\u6570\u5B57\uFF0C0 \u8868\u793A\u6309\u5E73\u53F0/\u8BA1\u5212\u9ED8\u8BA4\uFF09").option("--final-url <url>", "\u5173\u952E\u8BCD\u7EA7\u6700\u7EC8\u5230\u8FBE\u7F51\u5740 finalURL").option("--start <date>", "\u5217\u8868\u67E5\u8BE2\u8D77\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u5217\u8868\u67E5\u8BE2\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
13511
+ adCmd.command("keyword-edit").description("\u7F16\u8F91\u641C\u7D22\u5173\u952E\u8BCD\uFF08\u6570\u7EC4 body\uFF0C\u5148 list \u518D\u5408\u5E76\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <keywordId>", "\u5173\u952E\u8BCD ID\uFF08\u6765\u81EA ad keywords --json \u2192 id\uFF09").option("--text <text>", "\u65B0\u5173\u952E\u8BCD\u6587\u672C\uFF08\u5199\u5165 keywordText \u6570\u7EC4\uFF09").option("--match-type <type>", "\u65B0\u5339\u914D\u7C7B\u578B\uFF1ABroad | Phrase | Exact\uFF08\u5199\u5165 matchTypeV2\uFF09").option(
13512
+ "--max-cpc <n>",
13513
+ "\u6700\u9AD8\u6BCF\u6B21\u70B9\u51FB\u8D39\u7528 maxCPC\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982 5 \u8868\u793A \xA55\uFF1B\u26A0\uFE0F \u8FD9\u4E2A\u5B57\u6BB5\u540E\u7AEF\u5355\u4F4D\u5C31\u662F\u300C\u4E3B\u5E01\u79CD\u5143\u300D\uFF0CCLI \u76F4\u63A5\u900F\u4F20\u4E0D\u505A \xD7100\uFF0C\u4E0E budget / \u7EC4 maxCPCAmount \u4E0D\u540C\uFF1B0 \u8868\u793A\u6309\u5E73\u53F0/\u8BA1\u5212\u9ED8\u8BA4\uFF09"
13514
+ ).option("--final-url <url>", "\u5173\u952E\u8BCD\u7EA7\u6700\u7EC8\u5230\u8FBE\u7F51\u5740 finalURL").option("--start <date>", "\u5217\u8868\u67E5\u8BE2\u8D77\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u5217\u8868\u67E5\u8BE2\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
13199
13515
  async (opts) => {
13200
13516
  if (opts.matchType && !["Broad", "Phrase", "Exact"].includes(opts.matchType)) {
13201
13517
  console.error("\n\u274C --match-type \u53EA\u63A5\u53D7 Broad | Phrase | Exact\n");