siluzan-tso-cli 1.1.27-beta.3 → 1.1.28-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.
package/README.md CHANGED
@@ -51,7 +51,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
51
51
  siluzan-tso init --force # 强制覆盖已存在文件
52
52
  ```
53
53
 
54
- > **注意**:当前为测试版(1.1.27-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.28-beta.1),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
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;
@@ -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,9 +102726,9 @@ 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";
102729
+ import * as fs18 from "fs";
102730
+ import * as path26 from "path";
102731
+ import { fileURLToPath as fileURLToPath6 } from "url";
102732
102732
  import { Command } from "commander";
102733
102733
 
102734
102734
  // src/commands/login/urls.ts
@@ -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(/\/$/, "");
@@ -122476,9 +122476,296 @@ function endpointHintForFacebookSection(def, apiId) {
122476
122476
  return `GET \u2026/FacebookAds/${apiId}/${def.segment}`;
122477
122477
  }
122478
122478
 
122479
+ // src/commands/facebook-analysis/render-report.ts
122480
+ import fs17 from "fs";
122481
+ import fsPromises2 from "fs/promises";
122482
+ import path24 from "path";
122483
+ import { fileURLToPath as fileURLToPath5 } from "url";
122484
+
122485
+ // src/commands/facebook-analysis/merge-snapshot.ts
122486
+ import * as fs16 from "fs/promises";
122487
+ import * as path23 from "path";
122488
+ function asRecord5(value) {
122489
+ if (value && typeof value === "object" && !Array.isArray(value)) {
122490
+ return value;
122491
+ }
122492
+ return null;
122493
+ }
122494
+ function num(value) {
122495
+ const n = Number(value);
122496
+ return Number.isFinite(n) ? n : void 0;
122497
+ }
122498
+ function audienceLabel(age, gender) {
122499
+ const g = String(gender ?? "").toLowerCase();
122500
+ const genderZh = g === "male" ? "male" : g === "female" ? "female" : String(gender ?? "");
122501
+ return `${age ?? "\u2014"} \xB7 ${genderZh}`;
122502
+ }
122503
+ function fatigueFromFrequency(freq) {
122504
+ if (freq == null) return { level: "\u4F4E", score: 25 };
122505
+ if (freq >= 2.2) return { level: "\u9AD8", score: 75 };
122506
+ if (freq >= 1.6) return { level: "\u4E2D", score: 55 };
122507
+ return { level: "\u4F4E", score: 25 };
122508
+ }
122509
+ function statusFromCpl(cpl, avg) {
122510
+ if (cpl == null || avg <= 0) return { tag: "yellow", label: "\u89C2\u5BDF" };
122511
+ if (cpl <= avg * 0.9) return { tag: "green", label: "\u76C8\u5229" };
122512
+ if (cpl >= avg * 1.15) return { tag: "red", label: "\u5371\u9669" };
122513
+ return { tag: "yellow", label: "\u89C2\u5BDF" };
122514
+ }
122515
+ async function readManifest(snapshotDir) {
122516
+ const entries = await fs16.readdir(snapshotDir);
122517
+ const manifestNames = entries.filter((n) => n.startsWith("report-manifest") && n.endsWith(".json"));
122518
+ const ordered = [
122519
+ ...manifestNames.filter((n) => n !== REPORT_MANIFEST_FILE),
122520
+ ...manifestNames.includes(REPORT_MANIFEST_FILE) ? [REPORT_MANIFEST_FILE] : []
122521
+ ];
122522
+ for (const name2 of ordered) {
122523
+ try {
122524
+ const raw = await fs16.readFile(path23.join(snapshotDir, name2), "utf8");
122525
+ const parsed = JSON.parse(raw);
122526
+ if (parsed?.artifacts?.length) return parsed;
122527
+ } catch {
122528
+ }
122529
+ }
122530
+ return null;
122531
+ }
122532
+ async function loadSectionFile(snapshotDir, file) {
122533
+ try {
122534
+ const raw = await fs16.readFile(path23.join(snapshotDir, file), "utf8");
122535
+ return asRecord5(JSON.parse(raw));
122536
+ } catch {
122537
+ return null;
122538
+ }
122539
+ }
122540
+ function aggregatePlatform(networks) {
122541
+ const map = /* @__PURE__ */ new Map();
122542
+ for (const row of networks) {
122543
+ const platform = String(row.publisherPlatform ?? row.network ?? "unknown");
122544
+ const prev = map.get(platform) ?? { spend: 0, results: 0 };
122545
+ map.set(platform, {
122546
+ spend: prev.spend + (num(row.spend) ?? 0),
122547
+ results: prev.results + (num(row.results) ?? 0)
122548
+ });
122549
+ }
122550
+ const labels = [];
122551
+ const cpl = [];
122552
+ const spend = [];
122553
+ for (const [label, agg] of [...map.entries()].sort((a, b) => b[1].spend - a[1].spend)) {
122554
+ labels.push(label);
122555
+ spend.push(agg.spend);
122556
+ cpl.push(agg.results > 0 ? agg.spend / agg.results : 0);
122557
+ }
122558
+ return { labels, cpl, spend };
122559
+ }
122560
+ function buildAudienceRows(audiences) {
122561
+ return audiences.map((a) => ({
122562
+ label: audienceLabel(a.age, a.gender),
122563
+ spend: num(a.spend) ?? 0,
122564
+ results: num(a.results) ?? 0,
122565
+ costPerResult: num(a.costPerResult) ?? (num(a.results) ? (num(a.spend) ?? 0) / (num(a.results) ?? 1) : 0),
122566
+ frequency: num(a.frequency)
122567
+ })).filter((r) => r.spend > 0 || r.results > 0);
122568
+ }
122569
+ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
122570
+ const absDir = path23.resolve(snapshotDir);
122571
+ const manifest = await readManifest(snapshotDir);
122572
+ if (!manifest) {
122573
+ throw new Error(`\u672A\u5728 ${absDir} \u627E\u5230 report-manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C facebook-analysis --json-out`);
122574
+ }
122575
+ const sectionMap = /* @__PURE__ */ new Map();
122576
+ for (const art of manifest.artifacts) {
122577
+ const data = await loadSectionFile(absDir, art.file);
122578
+ if (data) sectionMap.set(art.section, data);
122579
+ }
122580
+ const overview = sectionMap.get("overview");
122581
+ const current = asRecord5(overview?.currentPeriod);
122582
+ const accountName = typeof overview?.accountName === "string" ? overview.accountName : void 0;
122583
+ const accountId = manifest.accountId ?? (typeof overview?.accountId === "string" ? overview.accountId : void 0);
122584
+ const kpis = { ...payload.kpis ?? {} };
122585
+ if (current) {
122586
+ if (kpis.spend == null) kpis.spend = current.spend;
122587
+ if (kpis.results == null) kpis.results = current.results;
122588
+ if (kpis.costPerResult == null) kpis.costPerResult = current.costPerResult;
122589
+ if (kpis.reach == null) kpis.reach = current.reach;
122590
+ if (kpis.impressions == null) kpis.impressions = current.impressions;
122591
+ if (kpis.frequency == null) kpis.frequency = current.frequency;
122592
+ }
122593
+ const meta = { ...payload.meta ?? {} };
122594
+ if (!meta.accountName && accountName) meta.accountName = accountName;
122595
+ if (!meta.accountId && accountId) meta.accountId = accountId;
122596
+ if (!meta.startDate && manifest.dateRange?.start) meta.startDate = manifest.dateRange.start;
122597
+ if (!meta.endDate && manifest.dateRange?.end) meta.endDate = manifest.dateRange.end;
122598
+ if (!meta.attributionSetting && current?.attributionSetting) {
122599
+ meta.attributionSetting = String(current.attributionSetting);
122600
+ }
122601
+ if (!meta.resultType && current?.resultType) meta.resultType = String(current.resultType);
122602
+ const charts = { ...payload.charts ?? {} };
122603
+ const avgCpl = num(kpis.costPerResult) ?? 0;
122604
+ if (!charts.platform) {
122605
+ const platform = sectionMap.get("platform");
122606
+ const networks = Array.isArray(platform?.networks) ? platform.networks : [];
122607
+ if (networks.length) charts.platform = aggregatePlatform(networks);
122608
+ }
122609
+ if (!charts.country) {
122610
+ const country = sectionMap.get("country");
122611
+ const countries = Array.isArray(country?.countries) ? country.countries : [];
122612
+ if (countries.length) {
122613
+ const sorted = [...countries].sort((a, b) => (num(b.spend) ?? 0) - (num(a.spend) ?? 0));
122614
+ charts.country = {
122615
+ labels: sorted.map((c) => String(c.countryOrRegion ?? "\u2014")),
122616
+ cpl: sorted.map((c) => num(c.costPerResult) ?? 0)
122617
+ };
122618
+ }
122619
+ }
122620
+ if (!charts.audience) {
122621
+ const audience = sectionMap.get("audience");
122622
+ const audiences = Array.isArray(audience?.audiences) ? audience.audiences : [];
122623
+ if (audiences.length) {
122624
+ const rows = buildAudienceRows(audiences).sort((a, b) => a.costPerResult - b.costPerResult);
122625
+ charts.audience = {
122626
+ labels: rows.map((r) => r.label),
122627
+ cpl: rows.map((r) => r.costPerResult),
122628
+ leads: rows.map((r) => r.results)
122629
+ };
122630
+ }
122631
+ }
122632
+ if (!charts.funnel && num(kpis.reach) != null) {
122633
+ charts.funnel = { reach: kpis.reach, results: kpis.results ?? 0 };
122634
+ }
122635
+ const tables = { ...payload.tables ?? {} };
122636
+ if (!tables.adSets) {
122637
+ const adSets = sectionMap.get("ad-sets");
122638
+ const groups = Array.isArray(adSets?.adGroups) ? adSets.adGroups : [];
122639
+ if (groups.length) {
122640
+ tables.adSets = groups.map((g) => {
122641
+ const cpl = num(g.costPerResult);
122642
+ const freq = num(g.frequency);
122643
+ const fatigue = fatigueFromFrequency(freq);
122644
+ const status = statusFromCpl(cpl, avgCpl);
122645
+ return {
122646
+ name: String(g.adGroupName ?? g.campaignName ?? "\u2014"),
122647
+ spend: num(g.spend) ?? 0,
122648
+ results: num(g.results) ?? 0,
122649
+ costPerResult: cpl ?? 0,
122650
+ frequency: freq,
122651
+ fatigueLevel: fatigue.level,
122652
+ fatigueScore: fatigue.score,
122653
+ statusTag: status.tag,
122654
+ statusLabel: status.label
122655
+ };
122656
+ }).sort((a, b) => b.spend - a.spend);
122657
+ }
122658
+ }
122659
+ if (!tables.audienceTop || !tables.audienceBottom) {
122660
+ const audience = sectionMap.get("audience");
122661
+ const audiences = Array.isArray(audience?.audiences) ? audience.audiences : [];
122662
+ if (audiences.length) {
122663
+ const rows = buildAudienceRows(audiences).sort((a, b) => a.costPerResult - b.costPerResult);
122664
+ const topN = rows.slice(0, 10);
122665
+ const bottomN = [...rows].reverse().slice(0, 10);
122666
+ if (!tables.audienceTop) tables.audienceTop = topN;
122667
+ if (!tables.audienceBottom) tables.audienceBottom = bottomN;
122668
+ }
122669
+ }
122670
+ return {
122671
+ ...payload,
122672
+ meta,
122673
+ kpis,
122674
+ charts,
122675
+ tables
122676
+ };
122677
+ }
122678
+
122679
+ // src/commands/facebook-analysis/render-report.ts
122680
+ var TEMPLATE_BASENAMES2 = {
122681
+ html: "meta-period-report.html",
122682
+ runtime: "meta-period-report.runtime.js"
122683
+ };
122684
+ var INJECT_MARKER = "window.__META_PERIOD_REPORT__ = window.__META_PERIOD_REPORT__ || null;";
122685
+ function resolveSkillTemplatePath2(basename11) {
122686
+ const dir = path24.dirname(fileURLToPath5(import.meta.url));
122687
+ const candidates = [
122688
+ path24.join(dir, "skill", "report-templates", basename11),
122689
+ path24.join(dir, "skill", "siluzan-ads", "report-templates", basename11),
122690
+ path24.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename11)
122691
+ ];
122692
+ for (const p of candidates) {
122693
+ if (fs17.existsSync(p)) return p;
122694
+ }
122695
+ return candidates[0];
122696
+ }
122697
+ function metaPeriodReportTemplatePath() {
122698
+ return resolveSkillTemplatePath2(TEMPLATE_BASENAMES2.html);
122699
+ }
122700
+ function metaPeriodReportRuntimePath() {
122701
+ return resolveSkillTemplatePath2(TEMPLATE_BASENAMES2.runtime);
122702
+ }
122703
+ async function readJsonFile2(filePath) {
122704
+ const text = await fsPromises2.readFile(filePath, "utf8");
122705
+ try {
122706
+ return JSON.parse(text);
122707
+ } catch {
122708
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${filePath}`);
122709
+ }
122710
+ }
122711
+ function asRecord6(value) {
122712
+ if (value && typeof value === "object" && !Array.isArray(value)) {
122713
+ return value;
122714
+ }
122715
+ throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
122716
+ }
122717
+ function injectReportData2(html, payload) {
122718
+ const json = JSON.stringify(payload).replace(/</g, "\\u003c");
122719
+ if (!html.includes(INJECT_MARKER)) {
122720
+ throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 meta-period-report.html");
122721
+ }
122722
+ return html.replace(INJECT_MARKER, `window.__META_PERIOD_REPORT__ = ${json};`);
122723
+ }
122724
+ async function runFacebookAnalysisRender(opts) {
122725
+ const dataPath = path24.resolve(opts.dataFile);
122726
+ let data = asRecord6(await readJsonFile2(dataPath));
122727
+ if (opts.snapshotDir?.trim()) {
122728
+ data = await mergeFacebookSnapshotIntoReport(data, path24.resolve(opts.snapshotDir));
122729
+ }
122730
+ const templatePath = metaPeriodReportTemplatePath();
122731
+ let html;
122732
+ try {
122733
+ html = await fsPromises2.readFile(templatePath, "utf8");
122734
+ } catch {
122735
+ console.error(`
122736
+ \u274C \u672A\u627E\u5230\u62A5\u544A\u6A21\u677F\uFF1A${templatePath}
122737
+ \u8BF7\u5148\u6267\u884C npm run build
122738
+ `);
122739
+ process.exit(1);
122740
+ }
122741
+ const outPath = path24.resolve(
122742
+ opts.out ?? path24.join(path24.dirname(dataPath), "meta-period-report.html")
122743
+ );
122744
+ const outDir = path24.dirname(outPath);
122745
+ await fsPromises2.mkdir(outDir, { recursive: true });
122746
+ await fsPromises2.writeFile(outPath, injectReportData2(html, data), "utf8");
122747
+ const runtimeSrc = metaPeriodReportRuntimePath();
122748
+ const runtimeOut = path24.join(outDir, TEMPLATE_BASENAMES2.runtime);
122749
+ try {
122750
+ await fsPromises2.copyFile(runtimeSrc, runtimeOut);
122751
+ } catch {
122752
+ console.error(`
122753
+ \u274C \u672A\u627E\u5230\u62A5\u544A\u8FD0\u884C\u65F6\uFF1A${runtimeSrc}
122754
+ \u8BF7\u5148\u6267\u884C npm run build
122755
+ `);
122756
+ process.exit(1);
122757
+ }
122758
+ console.log(`
122759
+ \u2705 Meta/Facebook \u5468\u671F\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
122760
+ `);
122761
+ console.log(` \u8FD0\u884C\u65F6\u811A\u672C\uFF1A${runtimeOut}
122762
+ `);
122763
+ 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");
122764
+ }
122765
+
122479
122766
  // src/commands/facebook-analysis/run-batch.ts
122480
122767
  init_auth();
122481
- import * as path23 from "path";
122768
+ import * as path25 from "path";
122482
122769
  import { performance as performance5 } from "perf_hooks";
122483
122770
  init_version();
122484
122771
 
@@ -122582,7 +122869,7 @@ async function runAllFacebookSections(opts) {
122582
122869
  const results = await runWithConcurrency2(tasks, concurrency);
122583
122870
  const succeeded = results.filter((r) => r.ok).length;
122584
122871
  const failed = results.length - succeeded;
122585
- const absoluteSnapshotDir = path23.resolve(opts.jsonOut);
122872
+ const absoluteSnapshotDir = path25.resolve(opts.jsonOut);
122586
122873
  const summary = {
122587
122874
  kind: "siluzan-tso-facebook-analysis-snapshot-batch",
122588
122875
  absoluteSnapshotDir,
@@ -122602,7 +122889,7 @@ async function runAllFacebookSections(opts) {
122602
122889
  // src/commands/facebook-analysis/register-cli.ts
122603
122890
  function registerFacebookAnalysisCommands(program2) {
122604
122891
  const sectionHelp = FACEBOOK_SECTION_NAMES.join(", ");
122605
- program2.command("facebook-analysis").description(
122892
+ const root = program2.command("facebook-analysis").description(
122606
122893
  "Facebook Ads \u8D26\u6237\u5206\u6790\u6279\u91CF\u62C9\u53D6\uFF08TSO reporting/media-account/FacebookAds/\u2026\uFF0C7 \u4E2A Section\uFF09"
122607
122894
  ).requiredOption(
122608
122895
  "-a, --account <id>",
@@ -122623,6 +122910,18 @@ function registerFacebookAnalysisCommands(program2) {
122623
122910
  ).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) => {
122624
122911
  await runAllFacebookSections(opts);
122625
122912
  });
122913
+ root.command("render").description(
122914
+ "\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"
122915
+ ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 meta-period-report.json").option(
122916
+ "--snapshot-dir <dir>",
122917
+ "\u53EF\u9009\uFF1Afacebook-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u5408\u5E76 KPI / \u56FE\u8868 / \u8868\u683C"
122918
+ ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
122919
+ await runFacebookAnalysisRender({
122920
+ dataFile: opts.data,
122921
+ snapshotDir: opts.snapshotDir,
122922
+ out: opts.out
122923
+ });
122924
+ });
122626
122925
  }
122627
122926
 
122628
122927
  // src/index.ts
@@ -122631,9 +122930,9 @@ init_cli_json_snapshot();
122631
122930
  installProcessHandlers();
122632
122931
  function getVersion() {
122633
122932
  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"));
122933
+ const __dirname3 = path26.dirname(fileURLToPath6(import.meta.url));
122934
+ const pkgPath = path26.join(__dirname3, "..", "package.json");
122935
+ const pkg = JSON.parse(fs18.readFileSync(pkgPath, "utf8"));
122637
122936
  return pkg.version ?? "0.0.0";
122638
122937
  } catch {
122639
122938
  return "0.0.0";