siluzan-tso-cli 1.1.39 → 1.1.40-beta.1

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 (35) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +897 -194
  3. package/dist/skill/SKILL.md +2 -1
  4. package/dist/skill/_meta.json +2 -2
  5. package/dist/skill/references/accounts/accounts-list.md +1 -1
  6. package/dist/skill/references/accounts/accounts-permissions.md +2 -2
  7. package/dist/skill/references/accounts/finance.md +1 -1
  8. package/dist/skill/references/accounts/open-account-by-media.md +72 -68
  9. package/dist/skill/references/accounts/open-account-google-ui.md +30 -27
  10. package/dist/skill/references/analytics/rag.md +1 -1
  11. package/dist/skill/references/analytics/reporting.md +5 -5
  12. package/dist/skill/references/core/agent-conventions.md +5 -3
  13. package/dist/skill/references/core/setup.md +5 -5
  14. package/dist/skill/references/core/workflows.md +7 -6
  15. package/dist/skill/references/misc/tso-home.md +2 -2
  16. package/dist/skill/references/operations/guard.md +94 -0
  17. package/dist/skill/references/operations/hosted-automation-self-control.md +35 -15
  18. package/dist/skill/references/operations/hosted-automation-user-catalog.md +5 -5
  19. package/dist/skill/references/report-templates/bing-period-report.md +3 -3
  20. package/dist/skill/references/report-templates/google-period-report.md +1 -1
  21. package/dist/skill/references/report-templates/meta-account-diagnosis-report.md +1 -1
  22. package/dist/skill/references/report-templates/meta-period-report.md +2 -2
  23. package/dist/skill/references/report-templates/tiktok-period-report.md +1 -1
  24. package/dist/skill/report-templates/bing-period-report.md +3 -3
  25. package/dist/skill/report-templates/google-period-report.md +1 -1
  26. package/dist/skill/report-templates/meta-account-diagnosis-report.md +1 -1
  27. package/dist/skill/report-templates/meta-period-report.md +2 -2
  28. package/dist/skill/report-templates/tiktok-period-report.md +1 -1
  29. package/dist/skill/scripts/install.ps1 +3 -3
  30. package/dist/skill/scripts/install.sh +3 -3
  31. package/eval/cases/guard-budget-circuit-all-google.scenario.json +25 -0
  32. package/eval/cases/guard-zero-conv-empty-spend.scenario.json +21 -0
  33. package/eval/stub-fixtures/guard-budget-circuit.json +36 -0
  34. package/eval/stub-fixtures/guard-zero-conv.json +41 -0
  35. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3937,7 +3937,7 @@ var DEFAULT_API_BASE;
3937
3937
  var init_defaults = __esm({
3938
3938
  "src/config/defaults.ts"() {
3939
3939
  "use strict";
3940
- DEFAULT_API_BASE = "https://tso-api.siluzan.com";
3940
+ DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
3941
3941
  }
3942
3942
  });
3943
3943
 
@@ -102283,15 +102283,17 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
102283
102283
  costPerConversion: num2(currentPeriod.costPerConversion)
102284
102284
  } : null);
102285
102285
  if (currentKpiSource) {
102286
- if (kpis.cost == null) kpis.cost = currentKpiSource.cost;
102287
- if (kpis.impressions == null) kpis.impressions = currentKpiSource.impressions;
102288
- if (kpis.clicks == null) kpis.clicks = currentKpiSource.clicks;
102289
- if (kpis.conversions == null) kpis.conversions = currentKpiSource.conversions;
102290
- if (kpis.ctr == null) kpis.ctr = currentKpiSource.ctr;
102291
- if (kpis.averageCpc == null) kpis.averageCpc = currentKpiSource.averageCpc;
102292
- if (kpis.costPerConversion == null) kpis.costPerConversion = currentKpiSource.costPerConversion;
102293
- }
102294
- if (!kpis.previousPeriod && previousPeriod) {
102286
+ if (currentKpiSource.cost != null) kpis.cost = currentKpiSource.cost;
102287
+ if (currentKpiSource.impressions != null) kpis.impressions = currentKpiSource.impressions;
102288
+ if (currentKpiSource.clicks != null) kpis.clicks = currentKpiSource.clicks;
102289
+ if (currentKpiSource.conversions != null) kpis.conversions = currentKpiSource.conversions;
102290
+ if (currentKpiSource.ctr != null) kpis.ctr = currentKpiSource.ctr;
102291
+ if (currentKpiSource.averageCpc != null) kpis.averageCpc = currentKpiSource.averageCpc;
102292
+ if (currentKpiSource.costPerConversion != null) {
102293
+ kpis.costPerConversion = currentKpiSource.costPerConversion;
102294
+ }
102295
+ }
102296
+ if (previousPeriod) {
102295
102297
  kpis.previousPeriod = {
102296
102298
  cost: num2(previousPeriod.spend),
102297
102299
  impressions: num2(previousPeriod.impressions),
@@ -102303,7 +102305,7 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
102303
102305
  };
102304
102306
  }
102305
102307
  const charts = { ...payload.charts ?? {} };
102306
- if (!charts.dailyTrend) {
102308
+ {
102307
102309
  const dailyRows = items(sectionMap.get("daily-metrics"));
102308
102310
  if (dailyRows.length > 0) {
102309
102311
  const sorted = [...dailyRows].sort(
@@ -102318,7 +102320,7 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
102318
102320
  }
102319
102321
  }
102320
102322
  const tables = { ...payload.tables ?? {} };
102321
- if (!tables.dimensionSummary) {
102323
+ {
102322
102324
  const summary = record(sectionMap.get("dimension-summary"));
102323
102325
  if (summary) {
102324
102326
  tables.dimensionSummary = {
@@ -102335,19 +102337,19 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
102335
102337
  };
102336
102338
  }
102337
102339
  }
102338
- if (!tables.campaigns && campaignRows2.length > 0) {
102340
+ if (campaignRows2.length > 0) {
102339
102341
  tables.campaigns = campaignRows2.map((r) => ({
102340
102342
  ...pickMetricRow(r, ["campaignName", "campaignStatusDisplay", "channelTypeV2"]),
102341
102343
  budgetAmountYuan: r.budgetAmountYuan ?? null
102342
102344
  }));
102343
102345
  }
102344
- if (!tables.devices) {
102346
+ {
102345
102347
  const rows = items(sectionMap.get("devices"));
102346
102348
  if (rows.length > 0) {
102347
102349
  tables.devices = rows.map((r) => pickMetricRow(r, ["deviceType"]));
102348
102350
  }
102349
102351
  }
102350
- if (!tables.geographic) {
102352
+ {
102351
102353
  const rows = items(sectionMap.get("geographic"));
102352
102354
  if (rows.length > 0) {
102353
102355
  tables.geographic = rows.map(
@@ -102355,7 +102357,7 @@ async function mergeGoogleAnalysisSnapshotIntoReport(payload, snapshotDir) {
102355
102357
  );
102356
102358
  }
102357
102359
  }
102358
- if (!tables.keywords) {
102360
+ {
102359
102361
  const rows = items(sectionMap.get("keywords"));
102360
102362
  if (rows.length > 0) {
102361
102363
  tables.keywords = rows.map(
@@ -103814,7 +103816,7 @@ function resolveSectionList(sections, exclude) {
103814
103816
  const base = include.length > 0 ? SECTIONS.filter((s) => include.includes(s.name)) : SECTIONS;
103815
103817
  return base.filter((s) => !drop.has(s.name));
103816
103818
  }
103817
- async function runWithConcurrency(tasks, concurrency) {
103819
+ async function runWithConcurrency2(tasks, concurrency) {
103818
103820
  const results = new Array(tasks.length);
103819
103821
  let cursor = 0;
103820
103822
  const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
@@ -103897,7 +103899,7 @@ async function runAllSections(opts) {
103897
103899
  const cliVersion = getCurrentVersion2();
103898
103900
  const overallT0 = performance4.now();
103899
103901
  const tasks = targets.map((def) => () => fetchAndWriteSection(def, opts, config, id, cliVersion));
103900
- let results = await runWithConcurrency(tasks, concurrency);
103902
+ let results = await runWithConcurrency2(tasks, concurrency);
103901
103903
  const failedIndices = results.map((r, i) => r.ok ? -1 : i).filter((i) => i >= 0);
103902
103904
  if (failedIndices.length > 0) {
103903
103905
  console.error(`
@@ -107055,6 +107057,747 @@ function register9(program2) {
107055
107057
  // src/index.ts
107056
107058
  init_accounts_digest2();
107057
107059
 
107060
+ // src/commands/guard/budget-circuit.ts
107061
+ init_cli_json_snapshot();
107062
+
107063
+ // src/commands/guard/shared.ts
107064
+ init_auth();
107065
+ init_list_fetch();
107066
+
107067
+ // src/commands/ad/_shared.ts
107068
+ init_auth();
107069
+ async function findItemInList(listUrl, config, id, verbose) {
107070
+ const res = await apiFetch2(listUrl, config, {}, verbose);
107071
+ const item = (res.data ?? []).find((x) => x["id"] === id);
107072
+ if (!item) {
107073
+ throw new Error(`\u672A\u5728\u5217\u8868\u4E2D\u627E\u5230 id="${id}" \u7684\u6761\u76EE`);
107074
+ }
107075
+ return item;
107076
+ }
107077
+ function toGoogleDate(dateStr, offsetDays) {
107078
+ if (dateStr) return dateStr.replace(/-/g, "/");
107079
+ const d = /* @__PURE__ */ new Date();
107080
+ d.setDate(d.getDate() + offsetDays);
107081
+ return d.toISOString().slice(0, 10).replace(/-/g, "/");
107082
+ }
107083
+ function toDisplayMoney(raw) {
107084
+ if (raw == null) return null;
107085
+ const n = Number(raw);
107086
+ if (!Number.isFinite(n)) return null;
107087
+ return n / 100;
107088
+ }
107089
+ function toCentAmount(displayValue) {
107090
+ if (!Number.isFinite(displayValue)) {
107091
+ throw new Error(`toCentAmount: \u8F93\u5165\u5FC5\u987B\u662F\u6709\u9650\u6570\u5B57\uFF0C\u6536\u5230 ${displayValue}`);
107092
+ }
107093
+ return Math.round(displayValue * 100);
107094
+ }
107095
+ var CAMPAIGN_BATCH_MONEY_KEYS = /* @__PURE__ */ new Set([
107096
+ "Budget",
107097
+ "MaxCPCAmount",
107098
+ "MaxCpmAmount",
107099
+ "MaxCPVAmount",
107100
+ "TargetCpa_BidingAmount",
107101
+ "TargetSpend_BidCeilingAmount",
107102
+ "TargetCpaAmount",
107103
+ "MaxCPC"
107104
+ ]);
107105
+ function convertBatchMoneyTreeFromYuan(value) {
107106
+ return convertBatchMoneyTreeFromYuanInner(value);
107107
+ }
107108
+ function convertBatchMoneyTreeFromYuanInner(value) {
107109
+ if (Array.isArray(value)) {
107110
+ return value.map(convertBatchMoneyTreeFromYuanInner);
107111
+ }
107112
+ if (value && typeof value === "object") {
107113
+ const out = {};
107114
+ for (const [key, val] of Object.entries(value)) {
107115
+ if (typeof val === "number" && CAMPAIGN_BATCH_MONEY_KEYS.has(key)) {
107116
+ out[key] = toCentAmount(val);
107117
+ } else {
107118
+ out[key] = convertBatchMoneyTreeFromYuanInner(val);
107119
+ }
107120
+ }
107121
+ return out;
107122
+ }
107123
+ return value;
107124
+ }
107125
+ function adgroupListUrl(googleApiUrl, account, startDate, endDate) {
107126
+ const params = new URLSearchParams();
107127
+ params.set("startDate", toGoogleDate(startDate, -30));
107128
+ params.set("endDate", toGoogleDate(endDate, 0));
107129
+ return `${googleApiUrl}/adgroupnmanagement/v2/list/${account}?${params}`;
107130
+ }
107131
+ function requireGoogleApi(config) {
107132
+ if (!config.googleApiUrl) {
107133
+ console.error(
107134
+ "\n\u274C \u672A\u627E\u5230 Google API \u5730\u5740\u3002\n Google API \u5730\u5740\u4ECE TSO API \u5730\u5740\u81EA\u52A8\u63A8\u5BFC\uFF0C\u4E5F\u53EF\u901A\u8FC7\u73AF\u5883\u53D8\u91CF SILUZAN_GOOGLE_API \u8986\u76D6\u3002\n"
107135
+ );
107136
+ process.exit(1);
107137
+ }
107138
+ return config.googleApiUrl;
107139
+ }
107140
+
107141
+ // src/commands/guard/shared.ts
107142
+ init_cli_table();
107143
+ var GUARD_ACCOUNT_CONCURRENCY = 4;
107144
+ async function resolveGuardConfig(opts) {
107145
+ let config = loadConfig(opts.token);
107146
+ if (opts.refreshDp) {
107147
+ config = await refreshDataPermission(config);
107148
+ }
107149
+ return config;
107150
+ }
107151
+ function assertGoogleOnly(media) {
107152
+ if (media !== "Google") {
107153
+ console.error(
107154
+ `
107155
+ \u274C guard \u7194\u65AD\u5199\u76EE\u524D\u4EC5\u652F\u6301 Google\uFF08\u5F53\u524D\uFF1A${media}\uFF09\u3002
107156
+ \u975E Google \u5A92\u4F53\u672C CLI \u65E0\u7CFB\u5217/\u7EC4/\u521B\u610F\u6682\u505C\u5199\u80FD\u529B\uFF1B\u8BF7\u6539\u7528 -m Google\u3002
107157
+ `
107158
+ );
107159
+ process.exit(1);
107160
+ }
107161
+ }
107162
+ async function resolveGuardAccounts(opts, config) {
107163
+ const filterIds = (opts.accounts ?? "").split(",").map((s) => s.trim()).filter(Boolean);
107164
+ if (filterIds.length > 0) {
107165
+ process.stderr.write(
107166
+ `\u23F3 [guard] \u5B50\u96C6\u6A21\u5F0F\uFF1A\u5BF9 -a \u6307\u5B9A\u7684 ${filterIds.length} \u4E2A\u8D26\u6237\u626B\u63CF\u3002
107167
+ `
107168
+ );
107169
+ return filterIds.map((id) => ({ mediaCustomerId: id, name: null }));
107170
+ }
107171
+ const pageSize = Math.max(1, Math.min(500, opts.pageSize ?? 200));
107172
+ const maxPages = Math.max(1, Math.min(200, opts.maxPages ?? 20));
107173
+ const res = await fetchAccountsByMedia("Google", config, {
107174
+ pageSize,
107175
+ maxPages,
107176
+ verbose: opts.verbose
107177
+ });
107178
+ return res.items.map((item) => ({
107179
+ mediaCustomerId: String(item.ma?.mediaCustomerId ?? ""),
107180
+ name: item.ma?.mediaCustomerName ?? null
107181
+ })).filter((a) => a.mediaCustomerId);
107182
+ }
107183
+ async function runWithConcurrency(tasks, concurrency) {
107184
+ const results = new Array(tasks.length);
107185
+ let cursor = 0;
107186
+ const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length || 1))).fill(0).map(async () => {
107187
+ while (true) {
107188
+ const i = cursor++;
107189
+ if (i >= tasks.length) return;
107190
+ results[i] = await tasks[i]();
107191
+ }
107192
+ });
107193
+ await Promise.all(workers);
107194
+ return results;
107195
+ }
107196
+ async function fetchCampaignsForDay(config, accountId, date, verbose) {
107197
+ const googleApiUrl = requireGoogleApi(config);
107198
+ const params = new URLSearchParams();
107199
+ params.set("startDate", toGoogleDate(date, 0));
107200
+ params.set("endDate", toGoogleDate(date, 0));
107201
+ const url = `${googleApiUrl}/campaignmanagement/v2/list/${accountId}?${params}`;
107202
+ const data = await apiFetch2(url, config, {}, verbose);
107203
+ return (data.data ?? []).map((item) => ({
107204
+ id: String(item["id"] ?? ""),
107205
+ name: String(item["name"] ?? ""),
107206
+ statusV2: String(item["statusV2"] ?? ""),
107207
+ budget: toDisplayMoney(item["budget"]),
107208
+ spend: item["spend"] != null && Number.isFinite(Number(item["spend"])) ? Number(item["spend"]) : 0,
107209
+ conversions: item["conversions"] != null && Number.isFinite(Number(item["conversions"])) ? Number(item["conversions"]) : null
107210
+ })).filter((r) => r.id);
107211
+ }
107212
+ async function fetchAdGroupsForDay(config, accountId, date, verbose) {
107213
+ const googleApiUrl = requireGoogleApi(config);
107214
+ const params = new URLSearchParams();
107215
+ params.set("startDate", toGoogleDate(date, 0));
107216
+ params.set("endDate", toGoogleDate(date, 0));
107217
+ const url = `${googleApiUrl}/adgroupnmanagement/v2/list/${accountId}?${params}`;
107218
+ const data = await apiFetch2(url, config, {}, verbose);
107219
+ return (data.data ?? []).map((item) => ({
107220
+ id: String(item["id"] ?? ""),
107221
+ name: String(item["name"] ?? ""),
107222
+ campaignName: String(item["campaignName"] ?? ""),
107223
+ statusV2: String(item["statusV2"] ?? ""),
107224
+ spend: item["spend"] != null && Number.isFinite(Number(item["spend"])) ? Number(item["spend"]) : 0,
107225
+ conversions: item["conversions"] != null && Number.isFinite(Number(item["conversions"])) ? Number(item["conversions"]) : null,
107226
+ targetCpaAmountYuan: toDisplayMoney(item["targetCpaAmount"])
107227
+ })).filter((r) => r.id);
107228
+ }
107229
+ async function fetchAdsForDay(config, accountId, date, verbose) {
107230
+ const googleApiUrl = requireGoogleApi(config);
107231
+ const params = new URLSearchParams();
107232
+ params.set("startDate", toGoogleDate(date, 0));
107233
+ params.set("endDate", toGoogleDate(date, 0));
107234
+ const url = `${googleApiUrl}/admanagement/v2/list/${accountId}?${params}`;
107235
+ const data = await apiFetch2(url, config, {}, verbose);
107236
+ return (data.data ?? []).map((item) => {
107237
+ const rawTitle = item["headlines"]?.[0] ?? item["adTitle"] ?? item["headlinePart1"] ?? item["name"] ?? "";
107238
+ return {
107239
+ id: String(item["id"] ?? ""),
107240
+ name: String(rawTitle),
107241
+ campaign: String(item["campaign"] ?? ""),
107242
+ adGroup: String(item["adGroup"] ?? ""),
107243
+ statusV2: String(item["statusV2"] ?? ""),
107244
+ spend: item["spend"] != null && Number.isFinite(Number(item["spend"])) ? Number(item["spend"]) : 0,
107245
+ conversions: item["conversions"] != null && Number.isFinite(Number(item["conversions"])) ? Number(item["conversions"]) : null
107246
+ };
107247
+ }).filter((r) => r.id);
107248
+ }
107249
+ async function pauseCampaign(config, accountId, campaignId, verbose) {
107250
+ const googleApiUrl = requireGoogleApi(config);
107251
+ const detailUrl = `${googleApiUrl}/campaignmanagement/campaign/${accountId}/${campaignId}`;
107252
+ const res = await apiFetch2(detailUrl, config, {}, verbose);
107253
+ const campaign = res.data ?? res;
107254
+ const body = { ...campaign, statusV2: "Paused" };
107255
+ await apiFetch2(detailUrl, config, { method: "PUT", body: JSON.stringify(body) }, verbose);
107256
+ }
107257
+ async function pauseAdGroup(config, accountId, adGroupId, date, verbose) {
107258
+ const googleApiUrl = requireGoogleApi(config);
107259
+ const listUrl = adgroupListUrl(googleApiUrl, accountId, date, date);
107260
+ const adgroup = await findItemInList(
107261
+ listUrl,
107262
+ config,
107263
+ adGroupId,
107264
+ verbose
107265
+ );
107266
+ const putUrl = `${googleApiUrl}/adgroupnmanagement/adgroup/${accountId}/${adGroupId}`;
107267
+ await apiFetch2(
107268
+ putUrl,
107269
+ config,
107270
+ { method: "PUT", body: JSON.stringify({ ...adgroup, statusV2: "Paused" }) },
107271
+ verbose
107272
+ );
107273
+ }
107274
+ async function pauseAd(config, accountId, adId, date, verbose) {
107275
+ const googleApiUrl = requireGoogleApi(config);
107276
+ const params = new URLSearchParams();
107277
+ params.set("startDate", toGoogleDate(date, 0));
107278
+ params.set("endDate", toGoogleDate(date, 0));
107279
+ const listUrl = `${googleApiUrl}/admanagement/v2/list/${accountId}?${params}`;
107280
+ const ad = await findItemInList(listUrl, config, adId, verbose);
107281
+ const putUrl = `${googleApiUrl}/admanagement/campaign/${accountId}/${adId}`;
107282
+ await apiFetch2(
107283
+ putUrl,
107284
+ config,
107285
+ { method: "PUT", body: JSON.stringify({ ...ad, statusV2: "Paused" }) },
107286
+ verbose
107287
+ );
107288
+ }
107289
+ function printHitsTable(title, rows, cols) {
107290
+ console.log(`
107291
+ ${title}
107292
+ `);
107293
+ if (rows.length === 0) {
107294
+ console.log(" \uFF08\u65E0\u547D\u4E2D\uFF09\n");
107295
+ return;
107296
+ }
107297
+ printCliTable(rows, cols);
107298
+ console.log();
107299
+ }
107300
+
107301
+ // src/commands/guard/rules.ts
107302
+ function moneyGte(leftYuan, rightYuan) {
107303
+ return Math.round(leftYuan * 100) >= Math.round(rightYuan * 100);
107304
+ }
107305
+ function isBudgetCircuitHit(spend, budgetYuan, ratio) {
107306
+ if (spend == null || budgetYuan == null) return false;
107307
+ if (!Number.isFinite(spend) || !Number.isFinite(budgetYuan) || !Number.isFinite(ratio)) {
107308
+ return false;
107309
+ }
107310
+ if (budgetYuan <= 0 || ratio <= 0) return false;
107311
+ return moneyGte(spend, budgetYuan * ratio);
107312
+ }
107313
+ function isZeroConvHit(spend, conversions, targetCpaYuan, multiple) {
107314
+ if (spend == null || targetCpaYuan == null) return false;
107315
+ if (!Number.isFinite(spend) || !Number.isFinite(targetCpaYuan) || !Number.isFinite(multiple)) {
107316
+ return false;
107317
+ }
107318
+ if (targetCpaYuan <= 0 || multiple <= 0) return false;
107319
+ const conv = conversions == null || !Number.isFinite(conversions) ? 0 : conversions;
107320
+ if (conv !== 0) return false;
107321
+ return moneyGte(spend, targetCpaYuan * multiple);
107322
+ }
107323
+ function shanghaiToday(now = /* @__PURE__ */ new Date()) {
107324
+ return new Intl.DateTimeFormat("en-CA", {
107325
+ timeZone: "Asia/Shanghai",
107326
+ year: "numeric",
107327
+ month: "2-digit",
107328
+ day: "2-digit"
107329
+ }).format(now);
107330
+ }
107331
+ function isEnabledStatus(statusV2) {
107332
+ return String(statusV2 ?? "").toLowerCase() === "enabled";
107333
+ }
107334
+
107335
+ // src/commands/guard/budget-circuit.ts
107336
+ async function runBudgetCircuit(opts) {
107337
+ assertGoogleOnly(opts.media);
107338
+ const config = await resolveGuardConfig(opts);
107339
+ const date = opts.date?.trim() || shanghaiToday();
107340
+ const ratio = opts.ratio ?? 1.1;
107341
+ if (!Number.isFinite(ratio) || ratio <= 0) {
107342
+ console.error("\n\u274C --ratio \u5FC5\u987B\u4E3A\u6B63\u6570\uFF08\u4F8B\u5982 1.1\uFF09\n");
107343
+ process.exit(1);
107344
+ }
107345
+ const statusFilter = opts.status === void 0 ? "Enabled" : opts.status;
107346
+ const filterAll = statusFilter === "" || statusFilter === "*";
107347
+ const apply = Boolean(opts.apply);
107348
+ const accounts = await resolveGuardAccounts(opts, config);
107349
+ if (accounts.length === 0) {
107350
+ console.error("\n\u274C \u672A\u627E\u5230\u53EF\u626B\u63CF\u7684 Google \u8D26\u6237\u3002\n");
107351
+ process.exit(1);
107352
+ }
107353
+ process.stderr.write(
107354
+ `\u23F3 [guard budget-circuit] ${accounts.length} \u6237 \xB7 \u65E5\u671F ${date} \xB7 ratio=${ratio}${apply ? " \xB7 APPLY" : " \xB7 dry-run"}
107355
+ `
107356
+ );
107357
+ const accountResults = await runWithConcurrency(
107358
+ accounts.map((acc) => async () => {
107359
+ try {
107360
+ const campaigns = await fetchCampaignsForDay(
107361
+ config,
107362
+ acc.mediaCustomerId,
107363
+ date,
107364
+ opts.verbose
107365
+ );
107366
+ const hits2 = [];
107367
+ let checked = 0;
107368
+ for (const c of campaigns) {
107369
+ if (!filterAll) {
107370
+ if (statusFilter.toLowerCase() === "enabled") {
107371
+ if (!isEnabledStatus(c.statusV2)) continue;
107372
+ } else if (String(c.statusV2) !== statusFilter) {
107373
+ continue;
107374
+ }
107375
+ }
107376
+ checked++;
107377
+ if (!isBudgetCircuitHit(c.spend, c.budget, ratio)) continue;
107378
+ const budget = c.budget;
107379
+ const hit = {
107380
+ mediaCustomerId: acc.mediaCustomerId,
107381
+ accountName: acc.name,
107382
+ campaignId: c.id,
107383
+ campaignName: c.name,
107384
+ statusV2: c.statusV2,
107385
+ spend: c.spend,
107386
+ budget,
107387
+ ratio,
107388
+ threshold: budget * ratio,
107389
+ applied: false
107390
+ };
107391
+ if (apply) {
107392
+ try {
107393
+ await pauseCampaign(config, acc.mediaCustomerId, c.id, opts.verbose);
107394
+ hit.applied = true;
107395
+ hit.statusV2 = "Paused";
107396
+ } catch (err) {
107397
+ hit.applyError = err instanceof Error ? err.message : String(err);
107398
+ }
107399
+ }
107400
+ hits2.push(hit);
107401
+ }
107402
+ return {
107403
+ mediaCustomerId: acc.mediaCustomerId,
107404
+ accountName: acc.name,
107405
+ checked,
107406
+ hits: hits2
107407
+ };
107408
+ } catch (err) {
107409
+ return {
107410
+ mediaCustomerId: acc.mediaCustomerId,
107411
+ accountName: acc.name,
107412
+ checked: 0,
107413
+ hits: [],
107414
+ error: err instanceof Error ? err.message : String(err)
107415
+ };
107416
+ }
107417
+ }),
107418
+ GUARD_ACCOUNT_CONCURRENCY
107419
+ );
107420
+ const hits = accountResults.flatMap((r) => r.hits);
107421
+ const errors = accountResults.filter((r) => r.error).map((r) => ({
107422
+ mediaCustomerId: r.mediaCustomerId,
107423
+ accountName: r.accountName,
107424
+ error: r.error
107425
+ }));
107426
+ const paused = hits.filter((h) => h.applied);
107427
+ const checkedCampaigns = accountResults.reduce((n, r) => n + r.checked, 0);
107428
+ const payload = {
107429
+ meta: {
107430
+ media: "Google",
107431
+ date,
107432
+ ratio,
107433
+ statusFilter: filterAll ? "*" : statusFilter,
107434
+ apply,
107435
+ note: "\u975E Google \u5A92\u4F53\u672C CLI \u65E0\u7CFB\u5217\u7194\u65AD\u5199\u80FD\u529B\uFF1B\u672C\u547D\u4EE4\u4EC5\u626B\u63CF Google\u3002"
107436
+ },
107437
+ summary: {
107438
+ label: "\u8D85\u9884\u7B97\u7194\u65AD",
107439
+ accountCount: accounts.length,
107440
+ checkedCampaigns,
107441
+ hitCount: hits.length,
107442
+ pausedCount: paused.length,
107443
+ errorCount: errors.length,
107444
+ mode: apply ? "apply" : "dry-run"
107445
+ },
107446
+ hits,
107447
+ paused,
107448
+ errors
107449
+ };
107450
+ if (await emitCliJsonOrSnapshot(opts, {
107451
+ section: "guard-budget-circuit",
107452
+ commandLabel: "guard budget-circuit",
107453
+ payload
107454
+ })) {
107455
+ }
107456
+ const title = `\u8D85\u9884\u7B97\u7194\u65AD\uFF08${apply ? "\u5DF2\u5C1D\u8BD5\u6682\u505C" : "dry-run"}\uFF09 \xB7 ${date} \xB7 ratio=${ratio} \xB7 \u547D\u4E2D ${hits.length}/${checkedCampaigns}`;
107457
+ printHitsTable(
107458
+ title,
107459
+ hits.map((h) => ({
107460
+ account: h.mediaCustomerId,
107461
+ campaign: h.campaignName.slice(0, 36),
107462
+ spend: h.spend.toFixed(2),
107463
+ budget: h.budget.toFixed(2),
107464
+ threshold: h.threshold.toFixed(2),
107465
+ applied: h.applied ? "Paused" : h.applyError ? `ERR:${h.applyError.slice(0, 20)}` : "\u2014"
107466
+ })),
107467
+ [
107468
+ { key: "account", header: "\u8D26\u6237ID" },
107469
+ { key: "campaign", header: "\u7CFB\u5217" },
107470
+ { key: "spend", header: "\u5F53\u65E5\u82B1\u8D39" },
107471
+ { key: "budget", header: "\u65E5\u9884\u7B97" },
107472
+ { key: "threshold", header: "\u9608\u503C" },
107473
+ { key: "applied", header: "\u6682\u505C" }
107474
+ ]
107475
+ );
107476
+ if (errors.length > 0) {
107477
+ console.error(`\u26A0\uFE0F ${errors.length} \u4E2A\u8D26\u6237\u62C9\u6570\u5931\u8D25\uFF08\u8BE6\u89C1 json-out errors[]\uFF09`);
107478
+ for (const e of errors.slice(0, 5)) {
107479
+ console.error(` \u2022 ${e.mediaCustomerId}: ${e.error}`);
107480
+ }
107481
+ }
107482
+ if (!apply && hits.length > 0) {
107483
+ console.log(
107484
+ '\u63D0\u793A\uFF1A\u4EE5\u4E0A\u4E3A dry-run\u3002\u786E\u8BA4\u540E\u8FFD\u52A0 --apply --commit "budget-circuit <date>" \u6267\u884C\u6682\u505C\u3002\n'
107485
+ );
107486
+ } else if (apply) {
107487
+ console.log(
107488
+ `\u2705 apply \u5B8C\u6210\uFF1A\u547D\u4E2D ${hits.length}\uFF0C\u6210\u529F\u6682\u505C ${paused.length}${hits.length - paused.length > 0 ? `\uFF0C\u5931\u8D25 ${hits.length - paused.length}` : ""}\u3002
107489
+ `
107490
+ );
107491
+ } else {
107492
+ console.log("\u2705 \u4ECA\u65E5\u65E0\u7CFB\u5217\u89E6\u53D1\u8D85\u9884\u7B97\u7194\u65AD\u3002\n");
107493
+ }
107494
+ }
107495
+
107496
+ // src/commands/guard/zero-conv.ts
107497
+ init_cli_json_snapshot();
107498
+ function parseLevel(raw) {
107499
+ const v = (raw ?? "adgroup").toLowerCase();
107500
+ if (v === "adgroup" || v === "ad" || v === "both") return v;
107501
+ console.error("\n\u274C --level \u987B\u4E3A adgroup | ad | both\n");
107502
+ process.exit(1);
107503
+ }
107504
+ async function runZeroConv(opts) {
107505
+ assertGoogleOnly(opts.media);
107506
+ const config = await resolveGuardConfig(opts);
107507
+ const date = opts.date?.trim() || shanghaiToday();
107508
+ const multiple = opts.cpaMultiple ?? 3;
107509
+ if (!Number.isFinite(multiple) || multiple <= 0) {
107510
+ console.error("\n\u274C --cpa-multiple \u5FC5\u987B\u4E3A\u6B63\u6570\uFF08\u4F8B\u5982 3\uFF09\n");
107511
+ process.exit(1);
107512
+ }
107513
+ const level = parseLevel(opts.level);
107514
+ const fallback = opts.fallbackTargetCpa != null && Number.isFinite(opts.fallbackTargetCpa) ? opts.fallbackTargetCpa : null;
107515
+ const onlyEnabled = opts.onlyEnabled !== false;
107516
+ const apply = Boolean(opts.apply);
107517
+ if ((level === "ad" || level === "both") && fallback == null) {
107518
+ process.stderr.write(
107519
+ "\u26A0\uFE0F [guard zero-conv] \u521B\u610F\u7EF4\u65E0\u7EC4\u7EA7 targetCpa \u5B57\u6BB5\uFF0C\u672A\u4F20 --fallback-target-cpa \u65F6\u521B\u610F\u547D\u4E2D\u53EF\u80FD\u4E3A 0\uFF08\u4F1A\u8BB0\u5165 skipped\uFF09\u3002\n"
107520
+ );
107521
+ }
107522
+ const accounts = await resolveGuardAccounts(opts, config);
107523
+ if (accounts.length === 0) {
107524
+ console.error("\n\u274C \u672A\u627E\u5230\u53EF\u626B\u63CF\u7684 Google \u8D26\u6237\u3002\n");
107525
+ process.exit(1);
107526
+ }
107527
+ process.stderr.write(
107528
+ `\u23F3 [guard zero-conv] ${accounts.length} \u6237 \xB7 ${date} \xB7 \xD7${multiple} \xB7 level=${level}${apply ? " \xB7 APPLY" : " \xB7 dry-run"}
107529
+ `
107530
+ );
107531
+ const accountResults = await runWithConcurrency(
107532
+ accounts.map((acc) => async () => {
107533
+ try {
107534
+ const hits2 = [];
107535
+ const skipped2 = [];
107536
+ let checked2 = 0;
107537
+ if (level === "adgroup" || level === "both") {
107538
+ const groups = await fetchAdGroupsForDay(
107539
+ config,
107540
+ acc.mediaCustomerId,
107541
+ date,
107542
+ opts.verbose
107543
+ );
107544
+ for (const g of groups) {
107545
+ if (onlyEnabled && !isEnabledStatus(g.statusV2)) continue;
107546
+ checked2++;
107547
+ const target = g.targetCpaAmountYuan ?? fallback;
107548
+ if (target == null) {
107549
+ skipped2.push({
107550
+ mediaCustomerId: acc.mediaCustomerId,
107551
+ level: "adgroup",
107552
+ objectId: g.id,
107553
+ objectName: g.name,
107554
+ reason: "missing_target_cpa"
107555
+ });
107556
+ continue;
107557
+ }
107558
+ if (!isZeroConvHit(g.spend, g.conversions, target, multiple)) continue;
107559
+ const hit = {
107560
+ mediaCustomerId: acc.mediaCustomerId,
107561
+ accountName: acc.name,
107562
+ level: "adgroup",
107563
+ objectId: g.id,
107564
+ objectName: g.name,
107565
+ parentName: g.campaignName,
107566
+ statusV2: g.statusV2,
107567
+ spend: g.spend,
107568
+ conversions: g.conversions ?? 0,
107569
+ targetCpaYuan: target,
107570
+ multiple,
107571
+ threshold: target * multiple,
107572
+ applied: false
107573
+ };
107574
+ if (apply) {
107575
+ try {
107576
+ await pauseAdGroup(config, acc.mediaCustomerId, g.id, date, opts.verbose);
107577
+ hit.applied = true;
107578
+ hit.statusV2 = "Paused";
107579
+ } catch (err) {
107580
+ hit.applyError = err instanceof Error ? err.message : String(err);
107581
+ }
107582
+ }
107583
+ hits2.push(hit);
107584
+ }
107585
+ }
107586
+ if (level === "ad" || level === "both") {
107587
+ const ads = await fetchAdsForDay(config, acc.mediaCustomerId, date, opts.verbose);
107588
+ for (const ad of ads) {
107589
+ if (onlyEnabled && !isEnabledStatus(ad.statusV2)) continue;
107590
+ checked2++;
107591
+ if (fallback == null) {
107592
+ skipped2.push({
107593
+ mediaCustomerId: acc.mediaCustomerId,
107594
+ level: "ad",
107595
+ objectId: ad.id,
107596
+ objectName: ad.name,
107597
+ reason: "missing_fallback_target_cpa"
107598
+ });
107599
+ continue;
107600
+ }
107601
+ if (!isZeroConvHit(ad.spend, ad.conversions, fallback, multiple)) continue;
107602
+ const hit = {
107603
+ mediaCustomerId: acc.mediaCustomerId,
107604
+ accountName: acc.name,
107605
+ level: "ad",
107606
+ objectId: ad.id,
107607
+ objectName: ad.name,
107608
+ parentName: `${ad.campaign} / ${ad.adGroup}`,
107609
+ statusV2: ad.statusV2,
107610
+ spend: ad.spend,
107611
+ conversions: ad.conversions ?? 0,
107612
+ targetCpaYuan: fallback,
107613
+ multiple,
107614
+ threshold: fallback * multiple,
107615
+ applied: false
107616
+ };
107617
+ if (apply) {
107618
+ try {
107619
+ await pauseAd(config, acc.mediaCustomerId, ad.id, date, opts.verbose);
107620
+ hit.applied = true;
107621
+ hit.statusV2 = "Paused";
107622
+ } catch (err) {
107623
+ hit.applyError = err instanceof Error ? err.message : String(err);
107624
+ }
107625
+ }
107626
+ hits2.push(hit);
107627
+ }
107628
+ }
107629
+ return {
107630
+ mediaCustomerId: acc.mediaCustomerId,
107631
+ accountName: acc.name,
107632
+ checked: checked2,
107633
+ hits: hits2,
107634
+ skipped: skipped2
107635
+ };
107636
+ } catch (err) {
107637
+ return {
107638
+ mediaCustomerId: acc.mediaCustomerId,
107639
+ accountName: acc.name,
107640
+ checked: 0,
107641
+ hits: [],
107642
+ skipped: [],
107643
+ error: err instanceof Error ? err.message : String(err)
107644
+ };
107645
+ }
107646
+ }),
107647
+ GUARD_ACCOUNT_CONCURRENCY
107648
+ );
107649
+ const hits = accountResults.flatMap((r) => r.hits);
107650
+ const skipped = accountResults.flatMap((r) => r.skipped);
107651
+ const errors = accountResults.filter((r) => r.error).map((r) => ({
107652
+ mediaCustomerId: r.mediaCustomerId,
107653
+ accountName: r.accountName,
107654
+ error: r.error
107655
+ }));
107656
+ const paused = hits.filter((h) => h.applied);
107657
+ const checked = accountResults.reduce((n, r) => n + r.checked, 0);
107658
+ const payload = {
107659
+ meta: {
107660
+ media: "Google",
107661
+ date,
107662
+ cpaMultiple: multiple,
107663
+ level,
107664
+ fallbackTargetCpa: fallback,
107665
+ apply,
107666
+ note: "\u975E Google \u5A92\u4F53\u672C CLI \u65E0\u7EC4/\u521B\u610F\u7194\u65AD\u5199\u80FD\u529B\uFF1B\u672C\u547D\u4EE4\u4EC5\u626B\u63CF Google\u3002P1\u300C\u7A7A\u8017\u7194\u65AD\u300D\u901A\u77E5\u7531\u5BBF\u4E3B\u53D1\u9001\u3002"
107667
+ },
107668
+ summary: {
107669
+ label: "\u7A7A\u8017\u7194\u65AD",
107670
+ accountCount: accounts.length,
107671
+ checkedObjects: checked,
107672
+ hitCount: hits.length,
107673
+ pausedCount: paused.length,
107674
+ skippedCount: skipped.length,
107675
+ errorCount: errors.length,
107676
+ mode: apply ? "apply" : "dry-run"
107677
+ },
107678
+ hits,
107679
+ paused,
107680
+ skipped,
107681
+ errors
107682
+ };
107683
+ await emitCliJsonOrSnapshot(opts, {
107684
+ section: "guard-zero-conv",
107685
+ commandLabel: "guard zero-conv",
107686
+ payload
107687
+ });
107688
+ const title = `\u7A7A\u8017\u7194\u65AD\uFF08${apply ? "\u5DF2\u5C1D\u8BD5\u6682\u505C" : "dry-run"}\uFF09 \xB7 ${date} \xB7 \xD7${multiple} \xB7 \u547D\u4E2D ${hits.length}/${checked}`;
107689
+ printHitsTable(
107690
+ title,
107691
+ hits.map((h) => ({
107692
+ account: h.mediaCustomerId,
107693
+ level: h.level,
107694
+ name: h.objectName.slice(0, 32),
107695
+ spend: h.spend.toFixed(2),
107696
+ target: h.targetCpaYuan.toFixed(2),
107697
+ threshold: h.threshold.toFixed(2),
107698
+ applied: h.applied ? "Paused" : h.applyError ? `ERR` : "\u2014"
107699
+ })),
107700
+ [
107701
+ { key: "account", header: "\u8D26\u6237ID" },
107702
+ { key: "level", header: "\u5C42\u7EA7" },
107703
+ { key: "name", header: "\u5BF9\u8C61" },
107704
+ { key: "spend", header: "\u5F53\u65E5\u82B1\u8D39" },
107705
+ { key: "target", header: "\u76EE\u6807CPA" },
107706
+ { key: "threshold", header: "\u9608\u503C" },
107707
+ { key: "applied", header: "\u6682\u505C" }
107708
+ ]
107709
+ );
107710
+ if (errors.length > 0) {
107711
+ console.error(`\u26A0\uFE0F ${errors.length} \u4E2A\u8D26\u6237\u62C9\u6570\u5931\u8D25\uFF08\u8BE6\u89C1 json-out errors[]\uFF09`);
107712
+ }
107713
+ if (!apply && hits.length > 0) {
107714
+ console.log(
107715
+ '\u63D0\u793A\uFF1A\u4EE5\u4E0A\u4E3A dry-run\u3002\u786E\u8BA4\u540E\u8FFD\u52A0 --apply --commit "zero-conv <date>" \u6267\u884C\u6682\u505C\u3002\n'
107716
+ );
107717
+ } else if (apply) {
107718
+ console.log(
107719
+ `\u2705 apply \u5B8C\u6210\uFF1A\u547D\u4E2D ${hits.length}\uFF0C\u6210\u529F\u6682\u505C ${paused.length}${hits.length - paused.length > 0 ? `\uFF0C\u5931\u8D25 ${hits.length - paused.length}` : ""}\u3002
107720
+ `
107721
+ );
107722
+ } else {
107723
+ console.log("\u2705 \u4ECA\u65E5\u65E0\u5BF9\u8C61\u89E6\u53D1\u7A7A\u8017\u7194\u65AD\u3002\n");
107724
+ }
107725
+ }
107726
+
107727
+ // src/commands/guard/_register.ts
107728
+ function register11(program2) {
107729
+ const guard = program2.command("guard").description(
107730
+ "Google \u6295\u653E\u81EA\u63A7\u7194\u65AD\uFF1A\u5168\u6237\uFF08\u6216 -a \u5B50\u96C6\uFF09\u626B\u63CF\u5F53\u65E5\u8D85\u9884\u7B97/\u7A7A\u8017\u547D\u4E2D\uFF0C\u9ED8\u8BA4 dry-run\uFF1B--apply \u65F6\u6682\u505C\u547D\u4E2D\u5BF9\u8C61\uFF08\u987B\u5168\u5C40 --commit\uFF09\u3002\u975E Google \u5A92\u4F53\u4E0D\u652F\u6301\u3002"
107731
+ );
107732
+ guard.command("budget-circuit").description(
107733
+ "\u5355\u65E5\u9884\u7B97\u7194\u65AD\uFF1A\u5F53\u65E5\u7CFB\u5217 spend \u2265 \u65E5\u9884\u7B97 \xD7 ratio \u65F6\u5217\u51FA\uFF08\u53EF\u9009\u6682\u505C\uFF09\u3002\u66FF\u4EE3\u300Clist-accounts \xD7 N + \u9010\u6237 ad campaigns + \u811A\u672C\u300D\u3002"
107734
+ ).requiredOption("-m, --media <type>", "\u76EE\u524D\u4EC5\u652F\u6301 Google").option(
107735
+ "-a, --accounts <ids>",
107736
+ "\u6307\u5B9A\u8D26\u6237 mediaCustomerId\uFF0C\u9017\u53F7\u5206\u9694\uFF1B\u7559\u7A7A\u5219\u626B\u63CF\u540D\u4E0B\u5168\u90E8 Google \u8D26\u6237"
107737
+ ).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(
107738
+ "--status <statusV2>",
107739
+ "\u53EA\u68C0\u67E5\u8BE5\u72B6\u6001\u7684\u7CFB\u5217\uFF08\u9ED8\u8BA4 Enabled\uFF1B\u4F20 * \u8868\u793A\u4E0D\u8FC7\u6EE4\uFF09",
107740
+ "Enabled"
107741
+ ).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(
107742
+ "--json-out <path>",
107743
+ "\u843D\u76D8\u76EE\u5F55\u6216 *.json\uFF1Bstdout \u4E00\u884C\u6458\u8981\uFF1B\u4EBA\u7C7B\u53EF\u8BFB\u8868\u59CB\u7EC8\u6253\u5370",
107744
+ void 0
107745
+ ).option("--refresh-dp", "\u5F3A\u5236\u91CD\u62C9 Datapermission", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF", false).action(
107746
+ async (opts) => {
107747
+ await runBudgetCircuit({
107748
+ media: opts.media,
107749
+ accounts: opts.accounts,
107750
+ date: opts.date,
107751
+ ratio: opts.ratio,
107752
+ status: opts.status,
107753
+ apply: opts.apply,
107754
+ pageSize: opts.pageSize,
107755
+ maxPages: opts.maxPages,
107756
+ token: opts.token,
107757
+ jsonOut: opts.jsonOut,
107758
+ refreshDp: opts.refreshDp,
107759
+ verbose: opts.verbose
107760
+ });
107761
+ }
107762
+ );
107763
+ guard.command("zero-conv").description(
107764
+ "\u7A7A\u8017\u7194\u65AD\uFF1A\u5F53\u65E5 spend \u2265 \u76EE\u6807 CPA \xD7 N \u4E14\u8F6C\u5316\u4E3A 0 \u65F6\u5217\u51FA\u7EC4/\u521B\u610F\uFF08\u53EF\u9009\u6682\u505C\uFF09\u3002\u7ED3\u679C summary.label=\u300C\u7A7A\u8017\u7194\u65AD\u300D\uFF1BP1 \u901A\u77E5\u7531\u5BBF\u4E3B\u53D1\u9001\u3002"
107765
+ ).requiredOption("-m, --media <type>", "\u76EE\u524D\u4EC5\u652F\u6301 Google").option(
107766
+ "-a, --accounts <ids>",
107767
+ "\u6307\u5B9A\u8D26\u6237 mediaCustomerId\uFF0C\u9017\u53F7\u5206\u9694\uFF1B\u7559\u7A7A\u5219\u626B\u63CF\u540D\u4E0B\u5168\u90E8 Google \u8D26\u6237"
107768
+ ).option("--date <YYYY-MM-DD>", "\u7EDF\u8BA1\u65E5\uFF08Asia/Shanghai \u9ED8\u8BA4\u4ECA\u5929\uFF09").option(
107769
+ "--cpa-multiple <n>",
107770
+ "\u500D\u6570 N\uFF1Aspend >= targetCpa \xD7 N\uFF08\u9ED8\u8BA4 3\uFF09",
107771
+ (v) => parseFloat(v)
107772
+ ).option(
107773
+ "--level <level>",
107774
+ "\u626B\u63CF\u5C42\u7EA7\uFF1Aadgroup | ad | both\uFF08\u9ED8\u8BA4 adgroup\uFF09",
107775
+ "adgroup"
107776
+ ).option(
107777
+ "--fallback-target-cpa <yuan>",
107778
+ "\u7EC4\u65E0 targetCpa\uFF08\u6216\u521B\u610F\u7EF4\uFF09\u65F6\u4F7F\u7528\u7684\u76EE\u6807 CPA\uFF08\u5143\uFF09",
107779
+ (v) => parseFloat(v)
107780
+ ).option("--apply", "\u5BF9\u547D\u4E2D\u5BF9\u8C61\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("--json-out <path>", "\u843D\u76D8\u76EE\u5F55\u6216 *.json", void 0).option("--refresh-dp", "\u5F3A\u5236\u91CD\u62C9 Datapermission", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF", false).action(
107781
+ async (opts) => {
107782
+ await runZeroConv({
107783
+ media: opts.media,
107784
+ accounts: opts.accounts,
107785
+ date: opts.date,
107786
+ cpaMultiple: opts.cpaMultiple,
107787
+ level: opts.level,
107788
+ fallbackTargetCpa: opts.fallbackTargetCpa,
107789
+ apply: opts.apply,
107790
+ pageSize: opts.pageSize,
107791
+ maxPages: opts.maxPages,
107792
+ token: opts.token,
107793
+ jsonOut: opts.jsonOut,
107794
+ refreshDp: opts.refreshDp,
107795
+ verbose: opts.verbose
107796
+ });
107797
+ }
107798
+ );
107799
+ }
107800
+
107058
107801
  // src/commands/stats.ts
107059
107802
  init_auth();
107060
107803
  init_cli_json_snapshot();
@@ -107307,7 +108050,7 @@ ${opts.media} \u6295\u653E\u6570\u636E\uFF08${rangeLabel}\uFF09\uFF08\u7B2C 1 \u
107307
108050
  printCliTable(rows, cols);
107308
108051
  console.log();
107309
108052
  }
107310
- function register11(program2) {
108053
+ function register12(program2) {
107311
108054
  program2.command("stats").description(
107312
108055
  "\u67E5\u8BE2\u5E7F\u544A\u6D88\u8017\u3001\u70B9\u51FB\u3001\u8F6C\u5316\u7B49\u6295\u653E\u6570\u636E\uFF08\u9ED8\u8BA4\u8FD1 7 \u5929\u533A\u95F4\u5408\u8BA1\uFF0C\u4E0D\u542B\u4ECA\u5929\uFF0C\u65E5\u5386\u6309 UTC+8\uFF1B\u8981\u6309\u65E5\u660E\u7EC6\u52A0 --by-day\uFF09"
107313
108056
  ).requiredOption(
@@ -107426,7 +108169,7 @@ async function runAccountHistory(opts) {
107426
108169
  }
107427
108170
  console.log();
107428
108171
  }
107429
- function register12(program2) {
108172
+ function register13(program2) {
107430
108173
  program2.command("account-history").description("\u67E5\u8BE2\u5F00\u6237\u7533\u8BF7\u5386\u53F2\u8BB0\u5F55").option("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | MetaAd | BingV2 | Kwai").option("-s, --status <status>", "\u7533\u8BF7\u72B6\u6001\u5173\u952E\u5B57").option("-k, --keyword <text>", "\u641C\u7D22\u5173\u952E\u5B57\uFF08\u8D26\u6237\u540D/ID\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-p, --page <n>", "\u9875\u7801", parseInt).option("--page-size <n>", "\u6BCF\u9875\u6570\u91CF\uFF08\u9ED8\u8BA4 20\uFF09", parseInt).option("-t, --token <token>", "Auth Token").option(
107431
108174
  "--json-out <path>",
107432
108175
  "\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",
@@ -108268,7 +109011,7 @@ async function runReportPushReceiveEmails(opts) {
108268
109011
  var splitCsv = (v) => v.split(",").map((s) => s.trim()).filter(Boolean);
108269
109012
 
108270
109013
  // src/commands/report/register.ts
108271
- function register13(program2) {
109014
+ function register14(program2) {
108272
109015
  const reportCmd = program2.command("report").description("\u4F18\u5316\u62A5\u544A\u4E0E\u63A8\u9001\u7BA1\u7406");
108273
109016
  reportCmd.command("list").description("\u67E5\u8BE2\u4F18\u5316\u62A5\u544A\u5217\u8868").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").option("-t, --type <type>", "\u62A5\u544A\u7C7B\u578B\uFF08\u5982 Daily / Hourly\uFF09").option("-s, --status <ready>", "\u751F\u6210\u72B6\u6001\uFF08true/false\uFF09").option("-k, --keyword <text>", "\u62A5\u544A\u540D\u79F0\u5173\u952E\u5B57").option("--start <date>", "\u5F00\u59CB\u65E5\u671F").option("--end <date>", "\u7ED3\u675F\u65E5\u671F").option("-p, --page <n>", "\u9875\u7801", parseInt).option("--page-size <n>", "\u6BCF\u9875\u6570\u91CF", parseInt).option("--token <token>", "Auth Token").option(
108274
109017
  "--json-out <path>",
@@ -108596,7 +109339,7 @@ async function runTransferList(opts) {
108596
109339
  printCliTable(rows, cols);
108597
109340
  console.log();
108598
109341
  }
108599
- function register14(program2) {
109342
+ function register15(program2) {
108600
109343
  const transferCmd = program2.command("transfer").description("\u5A92\u4F53\u8D26\u6237\u8F6C\u8D26\uFF1A\u63D0\u4EA4\u8F6C\u8D26\u7533\u8BF7\u6216\u67E5\u8BE2\u8F6C\u8D26\u8BB0\u5F55");
108601
109344
  transferCmd.command("create").description("\u63D0\u4EA4\u8D26\u6237\u95F4\u8F6C\u8D26\u7533\u8BF7\uFF08Google / TikTok / MetaAd / BingV2\uFF09").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | MetaAd | BingV2").requiredOption("--out <id>", "\u8F6C\u51FA\u8D26\u6237 mediaCustomerId\uFF08\u6765\u81EA list-accounts\uFF09").requiredOption("--in <id>", "\u8F6C\u5165\u8D26\u6237 mediaCustomerId\uFF08\u6765\u81EA list-accounts\uFF09").requiredOption("--amount <amount>", "\u8F6C\u8D26\u91D1\u989D\uFF08\u4E0E\u8D26\u6237\u8D27\u5E01\u5355\u4F4D\u4E00\u81F4\uFF09").option("--customer-name <name>", "\u5BA2\u6237\u540D\u79F0\uFF08\u5907\u6CE8\uFF0C\u53EF\u9009\uFF09").option("-t, --token <token>", "Auth Token").option(
108602
109345
  "--json-out <path>",
@@ -109041,7 +109784,7 @@ async function runInvoiceApply(opts) {
109041
109784
  }
109042
109785
 
109043
109786
  // src/commands/invoice/_register.ts
109044
- function register15(program2) {
109787
+ function register16(program2) {
109045
109788
  const invoiceCmd = program2.command("invoice").description("\u5F00\u7968\u8BB0\u5F55\u4E0E\u5F00\u7968\u7533\u8BF7\uFF0C\u4E0D\u652F\u6301\u8D26\u6237\u6FC0\u6D3B\u5145\u503C\u8BA2\u5355\u7684\u5F00\u7968\uFF0C\u5982\u9700\u5F00\u7968\u8BF7\u8054\u7CFB\u7EBF\u4E0B\u8FD0\u8425");
109046
109789
  invoiceCmd.command("list").description("\u67E5\u8BE2\u5F00\u7968\u7533\u8BF7\u8BB0\u5F55").option("-k, --keyword <text>", "\u53D1\u7968\u53F7\u6216\u5173\u952E\u5B57").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-p, --page <n>", "\u9875\u7801", parseInt).option("--page-size <n>", "\u6BCF\u9875\u6570\u91CF", parseInt).option("-t, --token <token>", "Auth Token").option(
109047
109790
  "--json-out <path>",
@@ -109312,7 +110055,7 @@ async function runInvoiceInfoDelete(opts) {
109312
110055
  \u2705 \u53D1\u7968\u62AC\u5934\u5DF2\u5220\u9664\uFF08ID: ${opts.id}\uFF09
109313
110056
  `);
109314
110057
  }
109315
- function register16(program2) {
110058
+ function register17(program2) {
109316
110059
  const invoiceInfoCmd = program2.command("invoice-info").description("\u53D1\u7968\u62AC\u5934\u7BA1\u7406\uFF08\u5BF9\u5E94\u300C\u8BBE\u7F6E \u2192 \u53D1\u7968\u62AC\u5934\u300D\u9875\u9762\uFF09");
109317
110060
  invoiceInfoCmd.command("list").description("\u67E5\u8BE2\u5DF2\u4FDD\u5B58\u7684\u53D1\u7968\u62AC\u5934\u5217\u8868").option("-k, --keyword <text>", "\u6309\u516C\u53F8\u540D\u79F0\u641C\u7D22").option("--invoice-type <type>", "\u6309\u53D1\u7968\u7C7B\u578B\u7B5B\u9009\uFF1API | VATI | VATSI").option("--page <n>", "\u9875\u7801", (v) => parseInt(v), 1).option("--page-size <n>", "\u6BCF\u9875\u6761\u6570", (v) => parseInt(v), 20).option("-t, --token <token>", "Auth Token").option(
109318
110061
  "--json-out <path>",
@@ -109660,7 +110403,7 @@ async function runOptimizeChildren(opts) {
109660
110403
  }
109661
110404
 
109662
110405
  // src/commands/optimize/_register.ts
109663
- function register17(program2) {
110406
+ function register18(program2) {
109664
110407
  const optimizeCmd = program2.command("optimize").description("AI \u5E7F\u544A\u4F18\u5316\uFF08Google\uFF09");
109665
110408
  optimizeCmd.command("list").description("\u67E5\u8BE2\u8D26\u6237\u7EA7\u4F18\u5316\u4E3B\u5217\u8868\uFF08\u5BF9\u5E94\u9875\u9762 /advertising/intelligentOptimization\uFF09").option("-a, --account <id>", "\u8D26\u6237 mediaCustomerId\uFF08\u7559\u7A7A\u67E5\u5168\u90E8\uFF09").option(
109666
110409
  "--match-media-customer-id <id>",
@@ -110106,7 +110849,7 @@ async function runForewarningCreate(opts) {
110106
110849
  }
110107
110850
 
110108
110851
  // src/commands/forewarning/_register.ts
110109
- function register18(program2) {
110852
+ function register19(program2) {
110110
110853
  const forewarningCmd = program2.command("forewarning").description("\u667A\u80FD\u9884\u8B66\u89C4\u5219\u4E0E\u9884\u8B66\u8BB0\u5F55\u7BA1\u7406");
110111
110854
  forewarningCmd.command("list").description("\u67E5\u8BE2\u9884\u8B66\u89C4\u5219\u5217\u8868").requiredOption("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok").option("-a, --account <id>", "\u8D26\u6237 ID \u7B5B\u9009").option("-k, --keyword <text>", "\u89C4\u5219\u540D\u79F0\u5173\u952E\u5B57").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-p, --page <n>", "\u9875\u7801", parseInt).option("--page-size <n>", "\u6BCF\u9875\u6570\u91CF", parseInt).option("-t, --token <token>", "Auth Token").option(
110112
110855
  "--json-out <path>",
@@ -110398,7 +111141,7 @@ Meta \u7EBF\u7D22\u8868\u5355\uFF08\u8D26\u6237 ${opts.account}\uFF0C\u7B2C 1 \u
110398
111141
  console.log();
110399
111142
  }
110400
111143
  }
110401
- function register19(program2) {
111144
+ function register20(program2) {
110402
111145
  program2.command("clue").description("\u67E5\u8BE2\u5E7F\u544A\u7EBF\u7D22\u8868\u5355\u6570\u636E\uFF08TikTok / Meta\uFF09").option("-m, --media <type>", "\u5A92\u4F53\u7C7B\u578B\uFF1ATikTok | Meta", "TikTok").requiredOption("-a, --account <id>", "\u5E7F\u544A\u8D26\u6237 mediaCustomerId").option("--region <region>", "TikTok \u533A\u57DF\uFF1Aeu | us | other | ALL\uFF08\u9ED8\u8BA4 ALL\uFF09").option("--start <date>", "Meta \u5F00\u59CB\u65E5\u671F\uFF08YYYY-MM-DD\uFF09").option("--end <date>", "Meta \u7ED3\u675F\u65E5\u671F\uFF08YYYY-MM-DD\uFF09").option(
110403
111146
  "--json-out <path>",
110404
111147
  "\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",
@@ -110417,80 +111160,6 @@ function register19(program2) {
110417
111160
  });
110418
111161
  }
110419
111162
 
110420
- // src/commands/ad/_shared.ts
110421
- init_auth();
110422
- async function findItemInList(listUrl, config, id, verbose) {
110423
- const res = await apiFetch2(listUrl, config, {}, verbose);
110424
- const item = (res.data ?? []).find((x) => x["id"] === id);
110425
- if (!item) {
110426
- throw new Error(`\u672A\u5728\u5217\u8868\u4E2D\u627E\u5230 id="${id}" \u7684\u6761\u76EE`);
110427
- }
110428
- return item;
110429
- }
110430
- function toGoogleDate(dateStr, offsetDays) {
110431
- if (dateStr) return dateStr.replace(/-/g, "/");
110432
- const d = /* @__PURE__ */ new Date();
110433
- d.setDate(d.getDate() + offsetDays);
110434
- return d.toISOString().slice(0, 10).replace(/-/g, "/");
110435
- }
110436
- function toDisplayMoney(raw) {
110437
- if (raw == null) return null;
110438
- const n = Number(raw);
110439
- if (!Number.isFinite(n)) return null;
110440
- return n / 100;
110441
- }
110442
- function toCentAmount(displayValue) {
110443
- if (!Number.isFinite(displayValue)) {
110444
- throw new Error(`toCentAmount: \u8F93\u5165\u5FC5\u987B\u662F\u6709\u9650\u6570\u5B57\uFF0C\u6536\u5230 ${displayValue}`);
110445
- }
110446
- return Math.round(displayValue * 100);
110447
- }
110448
- var CAMPAIGN_BATCH_MONEY_KEYS = /* @__PURE__ */ new Set([
110449
- "Budget",
110450
- "MaxCPCAmount",
110451
- "MaxCpmAmount",
110452
- "MaxCPVAmount",
110453
- "TargetCpa_BidingAmount",
110454
- "TargetSpend_BidCeilingAmount",
110455
- "TargetCpaAmount",
110456
- "MaxCPC"
110457
- ]);
110458
- function convertBatchMoneyTreeFromYuan(value) {
110459
- return convertBatchMoneyTreeFromYuanInner(value);
110460
- }
110461
- function convertBatchMoneyTreeFromYuanInner(value) {
110462
- if (Array.isArray(value)) {
110463
- return value.map(convertBatchMoneyTreeFromYuanInner);
110464
- }
110465
- if (value && typeof value === "object") {
110466
- const out = {};
110467
- for (const [key, val] of Object.entries(value)) {
110468
- if (typeof val === "number" && CAMPAIGN_BATCH_MONEY_KEYS.has(key)) {
110469
- out[key] = toCentAmount(val);
110470
- } else {
110471
- out[key] = convertBatchMoneyTreeFromYuanInner(val);
110472
- }
110473
- }
110474
- return out;
110475
- }
110476
- return value;
110477
- }
110478
- function adgroupListUrl(googleApiUrl, account, startDate, endDate) {
110479
- const params = new URLSearchParams();
110480
- params.set("startDate", toGoogleDate(startDate, -30));
110481
- params.set("endDate", toGoogleDate(endDate, 0));
110482
- return `${googleApiUrl}/adgroupnmanagement/v2/list/${account}?${params}`;
110483
- }
110484
- function requireGoogleApi(config) {
110485
- if (!config.googleApiUrl) {
110486
- console.error(
110487
- "\n\u274C \u672A\u627E\u5230 Google API \u5730\u5740\u3002\n Google API \u5730\u5740\u4ECE TSO API \u5730\u5740\u81EA\u52A8\u63A8\u5BFC\uFF0C\u4E5F\u53EF\u901A\u8FC7\u73AF\u5883\u53D8\u91CF SILUZAN_GOOGLE_API \u8986\u76D6\u3002\n"
110488
- );
110489
- process.exit(1);
110490
- }
110491
- return config.googleApiUrl;
110492
- }
110493
-
110494
111163
  // src/commands/ad/campaign.ts
110495
111164
  init_auth();
110496
111165
  init_cli_json_snapshot();
@@ -119638,7 +120307,7 @@ function parseOptionalMatchType(raw) {
119638
120307
  `);
119639
120308
  process.exit(1);
119640
120309
  }
119641
- function register20(program2) {
120310
+ function register21(program2) {
119642
120311
  const adCmd = program2.command("ad").description(
119643
120312
  "Google \u5E7F\u544A\u7BA1\u7406\uFF08Google API \u5730\u5740\u4ECE TSO API \u81EA\u52A8\u63A8\u5BFC\uFF0C\u53EF\u901A\u8FC7 SILUZAN_GOOGLE_API \u73AF\u5883\u53D8\u91CF\u8986\u76D6\uFF09\n \u542B\uFF1A\u7CFB\u5217/\u7EC4/\u521B\u610F/\u5173\u952E\u8BCD\u7B49\u5E38\u89C4\u5E7F\u544A\u6295\u653E\u4E0E\u7BA1\u7406\u80FD\u529B"
119644
120313
  );
@@ -121311,7 +121980,7 @@ function attachBaseCliOptions(cmd) {
121311
121980
  function splitKeywordSeeds(raw) {
121312
121981
  return raw.split(",").map((s) => s.trim()).filter(Boolean);
121313
121982
  }
121314
- function register21(program2) {
121983
+ function register22(program2) {
121315
121984
  const keywordCmd = program2.command("keyword").description(
121316
121985
  "Google \u5173\u952E\u5B57\u63A8\u8350\uFF08\u53EF\u9009 -a \u8D26\u6237 ID \u6309\u8D26\u6237\u5E01\u79CD\u51FA\u4EF7\uFF1B\u5426\u5219\u5171\u4EAB MCC/USD\uFF1B\u53EF --geo \u9650\u5B9A\u5E02\u573A\uFF09"
121317
121986
  );
@@ -121780,7 +122449,7 @@ async function runRagList(options) {
121780
122449
  ];
121781
122450
  console.log(lines.join("\n"));
121782
122451
  }
121783
- function register22(program2) {
122452
+ function register23(program2) {
121784
122453
  const ragCmd = program2.command("rag").description(
121785
122454
  "RAG \u77E5\u8BC6\u5E93\u68C0\u7D22\uFF08\u534F\u52A9\u5E7F\u544A\u6295\u653E\u3001\u62D3\u8BCD\u3001\u8D26\u6237\u8BCA\u65AD\u4E0E\u62A5\u544A\uFF1B\u9ED8\u8BA4 belongToId=account/me \u7684 companyId\uFF09"
121786
122455
  );
@@ -123231,7 +123900,7 @@ async function runAccountBmBind(opts) {
123231
123900
  }
123232
123901
 
123233
123902
  // src/commands/account-manage-register.ts
123234
- function register23(program2) {
123903
+ function register24(program2) {
123235
123904
  const accountCmd = program2.command("account").description("\u5E7F\u544A\u8D26\u6237\u7BA1\u7406\uFF1AOAuth \u6DFB\u52A0\u6388\u6743\u3001\u89E3\u9664\u5173\u8054\uFF08\u89E3\u7ED1\uFF09\u3001Google MCC \u7ED1\u5B9A/\u89E3\u7ED1\u3001\u8D26\u53F7\u5206\u4EAB");
123236
123905
  accountCmd.command("list", { hidden: true }).description("\uFF08\u517C\u5BB9\u522B\u540D\uFF09\u540C list-accounts").option(
123237
123906
  "-m, --media <type>",
@@ -125061,7 +125730,7 @@ async function runOpenAccountMeta(opts) {
125061
125730
  }
125062
125731
 
125063
125732
  // src/commands/open-account-register.ts
125064
- function register24(program2) {
125733
+ function register25(program2) {
125065
125734
  const openAccountCmd = program2.command("open-account").description("\u5F00\u6237\u7533\u8BF7\uFF08Google / TikTok / Yandex / Bing / Kwai / Meta \u5F00\u6237\u94FE\u63A5\uFF09");
125066
125735
  openAccountCmd.command("meta").description(
125067
125736
  "\u83B7\u53D6 Meta/Facebook \u5B98\u65B9\u5F00\u6237\u94FE\u63A5\uFF08GET MetaAccount/Management/GetOpenAccountLink\uFF0C\u4E0E\u7F51\u9875\u300C\u7533\u8BF7\u5F00\u6237\u300D\u4E00\u81F4\uFF09"
@@ -125285,7 +125954,7 @@ function register24(program2) {
125285
125954
  }
125286
125955
 
125287
125956
  // src/commands/diagnostic.ts
125288
- function register25(program2) {
125957
+ function register26(program2) {
125289
125958
  program2.command("diagnostic").description("\u8BCA\u65AD\u5165\u53E3\u8BF4\u660E\uFF1AGoogle \u5E7F\u544A\u8D26\u6237\u8BCA\u65AD\u9875 vs \u7F51\u7AD9\u8BCA\u65AD CLI").action(() => {
125290
125959
  console.log("\n\u8BCA\u65AD\u80FD\u529B\u8BF4\u660E\n");
125291
125960
  console.log(" \u3010Google \u5E7F\u544A\u8D26\u6237\u8BCA\u65AD\u3011");
@@ -129055,12 +129724,12 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
129055
129724
  const accountId = manifest.accountId ?? (typeof overview?.accountId === "string" ? overview.accountId : void 0);
129056
129725
  const kpis = { ...payload.kpis ?? {} };
129057
129726
  if (current) {
129058
- if (kpis.spend == null) kpis.spend = current.spend;
129059
- if (kpis.results == null) kpis.results = current.results;
129060
- if (kpis.costPerResult == null) kpis.costPerResult = current.costPerResult;
129061
- if (kpis.reach == null) kpis.reach = current.reach;
129062
- if (kpis.impressions == null) kpis.impressions = current.impressions;
129063
- if (kpis.frequency == null) kpis.frequency = current.frequency;
129727
+ if (current.spend != null) kpis.spend = current.spend;
129728
+ if (current.results != null) kpis.results = current.results;
129729
+ if (current.costPerResult != null) kpis.costPerResult = current.costPerResult;
129730
+ if (current.reach != null) kpis.reach = current.reach;
129731
+ if (current.impressions != null) kpis.impressions = current.impressions;
129732
+ if (current.frequency != null) kpis.frequency = current.frequency;
129064
129733
  }
129065
129734
  const meta = { ...payload.meta ?? {} };
129066
129735
  if (!meta.accountName && accountName) meta.accountName = accountName;
@@ -129073,12 +129742,12 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
129073
129742
  if (!meta.resultType && current?.resultType) meta.resultType = String(current.resultType);
129074
129743
  const charts = { ...payload.charts ?? {} };
129075
129744
  const avgCpl = num5(kpis.costPerResult) ?? 0;
129076
- if (!charts.platform) {
129745
+ {
129077
129746
  const platform = sectionMap.get("platform");
129078
129747
  const networks = Array.isArray(platform?.networks) ? platform.networks : [];
129079
129748
  if (networks.length) charts.platform = aggregatePlatform(networks);
129080
129749
  }
129081
- if (!charts.country) {
129750
+ {
129082
129751
  const country = sectionMap.get("country");
129083
129752
  const countries = Array.isArray(country?.countries) ? country.countries : [];
129084
129753
  if (countries.length) {
@@ -129089,7 +129758,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
129089
129758
  };
129090
129759
  }
129091
129760
  }
129092
- if (!charts.audience) {
129761
+ {
129093
129762
  const audience = sectionMap.get("audience");
129094
129763
  const audiences = Array.isArray(audience?.audiences) ? audience.audiences : [];
129095
129764
  if (audiences.length) {
@@ -129101,11 +129770,11 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
129101
129770
  };
129102
129771
  }
129103
129772
  }
129104
- if (!charts.funnel && num5(kpis.reach) != null) {
129773
+ if (num5(kpis.reach) != null) {
129105
129774
  charts.funnel = { reach: kpis.reach, results: kpis.results ?? 0 };
129106
129775
  }
129107
129776
  const tables = { ...payload.tables ?? {} };
129108
- if (!tables.adSets) {
129777
+ {
129109
129778
  const adSets = sectionMap.get("ad-sets");
129110
129779
  const groups = Array.isArray(adSets?.adGroups) ? adSets.adGroups : [];
129111
129780
  if (groups.length) {
@@ -129128,15 +129797,13 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
129128
129797
  }).sort((a, b) => b.spend - a.spend);
129129
129798
  }
129130
129799
  }
129131
- if (!tables.audienceTop || !tables.audienceBottom) {
129800
+ {
129132
129801
  const audience = sectionMap.get("audience");
129133
129802
  const audiences = Array.isArray(audience?.audiences) ? audience.audiences : [];
129134
129803
  if (audiences.length) {
129135
129804
  const rows = buildAudienceRows(audiences).sort((a, b) => a.costPerResult - b.costPerResult);
129136
- const topN = rows.slice(0, 10);
129137
- const bottomN = [...rows].reverse().slice(0, 10);
129138
- if (!tables.audienceTop) tables.audienceTop = topN;
129139
- if (!tables.audienceBottom) tables.audienceBottom = bottomN;
129805
+ tables.audienceTop = rows.slice(0, 10);
129806
+ tables.audienceBottom = [...rows].reverse().slice(0, 10);
129140
129807
  }
129141
129808
  }
129142
129809
  const currency = typeof meta.currency === "string" ? meta.currency : "USD";
@@ -129851,17 +130518,26 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
129851
130518
  if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
129852
130519
  const kpis = { ...payload.kpis ?? {} };
129853
130520
  if (currentPeriod) {
129854
- if (kpis.spend == null) kpis.spend = num6(currentPeriod.spend);
129855
- if (kpis.impressions == null) kpis.impressions = num6(currentPeriod.impressions);
129856
- if (kpis.clicks == null) kpis.clicks = num6(currentPeriod.clicks);
129857
- if (kpis.results == null) kpis.results = num6(currentPeriod.results);
129858
- if (kpis.costPerResult == null) kpis.costPerResult = num6(currentPeriod.costPerResult);
129859
- if (kpis.ctr == null) kpis.ctr = num6(currentPeriod.ctr);
129860
- if (kpis.cpc == null) kpis.cpc = num6(currentPeriod.cpc ?? currentPeriod.averageCpc);
129861
- if (kpis.reach == null) kpis.reach = num6(currentPeriod.reach);
129862
- if (kpis.frequency == null) kpis.frequency = num6(currentPeriod.frequency);
129863
- }
129864
- if (!kpis.previousPeriod && previousPeriod) {
130521
+ const spend = num6(currentPeriod.spend);
130522
+ const impressions = num6(currentPeriod.impressions);
130523
+ const clicks = num6(currentPeriod.clicks);
130524
+ const results = num6(currentPeriod.results);
130525
+ const costPerResult = num6(currentPeriod.costPerResult);
130526
+ const ctr = num6(currentPeriod.ctr);
130527
+ const cpc = num6(currentPeriod.cpc ?? currentPeriod.averageCpc);
130528
+ const reach = num6(currentPeriod.reach);
130529
+ const frequency = num6(currentPeriod.frequency);
130530
+ if (spend != null) kpis.spend = spend;
130531
+ if (impressions != null) kpis.impressions = impressions;
130532
+ if (clicks != null) kpis.clicks = clicks;
130533
+ if (results != null) kpis.results = results;
130534
+ if (costPerResult != null) kpis.costPerResult = costPerResult;
130535
+ if (ctr != null) kpis.ctr = ctr;
130536
+ if (cpc != null) kpis.cpc = cpc;
130537
+ if (reach != null) kpis.reach = reach;
130538
+ if (frequency != null) kpis.frequency = frequency;
130539
+ }
130540
+ if (previousPeriod) {
129865
130541
  kpis.previousPeriod = {
129866
130542
  spend: num6(previousPeriod.spend),
129867
130543
  impressions: num6(previousPeriod.impressions),
@@ -129875,7 +130551,7 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
129875
130551
  };
129876
130552
  }
129877
130553
  const tables = { ...payload.tables ?? {} };
129878
- if (!tables.adSets) {
130554
+ {
129879
130555
  const rows = asRows(sectionMap.get("ad-sets")?.adGroups).map(
129880
130556
  (r) => pickMetricExtras(r, [
129881
130557
  "adGroupId",
@@ -129886,38 +130562,38 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
129886
130562
  );
129887
130563
  if (rows.length > 0) tables.adSets = rows;
129888
130564
  }
129889
- if (!tables.platform) {
130565
+ {
129890
130566
  const rows = asRows(sectionMap.get("platform")?.networks).map(
129891
130567
  (r) => pickMetricExtras(r, ["publisherPlatform", "platformPosition", "network"])
129892
130568
  );
129893
130569
  if (rows.length > 0) tables.platform = rows;
129894
130570
  }
129895
- if (!tables.country) {
130571
+ {
129896
130572
  const rows = asRows(sectionMap.get("country")?.countries).map(
129897
130573
  (r) => pickMetricExtras(r, ["countryOrRegion"])
129898
130574
  );
129899
130575
  if (rows.length > 0) tables.country = rows;
129900
130576
  }
129901
- if (!tables.audience) {
130577
+ {
129902
130578
  const rows = asRows(sectionMap.get("audience")?.audiences).map(
129903
130579
  (r) => pickMetricExtras(r, ["age", "gender"])
129904
130580
  );
129905
130581
  if (rows.length > 0) tables.audience = rows;
129906
130582
  }
129907
- if (!tables.creative) {
130583
+ {
129908
130584
  const rows = asRows(sectionMap.get("creative")?.creatives).map(
129909
130585
  (r) => pickMetricExtras(r, ["adId", "adName", "creativeType", "title"])
129910
130586
  );
129911
130587
  if (rows.length > 0) tables.creative = rows;
129912
130588
  }
129913
- if (!tables.material) {
130589
+ {
129914
130590
  const mat = sectionMap.get("material");
129915
130591
  const rows = asRows(mat?.materials).map(
129916
130592
  (r) => pickMetricExtras(r, ["materialId", "materialName", "materialType"])
129917
130593
  );
129918
130594
  if (rows.length > 0) {
129919
130595
  tables.material = rows;
129920
- if (tables.materialMeta == null && mat) {
130596
+ if (mat) {
129921
130597
  tables.materialMeta = {
129922
130598
  dataSource: mat.dataSource,
129923
130599
  message: mat.message
@@ -130220,7 +130896,7 @@ function resolveSectionList2(sections, exclude) {
130220
130896
  }
130221
130897
 
130222
130898
  // src/commands/facebook-analysis/run-batch.ts
130223
- async function runWithConcurrency2(tasks, concurrency) {
130899
+ async function runWithConcurrency3(tasks, concurrency) {
130224
130900
  const results = new Array(tasks.length);
130225
130901
  let cursor = 0;
130226
130902
  const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
@@ -130284,7 +130960,7 @@ async function runAllFacebookSections(opts) {
130284
130960
  }
130285
130961
  }
130286
130962
  );
130287
- const results = await runWithConcurrency2(tasks, concurrency);
130963
+ const results = await runWithConcurrency3(tasks, concurrency);
130288
130964
  const succeeded = results.filter((r) => r.ok).length;
130289
130965
  const failed = results.length - succeeded;
130290
130966
  const absoluteSnapshotDir = path35.resolve(opts.jsonOut);
@@ -130796,7 +131472,7 @@ init_auth();
130796
131472
  import * as path36 from "path";
130797
131473
  import { performance as performance6 } from "perf_hooks";
130798
131474
  init_version();
130799
- async function runWithConcurrency3(tasks, concurrency) {
131475
+ async function runWithConcurrency4(tasks, concurrency) {
130800
131476
  const results = new Array(tasks.length);
130801
131477
  let cursor = 0;
130802
131478
  const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
@@ -130864,7 +131540,7 @@ async function runAllBingSections(opts) {
130864
131540
  };
130865
131541
  }
130866
131542
  });
130867
- const results = await runWithConcurrency3(tasks, concurrency);
131543
+ const results = await runWithConcurrency4(tasks, concurrency);
130868
131544
  const succeeded = results.filter((r) => r.ok).length;
130869
131545
  const failed = results.length - succeeded;
130870
131546
  const absoluteSnapshotDir = path36.resolve(opts.jsonOut);
@@ -131128,25 +131804,27 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
131128
131804
  costPerConversion: num8(currentPeriod.costPerConversion)
131129
131805
  } : null);
131130
131806
  if (currentKpiSource) {
131131
- if (kpis.spend == null) kpis.spend = currentKpiSource.spend;
131132
- if (kpis.impressions == null) kpis.impressions = currentKpiSource.impressions;
131133
- if (kpis.clicks == null) kpis.clicks = currentKpiSource.clicks;
131134
- if (kpis.conversions == null) kpis.conversions = currentKpiSource.conversions;
131135
- if (kpis.ctr == null) kpis.ctr = currentKpiSource.ctr;
131136
- if (kpis.averageCpc == null) kpis.averageCpc = currentKpiSource.averageCpc;
131137
- if (kpis.costPerConversion == null) kpis.costPerConversion = currentKpiSource.costPerConversion;
131138
- }
131139
- if (kpis.averageDailyCost == null && overview) {
131140
- kpis.averageDailyCost = num8(overview.averageDailyCost);
131141
- }
131142
- if (kpis.balance == null && overview) kpis.balance = num8(overview.balance);
131143
- if (kpis.remainingAccountBudget == null && overview) {
131144
- kpis.remainingAccountBudget = num8(overview.remainingAccountBudget);
131145
- }
131146
- if (kpis.optimizationScore == null && overview) {
131147
- kpis.optimizationScore = num8(overview.optimizationScore);
131148
- }
131149
- if (!kpis.previousPeriod && previousPeriod) {
131807
+ if (currentKpiSource.spend != null) kpis.spend = currentKpiSource.spend;
131808
+ if (currentKpiSource.impressions != null) kpis.impressions = currentKpiSource.impressions;
131809
+ if (currentKpiSource.clicks != null) kpis.clicks = currentKpiSource.clicks;
131810
+ if (currentKpiSource.conversions != null) kpis.conversions = currentKpiSource.conversions;
131811
+ if (currentKpiSource.ctr != null) kpis.ctr = currentKpiSource.ctr;
131812
+ if (currentKpiSource.averageCpc != null) kpis.averageCpc = currentKpiSource.averageCpc;
131813
+ if (currentKpiSource.costPerConversion != null) {
131814
+ kpis.costPerConversion = currentKpiSource.costPerConversion;
131815
+ }
131816
+ }
131817
+ if (overview) {
131818
+ const avgDaily = num8(overview.averageDailyCost);
131819
+ if (avgDaily != null) kpis.averageDailyCost = avgDaily;
131820
+ const bal = num8(overview.balance);
131821
+ if (bal != null) kpis.balance = bal;
131822
+ const rem = num8(overview.remainingAccountBudget);
131823
+ if (rem != null) kpis.remainingAccountBudget = rem;
131824
+ const opt = num8(overview.optimizationScore);
131825
+ if (opt != null) kpis.optimizationScore = opt;
131826
+ }
131827
+ if (previousPeriod) {
131150
131828
  kpis.previousPeriod = {
131151
131829
  spend: num8(previousPeriod.spend),
131152
131830
  impressions: num8(previousPeriod.impressions),
@@ -131158,35 +131836,33 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
131158
131836
  };
131159
131837
  }
131160
131838
  const tables = { ...payload.tables ?? {} };
131161
- if (!tables.devices) {
131839
+ {
131162
131840
  const rows = mapDeviceRows(sectionMap.get("device"));
131163
131841
  if (rows.length > 0) tables.devices = rows;
131164
131842
  }
131165
- if (!tables.geographic) {
131843
+ {
131166
131844
  const rows = mapGeographicRows(sectionMap.get("geographic"));
131167
131845
  if (rows.length > 0) tables.geographic = rows;
131168
131846
  }
131169
131847
  const audienceMapped = mapAudienceRows(sectionMap.get("audience-merged"));
131170
- if (!tables.audienceAge && audienceMapped.audienceAge.length > 0) {
131171
- tables.audienceAge = audienceMapped.audienceAge;
131172
- }
131173
- if (!tables.audienceGender && audienceMapped.audienceGender.length > 0) {
131848
+ if (audienceMapped.audienceAge.length > 0) tables.audienceAge = audienceMapped.audienceAge;
131849
+ if (audienceMapped.audienceGender.length > 0) {
131174
131850
  tables.audienceGender = audienceMapped.audienceGender;
131175
131851
  }
131176
- if (!tables.campaigns && campaignRows2.length > 0) tables.campaigns = campaignRows2;
131177
- if (!tables.adGroups) {
131852
+ if (campaignRows2.length > 0) tables.campaigns = campaignRows2;
131853
+ {
131178
131854
  const rows = mapAdGroupRows(sectionMap.get("ad-groups"));
131179
131855
  if (rows.length > 0) tables.adGroups = rows;
131180
131856
  }
131181
- if (!tables.ads) {
131857
+ {
131182
131858
  const rows = mapAdRows(sectionMap.get("ads"));
131183
131859
  if (rows.length > 0) tables.ads = rows;
131184
131860
  }
131185
- if (!tables.keywords) {
131861
+ {
131186
131862
  const rows = mapKeywordRows(sectionMap.get("keywords"));
131187
131863
  if (rows.length > 0) tables.keywords = rows;
131188
131864
  }
131189
- if (!tables.searchTerms) {
131865
+ {
131190
131866
  const rows = mapSearchTermRows(sectionMap.get("search-terms"));
131191
131867
  if (rows.length > 0) tables.searchTerms = rows;
131192
131868
  }
@@ -131666,7 +132342,7 @@ init_auth();
131666
132342
  import * as path39 from "path";
131667
132343
  import { performance as performance7 } from "perf_hooks";
131668
132344
  init_version();
131669
- async function runWithConcurrency4(tasks, concurrency) {
132345
+ async function runWithConcurrency5(tasks, concurrency) {
131670
132346
  const results = new Array(tasks.length);
131671
132347
  let cursor = 0;
131672
132348
  const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
@@ -131735,7 +132411,7 @@ async function runAllTikTokSections(opts) {
131735
132411
  }
131736
132412
  }
131737
132413
  );
131738
- const results = await runWithConcurrency4(tasks, concurrency);
132414
+ const results = await runWithConcurrency5(tasks, concurrency);
131739
132415
  const succeeded = results.filter((r) => r.ok).length;
131740
132416
  const failed = results.length - succeeded;
131741
132417
  const absoluteSnapshotDir = path39.resolve(opts.jsonOut);
@@ -131963,15 +132639,17 @@ async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {
131963
132639
  costPerConversion: num9(currentPeriod.costPerConversion)
131964
132640
  } : null);
131965
132641
  if (currentKpiSource) {
131966
- if (kpis.spend == null) kpis.spend = currentKpiSource.spend;
131967
- if (kpis.impressions == null) kpis.impressions = currentKpiSource.impressions;
131968
- if (kpis.clicks == null) kpis.clicks = currentKpiSource.clicks;
131969
- if (kpis.conversions == null) kpis.conversions = currentKpiSource.conversions;
131970
- if (kpis.ctr == null) kpis.ctr = currentKpiSource.ctr;
131971
- if (kpis.averageCpc == null) kpis.averageCpc = currentKpiSource.averageCpc;
131972
- if (kpis.costPerConversion == null) kpis.costPerConversion = currentKpiSource.costPerConversion;
131973
- }
131974
- if (!kpis.previousPeriod && previousPeriod) {
132642
+ if (currentKpiSource.spend != null) kpis.spend = currentKpiSource.spend;
132643
+ if (currentKpiSource.impressions != null) kpis.impressions = currentKpiSource.impressions;
132644
+ if (currentKpiSource.clicks != null) kpis.clicks = currentKpiSource.clicks;
132645
+ if (currentKpiSource.conversions != null) kpis.conversions = currentKpiSource.conversions;
132646
+ if (currentKpiSource.ctr != null) kpis.ctr = currentKpiSource.ctr;
132647
+ if (currentKpiSource.averageCpc != null) kpis.averageCpc = currentKpiSource.averageCpc;
132648
+ if (currentKpiSource.costPerConversion != null) {
132649
+ kpis.costPerConversion = currentKpiSource.costPerConversion;
132650
+ }
132651
+ }
132652
+ if (previousPeriod) {
131975
132653
  kpis.previousPeriod = {
131976
132654
  spend: num9(previousPeriod.spend),
131977
132655
  impressions: num9(previousPeriod.impressions),
@@ -131983,29 +132661,29 @@ async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {
131983
132661
  };
131984
132662
  }
131985
132663
  const tables = { ...payload.tables ?? {} };
131986
- if (!tables.campaigns && camps.length > 0) tables.campaigns = mapCampaignTable(camps);
131987
- if (!tables.adGroups) {
132664
+ if (camps.length > 0) tables.campaigns = mapCampaignTable(camps);
132665
+ {
131988
132666
  const rows = mapAdGroupTable(sectionMap.get("ad-groups"));
131989
132667
  if (rows.length > 0) tables.adGroups = rows;
131990
132668
  }
131991
- if (!tables.ads) {
132669
+ {
131992
132670
  const rows = mapAdTable(sectionMap.get("ads"));
131993
132671
  if (rows.length > 0) tables.ads = rows;
131994
132672
  }
131995
- if (!tables.videos) {
132673
+ {
131996
132674
  const rows = mapVideoTable(sectionMap.get("videos"));
131997
132675
  if (rows.length > 0) tables.videos = rows;
131998
132676
  }
131999
132677
  const fromMerged = mapAudienceMerged(sectionMap.get("audience-merged"));
132000
- if (!tables.audienceGender) {
132678
+ {
132001
132679
  const rows = fromMerged.audienceGender.length > 0 ? fromMerged.audienceGender : mapAudienceList(sectionMap.get("audience-gender"), "gender", "gender");
132002
132680
  if (rows.length > 0) tables.audienceGender = rows;
132003
132681
  }
132004
- if (!tables.audienceAge) {
132682
+ {
132005
132683
  const rows = fromMerged.audienceAge.length > 0 ? fromMerged.audienceAge : mapAudienceList(sectionMap.get("audience-age"), "age", "ageRange");
132006
132684
  if (rows.length > 0) tables.audienceAge = rows;
132007
132685
  }
132008
- if (!tables.audienceInterest) {
132686
+ {
132009
132687
  const rows = fromMerged.audienceInterest.length > 0 ? fromMerged.audienceInterest : mapAudienceList(
132010
132688
  sectionMap.get("audience-interest-category"),
132011
132689
  "interest_category",
@@ -132332,11 +133010,29 @@ function jsonOutMissingPath(args) {
132332
133010
  }
132333
133011
  return false;
132334
133012
  }
133013
+ function adSubcommandHasMediaFlag(args) {
133014
+ if (args[0] !== "ad") return false;
133015
+ const sub = args[1];
133016
+ if (!sub || sub.startsWith("-")) return false;
133017
+ for (let i = 2; i < args.length; i++) {
133018
+ const a = args[i];
133019
+ if (a === "-m" || a === "--media" || a.startsWith("--media=")) return true;
133020
+ }
133021
+ return false;
133022
+ }
133023
+ function hasDateFromTo(args) {
133024
+ return args.some(
133025
+ (a) => a === "--date-from" || a === "--date-to" || a.startsWith("--date-from=") || a.startsWith("--date-to=")
133026
+ );
133027
+ }
132335
133028
  function agentCompatFatalMessage(argv) {
132336
133029
  const args = argv.slice(2);
132337
133030
  if (args.includes("--period") || args.some((a) => a.startsWith("--period="))) {
132338
133031
  return "\n\u274C \u6CA1\u6709 --period \u9009\u9879\u3002\u8BF7\u7528 --start/--end\uFF08\u6216\u517C\u5BB9\u522B\u540D --start-date/--end-date\uFF09\u6307\u5B9A YYYY-MM-DD \u533A\u95F4\u3002\n";
132339
133032
  }
133033
+ if (hasDateFromTo(args)) {
133034
+ return "\n\u274C \u6CA1\u6709 `--date-from` / `--date-to`\u3002\u8BF7\u7528 `--start` / `--end`\uFF08YYYY-MM-DD\uFF09\u3002\n \u5355\u65E5\u9884\u7B97/\u7A7A\u8017\u7194\u65AD\u8BF7\u7528\uFF1Asiluzan-tso guard budget-circuit|zero-conv -m Google --date <\u5F53\u65E5> --json-out <dir>\n";
133035
+ }
132340
133036
  if (jsonOutMissingPath(args)) {
132341
133037
  return "\n\u274C `--json-out` \u5FC5\u987B\u5E26\u8DEF\u5F84\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\uFF09\uFF0CJSON \u843D\u76D8\u800C\u975E\u6253\u5370\u5230 stdout\uFF0C\u4E0D\u80FD\u7BA1\u9053\u7ED9 python/jq\u3002\n \u793A\u4F8B\uFF1Asiluzan-tso ad campaigns -a <id> --json-out ./snap\n \u7136\u540E\u7528 node/python \u8BFB ./snap \u4E0B writtenFiles\uFF08\u89C1 stdout \u6458\u8981\u7684 absoluteSnapshotDir\uFF09\u3002\n";
132342
133038
  }
@@ -132344,6 +133040,12 @@ function agentCompatFatalMessage(argv) {
132344
133040
  if (cmd === "google-ads") {
132345
133041
  return "\n\u274C \u6CA1\u6709 `google-ads` \u547D\u540D\u7A7A\u95F4\u3002\u8BF7\u6539\u7528\uFF1A\n - \u8D26\u6237\u5206\u6790\u62C9\u6570\uFF1Asiluzan-tso google-analysis \u2026\n - \u5E7F\u544A\u7CFB\u5217/\u7EC4/\u521B\u610F/\u5173\u952E\u8BCD\uFF1Asiluzan-tso ad campaigns|groups|list|keywords \u2026\n \uFF08\u6CA1\u6709 campaign-list\uFF1B\u5217\u8868\u547D\u4EE4\u662F campaigns / groups / list / keywords\uFF09\n - \u5E7F\u544A\u8BCA\u65AD\u62A5\u544A\uFF1Asiluzan-tso google-ads-diagnosis \u2026\n";
132346
133042
  }
133043
+ if (cmd === "adgroups" || cmd === "ad" && args[1] === "adgroups") {
133044
+ return "\n\u274C \u6CA1\u6709 `adgroups` \u547D\u4EE4\u3002\u8BF7\u7528\uFF1Asiluzan-tso ad groups -a <mediaCustomerId> --json-out <dir>\n";
133045
+ }
133046
+ if (adSubcommandHasMediaFlag(args)) {
133047
+ return "\n\u274C `ad \u2026` \u5B50\u547D\u4EE4\u6CA1\u6709 `-m/--media`\uFF0C\u8D26\u6237\u7528 `-a/--account <mediaCustomerId>`\u3002\n \u5355\u6237\uFF1Asiluzan-tso ad campaigns -a <id> --start <\u65E5> --end <\u65E5> --json-out ./snap\n \u5168\u6237\u5F53\u65E5\u8D85\u9884\u7B97/\u7A7A\u8017\u7194\u65AD\uFF1Asiluzan-tso guard budget-circuit|zero-conv -m Google --json-out ./snap\n";
133048
+ }
132347
133049
  if (cmd === "list" && (args.length === 1 || args[1]?.startsWith("-"))) {
132348
133050
  return "\n\u274C \u6CA1\u6709\u9876\u5C42 `list` \u547D\u4EE4\u3002\u67E5\u8BE2\u5E7F\u544A\u8D26\u6237\u5217\u8868\u8BF7\u7528\uFF1A\n siluzan-tso list-accounts [-m Google] [-k <id\u6216\u5173\u952E\u5B57>] --json-out <dir>\n";
132349
133051
  }
@@ -132415,12 +133117,12 @@ var REGISTRARS = [
132415
133117
  register11,
132416
133118
  register12,
132417
133119
  register13,
133120
+ register14,
132418
133121
  registerGoogleAnalysisCommands,
132419
133122
  registerGoogleAnalysisBatchCommands,
132420
133123
  registerFacebookAnalysisCommands,
132421
133124
  registerBingAnalysisCommands,
132422
133125
  registerTikTokAnalysisCommands,
132423
- register14,
132424
133126
  register15,
132425
133127
  register16,
132426
133128
  register17,
@@ -132432,6 +133134,7 @@ var REGISTRARS = [
132432
133134
  register23,
132433
133135
  register24,
132434
133136
  register25,
133137
+ register26,
132435
133138
  registerWebsiteDiagnosisCommands,
132436
133139
  registerMarketAnalysisCommands,
132437
133140
  registerGoogleAdsDiagnosisCommands