siluzan-tso-cli 1.1.44 → 1.1.45-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 (30) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +1001 -118
  3. package/dist/skill/SKILL.md +1 -0
  4. package/dist/skill/_meta.json +2 -2
  5. package/dist/skill/references/README.md +1 -0
  6. package/dist/skill/references/accounts/accounts-balance-stats.md +3 -3
  7. package/dist/skill/references/accounts/accounts-list.md +1 -1
  8. package/dist/skill/references/accounts/accounts-permissions.md +2 -2
  9. package/dist/skill/references/accounts/finance.md +1 -1
  10. package/dist/skill/references/accounts/open-account-by-media.md +1 -1
  11. package/dist/skill/references/accounts/open-account-google-ui.md +1 -1
  12. package/dist/skill/references/analytics/account-analytics.md +44 -1
  13. package/dist/skill/references/analytics/rag.md +1 -1
  14. package/dist/skill/references/analytics/reporting.md +5 -5
  15. package/dist/skill/references/core/playbooks.md +1 -1
  16. package/dist/skill/references/core/setup.md +5 -5
  17. package/dist/skill/references/misc/tso-home.md +2 -2
  18. package/dist/skill/references/operations/hosted-automation-monitoring-json.md +1 -1
  19. package/dist/skill/references/report-templates/README.md +2 -0
  20. package/dist/skill/references/report-templates/REPORT-WORKFLOW.md +6 -4
  21. package/dist/skill/references/report-templates/yandex-period-report-excel.md +123 -0
  22. package/dist/skill/references/report-templates/yandex-period-report.md +121 -0
  23. package/dist/skill/report-templates/README.md +2 -0
  24. package/dist/skill/report-templates/REPORT-WORKFLOW.md +6 -4
  25. package/dist/skill/report-templates/yandex-period-report-excel.md +123 -0
  26. package/dist/skill/report-templates/yandex-period-report.html +968 -0
  27. package/dist/skill/report-templates/yandex-period-report.md +121 -0
  28. package/dist/skill/scripts/install.ps1 +3 -3
  29. package/dist/skill/scripts/install.sh +3 -3
  30. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -258,9 +258,9 @@ var require_semver = __commonJS({
258
258
  } else {
259
259
  this.prerelease = m[4].split(".").map((id) => {
260
260
  if (/^[0-9]+$/.test(id)) {
261
- const num10 = +id;
262
- if (num10 >= 0 && num10 < MAX_SAFE_INTEGER) {
263
- return num10;
261
+ const num11 = +id;
262
+ if (num11 >= 0 && num11 < MAX_SAFE_INTEGER) {
263
+ return num11;
264
264
  }
265
265
  }
266
266
  return id;
@@ -4026,7 +4026,7 @@ var DEFAULT_API_BASE;
4026
4026
  var init_defaults = __esm({
4027
4027
  "src/config/defaults.ts"() {
4028
4028
  "use strict";
4029
- DEFAULT_API_BASE = "https://tso-api.siluzan.com";
4029
+ DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
4030
4030
  }
4031
4031
  });
4032
4032
 
@@ -4046,6 +4046,9 @@ function deriveTiktokApiUrl(tsoApiBaseUrl) {
4046
4046
  function deriveFacebookApiUrl(tsoApiBaseUrl) {
4047
4047
  return tsoApiBaseUrl.includes("-ci") ? "https://facebookapi-ci.mysiluzan.com" : "https://facebookapi.mysiluzan.com";
4048
4048
  }
4049
+ function deriveYandexApiUrl(tsoApiBaseUrl) {
4050
+ return tsoApiBaseUrl.includes("-ci") ? "https://yandexapi-ci.mysiluzan.com" : "https://yandexapi.mysiluzan.com";
4051
+ }
4049
4052
  function deriveChatGptApiUrl(tsoApiBaseUrl) {
4050
4053
  return tsoApiBaseUrl.includes("-ci") ? "https://chatgpt-ci.mysiluzan.com" : "https://chatgpt.mysiluzan.com";
4051
4054
  }
@@ -4431,6 +4434,7 @@ function loadConfig(tokenArg) {
4431
4434
  mainApiUrl: deriveMainApiUrl(apiBaseUrl),
4432
4435
  tiktokApiUrl: deriveTiktokApiUrl(apiBaseUrl),
4433
4436
  facebookApiUrl: deriveFacebookApiUrl(apiBaseUrl),
4437
+ yandexApiUrl: process.env.SILUZAN_YANDEX_API ?? deriveYandexApiUrl(apiBaseUrl),
4434
4438
  chatGptApiUrl: deriveChatGptApiUrl(apiBaseUrl),
4435
4439
  csoBaseUrl,
4436
4440
  dataPermission: process.env.SILUZAN_DATA_PERMISSION ?? shared.dataPermission
@@ -5561,8 +5565,8 @@ var init_daily_metrics_range = __esm({
5561
5565
  async function withAsyncLock(key, fn) {
5562
5566
  const prev = chains.get(key) ?? Promise.resolve();
5563
5567
  let release;
5564
- const gate = new Promise((resolve21) => {
5565
- release = resolve21;
5568
+ const gate = new Promise((resolve23) => {
5569
+ release = resolve23;
5566
5570
  });
5567
5571
  chains.set(
5568
5572
  key,
@@ -6151,14 +6155,14 @@ function computeBackoffMs(attempt, policy) {
6151
6155
  return capped + jitter;
6152
6156
  }
6153
6157
  function sleep3(ms, signal) {
6154
- return new Promise((resolve21, reject) => {
6158
+ return new Promise((resolve23, reject) => {
6155
6159
  if (signal?.aborted) {
6156
6160
  reject(new Error("retry sleep aborted"));
6157
6161
  return;
6158
6162
  }
6159
6163
  const timer = setTimeout(() => {
6160
6164
  signal?.removeEventListener("abort", onAbort);
6161
- resolve21();
6165
+ resolve23();
6162
6166
  }, ms);
6163
6167
  const onAbort = () => {
6164
6168
  clearTimeout(timer);
@@ -104361,10 +104365,10 @@ var init_google_analysis3 = __esm({
104361
104365
  // src/index.ts
104362
104366
  init_dist();
104363
104367
  init_dist();
104364
- import * as fs33 from "fs";
104365
- import * as path43 from "path";
104366
- import { fileURLToPath as fileURLToPath11 } from "url";
104367
- import { Command as Command5 } from "commander";
104368
+ import * as fs35 from "fs";
104369
+ import * as path46 from "path";
104370
+ import { fileURLToPath as fileURLToPath12 } from "url";
104371
+ import { Command as Command6 } from "commander";
104368
104372
 
104369
104373
  // src/commands/login/urls.ts
104370
104374
  init_defaults();
@@ -104849,8 +104853,8 @@ function deriveWebUrl(apiBaseUrl) {
104849
104853
  }
104850
104854
  var TSO_UMI_ROUTE_PREFIX = "/v3umijs/tso";
104851
104855
  function buildTsoPageWebUrl(webUrl, tsoSubRoute, extraQuery) {
104852
- const path44 = tsoSubRoute.replace(/^\/+/, "");
104853
- const umiPath = `${TSO_UMI_ROUTE_PREFIX}/${path44}`;
104856
+ const path47 = tsoSubRoute.replace(/^\/+/, "");
104857
+ const umiPath = `${TSO_UMI_ROUTE_PREFIX}/${path47}`;
104854
104858
  const q = new URLSearchParams({ tso: umiPath });
104855
104859
  if (extraQuery) {
104856
104860
  for (const [key, value] of Object.entries(extraQuery)) {
@@ -104858,7 +104862,7 @@ function buildTsoPageWebUrl(webUrl, tsoSubRoute, extraQuery) {
104858
104862
  if (v) q.set(key, v);
104859
104863
  }
104860
104864
  }
104861
- return `${webUrl.replace(/\/+$/, "")}/v3/foreign_trade/tso/${path44}?${q.toString()}`;
104865
+ return `${webUrl.replace(/\/+$/, "")}/v3/foreign_trade/tso/${path47}?${q.toString()}`;
104862
104866
  }
104863
104867
  function cmdConfigShow() {
104864
104868
  const shared = readSharedConfig();
@@ -106984,6 +106988,63 @@ function assessBalanceScanDataHealth(input) {
106984
106988
  return null;
106985
106989
  }
106986
106990
 
106991
+ // src/commands/balance-scan/evaluate.ts
106992
+ function evaluateBalanceScanAccounts(params) {
106993
+ const {
106994
+ items: items2,
106995
+ balanceMap,
106996
+ overviewMap,
106997
+ spendDays,
106998
+ thresholdDays,
106999
+ minBalance,
107000
+ minDailySpend,
107001
+ targetDays,
107002
+ includeInvalidOAuth
107003
+ } = params;
107004
+ const evaluated = [];
107005
+ for (const item of items2) {
107006
+ const ma = item.ma;
107007
+ const id = ma?.mediaCustomerId ? String(ma.mediaCustomerId) : "";
107008
+ if (!id) continue;
107009
+ if (!includeInvalidOAuth && ma.invalidOAuthToken) continue;
107010
+ const bal = balanceMap.get(id);
107011
+ const ov = overviewMap.get(id);
107012
+ const balance = bal?.remainingAccountBudget ?? null;
107013
+ const windowSpend = Number(ov?.spend ?? 0);
107014
+ const dailySpend = windowSpend > 0 ? windowSpend / spendDays : 0;
107015
+ let remainingDays = null;
107016
+ let recommendedTopup = null;
107017
+ if (dailySpend >= minDailySpend && balance !== null) {
107018
+ remainingDays = +(balance / dailySpend).toFixed(1);
107019
+ const needed = dailySpend * targetDays - balance;
107020
+ recommendedTopup = needed > 0 ? +needed.toFixed(2) : 0;
107021
+ }
107022
+ const lowDays = remainingDays !== null && remainingDays <= thresholdDays;
107023
+ const lowBalance = minBalance !== null && balance !== null && balance <= minBalance;
107024
+ const hitReason = lowDays && lowBalance ? "both" : lowDays ? "low-days" : lowBalance ? "low-balance" : "none";
107025
+ evaluated.push({
107026
+ mediaCustomerId: id,
107027
+ name: ma.mediaCustomerName ?? bal?.name ?? ov?.mediaCustomerName ?? null,
107028
+ advertiserName: item.mag?.advertiserName ?? null,
107029
+ currencyCode: bal?.currencyCode ?? ma.currencyCode ?? ov?.currencyCode ?? null,
107030
+ balance,
107031
+ dailySpend: dailySpend > 0 ? +dailySpend.toFixed(2) : null,
107032
+ remainingDays,
107033
+ recommendedTopup,
107034
+ invalidOAuthToken: !!ma.invalidOAuthToken,
107035
+ status: bal?.status ?? bal?.accountStatus ?? ov?.status ?? null,
107036
+ hitReason
107037
+ });
107038
+ }
107039
+ evaluated.sort((a, b) => {
107040
+ const ad = a.remainingDays ?? Number.POSITIVE_INFINITY;
107041
+ const bd = b.remainingDays ?? Number.POSITIVE_INFINITY;
107042
+ return ad - bd;
107043
+ });
107044
+ const hitCount = evaluated.filter((x) => x.hitReason !== "none").length;
107045
+ return { items: evaluated, hitCount };
107046
+ }
107047
+
106987
107048
  // src/commands/balance-scan/run.ts
106988
107049
  async function runBalanceScan(opts) {
106989
107050
  let config = loadConfig(opts.token);
@@ -107089,48 +107150,19 @@ async function runBalanceScan(opts) {
107089
107150
  balanceMap = fetched.balanceMap;
107090
107151
  overviewMap = fetched.overviewMap;
107091
107152
  batchChunkSize = fetched.chunkSize;
107092
- process.stderr.write(`\u23F3 [balance-scan] \u4F59\u989D\u4E0E\u6D88\u8017\u5DF2\u9F50\uFF0C\u6B63\u5728\u6309\u9608\u503C\u7B5B\u9009\u2026
107153
+ process.stderr.write(`\u23F3 [balance-scan] \u4F59\u989D\u4E0E\u6D88\u8017\u5DF2\u9F50\uFF0C\u6B63\u5728\u6C47\u603B\u5168\u90E8\u5DF2\u68C0\u67E5\u8D26\u6237\u2026
107093
107154
  `);
107094
107155
  }
107095
- const evaluated = [];
107096
- for (const item of allItems) {
107097
- const ma = item.ma;
107098
- const id = ma?.mediaCustomerId ? String(ma.mediaCustomerId) : "";
107099
- if (!id) continue;
107100
- const bal = balanceMap.get(id);
107101
- const ov = overviewMap.get(id);
107102
- const balance = bal?.remainingAccountBudget ?? null;
107103
- const windowSpend = Number(ov?.spend ?? 0);
107104
- const dailySpend = windowSpend > 0 ? windowSpend / spendDays : 0;
107105
- let remainingDays = null;
107106
- let recommendedTopup = null;
107107
- if (dailySpend >= minDailySpend && balance !== null) {
107108
- remainingDays = +(balance / dailySpend).toFixed(1);
107109
- const needed = dailySpend * targetDays - balance;
107110
- recommendedTopup = needed > 0 ? +needed.toFixed(2) : 0;
107111
- }
107112
- const lowDays = remainingDays !== null && remainingDays <= thresholdDays;
107113
- const lowBalance = minBalance !== null && balance !== null && balance <= minBalance;
107114
- if (!lowDays && !lowBalance && !isSubset) continue;
107115
- const hitReason = lowDays && lowBalance ? "both" : lowDays ? "low-days" : lowBalance ? "low-balance" : "none";
107116
- evaluated.push({
107117
- mediaCustomerId: id,
107118
- name: ma.mediaCustomerName ?? bal?.name ?? ov?.mediaCustomerName ?? null,
107119
- advertiserName: item.mag?.advertiserName ?? null,
107120
- currencyCode: bal?.currencyCode ?? ma.currencyCode ?? ov?.currencyCode ?? null,
107121
- balance,
107122
- dailySpend: dailySpend > 0 ? +dailySpend.toFixed(2) : null,
107123
- remainingDays,
107124
- recommendedTopup,
107125
- invalidOAuthToken: !!ma.invalidOAuthToken,
107126
- status: bal?.status ?? bal?.accountStatus ?? ov?.status ?? null,
107127
- hitReason
107128
- });
107129
- }
107130
- evaluated.sort((a, b) => {
107131
- const ad = a.remainingDays ?? Number.POSITIVE_INFINITY;
107132
- const bd = b.remainingDays ?? Number.POSITIVE_INFINITY;
107133
- return ad - bd;
107156
+ const { items: evaluated, hitCount } = evaluateBalanceScanAccounts({
107157
+ items: allItems,
107158
+ balanceMap,
107159
+ overviewMap,
107160
+ spendDays,
107161
+ thresholdDays,
107162
+ minBalance,
107163
+ minDailySpend,
107164
+ targetDays,
107165
+ includeInvalidOAuth: isSubset
107134
107166
  });
107135
107167
  const balancesForHealth = validIds.map((id) => balanceMap.get(id)?.remainingAccountBudget);
107136
107168
  const dataIssue = assessBalanceScanDataHealth({
@@ -107144,7 +107176,7 @@ async function runBalanceScan(opts) {
107144
107176
  scannedAccounts: allItems.length,
107145
107177
  validAccounts: validIds.length,
107146
107178
  skippedInvalidOAuth: allItems.length - validIds.length,
107147
- hitCount: evaluated.length,
107179
+ hitCount,
107148
107180
  nullBalanceCount: balancesForHealth.filter((b) => b == null).length,
107149
107181
  dataIssue: dataIssue?.code ?? null,
107150
107182
  dataIssueMessage: dataIssue?.message ?? null,
@@ -107184,22 +107216,23 @@ async function runBalanceScan(opts) {
107184
107216
  if (wroteJson) {
107185
107217
  return;
107186
107218
  }
107219
+ const thresholdHint = `\u9608\u503C\uFF1A\u7EED\u822A \u2264 ${thresholdDays} \u5929` + (minBalance !== null ? ` \u6216 \u4F59\u989D \u2264 ${minBalance}` : "");
107187
107220
  if (isSubset) {
107188
107221
  console.log(
107189
107222
  `
107190
107223
  ${media} \u8D26\u6237\u4F59\u989D\u753B\u50CF\uFF08\u5B50\u96C6\u6A21\u5F0F\uFF0C-a \u6307\u5B9A ${filterIds.length} \u4E2A\u8D26\u6237\uFF0C\u5168\u90E8\u5C55\u793A\uFF09
107191
- \u9608\u503C\u53C2\u8003\uFF1A\u7EED\u822A \u2264 ${thresholdDays} \u5929` + (minBalance !== null ? ` \u6216 \u4F59\u989D \u2264 ${minBalance}` : "") + `
107224
+ \u5DF2\u68C0\u67E5 ${evaluated.length} \u6237\uFF0C\u5176\u4E2D\u9884\u8B66\u547D\u4E2D ${hitCount} \u4E2A\uFF08${thresholdHint}\uFF09
107192
107225
  `
107193
107226
  );
107194
107227
  } else {
107195
107228
  console.log(
107196
107229
  `
107197
- ${media} \u8D26\u6237\u4F59\u989D\u626B\u63CF\u5B8C\u6210\uFF1A\u5171\u626B\u63CF ${meta.scannedAccounts} \u4E2A\u8D26\u6237\uFF08\u6709\u6548 ${meta.validAccounts}\uFF09\uFF0C\u547D\u4E2D ${meta.hitCount} \u4E2A\uFF08\u9608\u503C\uFF1A\u7EED\u822A \u2264 ${thresholdDays} \u5929` + (minBalance !== null ? ` \u6216 \u4F59\u989D \u2264 ${minBalance}` : "") + `\uFF09
107230
+ ${media} \u8D26\u6237\u4F59\u989D\u626B\u63CF\u5B8C\u6210\uFF1A\u5171\u626B\u63CF ${meta.scannedAccounts} \u4E2A\u8D26\u6237\uFF08\u6709\u6548 ${meta.validAccounts}\uFF09\uFF0C\u8FD4\u56DE\u5DF2\u68C0\u67E5 ${evaluated.length} \u6237\uFF0C\u5176\u4E2D\u9884\u8B66\u547D\u4E2D ${hitCount} \u4E2A\uFF08${thresholdHint}\uFF09
107198
107231
  `
107199
107232
  );
107200
107233
  }
107201
107234
  if (evaluated.length === 0) {
107202
- console.log(" \u2705 \u65E0\u547D\u4E2D\u8D26\u6237\uFF08\u9608\u503C\u5185\u65E0\u9884\u8B66\uFF1B\u6570\u636E\u5DF2\u6B63\u5E38\u8FD4\u56DE\uFF09\u3002\n");
107235
+ console.log(" \u2705 \u6CA1\u6709\u53EF\u5C55\u793A\u7684\u5DF2\u68C0\u67E5\u8D26\u6237\uFF08\u6570\u636E\u5DF2\u6B63\u5E38\u8FD4\u56DE\uFF09\u3002\n");
107203
107236
  return;
107204
107237
  }
107205
107238
  const cols = [
@@ -107213,7 +107246,7 @@ ${media} \u8D26\u6237\u4F59\u989D\u626B\u63CF\u5B8C\u6210\uFF1A\u5171\u626B\u63C
107213
107246
  { key: "reason", header: "\u547D\u4E2D\u539F\u56E0" }
107214
107247
  ];
107215
107248
  const reasonText = {
107216
- none: "\u2014\uFF08\u5B50\u96C6\u5C55\u793A\uFF0C\u672A\u89E6\u9608\u503C\uFF09",
107249
+ none: "\u2014\uFF08\u672A\u89E6\u9608\u503C\uFF09",
107217
107250
  "low-days": `\u7EED\u822A \u2264 ${thresholdDays} \u5929`,
107218
107251
  "low-balance": `\u4F59\u989D \u2264 ${minBalance ?? 0}`,
107219
107252
  both: `\u7EED\u822A + \u4F59\u989D\u53CC\u4F4E`
@@ -107231,7 +107264,7 @@ ${media} \u8D26\u6237\u4F59\u989D\u626B\u63CF\u5B8C\u6210\uFF1A\u5171\u626B\u63C
107231
107264
  printCliTable(rows, cols);
107232
107265
  console.log(
107233
107266
  `
107234
- \u63D0\u793A\uFF1A\u4E0A\u8868\u5DF2\u6309\u5269\u4F59\u5929\u6570\u5347\u5E8F\u6392\u5E8F\u3002
107267
+ \u63D0\u793A\uFF1A\u4E0A\u8868\u4E3A\u5168\u90E8\u5DF2\u68C0\u67E5\u8D26\u6237\uFF0C\u6309\u5269\u4F59\u5929\u6570\u5347\u5E8F\uFF1B\u9884\u8B66\u770B\u300C\u547D\u4E2D\u539F\u56E0\u300D\u5217\uFF08JSON \u7528 hitReason !== "none"\uFF09\u3002
107235
107268
  - \u5982\u9700\u8C03\u6574\u9608\u503C\uFF1A--threshold-days 14 / --min-balance 500
107236
107269
  - \u5982\u9700\u8C03\u6574\u65E5\u5747\u56DE\u770B\u5929\u6570\uFF1A--spend-days 3
107237
107270
  - \u5982\u9700\u8C03\u6574\u5EFA\u8BAE\u5145\u503C\u76EE\u6807\u7EED\u822A\uFF1A--target-days 60
@@ -107243,13 +107276,13 @@ ${media} \u8D26\u6237\u4F59\u989D\u626B\u63CF\u5B8C\u6210\uFF1A\u5171\u626B\u63C
107243
107276
  // src/commands/balance-scan/_register.ts
107244
107277
  function register9(program2) {
107245
107278
  program2.command("balance-scan").description(
107246
- "\u4E00\u952E\u626B\u63CF\u67D0\u5A92\u4F53\u4E0B\u6240\u6709\u8D26\u6237\u7684\u4F59\u989D\uFF0C\u7B5B\u51FA\u7EED\u822A\u5929\u6570\u4E0D\u8DB3\u7684\u8D26\u6237\u3002\u66FF\u4EE3\u5FAA\u73AF\u8C03\u7528 balance \u7684\u4F20\u7EDF\u505A\u6CD5\uFF0C\u5178\u578B\u573A\u666F\uFF1A\u300C\u5E2E\u6211\u627E\u51FA 117 \u4E2A Bing \u8D26\u6237\u91CC\u4F59\u989D\u4E0D\u8DB3 7 \u5929\u7684\u300D\u3002"
107279
+ "\u4E00\u952E\u626B\u63CF\u67D0\u5A92\u4F53\u4E0B\u6709\u6548\u8D26\u6237\u7684\u4F59\u989D\u4E0E\u65E5\u5747\u6D88\u8017\uFF0C\u8FD4\u56DE\u5168\u90E8\u5DF2\u68C0\u67E5\u8D26\u6237\uFF1B\u7528 hitReason \u6807\u8BB0\u7EED\u822A/\u4F59\u989D\u4E0D\u8DB3\u7684\u9884\u8B66\u884C\u3002\u66FF\u4EE3\u5FAA\u73AF\u8C03\u7528 balance \u7684\u4F20\u7EDF\u505A\u6CD5\u3002"
107247
107280
  ).requiredOption(
107248
107281
  "-m, --media <type>",
107249
107282
  "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai"
107250
107283
  ).option(
107251
107284
  "-a, --accounts <ids>",
107252
- "\u6307\u5B9A mediaCustomerId\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF1B\u8DF3\u8FC7\u6E05\u5355\u7FFB\u9875\uFF0C\u62C9\u53D6\u540E\u5168\u90E8\u8F93\u51FA\uFF08\u4E0D\u6309\u9608\u503C\u8FC7\u6EE4\uFF09"
107285
+ "\u6307\u5B9A mediaCustomerId\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF1B\u8DF3\u8FC7\u6E05\u5355\u7FFB\u9875\uFF0C\u5BF9\u8FD9\u4E9B ID \u62C9\u4F59\u989D+\u6D88\u8017\u540E\u5168\u90E8\u8F93\u51FA"
107253
107286
  ).option("--threshold-days <n>", "\u5269\u4F59\u7EED\u822A\u5929\u6570\u9608\u503C\uFF08\u9ED8\u8BA4 7\uFF09", (v) => parseFloat(v)).option(
107254
107287
  "--spend-days <n>",
107255
107288
  "\u65E5\u5747\u6D88\u8017\u56DE\u770B\u81EA\u7136\u65E5\u6570\uFF08\u622A\u81F3\u6628\u5929\u4E0D\u542B\u4ECA\u5929\uFF1B\u9ED8\u8BA4 7\uFF1B\u65E5\u5747=\u7A97\u53E3\u5408\u8BA1/\u5929\u6570\uFF09",
@@ -112413,7 +112446,7 @@ function cloneKeywordBlockShell(block) {
112413
112446
  delete shell["matchTypeV2"];
112414
112447
  return shell;
112415
112448
  }
112416
- function splitKeywordsForBatchJobBlockIfMixed(block, path44, warnings) {
112449
+ function splitKeywordsForBatchJobBlockIfMixed(block, path47, warnings) {
112417
112450
  const declaredUi = matchTypeV2ToUi(readBlockMatchTypeRaw(block));
112418
112451
  const entries = collectKeywordEntriesFromBlock(block);
112419
112452
  if (entries.length === 0) return [block];
@@ -112432,7 +112465,7 @@ function splitKeywordsForBatchJobBlockIfMixed(block, path44, warnings) {
112432
112465
  (ui) => matchTypeUiToV2(ui)
112433
112466
  );
112434
112467
  warnings.push(
112435
- `${path44} \u542B\u591A\u79CD\u5339\u914D\u7C7B\u578B\uFF0C\u5DF2\u81EA\u52A8\u62C6\u5206\u4E3A ${groups.size} \u4E2A KeywordsForBatchJob \u5757\uFF08${labels.join("\u3001")}\uFF09`
112468
+ `${path47} \u542B\u591A\u79CD\u5339\u914D\u7C7B\u578B\uFF0C\u5DF2\u81EA\u52A8\u62C6\u5206\u4E3A ${groups.size} \u4E2A KeywordsForBatchJob \u5757\uFF08${labels.join("\u3001")}\uFF09`
112436
112469
  );
112437
112470
  const shell = cloneKeywordBlockShell(block);
112438
112471
  const splitBlocks = [];
@@ -112452,16 +112485,16 @@ function splitKeywordsForBatchJobBlockIfMixed(block, path44, warnings) {
112452
112485
  }
112453
112486
  return splitBlocks;
112454
112487
  }
112455
- function validateKeywordCore(core, path44, errors, lengthViolations) {
112488
+ function validateKeywordCore(core, path47, errors, lengthViolations) {
112456
112489
  const trimmed = core.trim();
112457
112490
  if (!trimmed) {
112458
- errors.push(`${path44} \u5173\u952E\u8BCD\u8BCD\u5E72\u4E0D\u80FD\u4E3A\u7A7A`);
112491
+ errors.push(`${path47} \u5173\u952E\u8BCD\u8BCD\u5E72\u4E0D\u80FD\u4E3A\u7A7A`);
112459
112492
  return false;
112460
112493
  }
112461
112494
  if (trimmed.length > GOOGLE_KEYWORD_MAX_CORE_LENGTH) {
112462
112495
  if (lengthViolations) {
112463
112496
  pushLengthViolation(lengthViolations, {
112464
- path: path44,
112497
+ path: path47,
112465
112498
  field: "KeywordText",
112466
112499
  kind: "keyword_core",
112467
112500
  limit: GOOGLE_KEYWORD_MAX_CORE_LENGTH,
@@ -112471,13 +112504,13 @@ function validateKeywordCore(core, path44, errors, lengthViolations) {
112471
112504
  });
112472
112505
  }
112473
112506
  errors.push(
112474
- `${path44} \u5173\u952E\u8BCD\u8BCD\u5E72\u8D85\u8FC7 ${GOOGLE_KEYWORD_MAX_CORE_LENGTH} \u5B57\u7B26\uFF08\u5F53\u524D ${trimmed.length}\uFF09\uFF1A"${trimmed}"`
112507
+ `${path47} \u5173\u952E\u8BCD\u8BCD\u5E72\u8D85\u8FC7 ${GOOGLE_KEYWORD_MAX_CORE_LENGTH} \u5B57\u7B26\uFF08\u5F53\u524D ${trimmed.length}\uFF09\uFF1A"${trimmed}"`
112475
112508
  );
112476
112509
  return false;
112477
112510
  }
112478
112511
  if (!VALID_CORE_REGEX.test(trimmed)) {
112479
112512
  errors.push(
112480
- `${path44} \u542B Google \u4E0D\u5141\u8BB8\u7684\u5B57\u7B26\uFF08\u8BCD\u5E72\u4EC5\u5141\u8BB8\u5B57\u6BCD/\u6570\u5B57/\u7A7A\u683C/\u8FDE\u5B57\u7B26/\u53E5\u70B9\uFF09\uFF1A"${trimmed.slice(0, 40)}${trimmed.length > 40 ? "\u2026" : ""}"`
112513
+ `${path47} \u542B Google \u4E0D\u5141\u8BB8\u7684\u5B57\u7B26\uFF08\u8BCD\u5E72\u4EC5\u5141\u8BB8\u5B57\u6BCD/\u6570\u5B57/\u7A7A\u683C/\u8FDE\u5B57\u7B26/\u53E5\u70B9\uFF09\uFF1A"${trimmed.slice(0, 40)}${trimmed.length > 40 ? "\u2026" : ""}"`
112481
112514
  );
112482
112515
  return false;
112483
112516
  }
@@ -112534,24 +112567,24 @@ function canonicalizeKeywordBatchBlock(block, resolvedMatchV2, normalizedTexts,
112534
112567
  }
112535
112568
  }
112536
112569
  }
112537
- function pushKeywordAutoFixWarning(warnings, path44, fieldLabel, trimmedRaw, formatted, declaredUi, beforeUi, matchType, inferredMatchType) {
112570
+ function pushKeywordAutoFixWarning(warnings, path47, fieldLabel, trimmedRaw, formatted, declaredUi, beforeUi, matchType, inferredMatchType) {
112538
112571
  if (inferredMatchType) {
112539
112572
  warnings.push(
112540
- `${path44}.${fieldLabel} \u672A\u6307\u5B9A MatchTypeV2\uFF0C\u5DF2\u6309\u8BCD\u9762\u63A8\u65AD\u4E3A ${matchTypeUiToV2(matchType)}`
112573
+ `${path47}.${fieldLabel} \u672A\u6307\u5B9A MatchTypeV2\uFF0C\u5DF2\u6309\u8BCD\u9762\u63A8\u65AD\u4E3A ${matchTypeUiToV2(matchType)}`
112541
112574
  );
112542
112575
  return;
112543
112576
  }
112544
112577
  if (declaredUi && beforeUi !== declaredUi) {
112545
112578
  warnings.push(
112546
- `${path44}.${fieldLabel} MatchTypeV2=${matchTypeUiToV2(declaredUi)} \u4E0E\u8BCD\u9762\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u4FEE\u590D\u4E3A\uFF1A${formatted}`
112579
+ `${path47}.${fieldLabel} MatchTypeV2=${matchTypeUiToV2(declaredUi)} \u4E0E\u8BCD\u9762\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u4FEE\u590D\u4E3A\uFF1A${formatted}`
112547
112580
  );
112548
112581
  return;
112549
112582
  }
112550
112583
  if (formatted !== trimmedRaw) {
112551
- warnings.push(`${path44}.${fieldLabel} \u5DF2\u81EA\u52A8\u4FEE\u590D\u8BCD\u9762\uFF1A${trimmedRaw} \u2192 ${formatted}`);
112584
+ warnings.push(`${path47}.${fieldLabel} \u5DF2\u81EA\u52A8\u4FEE\u590D\u8BCD\u9762\uFF1A${trimmedRaw} \u2192 ${formatted}`);
112552
112585
  }
112553
112586
  }
112554
- function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, errors, warnings, lengthViolations) {
112587
+ function normalizeKeywordTextList(texts, matchTypeRaw, path47, fieldLabel, errors, warnings, lengthViolations) {
112555
112588
  const declaredUi = matchTypeV2ToUi(matchTypeRaw);
112556
112589
  const normalized = [];
112557
112590
  const seen = /* @__PURE__ */ new Set();
@@ -112559,7 +112592,7 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, error
112559
112592
  for (let k = 0; k < texts.length; k++) {
112560
112593
  const raw = texts[k];
112561
112594
  if (typeof raw !== "string" || !raw.trim()) {
112562
- errors.push(`${path44}.${fieldLabel}[${k}] \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
112595
+ errors.push(`${path47}.${fieldLabel}[${k}] \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
112563
112596
  continue;
112564
112597
  }
112565
112598
  const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
@@ -112570,7 +112603,7 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, error
112570
112603
  );
112571
112604
  pushKeywordAutoFixWarning(
112572
112605
  warnings,
112573
- path44,
112606
+ path47,
112574
112607
  fieldLabel,
112575
112608
  trimmedRaw,
112576
112609
  formatted,
@@ -112580,11 +112613,11 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, error
112580
112613
  inferredMatchType
112581
112614
  );
112582
112615
  const core = unwrapKeywordDisplayTextForEdit(formatted);
112583
- if (!validateKeywordCore(core, `${path44}.${fieldLabel}[${k}]`, errors, lengthViolations))
112616
+ if (!validateKeywordCore(core, `${path47}.${fieldLabel}[${k}]`, errors, lengthViolations))
112584
112617
  continue;
112585
112618
  const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
112586
112619
  if (seen.has(dedupeKey)) {
112587
- warnings.push(`${path44}.${fieldLabel}[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
112620
+ warnings.push(`${path47}.${fieldLabel}[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
112588
112621
  continue;
112589
112622
  }
112590
112623
  seen.add(dedupeKey);
@@ -112593,12 +112626,12 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, error
112593
112626
  }
112594
112627
  return { normalized, resolvedUi };
112595
112628
  }
112596
- function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, lengthViolations) {
112629
+ function normalizeKeywordsForBatchJobBlock(block, path47, errors, warnings, lengthViolations) {
112597
112630
  const matchTypeRaw = readBlockMatchTypeRaw(block);
112598
112631
  const declaredUi = matchTypeV2ToUi(matchTypeRaw);
112599
112632
  if (matchTypeRaw !== void 0 && declaredUi === null) {
112600
112633
  errors.push(
112601
- `${path44}.MatchTypeV2 \u65E0\u6548\uFF08${String(matchTypeRaw)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1ABROAD | PHRASE | EXACT`
112634
+ `${path47}.MatchTypeV2 \u65E0\u6548\uFF08${String(matchTypeRaw)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1ABROAD | PHRASE | EXACT`
112602
112635
  );
112603
112636
  return;
112604
112637
  }
@@ -112613,7 +112646,7 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
112613
112646
  const result = normalizeKeywordTextList(
112614
112647
  texts,
112615
112648
  matchTypeRaw,
112616
- path44,
112649
+ path47,
112617
112650
  "KeywordText",
112618
112651
  errors,
112619
112652
  warnings,
@@ -112622,7 +112655,7 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
112622
112655
  normalizedTexts = result.normalized;
112623
112656
  if (result.resolvedUi) resolvedUi = result.resolvedUi;
112624
112657
  if (normalizedTexts.length === 0 && texts.length > 0) {
112625
- errors.push(`${path44}.KeywordText \u7ECF\u6821\u9A8C\u540E\u65E0\u6709\u6548\u5173\u952E\u8BCD\uFF0C\u8BF7\u4FEE\u6B63\u8BCD\u9762\u6216 MatchTypeV2`);
112658
+ errors.push(`${path47}.KeywordText \u7ECF\u6821\u9A8C\u540E\u65E0\u6709\u6548\u5173\u952E\u8BCD\uFF0C\u8BF7\u4FEE\u6B63\u8BCD\u9762\u6216 MatchTypeV2`);
112626
112659
  }
112627
112660
  }
112628
112661
  if (hasItems && items2) {
@@ -112630,12 +112663,12 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
112630
112663
  for (let k = 0; k < items2.length; k++) {
112631
112664
  const item = items2[k];
112632
112665
  if (!item || typeof item !== "object") {
112633
- errors.push(`${path44}.Items[${k}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
112666
+ errors.push(`${path47}.Items[${k}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
112634
112667
  continue;
112635
112668
  }
112636
112669
  const raw = readItemText(item);
112637
112670
  if (raw === null || !raw.trim()) {
112638
- errors.push(`${path44}.Items[${k}].Text \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
112671
+ errors.push(`${path47}.Items[${k}].Text \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
112639
112672
  continue;
112640
112673
  }
112641
112674
  const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
@@ -112646,7 +112679,7 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
112646
112679
  );
112647
112680
  pushKeywordAutoFixWarning(
112648
112681
  warnings,
112649
- path44,
112682
+ path47,
112650
112683
  `Items[${k}].Text`,
112651
112684
  trimmedRaw,
112652
112685
  formatted,
@@ -112656,11 +112689,11 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
112656
112689
  inferredMatchType
112657
112690
  );
112658
112691
  const core = unwrapKeywordDisplayTextForEdit(formatted);
112659
- if (!validateKeywordCore(core, `${path44}.Items[${k}].Text`, errors, lengthViolations))
112692
+ if (!validateKeywordCore(core, `${path47}.Items[${k}].Text`, errors, lengthViolations))
112660
112693
  continue;
112661
112694
  const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
112662
112695
  if (seen.has(dedupeKey)) {
112663
- warnings.push(`${path44}.Items[${k}].Text \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
112696
+ warnings.push(`${path47}.Items[${k}].Text \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
112664
112697
  continue;
112665
112698
  }
112666
112699
  seen.add(dedupeKey);
@@ -112805,16 +112838,16 @@ function warnIfNegativeKeywordsInPositiveKeywords(campaign, warnings) {
112805
112838
  const raw = texts[ki];
112806
112839
  if (typeof raw !== "string" || !raw.trim()) continue;
112807
112840
  const core = normalizeKeywordCoreForNegativeCheck(raw);
112808
- const path44 = `campaign.AdGroupsForBatchJob[${gi}].KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
112841
+ const path47 = `campaign.AdGroupsForBatchJob[${gi}].KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
112809
112842
  if (plannedNegCores.has(core)) {
112810
112843
  warnings.push(
112811
- `${path44} \u8BCD\u9762\u300C${raw}\u300D\u5DF2\u51FA\u73B0\u5728 campaign.NegativeKeywordsForBatchJob\uFF0C\u4E0D\u5E94\u91CD\u590D\u5199\u5165\u6B63\u5411 KeywordsForBatchJob\uFF1B\u5426\u8BCD\u53EA\u653E NegativeKeywordsForBatchJob`
112844
+ `${path47} \u8BCD\u9762\u300C${raw}\u300D\u5DF2\u51FA\u73B0\u5728 campaign.NegativeKeywordsForBatchJob\uFF0C\u4E0D\u5E94\u91CD\u590D\u5199\u5165\u6B63\u5411 KeywordsForBatchJob\uFF1B\u5426\u8BCD\u53EA\u653E NegativeKeywordsForBatchJob`
112812
112845
  );
112813
112846
  continue;
112814
112847
  }
112815
112848
  if (looksLikeStandaloneNegativeKeyword(core)) {
112816
112849
  warnings.push(
112817
- `${path44} \u8BCD\u9762\u300C${raw}\u300D\u7591\u4F3C\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u5E38\u89C1\u5426\u8BCD\u8BCD\u6839\uFF09\uFF0C\u5E94\u5199\u5165 campaign.NegativeKeywordsForBatchJob\uFF08MatchTypeV2: BROAD\uFF09\uFF0C\u52FF\u653E\u5165 KeywordsForBatchJob`
112850
+ `${path47} \u8BCD\u9762\u300C${raw}\u300D\u7591\u4F3C\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u5E38\u89C1\u5426\u8BCD\u8BCD\u6839\uFF09\uFF0C\u5E94\u5199\u5165 campaign.NegativeKeywordsForBatchJob\uFF08MatchTypeV2: BROAD\uFF09\uFF0C\u52FF\u653E\u5165 KeywordsForBatchJob`
112818
112851
  );
112819
112852
  }
112820
112853
  }
@@ -116445,13 +116478,13 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116445
116478
  for (let ki = 0; ki < texts.length; ki++) {
116446
116479
  const t = texts[ki];
116447
116480
  if (typeof t !== "string" || !t.trim()) continue;
116448
- const path44 = `${groupPath}.KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
116481
+ const path47 = `${groupPath}.KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
116449
116482
  const key = keywordKey(t, matchTypeV2);
116450
116483
  const plannedContent = `\u8BCD\u9762: ${t} | \u5339\u914D: ${matchTypeV2}`;
116451
116484
  if (groupAbsent) {
116452
116485
  pushItem(items2, {
116453
116486
  layer: "keyword",
116454
- path: path44,
116487
+ path: path47,
116455
116488
  adGroupName: groupName,
116456
116489
  plannedContent,
116457
116490
  status: "skipped",
@@ -116463,7 +116496,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116463
116496
  if (!liveKwKeys.has(key)) {
116464
116497
  pushItem(items2, {
116465
116498
  layer: "keyword",
116466
- path: path44,
116499
+ path: path47,
116467
116500
  adGroupName: groupName,
116468
116501
  plannedContent,
116469
116502
  status: "missing",
@@ -116473,7 +116506,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116473
116506
  } else {
116474
116507
  pushItem(items2, {
116475
116508
  layer: "keyword",
116476
- path: path44,
116509
+ path: path47,
116477
116510
  adGroupName: groupName,
116478
116511
  plannedContent,
116479
116512
  status: "ok",
@@ -116498,14 +116531,14 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116498
116531
  for (let ai = 0; ai < ads.length; ai++) {
116499
116532
  const ad = asRecord3(ads[ai]);
116500
116533
  if (!ad) continue;
116501
- const path44 = `${groupPath}.AdsForBatchJob[${ai}]`;
116534
+ const path47 = `${groupPath}.AdsForBatchJob[${ai}]`;
116502
116535
  const primary = pickString2(ad["headlinePart1"], ad["AdTitle"]);
116503
116536
  const finalUrl = pickString2(ad["Finalurl"], ad["DestinationUrl"], ad["finalUrl"]);
116504
116537
  const rsaAssets = listPlannedRsaAssets(ad, groupPath, ai, groupName);
116505
116538
  if (groupAbsent) {
116506
116539
  pushItem(items2, {
116507
116540
  layer: "ad",
116508
- path: path44,
116541
+ path: path47,
116509
116542
  adGroupName: groupName,
116510
116543
  plannedContent: primary ? `RSA \u9996\u6807\u9898: ${primary}${finalUrl ? ` | \u843D\u5730\u9875: ${finalUrl}` : ""}` : `AdsForBatchJob[${ai}]\uFF08\u7F3A\u5C11 headlinePart1\uFF09`,
116511
116544
  status: "skipped",
@@ -116529,7 +116562,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116529
116562
  if (!primary) {
116530
116563
  pushItem(items2, {
116531
116564
  layer: "ad",
116532
- path: path44,
116565
+ path: path47,
116533
116566
  adGroupName: groupName,
116534
116567
  plannedContent: `AdsForBatchJob[${ai}]\uFF08\u7F3A\u5C11 headlinePart1\uFF0C\u65E0\u6CD5\u6BD4\u5BF9\u6574\u6761 RSA\uFF09`,
116535
116568
  status: "missing",
@@ -116555,7 +116588,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116555
116588
  const rsaFound = liveAdsInGroup.some((la) => liveAdHasHeadline(la, primary));
116556
116589
  pushItem(items2, {
116557
116590
  layer: "ad",
116558
- path: path44,
116591
+ path: path47,
116559
116592
  adGroupName: groupName,
116560
116593
  plannedContent: `RSA \u9996\u6807\u9898: ${primary}${finalUrl ? ` | \u843D\u5730\u9875: ${finalUrl}` : ""}`,
116561
116594
  status: rsaFound ? "ok" : "missing",
@@ -116621,11 +116654,11 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116621
116654
  for (let ei = 0; ei < extBlocks.length; ei++) {
116622
116655
  const ext = asRecord3(extBlocks[ei]);
116623
116656
  const plannedContent = formatExtensionPlanned(ext);
116624
- const path44 = `campaign.ExtensionsForBatchJob[${ei}]`;
116657
+ const path47 = `campaign.ExtensionsForBatchJob[${ei}]`;
116625
116658
  if (campaignMissing) {
116626
116659
  pushItem(items2, {
116627
116660
  layer: "extension",
116628
- path: path44,
116661
+ path: path47,
116629
116662
  plannedContent,
116630
116663
  status: "skipped",
116631
116664
  liveNote: "\u7CFB\u5217\u672A\u627E\u5230\uFF0C\u8DF3\u8FC7\u9644\u52A0\u4FE1\u606F\u6BD4\u5BF9",
@@ -116637,7 +116670,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116637
116670
  const remediateCommand = !found && ext && accountId && campaignId ? buildExtensionRemediateCommandLine({ accountId, campaignId, ext }) ?? void 0 : void 0;
116638
116671
  pushItem(items2, {
116639
116672
  layer: "extension",
116640
- path: path44,
116673
+ path: path47,
116641
116674
  plannedContent,
116642
116675
  status: found ? "ok" : "missing",
116643
116676
  liveNote: `\u7CFB\u5217\u7EA7\u9644\u52A0\u4FE1\u606F\u8D26\u6237\u5185 ${liveExt}/${plannedExt} \u6761`,
@@ -116664,11 +116697,11 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116664
116697
  if (!id) continue;
116665
116698
  const displayName = locationNames[li] || pickString2(loc["locationName"], loc["name"], loc["countryOrRegion"]) || "";
116666
116699
  const plannedContent = `locationId: ${id}${displayName ? ` | \u540D\u79F0: ${displayName}` : ""}`;
116667
- const path44 = `campaign.targetedLocations[${li}]`;
116700
+ const path47 = `campaign.targetedLocations[${li}]`;
116668
116701
  if (campaignMissing) {
116669
116702
  pushItem(items2, {
116670
116703
  layer: "location",
116671
- path: path44,
116704
+ path: path47,
116672
116705
  plannedContent,
116673
116706
  status: "skipped",
116674
116707
  liveNote: "\u7CFB\u5217\u672A\u627E\u5230\uFF0C\u8DF3\u8FC7\u5730\u57DF\u6BD4\u5BF9",
@@ -116680,7 +116713,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116680
116713
  const remediateCommand = !found && accountId && campaignId ? buildGeoRemediateCommandLine({ accountId, campaignId, locationId: id }) : void 0;
116681
116714
  pushItem(items2, {
116682
116715
  layer: "location",
116683
- path: path44,
116716
+ path: path47,
116684
116717
  plannedContent,
116685
116718
  status: found ? "ok" : "missing",
116686
116719
  liveNote: `\u7CFB\u5217\u5DF2\u5B9A\u5411 ${liveLocIds.size}/${plannedLocCount} \u4E2A\u5730\u57DF`,
@@ -118192,8 +118225,8 @@ function pmaxChannelTypesUrl(googleApiUrl) {
118192
118225
  }
118193
118226
  function pmaxCampaignUrl(googleApiUrl, accountId, campaignId) {
118194
118227
  const base = googleApiUrl.replace(/\/$/, "");
118195
- const path44 = `${base}/accounts/${accountId}/campaign/pmax`;
118196
- return campaignId ? `${path44}/${campaignId}` : path44;
118228
+ const path47 = `${base}/accounts/${accountId}/campaign/pmax`;
118229
+ return campaignId ? `${path47}/${campaignId}` : path47;
118197
118230
  }
118198
118231
  function pmaxBrandAssetsUrl(googleApiUrl, accountId, campaignId) {
118199
118232
  return `${pmaxCampaignUrl(googleApiUrl, accountId, campaignId)}/brand-assets`;
@@ -118203,8 +118236,8 @@ function pmaxBrandGuidelinesEnableUrl(googleApiUrl, accountId, campaignId) {
118203
118236
  }
118204
118237
  function pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, suffix) {
118205
118238
  const base = googleApiUrl.replace(/\/$/, "");
118206
- const path44 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
118207
- return suffix ? `${path44}/${suffix.replace(/^\//, "")}` : path44;
118239
+ const path47 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
118240
+ return suffix ? `${path47}/${suffix.replace(/^\//, "")}` : path47;
118208
118241
  }
118209
118242
  function pmaxCampaignAssetGroupUrl(googleApiUrl, accountId, campaignId) {
118210
118243
  const base = googleApiUrl.replace(/\/$/, "");
@@ -120945,8 +120978,8 @@ async function runAdPmaxImageConvert(opts) {
120945
120978
  }
120946
120979
 
120947
120980
  // src/commands/ad/pmax-brand-mgmt.ts
120948
- function loadBrandConfig(path44, accountId, campaignId) {
120949
- const cfg = loadPmaxJsonFile(path44);
120981
+ function loadBrandConfig(path47, accountId, campaignId) {
120982
+ const cfg = loadPmaxJsonFile(path47);
120950
120983
  return {
120951
120984
  ...cfg,
120952
120985
  account: String(cfg["account"] ?? accountId).trim(),
@@ -129664,9 +129697,9 @@ function asRecord7(value) {
129664
129697
  }
129665
129698
  return null;
129666
129699
  }
129667
- function getNestedValue(root, path44) {
129700
+ function getNestedValue(root, path47) {
129668
129701
  let cur = root;
129669
- for (const key of path44) {
129702
+ for (const key of path47) {
129670
129703
  const rec = asRecord7(cur);
129671
129704
  if (!rec) return void 0;
129672
129705
  cur = rec[key];
@@ -133961,6 +133994,855 @@ function registerTikTokAnalysisCommands(program2) {
133961
133994
  );
133962
133995
  }
133963
133996
 
133997
+ // src/commands/yandex-analysis/register-cli.ts
133998
+ import { Command as Command5 } from "commander";
133999
+
134000
+ // src/commands/yandex-analysis/sections.ts
134001
+ var YANDEX_SECTIONS = [
134002
+ {
134003
+ name: "overview",
134004
+ description: "\u8D26\u6237\u603B\u89C8\uFF1A\u4F59\u989D + \u533A\u95F4\u6C47\u603B KPI",
134005
+ path: "overview"
134006
+ },
134007
+ {
134008
+ name: "daily",
134009
+ description: "\u6309\u65E5\u7EE9\u6548\uFF08\u53EF\u4F9B\u8C03\u7528\u65B9\u81EA\u884C\u805A\u5468\uFF09",
134010
+ path: "daily"
134011
+ },
134012
+ {
134013
+ name: "search",
134014
+ description: "\u7F51\u7EDC\u7EF4\u5EA6\uFF08\u4EC5 SEARCH\uFF09",
134015
+ path: "search"
134016
+ },
134017
+ {
134018
+ name: "campaigns",
134019
+ description: "Campaign \u7EE9\u6548 + \u9884\u7B97/\u6218\u7565/targetCpa",
134020
+ path: "campaigns",
134021
+ limitOption: true
134022
+ },
134023
+ {
134024
+ name: "keywords",
134025
+ description: "\u5173\u952E\u8BCD\uFF08Criteria\uFF09\u7EE9\u6548",
134026
+ path: "keywords",
134027
+ limitOption: true
134028
+ },
134029
+ {
134030
+ name: "search-terms",
134031
+ description: "\u641C\u7D22\u8BCD\u7EE9\u6548\uFF08Direct \u9650\u5236\uFF1A\u4EC5\u8FD1 180 \u5929\uFF09",
134032
+ path: "search-terms",
134033
+ limitOption: true,
134034
+ searchTermsMaxDays: 180
134035
+ },
134036
+ {
134037
+ name: "geo",
134038
+ description: "\u5730\u57DF\u7EE9\u6548\uFF08\u4EC5 SEARCH\uFF09",
134039
+ path: "geo",
134040
+ limitOption: true
134041
+ },
134042
+ {
134043
+ name: "devices",
134044
+ description: "\u8BBE\u5907\u7EE9\u6548\uFF08\u4EC5 SEARCH\uFF09",
134045
+ path: "devices"
134046
+ }
134047
+ ];
134048
+ var YANDEX_SECTION_NAMES = YANDEX_SECTIONS.map((s) => s.name);
134049
+ var YANDEX_SECTION_ALIASES = {
134050
+ geographic: "geo",
134051
+ device: "devices",
134052
+ "search-queries": "search-terms"
134053
+ };
134054
+
134055
+ // src/commands/yandex-analysis/run-batch.ts
134056
+ init_auth();
134057
+ import * as path43 from "path";
134058
+ import { performance as performance8 } from "perf_hooks";
134059
+ init_version();
134060
+
134061
+ // src/commands/yandex-analysis/fetch.ts
134062
+ init_auth();
134063
+ var YANDEX_KEYWORD_LIMIT_DEFAULT = 100;
134064
+ var YANDEX_KEYWORD_LIMIT_MAX = 1e4;
134065
+ var YANDEX_SEARCH_TERMS_MAX_DAYS = 180;
134066
+ function stripLocalDate2(d) {
134067
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
134068
+ }
134069
+ function parseLocalYmd2(ymd) {
134070
+ const parts = ymd.trim().split("-").map((x) => parseInt(x, 10));
134071
+ if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) {
134072
+ return /* @__PURE__ */ new Date(NaN);
134073
+ }
134074
+ const [y, m, day] = parts;
134075
+ return new Date(y, m - 1, day);
134076
+ }
134077
+ function formatLocalYmd2(d) {
134078
+ const y = d.getFullYear();
134079
+ const m = String(d.getMonth() + 1).padStart(2, "0");
134080
+ const day = String(d.getDate()).padStart(2, "0");
134081
+ return `${y}-${m}-${day}`;
134082
+ }
134083
+ function daysInclusive(start, end) {
134084
+ const ms = stripLocalDate2(end).getTime() - stripLocalDate2(start).getTime();
134085
+ return Math.floor(ms / 864e5) + 1;
134086
+ }
134087
+ function resolveYandexDateRange(start, end) {
134088
+ if (start && end) {
134089
+ const s = parseLocalYmd2(start);
134090
+ const e = parseLocalYmd2(end);
134091
+ if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime())) {
134092
+ console.error("\n\u274C --start / --end \u987B\u4E3A\u6709\u6548\u65E5\u671F YYYY-MM-DD\u3002\n");
134093
+ process.exit(1);
134094
+ }
134095
+ if (s > e) {
134096
+ console.error("\n\u274C \u5F00\u59CB\u65E5\u671F\u4E0D\u80FD\u665A\u4E8E\u7ED3\u675F\u65E5\u671F\u3002\n");
134097
+ process.exit(1);
134098
+ }
134099
+ const today = stripLocalDate2(/* @__PURE__ */ new Date());
134100
+ if (stripLocalDate2(s) > today || stripLocalDate2(e) > today) {
134101
+ console.error("\n\u274C Yandex \u62A5\u8868\u8D77\u6B62\u65E5\u671F\u4E0D\u80FD\u665A\u4E8E\u4ECA\u5929\u3002\n");
134102
+ process.exit(1);
134103
+ }
134104
+ return { startDate: start.trim(), endDate: end.trim() };
134105
+ }
134106
+ if (!start && !end) {
134107
+ const endD = stripLocalDate2(/* @__PURE__ */ new Date());
134108
+ const startD = new Date(endD);
134109
+ startD.setDate(startD.getDate() - 6);
134110
+ return { startDate: formatLocalYmd2(startD), endDate: formatLocalYmd2(endD) };
134111
+ }
134112
+ console.error(
134113
+ "\n\u274C --start \u4E0E --end \u987B\u540C\u65F6\u4F20\u5165\uFF0C\u6216\u540C\u65F6\u7701\u7565\u4EE5\u4F7F\u7528\u9ED8\u8BA4\u533A\u95F4\uFF08\u8FD1 7 \u5929\uFF0C\u542B\u4ECA\u5929\uFF09\u3002\n"
134114
+ );
134115
+ process.exit(1);
134116
+ }
134117
+ function assertYandexSearchTermsRange(startDate, endDate) {
134118
+ const s = parseLocalYmd2(startDate);
134119
+ const e = parseLocalYmd2(endDate);
134120
+ const today = stripLocalDate2(/* @__PURE__ */ new Date());
134121
+ const earliest = new Date(today);
134122
+ earliest.setDate(earliest.getDate() - (YANDEX_SEARCH_TERMS_MAX_DAYS - 1));
134123
+ if (stripLocalDate2(s) < earliest || stripLocalDate2(e) > today) {
134124
+ console.error(
134125
+ `
134126
+ \u274C search-terms \u4EC5\u652F\u6301\u8FD1 ${YANDEX_SEARCH_TERMS_MAX_DAYS} \u5929\uFF08Direct \u9650\u5236\uFF09\u3002\u8BF7\u5C06 --start/--end \u9650\u5236\u5728 ${formatLocalYmd2(earliest)} ~ ${formatLocalYmd2(today)}\u3002
134127
+ `
134128
+ );
134129
+ process.exit(1);
134130
+ }
134131
+ if (daysInclusive(s, e) > YANDEX_SEARCH_TERMS_MAX_DAYS) {
134132
+ console.error(
134133
+ `
134134
+ \u274C search-terms \u67E5\u8BE2\u8DE8\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${YANDEX_SEARCH_TERMS_MAX_DAYS} \u5929\u3002
134135
+ `
134136
+ );
134137
+ process.exit(1);
134138
+ }
134139
+ }
134140
+ function assertYandexAccountId(id) {
134141
+ const t = id.trim();
134142
+ if (!t) {
134143
+ console.error("\n\u274C --account \u4E0D\u80FD\u4E3A\u7A7A\uFF08Yandex Client-Login / mediaCustomerId\uFF0C\u5982 porg-xxx\uFF09\u3002\n");
134144
+ process.exit(1);
134145
+ }
134146
+ return t;
134147
+ }
134148
+ function requireYandexApi(config) {
134149
+ const base = (config.yandexApiUrl ?? "").replace(/\/$/, "");
134150
+ if (!base) {
134151
+ console.error(
134152
+ "\n\u274C \u672A\u627E\u5230 Yandex API \u5730\u5740\uFF08yandexApiUrl\uFF09\u3002\n \u4ECE TSO API \u5730\u5740\u81EA\u52A8\u63A8\u5BFC\uFF0C\u4E5F\u53EF\u901A\u8FC7\u73AF\u5883\u53D8\u91CF SILUZAN_YANDEX_API \u8986\u76D6\u3002\n"
134153
+ );
134154
+ process.exit(1);
134155
+ }
134156
+ return base;
134157
+ }
134158
+ function resolveYandexLimit(raw) {
134159
+ const n = typeof raw === "number" ? raw : parseInt(String(raw ?? ""), 10);
134160
+ if (!Number.isFinite(n) || n <= 0) return YANDEX_KEYWORD_LIMIT_DEFAULT;
134161
+ return Math.min(Math.floor(n), YANDEX_KEYWORD_LIMIT_MAX);
134162
+ }
134163
+ function analysisUrl(yandexApiUrl, pathSeg, query) {
134164
+ const base = yandexApiUrl.replace(/\/$/, "");
134165
+ const q = query ? query.startsWith("?") ? query : `?${query}` : "";
134166
+ return `${base}/yandex-analysis/${pathSeg}${q}`;
134167
+ }
134168
+ async function fetchYandexSectionPayload(def, opts, config, accountId) {
134169
+ const { startDate, endDate } = resolveYandexDateRange(opts.start, opts.end);
134170
+ if (def.searchTermsMaxDays) {
134171
+ assertYandexSearchTermsRange(startDate, endDate);
134172
+ }
134173
+ const yandexApiUrl = requireYandexApi(config);
134174
+ const params = new URLSearchParams({
134175
+ account: accountId,
134176
+ start: startDate,
134177
+ end: endDate
134178
+ });
134179
+ if (def.limitOption) {
134180
+ params.set("limit", String(resolveYandexLimit(opts.limit)));
134181
+ }
134182
+ const url = analysisUrl(yandexApiUrl, def.path, params.toString());
134183
+ return apiFetch2(url, config, {}, !!opts.verbose);
134184
+ }
134185
+ function endpointHintForYandexSection(def) {
134186
+ return `GET \u2026/yandex-analysis/${def.path}?account=&start=&end=`;
134187
+ }
134188
+
134189
+ // src/commands/yandex-analysis/resolve-sections.ts
134190
+ function normalizeSectionToken4(raw) {
134191
+ const t = raw.trim();
134192
+ if (!t) return null;
134193
+ if (YANDEX_SECTION_NAMES.includes(t)) {
134194
+ return t;
134195
+ }
134196
+ return YANDEX_SECTION_ALIASES[t] ?? null;
134197
+ }
134198
+ function resolveSectionList5(sections, exclude) {
134199
+ const splitClean = (s) => (s ?? "").split(",").map((x) => x.trim()).filter(Boolean);
134200
+ const include = splitClean(sections);
134201
+ const dropRaw = splitClean(exclude);
134202
+ const unknown = [...include, ...dropRaw].filter((n) => normalizeSectionToken4(n) === null);
134203
+ if (unknown.length > 0) {
134204
+ console.error(
134205
+ `
134206
+ \u274C \u672A\u77E5\u7684 section \u540D\u79F0\uFF1A${unknown.join(", ")}
134207
+ \u53EF\u9009\uFF1A${YANDEX_SECTION_NAMES.join(", ")}
134208
+ \u522B\u540D\uFF1A${Object.keys(YANDEX_SECTION_ALIASES).join(", ")}
134209
+ `
134210
+ );
134211
+ process.exit(1);
134212
+ }
134213
+ const includeCanonical = new Set(include.map((n) => normalizeSectionToken4(n)).filter(Boolean));
134214
+ const dropCanonical = new Set(dropRaw.map((n) => normalizeSectionToken4(n)).filter(Boolean));
134215
+ const base = include.length > 0 ? YANDEX_SECTIONS.filter((s) => includeCanonical.has(s.name)) : [...YANDEX_SECTIONS];
134216
+ return base.filter((s) => !dropCanonical.has(s.name));
134217
+ }
134218
+
134219
+ // src/commands/yandex-analysis/run-batch.ts
134220
+ async function runWithConcurrency6(tasks, concurrency) {
134221
+ const results = new Array(tasks.length);
134222
+ let cursor = 0;
134223
+ const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
134224
+ while (true) {
134225
+ const i = cursor++;
134226
+ if (i >= tasks.length) return;
134227
+ results[i] = await tasks[i]();
134228
+ }
134229
+ });
134230
+ await Promise.all(workers);
134231
+ return results;
134232
+ }
134233
+ async function runAllYandexSections(opts) {
134234
+ const id = assertYandexAccountId(opts.account);
134235
+ const config = loadConfig(opts.token);
134236
+ if (!opts.jsonOut?.trim()) {
134237
+ console.error("\n\u274C \u5FC5\u987B\u4F20 --json-out <\u76EE\u5F55>\uFF08\u5404\u7EF4\u5EA6\u843D\u76D8\u540E\u7531 report-manifest \u7D22\u5F15\uFF09\u3002\n");
134238
+ process.exit(1);
134239
+ }
134240
+ const targets = resolveSectionList5(opts.sections, opts.exclude);
134241
+ if (targets.length === 0) {
134242
+ console.error("\n\u274C \u6CA1\u6709\u8981\u6267\u884C\u7684 section\uFF08\u8BF7\u68C0\u67E5 --sections / --exclude\uFF09\u3002\n");
134243
+ process.exit(1);
134244
+ }
134245
+ const { startDate, endDate } = resolveYandexDateRange(opts.start, opts.end);
134246
+ const concurrency = Math.max(
134247
+ 1,
134248
+ Math.min(typeof opts.concurrency === "number" ? opts.concurrency : 5, 16)
134249
+ );
134250
+ const cliVersion = getCurrentVersion2();
134251
+ const overallT0 = performance8.now();
134252
+ const tasks = targets.map((def) => async () => {
134253
+ const t0 = performance8.now();
134254
+ const hint = endpointHintForYandexSection(def);
134255
+ try {
134256
+ const payload = await fetchYandexSectionPayload(
134257
+ def,
134258
+ { ...opts, start: startDate, end: endDate },
134259
+ config,
134260
+ id
134261
+ );
134262
+ const summary2 = await writeReportAnalysisSnapshot({
134263
+ snapshotDir: opts.jsonOut,
134264
+ section: def.name,
134265
+ accountId: id,
134266
+ dateRange: { start: startDate, end: endDate },
134267
+ payload,
134268
+ cliVersion
134269
+ });
134270
+ return {
134271
+ section: def.name,
134272
+ ok: true,
134273
+ elapsedMs: performance8.now() - t0,
134274
+ file: summary2.writtenFiles[0],
134275
+ endpointHint: hint
134276
+ };
134277
+ } catch (err) {
134278
+ return {
134279
+ section: def.name,
134280
+ ok: false,
134281
+ elapsedMs: performance8.now() - t0,
134282
+ error: err instanceof Error ? err.message : String(err),
134283
+ endpointHint: hint
134284
+ };
134285
+ }
134286
+ });
134287
+ const results = await runWithConcurrency6(tasks, concurrency);
134288
+ const succeeded = results.filter((r) => r.ok).length;
134289
+ const failed = results.length - succeeded;
134290
+ const absoluteSnapshotDir = path43.resolve(opts.jsonOut);
134291
+ const summary = {
134292
+ kind: "siluzan-tso-yandex-analysis-snapshot-batch",
134293
+ absoluteSnapshotDir,
134294
+ manifestFile: reportManifestFile(id),
134295
+ accountId: id,
134296
+ totalSections: results.length,
134297
+ succeeded,
134298
+ failed,
134299
+ concurrency,
134300
+ elapsedMs: performance8.now() - overallT0,
134301
+ results
134302
+ };
134303
+ console.log(JSON.stringify(summary));
134304
+ if (failed > 0) process.exit(2);
134305
+ }
134306
+
134307
+ // src/commands/yandex-analysis/render-report.ts
134308
+ import fs34 from "fs";
134309
+ import fsPromises7 from "fs/promises";
134310
+ import path45 from "path";
134311
+ import { fileURLToPath as fileURLToPath11 } from "url";
134312
+
134313
+ // src/commands/yandex-analysis/merge-snapshot.ts
134314
+ import * as fs33 from "fs/promises";
134315
+ import * as path44 from "path";
134316
+ function asRecord26(value) {
134317
+ if (value && typeof value === "object" && !Array.isArray(value)) {
134318
+ return value;
134319
+ }
134320
+ return null;
134321
+ }
134322
+ function num10(value) {
134323
+ const n = Number(value);
134324
+ return Number.isFinite(n) ? n : void 0;
134325
+ }
134326
+ function round25(value) {
134327
+ return value == null ? void 0 : Math.round(value * 100) / 100;
134328
+ }
134329
+ function asItems(payload) {
134330
+ const obj = asRecord26(payload);
134331
+ if (!obj) return [];
134332
+ const items2 = obj.items;
134333
+ if (!Array.isArray(items2)) return [];
134334
+ return items2.filter((r) => asRecord26(r) !== null);
134335
+ }
134336
+ function pickMetrics2(row, extras = []) {
134337
+ const out = {
134338
+ spend: num10(row.spend) ?? 0,
134339
+ impressions: num10(row.impressions) ?? 0,
134340
+ clicks: num10(row.clicks) ?? 0,
134341
+ ctr: num10(row.ctr),
134342
+ averageCpc: num10(row.averageCpc),
134343
+ conversions: num10(row.conversions) ?? 0,
134344
+ conversionRate: num10(row.conversionRate),
134345
+ costPerConversion: num10(row.costPerConversion),
134346
+ roi: num10(row.roi)
134347
+ };
134348
+ for (const f of extras) {
134349
+ if (row[f] !== void 0 && row[f] !== null) out[f] = row[f];
134350
+ }
134351
+ return out;
134352
+ }
134353
+ async function loadSectionPayload3(snapshotDir, file) {
134354
+ try {
134355
+ const raw = await fs33.readFile(path44.join(snapshotDir, file), "utf8");
134356
+ return JSON.parse(raw);
134357
+ } catch {
134358
+ return null;
134359
+ }
134360
+ }
134361
+ async function readManifest6(snapshotDir, accountId) {
134362
+ const candidates = [];
134363
+ if (accountId) candidates.push(reportManifestFile(accountId));
134364
+ try {
134365
+ const entries = await fs33.readdir(snapshotDir);
134366
+ for (const name2 of entries.filter(
134367
+ (n) => n.startsWith("report-manifest") && n.endsWith(".json")
134368
+ )) {
134369
+ if (!candidates.includes(name2)) candidates.push(name2);
134370
+ }
134371
+ } catch {
134372
+ }
134373
+ for (const name2 of candidates) {
134374
+ try {
134375
+ const raw = await fs33.readFile(path44.join(snapshotDir, name2), "utf8");
134376
+ const parsed = JSON.parse(raw);
134377
+ if (Array.isArray(parsed.artifacts) && parsed.artifacts.length > 0) return parsed;
134378
+ } catch {
134379
+ }
134380
+ }
134381
+ return null;
134382
+ }
134383
+ function parseYmd2(value) {
134384
+ const s = String(value ?? "").trim().slice(0, 10);
134385
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
134386
+ if (!m) return null;
134387
+ const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
134388
+ return Number.isNaN(d.getTime()) ? null : d;
134389
+ }
134390
+ function formatYmd2(d) {
134391
+ const y = d.getFullYear();
134392
+ const m = String(d.getMonth() + 1).padStart(2, "0");
134393
+ const day = String(d.getDate()).padStart(2, "0");
134394
+ return `${y}-${m}-${day}`;
134395
+ }
134396
+ function isoWeekKey(d) {
134397
+ const day = new Date(d.getFullYear(), d.getMonth(), d.getDate());
134398
+ const dow = (day.getDay() + 6) % 7;
134399
+ const weekStart = new Date(day);
134400
+ weekStart.setDate(weekStart.getDate() - dow);
134401
+ const thursday = new Date(weekStart);
134402
+ thursday.setDate(thursday.getDate() + 3);
134403
+ const yearStart = new Date(thursday.getFullYear(), 0, 1);
134404
+ const week = Math.floor((thursday.getTime() - yearStart.getTime()) / 864e5 / 7) + 1;
134405
+ const label = `W${String(week).padStart(2, "0")}`;
134406
+ return { key: `${thursday.getFullYear()}-${label}`, label, weekStart };
134407
+ }
134408
+ function aggregateDailyToWeekly(dailyRows) {
134409
+ const map = /* @__PURE__ */ new Map();
134410
+ for (const row of dailyRows) {
134411
+ const d = parseYmd2(row.date);
134412
+ if (!d) continue;
134413
+ const { key, label, weekStart } = isoWeekKey(d);
134414
+ let acc = map.get(key);
134415
+ if (!acc) {
134416
+ acc = {
134417
+ label,
134418
+ weekStart,
134419
+ minDate: d,
134420
+ maxDate: d,
134421
+ spend: 0,
134422
+ impressions: 0,
134423
+ clicks: 0,
134424
+ conversions: 0
134425
+ };
134426
+ map.set(key, acc);
134427
+ }
134428
+ if (d < acc.minDate) acc.minDate = d;
134429
+ if (d > acc.maxDate) acc.maxDate = d;
134430
+ acc.spend += num10(row.spend) ?? 0;
134431
+ acc.impressions += num10(row.impressions) ?? 0;
134432
+ acc.clicks += num10(row.clicks) ?? 0;
134433
+ acc.conversions += num10(row.conversions) ?? 0;
134434
+ }
134435
+ return [...map.values()].sort((a, b) => a.weekStart.getTime() - b.weekStart.getTime()).map((acc) => {
134436
+ const days = Math.floor((acc.maxDate.getTime() - acc.minDate.getTime()) / 864e5) + 1;
134437
+ return {
134438
+ weekLabel: acc.label,
134439
+ startDate: formatYmd2(acc.minDate),
134440
+ endDate: formatYmd2(acc.maxDate),
134441
+ days,
134442
+ spend: round25(acc.spend),
134443
+ impressions: Math.round(acc.impressions),
134444
+ clicks: Math.round(acc.clicks),
134445
+ conversions: round25(acc.conversions),
134446
+ ctr: acc.impressions > 0 ? acc.clicks / acc.impressions : void 0,
134447
+ averageCpc: acc.clicks > 0 ? round25(acc.spend / acc.clicks) : void 0,
134448
+ costPerConversion: acc.conversions > 0 ? round25(acc.spend / acc.conversions) : void 0
134449
+ };
134450
+ });
134451
+ }
134452
+ function mapDailyRows(payload) {
134453
+ return asItems(payload).map((r) => {
134454
+ const dateRaw = r.date;
134455
+ const date = typeof dateRaw === "string" ? dateRaw.slice(0, 10) : dateRaw != null ? String(dateRaw).slice(0, 10) : "";
134456
+ return pickMetrics2({ ...r, date }, ["date"]);
134457
+ });
134458
+ }
134459
+ function mapSearchRows(payload) {
134460
+ return asItems(payload).map((r) => pickMetrics2(r, ["network"]));
134461
+ }
134462
+ function mapCampaignRows2(payload) {
134463
+ return asItems(payload).map(
134464
+ (r) => pickMetrics2(r, [
134465
+ "campaignId",
134466
+ "campaignName",
134467
+ "status",
134468
+ "state",
134469
+ "campaignType",
134470
+ "budget",
134471
+ "strategy",
134472
+ "targetCpa",
134473
+ "network"
134474
+ ])
134475
+ );
134476
+ }
134477
+ function mapKeywordRows2(payload) {
134478
+ return asItems(payload).map(
134479
+ (r) => pickMetrics2(r, [
134480
+ "campaignId",
134481
+ "campaignName",
134482
+ "adGroupId",
134483
+ "keyword",
134484
+ "criteriaType",
134485
+ "network"
134486
+ ])
134487
+ );
134488
+ }
134489
+ function mapSearchTermRows2(payload) {
134490
+ return asItems(payload).map(
134491
+ (r) => pickMetrics2(r, [
134492
+ "searchQuery",
134493
+ "matchedKeyword",
134494
+ "campaignId",
134495
+ "campaignName",
134496
+ "adGroupId"
134497
+ ])
134498
+ );
134499
+ }
134500
+ function mapGeoRows(payload) {
134501
+ return asItems(payload).map(
134502
+ (r) => pickMetrics2(r, ["locationName", "targetingLocationName", "network"])
134503
+ );
134504
+ }
134505
+ function mapDeviceRows2(payload) {
134506
+ return asItems(payload).map((r) => pickMetrics2(r, ["device", "network"]));
134507
+ }
134508
+ async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
134509
+ const absDir = path44.resolve(snapshotDir);
134510
+ const metaAccountId = typeof asRecord26(payload.meta)?.accountId === "string" ? asRecord26(payload.meta)?.accountId : void 0;
134511
+ const manifest = await readManifest6(snapshotDir, metaAccountId);
134512
+ if (!manifest) {
134513
+ throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 report-manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C yandex-analysis --json-out`);
134514
+ }
134515
+ const sectionMap = /* @__PURE__ */ new Map();
134516
+ for (const art of manifest.artifacts ?? []) {
134517
+ const data = await loadSectionPayload3(absDir, art.file);
134518
+ if (data != null) sectionMap.set(art.section, data);
134519
+ }
134520
+ const overview = asRecord26(sectionMap.get("overview"));
134521
+ const totals = asRecord26(overview?.totals);
134522
+ const accountId = manifest.accountId ?? metaAccountId;
134523
+ const meta = { ...payload.meta ?? {} };
134524
+ if (!meta.accountId && accountId) meta.accountId = accountId;
134525
+ if (!meta.currency && typeof overview?.currency === "string") meta.currency = overview.currency;
134526
+ if (!meta.startDate && manifest.dateRange) meta.startDate = manifest.dateRange.start;
134527
+ if (!meta.endDate && manifest.dateRange) meta.endDate = manifest.dateRange.end;
134528
+ if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
134529
+ if (!meta.platform) meta.platform = "Yandex Direct";
134530
+ const kpis = { ...payload.kpis ?? {} };
134531
+ if (totals) {
134532
+ if (num10(totals.spend) != null) kpis.spend = round25(num10(totals.spend));
134533
+ if (num10(totals.impressions) != null) kpis.impressions = Math.round(num10(totals.impressions));
134534
+ if (num10(totals.clicks) != null) kpis.clicks = Math.round(num10(totals.clicks));
134535
+ if (num10(totals.conversions) != null) kpis.conversions = round25(num10(totals.conversions));
134536
+ if (num10(totals.ctr) != null) kpis.ctr = num10(totals.ctr);
134537
+ if (num10(totals.averageCpc) != null) kpis.averageCpc = round25(num10(totals.averageCpc));
134538
+ if (num10(totals.costPerConversion) != null) {
134539
+ kpis.costPerConversion = round25(num10(totals.costPerConversion));
134540
+ }
134541
+ if (num10(totals.conversionRate) != null) kpis.conversionRate = num10(totals.conversionRate);
134542
+ if (num10(totals.roi) != null) kpis.roi = num10(totals.roi);
134543
+ }
134544
+ if (overview) {
134545
+ if (num10(overview.balance) != null) kpis.balance = round25(num10(overview.balance));
134546
+ if (num10(overview.avgDailySpend) != null) kpis.avgDailySpend = round25(num10(overview.avgDailySpend));
134547
+ if (num10(overview.activeDays) != null) kpis.activeDays = num10(overview.activeDays);
134548
+ if (overview.balanceAsOf != null) kpis.balanceAsOf = overview.balanceAsOf;
134549
+ }
134550
+ if (kpis.balance != null && kpis.avgDailySpend != null && Number(kpis.avgDailySpend) > 0) {
134551
+ kpis.runwayDays = Math.round(Number(kpis.balance) / Number(kpis.avgDailySpend));
134552
+ }
134553
+ const tables = { ...payload.tables ?? {} };
134554
+ const dailyRows = mapDailyRows(sectionMap.get("daily"));
134555
+ if (dailyRows.length > 0) {
134556
+ tables.daily = dailyRows;
134557
+ tables.weekly = aggregateDailyToWeekly(dailyRows);
134558
+ }
134559
+ {
134560
+ const rows = mapSearchRows(sectionMap.get("search"));
134561
+ if (rows.length > 0) tables.search = rows;
134562
+ }
134563
+ {
134564
+ const rows = mapCampaignRows2(sectionMap.get("campaigns"));
134565
+ if (rows.length > 0) tables.campaigns = rows;
134566
+ }
134567
+ {
134568
+ const rows = mapKeywordRows2(sectionMap.get("keywords"));
134569
+ if (rows.length > 0) tables.keywords = rows;
134570
+ }
134571
+ {
134572
+ const rows = mapSearchTermRows2(sectionMap.get("search-terms"));
134573
+ if (rows.length > 0) tables.searchTerms = rows;
134574
+ }
134575
+ {
134576
+ const rows = mapGeoRows(sectionMap.get("geo"));
134577
+ if (rows.length > 0) tables.geo = rows;
134578
+ }
134579
+ {
134580
+ const rows = mapDeviceRows2(sectionMap.get("devices"));
134581
+ if (rows.length > 0) tables.devices = rows;
134582
+ }
134583
+ return { ...payload, meta, kpis, tables };
134584
+ }
134585
+
134586
+ // src/commands/yandex-analysis/report-content.ts
134587
+ var SECTION_KEYS4 = [
134588
+ "overview",
134589
+ "search",
134590
+ "campaigns",
134591
+ "keywords",
134592
+ "searchTerms",
134593
+ "geo",
134594
+ "devices",
134595
+ "daily"
134596
+ ];
134597
+ var SECTION_LABELS5 = {
134598
+ overview: "\u2460\u8D26\u6237\u603B\u89C8",
134599
+ search: "\u2461Search \u7F51\u7EDC",
134600
+ campaigns: "\u2462Campaign",
134601
+ keywords: "\u2463\u5173\u952E\u8BCD",
134602
+ searchTerms: "\u2464\u641C\u7D22\u8BCD",
134603
+ geo: "\u2465\u5730\u57DF",
134604
+ devices: "\u2466\u8BBE\u5907",
134605
+ daily: "\u2467\u65E5\u671F/\u5468\u62A5"
134606
+ };
134607
+ function textLen7(value) {
134608
+ return typeof value === "string" ? value.trim().length : 0;
134609
+ }
134610
+ function asArray7(value) {
134611
+ return Array.isArray(value) ? value : [];
134612
+ }
134613
+ function asRecord27(value) {
134614
+ if (value && typeof value === "object" && !Array.isArray(value)) {
134615
+ return value;
134616
+ }
134617
+ return null;
134618
+ }
134619
+ function pushMissing6(missing, id, chapter, dimension, hint) {
134620
+ missing.push({ id, chapter, dimension, hint });
134621
+ }
134622
+ function validateYandexPeriodReportContent(data) {
134623
+ const missing = [];
134624
+ const meta = asRecord27(data.meta) ?? {};
134625
+ const narrative = asRecord27(data.narrative) ?? {};
134626
+ const sections = asRecord27(narrative.sections) ?? {};
134627
+ const kpis = asRecord27(data.kpis) ?? {};
134628
+ if (!textLen7(meta.accountId)) {
134629
+ pushMissing6(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
134630
+ }
134631
+ const executiveSummary = asArray7(narrative.executiveSummary).filter((p) => textLen7(p) > 0);
134632
+ if (executiveSummary.length < 1) {
134633
+ pushMissing6(
134634
+ missing,
134635
+ "narrative-executive-summary",
134636
+ "\u6267\u884C\u6458\u8981",
134637
+ "narrative.executiveSummary",
134638
+ "\u22651 \u6BB5\u6267\u884C\u6458\u8981\uFF0C\u6982\u8FF0\u672C\u671F\u6D88\u8017/\u70B9\u51FB/\u8F6C\u5316/CTR/CPC \u8D70\u52BF"
134639
+ );
134640
+ }
134641
+ const sectionAnalysisCounts = {};
134642
+ const sectionSuggestionCounts = {};
134643
+ for (const key of SECTION_KEYS4) {
134644
+ const section = asRecord27(sections[key]);
134645
+ const analysis = asArray7(section?.analysis).filter((s) => textLen7(s) > 0);
134646
+ const suggestions = asArray7(section?.suggestions).filter((s) => textLen7(s) > 0);
134647
+ sectionAnalysisCounts[key] = analysis.length;
134648
+ sectionSuggestionCounts[key] = suggestions.length;
134649
+ if (analysis.length < 1) {
134650
+ pushMissing6(
134651
+ missing,
134652
+ `section-${key}-analysis`,
134653
+ SECTION_LABELS5[key],
134654
+ `narrative.sections.${key}.analysis`,
134655
+ `\u22651 \u6761\u603B\u7ED3\uFF08\u5F53\u524D ${analysis.length} \u6761\uFF09\uFF0C\u987B\u5199\u6E05\u73B0\u8C61+\u4F9D\u636E\u6570\u5B57`
134656
+ );
134657
+ }
134658
+ if (suggestions.length < 1) {
134659
+ pushMissing6(
134660
+ missing,
134661
+ `section-${key}-suggestions`,
134662
+ SECTION_LABELS5[key],
134663
+ `narrative.sections.${key}.suggestions`,
134664
+ `\u22651 \u6761\u53EF\u6267\u884C\u5EFA\u8BAE\uFF08\u5F53\u524D ${suggestions.length} \u6761\uFF09`
134665
+ );
134666
+ }
134667
+ }
134668
+ const recommendations = asArray7(narrative.recommendations).filter((r) => {
134669
+ if (typeof r === "string") return textLen7(r) > 0;
134670
+ const rec = asRecord27(r);
134671
+ return textLen7(rec?.content) > 0 || textLen7(rec?.text) > 0;
134672
+ });
134673
+ if (recommendations.length < 3) {
134674
+ pushMissing6(
134675
+ missing,
134676
+ "narrative-recommendations",
134677
+ "\u540E\u7EED\u4F18\u5316\u52A8\u4F5C",
134678
+ "narrative.recommendations",
134679
+ `\u22653 \u6761\u8DE8\u7EF4\u5EA6\u4F18\u5316\u5EFA\u8BAE\uFF08\u5F53\u524D ${recommendations.length} \u6761\uFF1B\u53EF\u7528 {priority,text,anchor}\uFF09`
134680
+ );
134681
+ }
134682
+ const hasKpi = Number.isFinite(Number(kpis.spend)) || Number.isFinite(Number(kpis.clicks)) || Number.isFinite(Number(kpis.impressions));
134683
+ if (!hasKpi) {
134684
+ pushMissing6(
134685
+ missing,
134686
+ "kpis-core",
134687
+ "KPI",
134688
+ "kpis.spend|clicks|impressions",
134689
+ "\u81F3\u5C11\u4E00\u9879\u6709\u6548\u6570\u5B57\uFF08\u8BF7\u4F20 --snapshot-dir \u7531 CLI \u4ECE overview \u5408\u5E76\uFF09"
134690
+ );
134691
+ }
134692
+ return {
134693
+ ok: missing.length === 0,
134694
+ missing,
134695
+ stats: {
134696
+ executiveSummaryCount: executiveSummary.length,
134697
+ recommendationCount: recommendations.length,
134698
+ sectionAnalysisCounts,
134699
+ sectionSuggestionCounts
134700
+ }
134701
+ };
134702
+ }
134703
+ function formatYandexPeriodReportErrors(result) {
134704
+ if (result.ok) return "";
134705
+ const lines = result.missing.map((m) => ` - [${m.chapter}] ${m.dimension}\uFF1A${m.hint}`);
134706
+ return [
134707
+ `\u62A5\u544A JSON \u7F3A\u5C11 ${result.missing.length} \u9879\u5FC5\u542B\u5185\u5BB9\uFF08\u5BF9\u9F50 yandex-period-report.md\uFF09\uFF1A`,
134708
+ ...lines,
134709
+ "",
134710
+ "\u8BF7\u8865\u5168 narrative.sections.<dimension>.{analysis,suggestions} \u4E0E narrative.recommendations \u540E\u91CD\u65B0\u6267\u884C render\u3002",
134711
+ "\u6570\u503C\u7C7B\u5B57\u6BB5\uFF08kpis/tables\uFF09\u987B\u7531 --snapshot-dir \u4ECE\u843D\u76D8 JSON \u5408\u5E76\uFF1BAgent \u53EA\u64B0\u5199\u53D9\u4E8B\uFF0C\u7981\u6B62\u624B\u5199 HTML\u3002"
134712
+ ].join("\n");
134713
+ }
134714
+
134715
+ // src/commands/yandex-analysis/render-report.ts
134716
+ var TEMPLATE_BASENAME5 = "yandex-period-report.html";
134717
+ var INJECT_MARKER6 = "window.__YANDEX_PERIOD_REPORT__ = window.__YANDEX_PERIOD_REPORT__ || null;";
134718
+ function resolveSkillTemplatePath8(basename11) {
134719
+ const dir = path45.dirname(fileURLToPath11(import.meta.url));
134720
+ const candidates = [
134721
+ path45.join(dir, "skill", "report-templates", basename11),
134722
+ path45.join(dir, "skill", "siluzan-ads", "report-templates", basename11),
134723
+ path45.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename11)
134724
+ ];
134725
+ for (const p of candidates) {
134726
+ if (fs34.existsSync(p)) return p;
134727
+ }
134728
+ return candidates[0];
134729
+ }
134730
+ function yandexPeriodReportTemplatePath() {
134731
+ return resolveSkillTemplatePath8(TEMPLATE_BASENAME5);
134732
+ }
134733
+ async function readJsonFile7(filePath) {
134734
+ const text = await fsPromises7.readFile(filePath, "utf8");
134735
+ try {
134736
+ return JSON.parse(text);
134737
+ } catch {
134738
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
134739
+ }
134740
+ }
134741
+ function asRecord28(value) {
134742
+ if (value && typeof value === "object" && !Array.isArray(value)) {
134743
+ return value;
134744
+ }
134745
+ throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
134746
+ }
134747
+ function injectReportData7(html, payload) {
134748
+ const json = JSON.stringify(payload).replace(/</g, "\\u003c");
134749
+ if (!html.includes(INJECT_MARKER6)) {
134750
+ throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 yandex-period-report.html");
134751
+ }
134752
+ return html.replace(INJECT_MARKER6, `window.__YANDEX_PERIOD_REPORT__ = ${json};`);
134753
+ }
134754
+ async function runYandexAnalysisRender(opts) {
134755
+ const dataPath = path45.resolve(opts.dataFile);
134756
+ let data = asRecord28(await readJsonFile7(dataPath));
134757
+ if (opts.snapshotDir?.trim()) {
134758
+ data = await mergeYandexAnalysisSnapshotIntoReport(data, path45.resolve(opts.snapshotDir));
134759
+ }
134760
+ const contentCheck = validateYandexPeriodReportContent(data);
134761
+ if (!contentCheck.ok) {
134762
+ console.error(`
134763
+ \u274C ${formatYandexPeriodReportErrors(contentCheck)}
134764
+ `);
134765
+ const { stats: stats2 } = contentCheck;
134766
+ const sectionSummary = Object.entries(stats2.sectionAnalysisCounts).map(([key, count]) => `${key} \u603B\u7ED3${count}/\u5EFA\u8BAE${stats2.sectionSuggestionCounts[key] ?? 0}`).join("\uFF5C");
134767
+ console.error(
134768
+ ` \u7EDF\u8BA1\uFF1A\u6267\u884C\u6458\u8981 ${stats2.executiveSummaryCount} \u6BB5\uFF5C\u4F18\u5316\u5EFA\u8BAE ${stats2.recommendationCount} \u6761\uFF5C${sectionSummary}
134769
+ `
134770
+ );
134771
+ process.exit(1);
134772
+ }
134773
+ const templatePath = yandexPeriodReportTemplatePath();
134774
+ let html;
134775
+ try {
134776
+ html = await fsPromises7.readFile(templatePath, "utf8");
134777
+ } catch {
134778
+ console.error(`
134779
+ \u274C \u672A\u627E\u5230\u62A5\u544A\u6A21\u677F\uFF1A${templatePath}
134780
+ \u8BF7\u5148\u6267\u884C npm run build
134781
+ `);
134782
+ process.exit(1);
134783
+ }
134784
+ const outPath = path45.resolve(
134785
+ opts.out ?? path45.join(path45.dirname(dataPath), "yandex-period-report.html")
134786
+ );
134787
+ const outDir = path45.dirname(outPath);
134788
+ await fsPromises7.mkdir(outDir, { recursive: true });
134789
+ await fsPromises7.writeFile(outPath, injectReportData7(html, data), "utf8");
134790
+ const { stats } = contentCheck;
134791
+ console.log(`
134792
+ \u2705 Yandex \u5E7F\u544A\u8D26\u6237\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
134793
+ `);
134794
+ console.log(
134795
+ ` \u5FC5\u542B\u5B57\u6BB5\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C\u6267\u884C\u6458\u8981 ${stats.executiveSummaryCount} \u6BB5\uFF5C\u4F18\u5316\u5EFA\u8BAE ${stats.recommendationCount} \u6761\uFF5C8 \u4E2A\u5206\u6790\u7AE0\u8282\u5747\u5DF2\u9F50\u5168
134796
+ `
134797
+ );
134798
+ console.log("\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u5B8C\u6574\u7AE0\u8282\uFF08\u6837\u5F0F\u5BF9\u9F50\u5F00\u53D1\u6837\u4F8B\uFF09\u3002\n");
134799
+ }
134800
+
134801
+ // src/commands/yandex-analysis/register-cli.ts
134802
+ function registerBatchCommand4(sectionHelp, aliasHelp) {
134803
+ return new Command5("run").description(
134804
+ "\u6279\u91CF\u62C9\u53D6 YandexAPI /yandex-analysis \u5404\u7EF4\u5EA6\uFF08\u7701\u7565\u5B50\u547D\u4EE4\u540D\u65F6\u9ED8\u8BA4\u6267\u884C\u672C\u547D\u4EE4\uFF09"
134805
+ ).requiredOption(
134806
+ "-a, --account <id>",
134807
+ "Yandex Client-Login / mediaCustomerId\uFF08\u5982 porg-xxx\uFF0C\u4E0E list-accounts -m Yandex \u4E00\u81F4\uFF09"
134808
+ ).requiredOption(
134809
+ "--json-out <dir>",
134810
+ "\u7ED3\u679C\u843D\u76D8\u76EE\u5F55\uFF1B\u6BCF\u7EF4\u5EA6 `<section>-<accountId>.json` + `report-manifest-<accountId>.json`"
134811
+ ).option("-t, --token <token>", "Auth Token").option(
134812
+ "--start <date>",
134813
+ "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\u7565\uFF1B\u4E0D\u80FD\u665A\u4E8E\u4ECA\u5929\uFF1B\u9ED8\u8BA4\u8FD1 7 \u5929\u542B\u4ECA\u5929\uFF09"
134814
+ ).option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option(
134815
+ "--sections <list>",
134816
+ `\u4EC5\u6267\u884C\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u5982 overview,campaigns,keywords\uFF1B\u7701\u7565=\u5168\u90E8 8 \u4E2A\u3002\u53EF\u9009\uFF1A${sectionHelp}\uFF1B\u522B\u540D\uFF1A${aliasHelp}`
134817
+ ).option("--exclude <list>", "\u6392\u9664\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u4E0E --sections \u53EF\u53E0\u52A0").option(
134818
+ "--limit <n>",
134819
+ "\u4EC5 campaigns / keywords / search-terms / geo\uFF1A\u6761\u6570\u4E0A\u9650\uFF08\u9ED8\u8BA4 100\uFF0C\u6700\u5927 10000\uFF09",
134820
+ (v) => parseInt(v, 10)
134821
+ ).option("--concurrency <n>", "\u5E76\u53D1\u6570\uFF0C\u9ED8\u8BA4 5\uFF0C\u4E0A\u9650 16", (v) => parseInt(v, 10)).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
134822
+ await runAllYandexSections(opts);
134823
+ });
134824
+ }
134825
+ function registerYandexAnalysisCommands(program2) {
134826
+ const sectionHelp = YANDEX_SECTION_NAMES.join(", ");
134827
+ const aliasHelp = Object.keys(YANDEX_SECTION_ALIASES).join(", ");
134828
+ const root = program2.command("yandex-analysis").description(
134829
+ "Yandex \u8D26\u6237\u5206\u6790\uFF1A\u6279\u91CF\u62C9\u53D6 /yandex-analysis \u53EA\u8BFB\u6570\u636E\uFF08\u9ED8\u8BA4 run\uFF09\uFF0C\u6216\u7531 render \u751F\u6210 yandex-period-report.html \u7EC8\u7A3F\uFF08\u91D1\u989D\u5DF2\u4E3A\u8D26\u6237\u5E01\u79CD\u5C0F\u6570\uFF1Bsearch-terms \u4EC5\u8FD1 180 \u5929\uFF09"
134830
+ );
134831
+ root.addCommand(registerBatchCommand4(sectionHelp, aliasHelp), { isDefault: true });
134832
+ root.command("render").description(
134833
+ "\u6839\u636E Agent \u64B0\u5199\u7684\u62A5\u544A JSON \u751F\u6210 Yandex \u5E7F\u544A\u8D26\u6237\u5206\u6790 HTML \u7EC8\u7A3F\uFF08\u6821\u9A8C 8 \u4E2A\u5206\u6790\u7AE0\u8282\u5FC5\u542B\u5B57\u6BB5\uFF0C\u7F3A\u9879\u62D2\u7EDD\u6E32\u67D3\uFF09"
134834
+ ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 yandex-period-report.json").option(
134835
+ "--snapshot-dir <dir>",
134836
+ "\u53EF\u9009\uFF1Ayandex-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u8865\u5168 meta / kpis / tables"
134837
+ ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
134838
+ await runYandexAnalysisRender({
134839
+ dataFile: opts.data,
134840
+ snapshotDir: opts.snapshotDir,
134841
+ out: opts.out
134842
+ });
134843
+ });
134844
+ }
134845
+
133964
134846
  // src/index.ts
133965
134847
  init_version();
133966
134848
  init_cli_json_snapshot();
@@ -134035,9 +134917,9 @@ function interceptAgentCompatArgv(argv) {
134035
134917
  installProcessHandlers();
134036
134918
  function getVersion() {
134037
134919
  try {
134038
- const __dirname3 = path43.dirname(fileURLToPath11(import.meta.url));
134039
- const pkgPath = path43.join(__dirname3, "..", "package.json");
134040
- const pkg = JSON.parse(fs33.readFileSync(pkgPath, "utf8"));
134920
+ const __dirname3 = path46.dirname(fileURLToPath12(import.meta.url));
134921
+ const pkgPath = path46.join(__dirname3, "..", "package.json");
134922
+ const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf8"));
134041
134923
  return pkg.version ?? "0.0.0";
134042
134924
  } catch {
134043
134925
  return "0.0.0";
@@ -134056,9 +134938,9 @@ configureCliJsonSnapshot({
134056
134938
  ]
134057
134939
  });
134058
134940
  configureSiluzanClientIdentity({ client: "tso-cli", resolveVersion: getVersion });
134059
- var program = new Command5();
134941
+ var program = new Command6();
134060
134942
  program.name("siluzan-tso").description(
134061
- "Siluzan \u5E7F\u544A\u8D26\u6237\u7BA1\u7406\uFF1A\u8D26\u6237\u67E5\u8BE2\u3001\u4F59\u989D\u3001\u6295\u653E\u6570\u636E\u3001\u5F00\u6237\u7533\u8BF7\uFF08Google/TikTok/Yandex/Bing/Kwai\uFF09\u3001\n\u8D26\u53F7\u5206\u4EAB/\u89E3\u7ED1\u3001\u4F18\u5316\u62A5\u544A\u3001Google \u8D26\u6237\u5206\u6790\u7F51\u5173\uFF08google-analysis\uFF09\u3001Google \u5E7F\u544A\u8BCA\u65AD\u62A5\u544A\uFF08google-ads-diagnosis render\uFF09\u3001\u7F51\u7AD9\u8BCA\u65AD\uFF08website-diagnosis\uFF09\u3001\u6218\u7565\u5E02\u573A\u5206\u6790\uFF08market-analysis\uFF0CAgent \u751F\u6210\u62A5\u544A\uFF09\u3001\u5145\u503C\u8F6C\u8D26\u3001\u5F00\u7968\u3001\u667A\u80FD\u9884\u8B66\u3001Google \u5E7F\u544A\u7BA1\u7406\uFF08\u542B\u5F02\u6B65\u6279\u91CF\uFF09\u3002"
134943
+ "Siluzan \u5E7F\u544A\u8D26\u6237\u7BA1\u7406\uFF1A\u8D26\u6237\u67E5\u8BE2\u3001\u4F59\u989D\u3001\u6295\u653E\u6570\u636E\u3001\u5F00\u6237\u7533\u8BF7\uFF08Google/TikTok/Yandex/Bing/Kwai\uFF09\u3001\n\u8D26\u53F7\u5206\u4EAB/\u89E3\u7ED1\u3001\u4F18\u5316\u62A5\u544A\u3001Google/Meta/TikTok/Bing/Yandex \u8D26\u6237\u5206\u6790\u3001Google \u5E7F\u544A\u8BCA\u65AD\u62A5\u544A\uFF08google-ads-diagnosis render\uFF09\u3001\u7F51\u7AD9\u8BCA\u65AD\uFF08website-diagnosis\uFF09\u3001\u6218\u7565\u5E02\u573A\u5206\u6790\uFF08market-analysis\uFF0CAgent \u751F\u6210\u62A5\u544A\uFF09\u3001\u5145\u503C\u8F6C\u8D26\u3001\u5F00\u7968\u3001\u667A\u80FD\u9884\u8B66\u3001Google \u5E7F\u544A\u7BA1\u7406\uFF08\u542B\u5F02\u6B65\u6279\u91CF\uFF09\u3002"
134062
134944
  ).version(getVersion());
134063
134945
  program.option("--commit <text>", "\u5199\u64CD\u4F5C\u8BF4\u660E\uFF1B\u8BB0\u5165\u672C\u673A\u5199\u5BA1\u8BA1 JSONL");
134064
134946
  program.hook("preAction", async () => {
@@ -134093,6 +134975,7 @@ var REGISTRARS = [
134093
134975
  registerFacebookAnalysisCommands,
134094
134976
  registerBingAnalysisCommands,
134095
134977
  registerTikTokAnalysisCommands,
134978
+ registerYandexAnalysisCommands,
134096
134979
  register15,
134097
134980
  register16,
134098
134981
  register17,