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

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 (40) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +926 -194
  3. package/dist/skill/SKILL.md +4 -3
  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/analytics/website-diagnosis-guide.md +2 -0
  13. package/dist/skill/references/core/agent-conventions.md +5 -3
  14. package/dist/skill/references/core/intent-routing.md +10 -7
  15. package/dist/skill/references/core/playbooks.md +2 -1
  16. package/dist/skill/references/core/setup.md +5 -5
  17. package/dist/skill/references/core/workflows.md +7 -6
  18. package/dist/skill/references/misc/tso-home.md +2 -2
  19. package/dist/skill/references/operations/forewarning.md +2 -0
  20. package/dist/skill/references/operations/guard.md +94 -0
  21. package/dist/skill/references/operations/hosted-automation-self-control.md +35 -15
  22. package/dist/skill/references/operations/hosted-automation-user-catalog.md +5 -5
  23. package/dist/skill/references/report-templates/bing-period-report.md +3 -3
  24. package/dist/skill/references/report-templates/google-period-report.md +1 -1
  25. package/dist/skill/references/report-templates/meta-account-diagnosis-report.md +1 -1
  26. package/dist/skill/references/report-templates/meta-period-report.md +2 -2
  27. package/dist/skill/references/report-templates/tiktok-period-report.md +1 -1
  28. package/dist/skill/report-templates/bing-period-report.md +3 -3
  29. package/dist/skill/report-templates/google-period-report.md +1 -1
  30. package/dist/skill/report-templates/meta-account-diagnosis-report.md +1 -1
  31. package/dist/skill/report-templates/meta-period-report.md +2 -2
  32. package/dist/skill/report-templates/tiktok-period-report.md +1 -1
  33. package/dist/skill/scripts/install.ps1 +3 -3
  34. package/dist/skill/scripts/install.sh +3 -3
  35. package/eval/cases/guard-budget-circuit-all-google.scenario.json +25 -0
  36. package/eval/cases/guard-zero-conv-empty-spend.scenario.json +21 -0
  37. package/eval/cases/ir-p8-meets-google-ads-requirements-with-url.scenario.json +39 -0
  38. package/eval/stub-fixtures/guard-budget-circuit.json +36 -0
  39. package/eval/stub-fixtures/guard-zero-conv.json +41 -0
  40. 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>",
@@ -109720,13 +110463,31 @@ function register17(program2) {
109720
110463
  }
109721
110464
 
109722
110465
  // src/commands/forewarning/_shared.ts
110466
+ init_dist();
109723
110467
  var VALID_MEDIA_TYPES7 = ["Google", "TikTok"];
110468
+ function skipForewarningInAgentEnv(commandLabel = "siluzan-tso forewarning") {
110469
+ if (!isSiluzanAgentEnv()) return false;
110470
+ console.log(
110471
+ `
110472
+ \u2139\uFE0F \u68C0\u6D4B\u5230 Siluzan Agent \u73AF\u5883\uFF08IS_SILUZAN_AGENT_ENV=true\uFF09\u3002
110473
+ \`${commandLabel}\` \u5728 Agent \u6C99\u7BB1\u4E2D\u4E0D\u53EF\u7528\uFF0C\u5DF2\u8DF3\u8FC7\uFF08\u4E0D\u6267\u884C\u4EFB\u4F55\u64CD\u4F5C\uFF09\u3002
110474
+
110475
+ \u8BF7\u6539\u4E3A\uFF1A\u521B\u5EFA\u81EA\u52A8\u5316\u4EFB\u52A1\uFF0C\u5728\u4EFB\u52A1\u4E2D\u8C03\u7528 siluzan-tso \u672C Skill
110476
+ \uFF08\u62C9\u6570 / guard / \u5199\u547D\u4EE4\u7B49\uFF09\u5B8C\u6210\u7528\u6237\u7684\u9884\u8B66\u4E0E\u5DE1\u68C0\u9700\u6C42\u3002
110477
+
110478
+ \u5FC5\u8BFB\uFF1Areferences/operations/hosted-automation-user-catalog.md
110479
+ \uFF08\u9884\u7B97\u7194\u65AD / \u5F02\u5E38\u76D1\u63A7 / \u81EA\u52A8\u4F18\u5316\uFF1B\u8868\u5185\u6BCF\u884C\u53EA Read \u4E00\u4E2A SOP\uFF09
110480
+ `
110481
+ );
110482
+ return true;
110483
+ }
109724
110484
 
109725
110485
  // src/commands/forewarning/list.ts
109726
110486
  init_auth();
109727
110487
  init_cli_json_snapshot();
109728
110488
  init_cli_table();
109729
110489
  async function runForewarningList(opts) {
110490
+ if (skipForewarningInAgentEnv("siluzan-tso forewarning list")) return;
109730
110491
  if (!VALID_MEDIA_TYPES7.includes(opts.media)) {
109731
110492
  console.error(
109732
110493
  `
@@ -109809,6 +110570,7 @@ init_auth();
109809
110570
  init_cli_json_snapshot();
109810
110571
  init_cli_table();
109811
110572
  async function runForewarningRecords(opts) {
110573
+ if (skipForewarningInAgentEnv("siluzan-tso forewarning records")) return;
109812
110574
  if (!VALID_MEDIA_TYPES7.includes(opts.media)) {
109813
110575
  console.error(`
109814
110576
  \u274C \u4E0D\u652F\u6301\u7684\u5A92\u4F53\u7C7B\u578B\uFF1A${opts.media}
@@ -109884,6 +110646,7 @@ async function runForewarningRecords(opts) {
109884
110646
  // src/commands/forewarning/actions.ts
109885
110647
  init_auth();
109886
110648
  async function runForewarningStart(opts) {
110649
+ if (skipForewarningInAgentEnv("siluzan-tso forewarning start")) return;
109887
110650
  const config = loadConfig(opts.token);
109888
110651
  const url = `${config.apiBaseUrl}/command/smart-strategy/settings/${opts.media}/${opts.id}/start`;
109889
110652
  try {
@@ -109897,6 +110660,7 @@ async function runForewarningStart(opts) {
109897
110660
  console.log("\n\u2705 \u9884\u8B66\u89C4\u5219\u5DF2\u542F\u52A8\n");
109898
110661
  }
109899
110662
  async function runForewarningStop(opts) {
110663
+ if (skipForewarningInAgentEnv("siluzan-tso forewarning stop")) return;
109900
110664
  const config = loadConfig(opts.token);
109901
110665
  const url = `${config.apiBaseUrl}/command/smart-strategy/settings/${opts.media}/${opts.id}/stop`;
109902
110666
  try {
@@ -109910,6 +110674,7 @@ async function runForewarningStop(opts) {
109910
110674
  console.log("\n\u2705 \u9884\u8B66\u89C4\u5219\u5DF2\u505C\u6B62\n");
109911
110675
  }
109912
110676
  async function runForewarningDelete(opts) {
110677
+ if (skipForewarningInAgentEnv("siluzan-tso forewarning delete")) return;
109913
110678
  const config = loadConfig(opts.token);
109914
110679
  const url = `${config.apiBaseUrl}/command/smart-strategy/settings/${opts.media}/${opts.id}`;
109915
110680
  try {
@@ -109923,6 +110688,7 @@ async function runForewarningDelete(opts) {
109923
110688
  console.log("\n\u2705 \u9884\u8B66\u89C4\u5219\u5DF2\u5220\u9664\n");
109924
110689
  }
109925
110690
  async function runForewarningGet(opts) {
110691
+ if (skipForewarningInAgentEnv("siluzan-tso forewarning get")) return;
109926
110692
  const config = loadConfig(opts.token);
109927
110693
  const url = `${config.apiBaseUrl}/query/smart-strategy/settings/${opts.media}/${opts.id}`;
109928
110694
  let data;
@@ -109946,6 +110712,7 @@ import QRCode from "qrcode";
109946
110712
  import open2 from "open";
109947
110713
  init_cli_table();
109948
110714
  async function runForewarningNotifyAccounts(opts) {
110715
+ if (skipForewarningInAgentEnv("siluzan-tso forewarning notify-accounts")) return;
109949
110716
  const config = loadConfig(opts.token);
109950
110717
  const listUrl = `${config.apiBaseUrl}/query/notice-account/getAccount`;
109951
110718
  let accounts;
@@ -110066,6 +110833,11 @@ function buildRuleBody(opts) {
110066
110833
  };
110067
110834
  }
110068
110835
  async function runForewarningCreate(opts) {
110836
+ if (skipForewarningInAgentEnv(
110837
+ opts.id ? "siluzan-tso forewarning update" : "siluzan-tso forewarning create"
110838
+ )) {
110839
+ return;
110840
+ }
110069
110841
  if (!VALID_MEDIA_TYPES7.includes(opts.media)) {
110070
110842
  console.error(
110071
110843
  `
@@ -110106,7 +110878,7 @@ async function runForewarningCreate(opts) {
110106
110878
  }
110107
110879
 
110108
110880
  // src/commands/forewarning/_register.ts
110109
- function register18(program2) {
110881
+ function register19(program2) {
110110
110882
  const forewarningCmd = program2.command("forewarning").description("\u667A\u80FD\u9884\u8B66\u89C4\u5219\u4E0E\u9884\u8B66\u8BB0\u5F55\u7BA1\u7406");
110111
110883
  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
110884
  "--json-out <path>",
@@ -110398,7 +111170,7 @@ Meta \u7EBF\u7D22\u8868\u5355\uFF08\u8D26\u6237 ${opts.account}\uFF0C\u7B2C 1 \u
110398
111170
  console.log();
110399
111171
  }
110400
111172
  }
110401
- function register19(program2) {
111173
+ function register20(program2) {
110402
111174
  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
111175
  "--json-out <path>",
110404
111176
  "\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 +111189,6 @@ function register19(program2) {
110417
111189
  });
110418
111190
  }
110419
111191
 
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
111192
  // src/commands/ad/campaign.ts
110495
111193
  init_auth();
110496
111194
  init_cli_json_snapshot();
@@ -119638,7 +120336,7 @@ function parseOptionalMatchType(raw) {
119638
120336
  `);
119639
120337
  process.exit(1);
119640
120338
  }
119641
- function register20(program2) {
120339
+ function register21(program2) {
119642
120340
  const adCmd = program2.command("ad").description(
119643
120341
  "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
120342
  );
@@ -121311,7 +122009,7 @@ function attachBaseCliOptions(cmd) {
121311
122009
  function splitKeywordSeeds(raw) {
121312
122010
  return raw.split(",").map((s) => s.trim()).filter(Boolean);
121313
122011
  }
121314
- function register21(program2) {
122012
+ function register22(program2) {
121315
122013
  const keywordCmd = program2.command("keyword").description(
121316
122014
  "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
122015
  );
@@ -121780,7 +122478,7 @@ async function runRagList(options) {
121780
122478
  ];
121781
122479
  console.log(lines.join("\n"));
121782
122480
  }
121783
- function register22(program2) {
122481
+ function register23(program2) {
121784
122482
  const ragCmd = program2.command("rag").description(
121785
122483
  "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
122484
  );
@@ -123231,7 +123929,7 @@ async function runAccountBmBind(opts) {
123231
123929
  }
123232
123930
 
123233
123931
  // src/commands/account-manage-register.ts
123234
- function register23(program2) {
123932
+ function register24(program2) {
123235
123933
  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
123934
  accountCmd.command("list", { hidden: true }).description("\uFF08\u517C\u5BB9\u522B\u540D\uFF09\u540C list-accounts").option(
123237
123935
  "-m, --media <type>",
@@ -125061,7 +125759,7 @@ async function runOpenAccountMeta(opts) {
125061
125759
  }
125062
125760
 
125063
125761
  // src/commands/open-account-register.ts
125064
- function register24(program2) {
125762
+ function register25(program2) {
125065
125763
  const openAccountCmd = program2.command("open-account").description("\u5F00\u6237\u7533\u8BF7\uFF08Google / TikTok / Yandex / Bing / Kwai / Meta \u5F00\u6237\u94FE\u63A5\uFF09");
125066
125764
  openAccountCmd.command("meta").description(
125067
125765
  "\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 +125983,7 @@ function register24(program2) {
125285
125983
  }
125286
125984
 
125287
125985
  // src/commands/diagnostic.ts
125288
- function register25(program2) {
125986
+ function register26(program2) {
125289
125987
  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
125988
  console.log("\n\u8BCA\u65AD\u80FD\u529B\u8BF4\u660E\n");
125291
125989
  console.log(" \u3010Google \u5E7F\u544A\u8D26\u6237\u8BCA\u65AD\u3011");
@@ -129055,12 +129753,12 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
129055
129753
  const accountId = manifest.accountId ?? (typeof overview?.accountId === "string" ? overview.accountId : void 0);
129056
129754
  const kpis = { ...payload.kpis ?? {} };
129057
129755
  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;
129756
+ if (current.spend != null) kpis.spend = current.spend;
129757
+ if (current.results != null) kpis.results = current.results;
129758
+ if (current.costPerResult != null) kpis.costPerResult = current.costPerResult;
129759
+ if (current.reach != null) kpis.reach = current.reach;
129760
+ if (current.impressions != null) kpis.impressions = current.impressions;
129761
+ if (current.frequency != null) kpis.frequency = current.frequency;
129064
129762
  }
129065
129763
  const meta = { ...payload.meta ?? {} };
129066
129764
  if (!meta.accountName && accountName) meta.accountName = accountName;
@@ -129073,12 +129771,12 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
129073
129771
  if (!meta.resultType && current?.resultType) meta.resultType = String(current.resultType);
129074
129772
  const charts = { ...payload.charts ?? {} };
129075
129773
  const avgCpl = num5(kpis.costPerResult) ?? 0;
129076
- if (!charts.platform) {
129774
+ {
129077
129775
  const platform = sectionMap.get("platform");
129078
129776
  const networks = Array.isArray(platform?.networks) ? platform.networks : [];
129079
129777
  if (networks.length) charts.platform = aggregatePlatform(networks);
129080
129778
  }
129081
- if (!charts.country) {
129779
+ {
129082
129780
  const country = sectionMap.get("country");
129083
129781
  const countries = Array.isArray(country?.countries) ? country.countries : [];
129084
129782
  if (countries.length) {
@@ -129089,7 +129787,7 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
129089
129787
  };
129090
129788
  }
129091
129789
  }
129092
- if (!charts.audience) {
129790
+ {
129093
129791
  const audience = sectionMap.get("audience");
129094
129792
  const audiences = Array.isArray(audience?.audiences) ? audience.audiences : [];
129095
129793
  if (audiences.length) {
@@ -129101,11 +129799,11 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
129101
129799
  };
129102
129800
  }
129103
129801
  }
129104
- if (!charts.funnel && num5(kpis.reach) != null) {
129802
+ if (num5(kpis.reach) != null) {
129105
129803
  charts.funnel = { reach: kpis.reach, results: kpis.results ?? 0 };
129106
129804
  }
129107
129805
  const tables = { ...payload.tables ?? {} };
129108
- if (!tables.adSets) {
129806
+ {
129109
129807
  const adSets = sectionMap.get("ad-sets");
129110
129808
  const groups = Array.isArray(adSets?.adGroups) ? adSets.adGroups : [];
129111
129809
  if (groups.length) {
@@ -129128,15 +129826,13 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
129128
129826
  }).sort((a, b) => b.spend - a.spend);
129129
129827
  }
129130
129828
  }
129131
- if (!tables.audienceTop || !tables.audienceBottom) {
129829
+ {
129132
129830
  const audience = sectionMap.get("audience");
129133
129831
  const audiences = Array.isArray(audience?.audiences) ? audience.audiences : [];
129134
129832
  if (audiences.length) {
129135
129833
  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;
129834
+ tables.audienceTop = rows.slice(0, 10);
129835
+ tables.audienceBottom = [...rows].reverse().slice(0, 10);
129140
129836
  }
129141
129837
  }
129142
129838
  const currency = typeof meta.currency === "string" ? meta.currency : "USD";
@@ -129851,17 +130547,26 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
129851
130547
  if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
129852
130548
  const kpis = { ...payload.kpis ?? {} };
129853
130549
  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) {
130550
+ const spend = num6(currentPeriod.spend);
130551
+ const impressions = num6(currentPeriod.impressions);
130552
+ const clicks = num6(currentPeriod.clicks);
130553
+ const results = num6(currentPeriod.results);
130554
+ const costPerResult = num6(currentPeriod.costPerResult);
130555
+ const ctr = num6(currentPeriod.ctr);
130556
+ const cpc = num6(currentPeriod.cpc ?? currentPeriod.averageCpc);
130557
+ const reach = num6(currentPeriod.reach);
130558
+ const frequency = num6(currentPeriod.frequency);
130559
+ if (spend != null) kpis.spend = spend;
130560
+ if (impressions != null) kpis.impressions = impressions;
130561
+ if (clicks != null) kpis.clicks = clicks;
130562
+ if (results != null) kpis.results = results;
130563
+ if (costPerResult != null) kpis.costPerResult = costPerResult;
130564
+ if (ctr != null) kpis.ctr = ctr;
130565
+ if (cpc != null) kpis.cpc = cpc;
130566
+ if (reach != null) kpis.reach = reach;
130567
+ if (frequency != null) kpis.frequency = frequency;
130568
+ }
130569
+ if (previousPeriod) {
129865
130570
  kpis.previousPeriod = {
129866
130571
  spend: num6(previousPeriod.spend),
129867
130572
  impressions: num6(previousPeriod.impressions),
@@ -129875,7 +130580,7 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
129875
130580
  };
129876
130581
  }
129877
130582
  const tables = { ...payload.tables ?? {} };
129878
- if (!tables.adSets) {
130583
+ {
129879
130584
  const rows = asRows(sectionMap.get("ad-sets")?.adGroups).map(
129880
130585
  (r) => pickMetricExtras(r, [
129881
130586
  "adGroupId",
@@ -129886,38 +130591,38 @@ async function mergeMetaDiagnosisSnapshotIntoReport(payload, snapshotDir) {
129886
130591
  );
129887
130592
  if (rows.length > 0) tables.adSets = rows;
129888
130593
  }
129889
- if (!tables.platform) {
130594
+ {
129890
130595
  const rows = asRows(sectionMap.get("platform")?.networks).map(
129891
130596
  (r) => pickMetricExtras(r, ["publisherPlatform", "platformPosition", "network"])
129892
130597
  );
129893
130598
  if (rows.length > 0) tables.platform = rows;
129894
130599
  }
129895
- if (!tables.country) {
130600
+ {
129896
130601
  const rows = asRows(sectionMap.get("country")?.countries).map(
129897
130602
  (r) => pickMetricExtras(r, ["countryOrRegion"])
129898
130603
  );
129899
130604
  if (rows.length > 0) tables.country = rows;
129900
130605
  }
129901
- if (!tables.audience) {
130606
+ {
129902
130607
  const rows = asRows(sectionMap.get("audience")?.audiences).map(
129903
130608
  (r) => pickMetricExtras(r, ["age", "gender"])
129904
130609
  );
129905
130610
  if (rows.length > 0) tables.audience = rows;
129906
130611
  }
129907
- if (!tables.creative) {
130612
+ {
129908
130613
  const rows = asRows(sectionMap.get("creative")?.creatives).map(
129909
130614
  (r) => pickMetricExtras(r, ["adId", "adName", "creativeType", "title"])
129910
130615
  );
129911
130616
  if (rows.length > 0) tables.creative = rows;
129912
130617
  }
129913
- if (!tables.material) {
130618
+ {
129914
130619
  const mat = sectionMap.get("material");
129915
130620
  const rows = asRows(mat?.materials).map(
129916
130621
  (r) => pickMetricExtras(r, ["materialId", "materialName", "materialType"])
129917
130622
  );
129918
130623
  if (rows.length > 0) {
129919
130624
  tables.material = rows;
129920
- if (tables.materialMeta == null && mat) {
130625
+ if (mat) {
129921
130626
  tables.materialMeta = {
129922
130627
  dataSource: mat.dataSource,
129923
130628
  message: mat.message
@@ -130220,7 +130925,7 @@ function resolveSectionList2(sections, exclude) {
130220
130925
  }
130221
130926
 
130222
130927
  // src/commands/facebook-analysis/run-batch.ts
130223
- async function runWithConcurrency2(tasks, concurrency) {
130928
+ async function runWithConcurrency3(tasks, concurrency) {
130224
130929
  const results = new Array(tasks.length);
130225
130930
  let cursor = 0;
130226
130931
  const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
@@ -130284,7 +130989,7 @@ async function runAllFacebookSections(opts) {
130284
130989
  }
130285
130990
  }
130286
130991
  );
130287
- const results = await runWithConcurrency2(tasks, concurrency);
130992
+ const results = await runWithConcurrency3(tasks, concurrency);
130288
130993
  const succeeded = results.filter((r) => r.ok).length;
130289
130994
  const failed = results.length - succeeded;
130290
130995
  const absoluteSnapshotDir = path35.resolve(opts.jsonOut);
@@ -130796,7 +131501,7 @@ init_auth();
130796
131501
  import * as path36 from "path";
130797
131502
  import { performance as performance6 } from "perf_hooks";
130798
131503
  init_version();
130799
- async function runWithConcurrency3(tasks, concurrency) {
131504
+ async function runWithConcurrency4(tasks, concurrency) {
130800
131505
  const results = new Array(tasks.length);
130801
131506
  let cursor = 0;
130802
131507
  const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
@@ -130864,7 +131569,7 @@ async function runAllBingSections(opts) {
130864
131569
  };
130865
131570
  }
130866
131571
  });
130867
- const results = await runWithConcurrency3(tasks, concurrency);
131572
+ const results = await runWithConcurrency4(tasks, concurrency);
130868
131573
  const succeeded = results.filter((r) => r.ok).length;
130869
131574
  const failed = results.length - succeeded;
130870
131575
  const absoluteSnapshotDir = path36.resolve(opts.jsonOut);
@@ -131128,25 +131833,27 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
131128
131833
  costPerConversion: num8(currentPeriod.costPerConversion)
131129
131834
  } : null);
131130
131835
  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) {
131836
+ if (currentKpiSource.spend != null) kpis.spend = currentKpiSource.spend;
131837
+ if (currentKpiSource.impressions != null) kpis.impressions = currentKpiSource.impressions;
131838
+ if (currentKpiSource.clicks != null) kpis.clicks = currentKpiSource.clicks;
131839
+ if (currentKpiSource.conversions != null) kpis.conversions = currentKpiSource.conversions;
131840
+ if (currentKpiSource.ctr != null) kpis.ctr = currentKpiSource.ctr;
131841
+ if (currentKpiSource.averageCpc != null) kpis.averageCpc = currentKpiSource.averageCpc;
131842
+ if (currentKpiSource.costPerConversion != null) {
131843
+ kpis.costPerConversion = currentKpiSource.costPerConversion;
131844
+ }
131845
+ }
131846
+ if (overview) {
131847
+ const avgDaily = num8(overview.averageDailyCost);
131848
+ if (avgDaily != null) kpis.averageDailyCost = avgDaily;
131849
+ const bal = num8(overview.balance);
131850
+ if (bal != null) kpis.balance = bal;
131851
+ const rem = num8(overview.remainingAccountBudget);
131852
+ if (rem != null) kpis.remainingAccountBudget = rem;
131853
+ const opt = num8(overview.optimizationScore);
131854
+ if (opt != null) kpis.optimizationScore = opt;
131855
+ }
131856
+ if (previousPeriod) {
131150
131857
  kpis.previousPeriod = {
131151
131858
  spend: num8(previousPeriod.spend),
131152
131859
  impressions: num8(previousPeriod.impressions),
@@ -131158,35 +131865,33 @@ async function mergeBingAnalysisSnapshotIntoReport(payload, snapshotDir) {
131158
131865
  };
131159
131866
  }
131160
131867
  const tables = { ...payload.tables ?? {} };
131161
- if (!tables.devices) {
131868
+ {
131162
131869
  const rows = mapDeviceRows(sectionMap.get("device"));
131163
131870
  if (rows.length > 0) tables.devices = rows;
131164
131871
  }
131165
- if (!tables.geographic) {
131872
+ {
131166
131873
  const rows = mapGeographicRows(sectionMap.get("geographic"));
131167
131874
  if (rows.length > 0) tables.geographic = rows;
131168
131875
  }
131169
131876
  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) {
131877
+ if (audienceMapped.audienceAge.length > 0) tables.audienceAge = audienceMapped.audienceAge;
131878
+ if (audienceMapped.audienceGender.length > 0) {
131174
131879
  tables.audienceGender = audienceMapped.audienceGender;
131175
131880
  }
131176
- if (!tables.campaigns && campaignRows2.length > 0) tables.campaigns = campaignRows2;
131177
- if (!tables.adGroups) {
131881
+ if (campaignRows2.length > 0) tables.campaigns = campaignRows2;
131882
+ {
131178
131883
  const rows = mapAdGroupRows(sectionMap.get("ad-groups"));
131179
131884
  if (rows.length > 0) tables.adGroups = rows;
131180
131885
  }
131181
- if (!tables.ads) {
131886
+ {
131182
131887
  const rows = mapAdRows(sectionMap.get("ads"));
131183
131888
  if (rows.length > 0) tables.ads = rows;
131184
131889
  }
131185
- if (!tables.keywords) {
131890
+ {
131186
131891
  const rows = mapKeywordRows(sectionMap.get("keywords"));
131187
131892
  if (rows.length > 0) tables.keywords = rows;
131188
131893
  }
131189
- if (!tables.searchTerms) {
131894
+ {
131190
131895
  const rows = mapSearchTermRows(sectionMap.get("search-terms"));
131191
131896
  if (rows.length > 0) tables.searchTerms = rows;
131192
131897
  }
@@ -131666,7 +132371,7 @@ init_auth();
131666
132371
  import * as path39 from "path";
131667
132372
  import { performance as performance7 } from "perf_hooks";
131668
132373
  init_version();
131669
- async function runWithConcurrency4(tasks, concurrency) {
132374
+ async function runWithConcurrency5(tasks, concurrency) {
131670
132375
  const results = new Array(tasks.length);
131671
132376
  let cursor = 0;
131672
132377
  const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
@@ -131735,7 +132440,7 @@ async function runAllTikTokSections(opts) {
131735
132440
  }
131736
132441
  }
131737
132442
  );
131738
- const results = await runWithConcurrency4(tasks, concurrency);
132443
+ const results = await runWithConcurrency5(tasks, concurrency);
131739
132444
  const succeeded = results.filter((r) => r.ok).length;
131740
132445
  const failed = results.length - succeeded;
131741
132446
  const absoluteSnapshotDir = path39.resolve(opts.jsonOut);
@@ -131963,15 +132668,17 @@ async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {
131963
132668
  costPerConversion: num9(currentPeriod.costPerConversion)
131964
132669
  } : null);
131965
132670
  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) {
132671
+ if (currentKpiSource.spend != null) kpis.spend = currentKpiSource.spend;
132672
+ if (currentKpiSource.impressions != null) kpis.impressions = currentKpiSource.impressions;
132673
+ if (currentKpiSource.clicks != null) kpis.clicks = currentKpiSource.clicks;
132674
+ if (currentKpiSource.conversions != null) kpis.conversions = currentKpiSource.conversions;
132675
+ if (currentKpiSource.ctr != null) kpis.ctr = currentKpiSource.ctr;
132676
+ if (currentKpiSource.averageCpc != null) kpis.averageCpc = currentKpiSource.averageCpc;
132677
+ if (currentKpiSource.costPerConversion != null) {
132678
+ kpis.costPerConversion = currentKpiSource.costPerConversion;
132679
+ }
132680
+ }
132681
+ if (previousPeriod) {
131975
132682
  kpis.previousPeriod = {
131976
132683
  spend: num9(previousPeriod.spend),
131977
132684
  impressions: num9(previousPeriod.impressions),
@@ -131983,29 +132690,29 @@ async function mergeTikTokAnalysisSnapshotIntoReport(payload, snapshotDir) {
131983
132690
  };
131984
132691
  }
131985
132692
  const tables = { ...payload.tables ?? {} };
131986
- if (!tables.campaigns && camps.length > 0) tables.campaigns = mapCampaignTable(camps);
131987
- if (!tables.adGroups) {
132693
+ if (camps.length > 0) tables.campaigns = mapCampaignTable(camps);
132694
+ {
131988
132695
  const rows = mapAdGroupTable(sectionMap.get("ad-groups"));
131989
132696
  if (rows.length > 0) tables.adGroups = rows;
131990
132697
  }
131991
- if (!tables.ads) {
132698
+ {
131992
132699
  const rows = mapAdTable(sectionMap.get("ads"));
131993
132700
  if (rows.length > 0) tables.ads = rows;
131994
132701
  }
131995
- if (!tables.videos) {
132702
+ {
131996
132703
  const rows = mapVideoTable(sectionMap.get("videos"));
131997
132704
  if (rows.length > 0) tables.videos = rows;
131998
132705
  }
131999
132706
  const fromMerged = mapAudienceMerged(sectionMap.get("audience-merged"));
132000
- if (!tables.audienceGender) {
132707
+ {
132001
132708
  const rows = fromMerged.audienceGender.length > 0 ? fromMerged.audienceGender : mapAudienceList(sectionMap.get("audience-gender"), "gender", "gender");
132002
132709
  if (rows.length > 0) tables.audienceGender = rows;
132003
132710
  }
132004
- if (!tables.audienceAge) {
132711
+ {
132005
132712
  const rows = fromMerged.audienceAge.length > 0 ? fromMerged.audienceAge : mapAudienceList(sectionMap.get("audience-age"), "age", "ageRange");
132006
132713
  if (rows.length > 0) tables.audienceAge = rows;
132007
132714
  }
132008
- if (!tables.audienceInterest) {
132715
+ {
132009
132716
  const rows = fromMerged.audienceInterest.length > 0 ? fromMerged.audienceInterest : mapAudienceList(
132010
132717
  sectionMap.get("audience-interest-category"),
132011
132718
  "interest_category",
@@ -132332,11 +133039,29 @@ function jsonOutMissingPath(args) {
132332
133039
  }
132333
133040
  return false;
132334
133041
  }
133042
+ function adSubcommandHasMediaFlag(args) {
133043
+ if (args[0] !== "ad") return false;
133044
+ const sub = args[1];
133045
+ if (!sub || sub.startsWith("-")) return false;
133046
+ for (let i = 2; i < args.length; i++) {
133047
+ const a = args[i];
133048
+ if (a === "-m" || a === "--media" || a.startsWith("--media=")) return true;
133049
+ }
133050
+ return false;
133051
+ }
133052
+ function hasDateFromTo(args) {
133053
+ return args.some(
133054
+ (a) => a === "--date-from" || a === "--date-to" || a.startsWith("--date-from=") || a.startsWith("--date-to=")
133055
+ );
133056
+ }
132335
133057
  function agentCompatFatalMessage(argv) {
132336
133058
  const args = argv.slice(2);
132337
133059
  if (args.includes("--period") || args.some((a) => a.startsWith("--period="))) {
132338
133060
  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
133061
  }
133062
+ if (hasDateFromTo(args)) {
133063
+ 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";
133064
+ }
132340
133065
  if (jsonOutMissingPath(args)) {
132341
133066
  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
133067
  }
@@ -132344,6 +133069,12 @@ function agentCompatFatalMessage(argv) {
132344
133069
  if (cmd === "google-ads") {
132345
133070
  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
133071
  }
133072
+ if (cmd === "adgroups" || cmd === "ad" && args[1] === "adgroups") {
133073
+ return "\n\u274C \u6CA1\u6709 `adgroups` \u547D\u4EE4\u3002\u8BF7\u7528\uFF1Asiluzan-tso ad groups -a <mediaCustomerId> --json-out <dir>\n";
133074
+ }
133075
+ if (adSubcommandHasMediaFlag(args)) {
133076
+ 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";
133077
+ }
132347
133078
  if (cmd === "list" && (args.length === 1 || args[1]?.startsWith("-"))) {
132348
133079
  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
133080
  }
@@ -132415,12 +133146,12 @@ var REGISTRARS = [
132415
133146
  register11,
132416
133147
  register12,
132417
133148
  register13,
133149
+ register14,
132418
133150
  registerGoogleAnalysisCommands,
132419
133151
  registerGoogleAnalysisBatchCommands,
132420
133152
  registerFacebookAnalysisCommands,
132421
133153
  registerBingAnalysisCommands,
132422
133154
  registerTikTokAnalysisCommands,
132423
- register14,
132424
133155
  register15,
132425
133156
  register16,
132426
133157
  register17,
@@ -132432,6 +133163,7 @@ var REGISTRARS = [
132432
133163
  register23,
132433
133164
  register24,
132434
133165
  register25,
133166
+ register26,
132435
133167
  registerWebsiteDiagnosisCommands,
132436
133168
  registerMarketAnalysisCommands,
132437
133169
  registerGoogleAdsDiagnosisCommands