siluzan-tso-cli 1.1.46-beta.2 → 1.1.46-beta.6

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 (26) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +63 -132
  3. package/dist/skill/SKILL.md +3 -1
  4. package/dist/skill/_meta.json +2 -2
  5. package/dist/skill/references/README.md +5 -3
  6. package/dist/skill/references/analytics/account-analytics.md +5 -1
  7. package/dist/skill/references/core/agent-conventions.md +1 -1
  8. package/dist/skill/references/core/workflows.md +7 -5
  9. package/dist/skill/references/operations/guard.md +19 -28
  10. package/dist/skill/references/operations/hosted-automation-bing.md +118 -0
  11. package/dist/skill/references/operations/hosted-automation-monitoring-json.md +3 -2
  12. package/dist/skill/references/operations/hosted-automation-optimize-index.md +2 -1
  13. package/dist/skill/references/operations/hosted-automation-scenarios.md +18 -19
  14. package/dist/skill/references/operations/hosted-automation-self-control.md +10 -8
  15. package/dist/skill/references/operations/hosted-automation-user-catalog.md +35 -14
  16. package/dist/skill/references/operations/hosted-automation-yandex.md +83 -0
  17. package/dist/skill/references/report-templates/yandex-period-report.md +1 -1
  18. package/dist/skill/report-templates/yandex-period-report.html +12 -1
  19. package/dist/skill/report-templates/yandex-period-report.md +1 -1
  20. package/dist/skill/scripts/install.ps1 +1 -1
  21. package/dist/skill/scripts/install.sh +1 -1
  22. package/eval/cases/guard-budget-circuit-all-google.scenario.json +1 -1
  23. package/eval/cases/guard-zero-conv-empty-spend.scenario.json +1 -1
  24. package/eval/stub-fixtures/guard-budget-circuit.json +2 -7
  25. package/eval/stub-fixtures/guard-zero-conv.json +2 -7
  26. package/package.json +1 -1
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.46-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.46-beta.6),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -107451,8 +107451,8 @@ function assertGoogleOnly(media) {
107451
107451
  if (media !== "Google") {
107452
107452
  console.error(
107453
107453
  `
107454
- \u274C guard \u7194\u65AD\u5199\u76EE\u524D\u4EC5\u652F\u6301 Google\uFF08\u5F53\u524D\uFF1A${media}\uFF09\u3002
107455
- \u975E Google \u5A92\u4F53\u672C CLI \u65E0\u7CFB\u5217/\u7EC4/\u521B\u610F\u6682\u505C\u5199\u80FD\u529B\uFF1B\u8BF7\u6539\u7528 -m Google\u3002
107454
+ \u274C guard \u7194\u65AD\u626B\u63CF\u76EE\u524D\u4EC5\u652F\u6301 Google\uFF08\u5F53\u524D\uFF1A${media}\uFF09\u3002
107455
+ \u8BF7\u6539\u7528 -m Google\u3002
107456
107456
  `
107457
107457
  );
107458
107458
  process.exit(1);
@@ -107543,46 +107543,6 @@ async function fetchAdsForDay(config, accountId, date, verbose) {
107543
107543
  };
107544
107544
  }).filter((r) => r.id);
107545
107545
  }
107546
- async function pauseCampaign(config, accountId, campaignId, verbose) {
107547
- const googleApiUrl = requireGoogleApi(config);
107548
- const detailUrl = `${googleApiUrl}/campaignmanagement/campaign/${accountId}/${campaignId}`;
107549
- const res = await apiFetch2(detailUrl, config, {}, verbose);
107550
- const campaign = res.data ?? res;
107551
- const body = { ...campaign, statusV2: "Paused" };
107552
- await apiFetch2(detailUrl, config, { method: "PUT", body: JSON.stringify(body) }, verbose);
107553
- }
107554
- async function pauseAdGroup(config, accountId, adGroupId, date, verbose) {
107555
- const googleApiUrl = requireGoogleApi(config);
107556
- const listUrl = adgroupListUrl(googleApiUrl, accountId, date, date);
107557
- const adgroup = await findItemInList(
107558
- listUrl,
107559
- config,
107560
- adGroupId,
107561
- verbose
107562
- );
107563
- const putUrl = `${googleApiUrl}/adgroupnmanagement/adgroup/${accountId}/${adGroupId}`;
107564
- await apiFetch2(
107565
- putUrl,
107566
- config,
107567
- { method: "PUT", body: JSON.stringify({ ...adgroup, statusV2: "Paused" }) },
107568
- verbose
107569
- );
107570
- }
107571
- async function pauseAd(config, accountId, adId, date, verbose) {
107572
- const googleApiUrl = requireGoogleApi(config);
107573
- const params = new URLSearchParams();
107574
- params.set("startDate", toGoogleDate(date, 0));
107575
- params.set("endDate", toGoogleDate(date, 0));
107576
- const listUrl = `${googleApiUrl}/admanagement/v2/list/${accountId}?${params}`;
107577
- const ad = await findItemInList(listUrl, config, adId, verbose);
107578
- const putUrl = `${googleApiUrl}/admanagement/campaign/${accountId}/${adId}`;
107579
- await apiFetch2(
107580
- putUrl,
107581
- config,
107582
- { method: "PUT", body: JSON.stringify({ ...ad, statusV2: "Paused" }) },
107583
- verbose
107584
- );
107585
- }
107586
107546
  function printHitsTable(title, rows, cols) {
107587
107547
  console.log(`
107588
107548
  ${title}
@@ -107641,14 +107601,13 @@ async function runBudgetCircuit(opts) {
107641
107601
  }
107642
107602
  const statusFilter = opts.status === void 0 ? "Enabled" : opts.status;
107643
107603
  const filterAll = statusFilter === "" || statusFilter === "*";
107644
- const apply = Boolean(opts.apply);
107645
107604
  const accounts = await resolveGuardAccounts(opts, config);
107646
107605
  if (accounts.length === 0) {
107647
107606
  console.error("\n\u274C \u672A\u627E\u5230\u53EF\u626B\u63CF\u7684 Google \u8D26\u6237\u3002\n");
107648
107607
  process.exit(1);
107649
107608
  }
107650
107609
  process.stderr.write(
107651
- `\u23F3 [guard budget-circuit] ${accounts.length} \u6237 \xB7 \u65E5\u671F ${date} \xB7 ratio=${ratio}${apply ? " \xB7 APPLY" : " \xB7 dry-run"}
107610
+ `\u23F3 [guard budget-circuit] ${accounts.length} \u6237 \xB7 \u65E5\u671F ${date} \xB7 ratio=${ratio} \xB7 \u53EA\u8BFB\u626B\u63CF
107652
107611
  `
107653
107612
  );
107654
107613
  const accountResults = await runWithConcurrency(
@@ -107682,18 +107641,8 @@ async function runBudgetCircuit(opts) {
107682
107641
  spend: c.spend,
107683
107642
  budget,
107684
107643
  ratio,
107685
- threshold: budget * ratio,
107686
- applied: false
107644
+ threshold: budget * ratio
107687
107645
  };
107688
- if (apply) {
107689
- try {
107690
- await pauseCampaign(config, acc.mediaCustomerId, c.id, opts.verbose);
107691
- hit.applied = true;
107692
- hit.statusV2 = "Paused";
107693
- } catch (err) {
107694
- hit.applyError = err instanceof Error ? err.message : String(err);
107695
- }
107696
- }
107697
107646
  hits2.push(hit);
107698
107647
  }
107699
107648
  return {
@@ -107720,7 +107669,6 @@ async function runBudgetCircuit(opts) {
107720
107669
  accountName: r.accountName,
107721
107670
  error: r.error
107722
107671
  }));
107723
- const paused = hits.filter((h) => h.applied);
107724
107672
  const checkedCampaigns = accountResults.reduce((n, r) => n + r.checked, 0);
107725
107673
  const payload = {
107726
107674
  meta: {
@@ -107728,20 +107676,16 @@ async function runBudgetCircuit(opts) {
107728
107676
  date,
107729
107677
  ratio,
107730
107678
  statusFilter: filterAll ? "*" : statusFilter,
107731
- apply,
107732
- note: "\u975E Google \u5A92\u4F53\u672C CLI \u65E0\u7CFB\u5217\u7194\u65AD\u5199\u80FD\u529B\uFF1B\u672C\u547D\u4EE4\u4EC5\u626B\u63CF Google\u3002"
107679
+ note: "\u672C\u547D\u4EE4\u53EA\u626B\u63CF\u5217\u51FA\u547D\u4E2D\uFF0C\u4E0D\u6682\u505C\u3002\u8981\u5BF9\u67D0\u6761\u7CFB\u5217\u505C\u6295\uFF0C\u987B\u5355\u72EC\u6267\u884C ad campaign-status --status Paused --commit\u3002"
107733
107680
  },
107734
107681
  summary: {
107735
107682
  label: "\u8D85\u9884\u7B97\u7194\u65AD",
107736
107683
  accountCount: accounts.length,
107737
107684
  checkedCampaigns,
107738
107685
  hitCount: hits.length,
107739
- pausedCount: paused.length,
107740
- errorCount: errors.length,
107741
- mode: apply ? "apply" : "dry-run"
107686
+ errorCount: errors.length
107742
107687
  },
107743
107688
  hits,
107744
- paused,
107745
107689
  errors
107746
107690
  };
107747
107691
  if (await emitCliJsonOrSnapshot(opts, {
@@ -107750,7 +107694,7 @@ async function runBudgetCircuit(opts) {
107750
107694
  payload
107751
107695
  })) {
107752
107696
  }
107753
- const title = `\u8D85\u9884\u7B97\u7194\u65AD\uFF08${apply ? "\u5DF2\u5C1D\u8BD5\u6682\u505C" : "dry-run"}\uFF09 \xB7 ${date} \xB7 ratio=${ratio} \xB7 \u547D\u4E2D ${hits.length}/${checkedCampaigns}`;
107697
+ const title = `\u8D85\u9884\u7B97\u7194\u65AD\u626B\u63CF \xB7 ${date} \xB7 ratio=${ratio} \xB7 \u547D\u4E2D ${hits.length}/${checkedCampaigns}`;
107754
107698
  printHitsTable(
107755
107699
  title,
107756
107700
  hits.map((h) => ({
@@ -107758,16 +107702,14 @@ async function runBudgetCircuit(opts) {
107758
107702
  campaign: h.campaignName.slice(0, 36),
107759
107703
  spend: h.spend.toFixed(2),
107760
107704
  budget: h.budget.toFixed(2),
107761
- threshold: h.threshold.toFixed(2),
107762
- applied: h.applied ? "Paused" : h.applyError ? `ERR:${h.applyError.slice(0, 20)}` : "\u2014"
107705
+ threshold: h.threshold.toFixed(2)
107763
107706
  })),
107764
107707
  [
107765
107708
  { key: "account", header: "\u8D26\u6237ID" },
107766
107709
  { key: "campaign", header: "\u7CFB\u5217" },
107767
107710
  { key: "spend", header: "\u5F53\u65E5\u82B1\u8D39" },
107768
107711
  { key: "budget", header: "\u65E5\u9884\u7B97" },
107769
- { key: "threshold", header: "\u9608\u503C" },
107770
- { key: "applied", header: "\u6682\u505C" }
107712
+ { key: "threshold", header: "\u9608\u503C" }
107771
107713
  ]
107772
107714
  );
107773
107715
  if (errors.length > 0) {
@@ -107776,14 +107718,9 @@ async function runBudgetCircuit(opts) {
107776
107718
  console.error(` \u2022 ${e.mediaCustomerId}: ${e.error}`);
107777
107719
  }
107778
107720
  }
107779
- if (!apply && hits.length > 0) {
107721
+ if (hits.length > 0) {
107780
107722
  console.log(
107781
- '\u63D0\u793A\uFF1A\u4EE5\u4E0A\u4E3A dry-run\u3002\u786E\u8BA4\u540E\u8FFD\u52A0 --apply --commit "budget-circuit <date>" \u6267\u884C\u6682\u505C\u3002\n'
107782
- );
107783
- } else if (apply) {
107784
- console.log(
107785
- `\u2705 apply \u5B8C\u6210\uFF1A\u547D\u4E2D ${hits.length}\uFF0C\u6210\u529F\u6682\u505C ${paused.length}${hits.length - paused.length > 0 ? `\uFF0C\u5931\u8D25 ${hits.length - paused.length}` : ""}\u3002
107786
- `
107723
+ '\u63D0\u793A\uFF1A\u672C\u547D\u4EE4\u53EA\u5217\u51FA\u547D\u4E2D\uFF0C\u4E0D\u4F1A\u6682\u505C\u3002\u8981\u5BF9\u67D0\u6761\u7CFB\u5217\u505C\u6295\uFF0C\u8BF7\u5BF9\u8BE5\u8D26\u6237\u5355\u72EC\u6267\u884C\uFF1A\n siluzan-tso ad campaign-status -a <\u8D26\u6237ID> --id <\u7CFB\u5217ID> --status Paused --commit "\u8D85\u9884\u7B97\u7194\u65AD <date>"\n'
107787
107724
  );
107788
107725
  } else {
107789
107726
  console.log("\u2705 \u4ECA\u65E5\u65E0\u7CFB\u5217\u89E6\u53D1\u8D85\u9884\u7B97\u7194\u65AD\u3002\n");
@@ -107810,7 +107747,6 @@ async function runZeroConv(opts) {
107810
107747
  const level = parseLevel(opts.level);
107811
107748
  const fallback = opts.fallbackTargetCpa != null && Number.isFinite(opts.fallbackTargetCpa) ? opts.fallbackTargetCpa : null;
107812
107749
  const onlyEnabled = opts.onlyEnabled !== false;
107813
- const apply = Boolean(opts.apply);
107814
107750
  if ((level === "ad" || level === "both") && fallback == null) {
107815
107751
  process.stderr.write(
107816
107752
  "\u26A0\uFE0F [guard zero-conv] \u521B\u610F\u7EF4\u65E0\u7EC4\u7EA7 targetCpa \u5B57\u6BB5\uFF0C\u672A\u4F20 --fallback-target-cpa \u65F6\u521B\u610F\u547D\u4E2D\u53EF\u80FD\u4E3A 0\uFF08\u4F1A\u8BB0\u5165 skipped\uFF09\u3002\n"
@@ -107822,7 +107758,7 @@ async function runZeroConv(opts) {
107822
107758
  process.exit(1);
107823
107759
  }
107824
107760
  process.stderr.write(
107825
- `\u23F3 [guard zero-conv] ${accounts.length} \u6237 \xB7 ${date} \xB7 \xD7${multiple} \xB7 level=${level}${apply ? " \xB7 APPLY" : " \xB7 dry-run"}
107761
+ `\u23F3 [guard zero-conv] ${accounts.length} \u6237 \xB7 ${date} \xB7 \xD7${multiple} \xB7 level=${level} \xB7 \u53EA\u8BFB\u626B\u63CF
107826
107762
  `
107827
107763
  );
107828
107764
  const accountResults = await runWithConcurrency(
@@ -107860,18 +107796,8 @@ async function runZeroConv(opts) {
107860
107796
  conversions: g.conversions ?? 0,
107861
107797
  targetCpaYuan: target,
107862
107798
  multiple,
107863
- threshold: target * multiple,
107864
- applied: false
107799
+ threshold: target * multiple
107865
107800
  };
107866
- if (apply) {
107867
- try {
107868
- await pauseAdGroup(config, acc.mediaCustomerId, g.id, date, opts.verbose);
107869
- hit.applied = true;
107870
- hit.statusV2 = "Paused";
107871
- } catch (err) {
107872
- hit.applyError = err instanceof Error ? err.message : String(err);
107873
- }
107874
- }
107875
107801
  hits2.push(hit);
107876
107802
  }
107877
107803
  }
@@ -107903,18 +107829,8 @@ async function runZeroConv(opts) {
107903
107829
  conversions: ad.conversions ?? 0,
107904
107830
  targetCpaYuan: fallback,
107905
107831
  multiple,
107906
- threshold: fallback * multiple,
107907
- applied: false
107832
+ threshold: fallback * multiple
107908
107833
  };
107909
- if (apply) {
107910
- try {
107911
- await pauseAd(config, acc.mediaCustomerId, ad.id, date, opts.verbose);
107912
- hit.applied = true;
107913
- hit.statusV2 = "Paused";
107914
- } catch (err) {
107915
- hit.applyError = err instanceof Error ? err.message : String(err);
107916
- }
107917
- }
107918
107834
  hits2.push(hit);
107919
107835
  }
107920
107836
  }
@@ -107945,7 +107861,6 @@ async function runZeroConv(opts) {
107945
107861
  accountName: r.accountName,
107946
107862
  error: r.error
107947
107863
  }));
107948
- const paused = hits.filter((h) => h.applied);
107949
107864
  const checked = accountResults.reduce((n, r) => n + r.checked, 0);
107950
107865
  const payload = {
107951
107866
  meta: {
@@ -107954,21 +107869,17 @@ async function runZeroConv(opts) {
107954
107869
  cpaMultiple: multiple,
107955
107870
  level,
107956
107871
  fallbackTargetCpa: fallback,
107957
- apply,
107958
- note: "\u975E Google \u5A92\u4F53\u672C CLI \u65E0\u7EC4/\u521B\u610F\u7194\u65AD\u5199\u80FD\u529B\uFF1B\u672C\u547D\u4EE4\u4EC5\u626B\u63CF Google\u3002P1\u300C\u7A7A\u8017\u7194\u65AD\u300D\u901A\u77E5\u7531\u5BBF\u4E3B\u53D1\u9001\u3002"
107872
+ note: "\u672C\u547D\u4EE4\u53EA\u626B\u63CF\u5217\u51FA\u547D\u4E2D\uFF0C\u4E0D\u6682\u505C\u3002\u8981\u5BF9\u67D0\u4E2A\u7EC4/\u521B\u610F\u505C\u6295\uFF0C\u987B\u5355\u72EC\u6267\u884C ad adgroup-status / ad-status --status Paused --commit\u3002P1\u300C\u7A7A\u8017\u7194\u65AD\u300D\u901A\u77E5\u7531\u5BBF\u4E3B\u53D1\u9001\u3002"
107959
107873
  },
107960
107874
  summary: {
107961
107875
  label: "\u7A7A\u8017\u7194\u65AD",
107962
107876
  accountCount: accounts.length,
107963
107877
  checkedObjects: checked,
107964
107878
  hitCount: hits.length,
107965
- pausedCount: paused.length,
107966
107879
  skippedCount: skipped.length,
107967
- errorCount: errors.length,
107968
- mode: apply ? "apply" : "dry-run"
107880
+ errorCount: errors.length
107969
107881
  },
107970
107882
  hits,
107971
- paused,
107972
107883
  skipped,
107973
107884
  errors
107974
107885
  };
@@ -107977,7 +107888,7 @@ async function runZeroConv(opts) {
107977
107888
  commandLabel: "guard zero-conv",
107978
107889
  payload
107979
107890
  });
107980
- const title = `\u7A7A\u8017\u7194\u65AD\uFF08${apply ? "\u5DF2\u5C1D\u8BD5\u6682\u505C" : "dry-run"}\uFF09 \xB7 ${date} \xB7 \xD7${multiple} \xB7 \u547D\u4E2D ${hits.length}/${checked}`;
107891
+ const title = `\u7A7A\u8017\u7194\u65AD\u626B\u63CF \xB7 ${date} \xB7 \xD7${multiple} \xB7 \u547D\u4E2D ${hits.length}/${checked}`;
107981
107892
  printHitsTable(
107982
107893
  title,
107983
107894
  hits.map((h) => ({
@@ -107986,8 +107897,7 @@ async function runZeroConv(opts) {
107986
107897
  name: h.objectName.slice(0, 32),
107987
107898
  spend: h.spend.toFixed(2),
107988
107899
  target: h.targetCpaYuan.toFixed(2),
107989
- threshold: h.threshold.toFixed(2),
107990
- applied: h.applied ? "Paused" : h.applyError ? `ERR` : "\u2014"
107900
+ threshold: h.threshold.toFixed(2)
107991
107901
  })),
107992
107902
  [
107993
107903
  { key: "account", header: "\u8D26\u6237ID" },
@@ -107995,21 +107905,15 @@ async function runZeroConv(opts) {
107995
107905
  { key: "name", header: "\u5BF9\u8C61" },
107996
107906
  { key: "spend", header: "\u5F53\u65E5\u82B1\u8D39" },
107997
107907
  { key: "target", header: "\u76EE\u6807CPA" },
107998
- { key: "threshold", header: "\u9608\u503C" },
107999
- { key: "applied", header: "\u6682\u505C" }
107908
+ { key: "threshold", header: "\u9608\u503C" }
108000
107909
  ]
108001
107910
  );
108002
107911
  if (errors.length > 0) {
108003
107912
  console.error(`\u26A0\uFE0F ${errors.length} \u4E2A\u8D26\u6237\u62C9\u6570\u5931\u8D25\uFF08\u8BE6\u89C1 json-out errors[]\uFF09`);
108004
107913
  }
108005
- if (!apply && hits.length > 0) {
108006
- console.log(
108007
- '\u63D0\u793A\uFF1A\u4EE5\u4E0A\u4E3A dry-run\u3002\u786E\u8BA4\u540E\u8FFD\u52A0 --apply --commit "zero-conv <date>" \u6267\u884C\u6682\u505C\u3002\n'
108008
- );
108009
- } else if (apply) {
107914
+ if (hits.length > 0) {
108010
107915
  console.log(
108011
- `\u2705 apply \u5B8C\u6210\uFF1A\u547D\u4E2D ${hits.length}\uFF0C\u6210\u529F\u6682\u505C ${paused.length}${hits.length - paused.length > 0 ? `\uFF0C\u5931\u8D25 ${hits.length - paused.length}` : ""}\u3002
108012
- `
107916
+ '\u63D0\u793A\uFF1A\u672C\u547D\u4EE4\u53EA\u5217\u51FA\u547D\u4E2D\uFF0C\u4E0D\u4F1A\u6682\u505C\u3002\u8981\u5BF9\u67D0\u4E2A\u7EC4/\u521B\u610F\u505C\u6295\uFF0C\u8BF7\u5BF9\u8BE5\u8D26\u6237\u5355\u72EC\u6267\u884C\uFF1A\n siluzan-tso ad adgroup-status -a <\u8D26\u6237ID> --id <\u7EC4ID> --status Paused --commit "\u7A7A\u8017\u7194\u65AD <date>"\n siluzan-tso ad ad-status -a <\u8D26\u6237ID> --id <\u5E7F\u544AID> --status Paused --commit "\u7A7A\u8017\u7194\u65AD <date>"\n'
108013
107917
  );
108014
107918
  } else {
108015
107919
  console.log("\u2705 \u4ECA\u65E5\u65E0\u5BF9\u8C61\u89E6\u53D1\u7A7A\u8017\u7194\u65AD\u3002\n");
@@ -108019,14 +107923,14 @@ async function runZeroConv(opts) {
108019
107923
  // src/commands/guard/_register.ts
108020
107924
  function register11(program2) {
108021
107925
  const guard = program2.command("guard").description(
108022
- "Google \u6295\u653E\u81EA\u63A7\u7194\u65AD\uFF1A\u5168\u6237\uFF08\u6216 -a \u5B50\u96C6\uFF09\u626B\u63CF\u5F53\u65E5\u8D85\u9884\u7B97/\u7A7A\u8017\u547D\u4E2D\uFF0C\u9ED8\u8BA4 dry-run\uFF1B--apply \u65F6\u6682\u505C\u547D\u4E2D\u5BF9\u8C61\uFF08\u987B\u5168\u5C40 --commit\uFF09\u3002\u975E Google \u5A92\u4F53\u4E0D\u652F\u6301\u3002"
107926
+ "Google \u6295\u653E\u81EA\u63A7\u7194\u65AD\u626B\u63CF\uFF1A\u5168\u6237\uFF08\u6216 -a \u5B50\u96C6\uFF09\u5217\u51FA\u5F53\u65E5\u8D85\u9884\u7B97/\u7A7A\u8017\u547D\u4E2D\uFF0C\u53EA\u8BFB\u4E0D\u6682\u505C\u3002\u8981\u505C\u6295\u653E\u987B\u5BF9\u6BCF\u4E2A\u547D\u4E2D\u5BF9\u8C61\u5355\u72EC\u6267\u884C ad campaign-status / adgroup-status / ad-status\uFF08\u5E26 --commit\uFF09\u3002\u975E Google \u5A92\u4F53\u4E0D\u652F\u6301\u3002"
108023
107927
  );
108024
107928
  guard.command("budget-circuit").description(
108025
- "\u5355\u65E5\u9884\u7B97\u7194\u65AD\uFF1A\u5F53\u65E5\u7CFB\u5217 spend \u2265 \u65E5\u9884\u7B97 \xD7 ratio \u65F6\u5217\u51FA\uFF08\u53EF\u9009\u6682\u505C\uFF09\u3002\u66FF\u4EE3\u300Clist-accounts \xD7 N + \u9010\u6237 ad campaigns + \u811A\u672C\u300D\u3002"
107929
+ "\u5355\u65E5\u9884\u7B97\u7194\u65AD\u626B\u63CF\uFF1A\u5F53\u65E5\u7CFB\u5217 spend \u2265 \u65E5\u9884\u7B97 \xD7 ratio \u65F6\u5217\u51FA\u547D\u4E2D\uFF08\u4E0D\u6682\u505C\uFF09\u3002\u66FF\u4EE3\u300Clist-accounts \xD7 N + \u9010\u6237 ad campaigns + \u811A\u672C\u300D\u3002"
108026
107930
  ).requiredOption("-m, --media <type>", "\u76EE\u524D\u4EC5\u652F\u6301 Google").option(
108027
107931
  "-a, --accounts <ids>",
108028
107932
  "\u6307\u5B9A\u8D26\u6237 mediaCustomerId\uFF0C\u9017\u53F7\u5206\u9694\uFF1B\u7559\u7A7A\u5219\u626B\u63CF\u540D\u4E0B\u5168\u90E8 Google \u8D26\u6237"
108029
- ).option("--date <YYYY-MM-DD>", "\u7EDF\u8BA1\u65E5\uFF08Asia/Shanghai \u9ED8\u8BA4\u4ECA\u5929\uFF09\uFF1B\u5F3A\u5236 start=end=\u8BE5\u65E5").option("--ratio <n>", "\u9608\u503C\u7CFB\u6570\uFF1Aspend >= budget * ratio\uFF08\u9ED8\u8BA4 1.1\uFF09", (v) => parseFloat(v)).option("--status <statusV2>", "\u53EA\u68C0\u67E5\u8BE5\u72B6\u6001\u7684\u7CFB\u5217\uFF08\u9ED8\u8BA4 Enabled\uFF1B\u4F20 * \u8868\u793A\u4E0D\u8FC7\u6EE4\uFF09", "Enabled").option("--apply", "\u5BF9\u547D\u4E2D\u7CFB\u5217\u6267\u884C Paused\uFF08\u987B\u914D\u5408\u5168\u5C40 --commit\uFF09", false).option("--page-size <n>", "\u8D26\u6237\u6E05\u5355\u5206\u9875\u5927\u5C0F\uFF08\u9ED8\u8BA4 200\uFF09", (v) => parseInt(v, 10)).option("--max-pages <n>", "\u6700\u591A\u626B\u63CF\u9875\u6570\uFF08\u9ED8\u8BA4 20\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF09").option(
107933
+ ).option("--date <YYYY-MM-DD>", "\u7EDF\u8BA1\u65E5\uFF08Asia/Shanghai \u9ED8\u8BA4\u4ECA\u5929\uFF09\uFF1B\u5F3A\u5236 start=end=\u8BE5\u65E5").option("--ratio <n>", "\u9608\u503C\u7CFB\u6570\uFF1Aspend >= budget * ratio\uFF08\u9ED8\u8BA4 1.1\uFF09", (v) => parseFloat(v)).option("--status <statusV2>", "\u53EA\u68C0\u67E5\u8BE5\u72B6\u6001\u7684\u7CFB\u5217\uFF08\u9ED8\u8BA4 Enabled\uFF1B\u4F20 * \u8868\u793A\u4E0D\u8FC7\u6EE4\uFF09", "Enabled").option("--page-size <n>", "\u8D26\u6237\u6E05\u5355\u5206\u9875\u5927\u5C0F\uFF08\u9ED8\u8BA4 200\uFF09", (v) => parseInt(v, 10)).option("--max-pages <n>", "\u6700\u591A\u626B\u63CF\u9875\u6570\uFF08\u9ED8\u8BA4 20\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF09").option(
108030
107934
  "--json-out <path>",
108031
107935
  "\u843D\u76D8\u76EE\u5F55\u6216 *.json\uFF1Bstdout \u4E00\u884C\u6458\u8981\uFF1B\u4EBA\u7C7B\u53EF\u8BFB\u8868\u59CB\u7EC8\u6253\u5370",
108032
107936
  void 0
@@ -108038,7 +107942,6 @@ function register11(program2) {
108038
107942
  date: opts.date,
108039
107943
  ratio: opts.ratio,
108040
107944
  status: opts.status,
108041
- apply: opts.apply,
108042
107945
  pageSize: opts.pageSize,
108043
107946
  maxPages: opts.maxPages,
108044
107947
  token: opts.token,
@@ -108049,7 +107952,7 @@ function register11(program2) {
108049
107952
  }
108050
107953
  );
108051
107954
  guard.command("zero-conv").description(
108052
- "\u7A7A\u8017\u7194\u65AD\uFF1A\u5F53\u65E5 spend \u2265 \u76EE\u6807 CPA \xD7 N \u4E14\u8F6C\u5316\u4E3A 0 \u65F6\u5217\u51FA\u7EC4/\u521B\u610F\uFF08\u53EF\u9009\u6682\u505C\uFF09\u3002\u7ED3\u679C summary.label=\u300C\u7A7A\u8017\u7194\u65AD\u300D\uFF1BP1 \u901A\u77E5\u7531\u5BBF\u4E3B\u53D1\u9001\u3002"
107955
+ "\u7A7A\u8017\u7194\u65AD\u626B\u63CF\uFF1A\u5F53\u65E5 spend \u2265 \u76EE\u6807 CPA \xD7 N \u4E14\u8F6C\u5316\u4E3A 0 \u65F6\u5217\u51FA\u7EC4/\u521B\u610F\uFF08\u4E0D\u6682\u505C\uFF09\u3002\u7ED3\u679C summary.label=\u300C\u7A7A\u8017\u7194\u65AD\u300D\uFF1BP1 \u901A\u77E5\u7531\u5BBF\u4E3B\u53D1\u9001\u3002"
108053
107956
  ).requiredOption("-m, --media <type>", "\u76EE\u524D\u4EC5\u652F\u6301 Google").option(
108054
107957
  "-a, --accounts <ids>",
108055
107958
  "\u6307\u5B9A\u8D26\u6237 mediaCustomerId\uFF0C\u9017\u53F7\u5206\u9694\uFF1B\u7559\u7A7A\u5219\u626B\u63CF\u540D\u4E0B\u5168\u90E8 Google \u8D26\u6237"
@@ -108057,7 +107960,7 @@ function register11(program2) {
108057
107960
  "--fallback-target-cpa <yuan>",
108058
107961
  "\u7EC4\u65E0 targetCpa\uFF08\u6216\u521B\u610F\u7EF4\uFF09\u65F6\u4F7F\u7528\u7684\u76EE\u6807 CPA\uFF08\u5143\uFF09",
108059
107962
  (v) => parseFloat(v)
108060
- ).option("--apply", "\u5BF9\u547D\u4E2D\u5BF9\u8C61\u6267\u884C Paused\uFF08\u987B\u914D\u5408\u5168\u5C40 --commit\uFF09", false).option("--page-size <n>", "\u8D26\u6237\u6E05\u5355\u5206\u9875\u5927\u5C0F\uFF08\u9ED8\u8BA4 200\uFF09", (v) => parseInt(v, 10)).option("--max-pages <n>", "\u6700\u591A\u626B\u63CF\u9875\u6570\uFF08\u9ED8\u8BA4 20\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF09").option("--json-out <path>", "\u843D\u76D8\u76EE\u5F55\u6216 *.json", void 0).option("--refresh-dp", "\u5F3A\u5236\u91CD\u62C9 Datapermission", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF", false).action(
107963
+ ).option("--page-size <n>", "\u8D26\u6237\u6E05\u5355\u5206\u9875\u5927\u5C0F\uFF08\u9ED8\u8BA4 200\uFF09", (v) => parseInt(v, 10)).option("--max-pages <n>", "\u6700\u591A\u626B\u63CF\u9875\u6570\uFF08\u9ED8\u8BA4 20\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF09").option("--json-out <path>", "\u843D\u76D8\u76EE\u5F55\u6216 *.json", void 0).option("--refresh-dp", "\u5F3A\u5236\u91CD\u62C9 Datapermission", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF", false).action(
108061
107964
  async (opts) => {
108062
107965
  await runZeroConv({
108063
107966
  media: opts.media,
@@ -108066,7 +107969,6 @@ function register11(program2) {
108066
107969
  cpaMultiple: opts.cpaMultiple,
108067
107970
  level: opts.level,
108068
107971
  fallbackTargetCpa: opts.fallbackTargetCpa,
108069
- apply: opts.apply,
108070
107972
  pageSize: opts.pageSize,
108071
107973
  maxPages: opts.maxPages,
108072
107974
  token: opts.token,
@@ -132267,7 +132169,14 @@ function parseLocalYmd(ymd) {
132267
132169
  return /* @__PURE__ */ new Date(NaN);
132268
132170
  }
132269
132171
  const [y, m, day] = parts;
132270
- return new Date(y, m - 1, day);
132172
+ if (m < 1 || m > 12 || day < 1 || day > 31) {
132173
+ return /* @__PURE__ */ new Date(NaN);
132174
+ }
132175
+ const d = new Date(y, m - 1, day);
132176
+ if (d.getFullYear() !== y || d.getMonth() !== m - 1 || d.getDate() !== day) {
132177
+ return /* @__PURE__ */ new Date(NaN);
132178
+ }
132179
+ return d;
132271
132180
  }
132272
132181
  function formatLocalYmd(d) {
132273
132182
  const y = d.getFullYear();
@@ -134469,7 +134378,14 @@ function parseLocalYmd2(ymd) {
134469
134378
  return /* @__PURE__ */ new Date(NaN);
134470
134379
  }
134471
134380
  const [y, m, day] = parts;
134472
- return new Date(y, m - 1, day);
134381
+ if (m < 1 || m > 12 || day < 1 || day > 31) {
134382
+ return /* @__PURE__ */ new Date(NaN);
134383
+ }
134384
+ const d = new Date(y, m - 1, day);
134385
+ if (d.getFullYear() !== y || d.getMonth() !== m - 1 || d.getDate() !== day) {
134386
+ return /* @__PURE__ */ new Date(NaN);
134387
+ }
134388
+ return d;
134473
134389
  }
134474
134390
  function formatLocalYmd2(d) {
134475
134391
  const y = d.getFullYear();
@@ -134600,6 +134516,11 @@ function num10(value) {
134600
134516
  function round25(value) {
134601
134517
  return value == null ? void 0 : Math.round(value * 100) / 100;
134602
134518
  }
134519
+ function normalizeYandexRoi(value) {
134520
+ if (typeof value !== "number" && typeof value !== "string") return void 0;
134521
+ const r = num10(value);
134522
+ return r === -1 ? void 0 : r;
134523
+ }
134603
134524
  function asItems(payload) {
134604
134525
  const obj = asRecord26(payload);
134605
134526
  if (!obj) return [];
@@ -134621,7 +134542,7 @@ function pickMetrics2(row, extras = []) {
134621
134542
  averageCpc: num10(row.averageCpc) ?? (clicks > 0 ? round25(spend / clicks) : void 0),
134622
134543
  conversionRate: num10(row.conversionRate) ?? (clicks > 0 ? conversions / clicks : void 0),
134623
134544
  costPerConversion: num10(row.costPerConversion) ?? (conversions > 0 ? round25(spend / conversions) : void 0),
134624
- roi: num10(row.roi)
134545
+ roi: normalizeYandexRoi(row.roi)
134625
134546
  };
134626
134547
  for (const f of extras) {
134627
134548
  if (row[f] !== void 0 && row[f] !== null) out[f] = row[f];
@@ -134666,8 +134587,14 @@ function parseYmd2(value) {
134666
134587
  const s = normalizeYandexDailyDate(value);
134667
134588
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
134668
134589
  if (!m) return null;
134669
- const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
134670
- return Number.isNaN(d.getTime()) ? null : d;
134590
+ const y = Number(m[1]);
134591
+ const month = Number(m[2]);
134592
+ const day = Number(m[3]);
134593
+ const d = new Date(y, month - 1, day);
134594
+ if (Number.isNaN(d.getTime()) || d.getFullYear() !== y || d.getMonth() !== month - 1 || d.getDate() !== day) {
134595
+ return null;
134596
+ }
134597
+ return d;
134671
134598
  }
134672
134599
  function formatYmd2(d) {
134673
134600
  const y = d.getFullYear();
@@ -134746,9 +134673,12 @@ var YANDEX_DAILY_OUTLINE_HINTS = [
134746
134673
  "// `items[].date` \u5DF2\u89C4\u8303\u4E3A YYYY-MM-DD\u3002\u63A5\u53E3\u53EF\u80FD\u8FD4\u56DE ISO \u65F6\u95F4\u6233\uFF08\u5982 2026-07-01T00:00:00+00:00\uFF09\uFF0CCLI \u5DF2\u622A\u65AD\uFF1B\u7981\u6B62\u518D\u62FC\u63A5 T00:00:00Z\uFF0C\u5426\u5219 new Date() \u4F1A Invalid Date\uFF0C\u5468\u6807\u7B7E\u53D8\u6210 NaN\u3002",
134747
134674
  "// \u5468\u8D8B\u52BF\u8BFB\u672C\u6587\u4EF6 `weekly[]`\uFF08ISO \u5468\u4E00~\u5468\u65E5\uFF1B`days` \u4E3A\u6709\u6570\u636E\u7684\u5929\u6570\uFF09\u3002\u7981\u6B62\u81EA\u884C\u6309 date \u805A\u5468\u3002"
134748
134675
  ];
134676
+ var YANDEX_ROI_OUTLINE_HINTS = [
134677
+ "// `roi` = (Revenue-Spend)/Spend\uFF0C\u5DF2\u662F\u500D\u6570\uFF083.72 \u2248 \u5229\u6DA6 372%\uFF09\uFF0C\u4E0D\u662F 0~1\u3002\u7981\u6B62\u518D \xD7100\u3002\u65E0\u6536\u76CA\u65F6\u5E38\u4E3A -1\uFF0C\u6309\u7F3A\u5931\u5904\u7406\u3002"
134678
+ ];
134749
134679
  function buildYandexOutlineHints(section) {
134750
- if (section === "daily") return YANDEX_DAILY_OUTLINE_HINTS;
134751
- return void 0;
134680
+ if (section === "daily") return [...YANDEX_DAILY_OUTLINE_HINTS, ...YANDEX_ROI_OUTLINE_HINTS];
134681
+ return YANDEX_ROI_OUTLINE_HINTS;
134752
134682
  }
134753
134683
  function mapDailyRows(payload) {
134754
134684
  return asItems(payload).map((r) => {
@@ -134835,7 +134765,8 @@ async function mergeYandexAnalysisSnapshotIntoReport(payload, snapshotDir) {
134835
134765
  kpis.costPerConversion = round25(num10(totals.costPerConversion));
134836
134766
  }
134837
134767
  if (num10(totals.conversionRate) != null) kpis.conversionRate = num10(totals.conversionRate);
134838
- if (num10(totals.roi) != null) kpis.roi = num10(totals.roi);
134768
+ const overviewRoi = normalizeYandexRoi(totals.roi);
134769
+ if (overviewRoi != null) kpis.roi = overviewRoi;
134839
134770
  }
134840
134771
  if (overview) {
134841
134772
  if (num10(overview.balance) != null) kpis.balance = round25(num10(overview.balance));
@@ -113,7 +113,9 @@ siluzan-tso -h
113
113
  | TikTok / Meta 线索表单 | W11 | `references/operations/clue.md` |
114
114
  | 日/周巡检 | W12 | `references/core/workflows.md`(W12)+ `references/accounts/accounts-balance-stats.md` |
115
115
  | 超预算熔断 / 空耗熔断(全户 Google) | — | `references/operations/guard.md`(`guard budget-circuit` / `guard zero-conv`;禁止逐户 for-loop) |
116
- | 宿主编排 / 投放自控 / 异常监控 / 自动优化 | — | `references/operations/hosted-automation-user-catalog.md`(**仅当用户问自动化/巡检/熔断**;表内每行只 Read **一个** SOP;预算/空耗熔断优先 guard.md) |
116
+ | Bing 自动化巡检(封禁/拒审/超预算/空耗预警) | — | `references/operations/hosted-automation-bing.md`(只读告警,不能自动暂停/改价) |
117
+ | Yandex 自动化巡检(余额/CPA/日花费预警) | — | `references/operations/hosted-automation-yandex.md`(只读告警,不能自动改投放) |
118
+ | 宿主编排 / 投放自控 / 异常监控 / 自动优化 | — | `references/operations/hosted-automation-user-catalog.md`(**仅当用户问自动化/巡检/熔断**;表内每行只 Read **一个** SOP;Google 熔断优先 guard.md;Bing/Yandex 走上两行) |
117
119
 
118
120
  ---
119
121
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.46-beta.2",
4
- "publishedAt": 1787013760194
3
+ "version": "1.1.46-beta.6",
4
+ "publishedAt": 1787040608858
5
5
  }
@@ -65,9 +65,11 @@
65
65
  | `references/operations/forewarning.md` | 智能预警 |
66
66
  | `references/operations/clue.md` | TikTok / Meta 线索表单 |
67
67
  | `references/operations/hosted-automation-user-catalog.md` | 高阶自动化能力目录 |
68
- | `references/operations/hosted-automation-self-control.md` | 预算/CPA/空耗自控 SOP |
69
- | `references/operations/hosted-automation-monitoring-json.md` | 异常监控 JSON 键名 |
70
- | `references/operations/hosted-automation-optimize-index.md` | 自动优化 SOP 索引 |
68
+ | `references/operations/hosted-automation-self-control.md` | Google 预算/CPA/空耗自控 SOP |
69
+ | `references/operations/hosted-automation-monitoring-json.md` | Google 异常监控 JSON 键名 |
70
+ | `references/operations/hosted-automation-optimize-index.md` | Google 自动优化 SOP 索引 |
71
+ | `references/operations/hosted-automation-bing.md` | Bing 只读巡检 SOP |
72
+ | `references/operations/hosted-automation-yandex.md` | Yandex 只读巡检 SOP |
71
73
  | `references/operations/hosted-automation-scenarios.md` | 宿主编排场景索引 |
72
74
 
73
75
  ## Assets 模板(`../assets/`)
@@ -332,6 +332,8 @@ siluzan-tso tiktok-analysis -a 1234567890 --start 2026-03-01 --end 2026-03-31 \
332
332
  ## Bing(BingV2)账户分析
333
333
 
334
334
  > **日期限制**:Bing 报表不能包含今天或昨天。省略时 CLI 默认截至前天的近 7 天。用户要**已结束的完整自然月**(如「6月份」)→ `--start` 当月 1 日、`--end` 当月最后一天(6 月 = `06-30`,**禁止**减 1 天),见 `report-templates/bing-period-report.md` §日期规则。
335
+ >
336
+ > **自动化巡检步骤**(封禁 / 拒审 / 落地页 / 当日超预算 / 空耗预警,只读告警):`references/operations/hosted-automation-bing.md`。
335
337
 
336
338
  与 `google-analysis` / `facebook-analysis` 同架构:`bing-analysis -a … --json-out …`(省略子命令名默认执行 `run`,批量拉取全部维度或 `--sections` 指定子集)。
337
339
 
@@ -432,13 +434,15 @@ siluzan-tso bing-analysis run -a <mediaCustomerId> --json-out ./snap --sections
432
434
 
433
435
  ## Yandex 账户分析
434
436
 
437
+ > **自动化巡检步骤**(余额 / 多账户 CPA / 日报花费,只读告警):`references/operations/hosted-automation-yandex.md`。
438
+ >
435
439
  > **网关**:直打 YandexAPI `{yandexApiUrl}/yandex-analysis/*`(从 TSO `apiBaseUrl` 推导:`tso-api-ci` → `yandexapi-ci.mysiluzan.com`;可用 `SILUZAN_YANDEX_API` 覆盖)。
436
440
  >
437
441
  > **日期**:`start`/`end` 不能晚于今天;省略默认近 7 天(含今天)。**search-terms** 仅支持近 **180 天**(Direct 限制),超窗网关/CLI 均会拒绝。
438
442
  >
439
443
  > **账户 ID**:Yandex Client-Login / mediaCustomerId(如 `porg-xxx`,与 `list-accounts -m Yandex` 一致),**不是**纯数字。
440
444
  >
441
- > **金额**:已从 micros 转为账户币种小数(`/1e6`)。`ctr` / `conversionRate` 为 **0~1 小数**(先读 outline)。
445
+ > **金额**:已从 micros 转为账户币种小数(`/1e6`)。`ctr` / `conversionRate` 为 **0~1 小数**(先读 outline)。`roi` 为 **(收益-消耗)/消耗** 倍数,禁止再 ×100。
442
446
 
443
447
  与 `bing-analysis` 同架构:`yandex-analysis -a … --json-out …`(省略子命令名默认执行 `run`);周期报告默认 **HTML**:`yandex-analysis render`(见 `report-templates/yandex-period-report.md`)。用户要 Excel → `yandex-period-report-excel.md`(Agent 脚本,无 excel 子命令)。
444
448
 
@@ -285,7 +285,7 @@
285
285
 
286
286
  ## 十二、风险预警与自动化(按需)
287
287
 
288
- **仅当**用户问自动化 / 巡检 / 熔断 / 自控 / 异常监控时:Read `references/operations/hosted-automation-user-catalog.md`,按表选 **一个** SOP 介绍或执行。**禁止**在无关任务(查余额、出报告、开户等)主动灌输自动化目录。
288
+ **仅当**用户问自动化 / 巡检 / 熔断 / 自控 / 异常监控时:Read `references/operations/hosted-automation-user-catalog.md`,按表选 **一个** SOP 介绍或执行。Google 熔断优先 `guard.md`;Bing 走 `hosted-automation-bing.md`;Yandex 走 `hosted-automation-yandex.md`。**禁止**在无关任务(查余额、出报告、开户等)主动灌输自动化目录。
289
289
 
290
290
  ---
291
291
 
@@ -208,11 +208,13 @@
208
208
  ## W12 · 日 / 周巡检
209
209
 
210
210
  - **触发**:日常/每周快速了解各媒体余额、消耗、预警与报告/智投状态。
211
- - **必读**:`references/accounts/accounts-balance-stats.md`;首页看板口径见 `references/misc/tso-home.md`。超预算/空耗熔断另读 `references/operations/guard.md`。
211
+ - **必读**:`references/accounts/accounts-balance-stats.md`;首页看板口径见 `references/misc/tso-home.md`。Google 超预算/空耗熔断另读 `references/operations/guard.md`。Bing / Yandex 只读巡检读 `hosted-automation-bing.md` / `hosted-automation-yandex.md`。
212
212
  - **步骤**:
213
213
  1. 余额:`list-accounts -m <媒体> --json-out ./snap` → `balance -m <媒体> -a <mediaCustomerId,...>`(多户续航预警走 **P2**)。
214
- 2. 消耗:`stats -m <媒体> -a <id> --start <昨天/上周一> --end <昨天/上周日>`(多户汇总走 **P3**)。
214
+ 2. 消耗:`stats -m <媒体> -a <id> --start <昨天/上周一> --end <昨天/上周日>`(多户汇总走 **P3**;Bing/Yandex **今天**不要用 `stats`)。
215
215
  3. Google 当日超预算/空耗熔断(若用户要求):`guard budget-circuit` / `guard zero-conv -m Google --json-out`(**禁止**逐户 for-loop)。
216
- 4. 预警触发:`forewarning records -m <媒体> --start <S>`(见 **W10**)。
217
- 5. 智投/线索(按需):`ad batch list --state Failed/HasFailed`(**W4**)、`clue …`(**W11**)、`optimize list/records`(**W6**)。
218
- - **产物**:要与网页首页看板数字完全一致(聚合口径)时引导打开首页(`tso-home.md`);CLI 给的是单账户粒度的近似巡检;熔断任务须交付命中表或显式「无命中」。
216
+ 4. Bing 巡检(若用户要求):按 `hosted-automation-bing.md` 跑封禁/拒审/当日超预算或空耗预警(只告警,不暂停)。
217
+ 5. Yandex 巡检(若用户要求):按 `hosted-automation-yandex.md` `balance-scan` / `accounts-digest` / `yandex-analysis`(只告警)。
218
+ 6. 预警触发:`forewarning records -m <媒体> --start <S>`(见 **W10**)。
219
+ 7. 智投/线索(按需):`ad batch list --state Failed/HasFailed`(**W4**)、`clue …`(**W11**)、`optimize list/records`(**W6**)。
220
+ - **产物**:要与网页首页看板数字完全一致(聚合口径)时引导打开首页(`tso-home.md`);CLI 给的是单账户粒度的近似巡检;Google 熔断须交付命中表或显式「无命中」;Bing/Yandex 巡检交付告警列表(不能写成已自动暂停)。
@@ -1,27 +1,20 @@
1
- # guard —— Google 全户熔断扫描
1
+ # guard —— Google 全户熔断扫描(只读)
2
2
 
3
3
  > 流程见 `hosted-automation-user-catalog.md`(预算/空耗自控)。单户排障仍可用 `ad campaigns` / `ad groups`。
4
- > **仅 Google**。非 Google 媒体本 CLI 无系列/组/创意暂停写能力。
5
-
6
- ## Contents
7
-
8
- - 何时用
9
- - budget-circuit(超预算)
10
- - zero-conv(空耗)
11
- - 输出与交付
12
- - 与旧编排对照
13
-
14
- ---
4
+ > **仅 Google**。本命令**只扫描列出命中,不会暂停任何账户/系列/组/创意**。
5
+ > 若要对某个命中对象停投,必须对该账户单独执行 `ad campaign-status` / `ad adgroup-status` / `ad ad-status`(带 `--commit`),以便写审计能记清「改了哪个账户的哪个对象」。
15
6
 
16
7
  ## 何时用
17
8
 
18
9
  | 用户话术 | 命令 |
19
10
  | -------------------------------------------------- | -------------------------------------- |
20
- | 所有账户系列当日花费 ≥ 日预算 110% → 暂停 | `guard budget-circuit` |
21
- | 空耗熔断:花 ≥ 目标 CPA × N 且转化 0 → 暂停组/创意 | `guard zero-conv` |
11
+ | 所有账户系列当日花费 ≥ 日预算 110% → **列出** | `guard budget-circuit` |
12
+ | 空耗:花 ≥ 目标 CPA × N 且转化 0 → **列出**组/创意 | `guard zero-conv` |
13
+ | 确认后暂停某一户的某一条系列/组/创意 | `ad campaign-status` / `adgroup-status` / `ad-status` |
22
14
  | 只查某一户 / 改预算后复核单系列 | 仍用 `ad campaigns -a … --start=--end` |
23
15
 
24
- **禁止**外层 for-loop:`list-accounts` × 多媒体再逐户 `ad campaigns` 做全户熔断。
16
+ **禁止**外层 for-loop:`list-accounts` × 多媒体再逐户 `ad campaigns` 做全户扫描。
17
+ **禁止**指望 `guard` 一条命令批量暂停——没有 `--apply`,也不会改投放状态。
25
18
 
26
19
  ---
27
20
 
@@ -32,9 +25,9 @@ siluzan-tso guard budget-circuit -m Google \
32
25
  [--date YYYY-MM-DD] [--ratio 1.1] [-a id1,id2] \
33
26
  --json-out ./snap-guard
34
27
 
35
- # 确认命中后暂停(须全局 --commit
36
- siluzan-tso guard budget-circuit -m Google --date 2026-07-28 --ratio 1.1 \
37
- --apply --json-out ./snap-guard --commit "budget-circuit 2026-07-28"
28
+ # 确认某条命中后,对该账户单独暂停(须 --commit,写审计记这一次)
29
+ siluzan-tso ad campaign-status -a <mediaCustomerId> --id <campaignId> \
30
+ --status Paused --commit "超预算熔断 2026-07-28"
38
31
  ```
39
32
 
40
33
  | 选项 | 说明 |
@@ -44,7 +37,6 @@ siluzan-tso guard budget-circuit -m Google --date 2026-07-28 --ratio 1.1 \
44
37
  | `--date` | 统计日;默认 Asia/Shanghai 今天;**强制当日 spend**(start=end) |
45
38
  | `--ratio` | 默认 `1.1`(建议 1.10–1.20) |
46
39
  | `--status` | 默认只查 `Enabled`;传 `*` 不过滤 |
47
- | `--apply` | 默认 dry-run;加此旗才 `Paused` |
48
40
  | `--json-out` | Agent 必带 |
49
41
 
50
42
  命中条件:`budget > 0` 且 `spend >= budget * ratio`(`budget`/`spend` 均为元;`spend` 为该日合计)。
@@ -59,8 +51,9 @@ siluzan-tso guard zero-conv -m Google \
59
51
  [--fallback-target-cpa <元>] \
60
52
  --json-out ./snap-guard
61
53
 
62
- siluzan-tso guard zero-conv -m Google --cpa-multiple 3 --apply \
63
- --json-out ./snap-guard --commit "zero-conv 空耗熔断"
54
+ # 确认某条命中后,对该账户单独暂停
55
+ siluzan-tso ad adgroup-status -a <mediaCustomerId> --id <adGroupId> \
56
+ --status Paused --commit "空耗熔断 2026-07-28"
64
57
  ```
65
58
 
66
59
  | 选项 | 说明 |
@@ -68,18 +61,16 @@ siluzan-tso guard zero-conv -m Google --cpa-multiple 3 --apply \
68
61
  | `--cpa-multiple` | 默认 `3` |
69
62
  | `--level` | 默认 `adgroup`;`ad`/`both` 需 `--fallback-target-cpa`(创意无组级 CPA 字段) |
70
63
  | `--fallback-target-cpa` | 组无 `targetCpaAmountYuan` 时回退;缺失则进 `skipped[]` |
71
- | `--apply` / `--json-out` | budget-circuit |
64
+ | `--json-out` | Agent 必带 |
72
65
 
73
- 命中:`conversions === 0` 且 `spend >= targetCpaYuan * N`。`summary.label` 固定为 **「空耗熔断」**(宿主发 P1 时用此文案;CLI 不代发通知)。
66
+ 命中:`conversions === 0` 且 `spend >= targetCpaYuan * N`。摘要文案为 **「空耗熔断」**(宿主发 P1 时用此文案;CLI 不代发通知)。
74
67
 
75
68
  ---
76
69
 
77
70
  ## 输出与交付
78
71
 
79
- - 落盘 section:`guard-budget-circuit` / `guard-zero-conv`
80
- - 字段:`meta`、`summary`、`hits[]`、`paused[]`、`errors[]`(zero-conv 另有 `skipped[]`)
81
- - stdout:一行摘要 JSON(若 `--json-out`)+ **人类可读命中表**(无命中也打印「无命中」——巡检必须收口)
82
- - dry-run 命中后提示追加 `--apply --commit …`
72
+ - `--json-out` 落盘命中表;无命中也必须收口(打印「无命中」)
73
+ - 命中后用 **逐账户** `ad *-status` 暂停,**没有** `--apply`
83
74
 
84
75
  恢复:人工加预算 / 改出价后,用 `ad campaign-status|adgroup-status|ad-status … --status Enabled`(或网页),**禁止**无人值守自动恢复。
85
76
 
@@ -91,4 +82,4 @@ siluzan-tso guard zero-conv -m Google --cpa-multiple 3 --apply \
91
82
  | -------------------------------------------------------- | -------------------------------- |
92
83
  | 6 媒体 list-accounts → 逐户 ad campaigns → 脚本比 budget | `guard budget-circuit -m Google` |
93
84
  | 逐户 ad groups + 手写阈值 | `guard zero-conv -m Google` |
94
- | `ad campaigns -m Google`(非法) | 单户用 `-a`;全户熔断用 `guard` |
85
+ | `ad campaigns -m Google`(非法) | 单户用 `-a`;全户扫描用 `guard` |
@@ -0,0 +1,118 @@
1
+ # 宿主编排:Bing(BingV2)只读巡检
2
+
3
+ > **何时 Read**:用户要 Bing / Microsoft Ads 自动化、巡检、熔断、封禁、拒审、落地页、超预算或空耗。Google 熔断走 `references/operations/guard.md`。
4
+ > **能做什么**:拉数、对比阈值、宿主告警。
5
+ > **不能做什么**:暂停系列/组/广告、改价、改预算。没有对应写命令,**禁止**假装已执行熔断。
6
+
7
+ **前置**:已 `siluzan-tso login`,并用 `list-accounts -m BingV2 --json-out ./snap` 确认 `mediaCustomerId`(数字)。
8
+
9
+ **硬约束**:
10
+
11
+ - `guard` **只支持 Google**。禁止 `guard … -m BingV2`。
12
+ - 周期报表(不带 `--aggregation`)日期**不能含今天/昨天**;省略则默认截至前天的近 7 天。
13
+ - 当日花费、近两小时 CPA 必须显式传 `--aggregation Daily|Hourly`,且 `--sections` 只能是 `campaigns` / `ad-groups` / `ads`。
14
+ - Hourly 时间桶是**太平洋时区**,和北京日期可能差一天;阈值按 `timePeriod` 对齐,不要按北京日历硬切。
15
+ - 花费/转化用 `bing-analysis run --sections …`;日预算/启停状态用 `campaign-entities`。两套数据不要混成一行。
16
+
17
+ ---
18
+
19
+ ## 1. 余额 / 额度续航
20
+
21
+ 多户先扫 TSO 余额(截至昨天的日均,**不含当天**):
22
+
23
+ ```bash
24
+ siluzan-tso balance-scan -m BingV2 --threshold-days 7 --json-out ./snap-bing
25
+ ```
26
+
27
+ 有插入订单(IO)的户再核对官方额度:
28
+
29
+ ```bash
30
+ siluzan-tso bing-analysis insertion-orders -a <mediaCustomerId> --json-out ./snap-bing
31
+ siluzan-tso bing-analysis monthly-spend -a <mediaCustomerId> --json-out ./snap-bing
32
+ ```
33
+
34
+ 无 IO 时没有额度上限,只看 `monthly-spend` 与 `balance-scan`。命中续航/额度不足 → 宿主通知充值或补 IO;CLI **不能**自动充值。
35
+
36
+ ---
37
+
38
+ ## 2. 账户封禁
39
+
40
+ ```bash
41
+ siluzan-tso bing-analysis account-status -a <mediaCustomerId> --json-out ./snap-bing
42
+ ```
43
+
44
+ `status` 不是 `Active` → 宿主发 **告警P1**(账户、状态原文)。不能在 CLI 里解封。
45
+
46
+ ---
47
+
48
+ ## 3. 素材拒审
49
+
50
+ 全账户巡检用 `bulk-ads`,**禁止**按系列循环 `ad-entities`。首次全量;下次把上次响应的 `syncTimeUtc` 原样回传做增量(最早回溯 30 天):
51
+
52
+ ```bash
53
+ siluzan-tso bing-analysis bulk-ads -a <mediaCustomerId> --editorial-status Disapproved --json-out ./snap-bing
54
+ siluzan-tso bing-analysis bulk-ads -a <mediaCustomerId> --editorial-status Disapproved --last-sync-time-utc <上次syncTimeUtc> --json-out ./snap-bing
55
+ ```
56
+
57
+ `bulk-ads` 是官方异步下载,大账户可能要几分钟,适合定时任务。命中 → 告警广告 ID / 系列 / 审核状态;改创意、申诉在 Bing 后台做。
58
+
59
+ ---
60
+
61
+ ## 4. 落地页死链
62
+
63
+ 同样用 `bulk-ads` 拉广告上的到达网址(不传 `--editorial-status` 则全量)。CLI **不**发 HTTP。宿主对 URL 做 HEAD/GET,4xx/5xx 或超时 → 告警。不要为了探活去遍历 `ad-entities`。
64
+
65
+ ---
66
+
67
+ ## 5. 当日超预算(只预警,不暂停)
68
+
69
+ 1. 拉系列日预算:
70
+
71
+ ```bash
72
+ siluzan-tso bing-analysis campaign-entities -a <mediaCustomerId> --json-out ./snap-bing
73
+ ```
74
+
75
+ 2. 拉**当天**花费(必须 `--aggregation Daily`,日期可含今天):
76
+
77
+ ```bash
78
+ siluzan-tso bing-analysis run -a <mediaCustomerId> --json-out ./snap-bing --sections campaigns \
79
+ --start <当日YYYY-MM-DD> --end <当日YYYY-MM-DD> --aggregation Daily
80
+ ```
81
+
82
+ 3. 按同一 `campaignId` 对齐:当日花费对照 `dailyBudget`。`isSharedBudget=true` 时 `dailyBudget` 是共享总额,不要当成单系列上限。建议阈值 **110%–120%**(缓冲报表延迟)。
83
+ 4. 超阈值 → 宿主告警系列名、花费、日预算。**禁止**调用 `ad campaign-status`(那是 Google 命令)。恢复加预算或暂停由人工在 Bing 后台处理。
84
+
85
+ ---
86
+
87
+ ## 6. CPA 飙升 / 空耗(只预警,不降价、不暂停)
88
+
89
+ 近实时(近两小时或当日)用 Hourly / Daily,不要用默认周期报表冒充「今天」:
90
+
91
+ ```bash
92
+ # 当日组级花费与转化
93
+ siluzan-tso bing-analysis run -a <mediaCustomerId> --json-out ./snap-bing --sections ad-groups \
94
+ --start <当日> --end <当日> --aggregation Daily
95
+
96
+ # 近两小时(按 adGroupId 取最近两个整点相加再算)
97
+ siluzan-tso bing-analysis run -a <mediaCustomerId> --json-out ./snap-bing --sections ad-groups \
98
+ --start <起> --end <止> --aggregation Hourly
99
+ ```
100
+
101
+ 宿主侧:
102
+
103
+ - 有转化:`CPA = 花费 / 转化`;超过约定目标倍数 → 告警「CPA 飙升」。
104
+ - 花费已达「目标 CPA × N」且转化为 0 → 告警「空耗预警」(文案不要写成「已熔断」)。
105
+
106
+ 目标 CPA 以账户约定或系列出价策略为准;落盘里没有就不要猜。拉今天数据**不要**显式传 `--return-only-complete-data true`。
107
+
108
+ ---
109
+
110
+ ## 7. 多账户消耗 / 零转化巡检(截至昨天)
111
+
112
+ `accounts-digest` / `stats` 走每日同步,**今天为 0**,只适合回看已结束的日期:
113
+
114
+ ```bash
115
+ siluzan-tso accounts-digest -m BingV2 --start <S> --end <D> --zero-conversions --json-out ./snap-bing
116
+ ```
117
+
118
+ 看当天必须走第 5、6 步的 `--aggregation` 报表。
@@ -1,8 +1,9 @@
1
1
  # 异常监控巡检:`siluzan-tso` CLI `--json-out` 落盘 JSON 键名与命令
2
2
 
3
- > **主题索引**:[`references/operations/hosted-automation-scenarios.md`](references/operations/hosted-automation-scenarios.md)
4
- > **编排责任**:定时、HTTP 探活、通知等在宿主。本页只列 **读数命令与常见 JSON 键名**。
3
+ > **主题索引**:[`references/operations/hosted-automation-scenarios.md`](references/operations/hosted-automation-scenarios.md)
4
+ > **编排责任**:定时、HTTP 探活、通知等在宿主。本页只列 **Google 读数命令与常见 JSON 键名**。
5
5
  > **统计日 / 时区**:与自控场景相同,见 [`references/operations/hosted-automation-self-control.md`](references/operations/hosted-automation-self-control.md)「统计日与今日」。
6
+ > Bing / Yandex 巡检步骤见 `hosted-automation-bing.md` / `hosted-automation-yandex.md`,不要套用本页 Google 键名。
6
7
 
7
8
  宿主做定时巡检时,**只以当次 CLI 落盘 JSON 键名为准**(读命令统一 **`--json-out <dir>`**,以**落盘 `*.json` 文件正文**为准;`google-analysis` 等同理)。Google Ads API 文档里的资源名(如 `billing_setup`、`account_budget`、`amount_served_micros`)**与本 CLI 输出不是同一套命名**,不要当作本仓库 JSON 的键去解析。若某键缺失,**禁止猜测**:以实际输出为准,或换用 `references/analytics/account-analytics.md` 中的其它子命令。
8
9
 
@@ -1,7 +1,8 @@
1
1
  # 宿主编排:自动优化(Google)— 文档索引
2
2
 
3
- > **编排责任**:定时、阈值、多轮状态(如「连续 3 日未改善」)、统计检验、通知(P1/P2)由 **宿主** 实现。
3
+ > **编排责任**:定时、阈值、多轮状态(如「连续 3 日未改善」)、统计检验、通知(P1/P2)由 **宿主** 实现。
4
4
  > **本组文档**:说明如何用 `siluzan-tso` **拉检查项**、**执行写操作**、**写后复核**;每条 SOP 独立成文,避免单文件过长。
5
+ > Bing / Yandex 没有降价、暂停、改预算接口,不要套用本组 SOP;巡检见 `hosted-automation-bing.md` / `hosted-automation-yandex.md`。
5
6
 
6
7
  | 文档 | 场景 |
7
8
  | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
@@ -1,25 +1,24 @@
1
- # 宿主编排(Google):文档索引
1
+ # 宿主编排:文档索引
2
2
 
3
- > **Bing(BingV2)**:同类「预算与 ROI / 异常监控 / 自动优化」场景的**只读**读数命令见 `references/analytics/account-analytics.md` § Bing 只读运营子命令(`bing-analysis account-status`/`campaign-entities`/`ad-entities`/`bulk-ads`/`insertion-orders`/`monthly-spend`:账户封禁、拒审巡检、余额续航)与 § Bing 近实时报表(`bing-analysis run --aggregation`:单日花费、近两小时 CPA);本批暂无暂停/改价/改预算接口,宿主侧只能读数决策,不能自动执行写操作。
3
+ > **编排责任**:定时、多条件 IF、滑动时间窗、通知(含 P1 /「空耗熔断」文案)由 **OpenClaw / WorkBuddy / Cron** 等宿主实现。
4
+ > **CLI 责任**:单次调用给出检查与(仅 Google 具备的)写操作命令;**不**内置常驻调度或通知渠道。
4
5
 
5
- > **编排责任**:定时、多条件 IF、滑动时间窗、通知(含 P1 /「空耗熔断」文案)由 **OpenClaw / WorkBuddy / Cron** 等宿主实现。
6
- > **CLI 责任**:单次调用下给出检查与写操作所需命令;**不**内置常驻调度或通知渠道。
6
+ 按媒体选文档(本文件只做入口)。
7
7
 
8
- 本主题拆成下面两份子文档,按需打开(本文件只做**入口与导航**)。
8
+ | 文档 | 内容 |
9
+ | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
10
+ | [`references/operations/hosted-automation-user-catalog.md`](references/operations/hosted-automation-user-catalog.md) | **用户向一览**:先看这张表,每行只 Read **一个** SOP |
11
+ | [`references/operations/hosted-automation-self-control.md`](references/operations/hosted-automation-self-control.md) | **Google 三类投放自控**:单日预算熔断、CPA 飙升降价、连续空耗暂停 |
12
+ | [`references/operations/hosted-automation-monitoring-json.md`](references/operations/hosted-automation-monitoring-json.md) | **Google 异常监控**落盘与读数 |
13
+ | [`references/operations/hosted-automation-optimize-index.md`](references/operations/hosted-automation-optimize-index.md) | **Google 自动优化**:差素材降价/关停、高转化扩量、A/B 停输家 |
14
+ | [`references/operations/hosted-automation-bing.md`](references/operations/hosted-automation-bing.md) | **Bing 只读巡检**:余额/IO、封禁、拒审、落地页、当日超预算预警、CPA/空耗预警;**不能**自动暂停/改价 |
15
+ | [`references/operations/hosted-automation-yandex.md`](references/operations/hosted-automation-yandex.md) | **Yandex 只读巡检**:余额、多账户 CPA/零转化、日报花费异动;**不能**自动改投放 |
9
16
 
10
- | 文档 | 内容 |
11
- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
12
- | [`references/operations/hosted-automation-self-control.md`](references/operations/hosted-automation-self-control.md) | **三类投放自控**:单日预算熔断、CPA 飙升降价、连续空耗暂停;通用约定与常用 JSON 字段表;宿主编排实现清单;与 `forewarning` 的关系 |
13
- | [`references/operations/hosted-automation-monitoring-json.md`](references/operations/hosted-automation-monitoring-json.md) | **异常监控巡检**:`balance-scan` / `balance` 等 **`--json-out` 落盘**;**`google-analysis --sections overview` / `campaign-hour` / `final-urls`** 与账户分析口径一致时用 **`--json-out <dir>`** 读落盘 `*.json`;拒审相关键名与命令见该文档 |
14
- | [`references/operations/hosted-automation-optimize-index.md`](references/operations/hosted-automation-optimize-index.md) | **自动优化(宿主编排)**:差素材降价/关停、高转化扩量、A/B 停输家 — 分文件 SOP(含 `ad list` 按日聚合、`google-analysis --sections campaigns` 等) |
15
- | [`references/operations/hosted-automation-user-catalog.md`](references/operations/hosted-automation-user-catalog.md) | **用户向一览**:预算/ROI 自控、异常监控、自动优化表格(可节选给用户;与上表互补) |
17
+ **必读交叉引用**:
16
18
 
17
- **必读交叉引用**(子文档内会再写一遍):
19
+ - Google 金额与写命令:`references/google-ads/google-ads.md`
20
+ - 各媒体拉数与日期限制:`references/analytics/account-analytics.md`
21
+ - 余额类命令:`references/accounts/accounts-balance-stats.md`
22
+ - `--json-out`:`references/core/tips.md`
18
23
 
19
- - 金额与展示:`references/google-ads/google-ads.md` § Gotchas(金额单位);币种见 `references/accounts/currency.md`
20
- - 通用读命令 **`--json-out`** 与 Node 过滤:`references/core/tips.md`;**`google-analysis` 账户分析** 用 **`--json-out`**:`references/analytics/account-analytics.md`
21
- - 写命令语法:`references/google-ads/google-ads.md`
22
- - 账户/维度分析、时间窗:`references/analytics/account-analytics.md`
23
- - 余额类命令详解:`references/accounts/accounts-balance-stats.md`
24
-
25
- **前置**:已 `siluzan-tso login`,并已用 `list-accounts -m Google -k <mediaCustomerId> --json-out ./snap` 确认账户与 `mediaCustomerId`。
24
+ **前置**:已 `siluzan-tso login`。Google 用 `list-accounts -m Google`;Bing `-m BingV2`(数字 ID);Yandex 用 `-m Yandex`(`porg-…`)。
@@ -1,8 +1,9 @@
1
1
  # 宿主编排:三类 Google 投放自控(详细步骤)
2
2
 
3
- > **主题索引**:[`references/operations/hosted-automation-scenarios.md`](references/operations/hosted-automation-scenarios.md)
4
- > **编排责任**:定时、多条件 IF、滑动时间窗、通知(含 P1 /「空耗熔断」文案)由 **OpenClaw / WorkBuddy / Cron** 等宿主实现。
3
+ > **主题索引**:[`references/operations/hosted-automation-scenarios.md`](references/operations/hosted-automation-scenarios.md)
4
+ > **编排责任**:定时、多条件 IF、滑动时间窗、通知(含 P1 /「空耗熔断」文案)由 **OpenClaw / WorkBuddy / Cron** 等宿主实现。
5
5
  > **本页责任**:说明如何用 `siluzan-tso` **拉检查项**、**执行最终写操作**、**写后复核**。
6
+ > Bing / Yandex **没有**本页写操作,只读巡检见 `hosted-automation-bing.md` / `hosted-automation-yandex.md`。
6
7
 
7
8
  ## Contents
8
9
 
@@ -57,12 +58,12 @@
57
58
 
58
59
  ### 首选(全户 / 多户 Google)
59
60
 
60
- 一条命令扫描名下 Google 户并交付命中表(默认 dry-run):
61
+ 一条命令扫描名下 Google 户并交付命中表(**只读,不暂停**):
61
62
 
62
63
  ```bash
63
64
  siluzan-tso guard budget-circuit -m Google --date <当日YYYY-MM-DD> --ratio 1.1 --json-out ./snap-guard
64
- # 确认后:
65
- siluzan-tso guard budget-circuit -m Google --date <当日> --ratio 1.1 --apply --json-out ./snap-guard --commit "budget-circuit"
65
+ # 确认某条命中后,对该账户单独暂停(写审计记这一次):
66
+ siluzan-tso ad campaign-status -a <mediaCustomerId> --id <campaignId> --status Paused --commit "超预算熔断"
66
67
  ```
67
68
 
68
69
  参数与落盘字段见 **`references/operations/guard.md`**。**禁止** `list-accounts` 六媒体循环 + 逐户 `ad campaigns`。
@@ -106,7 +107,7 @@ siluzan-tso ad campaign-status -a <mediaCustomerId> --id <campaignId> --status E
106
107
 
107
108
  | 状态 | 说明 |
108
109
  | ---- | ---------------------------------------------------------------------------------- |
109
- | 首选 | `guard budget-circuit -m Google`(全户扫描 + 可选 `--apply`) |
110
+ | 首选 | `guard budget-circuit -m Google`(全户扫描,只读);暂停用 `ad campaign-status` |
110
111
  | 单户 | 读:`ad campaigns --json-out`;写:`ad campaign-status`;复核:再次 `ad campaigns` |
111
112
 
112
113
  ---
@@ -180,10 +181,11 @@ siluzan-tso ad adgroup-edit -a <mediaCustomerId> --id <adGroupId> --max-cpc <主
180
181
 
181
182
  ```bash
182
183
  siluzan-tso guard zero-conv -m Google --date <当日> --cpa-multiple 3 --level adgroup --json-out ./snap-guard
183
- # 确认后 --apply --commit …;创意维加 --level ad|both --fallback-target-cpa <元>
184
+ # 确认某条命中后,对该账户单独暂停;创意维加 --level ad|both --fallback-target-cpa <元>
185
+ siluzan-tso ad adgroup-status -a <mediaCustomerId> --id <adGroupId> --status Paused --commit "空耗熔断"
184
186
  ```
185
187
 
186
- 见 **`references/operations/guard.md`**。`summary.label` =「空耗熔断」。
188
+ 见 **`references/operations/guard.md`**。摘要文案为「空耗熔断」。
187
189
 
188
190
  ### 单户排障(可选)
189
191
 
@@ -1,28 +1,24 @@
1
1
  # 宿主编排能力目录
2
2
 
3
3
  > **何时 Read**:用户明确问自动化 / 巡检 / 熔断 / 自控 / 异常监控 / 自动优化时。其他任务**跳过**本文件。
4
- > **怎么读**:下表每行只 Read **一个**「SOP」列路径。金额口径见 `references/google-ads/google-ads.md` § Gotchas(金额单位) + `references/accounts/currency.md`(**不要**在 SKILL.md 找「金额硬约束」节)。
4
+ > **怎么读**:下表每行只 Read **一个**「SOP」列路径。Google 金额口径见 `references/google-ads/google-ads.md` § Gotchas + `references/accounts/currency.md`。
5
5
  > 宿主侧告警优先级写作 **告警P1**(勿与 Playbook **P1** 账户诊断混淆)。
6
6
 
7
- ## Contents
8
-
9
- - 预算与 ROI 自控
10
- - 异常监控
11
- - 自动优化
7
+ `guard` 与自动暂停/改价**只支持 Google**。Bing / Yandex 只能拉数告警,步骤见各自 SOP。
12
8
 
13
9
  ---
14
10
 
15
- ## 预算与 ROI 自控
11
+ ## Google:预算与 ROI 自控
16
12
 
17
- | 场景 | 自动动作(摘要) | 首选 CLI / SOP(只读一份) |
18
- | ---------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
19
- | 单日预算熔断 | 当日花费相对日预算达阈值 → 暂停系列 | **首选** `references/operations/guard.md`(`guard budget-circuit`);细节/单户排障见 `hosted-automation-self-control.md` |
20
- | CPA 飙升自动降价 | 窗内 CPA 超目标 → 下调出价上限 | `references/operations/hosted-automation-optimize-weak-downbid.md` |
21
- | 连续空耗自动暂停 | 当日花费达阈值且 0 转化 → 暂停组/创意;宿主发 **告警P1**「空耗熔断」 | **首选** `references/operations/guard.md`(`guard zero-conv`);细节见 `hosted-automation-self-control.md` |
13
+ | 场景 | 自动动作(摘要) | 首选 CLI / SOP(只读一份) |
14
+ | ---------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
15
+ | 单日预算熔断 | 当日花费相对日预算达阈值 → **列出命中**;暂停须逐账户 `ad campaign-status` | **首选** `references/operations/guard.md`(`guard budget-circuit`);细节/单户排障见 `hosted-automation-self-control.md` |
16
+ | CPA 飙升自动降价 | 窗内 CPA 超目标 → 下调出价上限 | `references/operations/hosted-automation-optimize-weak-downbid.md` |
17
+ | 连续空耗自动暂停 | 当日花费达阈值且 0 转化 → **列出命中**;暂停须逐账户 `adgroup-status`/`ad-status`;宿主发 **告警P1**「空耗熔断」 | **首选** `references/operations/guard.md`(`guard zero-conv`);细节见 `hosted-automation-self-control.md` |
22
18
 
23
19
  > **消耗 ≠ 日消耗**:`spend` 为 `--start`~`--end` 合计。`guard` 命令已强制 `--date` 单日;手写 `ad campaigns` 时须 `start=end=当日`。续航日均用 `balance-scan.dailySpend`。
24
20
 
25
- ## 异常监控
21
+ ## Google:异常监控
26
22
 
27
23
  字段键名以当次 `--json-out` 为准,细则只读:`references/operations/hosted-automation-monitoring-json.md`。
28
24
 
@@ -34,7 +30,7 @@
34
30
  | 花费异动 | `google-analysis --sections campaign-hour` |
35
31
  | 余额枯竭 | `balance-scan`(P2) |
36
32
 
37
- ## 自动优化
33
+ ## Google:自动优化
38
34
 
39
35
  索引(可选):`references/operations/hosted-automation-optimize-index.md`。执行时直接读 SOP:
40
36
 
@@ -44,3 +40,28 @@
44
40
  | 高转化提价扩量 | `references/operations/hosted-automation-optimize-scale.md` |
45
41
  | A/B 决出胜者 | `references/operations/hosted-automation-optimize-ab-winner.md` |
46
42
  | 异动根因排查 | `references/analytics/account-analytics.md` + 对应 `report-templates/*.md` |
43
+
44
+ ---
45
+
46
+ ## Bing(BingV2):只读巡检
47
+
48
+ 全部步骤只读一份:`references/operations/hosted-automation-bing.md`。**不能**自动暂停/改价/改预算。
49
+
50
+ | 场景 | 做什么 | SOP |
51
+ | -------------- | ---------------------------------------------------------------------- | ------------------------------------------------ |
52
+ | 余额 / IO 续航 | `balance-scan -m BingV2`;有 IO 再 `insertion-orders` + `monthly-spend` | `references/operations/hosted-automation-bing.md` |
53
+ | 账户封禁 | `bing-analysis account-status`;非 Active 告警 | 同上 |
54
+ | 素材拒审 | `bing-analysis bulk-ads --editorial-status Disapproved` | 同上 |
55
+ | 落地页死链 | `bulk-ads` 拉 URL,宿主 HTTP 探活 | 同上 |
56
+ | 当日超预算 | `campaign-entities` 日预算 + `run --aggregation Daily` 当日花费;只告警 | 同上 |
57
+ | CPA / 空耗 | `run --aggregation Daily\|Hourly`;只告警,不降价、不暂停 | 同上 |
58
+
59
+ ## Yandex:只读巡检
60
+
61
+ 全部步骤只读一份:`references/operations/hosted-automation-yandex.md`。**不能**自动改投放;无封禁/拒审/落地页/小时接口。
62
+
63
+ | 场景 | 做什么 | SOP |
64
+ | -------------------- | ---------------------------------------------------------------------- | -------------------------------------------------- |
65
+ | 余额续航 | `balance-scan -m Yandex` | `references/operations/hosted-automation-yandex.md` |
66
+ | 多账户 CPA / 零转化 | `accounts-digest -m Yandex`(截至昨天) | 同上 |
67
+ | 当日/近日花费与 CPA | `yandex-analysis --sections overview,daily,campaigns`(可含今天) | 同上 |
@@ -0,0 +1,83 @@
1
+ # 宿主编排:Yandex 只读巡检
2
+
3
+ > **何时 Read**:用户要 Yandex / 直通广告自动化、巡检、熔断、余额或 CPA 预警。Google 熔断走 `references/operations/guard.md`;Bing 走 `hosted-automation-bing.md`。
4
+ > **能做什么**:拉余额、拉日报/周期报表、宿主按阈值告警。
5
+ > **不能做什么**:暂停、改价、改预算、封禁专用接口、拒审列表、落地页列表、小时花费。没有写命令,**禁止**假装已执行熔断。
6
+
7
+ **前置**:已 `siluzan-tso login`,并用 `list-accounts -m Yandex --json-out ./snap` 确认 `mediaCustomerId`(形如 `porg-…`,**不是**纯数字,也不是 `entityId`)。
8
+
9
+ **硬约束**:
10
+
11
+ - `guard` **只支持 Google**。禁止 `guard … -m Yandex`。
12
+ - 没有 `account-status` / `bulk-ads` / `campaign-entities` 这类实体子命令。
13
+ - `yandex-analysis` 日期不能晚于今天;省略则默认近 7 天**含今天**。`search-terms` 只支持近 180 天。
14
+ - `balance-scan` / `accounts-digest` 的消耗是每日同步**截至昨天**;查当天消耗用 `yandex-analysis`,不要用 `stats` 判断今天。
15
+
16
+ ---
17
+
18
+ ## 1. 余额续航
19
+
20
+ ```bash
21
+ siluzan-tso balance-scan -m Yandex --threshold-days 7 --json-out ./snap-yandex
22
+ ```
23
+
24
+ 已知子集加 `-a porg-xxx,porg-yyy`。命中续航/余额不足 → 宿主通知充值。CLI **不能**自动充值。单户复核:
25
+
26
+ ```bash
27
+ siluzan-tso balance -m Yandex -a <porg-xxx> --json-out ./snap-yandex
28
+ ```
29
+
30
+ ---
31
+
32
+ ## 2. 多账户消耗 / CPA / 零转化(截至昨天)
33
+
34
+ 一条命令扫该媒体全部户(或 `-a` 子集)。区间必须是已结束的日期,**不要把今天当完整日**:
35
+
36
+ ```bash
37
+ siluzan-tso accounts-digest -m Yandex --start <S> --end <D> --json-out ./snap-yandex
38
+ siluzan-tso accounts-digest -m Yandex --start <S> --end <D> --zero-conversions --json-out ./snap-yandex
39
+ siluzan-tso accounts-digest -m Yandex --start <S> --end <D> --max-cpa <目标CPA> --json-out ./snap-yandex
40
+ ```
41
+
42
+ 命中 → 宿主告警账户、区间花费、转化、CPA。不能自动关停。
43
+
44
+ ---
45
+
46
+ ## 3. 单户当日 / 近日花费与 CPA(可含今天)
47
+
48
+ ```bash
49
+ mkdir -p ./snap-yandex
50
+ siluzan-tso yandex-analysis -a <porg-xxx> --json-out ./snap-yandex \
51
+ --start <当日或近几日> --end <当日> --sections overview,daily,campaigns
52
+ ```
53
+
54
+ 宿主侧:
55
+
56
+ - **日花费异动**:读 `daily` 按日对比前一日或近 7 日均值,超阈值告警。
57
+ - **系列花费 / 预算**:同一窗口读 `campaigns`,用落盘里的花费对照 `budget`;缺字段就不要猜,改用日环比。
58
+ - **CPA**:`花费 / 转化`(转化为 0 不算 CPA,改走空耗判断)。超过约定目标倍数 → 告警。
59
+ - **空耗**:当日(或约定日)花费已达「目标 CPA × N」且转化为 0 → 告警「空耗预警」,文案不要写成「已熔断」。目标 CPA 用落盘 `targetCpa` 或账户约定。
60
+
61
+ 没有小时维度,做不了「近两小时 CPA」。
62
+
63
+ ---
64
+
65
+ ## 4. 周期报告(给人看,不代替巡检告警)
66
+
67
+ ```bash
68
+ siluzan-tso yandex-analysis -a <porg-xxx> --start <S> --end <D> --json-out ./snap-yandex
69
+ ```
70
+
71
+ 出 HTML 按 `report-templates/yandex-period-report.md` 写 narrative 后再 `yandex-analysis render`。自动化任务里报告是可选交付物;阈值告警仍走上面 1–3 步。
72
+
73
+ ---
74
+
75
+ ## 做不到(勿编造命令)
76
+
77
+ | 场景 | 现状 |
78
+ | -------------- | ----------------------------------------- |
79
+ | 账户封禁专用 | 无;`list-accounts` 状态仅供人工看 |
80
+ | 素材拒审 | 无拒审列表 |
81
+ | 落地页探活 | 无最终到达网址接口 |
82
+ | 小时花费 | 无 |
83
+ | 自动暂停 / 改价 | 无写接口;告警后人工在 Yandex 后台处理 |
@@ -19,7 +19,7 @@
19
19
  | `geo` | `{ …, items: GeoRow[] }` | → `tables.geo[]` |
20
20
  | `devices` | `{ …, items: DeviceRow[] }` | → `tables.devices[]` |
21
21
 
22
- > 金额已为账户币种小数;`ctr` / `conversionRate` 为 **0~1 小数**。写叙事前先读各维 `*.outline.txt`。
22
+ > 金额已为账户币种小数;`ctr` / `conversionRate` 为 **0~1 小数**。`roi` 为 **(收益-消耗)/消耗** 的倍数(如 `3.72` 表示利润约 372%),**不是** 0~1,禁止再 ×100;无收益时常为 `-1`(按缺失)。写叙事前先读各维 `*.outline.txt`。
23
23
 
24
24
  ---
25
25
 
@@ -492,6 +492,17 @@
492
492
  return (n * 100).toFixed(2) + "%";
493
493
  }
494
494
 
495
+ /**
496
+ * 后端 roi = (Revenue - Spend) / Spend,已是倍数(如 3.72 = 372% 利润),
497
+ * **不是** CTR 那种 0~1。禁止走 pct(),否则 371.96 会显示成 37195.98%。
498
+ * 无收益时网关算成 -1,展示为 —。
499
+ */
500
+ function fmtRoi(v) {
501
+ const n = toNum(v);
502
+ if (n === undefined || n === -1) return "—";
503
+ return n.toFixed(2) + "%";
504
+ }
505
+
495
506
  function num2(v) {
496
507
  const n = toNum(v);
497
508
  return n === undefined ? "—" : n.toFixed(2);
@@ -1158,7 +1169,7 @@
1158
1169
  '</td><td class="num">' +
1159
1170
  money(r.costPerConversion, currency) +
1160
1171
  '</td><td class="num">' +
1161
- pct(r.roi) +
1172
+ fmtRoi(r.roi) +
1162
1173
  '</td><td class="num">' +
1163
1174
  pct(r.ctr) +
1164
1175
  "</td><td>" +
@@ -19,7 +19,7 @@
19
19
  | `geo` | `{ …, items: GeoRow[] }` | → `tables.geo[]` |
20
20
  | `devices` | `{ …, items: DeviceRow[] }` | → `tables.devices[]` |
21
21
 
22
- > 金额已为账户币种小数;`ctr` / `conversionRate` 为 **0~1 小数**。写叙事前先读各维 `*.outline.txt`。
22
+ > 金额已为账户币种小数;`ctr` / `conversionRate` 为 **0~1 小数**。`roi` 为 **(收益-消耗)/消耗** 的倍数(如 `3.72` 表示利润约 372%),**不是** 0~1,禁止再 ×100;无收益时常为 `-1`(按缺失)。写叙事前先读各维 `*.outline.txt`。
23
23
 
24
24
  ---
25
25
 
@@ -9,7 +9,7 @@ $ErrorActionPreference = 'Stop'
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  $PKG_NAME = 'siluzan-tso-cli'
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- $PKG_VERSION = '1.1.46-beta.2'
12
+ $PKG_VERSION = '1.1.46-beta.6'
13
13
  $CLI_BIN = 'siluzan-tso'
14
14
  $SKILL_LABEL = 'Siluzan TSO'
15
15
  $INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
@@ -9,7 +9,7 @@ set -euo pipefail
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  readonly PKG_NAME="siluzan-tso-cli"
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- readonly PKG_VERSION="1.1.46-beta.2"
12
+ readonly PKG_VERSION="1.1.46-beta.6"
13
13
  readonly CLI_BIN="siluzan-tso"
14
14
  readonly SKILL_LABEL="Siluzan TSO"
15
15
  readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
@@ -4,7 +4,7 @@
4
4
  "turns": [
5
5
  "检查我名下的所有广告账户下所有广告系列的当日花费。如果某个系列的当日花费达到其日预算的 110% 以上,先列出被命中的系列名称、当日花费和日预算(先不要暂停)。用 siluzan-tso,JSON 落盘。这是定时任务,直接执行。"
6
6
  ],
7
- "judgeExpectation": "路径:用户要求检查名下所有账户系列当日花费相对日预算达 110% 并列出/暂停时,应执行 `siluzan-tso guard budget-circuit -m Google`(可带 --date/--ratio/--json-out),**不要**对六媒体 list-accounts 后再逐户 `ad campaigns`。\n默认 dry-run 即可交付命中表;未获确认前可不加 --apply。输出须收口(命中列表或明确无命中)。",
7
+ "judgeExpectation": "路径:用户要求检查名下所有账户系列当日花费相对日预算达 110% 并列出命中时,应执行 `siluzan-tso guard budget-circuit -m Google`(可带 --date/--ratio/--json-out),**不要**对六媒体 list-accounts 后再逐户 `ad campaigns`。\n`guard` 只扫描不暂停;若用户随后要求停某一条,须对该账户单独执行 `ad campaign-status --status Paused --commit`。输出须收口(命中列表或明确无命中)。",
8
8
  "skillMapping": "references/operations/guard.md · budget-circuit",
9
9
  "judgeReferencePaths": [
10
10
  "references/operations/guard.md",
@@ -4,7 +4,7 @@
4
4
  "turns": [
5
5
  "检查我名下的所有广告账户的当日数据。对于当日累计花费已达到目标 CPA 3 倍以上、但转化数为 0 的广告组,先列出(标注「空耗熔断」),先不要暂停。用 siluzan-tso,JSON 落盘,直接执行。"
6
6
  ],
7
- "judgeExpectation": "路径:用户要求空耗熔断(当日花费 ≥ 目标 CPA × 3 且转化 0)时,应执行 `siluzan-tso guard zero-conv -m Google`(--cpa-multiple 3、--json-out),结果摘要应体现「空耗熔断」。\n默认 dry-run;未确认前可不 --apply。禁止逐户 for-loop `ad groups`。",
7
+ "judgeExpectation": "路径:用户要求空耗熔断(当日花费 ≥ 目标 CPA × 3 且转化 0)时,应执行 `siluzan-tso guard zero-conv -m Google`(--cpa-multiple 3、--json-out),结果摘要应体现「空耗熔断」。\n`guard` 只扫描不暂停;禁止 `--apply`。禁止逐户 for-loop `ad groups`。",
8
8
  "skillMapping": "references/operations/guard.md · zero-conv",
9
9
  "judgeReferencePaths": ["references/operations/guard.md"],
10
10
  "commandMustInclude": [["guard", "zero-conv"], ["--json-out"]]
@@ -7,7 +7,6 @@
7
7
  "media": "Google",
8
8
  "date": "2026-07-28",
9
9
  "ratio": 1.1,
10
- "apply": false,
11
10
  "stub": true
12
11
  },
13
12
  "summary": {
@@ -15,9 +14,7 @@
15
14
  "accountCount": 1,
16
15
  "checkedCampaigns": 2,
17
16
  "hitCount": 1,
18
- "pausedCount": 0,
19
- "errorCount": 0,
20
- "mode": "dry-run"
17
+ "errorCount": 0
21
18
  },
22
19
  "hits": [
23
20
  {
@@ -27,10 +24,8 @@
27
24
  "spend": 120,
28
25
  "budget": 100,
29
26
  "ratio": 1.1,
30
- "threshold": 110,
31
- "applied": false
27
+ "threshold": 110
32
28
  }
33
29
  ],
34
- "paused": [],
35
30
  "errors": []
36
31
  }
@@ -8,7 +8,6 @@
8
8
  "date": "2026-07-28",
9
9
  "cpaMultiple": 3,
10
10
  "level": "adgroup",
11
- "apply": false,
12
11
  "stub": true
13
12
  },
14
13
  "summary": {
@@ -16,10 +15,8 @@
16
15
  "accountCount": 1,
17
16
  "checkedObjects": 2,
18
17
  "hitCount": 1,
19
- "pausedCount": 0,
20
18
  "skippedCount": 0,
21
- "errorCount": 0,
22
- "mode": "dry-run"
19
+ "errorCount": 0
23
20
  },
24
21
  "hits": [
25
22
  {
@@ -31,11 +28,9 @@
31
28
  "conversions": 0,
32
29
  "targetCpaYuan": 100,
33
30
  "multiple": 3,
34
- "threshold": 300,
35
- "applied": false
31
+ "threshold": 300
36
32
  }
37
33
  ],
38
- "paused": [],
39
34
  "skipped": [],
40
35
  "errors": []
41
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.46-beta.2",
3
+ "version": "1.1.46-beta.6",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",