siluzan-tso-cli 1.1.44 → 1.1.45-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +924 -70
  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-list.md +1 -1
  7. package/dist/skill/references/accounts/accounts-permissions.md +2 -2
  8. package/dist/skill/references/accounts/finance.md +1 -1
  9. package/dist/skill/references/accounts/open-account-by-media.md +1 -1
  10. package/dist/skill/references/accounts/open-account-google-ui.md +1 -1
  11. package/dist/skill/references/analytics/account-analytics.md +44 -1
  12. package/dist/skill/references/analytics/rag.md +1 -1
  13. package/dist/skill/references/analytics/reporting.md +5 -5
  14. package/dist/skill/references/core/setup.md +5 -5
  15. package/dist/skill/references/misc/tso-home.md +2 -2
  16. package/dist/skill/references/report-templates/README.md +2 -0
  17. package/dist/skill/references/report-templates/REPORT-WORKFLOW.md +6 -4
  18. package/dist/skill/references/report-templates/yandex-period-report-excel.md +123 -0
  19. package/dist/skill/references/report-templates/yandex-period-report.md +121 -0
  20. package/dist/skill/report-templates/README.md +2 -0
  21. package/dist/skill/report-templates/REPORT-WORKFLOW.md +6 -4
  22. package/dist/skill/report-templates/yandex-period-report-excel.md +123 -0
  23. package/dist/skill/report-templates/yandex-period-report.html +968 -0
  24. package/dist/skill/report-templates/yandex-period-report.md +121 -0
  25. package/dist/skill/scripts/install.ps1 +3 -3
  26. package/dist/skill/scripts/install.sh +3 -3
  27. 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();
@@ -112413,7 +112417,7 @@ function cloneKeywordBlockShell(block) {
112413
112417
  delete shell["matchTypeV2"];
112414
112418
  return shell;
112415
112419
  }
112416
- function splitKeywordsForBatchJobBlockIfMixed(block, path44, warnings) {
112420
+ function splitKeywordsForBatchJobBlockIfMixed(block, path47, warnings) {
112417
112421
  const declaredUi = matchTypeV2ToUi(readBlockMatchTypeRaw(block));
112418
112422
  const entries = collectKeywordEntriesFromBlock(block);
112419
112423
  if (entries.length === 0) return [block];
@@ -112432,7 +112436,7 @@ function splitKeywordsForBatchJobBlockIfMixed(block, path44, warnings) {
112432
112436
  (ui) => matchTypeUiToV2(ui)
112433
112437
  );
112434
112438
  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`
112439
+ `${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
112440
  );
112437
112441
  const shell = cloneKeywordBlockShell(block);
112438
112442
  const splitBlocks = [];
@@ -112452,16 +112456,16 @@ function splitKeywordsForBatchJobBlockIfMixed(block, path44, warnings) {
112452
112456
  }
112453
112457
  return splitBlocks;
112454
112458
  }
112455
- function validateKeywordCore(core, path44, errors, lengthViolations) {
112459
+ function validateKeywordCore(core, path47, errors, lengthViolations) {
112456
112460
  const trimmed = core.trim();
112457
112461
  if (!trimmed) {
112458
- errors.push(`${path44} \u5173\u952E\u8BCD\u8BCD\u5E72\u4E0D\u80FD\u4E3A\u7A7A`);
112462
+ errors.push(`${path47} \u5173\u952E\u8BCD\u8BCD\u5E72\u4E0D\u80FD\u4E3A\u7A7A`);
112459
112463
  return false;
112460
112464
  }
112461
112465
  if (trimmed.length > GOOGLE_KEYWORD_MAX_CORE_LENGTH) {
112462
112466
  if (lengthViolations) {
112463
112467
  pushLengthViolation(lengthViolations, {
112464
- path: path44,
112468
+ path: path47,
112465
112469
  field: "KeywordText",
112466
112470
  kind: "keyword_core",
112467
112471
  limit: GOOGLE_KEYWORD_MAX_CORE_LENGTH,
@@ -112471,13 +112475,13 @@ function validateKeywordCore(core, path44, errors, lengthViolations) {
112471
112475
  });
112472
112476
  }
112473
112477
  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}"`
112478
+ `${path47} \u5173\u952E\u8BCD\u8BCD\u5E72\u8D85\u8FC7 ${GOOGLE_KEYWORD_MAX_CORE_LENGTH} \u5B57\u7B26\uFF08\u5F53\u524D ${trimmed.length}\uFF09\uFF1A"${trimmed}"`
112475
112479
  );
112476
112480
  return false;
112477
112481
  }
112478
112482
  if (!VALID_CORE_REGEX.test(trimmed)) {
112479
112483
  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" : ""}"`
112484
+ `${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
112485
  );
112482
112486
  return false;
112483
112487
  }
@@ -112534,24 +112538,24 @@ function canonicalizeKeywordBatchBlock(block, resolvedMatchV2, normalizedTexts,
112534
112538
  }
112535
112539
  }
112536
112540
  }
112537
- function pushKeywordAutoFixWarning(warnings, path44, fieldLabel, trimmedRaw, formatted, declaredUi, beforeUi, matchType, inferredMatchType) {
112541
+ function pushKeywordAutoFixWarning(warnings, path47, fieldLabel, trimmedRaw, formatted, declaredUi, beforeUi, matchType, inferredMatchType) {
112538
112542
  if (inferredMatchType) {
112539
112543
  warnings.push(
112540
- `${path44}.${fieldLabel} \u672A\u6307\u5B9A MatchTypeV2\uFF0C\u5DF2\u6309\u8BCD\u9762\u63A8\u65AD\u4E3A ${matchTypeUiToV2(matchType)}`
112544
+ `${path47}.${fieldLabel} \u672A\u6307\u5B9A MatchTypeV2\uFF0C\u5DF2\u6309\u8BCD\u9762\u63A8\u65AD\u4E3A ${matchTypeUiToV2(matchType)}`
112541
112545
  );
112542
112546
  return;
112543
112547
  }
112544
112548
  if (declaredUi && beforeUi !== declaredUi) {
112545
112549
  warnings.push(
112546
- `${path44}.${fieldLabel} MatchTypeV2=${matchTypeUiToV2(declaredUi)} \u4E0E\u8BCD\u9762\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u4FEE\u590D\u4E3A\uFF1A${formatted}`
112550
+ `${path47}.${fieldLabel} MatchTypeV2=${matchTypeUiToV2(declaredUi)} \u4E0E\u8BCD\u9762\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u4FEE\u590D\u4E3A\uFF1A${formatted}`
112547
112551
  );
112548
112552
  return;
112549
112553
  }
112550
112554
  if (formatted !== trimmedRaw) {
112551
- warnings.push(`${path44}.${fieldLabel} \u5DF2\u81EA\u52A8\u4FEE\u590D\u8BCD\u9762\uFF1A${trimmedRaw} \u2192 ${formatted}`);
112555
+ warnings.push(`${path47}.${fieldLabel} \u5DF2\u81EA\u52A8\u4FEE\u590D\u8BCD\u9762\uFF1A${trimmedRaw} \u2192 ${formatted}`);
112552
112556
  }
112553
112557
  }
112554
- function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, errors, warnings, lengthViolations) {
112558
+ function normalizeKeywordTextList(texts, matchTypeRaw, path47, fieldLabel, errors, warnings, lengthViolations) {
112555
112559
  const declaredUi = matchTypeV2ToUi(matchTypeRaw);
112556
112560
  const normalized = [];
112557
112561
  const seen = /* @__PURE__ */ new Set();
@@ -112559,7 +112563,7 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, error
112559
112563
  for (let k = 0; k < texts.length; k++) {
112560
112564
  const raw = texts[k];
112561
112565
  if (typeof raw !== "string" || !raw.trim()) {
112562
- errors.push(`${path44}.${fieldLabel}[${k}] \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
112566
+ errors.push(`${path47}.${fieldLabel}[${k}] \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
112563
112567
  continue;
112564
112568
  }
112565
112569
  const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
@@ -112570,7 +112574,7 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, error
112570
112574
  );
112571
112575
  pushKeywordAutoFixWarning(
112572
112576
  warnings,
112573
- path44,
112577
+ path47,
112574
112578
  fieldLabel,
112575
112579
  trimmedRaw,
112576
112580
  formatted,
@@ -112580,11 +112584,11 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, error
112580
112584
  inferredMatchType
112581
112585
  );
112582
112586
  const core = unwrapKeywordDisplayTextForEdit(formatted);
112583
- if (!validateKeywordCore(core, `${path44}.${fieldLabel}[${k}]`, errors, lengthViolations))
112587
+ if (!validateKeywordCore(core, `${path47}.${fieldLabel}[${k}]`, errors, lengthViolations))
112584
112588
  continue;
112585
112589
  const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
112586
112590
  if (seen.has(dedupeKey)) {
112587
- warnings.push(`${path44}.${fieldLabel}[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
112591
+ warnings.push(`${path47}.${fieldLabel}[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
112588
112592
  continue;
112589
112593
  }
112590
112594
  seen.add(dedupeKey);
@@ -112593,12 +112597,12 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path44, fieldLabel, error
112593
112597
  }
112594
112598
  return { normalized, resolvedUi };
112595
112599
  }
112596
- function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, lengthViolations) {
112600
+ function normalizeKeywordsForBatchJobBlock(block, path47, errors, warnings, lengthViolations) {
112597
112601
  const matchTypeRaw = readBlockMatchTypeRaw(block);
112598
112602
  const declaredUi = matchTypeV2ToUi(matchTypeRaw);
112599
112603
  if (matchTypeRaw !== void 0 && declaredUi === null) {
112600
112604
  errors.push(
112601
- `${path44}.MatchTypeV2 \u65E0\u6548\uFF08${String(matchTypeRaw)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1ABROAD | PHRASE | EXACT`
112605
+ `${path47}.MatchTypeV2 \u65E0\u6548\uFF08${String(matchTypeRaw)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1ABROAD | PHRASE | EXACT`
112602
112606
  );
112603
112607
  return;
112604
112608
  }
@@ -112613,7 +112617,7 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
112613
112617
  const result = normalizeKeywordTextList(
112614
112618
  texts,
112615
112619
  matchTypeRaw,
112616
- path44,
112620
+ path47,
112617
112621
  "KeywordText",
112618
112622
  errors,
112619
112623
  warnings,
@@ -112622,7 +112626,7 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
112622
112626
  normalizedTexts = result.normalized;
112623
112627
  if (result.resolvedUi) resolvedUi = result.resolvedUi;
112624
112628
  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`);
112629
+ errors.push(`${path47}.KeywordText \u7ECF\u6821\u9A8C\u540E\u65E0\u6709\u6548\u5173\u952E\u8BCD\uFF0C\u8BF7\u4FEE\u6B63\u8BCD\u9762\u6216 MatchTypeV2`);
112626
112630
  }
112627
112631
  }
112628
112632
  if (hasItems && items2) {
@@ -112630,12 +112634,12 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
112630
112634
  for (let k = 0; k < items2.length; k++) {
112631
112635
  const item = items2[k];
112632
112636
  if (!item || typeof item !== "object") {
112633
- errors.push(`${path44}.Items[${k}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
112637
+ errors.push(`${path47}.Items[${k}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
112634
112638
  continue;
112635
112639
  }
112636
112640
  const raw = readItemText(item);
112637
112641
  if (raw === null || !raw.trim()) {
112638
- errors.push(`${path44}.Items[${k}].Text \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
112642
+ errors.push(`${path47}.Items[${k}].Text \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
112639
112643
  continue;
112640
112644
  }
112641
112645
  const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
@@ -112646,7 +112650,7 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
112646
112650
  );
112647
112651
  pushKeywordAutoFixWarning(
112648
112652
  warnings,
112649
- path44,
112653
+ path47,
112650
112654
  `Items[${k}].Text`,
112651
112655
  trimmedRaw,
112652
112656
  formatted,
@@ -112656,11 +112660,11 @@ function normalizeKeywordsForBatchJobBlock(block, path44, errors, warnings, leng
112656
112660
  inferredMatchType
112657
112661
  );
112658
112662
  const core = unwrapKeywordDisplayTextForEdit(formatted);
112659
- if (!validateKeywordCore(core, `${path44}.Items[${k}].Text`, errors, lengthViolations))
112663
+ if (!validateKeywordCore(core, `${path47}.Items[${k}].Text`, errors, lengthViolations))
112660
112664
  continue;
112661
112665
  const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
112662
112666
  if (seen.has(dedupeKey)) {
112663
- warnings.push(`${path44}.Items[${k}].Text \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
112667
+ warnings.push(`${path47}.Items[${k}].Text \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
112664
112668
  continue;
112665
112669
  }
112666
112670
  seen.add(dedupeKey);
@@ -112805,16 +112809,16 @@ function warnIfNegativeKeywordsInPositiveKeywords(campaign, warnings) {
112805
112809
  const raw = texts[ki];
112806
112810
  if (typeof raw !== "string" || !raw.trim()) continue;
112807
112811
  const core = normalizeKeywordCoreForNegativeCheck(raw);
112808
- const path44 = `campaign.AdGroupsForBatchJob[${gi}].KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
112812
+ const path47 = `campaign.AdGroupsForBatchJob[${gi}].KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
112809
112813
  if (plannedNegCores.has(core)) {
112810
112814
  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`
112815
+ `${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
112816
  );
112813
112817
  continue;
112814
112818
  }
112815
112819
  if (looksLikeStandaloneNegativeKeyword(core)) {
112816
112820
  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`
112821
+ `${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
112822
  );
112819
112823
  }
112820
112824
  }
@@ -116445,13 +116449,13 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116445
116449
  for (let ki = 0; ki < texts.length; ki++) {
116446
116450
  const t = texts[ki];
116447
116451
  if (typeof t !== "string" || !t.trim()) continue;
116448
- const path44 = `${groupPath}.KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
116452
+ const path47 = `${groupPath}.KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
116449
116453
  const key = keywordKey(t, matchTypeV2);
116450
116454
  const plannedContent = `\u8BCD\u9762: ${t} | \u5339\u914D: ${matchTypeV2}`;
116451
116455
  if (groupAbsent) {
116452
116456
  pushItem(items2, {
116453
116457
  layer: "keyword",
116454
- path: path44,
116458
+ path: path47,
116455
116459
  adGroupName: groupName,
116456
116460
  plannedContent,
116457
116461
  status: "skipped",
@@ -116463,7 +116467,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116463
116467
  if (!liveKwKeys.has(key)) {
116464
116468
  pushItem(items2, {
116465
116469
  layer: "keyword",
116466
- path: path44,
116470
+ path: path47,
116467
116471
  adGroupName: groupName,
116468
116472
  plannedContent,
116469
116473
  status: "missing",
@@ -116473,7 +116477,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116473
116477
  } else {
116474
116478
  pushItem(items2, {
116475
116479
  layer: "keyword",
116476
- path: path44,
116480
+ path: path47,
116477
116481
  adGroupName: groupName,
116478
116482
  plannedContent,
116479
116483
  status: "ok",
@@ -116498,14 +116502,14 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116498
116502
  for (let ai = 0; ai < ads.length; ai++) {
116499
116503
  const ad = asRecord3(ads[ai]);
116500
116504
  if (!ad) continue;
116501
- const path44 = `${groupPath}.AdsForBatchJob[${ai}]`;
116505
+ const path47 = `${groupPath}.AdsForBatchJob[${ai}]`;
116502
116506
  const primary = pickString2(ad["headlinePart1"], ad["AdTitle"]);
116503
116507
  const finalUrl = pickString2(ad["Finalurl"], ad["DestinationUrl"], ad["finalUrl"]);
116504
116508
  const rsaAssets = listPlannedRsaAssets(ad, groupPath, ai, groupName);
116505
116509
  if (groupAbsent) {
116506
116510
  pushItem(items2, {
116507
116511
  layer: "ad",
116508
- path: path44,
116512
+ path: path47,
116509
116513
  adGroupName: groupName,
116510
116514
  plannedContent: primary ? `RSA \u9996\u6807\u9898: ${primary}${finalUrl ? ` | \u843D\u5730\u9875: ${finalUrl}` : ""}` : `AdsForBatchJob[${ai}]\uFF08\u7F3A\u5C11 headlinePart1\uFF09`,
116511
116515
  status: "skipped",
@@ -116529,7 +116533,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116529
116533
  if (!primary) {
116530
116534
  pushItem(items2, {
116531
116535
  layer: "ad",
116532
- path: path44,
116536
+ path: path47,
116533
116537
  adGroupName: groupName,
116534
116538
  plannedContent: `AdsForBatchJob[${ai}]\uFF08\u7F3A\u5C11 headlinePart1\uFF0C\u65E0\u6CD5\u6BD4\u5BF9\u6574\u6761 RSA\uFF09`,
116535
116539
  status: "missing",
@@ -116555,7 +116559,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116555
116559
  const rsaFound = liveAdsInGroup.some((la) => liveAdHasHeadline(la, primary));
116556
116560
  pushItem(items2, {
116557
116561
  layer: "ad",
116558
- path: path44,
116562
+ path: path47,
116559
116563
  adGroupName: groupName,
116560
116564
  plannedContent: `RSA \u9996\u6807\u9898: ${primary}${finalUrl ? ` | \u843D\u5730\u9875: ${finalUrl}` : ""}`,
116561
116565
  status: rsaFound ? "ok" : "missing",
@@ -116621,11 +116625,11 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116621
116625
  for (let ei = 0; ei < extBlocks.length; ei++) {
116622
116626
  const ext = asRecord3(extBlocks[ei]);
116623
116627
  const plannedContent = formatExtensionPlanned(ext);
116624
- const path44 = `campaign.ExtensionsForBatchJob[${ei}]`;
116628
+ const path47 = `campaign.ExtensionsForBatchJob[${ei}]`;
116625
116629
  if (campaignMissing) {
116626
116630
  pushItem(items2, {
116627
116631
  layer: "extension",
116628
- path: path44,
116632
+ path: path47,
116629
116633
  plannedContent,
116630
116634
  status: "skipped",
116631
116635
  liveNote: "\u7CFB\u5217\u672A\u627E\u5230\uFF0C\u8DF3\u8FC7\u9644\u52A0\u4FE1\u606F\u6BD4\u5BF9",
@@ -116637,7 +116641,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116637
116641
  const remediateCommand = !found && ext && accountId && campaignId ? buildExtensionRemediateCommandLine({ accountId, campaignId, ext }) ?? void 0 : void 0;
116638
116642
  pushItem(items2, {
116639
116643
  layer: "extension",
116640
- path: path44,
116644
+ path: path47,
116641
116645
  plannedContent,
116642
116646
  status: found ? "ok" : "missing",
116643
116647
  liveNote: `\u7CFB\u5217\u7EA7\u9644\u52A0\u4FE1\u606F\u8D26\u6237\u5185 ${liveExt}/${plannedExt} \u6761`,
@@ -116664,11 +116668,11 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116664
116668
  if (!id) continue;
116665
116669
  const displayName = locationNames[li] || pickString2(loc["locationName"], loc["name"], loc["countryOrRegion"]) || "";
116666
116670
  const plannedContent = `locationId: ${id}${displayName ? ` | \u540D\u79F0: ${displayName}` : ""}`;
116667
- const path44 = `campaign.targetedLocations[${li}]`;
116671
+ const path47 = `campaign.targetedLocations[${li}]`;
116668
116672
  if (campaignMissing) {
116669
116673
  pushItem(items2, {
116670
116674
  layer: "location",
116671
- path: path44,
116675
+ path: path47,
116672
116676
  plannedContent,
116673
116677
  status: "skipped",
116674
116678
  liveNote: "\u7CFB\u5217\u672A\u627E\u5230\uFF0C\u8DF3\u8FC7\u5730\u57DF\u6BD4\u5BF9",
@@ -116680,7 +116684,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
116680
116684
  const remediateCommand = !found && accountId && campaignId ? buildGeoRemediateCommandLine({ accountId, campaignId, locationId: id }) : void 0;
116681
116685
  pushItem(items2, {
116682
116686
  layer: "location",
116683
- path: path44,
116687
+ path: path47,
116684
116688
  plannedContent,
116685
116689
  status: found ? "ok" : "missing",
116686
116690
  liveNote: `\u7CFB\u5217\u5DF2\u5B9A\u5411 ${liveLocIds.size}/${plannedLocCount} \u4E2A\u5730\u57DF`,
@@ -118192,8 +118196,8 @@ function pmaxChannelTypesUrl(googleApiUrl) {
118192
118196
  }
118193
118197
  function pmaxCampaignUrl(googleApiUrl, accountId, campaignId) {
118194
118198
  const base = googleApiUrl.replace(/\/$/, "");
118195
- const path44 = `${base}/accounts/${accountId}/campaign/pmax`;
118196
- return campaignId ? `${path44}/${campaignId}` : path44;
118199
+ const path47 = `${base}/accounts/${accountId}/campaign/pmax`;
118200
+ return campaignId ? `${path47}/${campaignId}` : path47;
118197
118201
  }
118198
118202
  function pmaxBrandAssetsUrl(googleApiUrl, accountId, campaignId) {
118199
118203
  return `${pmaxCampaignUrl(googleApiUrl, accountId, campaignId)}/brand-assets`;
@@ -118203,8 +118207,8 @@ function pmaxBrandGuidelinesEnableUrl(googleApiUrl, accountId, campaignId) {
118203
118207
  }
118204
118208
  function pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, suffix) {
118205
118209
  const base = googleApiUrl.replace(/\/$/, "");
118206
- const path44 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
118207
- return suffix ? `${path44}/${suffix.replace(/^\//, "")}` : path44;
118210
+ const path47 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
118211
+ return suffix ? `${path47}/${suffix.replace(/^\//, "")}` : path47;
118208
118212
  }
118209
118213
  function pmaxCampaignAssetGroupUrl(googleApiUrl, accountId, campaignId) {
118210
118214
  const base = googleApiUrl.replace(/\/$/, "");
@@ -120945,8 +120949,8 @@ async function runAdPmaxImageConvert(opts) {
120945
120949
  }
120946
120950
 
120947
120951
  // src/commands/ad/pmax-brand-mgmt.ts
120948
- function loadBrandConfig(path44, accountId, campaignId) {
120949
- const cfg = loadPmaxJsonFile(path44);
120952
+ function loadBrandConfig(path47, accountId, campaignId) {
120953
+ const cfg = loadPmaxJsonFile(path47);
120950
120954
  return {
120951
120955
  ...cfg,
120952
120956
  account: String(cfg["account"] ?? accountId).trim(),
@@ -129664,9 +129668,9 @@ function asRecord7(value) {
129664
129668
  }
129665
129669
  return null;
129666
129670
  }
129667
- function getNestedValue(root, path44) {
129671
+ function getNestedValue(root, path47) {
129668
129672
  let cur = root;
129669
- for (const key of path44) {
129673
+ for (const key of path47) {
129670
129674
  const rec = asRecord7(cur);
129671
129675
  if (!rec) return void 0;
129672
129676
  cur = rec[key];
@@ -133961,6 +133965,855 @@ function registerTikTokAnalysisCommands(program2) {
133961
133965
  );
133962
133966
  }
133963
133967
 
133968
+ // src/commands/yandex-analysis/register-cli.ts
133969
+ import { Command as Command5 } from "commander";
133970
+
133971
+ // src/commands/yandex-analysis/sections.ts
133972
+ var YANDEX_SECTIONS = [
133973
+ {
133974
+ name: "overview",
133975
+ description: "\u8D26\u6237\u603B\u89C8\uFF1A\u4F59\u989D + \u533A\u95F4\u6C47\u603B KPI",
133976
+ path: "overview"
133977
+ },
133978
+ {
133979
+ name: "daily",
133980
+ description: "\u6309\u65E5\u7EE9\u6548\uFF08\u53EF\u4F9B\u8C03\u7528\u65B9\u81EA\u884C\u805A\u5468\uFF09",
133981
+ path: "daily"
133982
+ },
133983
+ {
133984
+ name: "search",
133985
+ description: "\u7F51\u7EDC\u7EF4\u5EA6\uFF08\u4EC5 SEARCH\uFF09",
133986
+ path: "search"
133987
+ },
133988
+ {
133989
+ name: "campaigns",
133990
+ description: "Campaign \u7EE9\u6548 + \u9884\u7B97/\u6218\u7565/targetCpa",
133991
+ path: "campaigns",
133992
+ limitOption: true
133993
+ },
133994
+ {
133995
+ name: "keywords",
133996
+ description: "\u5173\u952E\u8BCD\uFF08Criteria\uFF09\u7EE9\u6548",
133997
+ path: "keywords",
133998
+ limitOption: true
133999
+ },
134000
+ {
134001
+ name: "search-terms",
134002
+ description: "\u641C\u7D22\u8BCD\u7EE9\u6548\uFF08Direct \u9650\u5236\uFF1A\u4EC5\u8FD1 180 \u5929\uFF09",
134003
+ path: "search-terms",
134004
+ limitOption: true,
134005
+ searchTermsMaxDays: 180
134006
+ },
134007
+ {
134008
+ name: "geo",
134009
+ description: "\u5730\u57DF\u7EE9\u6548\uFF08\u4EC5 SEARCH\uFF09",
134010
+ path: "geo",
134011
+ limitOption: true
134012
+ },
134013
+ {
134014
+ name: "devices",
134015
+ description: "\u8BBE\u5907\u7EE9\u6548\uFF08\u4EC5 SEARCH\uFF09",
134016
+ path: "devices"
134017
+ }
134018
+ ];
134019
+ var YANDEX_SECTION_NAMES = YANDEX_SECTIONS.map((s) => s.name);
134020
+ var YANDEX_SECTION_ALIASES = {
134021
+ geographic: "geo",
134022
+ device: "devices",
134023
+ "search-queries": "search-terms"
134024
+ };
134025
+
134026
+ // src/commands/yandex-analysis/run-batch.ts
134027
+ init_auth();
134028
+ import * as path43 from "path";
134029
+ import { performance as performance8 } from "perf_hooks";
134030
+ init_version();
134031
+
134032
+ // src/commands/yandex-analysis/fetch.ts
134033
+ init_auth();
134034
+ var YANDEX_KEYWORD_LIMIT_DEFAULT = 100;
134035
+ var YANDEX_KEYWORD_LIMIT_MAX = 1e4;
134036
+ var YANDEX_SEARCH_TERMS_MAX_DAYS = 180;
134037
+ function stripLocalDate2(d) {
134038
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
134039
+ }
134040
+ function parseLocalYmd2(ymd) {
134041
+ const parts = ymd.trim().split("-").map((x) => parseInt(x, 10));
134042
+ if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n))) {
134043
+ return /* @__PURE__ */ new Date(NaN);
134044
+ }
134045
+ const [y, m, day] = parts;
134046
+ return new Date(y, m - 1, day);
134047
+ }
134048
+ function formatLocalYmd2(d) {
134049
+ const y = d.getFullYear();
134050
+ const m = String(d.getMonth() + 1).padStart(2, "0");
134051
+ const day = String(d.getDate()).padStart(2, "0");
134052
+ return `${y}-${m}-${day}`;
134053
+ }
134054
+ function daysInclusive(start, end) {
134055
+ const ms = stripLocalDate2(end).getTime() - stripLocalDate2(start).getTime();
134056
+ return Math.floor(ms / 864e5) + 1;
134057
+ }
134058
+ function resolveYandexDateRange(start, end) {
134059
+ if (start && end) {
134060
+ const s = parseLocalYmd2(start);
134061
+ const e = parseLocalYmd2(end);
134062
+ if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime())) {
134063
+ console.error("\n\u274C --start / --end \u987B\u4E3A\u6709\u6548\u65E5\u671F YYYY-MM-DD\u3002\n");
134064
+ process.exit(1);
134065
+ }
134066
+ if (s > e) {
134067
+ console.error("\n\u274C \u5F00\u59CB\u65E5\u671F\u4E0D\u80FD\u665A\u4E8E\u7ED3\u675F\u65E5\u671F\u3002\n");
134068
+ process.exit(1);
134069
+ }
134070
+ const today = stripLocalDate2(/* @__PURE__ */ new Date());
134071
+ if (stripLocalDate2(s) > today || stripLocalDate2(e) > today) {
134072
+ console.error("\n\u274C Yandex \u62A5\u8868\u8D77\u6B62\u65E5\u671F\u4E0D\u80FD\u665A\u4E8E\u4ECA\u5929\u3002\n");
134073
+ process.exit(1);
134074
+ }
134075
+ return { startDate: start.trim(), endDate: end.trim() };
134076
+ }
134077
+ if (!start && !end) {
134078
+ const endD = stripLocalDate2(/* @__PURE__ */ new Date());
134079
+ const startD = new Date(endD);
134080
+ startD.setDate(startD.getDate() - 6);
134081
+ return { startDate: formatLocalYmd2(startD), endDate: formatLocalYmd2(endD) };
134082
+ }
134083
+ console.error(
134084
+ "\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"
134085
+ );
134086
+ process.exit(1);
134087
+ }
134088
+ function assertYandexSearchTermsRange(startDate, endDate) {
134089
+ const s = parseLocalYmd2(startDate);
134090
+ const e = parseLocalYmd2(endDate);
134091
+ const today = stripLocalDate2(/* @__PURE__ */ new Date());
134092
+ const earliest = new Date(today);
134093
+ earliest.setDate(earliest.getDate() - (YANDEX_SEARCH_TERMS_MAX_DAYS - 1));
134094
+ if (stripLocalDate2(s) < earliest || stripLocalDate2(e) > today) {
134095
+ console.error(
134096
+ `
134097
+ \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
134098
+ `
134099
+ );
134100
+ process.exit(1);
134101
+ }
134102
+ if (daysInclusive(s, e) > YANDEX_SEARCH_TERMS_MAX_DAYS) {
134103
+ console.error(
134104
+ `
134105
+ \u274C search-terms \u67E5\u8BE2\u8DE8\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${YANDEX_SEARCH_TERMS_MAX_DAYS} \u5929\u3002
134106
+ `
134107
+ );
134108
+ process.exit(1);
134109
+ }
134110
+ }
134111
+ function assertYandexAccountId(id) {
134112
+ const t = id.trim();
134113
+ if (!t) {
134114
+ console.error("\n\u274C --account \u4E0D\u80FD\u4E3A\u7A7A\uFF08Yandex Client-Login / mediaCustomerId\uFF0C\u5982 porg-xxx\uFF09\u3002\n");
134115
+ process.exit(1);
134116
+ }
134117
+ return t;
134118
+ }
134119
+ function requireYandexApi(config) {
134120
+ const base = (config.yandexApiUrl ?? "").replace(/\/$/, "");
134121
+ if (!base) {
134122
+ console.error(
134123
+ "\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"
134124
+ );
134125
+ process.exit(1);
134126
+ }
134127
+ return base;
134128
+ }
134129
+ function resolveYandexLimit(raw) {
134130
+ const n = typeof raw === "number" ? raw : parseInt(String(raw ?? ""), 10);
134131
+ if (!Number.isFinite(n) || n <= 0) return YANDEX_KEYWORD_LIMIT_DEFAULT;
134132
+ return Math.min(Math.floor(n), YANDEX_KEYWORD_LIMIT_MAX);
134133
+ }
134134
+ function analysisUrl(yandexApiUrl, pathSeg, query) {
134135
+ const base = yandexApiUrl.replace(/\/$/, "");
134136
+ const q = query ? query.startsWith("?") ? query : `?${query}` : "";
134137
+ return `${base}/yandex-analysis/${pathSeg}${q}`;
134138
+ }
134139
+ async function fetchYandexSectionPayload(def, opts, config, accountId) {
134140
+ const { startDate, endDate } = resolveYandexDateRange(opts.start, opts.end);
134141
+ if (def.searchTermsMaxDays) {
134142
+ assertYandexSearchTermsRange(startDate, endDate);
134143
+ }
134144
+ const yandexApiUrl = requireYandexApi(config);
134145
+ const params = new URLSearchParams({
134146
+ account: accountId,
134147
+ start: startDate,
134148
+ end: endDate
134149
+ });
134150
+ if (def.limitOption) {
134151
+ params.set("limit", String(resolveYandexLimit(opts.limit)));
134152
+ }
134153
+ const url = analysisUrl(yandexApiUrl, def.path, params.toString());
134154
+ return apiFetch2(url, config, {}, !!opts.verbose);
134155
+ }
134156
+ function endpointHintForYandexSection(def) {
134157
+ return `GET \u2026/yandex-analysis/${def.path}?account=&start=&end=`;
134158
+ }
134159
+
134160
+ // src/commands/yandex-analysis/resolve-sections.ts
134161
+ function normalizeSectionToken4(raw) {
134162
+ const t = raw.trim();
134163
+ if (!t) return null;
134164
+ if (YANDEX_SECTION_NAMES.includes(t)) {
134165
+ return t;
134166
+ }
134167
+ return YANDEX_SECTION_ALIASES[t] ?? null;
134168
+ }
134169
+ function resolveSectionList5(sections, exclude) {
134170
+ const splitClean = (s) => (s ?? "").split(",").map((x) => x.trim()).filter(Boolean);
134171
+ const include = splitClean(sections);
134172
+ const dropRaw = splitClean(exclude);
134173
+ const unknown = [...include, ...dropRaw].filter((n) => normalizeSectionToken4(n) === null);
134174
+ if (unknown.length > 0) {
134175
+ console.error(
134176
+ `
134177
+ \u274C \u672A\u77E5\u7684 section \u540D\u79F0\uFF1A${unknown.join(", ")}
134178
+ \u53EF\u9009\uFF1A${YANDEX_SECTION_NAMES.join(", ")}
134179
+ \u522B\u540D\uFF1A${Object.keys(YANDEX_SECTION_ALIASES).join(", ")}
134180
+ `
134181
+ );
134182
+ process.exit(1);
134183
+ }
134184
+ const includeCanonical = new Set(include.map((n) => normalizeSectionToken4(n)).filter(Boolean));
134185
+ const dropCanonical = new Set(dropRaw.map((n) => normalizeSectionToken4(n)).filter(Boolean));
134186
+ const base = include.length > 0 ? YANDEX_SECTIONS.filter((s) => includeCanonical.has(s.name)) : [...YANDEX_SECTIONS];
134187
+ return base.filter((s) => !dropCanonical.has(s.name));
134188
+ }
134189
+
134190
+ // src/commands/yandex-analysis/run-batch.ts
134191
+ async function runWithConcurrency6(tasks, concurrency) {
134192
+ const results = new Array(tasks.length);
134193
+ let cursor = 0;
134194
+ const workers = new Array(Math.max(1, Math.min(concurrency, tasks.length))).fill(0).map(async () => {
134195
+ while (true) {
134196
+ const i = cursor++;
134197
+ if (i >= tasks.length) return;
134198
+ results[i] = await tasks[i]();
134199
+ }
134200
+ });
134201
+ await Promise.all(workers);
134202
+ return results;
134203
+ }
134204
+ async function runAllYandexSections(opts) {
134205
+ const id = assertYandexAccountId(opts.account);
134206
+ const config = loadConfig(opts.token);
134207
+ if (!opts.jsonOut?.trim()) {
134208
+ 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");
134209
+ process.exit(1);
134210
+ }
134211
+ const targets = resolveSectionList5(opts.sections, opts.exclude);
134212
+ if (targets.length === 0) {
134213
+ console.error("\n\u274C \u6CA1\u6709\u8981\u6267\u884C\u7684 section\uFF08\u8BF7\u68C0\u67E5 --sections / --exclude\uFF09\u3002\n");
134214
+ process.exit(1);
134215
+ }
134216
+ const { startDate, endDate } = resolveYandexDateRange(opts.start, opts.end);
134217
+ const concurrency = Math.max(
134218
+ 1,
134219
+ Math.min(typeof opts.concurrency === "number" ? opts.concurrency : 5, 16)
134220
+ );
134221
+ const cliVersion = getCurrentVersion2();
134222
+ const overallT0 = performance8.now();
134223
+ const tasks = targets.map((def) => async () => {
134224
+ const t0 = performance8.now();
134225
+ const hint = endpointHintForYandexSection(def);
134226
+ try {
134227
+ const payload = await fetchYandexSectionPayload(
134228
+ def,
134229
+ { ...opts, start: startDate, end: endDate },
134230
+ config,
134231
+ id
134232
+ );
134233
+ const summary2 = await writeReportAnalysisSnapshot({
134234
+ snapshotDir: opts.jsonOut,
134235
+ section: def.name,
134236
+ accountId: id,
134237
+ dateRange: { start: startDate, end: endDate },
134238
+ payload,
134239
+ cliVersion
134240
+ });
134241
+ return {
134242
+ section: def.name,
134243
+ ok: true,
134244
+ elapsedMs: performance8.now() - t0,
134245
+ file: summary2.writtenFiles[0],
134246
+ endpointHint: hint
134247
+ };
134248
+ } catch (err) {
134249
+ return {
134250
+ section: def.name,
134251
+ ok: false,
134252
+ elapsedMs: performance8.now() - t0,
134253
+ error: err instanceof Error ? err.message : String(err),
134254
+ endpointHint: hint
134255
+ };
134256
+ }
134257
+ });
134258
+ const results = await runWithConcurrency6(tasks, concurrency);
134259
+ const succeeded = results.filter((r) => r.ok).length;
134260
+ const failed = results.length - succeeded;
134261
+ const absoluteSnapshotDir = path43.resolve(opts.jsonOut);
134262
+ const summary = {
134263
+ kind: "siluzan-tso-yandex-analysis-snapshot-batch",
134264
+ absoluteSnapshotDir,
134265
+ manifestFile: reportManifestFile(id),
134266
+ accountId: id,
134267
+ totalSections: results.length,
134268
+ succeeded,
134269
+ failed,
134270
+ concurrency,
134271
+ elapsedMs: performance8.now() - overallT0,
134272
+ results
134273
+ };
134274
+ console.log(JSON.stringify(summary));
134275
+ if (failed > 0) process.exit(2);
134276
+ }
134277
+
134278
+ // src/commands/yandex-analysis/render-report.ts
134279
+ import fs34 from "fs";
134280
+ import fsPromises7 from "fs/promises";
134281
+ import path45 from "path";
134282
+ import { fileURLToPath as fileURLToPath11 } from "url";
134283
+
134284
+ // src/commands/yandex-analysis/merge-snapshot.ts
134285
+ import * as fs33 from "fs/promises";
134286
+ import * as path44 from "path";
134287
+ function asRecord26(value) {
134288
+ if (value && typeof value === "object" && !Array.isArray(value)) {
134289
+ return value;
134290
+ }
134291
+ return null;
134292
+ }
134293
+ function num10(value) {
134294
+ const n = Number(value);
134295
+ return Number.isFinite(n) ? n : void 0;
134296
+ }
134297
+ function round25(value) {
134298
+ return value == null ? void 0 : Math.round(value * 100) / 100;
134299
+ }
134300
+ function asItems(payload) {
134301
+ const obj = asRecord26(payload);
134302
+ if (!obj) return [];
134303
+ const items2 = obj.items;
134304
+ if (!Array.isArray(items2)) return [];
134305
+ return items2.filter((r) => asRecord26(r) !== null);
134306
+ }
134307
+ function pickMetrics2(row, extras = []) {
134308
+ const out = {
134309
+ spend: num10(row.spend) ?? 0,
134310
+ impressions: num10(row.impressions) ?? 0,
134311
+ clicks: num10(row.clicks) ?? 0,
134312
+ ctr: num10(row.ctr),
134313
+ averageCpc: num10(row.averageCpc),
134314
+ conversions: num10(row.conversions) ?? 0,
134315
+ conversionRate: num10(row.conversionRate),
134316
+ costPerConversion: num10(row.costPerConversion),
134317
+ roi: num10(row.roi)
134318
+ };
134319
+ for (const f of extras) {
134320
+ if (row[f] !== void 0 && row[f] !== null) out[f] = row[f];
134321
+ }
134322
+ return out;
134323
+ }
134324
+ async function loadSectionPayload3(snapshotDir, file) {
134325
+ try {
134326
+ const raw = await fs33.readFile(path44.join(snapshotDir, file), "utf8");
134327
+ return JSON.parse(raw);
134328
+ } catch {
134329
+ return null;
134330
+ }
134331
+ }
134332
+ async function readManifest6(snapshotDir, accountId) {
134333
+ const candidates = [];
134334
+ if (accountId) candidates.push(reportManifestFile(accountId));
134335
+ try {
134336
+ const entries = await fs33.readdir(snapshotDir);
134337
+ for (const name2 of entries.filter(
134338
+ (n) => n.startsWith("report-manifest") && n.endsWith(".json")
134339
+ )) {
134340
+ if (!candidates.includes(name2)) candidates.push(name2);
134341
+ }
134342
+ } catch {
134343
+ }
134344
+ for (const name2 of candidates) {
134345
+ try {
134346
+ const raw = await fs33.readFile(path44.join(snapshotDir, name2), "utf8");
134347
+ const parsed = JSON.parse(raw);
134348
+ if (Array.isArray(parsed.artifacts) && parsed.artifacts.length > 0) return parsed;
134349
+ } catch {
134350
+ }
134351
+ }
134352
+ return null;
134353
+ }
134354
+ function parseYmd2(value) {
134355
+ const s = String(value ?? "").trim().slice(0, 10);
134356
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
134357
+ if (!m) return null;
134358
+ const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
134359
+ return Number.isNaN(d.getTime()) ? null : d;
134360
+ }
134361
+ function formatYmd2(d) {
134362
+ const y = d.getFullYear();
134363
+ const m = String(d.getMonth() + 1).padStart(2, "0");
134364
+ const day = String(d.getDate()).padStart(2, "0");
134365
+ return `${y}-${m}-${day}`;
134366
+ }
134367
+ function isoWeekKey(d) {
134368
+ const day = new Date(d.getFullYear(), d.getMonth(), d.getDate());
134369
+ const dow = (day.getDay() + 6) % 7;
134370
+ const weekStart = new Date(day);
134371
+ weekStart.setDate(weekStart.getDate() - dow);
134372
+ const thursday = new Date(weekStart);
134373
+ thursday.setDate(thursday.getDate() + 3);
134374
+ const yearStart = new Date(thursday.getFullYear(), 0, 1);
134375
+ const week = Math.floor((thursday.getTime() - yearStart.getTime()) / 864e5 / 7) + 1;
134376
+ const label = `W${String(week).padStart(2, "0")}`;
134377
+ return { key: `${thursday.getFullYear()}-${label}`, label, weekStart };
134378
+ }
134379
+ function aggregateDailyToWeekly(dailyRows) {
134380
+ const map = /* @__PURE__ */ new Map();
134381
+ for (const row of dailyRows) {
134382
+ const d = parseYmd2(row.date);
134383
+ if (!d) continue;
134384
+ const { key, label, weekStart } = isoWeekKey(d);
134385
+ let acc = map.get(key);
134386
+ if (!acc) {
134387
+ acc = {
134388
+ label,
134389
+ weekStart,
134390
+ minDate: d,
134391
+ maxDate: d,
134392
+ spend: 0,
134393
+ impressions: 0,
134394
+ clicks: 0,
134395
+ conversions: 0
134396
+ };
134397
+ map.set(key, acc);
134398
+ }
134399
+ if (d < acc.minDate) acc.minDate = d;
134400
+ if (d > acc.maxDate) acc.maxDate = d;
134401
+ acc.spend += num10(row.spend) ?? 0;
134402
+ acc.impressions += num10(row.impressions) ?? 0;
134403
+ acc.clicks += num10(row.clicks) ?? 0;
134404
+ acc.conversions += num10(row.conversions) ?? 0;
134405
+ }
134406
+ return [...map.values()].sort((a, b) => a.weekStart.getTime() - b.weekStart.getTime()).map((acc) => {
134407
+ const days = Math.floor((acc.maxDate.getTime() - acc.minDate.getTime()) / 864e5) + 1;
134408
+ return {
134409
+ weekLabel: acc.label,
134410
+ startDate: formatYmd2(acc.minDate),
134411
+ endDate: formatYmd2(acc.maxDate),
134412
+ days,
134413
+ spend: round25(acc.spend),
134414
+ impressions: Math.round(acc.impressions),
134415
+ clicks: Math.round(acc.clicks),
134416
+ conversions: round25(acc.conversions),
134417
+ ctr: acc.impressions > 0 ? acc.clicks / acc.impressions : void 0,
134418
+ averageCpc: acc.clicks > 0 ? round25(acc.spend / acc.clicks) : void 0,
134419
+ costPerConversion: acc.conversions > 0 ? round25(acc.spend / acc.conversions) : void 0
134420
+ };
134421
+ });
134422
+ }
134423
+ function mapDailyRows(payload) {
134424
+ return asItems(payload).map((r) => {
134425
+ const dateRaw = r.date;
134426
+ const date = typeof dateRaw === "string" ? dateRaw.slice(0, 10) : dateRaw != null ? String(dateRaw).slice(0, 10) : "";
134427
+ return pickMetrics2({ ...r, date }, ["date"]);
134428
+ });
134429
+ }
134430
+ function mapSearchRows(payload) {
134431
+ return asItems(payload).map((r) => pickMetrics2(r, ["network"]));
134432
+ }
134433
+ function mapCampaignRows2(payload) {
134434
+ return asItems(payload).map(
134435
+ (r) => pickMetrics2(r, [
134436
+ "campaignId",
134437
+ "campaignName",
134438
+ "status",
134439
+ "state",
134440
+ "campaignType",
134441
+ "budget",
134442
+ "strategy",
134443
+ "targetCpa",
134444
+ "network"
134445
+ ])
134446
+ );
134447
+ }
134448
+ function mapKeywordRows2(payload) {
134449
+ return asItems(payload).map(
134450
+ (r) => pickMetrics2(r, [
134451
+ "campaignId",
134452
+ "campaignName",
134453
+ "adGroupId",
134454
+ "keyword",
134455
+ "criteriaType",
134456
+ "network"
134457
+ ])
134458
+ );
134459
+ }
134460
+ function mapSearchTermRows2(payload) {
134461
+ return asItems(payload).map(
134462
+ (r) => pickMetrics2(r, [
134463
+ "searchQuery",
134464
+ "matchedKeyword",
134465
+ "campaignId",
134466
+ "campaignName",
134467
+ "adGroupId"
134468
+ ])
134469
+ );
134470
+ }
134471
+ function mapGeoRows(payload) {
134472
+ return asItems(payload).map(
134473
+ (r) => pickMetrics2(r, ["locationName", "targetingLocationName", "network"])
134474
+ );
134475
+ }
134476
+ function mapDeviceRows2(payload) {
134477
+ return asItems(payload).map((r) => pickMetrics2(r, ["device", "network"]));
134478
+ }
134479
+ async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
134480
+ const absDir = path44.resolve(snapshotDir);
134481
+ const metaAccountId = typeof asRecord26(payload.meta)?.accountId === "string" ? asRecord26(payload.meta)?.accountId : void 0;
134482
+ const manifest = await readManifest6(snapshotDir, metaAccountId);
134483
+ if (!manifest) {
134484
+ throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 report-manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C yandex-analysis --json-out`);
134485
+ }
134486
+ const sectionMap = /* @__PURE__ */ new Map();
134487
+ for (const art of manifest.artifacts ?? []) {
134488
+ const data = await loadSectionPayload3(absDir, art.file);
134489
+ if (data != null) sectionMap.set(art.section, data);
134490
+ }
134491
+ const overview = asRecord26(sectionMap.get("overview"));
134492
+ const totals = asRecord26(overview?.totals);
134493
+ const accountId = manifest.accountId ?? metaAccountId;
134494
+ const meta = { ...payload.meta ?? {} };
134495
+ if (!meta.accountId && accountId) meta.accountId = accountId;
134496
+ if (!meta.currency && typeof overview?.currency === "string") meta.currency = overview.currency;
134497
+ if (!meta.startDate && manifest.dateRange) meta.startDate = manifest.dateRange.start;
134498
+ if (!meta.endDate && manifest.dateRange) meta.endDate = manifest.dateRange.end;
134499
+ if (!meta.generatedAt) meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
134500
+ if (!meta.platform) meta.platform = "Yandex Direct";
134501
+ const kpis = { ...payload.kpis ?? {} };
134502
+ if (totals) {
134503
+ if (num10(totals.spend) != null) kpis.spend = round25(num10(totals.spend));
134504
+ if (num10(totals.impressions) != null) kpis.impressions = Math.round(num10(totals.impressions));
134505
+ if (num10(totals.clicks) != null) kpis.clicks = Math.round(num10(totals.clicks));
134506
+ if (num10(totals.conversions) != null) kpis.conversions = round25(num10(totals.conversions));
134507
+ if (num10(totals.ctr) != null) kpis.ctr = num10(totals.ctr);
134508
+ if (num10(totals.averageCpc) != null) kpis.averageCpc = round25(num10(totals.averageCpc));
134509
+ if (num10(totals.costPerConversion) != null) {
134510
+ kpis.costPerConversion = round25(num10(totals.costPerConversion));
134511
+ }
134512
+ if (num10(totals.conversionRate) != null) kpis.conversionRate = num10(totals.conversionRate);
134513
+ if (num10(totals.roi) != null) kpis.roi = num10(totals.roi);
134514
+ }
134515
+ if (overview) {
134516
+ if (num10(overview.balance) != null) kpis.balance = round25(num10(overview.balance));
134517
+ if (num10(overview.avgDailySpend) != null) kpis.avgDailySpend = round25(num10(overview.avgDailySpend));
134518
+ if (num10(overview.activeDays) != null) kpis.activeDays = num10(overview.activeDays);
134519
+ if (overview.balanceAsOf != null) kpis.balanceAsOf = overview.balanceAsOf;
134520
+ }
134521
+ if (kpis.balance != null && kpis.avgDailySpend != null && Number(kpis.avgDailySpend) > 0) {
134522
+ kpis.runwayDays = Math.round(Number(kpis.balance) / Number(kpis.avgDailySpend));
134523
+ }
134524
+ const tables = { ...payload.tables ?? {} };
134525
+ const dailyRows = mapDailyRows(sectionMap.get("daily"));
134526
+ if (dailyRows.length > 0) {
134527
+ tables.daily = dailyRows;
134528
+ tables.weekly = aggregateDailyToWeekly(dailyRows);
134529
+ }
134530
+ {
134531
+ const rows = mapSearchRows(sectionMap.get("search"));
134532
+ if (rows.length > 0) tables.search = rows;
134533
+ }
134534
+ {
134535
+ const rows = mapCampaignRows2(sectionMap.get("campaigns"));
134536
+ if (rows.length > 0) tables.campaigns = rows;
134537
+ }
134538
+ {
134539
+ const rows = mapKeywordRows2(sectionMap.get("keywords"));
134540
+ if (rows.length > 0) tables.keywords = rows;
134541
+ }
134542
+ {
134543
+ const rows = mapSearchTermRows2(sectionMap.get("search-terms"));
134544
+ if (rows.length > 0) tables.searchTerms = rows;
134545
+ }
134546
+ {
134547
+ const rows = mapGeoRows(sectionMap.get("geo"));
134548
+ if (rows.length > 0) tables.geo = rows;
134549
+ }
134550
+ {
134551
+ const rows = mapDeviceRows2(sectionMap.get("devices"));
134552
+ if (rows.length > 0) tables.devices = rows;
134553
+ }
134554
+ return { ...payload, meta, kpis, tables };
134555
+ }
134556
+
134557
+ // src/commands/yandex-analysis/report-content.ts
134558
+ var SECTION_KEYS4 = [
134559
+ "overview",
134560
+ "search",
134561
+ "campaigns",
134562
+ "keywords",
134563
+ "searchTerms",
134564
+ "geo",
134565
+ "devices",
134566
+ "daily"
134567
+ ];
134568
+ var SECTION_LABELS5 = {
134569
+ overview: "\u2460\u8D26\u6237\u603B\u89C8",
134570
+ search: "\u2461Search \u7F51\u7EDC",
134571
+ campaigns: "\u2462Campaign",
134572
+ keywords: "\u2463\u5173\u952E\u8BCD",
134573
+ searchTerms: "\u2464\u641C\u7D22\u8BCD",
134574
+ geo: "\u2465\u5730\u57DF",
134575
+ devices: "\u2466\u8BBE\u5907",
134576
+ daily: "\u2467\u65E5\u671F/\u5468\u62A5"
134577
+ };
134578
+ function textLen7(value) {
134579
+ return typeof value === "string" ? value.trim().length : 0;
134580
+ }
134581
+ function asArray7(value) {
134582
+ return Array.isArray(value) ? value : [];
134583
+ }
134584
+ function asRecord27(value) {
134585
+ if (value && typeof value === "object" && !Array.isArray(value)) {
134586
+ return value;
134587
+ }
134588
+ return null;
134589
+ }
134590
+ function pushMissing6(missing, id, chapter, dimension, hint) {
134591
+ missing.push({ id, chapter, dimension, hint });
134592
+ }
134593
+ function validateYandexPeriodReportContent(data) {
134594
+ const missing = [];
134595
+ const meta = asRecord27(data.meta) ?? {};
134596
+ const narrative = asRecord27(data.narrative) ?? {};
134597
+ const sections = asRecord27(narrative.sections) ?? {};
134598
+ const kpis = asRecord27(data.kpis) ?? {};
134599
+ if (!textLen7(meta.accountId)) {
134600
+ pushMissing6(missing, "meta-account", "\u5143\u4FE1\u606F", "\u8D26\u6237 ID", "meta.accountId \u5FC5\u586B");
134601
+ }
134602
+ const executiveSummary = asArray7(narrative.executiveSummary).filter((p) => textLen7(p) > 0);
134603
+ if (executiveSummary.length < 1) {
134604
+ pushMissing6(
134605
+ missing,
134606
+ "narrative-executive-summary",
134607
+ "\u6267\u884C\u6458\u8981",
134608
+ "narrative.executiveSummary",
134609
+ "\u22651 \u6BB5\u6267\u884C\u6458\u8981\uFF0C\u6982\u8FF0\u672C\u671F\u6D88\u8017/\u70B9\u51FB/\u8F6C\u5316/CTR/CPC \u8D70\u52BF"
134610
+ );
134611
+ }
134612
+ const sectionAnalysisCounts = {};
134613
+ const sectionSuggestionCounts = {};
134614
+ for (const key of SECTION_KEYS4) {
134615
+ const section = asRecord27(sections[key]);
134616
+ const analysis = asArray7(section?.analysis).filter((s) => textLen7(s) > 0);
134617
+ const suggestions = asArray7(section?.suggestions).filter((s) => textLen7(s) > 0);
134618
+ sectionAnalysisCounts[key] = analysis.length;
134619
+ sectionSuggestionCounts[key] = suggestions.length;
134620
+ if (analysis.length < 1) {
134621
+ pushMissing6(
134622
+ missing,
134623
+ `section-${key}-analysis`,
134624
+ SECTION_LABELS5[key],
134625
+ `narrative.sections.${key}.analysis`,
134626
+ `\u22651 \u6761\u603B\u7ED3\uFF08\u5F53\u524D ${analysis.length} \u6761\uFF09\uFF0C\u987B\u5199\u6E05\u73B0\u8C61+\u4F9D\u636E\u6570\u5B57`
134627
+ );
134628
+ }
134629
+ if (suggestions.length < 1) {
134630
+ pushMissing6(
134631
+ missing,
134632
+ `section-${key}-suggestions`,
134633
+ SECTION_LABELS5[key],
134634
+ `narrative.sections.${key}.suggestions`,
134635
+ `\u22651 \u6761\u53EF\u6267\u884C\u5EFA\u8BAE\uFF08\u5F53\u524D ${suggestions.length} \u6761\uFF09`
134636
+ );
134637
+ }
134638
+ }
134639
+ const recommendations = asArray7(narrative.recommendations).filter((r) => {
134640
+ if (typeof r === "string") return textLen7(r) > 0;
134641
+ const rec = asRecord27(r);
134642
+ return textLen7(rec?.content) > 0 || textLen7(rec?.text) > 0;
134643
+ });
134644
+ if (recommendations.length < 3) {
134645
+ pushMissing6(
134646
+ missing,
134647
+ "narrative-recommendations",
134648
+ "\u540E\u7EED\u4F18\u5316\u52A8\u4F5C",
134649
+ "narrative.recommendations",
134650
+ `\u22653 \u6761\u8DE8\u7EF4\u5EA6\u4F18\u5316\u5EFA\u8BAE\uFF08\u5F53\u524D ${recommendations.length} \u6761\uFF1B\u53EF\u7528 {priority,text,anchor}\uFF09`
134651
+ );
134652
+ }
134653
+ const hasKpi = Number.isFinite(Number(kpis.spend)) || Number.isFinite(Number(kpis.clicks)) || Number.isFinite(Number(kpis.impressions));
134654
+ if (!hasKpi) {
134655
+ pushMissing6(
134656
+ missing,
134657
+ "kpis-core",
134658
+ "KPI",
134659
+ "kpis.spend|clicks|impressions",
134660
+ "\u81F3\u5C11\u4E00\u9879\u6709\u6548\u6570\u5B57\uFF08\u8BF7\u4F20 --snapshot-dir \u7531 CLI \u4ECE overview \u5408\u5E76\uFF09"
134661
+ );
134662
+ }
134663
+ return {
134664
+ ok: missing.length === 0,
134665
+ missing,
134666
+ stats: {
134667
+ executiveSummaryCount: executiveSummary.length,
134668
+ recommendationCount: recommendations.length,
134669
+ sectionAnalysisCounts,
134670
+ sectionSuggestionCounts
134671
+ }
134672
+ };
134673
+ }
134674
+ function formatYandexPeriodReportErrors(result) {
134675
+ if (result.ok) return "";
134676
+ const lines = result.missing.map((m) => ` - [${m.chapter}] ${m.dimension}\uFF1A${m.hint}`);
134677
+ return [
134678
+ `\u62A5\u544A JSON \u7F3A\u5C11 ${result.missing.length} \u9879\u5FC5\u542B\u5185\u5BB9\uFF08\u5BF9\u9F50 yandex-period-report.md\uFF09\uFF1A`,
134679
+ ...lines,
134680
+ "",
134681
+ "\u8BF7\u8865\u5168 narrative.sections.<dimension>.{analysis,suggestions} \u4E0E narrative.recommendations \u540E\u91CD\u65B0\u6267\u884C render\u3002",
134682
+ "\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"
134683
+ ].join("\n");
134684
+ }
134685
+
134686
+ // src/commands/yandex-analysis/render-report.ts
134687
+ var TEMPLATE_BASENAME5 = "yandex-period-report.html";
134688
+ var INJECT_MARKER6 = "window.__YANDEX_PERIOD_REPORT__ = window.__YANDEX_PERIOD_REPORT__ || null;";
134689
+ function resolveSkillTemplatePath8(basename11) {
134690
+ const dir = path45.dirname(fileURLToPath11(import.meta.url));
134691
+ const candidates = [
134692
+ path45.join(dir, "skill", "report-templates", basename11),
134693
+ path45.join(dir, "skill", "siluzan-ads", "report-templates", basename11),
134694
+ path45.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename11)
134695
+ ];
134696
+ for (const p of candidates) {
134697
+ if (fs34.existsSync(p)) return p;
134698
+ }
134699
+ return candidates[0];
134700
+ }
134701
+ function yandexPeriodReportTemplatePath() {
134702
+ return resolveSkillTemplatePath8(TEMPLATE_BASENAME5);
134703
+ }
134704
+ async function readJsonFile7(filePath) {
134705
+ const text = await fsPromises7.readFile(filePath, "utf8");
134706
+ try {
134707
+ return JSON.parse(text);
134708
+ } catch {
134709
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
134710
+ }
134711
+ }
134712
+ function asRecord28(value) {
134713
+ if (value && typeof value === "object" && !Array.isArray(value)) {
134714
+ return value;
134715
+ }
134716
+ throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
134717
+ }
134718
+ function injectReportData7(html, payload) {
134719
+ const json = JSON.stringify(payload).replace(/</g, "\\u003c");
134720
+ if (!html.includes(INJECT_MARKER6)) {
134721
+ throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 yandex-period-report.html");
134722
+ }
134723
+ return html.replace(INJECT_MARKER6, `window.__YANDEX_PERIOD_REPORT__ = ${json};`);
134724
+ }
134725
+ async function runYandexAnalysisRender(opts) {
134726
+ const dataPath = path45.resolve(opts.dataFile);
134727
+ let data = asRecord28(await readJsonFile7(dataPath));
134728
+ if (opts.snapshotDir?.trim()) {
134729
+ data = await mergeYandexAnalysisSnapshotIntoReport(data, path45.resolve(opts.snapshotDir));
134730
+ }
134731
+ const contentCheck = validateYandexPeriodReportContent(data);
134732
+ if (!contentCheck.ok) {
134733
+ console.error(`
134734
+ \u274C ${formatYandexPeriodReportErrors(contentCheck)}
134735
+ `);
134736
+ const { stats: stats2 } = contentCheck;
134737
+ const sectionSummary = Object.entries(stats2.sectionAnalysisCounts).map(([key, count]) => `${key} \u603B\u7ED3${count}/\u5EFA\u8BAE${stats2.sectionSuggestionCounts[key] ?? 0}`).join("\uFF5C");
134738
+ console.error(
134739
+ ` \u7EDF\u8BA1\uFF1A\u6267\u884C\u6458\u8981 ${stats2.executiveSummaryCount} \u6BB5\uFF5C\u4F18\u5316\u5EFA\u8BAE ${stats2.recommendationCount} \u6761\uFF5C${sectionSummary}
134740
+ `
134741
+ );
134742
+ process.exit(1);
134743
+ }
134744
+ const templatePath = yandexPeriodReportTemplatePath();
134745
+ let html;
134746
+ try {
134747
+ html = await fsPromises7.readFile(templatePath, "utf8");
134748
+ } catch {
134749
+ console.error(`
134750
+ \u274C \u672A\u627E\u5230\u62A5\u544A\u6A21\u677F\uFF1A${templatePath}
134751
+ \u8BF7\u5148\u6267\u884C npm run build
134752
+ `);
134753
+ process.exit(1);
134754
+ }
134755
+ const outPath = path45.resolve(
134756
+ opts.out ?? path45.join(path45.dirname(dataPath), "yandex-period-report.html")
134757
+ );
134758
+ const outDir = path45.dirname(outPath);
134759
+ await fsPromises7.mkdir(outDir, { recursive: true });
134760
+ await fsPromises7.writeFile(outPath, injectReportData7(html, data), "utf8");
134761
+ const { stats } = contentCheck;
134762
+ console.log(`
134763
+ \u2705 Yandex \u5E7F\u544A\u8D26\u6237\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
134764
+ `);
134765
+ console.log(
134766
+ ` \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
134767
+ `
134768
+ );
134769
+ 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");
134770
+ }
134771
+
134772
+ // src/commands/yandex-analysis/register-cli.ts
134773
+ function registerBatchCommand4(sectionHelp, aliasHelp) {
134774
+ return new Command5("run").description(
134775
+ "\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"
134776
+ ).requiredOption(
134777
+ "-a, --account <id>",
134778
+ "Yandex Client-Login / mediaCustomerId\uFF08\u5982 porg-xxx\uFF0C\u4E0E list-accounts -m Yandex \u4E00\u81F4\uFF09"
134779
+ ).requiredOption(
134780
+ "--json-out <dir>",
134781
+ "\u7ED3\u679C\u843D\u76D8\u76EE\u5F55\uFF1B\u6BCF\u7EF4\u5EA6 `<section>-<accountId>.json` + `report-manifest-<accountId>.json`"
134782
+ ).option("-t, --token <token>", "Auth Token").option(
134783
+ "--start <date>",
134784
+ "\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"
134785
+ ).option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option(
134786
+ "--sections <list>",
134787
+ `\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}`
134788
+ ).option("--exclude <list>", "\u6392\u9664\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u4E0E --sections \u53EF\u53E0\u52A0").option(
134789
+ "--limit <n>",
134790
+ "\u4EC5 campaigns / keywords / search-terms / geo\uFF1A\u6761\u6570\u4E0A\u9650\uFF08\u9ED8\u8BA4 100\uFF0C\u6700\u5927 10000\uFF09",
134791
+ (v) => parseInt(v, 10)
134792
+ ).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) => {
134793
+ await runAllYandexSections(opts);
134794
+ });
134795
+ }
134796
+ function registerYandexAnalysisCommands(program2) {
134797
+ const sectionHelp = YANDEX_SECTION_NAMES.join(", ");
134798
+ const aliasHelp = Object.keys(YANDEX_SECTION_ALIASES).join(", ");
134799
+ const root = program2.command("yandex-analysis").description(
134800
+ "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"
134801
+ );
134802
+ root.addCommand(registerBatchCommand4(sectionHelp, aliasHelp), { isDefault: true });
134803
+ root.command("render").description(
134804
+ "\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"
134805
+ ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 yandex-period-report.json").option(
134806
+ "--snapshot-dir <dir>",
134807
+ "\u53EF\u9009\uFF1Ayandex-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u8865\u5168 meta / kpis / tables"
134808
+ ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
134809
+ await runYandexAnalysisRender({
134810
+ dataFile: opts.data,
134811
+ snapshotDir: opts.snapshotDir,
134812
+ out: opts.out
134813
+ });
134814
+ });
134815
+ }
134816
+
133964
134817
  // src/index.ts
133965
134818
  init_version();
133966
134819
  init_cli_json_snapshot();
@@ -134035,9 +134888,9 @@ function interceptAgentCompatArgv(argv) {
134035
134888
  installProcessHandlers();
134036
134889
  function getVersion() {
134037
134890
  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"));
134891
+ const __dirname3 = path46.dirname(fileURLToPath12(import.meta.url));
134892
+ const pkgPath = path46.join(__dirname3, "..", "package.json");
134893
+ const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf8"));
134041
134894
  return pkg.version ?? "0.0.0";
134042
134895
  } catch {
134043
134896
  return "0.0.0";
@@ -134056,9 +134909,9 @@ configureCliJsonSnapshot({
134056
134909
  ]
134057
134910
  });
134058
134911
  configureSiluzanClientIdentity({ client: "tso-cli", resolveVersion: getVersion });
134059
- var program = new Command5();
134912
+ var program = new Command6();
134060
134913
  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"
134914
+ "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
134915
  ).version(getVersion());
134063
134916
  program.option("--commit <text>", "\u5199\u64CD\u4F5C\u8BF4\u660E\uFF1B\u8BB0\u5165\u672C\u673A\u5199\u5BA1\u8BA1 JSONL");
134064
134917
  program.hook("preAction", async () => {
@@ -134093,6 +134946,7 @@ var REGISTRARS = [
134093
134946
  registerFacebookAnalysisCommands,
134094
134947
  registerBingAnalysisCommands,
134095
134948
  registerTikTokAnalysisCommands,
134949
+ registerYandexAnalysisCommands,
134096
134950
  register15,
134097
134951
  register16,
134098
134952
  register17,