siluzan-tso-cli 1.1.27 → 1.1.28-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 (32) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +369 -60
  3. package/dist/skill/SKILL.md +8 -6
  4. package/dist/skill/_meta.json +2 -2
  5. package/dist/skill/assets/meta-period-report-rules.md +169 -0
  6. package/dist/skill/assets/meta-period-report.schema.json +228 -0
  7. package/dist/skill/references/README.md +2 -0
  8. package/dist/skill/references/accounts/accounts.md +43 -14
  9. package/dist/skill/references/accounts/finance.md +32 -32
  10. package/dist/skill/references/accounts/open-account-google-ui.md +1 -1
  11. package/dist/skill/references/analytics/account-analytics.md +7 -6
  12. package/dist/skill/references/analytics/facebook-analysis-guide.md +65 -12
  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/analytics/website-diagnosis-guide.md +1 -1
  16. package/dist/skill/references/core/agent-conventions.md +1 -1
  17. package/dist/skill/references/core/deliverable-preflight.md +3 -2
  18. package/dist/skill/references/core/playbooks.md +15 -9
  19. package/dist/skill/references/core/setup.md +5 -5
  20. package/dist/skill/references/core/skill-authoring.md +1 -1
  21. package/dist/skill/references/core/tips.md +4 -2
  22. package/dist/skill/references/core/workflows.md +29 -12
  23. package/dist/skill/references/misc/tso-home.md +2 -2
  24. package/dist/skill/report-templates/README.md +2 -1
  25. package/dist/skill/report-templates/REPORT-WORKFLOW.md +15 -5
  26. package/dist/skill/report-templates/meta-period-report-excel.md +222 -0
  27. package/dist/skill/report-templates/meta-period-report.html +634 -0
  28. package/dist/skill/report-templates/meta-period-report.md +149 -48
  29. package/dist/skill/scripts/install.ps1 +3 -3
  30. package/dist/skill/scripts/install.sh +3 -3
  31. package/eval/cases/uj-ops-google-accounts-list-normal.scenario.json +4 -1
  32. package/package.json +1 -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 num = +id;
262
- if (num >= 0 && num < MAX_SAFE_INTEGER) {
263
- return num;
261
+ const num2 = +id;
262
+ if (num2 >= 0 && num2 < MAX_SAFE_INTEGER) {
263
+ return num2;
264
264
  }
265
265
  }
266
266
  return id;
@@ -3328,7 +3328,7 @@ var DEFAULT_API_BASE;
3328
3328
  var init_defaults = __esm({
3329
3329
  "src/config/defaults.ts"() {
3330
3330
  "use strict";
3331
- DEFAULT_API_BASE = "https://tso-api.siluzan.com";
3331
+ DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
3332
3332
  }
3333
3333
  });
3334
3334
 
@@ -101099,14 +101099,14 @@ function computeBackoffMs(attempt, policy) {
101099
101099
  return capped + jitter;
101100
101100
  }
101101
101101
  function sleep3(ms, signal) {
101102
- return new Promise((resolve14, reject) => {
101102
+ return new Promise((resolve15, reject) => {
101103
101103
  if (signal?.aborted) {
101104
101104
  reject(new Error("retry sleep aborted"));
101105
101105
  return;
101106
101106
  }
101107
101107
  const timer = setTimeout(() => {
101108
101108
  signal?.removeEventListener("abort", onAbort);
101109
- resolve14();
101109
+ resolve15();
101110
101110
  }, ms);
101111
101111
  const onAbort = () => {
101112
101112
  clearTimeout(timer);
@@ -102726,10 +102726,10 @@ var init_google_analysis3 = __esm({
102726
102726
  // src/index.ts
102727
102727
  init_dist();
102728
102728
  init_dist();
102729
- import * as fs16 from "fs";
102730
- import * as path24 from "path";
102731
- import { fileURLToPath as fileURLToPath5 } from "url";
102732
- import { Command } from "commander";
102729
+ import * as fs18 from "fs";
102730
+ import * as path26 from "path";
102731
+ import { fileURLToPath as fileURLToPath6 } from "url";
102732
+ import { Command as Command2 } from "commander";
102733
102733
 
102734
102734
  // src/commands/login/urls.ts
102735
102735
  init_defaults();
@@ -103212,8 +103212,8 @@ function deriveWebUrl(apiBaseUrl) {
103212
103212
  }
103213
103213
  var TSO_UMI_ROUTE_PREFIX = "/v3umijs/tso";
103214
103214
  function buildTsoPageWebUrl(webUrl, tsoSubRoute, extraQuery) {
103215
- const path25 = tsoSubRoute.replace(/^\/+/, "");
103216
- const umiPath = `${TSO_UMI_ROUTE_PREFIX}/${path25}`;
103215
+ const path27 = tsoSubRoute.replace(/^\/+/, "");
103216
+ const umiPath = `${TSO_UMI_ROUTE_PREFIX}/${path27}`;
103217
103217
  const q = new URLSearchParams({ tso: umiPath });
103218
103218
  if (extraQuery) {
103219
103219
  for (const [key, value] of Object.entries(extraQuery)) {
@@ -103221,7 +103221,7 @@ function buildTsoPageWebUrl(webUrl, tsoSubRoute, extraQuery) {
103221
103221
  if (v) q.set(key, v);
103222
103222
  }
103223
103223
  }
103224
- return `${webUrl.replace(/\/+$/, "")}/v3/foreign_trade/tso/${path25}?${q.toString()}`;
103224
+ return `${webUrl.replace(/\/+$/, "")}/v3/foreign_trade/tso/${path27}?${q.toString()}`;
103225
103225
  }
103226
103226
  function cmdConfigShow() {
103227
103227
  const shared = readSharedConfig();
@@ -110408,7 +110408,7 @@ function cloneKeywordBlockShell(block) {
110408
110408
  delete shell["matchTypeV2"];
110409
110409
  return shell;
110410
110410
  }
110411
- function splitKeywordsForBatchJobBlockIfMixed(block, path25, warnings) {
110411
+ function splitKeywordsForBatchJobBlockIfMixed(block, path27, warnings) {
110412
110412
  const declaredUi = matchTypeV2ToUi(readBlockMatchTypeRaw(block));
110413
110413
  const entries = collectKeywordEntriesFromBlock(block);
110414
110414
  if (entries.length === 0) return [block];
@@ -110427,7 +110427,7 @@ function splitKeywordsForBatchJobBlockIfMixed(block, path25, warnings) {
110427
110427
  (ui) => matchTypeUiToV2(ui)
110428
110428
  );
110429
110429
  warnings.push(
110430
- `${path25} \u542B\u591A\u79CD\u5339\u914D\u7C7B\u578B\uFF0C\u5DF2\u81EA\u52A8\u62C6\u5206\u4E3A ${groups.size} \u4E2A KeywordsForBatchJob \u5757\uFF08${labels.join("\u3001")}\uFF09`
110430
+ `${path27} \u542B\u591A\u79CD\u5339\u914D\u7C7B\u578B\uFF0C\u5DF2\u81EA\u52A8\u62C6\u5206\u4E3A ${groups.size} \u4E2A KeywordsForBatchJob \u5757\uFF08${labels.join("\u3001")}\uFF09`
110431
110431
  );
110432
110432
  const shell = cloneKeywordBlockShell(block);
110433
110433
  const splitBlocks = [];
@@ -110447,16 +110447,16 @@ function splitKeywordsForBatchJobBlockIfMixed(block, path25, warnings) {
110447
110447
  }
110448
110448
  return splitBlocks;
110449
110449
  }
110450
- function validateKeywordCore(core, path25, errors, lengthViolations) {
110450
+ function validateKeywordCore(core, path27, errors, lengthViolations) {
110451
110451
  const trimmed = core.trim();
110452
110452
  if (!trimmed) {
110453
- errors.push(`${path25} \u5173\u952E\u8BCD\u8BCD\u5E72\u4E0D\u80FD\u4E3A\u7A7A`);
110453
+ errors.push(`${path27} \u5173\u952E\u8BCD\u8BCD\u5E72\u4E0D\u80FD\u4E3A\u7A7A`);
110454
110454
  return false;
110455
110455
  }
110456
110456
  if (trimmed.length > GOOGLE_KEYWORD_MAX_CORE_LENGTH) {
110457
110457
  if (lengthViolations) {
110458
110458
  pushLengthViolation(lengthViolations, {
110459
- path: path25,
110459
+ path: path27,
110460
110460
  field: "KeywordText",
110461
110461
  kind: "keyword_core",
110462
110462
  limit: GOOGLE_KEYWORD_MAX_CORE_LENGTH,
@@ -110466,13 +110466,13 @@ function validateKeywordCore(core, path25, errors, lengthViolations) {
110466
110466
  });
110467
110467
  }
110468
110468
  errors.push(
110469
- `${path25} \u5173\u952E\u8BCD\u8BCD\u5E72\u8D85\u8FC7 ${GOOGLE_KEYWORD_MAX_CORE_LENGTH} \u5B57\u7B26\uFF08\u5F53\u524D ${trimmed.length}\uFF09\uFF1A"${trimmed}"`
110469
+ `${path27} \u5173\u952E\u8BCD\u8BCD\u5E72\u8D85\u8FC7 ${GOOGLE_KEYWORD_MAX_CORE_LENGTH} \u5B57\u7B26\uFF08\u5F53\u524D ${trimmed.length}\uFF09\uFF1A"${trimmed}"`
110470
110470
  );
110471
110471
  return false;
110472
110472
  }
110473
110473
  if (!VALID_CORE_REGEX.test(trimmed)) {
110474
110474
  errors.push(
110475
- `${path25} \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" : ""}"`
110475
+ `${path27} \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" : ""}"`
110476
110476
  );
110477
110477
  return false;
110478
110478
  }
@@ -110529,24 +110529,24 @@ function canonicalizeKeywordBatchBlock(block, resolvedMatchV2, normalizedTexts,
110529
110529
  }
110530
110530
  }
110531
110531
  }
110532
- function pushKeywordAutoFixWarning(warnings, path25, fieldLabel, trimmedRaw, formatted, declaredUi, beforeUi, matchType, inferredMatchType) {
110532
+ function pushKeywordAutoFixWarning(warnings, path27, fieldLabel, trimmedRaw, formatted, declaredUi, beforeUi, matchType, inferredMatchType) {
110533
110533
  if (inferredMatchType) {
110534
110534
  warnings.push(
110535
- `${path25}.${fieldLabel} \u672A\u6307\u5B9A MatchTypeV2\uFF0C\u5DF2\u6309\u8BCD\u9762\u63A8\u65AD\u4E3A ${matchTypeUiToV2(matchType)}`
110535
+ `${path27}.${fieldLabel} \u672A\u6307\u5B9A MatchTypeV2\uFF0C\u5DF2\u6309\u8BCD\u9762\u63A8\u65AD\u4E3A ${matchTypeUiToV2(matchType)}`
110536
110536
  );
110537
110537
  return;
110538
110538
  }
110539
110539
  if (declaredUi && beforeUi !== declaredUi) {
110540
110540
  warnings.push(
110541
- `${path25}.${fieldLabel} MatchTypeV2=${matchTypeUiToV2(declaredUi)} \u4E0E\u8BCD\u9762\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u4FEE\u590D\u4E3A\uFF1A${formatted}`
110541
+ `${path27}.${fieldLabel} MatchTypeV2=${matchTypeUiToV2(declaredUi)} \u4E0E\u8BCD\u9762\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u4FEE\u590D\u4E3A\uFF1A${formatted}`
110542
110542
  );
110543
110543
  return;
110544
110544
  }
110545
110545
  if (formatted !== trimmedRaw) {
110546
- warnings.push(`${path25}.${fieldLabel} \u5DF2\u81EA\u52A8\u4FEE\u590D\u8BCD\u9762\uFF1A${trimmedRaw} \u2192 ${formatted}`);
110546
+ warnings.push(`${path27}.${fieldLabel} \u5DF2\u81EA\u52A8\u4FEE\u590D\u8BCD\u9762\uFF1A${trimmedRaw} \u2192 ${formatted}`);
110547
110547
  }
110548
110548
  }
110549
- function normalizeKeywordTextList(texts, matchTypeRaw, path25, fieldLabel, errors, warnings, lengthViolations) {
110549
+ function normalizeKeywordTextList(texts, matchTypeRaw, path27, fieldLabel, errors, warnings, lengthViolations) {
110550
110550
  const declaredUi = matchTypeV2ToUi(matchTypeRaw);
110551
110551
  const normalized = [];
110552
110552
  const seen = /* @__PURE__ */ new Set();
@@ -110554,7 +110554,7 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path25, fieldLabel, error
110554
110554
  for (let k = 0; k < texts.length; k++) {
110555
110555
  const raw = texts[k];
110556
110556
  if (typeof raw !== "string" || !raw.trim()) {
110557
- errors.push(`${path25}.${fieldLabel}[${k}] \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
110557
+ errors.push(`${path27}.${fieldLabel}[${k}] \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
110558
110558
  continue;
110559
110559
  }
110560
110560
  const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
@@ -110562,7 +110562,7 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path25, fieldLabel, error
110562
110562
  const { formatted, matchType, inferredMatchType } = normalizeKeywordSurface(trimmedRaw, matchTypeRaw);
110563
110563
  pushKeywordAutoFixWarning(
110564
110564
  warnings,
110565
- path25,
110565
+ path27,
110566
110566
  fieldLabel,
110567
110567
  trimmedRaw,
110568
110568
  formatted,
@@ -110572,10 +110572,10 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path25, fieldLabel, error
110572
110572
  inferredMatchType
110573
110573
  );
110574
110574
  const core = unwrapKeywordDisplayTextForEdit(formatted);
110575
- if (!validateKeywordCore(core, `${path25}.${fieldLabel}[${k}]`, errors, lengthViolations)) continue;
110575
+ if (!validateKeywordCore(core, `${path27}.${fieldLabel}[${k}]`, errors, lengthViolations)) continue;
110576
110576
  const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
110577
110577
  if (seen.has(dedupeKey)) {
110578
- warnings.push(`${path25}.${fieldLabel}[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
110578
+ warnings.push(`${path27}.${fieldLabel}[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
110579
110579
  continue;
110580
110580
  }
110581
110581
  seen.add(dedupeKey);
@@ -110584,12 +110584,12 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path25, fieldLabel, error
110584
110584
  }
110585
110585
  return { normalized, resolvedUi };
110586
110586
  }
110587
- function normalizeKeywordsForBatchJobBlock(block, path25, errors, warnings, lengthViolations) {
110587
+ function normalizeKeywordsForBatchJobBlock(block, path27, errors, warnings, lengthViolations) {
110588
110588
  const matchTypeRaw = readBlockMatchTypeRaw(block);
110589
110589
  const declaredUi = matchTypeV2ToUi(matchTypeRaw);
110590
110590
  if (matchTypeRaw !== void 0 && declaredUi === null) {
110591
110591
  errors.push(
110592
- `${path25}.MatchTypeV2 \u65E0\u6548\uFF08${String(matchTypeRaw)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1ABROAD | PHRASE | EXACT`
110592
+ `${path27}.MatchTypeV2 \u65E0\u6548\uFF08${String(matchTypeRaw)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1ABROAD | PHRASE | EXACT`
110593
110593
  );
110594
110594
  return;
110595
110595
  }
@@ -110604,7 +110604,7 @@ function normalizeKeywordsForBatchJobBlock(block, path25, errors, warnings, leng
110604
110604
  const result = normalizeKeywordTextList(
110605
110605
  texts,
110606
110606
  matchTypeRaw,
110607
- path25,
110607
+ path27,
110608
110608
  "KeywordText",
110609
110609
  errors,
110610
110610
  warnings,
@@ -110613,7 +110613,7 @@ function normalizeKeywordsForBatchJobBlock(block, path25, errors, warnings, leng
110613
110613
  normalizedTexts = result.normalized;
110614
110614
  if (result.resolvedUi) resolvedUi = result.resolvedUi;
110615
110615
  if (normalizedTexts.length === 0 && texts.length > 0) {
110616
- errors.push(`${path25}.KeywordText \u7ECF\u6821\u9A8C\u540E\u65E0\u6709\u6548\u5173\u952E\u8BCD\uFF0C\u8BF7\u4FEE\u6B63\u8BCD\u9762\u6216 MatchTypeV2`);
110616
+ errors.push(`${path27}.KeywordText \u7ECF\u6821\u9A8C\u540E\u65E0\u6709\u6548\u5173\u952E\u8BCD\uFF0C\u8BF7\u4FEE\u6B63\u8BCD\u9762\u6216 MatchTypeV2`);
110617
110617
  }
110618
110618
  }
110619
110619
  if (hasItems && items) {
@@ -110621,12 +110621,12 @@ function normalizeKeywordsForBatchJobBlock(block, path25, errors, warnings, leng
110621
110621
  for (let k = 0; k < items.length; k++) {
110622
110622
  const item = items[k];
110623
110623
  if (!item || typeof item !== "object") {
110624
- errors.push(`${path25}.Items[${k}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
110624
+ errors.push(`${path27}.Items[${k}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
110625
110625
  continue;
110626
110626
  }
110627
110627
  const raw = readItemText(item);
110628
110628
  if (raw === null || !raw.trim()) {
110629
- errors.push(`${path25}.Items[${k}].Text \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
110629
+ errors.push(`${path27}.Items[${k}].Text \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
110630
110630
  continue;
110631
110631
  }
110632
110632
  const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
@@ -110634,7 +110634,7 @@ function normalizeKeywordsForBatchJobBlock(block, path25, errors, warnings, leng
110634
110634
  const { formatted, matchType, inferredMatchType } = normalizeKeywordSurface(trimmedRaw, matchTypeRaw);
110635
110635
  pushKeywordAutoFixWarning(
110636
110636
  warnings,
110637
- path25,
110637
+ path27,
110638
110638
  `Items[${k}].Text`,
110639
110639
  trimmedRaw,
110640
110640
  formatted,
@@ -110644,10 +110644,10 @@ function normalizeKeywordsForBatchJobBlock(block, path25, errors, warnings, leng
110644
110644
  inferredMatchType
110645
110645
  );
110646
110646
  const core = unwrapKeywordDisplayTextForEdit(formatted);
110647
- if (!validateKeywordCore(core, `${path25}.Items[${k}].Text`, errors, lengthViolations)) continue;
110647
+ if (!validateKeywordCore(core, `${path27}.Items[${k}].Text`, errors, lengthViolations)) continue;
110648
110648
  const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
110649
110649
  if (seen.has(dedupeKey)) {
110650
- warnings.push(`${path25}.Items[${k}].Text \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
110650
+ warnings.push(`${path27}.Items[${k}].Text \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
110651
110651
  continue;
110652
110652
  }
110653
110653
  seen.add(dedupeKey);
@@ -113255,12 +113255,12 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113255
113255
  for (let ki = 0; ki < texts.length; ki++) {
113256
113256
  const t = texts[ki];
113257
113257
  if (typeof t !== "string" || !t.trim()) continue;
113258
- const path25 = `${groupPath}.KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
113258
+ const path27 = `${groupPath}.KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
113259
113259
  const key = keywordKey(t, matchTypeV2);
113260
113260
  if (!liveKwKeys.has(key)) {
113261
113261
  missing.push({
113262
113262
  layer: "keyword",
113263
- path: path25,
113263
+ path: path27,
113264
113264
  adGroupName: groupName,
113265
113265
  plannedContent: `\u8BCD\u9762: ${t} | \u5339\u914D: ${matchTypeV2}`,
113266
113266
  liveNote: `\u540C\u7EC4\u5DF2\u6709 ${liveKwInGroup.length} \u6761\u5173\u952E\u8BCD\uFF0C\u65E0\u952E ${key}`,
@@ -113278,12 +113278,12 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113278
113278
  for (let ai = 0; ai < ads.length; ai++) {
113279
113279
  const ad = asRecord2(ads[ai]);
113280
113280
  if (!ad) continue;
113281
- const path25 = `${groupPath}.AdsForBatchJob[${ai}]`;
113281
+ const path27 = `${groupPath}.AdsForBatchJob[${ai}]`;
113282
113282
  const primary = pickString(ad["headlinePart1"], ad["AdTitle"]);
113283
113283
  if (!primary) {
113284
113284
  missing.push({
113285
113285
  layer: "ad",
113286
- path: path25,
113286
+ path: path27,
113287
113287
  adGroupName: groupName,
113288
113288
  plannedContent: `AdsForBatchJob[${ai}]\uFF08\u7F3A\u5C11 headlinePart1\uFF0C\u65E0\u6CD5\u6BD4\u5BF9\uFF09`,
113289
113289
  liveNote: `\u540C\u7EC4\u5DF2\u6709 ${liveAdsInGroup.length} \u6761\u521B\u610F`,
@@ -113296,7 +113296,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113296
113296
  if (!found) {
113297
113297
  missing.push({
113298
113298
  layer: "ad",
113299
- path: path25,
113299
+ path: path27,
113300
113300
  adGroupName: groupName,
113301
113301
  plannedContent: `RSA \u9996\u6807\u9898: ${primary}${finalUrl ? ` | \u843D\u5730\u9875: ${finalUrl}` : ""}`,
113302
113302
  liveNote: `\u540C\u7EC4\u5DF2\u6709 ${liveAdsInGroup.length} \u6761 RSA\uFF0C\u65E0\u6B64\u9996\u6807\u9898`,
@@ -114494,13 +114494,13 @@ function pmaxChannelTypesUrl(googleApiUrl) {
114494
114494
  }
114495
114495
  function pmaxCampaignUrl(googleApiUrl, accountId, campaignId) {
114496
114496
  const base = googleApiUrl.replace(/\/$/, "");
114497
- const path25 = `${base}/accounts/${accountId}/campaign/pmax`;
114498
- return campaignId ? `${path25}/${campaignId}` : path25;
114497
+ const path27 = `${base}/accounts/${accountId}/campaign/pmax`;
114498
+ return campaignId ? `${path27}/${campaignId}` : path27;
114499
114499
  }
114500
114500
  function pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, suffix) {
114501
114501
  const base = googleApiUrl.replace(/\/$/, "");
114502
- const path25 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
114503
- return suffix ? `${path25}/${suffix.replace(/^\//, "")}` : path25;
114502
+ const path27 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
114503
+ return suffix ? `${path27}/${suffix.replace(/^\//, "")}` : path27;
114504
114504
  }
114505
114505
  function pmaxCampaignAssetGroupUrl(googleApiUrl, accountId, campaignId) {
114506
114506
  const base = googleApiUrl.replace(/\/$/, "");
@@ -121435,6 +121435,8 @@ function register25(program2) {
121435
121435
  console.log(" CLI \u62C9\u6570\uFF1A");
121436
121436
  console.log(" siluzan-tso website-diagnosis collect --url <url> --json-out ./snap-web");
121437
121437
  console.log(" siluzan-tso website-diagnosis performance --url <url> --json-out ./snap-web");
121438
+ console.log(" \u9ED8\u8BA4\u4EA4\u4ED8 HTML\uFF1A");
121439
+ console.log(" siluzan-tso website-diagnosis render --data ./diagnosis.json --collect ./snap-web/<collect>.json");
121438
121440
  console.log(" Skill\uFF1Areferences/analytics/website-diagnosis-guide.md\n");
121439
121441
  console.log(" \u3010\u8D26\u6237\u5217\u8868 ARIT \u5206\u3011");
121440
121442
  console.log(" list-accounts \u4F1A\u8865\u5145 ma.diagnoseReports \u4E2D reportSource=ARIT \u7684\u5F97\u5206\uFF1B");
@@ -121633,7 +121635,7 @@ async function runWebsiteDiagnosisCollect(opts) {
121633
121635
  htmlPreview,
121634
121636
  ...htmlFull ? { htmlContent: htmlFull } : {},
121635
121637
  ...htmlError ? { htmlError } : {},
121636
- agentHint: "1) \u6309 website-diagnosis-rules.md \u751F\u6210\u8BCA\u65AD JSON\uFF1B2) siluzan-tso website-diagnosis render --data <diagnosis.json> --collect <\u672C\u6587\u4EF6> \u8F93\u51FA\u5E26\u56FE\u8868\u7684 website-diagnosis-report.html\u3002"
121638
+ agentHint: "1) \u6309 website-diagnosis-rules.md \u751F\u6210\u8BCA\u65AD JSON\uFF1B2) siluzan-tso website-diagnosis render --data <diagnosis.json> --collect <\u672C\u6587\u4EF6> \u9ED8\u8BA4\u4EA7\u51FA HTML \u7EC8\u7A3F website-diagnosis-report.html\uFF08\u7981\u6B62\u4EC5 Markdown/JSON \u4EA4\u4ED8\uFF09\u3002"
121637
121639
  };
121638
121640
  if (await emitCliJsonOrSnapshot(
121639
121641
  { jsonOut: opts.jsonOut },
@@ -121756,7 +121758,7 @@ async function runWebsiteDiagnosisRender(opts) {
121756
121758
  // src/commands/website-diagnosis/register.ts
121757
121759
  function registerWebsiteDiagnosisCommands(program2) {
121758
121760
  const root = program2.command("website-diagnosis").description(
121759
- "\u7F51\u7AD9\u8BCA\u65AD\uFF1ALighthouse \u6027\u80FD\u3001HTML \u91C7\u96C6\uFF08Agent download-assets\uFF09\u3001ARIT \u5386\u53F2\u5F97\u5206\u67E5\u8BE2"
121761
+ "\u7F51\u7AD9\u8BCA\u65AD\uFF1ALighthouse \u6027\u80FD\u3001HTML \u91C7\u96C6\u3001ARIT \u5386\u53F2\u5F97\u5206\uFF1B\u9ED8\u8BA4\u4EA4\u4ED8 HTML \u62A5\u544A\uFF08render \u5B50\u547D\u4EE4\uFF09"
121760
121762
  );
121761
121763
  root.command("performance").description("\u62C9\u53D6 Lighthouse \u6027\u80FD\u6570\u636E\uFF08GET WebsiteDiagnosisReports/performance\uFF09").requiredOption("--url <url>", "\u7F51\u7AD9 URL\uFF08\u53EF\u7701\u7565 https://\uFF0C\u81EA\u52A8\u8865\u5168\uFF09").option("--token <token>", "JWT\uFF08\u9ED8\u8BA4\u8BFB config / \u73AF\u5883\u53D8\u91CF\uFF09").option("--verbose", "\u6253\u5370\u8BF7\u6C42\u8BE6\u60C5").option("--json-out <dir>", "\u843D\u76D8 cli-manifest + JSON").action(async (opts) => {
121762
121764
  await runWebsiteDiagnosisPerformance({
@@ -121789,7 +121791,7 @@ function registerWebsiteDiagnosisCommands(program2) {
121789
121791
  }
121790
121792
  );
121791
121793
  root.command("render").description(
121792
- "\u6839\u636E\u8BCA\u65AD JSON \u751F\u6210\u5E26\u56FE\u8868\u7684\u5355\u6587\u4EF6 HTML \u62A5\u544A\uFF08website-diagnosis-report.html\uFF09"
121794
+ "\u6839\u636E\u8BCA\u65AD JSON \u751F\u6210 HTML \u7EC8\u7A3F\uFF08\u9ED8\u8BA4\u4EA4\u4ED8\u683C\u5F0F\uFF0Cwebsite-diagnosis-report.html\uFF09"
121793
121795
  ).requiredOption("--data <file>", "\u8BCA\u65AD\u7ED3\u679C JSON\uFF08getWebsiteDiagnosisData \u540C\u7ED3\u6784\uFF09").option("--collect <file>", "\u53EF\u9009\uFF1Acollect \u843D\u76D8 JSON\uFF0C\u7528\u4E8E\u5408\u5E76 lighthouse").option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
121794
121796
  await runWebsiteDiagnosisRender({
121795
121797
  dataFile: opts.data,
@@ -122476,9 +122478,298 @@ function endpointHintForFacebookSection(def, apiId) {
122476
122478
  return `GET \u2026/FacebookAds/${apiId}/${def.segment}`;
122477
122479
  }
122478
122480
 
122481
+ // src/commands/facebook-analysis/register-cli.ts
122482
+ import { Command } from "commander";
122483
+
122484
+ // src/commands/facebook-analysis/render-report.ts
122485
+ import fs17 from "fs";
122486
+ import fsPromises2 from "fs/promises";
122487
+ import path24 from "path";
122488
+ import { fileURLToPath as fileURLToPath5 } from "url";
122489
+
122490
+ // src/commands/facebook-analysis/merge-snapshot.ts
122491
+ import * as fs16 from "fs/promises";
122492
+ import * as path23 from "path";
122493
+ function asRecord5(value) {
122494
+ if (value && typeof value === "object" && !Array.isArray(value)) {
122495
+ return value;
122496
+ }
122497
+ return null;
122498
+ }
122499
+ function num(value) {
122500
+ const n = Number(value);
122501
+ return Number.isFinite(n) ? n : void 0;
122502
+ }
122503
+ function audienceLabel(age, gender) {
122504
+ const g = String(gender ?? "").toLowerCase();
122505
+ const genderZh = g === "male" ? "male" : g === "female" ? "female" : String(gender ?? "");
122506
+ return `${age ?? "\u2014"} \xB7 ${genderZh}`;
122507
+ }
122508
+ function fatigueFromFrequency(freq) {
122509
+ if (freq == null) return { level: "\u4F4E", score: 25 };
122510
+ if (freq >= 2.2) return { level: "\u9AD8", score: 75 };
122511
+ if (freq >= 1.6) return { level: "\u4E2D", score: 55 };
122512
+ return { level: "\u4F4E", score: 25 };
122513
+ }
122514
+ function statusFromCpl(cpl, avg) {
122515
+ if (cpl == null || avg <= 0) return { tag: "yellow", label: "\u89C2\u5BDF" };
122516
+ if (cpl <= avg * 0.9) return { tag: "green", label: "\u76C8\u5229" };
122517
+ if (cpl >= avg * 1.15) return { tag: "red", label: "\u5371\u9669" };
122518
+ return { tag: "yellow", label: "\u89C2\u5BDF" };
122519
+ }
122520
+ async function readManifest(snapshotDir) {
122521
+ const entries = await fs16.readdir(snapshotDir);
122522
+ const manifestNames = entries.filter((n) => n.startsWith("report-manifest") && n.endsWith(".json"));
122523
+ const ordered = [
122524
+ ...manifestNames.filter((n) => n !== REPORT_MANIFEST_FILE),
122525
+ ...manifestNames.includes(REPORT_MANIFEST_FILE) ? [REPORT_MANIFEST_FILE] : []
122526
+ ];
122527
+ for (const name2 of ordered) {
122528
+ try {
122529
+ const raw = await fs16.readFile(path23.join(snapshotDir, name2), "utf8");
122530
+ const parsed = JSON.parse(raw);
122531
+ if (parsed?.artifacts?.length) return parsed;
122532
+ } catch {
122533
+ }
122534
+ }
122535
+ return null;
122536
+ }
122537
+ async function loadSectionFile(snapshotDir, file) {
122538
+ try {
122539
+ const raw = await fs16.readFile(path23.join(snapshotDir, file), "utf8");
122540
+ return asRecord5(JSON.parse(raw));
122541
+ } catch {
122542
+ return null;
122543
+ }
122544
+ }
122545
+ function aggregatePlatform(networks) {
122546
+ const map = /* @__PURE__ */ new Map();
122547
+ for (const row of networks) {
122548
+ const platform = String(row.publisherPlatform ?? row.network ?? "unknown");
122549
+ const prev = map.get(platform) ?? { spend: 0, results: 0 };
122550
+ map.set(platform, {
122551
+ spend: prev.spend + (num(row.spend) ?? 0),
122552
+ results: prev.results + (num(row.results) ?? 0)
122553
+ });
122554
+ }
122555
+ const labels = [];
122556
+ const cpl = [];
122557
+ const spend = [];
122558
+ for (const [label, agg] of [...map.entries()].sort((a, b) => b[1].spend - a[1].spend)) {
122559
+ labels.push(label);
122560
+ spend.push(agg.spend);
122561
+ cpl.push(agg.results > 0 ? agg.spend / agg.results : 0);
122562
+ }
122563
+ return { labels, cpl, spend };
122564
+ }
122565
+ function buildAudienceRows(audiences) {
122566
+ return audiences.map((a) => ({
122567
+ label: audienceLabel(a.age, a.gender),
122568
+ spend: num(a.spend) ?? 0,
122569
+ results: num(a.results) ?? 0,
122570
+ costPerResult: num(a.costPerResult) ?? (num(a.results) ? (num(a.spend) ?? 0) / (num(a.results) ?? 1) : 0),
122571
+ frequency: num(a.frequency)
122572
+ })).filter((r) => r.spend > 0 || r.results > 0);
122573
+ }
122574
+ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
122575
+ const absDir = path23.resolve(snapshotDir);
122576
+ const manifest = await readManifest(snapshotDir);
122577
+ if (!manifest) {
122578
+ throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 report-manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C facebook-analysis --json-out`);
122579
+ }
122580
+ const sectionMap = /* @__PURE__ */ new Map();
122581
+ for (const art of manifest.artifacts) {
122582
+ const data = await loadSectionFile(absDir, art.file);
122583
+ if (data) sectionMap.set(art.section, data);
122584
+ }
122585
+ const overview = sectionMap.get("overview");
122586
+ const current = asRecord5(overview?.currentPeriod);
122587
+ const accountName = typeof overview?.accountName === "string" ? overview.accountName : void 0;
122588
+ const accountId = manifest.accountId ?? (typeof overview?.accountId === "string" ? overview.accountId : void 0);
122589
+ const kpis = { ...payload.kpis ?? {} };
122590
+ if (current) {
122591
+ if (kpis.spend == null) kpis.spend = current.spend;
122592
+ if (kpis.results == null) kpis.results = current.results;
122593
+ if (kpis.costPerResult == null) kpis.costPerResult = current.costPerResult;
122594
+ if (kpis.reach == null) kpis.reach = current.reach;
122595
+ if (kpis.impressions == null) kpis.impressions = current.impressions;
122596
+ if (kpis.frequency == null) kpis.frequency = current.frequency;
122597
+ }
122598
+ const meta = { ...payload.meta ?? {} };
122599
+ if (!meta.accountName && accountName) meta.accountName = accountName;
122600
+ if (!meta.accountId && accountId) meta.accountId = accountId;
122601
+ if (!meta.startDate && manifest.dateRange?.start) meta.startDate = manifest.dateRange.start;
122602
+ if (!meta.endDate && manifest.dateRange?.end) meta.endDate = manifest.dateRange.end;
122603
+ if (!meta.attributionSetting && current?.attributionSetting) {
122604
+ meta.attributionSetting = String(current.attributionSetting);
122605
+ }
122606
+ if (!meta.resultType && current?.resultType) meta.resultType = String(current.resultType);
122607
+ const charts = { ...payload.charts ?? {} };
122608
+ const avgCpl = num(kpis.costPerResult) ?? 0;
122609
+ if (!charts.platform) {
122610
+ const platform = sectionMap.get("platform");
122611
+ const networks = Array.isArray(platform?.networks) ? platform.networks : [];
122612
+ if (networks.length) charts.platform = aggregatePlatform(networks);
122613
+ }
122614
+ if (!charts.country) {
122615
+ const country = sectionMap.get("country");
122616
+ const countries = Array.isArray(country?.countries) ? country.countries : [];
122617
+ if (countries.length) {
122618
+ const sorted = [...countries].sort((a, b) => (num(b.spend) ?? 0) - (num(a.spend) ?? 0));
122619
+ charts.country = {
122620
+ labels: sorted.map((c) => String(c.countryOrRegion ?? "\u2014")),
122621
+ cpl: sorted.map((c) => num(c.costPerResult) ?? 0)
122622
+ };
122623
+ }
122624
+ }
122625
+ if (!charts.audience) {
122626
+ const audience = sectionMap.get("audience");
122627
+ const audiences = Array.isArray(audience?.audiences) ? audience.audiences : [];
122628
+ if (audiences.length) {
122629
+ const rows = buildAudienceRows(audiences).sort((a, b) => a.costPerResult - b.costPerResult);
122630
+ charts.audience = {
122631
+ labels: rows.map((r) => r.label),
122632
+ cpl: rows.map((r) => r.costPerResult),
122633
+ leads: rows.map((r) => r.results)
122634
+ };
122635
+ }
122636
+ }
122637
+ if (!charts.funnel && num(kpis.reach) != null) {
122638
+ charts.funnel = { reach: kpis.reach, results: kpis.results ?? 0 };
122639
+ }
122640
+ const tables = { ...payload.tables ?? {} };
122641
+ if (!tables.adSets) {
122642
+ const adSets = sectionMap.get("ad-sets");
122643
+ const groups = Array.isArray(adSets?.adGroups) ? adSets.adGroups : [];
122644
+ if (groups.length) {
122645
+ tables.adSets = groups.map((g) => {
122646
+ const cpl = num(g.costPerResult);
122647
+ const freq = num(g.frequency);
122648
+ const fatigue = fatigueFromFrequency(freq);
122649
+ const status = statusFromCpl(cpl, avgCpl);
122650
+ return {
122651
+ name: String(g.adGroupName ?? g.campaignName ?? "\u2014"),
122652
+ spend: num(g.spend) ?? 0,
122653
+ results: num(g.results) ?? 0,
122654
+ costPerResult: cpl ?? 0,
122655
+ frequency: freq,
122656
+ fatigueLevel: fatigue.level,
122657
+ fatigueScore: fatigue.score,
122658
+ statusTag: status.tag,
122659
+ statusLabel: status.label
122660
+ };
122661
+ }).sort((a, b) => b.spend - a.spend);
122662
+ }
122663
+ }
122664
+ if (!tables.audienceTop || !tables.audienceBottom) {
122665
+ const audience = sectionMap.get("audience");
122666
+ const audiences = Array.isArray(audience?.audiences) ? audience.audiences : [];
122667
+ if (audiences.length) {
122668
+ const rows = buildAudienceRows(audiences).sort((a, b) => a.costPerResult - b.costPerResult);
122669
+ const topN = rows.slice(0, 10);
122670
+ const bottomN = [...rows].reverse().slice(0, 10);
122671
+ if (!tables.audienceTop) tables.audienceTop = topN;
122672
+ if (!tables.audienceBottom) tables.audienceBottom = bottomN;
122673
+ }
122674
+ }
122675
+ return {
122676
+ ...payload,
122677
+ meta,
122678
+ kpis,
122679
+ charts,
122680
+ tables
122681
+ };
122682
+ }
122683
+
122684
+ // src/commands/facebook-analysis/render-report.ts
122685
+ var TEMPLATE_BASENAMES2 = {
122686
+ html: "meta-period-report.html",
122687
+ runtime: "meta-period-report.runtime.js"
122688
+ };
122689
+ var INJECT_MARKER = "window.__META_PERIOD_REPORT__ = window.__META_PERIOD_REPORT__ || null;";
122690
+ function resolveSkillTemplatePath2(basename11) {
122691
+ const dir = path24.dirname(fileURLToPath5(import.meta.url));
122692
+ const candidates = [
122693
+ path24.join(dir, "skill", "report-templates", basename11),
122694
+ path24.join(dir, "skill", "siluzan-ads", "report-templates", basename11),
122695
+ path24.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename11)
122696
+ ];
122697
+ for (const p of candidates) {
122698
+ if (fs17.existsSync(p)) return p;
122699
+ }
122700
+ return candidates[0];
122701
+ }
122702
+ function metaPeriodReportTemplatePath() {
122703
+ return resolveSkillTemplatePath2(TEMPLATE_BASENAMES2.html);
122704
+ }
122705
+ function metaPeriodReportRuntimePath() {
122706
+ return resolveSkillTemplatePath2(TEMPLATE_BASENAMES2.runtime);
122707
+ }
122708
+ async function readJsonFile2(filePath) {
122709
+ const text = await fsPromises2.readFile(filePath, "utf8");
122710
+ try {
122711
+ return JSON.parse(text);
122712
+ } catch {
122713
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
122714
+ }
122715
+ }
122716
+ function asRecord6(value) {
122717
+ if (value && typeof value === "object" && !Array.isArray(value)) {
122718
+ return value;
122719
+ }
122720
+ throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
122721
+ }
122722
+ function injectReportData2(html, payload) {
122723
+ const json = JSON.stringify(payload).replace(/</g, "\\u003c");
122724
+ if (!html.includes(INJECT_MARKER)) {
122725
+ throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 meta-period-report.html");
122726
+ }
122727
+ return html.replace(INJECT_MARKER, `window.__META_PERIOD_REPORT__ = ${json};`);
122728
+ }
122729
+ async function runFacebookAnalysisRender(opts) {
122730
+ const dataPath = path24.resolve(opts.dataFile);
122731
+ let data = asRecord6(await readJsonFile2(dataPath));
122732
+ if (opts.snapshotDir?.trim()) {
122733
+ data = await mergeFacebookSnapshotIntoReport(data, path24.resolve(opts.snapshotDir));
122734
+ }
122735
+ const templatePath = metaPeriodReportTemplatePath();
122736
+ let html;
122737
+ try {
122738
+ html = await fsPromises2.readFile(templatePath, "utf8");
122739
+ } catch {
122740
+ console.error(`
122741
+ \u274C \u672A\u627E\u5230\u62A5\u544A\u6A21\u677F\uFF1A${templatePath}
122742
+ \u8BF7\u5148\u6267\u884C npm run build
122743
+ `);
122744
+ process.exit(1);
122745
+ }
122746
+ const outPath = path24.resolve(
122747
+ opts.out ?? path24.join(path24.dirname(dataPath), "meta-period-report.html")
122748
+ );
122749
+ const outDir = path24.dirname(outPath);
122750
+ await fsPromises2.mkdir(outDir, { recursive: true });
122751
+ await fsPromises2.writeFile(outPath, injectReportData2(html, data), "utf8");
122752
+ const runtimeSrc = metaPeriodReportRuntimePath();
122753
+ const runtimeOut = path24.join(outDir, TEMPLATE_BASENAMES2.runtime);
122754
+ let runtimeCopied = false;
122755
+ try {
122756
+ await fsPromises2.copyFile(runtimeSrc, runtimeOut);
122757
+ runtimeCopied = true;
122758
+ } catch {
122759
+ }
122760
+ console.log(`
122761
+ \u2705 Meta/Facebook \u5468\u671F\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
122762
+ `);
122763
+ if (runtimeCopied) {
122764
+ console.log(` \u8FD0\u884C\u65F6\u811A\u672C\uFF1A${runtimeOut}
122765
+ `);
122766
+ }
122767
+ console.log("\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u56FE\u8868\u4E0E\u5B8C\u6574\u7AE0\u8282\uFF08\u9700\u8054\u7F51\u52A0\u8F7D Chart.js CDN\uFF09\u3002\n");
122768
+ }
122769
+
122479
122770
  // src/commands/facebook-analysis/run-batch.ts
122480
122771
  init_auth();
122481
- import * as path23 from "path";
122772
+ import * as path25 from "path";
122482
122773
  import { performance as performance5 } from "perf_hooks";
122483
122774
  init_version();
122484
122775
 
@@ -122582,7 +122873,7 @@ async function runAllFacebookSections(opts) {
122582
122873
  const results = await runWithConcurrency2(tasks, concurrency);
122583
122874
  const succeeded = results.filter((r) => r.ok).length;
122584
122875
  const failed = results.length - succeeded;
122585
- const absoluteSnapshotDir = path23.resolve(opts.jsonOut);
122876
+ const absoluteSnapshotDir = path25.resolve(opts.jsonOut);
122586
122877
  const summary = {
122587
122878
  kind: "siluzan-tso-facebook-analysis-snapshot-batch",
122588
122879
  absoluteSnapshotDir,
@@ -122600,10 +122891,9 @@ async function runAllFacebookSections(opts) {
122600
122891
  }
122601
122892
 
122602
122893
  // src/commands/facebook-analysis/register-cli.ts
122603
- function registerFacebookAnalysisCommands(program2) {
122604
- const sectionHelp = FACEBOOK_SECTION_NAMES.join(", ");
122605
- program2.command("facebook-analysis").description(
122606
- "Facebook Ads \u8D26\u6237\u5206\u6790\u6279\u91CF\u62C9\u53D6\uFF08TSO reporting/media-account/FacebookAds/\u2026\uFF0C7 \u4E2A Section\uFF09"
122894
+ function registerBatchCommand(sectionHelp) {
122895
+ return new Command("run").description(
122896
+ "\u6279\u91CF\u62C9\u53D6 TSO reporting/media-account/FacebookAds Section \u6570\u636E\uFF08\u7701\u7565\u5B50\u547D\u4EE4\u540D\u65F6\u9ED8\u8BA4\u6267\u884C\u672C\u547D\u4EE4\uFF09"
122607
122897
  ).requiredOption(
122608
122898
  "-a, --account <id>",
122609
122899
  "Facebook \u5E7F\u544A\u8D26\u6237 mediaCustomerId\uFF08\u6570\u5B57\u6216 act_<\u6570\u5B57>\uFF09"
@@ -122624,6 +122914,25 @@ function registerFacebookAnalysisCommands(program2) {
122624
122914
  await runAllFacebookSections(opts);
122625
122915
  });
122626
122916
  }
122917
+ function registerFacebookAnalysisCommands(program2) {
122918
+ const sectionHelp = FACEBOOK_SECTION_NAMES.join(", ");
122919
+ const root = program2.command("facebook-analysis").description(
122920
+ "Facebook Ads \u8D26\u6237\u5206\u6790\uFF1A\u6279\u91CF\u62C9\u53D6 Section \u6570\u636E\uFF08\u9ED8\u8BA4\uFF09\uFF0C\u6216\u7531 render \u751F\u6210 HTML \u5468\u671F\u62A5\u544A"
122921
+ );
122922
+ root.addCommand(registerBatchCommand(sectionHelp), { isDefault: true });
122923
+ root.command("render").description(
122924
+ "\u6839\u636E Agent \u64B0\u5199\u7684\u62A5\u544A JSON \u751F\u6210 Meta/Facebook \u5468\u671F\u5206\u6790 HTML \u7EC8\u7A3F\uFF08meta-period-report.html\uFF09"
122925
+ ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 meta-period-report.json").option(
122926
+ "--snapshot-dir <dir>",
122927
+ "\u53EF\u9009\uFF1Afacebook-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u5408\u5E76 KPI / \u56FE\u8868 / \u8868\u683C"
122928
+ ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
122929
+ await runFacebookAnalysisRender({
122930
+ dataFile: opts.data,
122931
+ snapshotDir: opts.snapshotDir,
122932
+ out: opts.out
122933
+ });
122934
+ });
122935
+ }
122627
122936
 
122628
122937
  // src/index.ts
122629
122938
  init_version();
@@ -122631,15 +122940,15 @@ init_cli_json_snapshot();
122631
122940
  installProcessHandlers();
122632
122941
  function getVersion() {
122633
122942
  try {
122634
- const __dirname3 = path24.dirname(fileURLToPath5(import.meta.url));
122635
- const pkgPath = path24.join(__dirname3, "..", "package.json");
122636
- const pkg = JSON.parse(fs16.readFileSync(pkgPath, "utf8"));
122943
+ const __dirname3 = path26.dirname(fileURLToPath6(import.meta.url));
122944
+ const pkgPath = path26.join(__dirname3, "..", "package.json");
122945
+ const pkg = JSON.parse(fs18.readFileSync(pkgPath, "utf8"));
122637
122946
  return pkg.version ?? "0.0.0";
122638
122947
  } catch {
122639
122948
  return "0.0.0";
122640
122949
  }
122641
122950
  }
122642
- var program = new Command();
122951
+ var program = new Command2();
122643
122952
  program.name("siluzan-tso").description(
122644
122953
  "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\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"
122645
122954
  ).version(getVersion());