siluzan-tso-cli 1.1.48-beta.8 → 1.1.48-beta.9

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.48-beta.8),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.48-beta.9),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -103520,8 +103520,8 @@ var init_batch_runner = __esm({
103520
103520
  shouldDowngrade(now = Date.now()) {
103521
103521
  if (this.window.length < this.opts.windowSize) return false;
103522
103522
  if (now - this.lastDowngradeAt < this.opts.cooldownMs) return false;
103523
- const fail3 = this.window.filter((s) => !s).length;
103524
- const rate = fail3 / this.window.length;
103523
+ const fail4 = this.window.filter((s) => !s).length;
103524
+ const rate = fail4 / this.window.length;
103525
103525
  if (rate >= this.opts.errorRateThreshold) {
103526
103526
  this.lastDowngradeAt = now;
103527
103527
  this.window.length = 0;
@@ -123849,9 +123849,9 @@ function xfXml(x) {
123849
123849
  function stylesXml(reg) {
123850
123850
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="${reg.fonts.length}">${reg.fonts.map(fontXml).join("")}</fonts><fills count="${reg.fills.length}">${reg.fills.map(fillXml).join("")}</fills><borders count="${reg.borders.length}">${reg.borders.map(borderXml).join("")}</borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="${reg.xfs.length}">${reg.xfs.map(xfXml).join("")}</cellXfs><cellStyles count="1"><cellStyle name="\u5E38\u89C4" xfId="0" builtinId="0"/></cellStyles></styleSheet>`;
123851
123851
  }
123852
- function cellXml(ref, cell2) {
123853
- if (cell2 == null) return "";
123854
- const { value, styleId } = cell2;
123852
+ function cellXml(ref, cell3) {
123853
+ if (cell3 == null) return "";
123854
+ const { value, styleId } = cell3;
123855
123855
  const sAttr = styleId ? ` s="${styleId}"` : "";
123856
123856
  if (value == null || value === "") {
123857
123857
  return styleId ? `<c r="${ref}"${sAttr}/>` : "";
@@ -123866,7 +123866,7 @@ function sheetXml(sheet) {
123866
123866
  const rowsXml = sheet.rows.map((row, i) => {
123867
123867
  const r = i + 1;
123868
123868
  const htAttr = row.height ? ` ht="${row.height}" customHeight="1"` : "";
123869
- const cells = row.cells.map((cell2, cIdx) => cellXml(`${colLetter(cIdx)}${r}`, cell2)).join("");
123869
+ const cells = row.cells.map((cell3, cIdx) => cellXml(`${colLetter(cIdx)}${r}`, cell3)).join("");
123870
123870
  return `<row r="${r}"${htAttr}>${cells}</row>`;
123871
123871
  }).join("");
123872
123872
  const mergesXml = sheet.merges.length ? `<mergeCells count="${sheet.merges.length}">${sheet.merges.map((m) => `<mergeCell ref="${m}"/>`).join("")}</mergeCells>` : "";
@@ -133577,10 +133577,10 @@ function estimateColWidths(rows) {
133577
133577
  const colCount = rows.reduce((max, row) => Math.max(max, row.length), 0);
133578
133578
  const widths = new Array(colCount).fill(MIN_COL_WIDTH);
133579
133579
  for (const row of rows) {
133580
- row.forEach((cell2, idx) => {
133581
- if (cell2 == null) return;
133580
+ row.forEach((cell3, idx) => {
133581
+ if (cell3 == null) return;
133582
133582
  const longestLine = Math.max(
133583
- ...String(cell2).split("\n").map((l) => l.length)
133583
+ ...String(cell3).split("\n").map((l) => l.length)
133584
133584
  );
133585
133585
  const width = Math.min(MAX_COL_WIDTH, Math.max(MIN_COL_WIDTH, Math.ceil(longestLine * 1.3)));
133586
133586
  if (width > widths[idx]) widths[idx] = width;
@@ -139411,7 +139411,472 @@ async function runYandexAnalysisRender(opts) {
139411
139411
  console.log("\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u5B8C\u6574\u7AE0\u8282\uFF08\u6837\u5F0F\u5BF9\u9F50\u5F00\u53D1\u6837\u4F8B\uFF09\u3002\n");
139412
139412
  }
139413
139413
 
139414
+ // src/commands/yandex-analysis/entities.ts
139415
+ init_auth();
139416
+ init_cli_json_snapshot();
139417
+ init_cli_table();
139418
+
139419
+ // src/commands/yandex-analysis/yandex-api.ts
139420
+ init_auth();
139421
+ var YANDEX_AD_STATUSES = [
139422
+ "DRAFT",
139423
+ "MODERATION",
139424
+ "PREACCEPTED",
139425
+ "ACCEPTED",
139426
+ "REJECTED",
139427
+ "UNKNOWN"
139428
+ ];
139429
+ var YANDEX_INSIGHTS_LEVELS = [
139430
+ "daily",
139431
+ "geo",
139432
+ "devices",
139433
+ "keywords",
139434
+ "campaigns",
139435
+ "adgroups",
139436
+ "ads"
139437
+ ];
139438
+ var YANDEX_PATH_CAPS = {
139439
+ overview: {},
139440
+ daily: {},
139441
+ campaigns: { limit: true, timeIncrement: true, network: true },
139442
+ adgroups: { limit: true, timeIncrement: true, campaignId: true },
139443
+ ads: { limit: true, timeIncrement: true, status: true, campaignId: true, adGroupId: true },
139444
+ keywords: { limit: true },
139445
+ geo: { limit: true, network: true },
139446
+ devices: { network: true }
139447
+ };
139448
+ function localYmd(d = /* @__PURE__ */ new Date()) {
139449
+ const y = d.getFullYear();
139450
+ const m = String(d.getMonth() + 1).padStart(2, "0");
139451
+ const day = String(d.getDate()).padStart(2, "0");
139452
+ return `${y}-${m}-${day}`;
139453
+ }
139454
+ function parseYandexTimeIncrement(raw) {
139455
+ if (raw == null || String(raw).trim() === "") return void 0;
139456
+ const t = String(raw).trim();
139457
+ if (t !== "1") {
139458
+ throw new Error("--time-increment \u53EA\u5141\u8BB8 1\uFF08\u6309\u65E5\uFF09\uFF1B\u4E0D\u4F20=\u533A\u95F4\u6C47\u603B\u3002Direct \u6CA1\u6709\u5C0F\u65F6\u7EF4\u5EA6\u3002");
139459
+ }
139460
+ return 1;
139461
+ }
139462
+ function parseYandexNetwork(raw) {
139463
+ if (raw == null || String(raw).trim() === "") return void 0;
139464
+ const t = String(raw).trim().toUpperCase();
139465
+ if (t !== "SEARCH" && t !== "ALL") {
139466
+ throw new Error(
139467
+ "--network \u53EA\u5141\u8BB8 SEARCH \u6216 ALL\uFF08\u9ED8\u8BA4 SEARCH\uFF1BALL=\u5168\u7F51\u4E00\u884C\u5408\u8BA1\uFF0C\u884C\u5185 network \u4E3A null\uFF09\u3002"
139468
+ );
139469
+ }
139470
+ return t;
139471
+ }
139472
+ function parseYandexAdStatus(raw) {
139473
+ if (raw == null || String(raw).trim() === "") return void 0;
139474
+ const parts = String(raw).split(",").map((x) => x.trim().toUpperCase()).filter(Boolean);
139475
+ if (parts.length === 0) return void 0;
139476
+ const unknown = parts.filter((p) => !YANDEX_AD_STATUSES.includes(p));
139477
+ if (unknown.length > 0) {
139478
+ throw new Error(
139479
+ `--status \u975E\u6CD5\u503C\uFF1A${unknown.join(", ")}\u3002\u5408\u6CD5\uFF1A${YANDEX_AD_STATUSES.join(", ")}\u3002`
139480
+ );
139481
+ }
139482
+ return [...new Set(parts)].join(",");
139483
+ }
139484
+ function parseYandexInsightsLevel(raw) {
139485
+ const t = raw.trim();
139486
+ if (!YANDEX_INSIGHTS_LEVELS.includes(t)) {
139487
+ throw new Error(
139488
+ `--level \u53EA\u5141\u8BB8 ${YANDEX_INSIGHTS_LEVELS.join(" / ")}\uFF08daily/geo/devices/keywords \u6216\u4E0E\u5B9E\u4F53\u5B50\u547D\u4EE4\u540C\u53E3\u5F84\u7684 campaigns/adgroups/ads\uFF09\u3002`
139489
+ );
139490
+ }
139491
+ return t;
139492
+ }
139493
+ function optionalId(raw) {
139494
+ const t = raw?.trim();
139495
+ return t ? t : void 0;
139496
+ }
139497
+ function insightsLevelToPath(level) {
139498
+ return level;
139499
+ }
139500
+ function assertYandexAnalysisQuery(path49, query) {
139501
+ const caps = YANDEX_PATH_CAPS[path49];
139502
+ if (query.timeIncrement != null && !caps.timeIncrement) {
139503
+ throw new Error(`--time-increment \u4E0D\u80FD\u7528\u4E8E /${path49}\uFF08\u4EC5 campaigns / adgroups / ads\uFF09\u3002`);
139504
+ }
139505
+ if (query.network != null && !caps.network) {
139506
+ throw new Error(`--network \u4E0D\u80FD\u7528\u4E8E /${path49}\uFF08\u4EC5 campaigns / geo / devices\uFF09\u3002`);
139507
+ }
139508
+ if (query.status != null && !caps.status) {
139509
+ throw new Error(`--status \u4EC5\u7528\u4E8E /ads\uFF08\u62D2\u5BA1/\u5BA1\u6838\u6001\u8D70\u7ED3\u6784\u67E5\u8BE2\uFF0C\u884C\u5185 spend \u4E3A 0\uFF09\u3002`);
139510
+ }
139511
+ if (query.campaignId != null && !caps.campaignId) {
139512
+ throw new Error(`--campaign-id \u4EC5\u7528\u4E8E /adgroups \u4E0E /ads\u3002`);
139513
+ }
139514
+ if (query.adGroupId != null && !caps.adGroupId) {
139515
+ throw new Error(`--adgroup-id \u4EC5\u7528\u4E8E /ads\u3002`);
139516
+ }
139517
+ if (query.status != null && query.timeIncrement != null) {
139518
+ throw new Error(
139519
+ "--status \u4E0E --time-increment \u4E0D\u80FD\u540C\u65F6\u4F20\uFF08\u7F51\u5173 400\uFF09\u3002\u62D2\u5BA1\u7528\u533A\u95F4\u6C47\u603B\uFF0C\u4E0D\u8981\u52A0\u6309\u65E5\u3002"
139520
+ );
139521
+ }
139522
+ if (query.limit != null && !caps.limit) {
139523
+ throw new Error(`--limit \u4E0D\u80FD\u7528\u4E8E /${path49}\uFF08overview / daily / devices \u65E0\u6B64\u53C2\u6570\uFF09\u3002`);
139524
+ }
139525
+ }
139526
+ function buildYandexAnalysisSearchParams(path49, query) {
139527
+ assertYandexAnalysisQuery(path49, query);
139528
+ const caps = YANDEX_PATH_CAPS[path49];
139529
+ const params = new URLSearchParams({
139530
+ account: query.account,
139531
+ start: query.start,
139532
+ end: query.end
139533
+ });
139534
+ if (caps.limit && query.limit != null) {
139535
+ params.set("limit", String(resolveYandexLimit(query.limit)));
139536
+ }
139537
+ if (caps.timeIncrement && query.timeIncrement === 1) {
139538
+ params.set("timeIncrement", "1");
139539
+ }
139540
+ if (caps.network && query.network) {
139541
+ params.set("network", query.network);
139542
+ }
139543
+ if (caps.status && query.status) {
139544
+ params.set("status", query.status);
139545
+ }
139546
+ if (caps.campaignId && query.campaignId) {
139547
+ params.set("campaignId", query.campaignId);
139548
+ }
139549
+ if (caps.adGroupId && query.adGroupId) {
139550
+ params.set("adGroupId", query.adGroupId);
139551
+ }
139552
+ return params;
139553
+ }
139554
+ async function fetchYandexAnalysisPath(config, path49, query, verbose) {
139555
+ const yandexApiUrl = requireYandexApi(config);
139556
+ const params = buildYandexAnalysisSearchParams(path49, query);
139557
+ const url = analysisUrl(yandexApiUrl, path49, params.toString());
139558
+ return apiFetch2(url, config, {}, verbose);
139559
+ }
139560
+ function unwrapYandexItems(raw) {
139561
+ if (raw && typeof raw === "object" && Array.isArray(raw.items)) {
139562
+ return raw.items;
139563
+ }
139564
+ return [];
139565
+ }
139566
+ function asRecord31(raw) {
139567
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
139568
+ }
139569
+
139570
+ // src/commands/yandex-analysis/entities.ts
139571
+ function fail3(label, err) {
139572
+ const msg = err instanceof Error ? err.message : String(err);
139573
+ console.error(`
139574
+ \u274C ${label}\uFF1A${msg}
139575
+ `);
139576
+ process.exit(1);
139577
+ }
139578
+ function cell2(value) {
139579
+ if (value == null || value === "") return "";
139580
+ if (Array.isArray(value)) return value.map((x) => String(x)).join(" | ");
139581
+ return String(value);
139582
+ }
139583
+ function resolveRange(start, end) {
139584
+ if (!start && !end) {
139585
+ const today = localYmd();
139586
+ return resolveYandexDateRange(today, today);
139587
+ }
139588
+ return resolveYandexDateRange(start, end);
139589
+ }
139590
+ function parseCommonFlags(opts) {
139591
+ return {
139592
+ timeIncrement: parseYandexTimeIncrement(opts.timeIncrement),
139593
+ network: parseYandexNetwork(opts.network),
139594
+ status: parseYandexAdStatus(opts.status),
139595
+ campaignId: optionalId(opts.campaignId),
139596
+ adGroupId: optionalId(opts.adGroupId)
139597
+ };
139598
+ }
139599
+ async function emitOrTable(opts, args) {
139600
+ if (await emitCliJsonOrSnapshot(opts, {
139601
+ section: args.section,
139602
+ commandLabel: args.commandLabel,
139603
+ commandHint: args.accountId,
139604
+ payload: args.payload,
139605
+ idSuffix: args.accountId
139606
+ })) {
139607
+ return;
139608
+ }
139609
+ console.log(`
139610
+ ${args.title}
139611
+ `);
139612
+ if (args.rows.length === 0) {
139613
+ console.log(` ${args.empty}
139614
+ `);
139615
+ return;
139616
+ }
139617
+ printCliTable(args.rows, args.columns);
139618
+ console.log(` ${args.footnote}
139619
+ `);
139620
+ }
139621
+ async function fetchPath(opts, path49, extra) {
139622
+ const accountId = assertYandexAccountId(opts.account);
139623
+ const { startDate, endDate } = resolveRange(opts.start, opts.end);
139624
+ const config = loadConfig(opts.token);
139625
+ const raw = await fetchYandexAnalysisPath(
139626
+ config,
139627
+ path49,
139628
+ {
139629
+ account: accountId,
139630
+ start: startDate,
139631
+ end: endDate,
139632
+ ...extra
139633
+ },
139634
+ !!opts.verbose
139635
+ );
139636
+ return { accountId, startDate, endDate, raw };
139637
+ }
139638
+ async function runYandexAccountStatus(opts) {
139639
+ let result;
139640
+ try {
139641
+ result = await fetchPath(opts, "overview", {});
139642
+ } catch (err) {
139643
+ fail3("\u67E5\u8BE2 Yandex \u8D26\u6237\u603B\u89C8\u5931\u8D25", err);
139644
+ }
139645
+ const row = asRecord31(result.raw);
139646
+ await emitOrTable(opts, {
139647
+ section: "yandex-account-status",
139648
+ commandLabel: "yandex-analysis account-status",
139649
+ accountId: result.accountId,
139650
+ payload: result.raw,
139651
+ title: `Yandex \u8D26\u6237\u603B\u89C8\uFF08${result.accountId}\uFF0C${result.startDate}~${result.endDate}\uFF09`,
139652
+ empty: "\u65E0\u603B\u89C8\u6570\u636E\u3002",
139653
+ footnote: "archived=true \u8868\u793A\u5E7F\u544A\u4E3B\u5DF2\u5F52\u6863\uFF08inactive\uFF09\u3002\u5B98\u65B9\u65E0\u5C01\u6237\u72B6\u6001\u4E0E\u539F\u56E0\u3002\u4F59\u989D\u770B balance / currency\uFF1B\u5B98\u65B9\u65E0\u72EC\u7ACB\u989D\u5EA6\u3002",
139654
+ rows: [
139655
+ {
139656
+ account: cell2(row.account ?? result.accountId),
139657
+ archived: cell2(row.archived),
139658
+ balance: cell2(row.balance),
139659
+ currency: cell2(row.currency),
139660
+ spend: cell2(row.spend),
139661
+ conversions: cell2(row.conversions)
139662
+ }
139663
+ ],
139664
+ columns: [
139665
+ { key: "account", header: "\u8D26\u6237" },
139666
+ { key: "archived", header: "archived" },
139667
+ { key: "balance", header: "\u4F59\u989D" },
139668
+ { key: "currency", header: "\u5E01\u79CD" },
139669
+ { key: "spend", header: "\u533A\u95F4\u82B1\u8D39" },
139670
+ { key: "conversions", header: "\u8F6C\u5316" }
139671
+ ]
139672
+ });
139673
+ }
139674
+ async function runYandexCampaignEntities(opts) {
139675
+ let flags;
139676
+ let result;
139677
+ try {
139678
+ flags = parseCommonFlags(opts);
139679
+ result = await fetchPath(opts, "campaigns", {
139680
+ limit: opts.limit,
139681
+ timeIncrement: flags.timeIncrement,
139682
+ network: flags.network
139683
+ });
139684
+ } catch (err) {
139685
+ fail3("\u67E5\u8BE2 Yandex \u7CFB\u5217\u5931\u8D25", err);
139686
+ }
139687
+ const items2 = unwrapYandexItems(result.raw);
139688
+ await emitOrTable(opts, {
139689
+ section: "yandex-campaign-entities",
139690
+ commandLabel: "yandex-analysis campaign-entities",
139691
+ accountId: result.accountId,
139692
+ payload: result.raw,
139693
+ title: `Yandex \u5E7F\u544A\u7CFB\u5217\uFF08${result.accountId}\uFF0C${result.startDate}~${result.endDate}\uFF0C\u5171 ${items2.length} \u884C\uFF09`,
139694
+ empty: "\u5F53\u524D\u8FC7\u6EE4\u4E0B\u65E0\u7CFB\u5217\u884C\uFF08\u9ED8\u8BA4\u53EA\u51FA\u73B0\u6709\u6D88\u8017\u7684\u5BF9\u8C61\uFF09\u3002",
139695
+ footnote: "\u9ED8\u8BA4 SEARCH\u3002\u5168\u6E20\u9053\u82B1\u8D39\u987B --network ALL\uFF08\u884C\u5185 network \u4E3A null\uFF09\u3002budget \u662F\u7CFB\u5217\u65E5\u9884\u7B97\uFF0C\u81EA\u52A8\u7B56\u7565\u5E38\u4E3A\u7A7A\uFF1B\u5BF9\u7167 weeklyBudget\u3002\u81EA\u52A8\u51FA\u4EF7\u4E0D\u8981\u5F53\u624B\u52A8 CPC \u6539\u4EF7\u3002",
139696
+ rows: items2.map((r) => ({
139697
+ campaignId: cell2(r.campaignId),
139698
+ name: cell2(r.campaignName),
139699
+ spend: cell2(r.spend),
139700
+ budget: cell2(r.budget),
139701
+ weeklyBudget: cell2(r.weeklyBudget),
139702
+ strategy: cell2(r.strategy),
139703
+ targetCpa: cell2(r.targetCpa),
139704
+ status: cell2(r.status),
139705
+ date: cell2(r.date)
139706
+ })),
139707
+ columns: [
139708
+ { key: "campaignId", header: "\u7CFB\u5217ID" },
139709
+ { key: "name", header: "\u540D\u79F0" },
139710
+ { key: "spend", header: "spend" },
139711
+ { key: "budget", header: "\u65E5\u9884\u7B97" },
139712
+ { key: "weeklyBudget", header: "\u5468\u9884\u7B97" },
139713
+ { key: "strategy", header: "\u7B56\u7565" },
139714
+ { key: "targetCpa", header: "\u76EE\u6807CPA" },
139715
+ { key: "status", header: "\u72B6\u6001" },
139716
+ { key: "date", header: "date" }
139717
+ ]
139718
+ });
139719
+ }
139720
+ async function runYandexAdGroupEntities(opts) {
139721
+ let flags;
139722
+ let result;
139723
+ try {
139724
+ flags = parseCommonFlags(opts);
139725
+ result = await fetchPath(opts, "adgroups", {
139726
+ limit: opts.limit,
139727
+ timeIncrement: flags.timeIncrement,
139728
+ campaignId: flags.campaignId
139729
+ });
139730
+ } catch (err) {
139731
+ fail3("\u67E5\u8BE2 Yandex \u5E7F\u544A\u7EC4\u5931\u8D25", err);
139732
+ }
139733
+ const items2 = unwrapYandexItems(result.raw);
139734
+ await emitOrTable(opts, {
139735
+ section: "yandex-adgroup-entities",
139736
+ commandLabel: "yandex-analysis adgroup-entities",
139737
+ accountId: result.accountId,
139738
+ payload: result.raw,
139739
+ title: `Yandex \u5E7F\u544A\u7EC4\uFF08${result.accountId}\uFF0C${result.startDate}~${result.endDate}\uFF0C\u5171 ${items2.length} \u884C\uFF09`,
139740
+ empty: "\u5F53\u524D\u8FC7\u6EE4\u4E0B\u65E0\u5E7F\u544A\u7EC4\u884C\uFF08\u9ED8\u8BA4\u53EA\u51FA\u73B0\u6709\u6D88\u8017\u7684\u5BF9\u8C61\uFF09\u3002",
139741
+ footnote: "campaignTargetCpa \u662F\u7CFB\u5217\u76EE\u6807\uFF08\u53EA\u53D6 Search\uFF09\uFF0C\u4E0D\u662F\u7EC4\u7EA7\u76EE\u6807\u4EF7\u3002conversions<=0 \u65F6\u4E0D\u8981\u5224 CPA\u3002\u672C\u6279\u6CA1\u6709\u6539\u4EF7\u63A5\u53E3\u3002",
139742
+ rows: items2.map((r) => ({
139743
+ adGroupId: cell2(r.adGroupId),
139744
+ name: cell2(r.adGroupName),
139745
+ campaignId: cell2(r.campaignId),
139746
+ spend: cell2(r.spend),
139747
+ conversions: cell2(r.conversions),
139748
+ cpa: cell2(r.costPerConversion),
139749
+ targetCpa: cell2(r.campaignTargetCpa),
139750
+ weeklyBudget: cell2(r.campaignWeeklyBudget),
139751
+ strategy: cell2(r.campaignStrategy),
139752
+ date: cell2(r.date)
139753
+ })),
139754
+ columns: [
139755
+ { key: "adGroupId", header: "\u7EC4ID" },
139756
+ { key: "name", header: "\u540D\u79F0" },
139757
+ { key: "campaignId", header: "\u7CFB\u5217ID" },
139758
+ { key: "spend", header: "spend" },
139759
+ { key: "conversions", header: "\u8F6C\u5316" },
139760
+ { key: "cpa", header: "\u5B9E\u9645CPA" },
139761
+ { key: "targetCpa", header: "\u7CFB\u5217\u76EE\u6807CPA" },
139762
+ { key: "weeklyBudget", header: "\u7CFB\u5217\u5468\u9884\u7B97" },
139763
+ { key: "strategy", header: "\u7B56\u7565" },
139764
+ { key: "date", header: "date" }
139765
+ ]
139766
+ });
139767
+ }
139768
+ async function runYandexAdEntities(opts) {
139769
+ let flags;
139770
+ let result;
139771
+ try {
139772
+ flags = parseCommonFlags(opts);
139773
+ result = await fetchPath(opts, "ads", {
139774
+ limit: opts.limit,
139775
+ timeIncrement: flags.timeIncrement,
139776
+ status: flags.status,
139777
+ campaignId: flags.campaignId,
139778
+ adGroupId: flags.adGroupId
139779
+ });
139780
+ } catch (err) {
139781
+ fail3("\u67E5\u8BE2 Yandex \u5E7F\u544A\u5931\u8D25", err);
139782
+ }
139783
+ const items2 = unwrapYandexItems(result.raw);
139784
+ const filtered = flags.status != null;
139785
+ await emitOrTable(opts, {
139786
+ section: "yandex-ad-entities",
139787
+ commandLabel: "yandex-analysis ad-entities",
139788
+ accountId: result.accountId,
139789
+ payload: result.raw,
139790
+ title: `Yandex \u5E7F\u544A\uFF08${result.accountId}\uFF0C${result.startDate}~${result.endDate}\uFF0C\u5171 ${items2.length} \u884C\uFF09`,
139791
+ empty: filtered ? "\u5F53\u524D\u5BA1\u6838\u8FC7\u6EE4\u4E0B\u65E0\u8BB0\u5F55\uFF08\u7A7A\u5217\u8868=\u6210\u529F\u4E14\u6CA1\u6709\u672A\u5F52\u6863\u5339\u914D\u9879\uFF09\u3002" : "\u5F53\u524D\u8FC7\u6EE4\u4E0B\u65E0\u5E7F\u544A\u884C\uFF08\u9ED8\u8BA4\u53EA\u51FA\u73B0\u533A\u95F4\u5185\u6709\u6D88\u8017\u7684\u5E7F\u544A\uFF09\u3002",
139792
+ footnote: "landingUrls \u7A7A\u6570\u7EC4\u4E0D\u662F\u6B7B\u94FE\uFF08Dynamic/Smart/Shopping \u6216\u4EC5 TurboPage \u5E38\u4E3A []\uFF09\u3002CLI \u4E0D\u63A2\u6D3B HTTP\u3002\u62D2\u5BA1\u7528 --status REJECTED\uFF08\u4E0E --time-increment \u4E92\u65A5\uFF1B\u5DF2\u5F52\u6863\u4E0D\u4F1A\u51FA\u73B0\uFF09\u3002",
139793
+ rows: items2.map((r) => ({
139794
+ adId: cell2(r.adId),
139795
+ campaignId: cell2(r.campaignId),
139796
+ adGroupId: cell2(r.adGroupId),
139797
+ spend: cell2(r.spend),
139798
+ conversions: cell2(r.conversions),
139799
+ ctr: cell2(r.ctr),
139800
+ roi: cell2(r.roi),
139801
+ status: cell2(r.status),
139802
+ landing: cell2(r.landingUrls)
139803
+ })),
139804
+ columns: [
139805
+ { key: "adId", header: "\u5E7F\u544AID" },
139806
+ { key: "campaignId", header: "\u7CFB\u5217ID" },
139807
+ { key: "adGroupId", header: "\u7EC4ID" },
139808
+ { key: "spend", header: "spend" },
139809
+ { key: "conversions", header: "\u8F6C\u5316" },
139810
+ { key: "ctr", header: "ctr" },
139811
+ { key: "roi", header: "roi" },
139812
+ { key: "status", header: "\u5BA1\u6838\u6001" },
139813
+ { key: "landing", header: "\u843D\u5730\u9875" }
139814
+ ]
139815
+ });
139816
+ }
139817
+ async function runYandexInsights(opts) {
139818
+ let level;
139819
+ let flags;
139820
+ let result;
139821
+ try {
139822
+ level = parseYandexInsightsLevel(opts.level);
139823
+ flags = parseCommonFlags(opts);
139824
+ result = await fetchPath(opts, insightsLevelToPath(level), {
139825
+ limit: opts.limit,
139826
+ timeIncrement: flags.timeIncrement,
139827
+ network: flags.network,
139828
+ status: flags.status,
139829
+ campaignId: flags.campaignId,
139830
+ adGroupId: flags.adGroupId
139831
+ });
139832
+ } catch (err) {
139833
+ fail3("\u67E5\u8BE2 Yandex insights \u5931\u8D25", err);
139834
+ }
139835
+ const items2 = unwrapYandexItems(result.raw);
139836
+ await emitOrTable(opts, {
139837
+ section: "yandex-insights",
139838
+ commandLabel: "yandex-analysis insights",
139839
+ accountId: result.accountId,
139840
+ payload: result.raw,
139841
+ title: `Yandex insights ${level}\uFF08${result.accountId}\uFF0C${result.startDate}~${result.endDate}\uFF0C\u5171 ${items2.length} \u884C\uFF09`,
139842
+ empty: "\u65E0\u62A5\u8868\u884C\uFF08\u9ED8\u8BA4\u53EA\u51FA\u73B0\u533A\u95F4\u5185\u6709\u6D88\u8017\u7684\u5BF9\u8C61\uFF09\u3002",
139843
+ footnote: "revenue \u662F\u6210\u4EA4\u989D PurchaseRevenue\uFF0C\u4E0D\u662F\u76EE\u6807\u8D4B\u503C\u3002roi=(\u6210\u4EA4\u989D\u2212\u82B1\u8D39)/\u82B1\u8D39\u3002geo/devices \u9ED8\u8BA4 SEARCH\uFF0C\u5168\u7F51\u987B --network ALL\u3002\u65E0\u5C0F\u65F6\u7EF4\u5EA6\u3002",
139844
+ rows: items2.map((r) => ({
139845
+ id: cell2(
139846
+ r.adId ?? r.adGroupId ?? r.campaignId ?? r.keywordId ?? r.regionName ?? r.device ?? r.date
139847
+ ),
139848
+ name: cell2(
139849
+ r.adName ?? r.adGroupName ?? r.campaignName ?? r.keyword ?? r.regionName ?? r.device
139850
+ ),
139851
+ spend: cell2(r.spend),
139852
+ conversions: cell2(r.conversions),
139853
+ ctr: cell2(r.ctr),
139854
+ roi: cell2(r.roi),
139855
+ date: cell2(r.date)
139856
+ })),
139857
+ columns: [
139858
+ { key: "id", header: "\u5BF9\u8C61" },
139859
+ { key: "name", header: "\u540D\u79F0" },
139860
+ { key: "spend", header: "spend" },
139861
+ { key: "conversions", header: "\u8F6C\u5316" },
139862
+ { key: "ctr", header: "ctr" },
139863
+ { key: "roi", header: "roi" },
139864
+ { key: "date", header: "date" }
139865
+ ]
139866
+ });
139867
+ }
139868
+
139414
139869
  // src/commands/yandex-analysis/register-cli.ts
139870
+ var ACCOUNT_OPTION4 = [
139871
+ "-a, --account <id>",
139872
+ "Yandex Client-Login / mediaCustomerId\uFF08\u5982 porg-xxx\uFF0C\u4E0E list-accounts -m Yandex \u4E00\u81F4\uFF09"
139873
+ ];
139874
+ var JSON_OUT_OPTION_DESC4 = "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON";
139875
+ var START_OPTION = [
139876
+ "--start <date>",
139877
+ "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\u7565\uFF1B\u7701\u7565=\u5F53\u5929\uFF09"
139878
+ ];
139879
+ var END_OPTION = ["--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD"];
139415
139880
  function registerBatchCommand4(sectionHelp, aliasHelp) {
139416
139881
  return new Command5("run").description("\u6279\u91CF\u62C9\u53D6 YandexAPI /yandex-analysis \u5404\u7EF4\u5EA6\uFF08\u7701\u7565\u5B50\u547D\u4EE4\u540D\u65F6\u9ED8\u8BA4\u6267\u884C\u672C\u547D\u4EE4\uFF09").requiredOption(
139417
139882
  "-a, --account <id>",
@@ -139437,7 +139902,7 @@ function registerYandexAnalysisCommands(program2) {
139437
139902
  const sectionHelp = YANDEX_SECTION_NAMES.join(", ");
139438
139903
  const aliasHelp = Object.keys(YANDEX_SECTION_ALIASES).join(", ");
139439
139904
  const root = program2.command("yandex-analysis").description(
139440
- "Yandex \u8D26\u6237\u5206\u6790\uFF1A\u6279\u91CF\u62C9\u53D6 /yandex-analysis \u53EA\u8BFB\u6570\u636E\uFF08\u9ED8\u8BA4 run\uFF09\uFF0C\u6216\u7531 render \u751F\u6210 yandex-period-report.html \u7EC8\u7A3F\uFF08\u91D1\u989D\u5DF2\u4E3A\u8D26\u6237\u5E01\u79CD\u5C0F\u6570\uFF1Bsearch-terms \u4EC5\u8FD1 180 \u5929\uFF09"
139905
+ "Yandex \u8D26\u6237\u5206\u6790\uFF1A\u6279\u91CF\u62C9\u53D6 /yandex-analysis \u5468\u671F\u62A5\u8868\uFF08\u9ED8\u8BA4 run\uFF09\u3001render HTML\uFF0C\u6216\u67E5\u8BE2\u8D26\u6237\u5F52\u6863/\u7CFB\u5217/\u7EC4/\u5E7F\u544A/insights\uFF08\u53EA\u8BFB\u8FD0\u8425\uFF1B\u672C\u6279\u4E0D\u80FD\u6682\u505C/\u6539\u4EF7/\u6539\u9884\u7B97\uFF09"
139441
139906
  );
139442
139907
  root.addCommand(registerBatchCommand4(sectionHelp, aliasHelp), { isDefault: true });
139443
139908
  root.command("render").description(
@@ -139452,6 +139917,44 @@ function registerYandexAnalysisCommands(program2) {
139452
139917
  out: opts.out
139453
139918
  });
139454
139919
  });
139920
+ root.command("account-status").description(
139921
+ "\u8D26\u6237\u5F52\u6863/\u4F59\u989D/\u533A\u95F4 KPI\uFF08GET /overview\uFF1Barchived=true \u4E3A\u5DF2\u5F52\u6863\uFF1B\u5B98\u65B9\u65E0\u5C01\u6237\u539F\u56E0\uFF1B\u7701\u7565\u65E5\u671F=\u5F53\u5929\uFF09"
139922
+ ).requiredOption(...ACCOUNT_OPTION4).option(...START_OPTION).option(...END_OPTION).option("-t, --token <token>", "Auth Token").option("--json-out <path>", JSON_OUT_OPTION_DESC4).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
139923
+ async (opts) => {
139924
+ await runYandexAccountStatus(opts);
139925
+ }
139926
+ );
139927
+ root.command("campaign-entities").description(
139928
+ "\u7CFB\u5217\u7EE9\u6548+\u9884\u7B97/\u7B56\u7565\uFF08GET /campaigns\uFF1Aspend/weeklyBudget/budget/strategy/targetCpa\uFF1B\u5168\u6E20\u9053\u82B1\u8D39\u987B --network ALL\uFF1B\u53EF --time-increment 1 \u6309\u65E5\uFF09"
139929
+ ).requiredOption(...ACCOUNT_OPTION4).option(...START_OPTION).option(...END_OPTION).option("--time-increment <n>", "\u4F20 1 \u6309\u65E5\u5207\u5206\uFF1B\u4E0D\u4F20=\u533A\u95F4\u6C47\u603B\u3002Direct \u65E0\u5C0F\u65F6\u7EF4\u5EA6").option("--network <value>", "SEARCH\uFF08\u9ED8\u8BA4\uFF09\u6216 ALL\uFF08\u5168\u7F51\u4E00\u884C\u5408\u8BA1\uFF0C\u884C\u5185 network \u4E3A null\uFF09").option("--limit <n>", "\u6761\u6570\u4E0A\u9650\uFF08\u9ED8\u8BA4\u7F51\u5173 100\uFF0C\u6700\u5927 10000\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Auth Token").option("--json-out <path>", JSON_OUT_OPTION_DESC4).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
139930
+ async (opts) => {
139931
+ await runYandexCampaignEntities(opts);
139932
+ }
139933
+ );
139934
+ root.command("adgroup-entities").description(
139935
+ "\u5E7F\u544A\u7EC4\u7EE9\u6548+\u7CFB\u5217\u76EE\u6807 CPA/\u5468\u9884\u7B97\uFF08GET /adgroups\uFF1B\u9ED8\u8BA4\u5168\u7F51\uFF1BCPA \u98D9\u5347\u770B\u8FD1 3 \u65E5\u533A\u95F4\u6C47\u603B\uFF0C\u4E0D\u8981\u4F20 timeIncrement\uFF09"
139936
+ ).requiredOption(...ACCOUNT_OPTION4).option(...START_OPTION).option(...END_OPTION).option("--time-increment <n>", "\u4F20 1 \u6309\u65E5\u5207\u5206\uFF1B\u4E0D\u4F20=\u533A\u95F4\u6C47\u603B").option("--campaign-id <id>", "\u53EA\u62C9\u8BE5\u7CFB\u5217\u4E0B\u7684\u7EC4").option("--limit <n>", "\u6761\u6570\u4E0A\u9650\uFF08\u9ED8\u8BA4\u7F51\u5173 100\uFF0C\u6700\u5927 10000\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Auth Token").option("--json-out <path>", JSON_OUT_OPTION_DESC4).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
139937
+ async (opts) => {
139938
+ await runYandexAdGroupEntities(opts);
139939
+ }
139940
+ );
139941
+ root.command("ad-entities").description(
139942
+ `\u5E7F\u544A\u7EE9\u6548+\u843D\u5730\u9875/\u62D2\u5BA1\uFF08GET /ads\uFF1B\u62D2\u5BA1\u4F20 --status REJECTED\uFF1B\u5408\u6CD5\uFF1A${YANDEX_AD_STATUSES.join(",")}\uFF1B\u4E0E --time-increment \u4E92\u65A5\uFF09`
139943
+ ).requiredOption(...ACCOUNT_OPTION4).option(...START_OPTION).option(...END_OPTION).option("--time-increment <n>", "\u4F20 1 \u6309\u65E5\u5207\u5206\uFF1B\u4E0D\u4F20=\u533A\u95F4\u6C47\u603B\u3002\u4E0D\u8981\u548C --status \u540C\u65F6\u4F20").option(
139944
+ "--status <list>",
139945
+ "\u5BA1\u6838\u6001\u9017\u53F7\u5206\u9694\uFF0C\u5982 REJECTED \u6216 MODERATION,REJECTED\uFF1B\u5E26 status \u65F6\u4E0D\u62C9\u7EE9\u6548\uFF08spend=0\uFF09"
139946
+ ).option("--campaign-id <id>", "\u53EA\u62C9\u8BE5\u7CFB\u5217\u4E0B\u7684\u5E7F\u544A").option("--adgroup-id <id>", "\u53EA\u62C9\u8BE5\u7EC4\u4E0B\u7684\u5E7F\u544A").option("--limit <n>", "\u6761\u6570\u4E0A\u9650\uFF08\u9ED8\u8BA4\u7F51\u5173 100\uFF0C\u6700\u5927 10000\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Auth Token").option("--json-out <path>", JSON_OUT_OPTION_DESC4).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
139947
+ async (opts) => {
139948
+ await runYandexAdEntities(opts);
139949
+ }
139950
+ );
139951
+ root.command("insights").description(
139952
+ "YandexAPI \u62A5\u8868\u5207\u7247\uFF08GET /daily|/geo|/devices|/keywords \u6216 campaigns/adgroups/ads\uFF1B\u53EF\u542B\u4ECA\u5929\uFF1B\u65E0\u5C0F\u65F6\u7EF4\u5EA6\u3002geo/devices \u5168\u7F51\u987B --network ALL\uFF09"
139953
+ ).requiredOption(...ACCOUNT_OPTION4).requiredOption("--level <level>", YANDEX_INSIGHTS_LEVELS.join(" / ")).option(...START_OPTION).option(...END_OPTION).option("--time-increment <n>", "\u4EC5 campaigns/adgroups/ads\uFF1A\u4F20 1 \u6309\u65E5\uFF1B\u4E0D\u8981\u548C --status \u540C\u65F6\u4F20").option("--network <value>", "\u4EC5 campaigns/geo/devices\uFF1ASEARCH \u6216 ALL").option("--status <list>", "\u4EC5 --level ads\uFF1A\u5BA1\u6838\u6001\u8FC7\u6EE4").option("--campaign-id <id>", "\u4EC5 adgroups/ads").option("--adgroup-id <id>", "\u4EC5 ads").option("--limit <n>", "\u6709 limit \u7684 path\uFF1A\u9ED8\u8BA4\u7F51\u5173 100\uFF0C\u6700\u5927 10000", (v) => parseInt(v, 10)).option("-t, --token <token>", "Auth Token").option("--json-out <path>", JSON_OUT_OPTION_DESC4).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
139954
+ async (opts) => {
139955
+ await runYandexInsights(opts);
139956
+ }
139957
+ );
139455
139958
  }
139456
139959
 
139457
139960
  // src/index.ts
@@ -125,7 +125,7 @@ siluzan-tso -h
125
125
  | 日/周巡检 | W12 | `references/core/workflows.md`(W12)+ `references/accounts/accounts-balance-stats.md` |
126
126
  | 超预算熔断 / 空耗熔断(全户 Google) | — | `references/operations/guard.md`(`guard budget-circuit` / `guard zero-conv`;禁止逐户 for-loop) |
127
127
  | Bing 自动化巡检(封禁/拒审/超预算/空耗预警) | — | `references/operations/hosted-automation-bing.md`(只读告警,不能自动暂停/改价) |
128
- | Yandex 自动化巡检(余额/CPA/日花费预警) | — | `references/operations/hosted-automation-yandex.md`(只读告警,不能自动改投放) |
128
+ | Yandex 自动化巡检(归档/拒审/超预算/空耗预警) | — | `references/operations/hosted-automation-yandex.md`(只读告警,不能自动暂停/改价) |
129
129
  | TikTok 自动化巡检(封禁/拒审/超预算/空耗预警) | — | `references/operations/hosted-automation-tiktok.md`(只读告警,不能自动暂停/改价) |
130
130
  | Facebook / MetaAd 自动化巡检(封禁/拒审/超预算/空耗预警) | — | `references/operations/hosted-automation-facebook.md`(只读告警,不能自动暂停/改价) |
131
131
  | 宿主编排 / 投放自控 / 异常监控 / 自动优化 | — | `references/operations/hosted-automation-user-catalog.md`(**仅当用户问自动化/巡检/熔断**;表内每行只 Read **一个** SOP;Google 熔断优先 guard.md;Bing/Yandex/TikTok/Facebook 走上四行) |
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.48-beta.8",
4
- "publishedAt": 1787738525935
3
+ "version": "1.1.48-beta.9",
4
+ "publishedAt": 1787813752940
5
5
  }
@@ -460,7 +460,7 @@ siluzan-tso bing-analysis run -a <mediaCustomerId> --json-out ./snap --sections
460
460
 
461
461
  ## Yandex 账户分析
462
462
 
463
- > **自动化巡检步骤**(余额 / 多账户 CPA / 日报花费,只读告警):`references/operations/hosted-automation-yandex.md`。
463
+ > **自动化巡检步骤**(归档 / 拒审 / 落地页 / 当日超预算 / 空耗预警,只读告警):`references/operations/hosted-automation-yandex.md`。
464
464
  >
465
465
  > **网关**:直打 YandexAPI `{yandexApiUrl}/yandex-analysis/*`(从 TSO `apiBaseUrl` 推导:`tso-api-ci` → `yandexapi-ci.mysiluzan.com`;可用 `SILUZAN_YANDEX_API` 覆盖)。
466
466
  >
@@ -498,6 +498,30 @@ siluzan-tso yandex-analysis render \
498
498
 
499
499
  `search` / `campaigns` / `keywords` / `geo` / `devices` 在 Direct 侧过滤 `AdNetworkType=SEARCH`。无 Metrica / 写操作。报告模板:`report-templates/yandex-period-report.md`(8 个分析章节必写总结+建议;周趋势读落盘 `daily.weekly[]`,**禁止**自行按 `date` 聚周)。
500
500
 
501
+ ### Yandex 只读运营(`yandex-analysis` 子命令,YandexAPI)
502
+
503
+ > 与上面 `run` 的周期报表不同。归档/余额、当日全网花费、组级 CPA、拒审、落地页、按日环比走以下子命令。省略 `--start/--end` 默认当天。金额已是账户币种小数。本批**不能**暂停 / 改价 / 改预算。
504
+
505
+ | 子命令 | 说明 |
506
+ | --- | --- |
507
+ | `account-status` | `GET /overview`:`archived` / `balance` / `currency`;官方无封户原因 |
508
+ | `campaign-entities` | `GET /campaigns`:`spend` / `weeklyBudget` / `budget` / `strategy` / `targetCpa`;全网加 `--network ALL`;按日加 `--time-increment 1` |
509
+ | `adgroup-entities` | `GET /adgroups`:组绩效 + `campaignTargetCpa` / `campaignWeeklyBudget`;默认全网 |
510
+ | `ad-entities` | `GET /ads`:落地页 `landingUrls`;拒审 `--status REJECTED`(与 `--time-increment` 互斥) |
511
+ | `insights` | `--level daily\|geo\|devices\|keywords\|campaigns\|adgroups\|ads`;geo/devices 全网须 `--network ALL` |
512
+
513
+ ```bash
514
+ siluzan-tso yandex-analysis account-status -a <porg-xxx> --json-out ./snap-yandex
515
+ siluzan-tso yandex-analysis campaign-entities -a <porg-xxx> --json-out ./snap-yandex \
516
+ --start 2026-08-27 --end 2026-08-27 --time-increment 1 --network ALL
517
+ siluzan-tso yandex-analysis ad-entities -a <porg-xxx> --json-out ./snap-yandex \
518
+ --status REJECTED --start 2026-08-27 --end 2026-08-27
519
+ siluzan-tso yandex-analysis insights -a <porg-xxx> --json-out ./snap-yandex \
520
+ --level daily --start 2026-08-25 --end 2026-08-27
521
+ ```
522
+
523
+ **要点**:Direct 无小时维度。默认报表只出现有消耗对象。`budget` 是系列日预算(自动策略常空),对照 `weeklyBudget`。`campaignTargetCpa` 只取 Search。`roi` 按成交额算。
524
+
501
525
  ---
502
526
 
503
527
  ## 报告模板
@@ -215,7 +215,7 @@
215
215
  2. 消耗:`stats -m <媒体> -a <id> --start <昨天/上周一> --end <昨天/上周日>`(多户汇总走 **P3**;Bing/Yandex **今天**不要用 `stats`)。
216
216
  3. Google 当日超预算/空耗熔断(若用户要求):`guard budget-circuit` / `guard zero-conv -m Google --json-out`(**禁止**逐户 for-loop)。
217
217
  4. Bing 巡检(若用户要求):按 `hosted-automation-bing.md` 跑封禁/拒审/当日超预算或空耗预警(只告警,不暂停)。
218
- 5. Yandex 巡检(若用户要求):按 `hosted-automation-yandex.md` 跑 `balance-scan` / `accounts-digest` / `yandex-analysis`(只告警)。
218
+ 5. Yandex 巡检(若用户要求):按 `hosted-automation-yandex.md` 跑归档/拒审/当日超预算或空耗预警(只告警,不暂停)。
219
219
  6. TikTok 巡检(若用户要求):按 `hosted-automation-tiktok.md` 跑封禁/拒审/当日超预算或空耗预警(只告警,不暂停)。
220
220
  7. Facebook / MetaAd 巡检(若用户要求):按 `hosted-automation-facebook.md` 跑封禁/拒审/当日超预算或空耗预警(只告警,不暂停)。
221
221
  8. 预警触发:`forewarning records -m <媒体> --start <S>`(见 **W10**)。
@@ -12,7 +12,7 @@
12
12
  | [`references/operations/hosted-automation-monitoring-json.md`](references/operations/hosted-automation-monitoring-json.md) | **Google 异常监控**落盘与读数 |
13
13
  | [`references/operations/hosted-automation-optimize-index.md`](references/operations/hosted-automation-optimize-index.md) | **Google 自动优化**:差素材降价/关停、高转化扩量、A/B 停输家 |
14
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/零转化、日报花费异动;**不能**自动改投放 |
15
+ | [`references/operations/hosted-automation-yandex.md`](references/operations/hosted-automation-yandex.md) | **Yandex 只读巡检**:归档/余额、拒审、落地页、当日超预算、CPA/空耗、按日异动;**不能**自动暂停/改价 |
16
16
  | [`references/operations/hosted-automation-tiktok.md`](references/operations/hosted-automation-tiktok.md) | **TikTok 只读巡检**:封禁/余额、拒审、落地页、当日超预算、CPA/空耗、小时异动;**不能**自动暂停/改价 |
17
17
  | [`references/operations/hosted-automation-facebook.md`](references/operations/hosted-automation-facebook.md) | **Facebook / MetaAd 只读巡检**:封禁/余额、拒审、落地页、当日超预算、CPA/空耗、小时异动;**不能**自动暂停/改价 |
18
18
 
@@ -70,13 +70,15 @@
70
70
 
71
71
  ## Yandex:只读巡检
72
72
 
73
- 全部步骤只读一份:`references/operations/hosted-automation-yandex.md`。**不能**自动改投放;无封禁/拒审/落地页/小时接口。
74
-
75
- | 场景 | 做什么 | SOP |
76
- | -------------------- | ---------------------------------------------------------------------- | -------------------------------------------------- |
77
- | 余额续航 | `balance-scan -m Yandex` | `references/operations/hosted-automation-yandex.md` |
78
- | 多账户 CPA / 零转化 | `accounts-digest -m Yandex`(截至昨天) | 同上 |
79
- | 当日/近日花费与 CPA | `yandex-analysis --sections overview,daily,campaigns`(可含今天) | 同上 |
73
+ 全部步骤只读一份:`references/operations/hosted-automation-yandex.md`。**不能**自动暂停/改价/改预算。无小时维度。
74
+
75
+ | 场景 | 做什么 | SOP |
76
+ | ------------ | ---------------------------------------------------------------------- | -------------------------------------------------- |
77
+ | 余额 / 归档 | `yandex-analysis account-status`;`archived=true` 告警 | `references/operations/hosted-automation-yandex.md` |
78
+ | 素材拒审 | `ad-entities --status REJECTED` | 同上 |
79
+ | 落地页死链 | `ad-entities` `landingUrls`,宿主 HTTP 探活 | 同上 |
80
+ | 当日超预算 | `campaign-entities --time-increment 1 --network ALL`;只告警 | 同上 |
81
+ | CPA / 空耗 | `adgroup-entities` 近 3 日 + 当日 `--time-increment 1`;只告警 | 同上 |
80
82
 
81
83
  ## Facebook / MetaAd:只读巡检
82
84
 
@@ -1,83 +1,153 @@
1
1
  # 宿主编排:Yandex 只读巡检
2
2
 
3
- > **何时 Read**:用户要 Yandex / 直通广告自动化、巡检、熔断、余额或 CPA 预警。Google 熔断走 `references/operations/guard.md`;Bing 走 `hosted-automation-bing.md`。
4
- > **能做什么**:拉余额、拉日报/周期报表、宿主按阈值告警。
5
- > **不能做什么**:暂停、改价、改预算、封禁专用接口、拒审列表、落地页列表、小时花费。没有写命令,**禁止**假装已执行熔断。
3
+ > **何时 Read**:用户要 Yandex / 直通广告自动化、巡检、熔断、封禁、拒审、落地页、超预算或空耗。Google 熔断走 `references/operations/guard.md`;Bing 走 `hosted-automation-bing.md`。
4
+ > **能做什么**:拉数、对比阈值、宿主告警。
5
+ > **不能做什么**:暂停系列/组/广告、改价、改预算。本批只读,**禁止**假装已执行熔断。
6
6
 
7
7
  **前置**:已 `siluzan-tso login`,并用 `list-accounts -m Yandex --json-out ./snap` 确认 `mediaCustomerId`(形如 `porg-…`,**不是**纯数字,也不是 `entityId`)。
8
8
 
9
9
  **硬约束**:
10
10
 
11
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` 判断今天。
12
+ - `yandex-analysis run` 是**周期报表**(默认 8 维、SEARCH、区间汇总)。当天花费、拒审、落地页、组级 CPA、全网花费走下面的**只读运营子命令**。
13
+ - 省略 `--start/--end` 时运营子命令默认**当天**(本机日历日)。`run` 省略则仍是近 7 天含今天。
14
+ - Direct **没有小时报表**。不要传 hourly,也不要编造近两小时 CPA。
15
+ - `revenue` 是成交额 PurchaseRevenue;`roi = (成交额 − 花费) / 花费`。不要用目标赋值反推。
16
+ - 默认报表路径只出现**有消耗**的对象。拒审须带 `--status`(结构查询,行内 `spend` 为 0)。
17
+ - `--status` 与 `--time-increment` **不能同时传**(400)。
18
+ - `/campaigns` `/geo` `/devices` 默认 SEARCH。全渠道花费须 `--network ALL`(行内 `network` 为 null,不拆行)。
19
+ - 自动策略与共享账户通常只有**周预算**,`budget`(日预算)常为 null。不要把周预算写进日预算字段。
15
20
 
16
21
  ---
17
22
 
18
- ## 1. 余额续航
23
+ ## 1. 余额 / 账户状态
24
+
25
+ ```bash
26
+ siluzan-tso yandex-analysis account-status -a <porg-xxx> --json-out ./snap-yandex
27
+ ```
28
+
29
+ 读 `archived`、`balance`、`currency`、`balanceAsOf`。`archived=true` → 宿主告警「已归档」。官方**无**封户状态与原因,不要把 `archived` 写成「因违规被封」。官方无独立额度;续航用余额 ÷ 近几日日均花费(日均走 `insights --level daily`)。CLI **不能**自动充值。
30
+
31
+ 多户续航也可先扫 TSO 余额(截至昨天的日均):
19
32
 
20
33
  ```bash
21
34
  siluzan-tso balance-scan -m Yandex --threshold-days 7 --json-out ./snap-yandex
22
35
  ```
23
36
 
24
- 已知子集加 `-a porg-xxx,porg-yyy`。命中续航/余额不足 → 宿主通知充值。CLI **不能**自动充值。单户复核:
37
+ ---
38
+
39
+ ## 2. 素材拒审
25
40
 
26
41
  ```bash
27
- siluzan-tso balance -m Yandex -a <porg-xxx> --json-out ./snap-yandex
42
+ siluzan-tso yandex-analysis ad-entities -a <porg-xxx> \
43
+ --start <当天> --end <当天> --status REJECTED --json-out ./snap-yandex
28
44
  ```
29
45
 
46
+ 也可 `MODERATION` / `PREACCEPTED`。合法值:`DRAFT,MODERATION,PREACCEPTED,ACCEPTED,REJECTED,UNKNOWN`。空列表 = 成功且没有未归档拒审,不是失败。已归档素材不会出现。命中 → 告警 `adId` / `campaignId` / `status` / `statusClarification`。
47
+
30
48
  ---
31
49
 
32
- ## 2. 多账户消耗 / CPA / 零转化(截至昨天)
50
+ ## 3. 落地页死链
51
+
52
+ ```bash
53
+ siluzan-tso yandex-analysis ad-entities -a <porg-xxx> \
54
+ --start <当天> --end <当天> --json-out ./snap-yandex
55
+ ```
33
56
 
34
- 一条命令扫该媒体全部户(或 `-a` 子集)。区间必须是已结束的日期,**不要把今天当完整日**:
57
+ 读每条广告的 `landingUrls`(数组)。空数组 **不要**当死链(Dynamic / Smart / Shopping 或仅 `TurboPageId` 常为 `[]`)。CLI **不**发 HTTP。宿主对非空 URL 做 HEAD/GET,4xx/5xx 或超时 → 告警。扫在投、含 0 消耗素材用 `--status ACCEPTED`(该路径不带花费)。
58
+
59
+ ---
60
+
61
+ ## 4. 当日超预算(只预警,不暂停)
62
+
63
+ 全渠道当日花费 + 周预算(官方常无系列日预算):
35
64
 
36
65
  ```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
66
+ siluzan-tso yandex-analysis campaign-entities -a <porg-xxx> --json-out ./snap-yandex \
67
+ --start <当天> --end <当天> --time-increment 1 --network ALL
40
68
  ```
41
69
 
42
- 命中宿主告警账户、区间花费、转化、CPA。不能自动关停。
70
+ 用同一 `campaignId` 看 `spend` 对 `weeklyBudget`(点击策略、投放天数 ≥ 3 时,单日大约最多花到周预算的 35%)。`budget` 仍是系列日预算,自动策略下通常为 null。命中 **告警P1**,不要写「已暂停」。
43
71
 
44
72
  ---
45
73
 
46
- ## 3. 单户当日 / 近日花费与 CPA(可含今天)
74
+ ## 5. CPA / 空耗(只预警,不降价、不暂停)
75
+
76
+ 近 3 日组级转化成本(无小时维度,不要编造近两小时):
47
77
 
48
78
  ```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
79
+ siluzan-tso yandex-analysis adgroup-entities -a <porg-xxx> --json-out ./snap-yandex \
80
+ --start <今天-2> --end <今天>
52
81
  ```
53
82
 
54
- 宿主侧:
83
+ 对照 `conversions`、`costPerConversion`、`campaignTargetCpa`。`conversions <= 0` 时通常不判 CPA。有目标价时可用 `costPerConversion > campaignTargetCpa × 1.5` 作为飙升参考。没有目标价不算读取失败。`campaignStrategy` 只说明策略类型。
55
84
 
56
- - **日花费异动**:读 `daily` 按日对比前一日或近 7 日均值,超阈值告警。
57
- - **系列花费 / 预算**:同一窗口读 `campaigns`,用落盘里的花费对照 `budget`;缺字段就不要猜,改用日环比。
58
- - **CPA**:`花费 / 转化`(转化为 0 不算 CPA,改走空耗判断)。超过约定目标倍数 → 告警。
59
- - **空耗**:当日(或约定日)花费已达「目标 CPA × N」且转化为 0 → 告警「空耗预警」,文案不要写成「已熔断」。目标 CPA 用落盘 `targetCpa` 或账户约定。
85
+ 当日空耗(组 / 广告):
60
86
 
61
- 没有小时维度,做不了「近两小时 CPA」。
87
+ ```bash
88
+ siluzan-tso yandex-analysis adgroup-entities -a <porg-xxx> --json-out ./snap-yandex \
89
+ --start <当天> --end <当天> --time-increment 1
90
+ siluzan-tso yandex-analysis ad-entities -a <porg-xxx> --json-out ./snap-yandex \
91
+ --start <当天> --end <当天> --time-increment 1
92
+ ```
93
+
94
+ 有花费、无转化 → 告警「空耗预警」,文案不要写成「已熔断」。连续天数由宿主按日统计。
62
95
 
63
96
  ---
64
97
 
65
- ## 4. 周期报告(给人看,不代替巡检告警)
98
+ ## 6. 花费异动 / 广告效果 / A/B / 根因
99
+
100
+ 按日花费环比(前天~今天):
66
101
 
67
102
  ```bash
103
+ siluzan-tso yandex-analysis insights -a <porg-xxx> --json-out ./snap-yandex \
104
+ --level daily --start <前天> --end <今天>
105
+ ```
106
+
107
+ 差广告 / A/B(同一份广告列表,按 `adId` 比 `spend` / `ctr` / `conversions` / `roi` / `costPerConversion`):
108
+
109
+ ```bash
110
+ siluzan-tso yandex-analysis ad-entities -a <porg-xxx> --json-out ./snap-yandex \
111
+ --start <区间起> --end <区间止>
112
+ ```
113
+
114
+ 高转化扩量(组绩效 + 关键词搜索出价;自动策略下看系列周预算,不要按手动 CPC 改价):
115
+
116
+ ```bash
117
+ siluzan-tso yandex-analysis adgroup-entities -a <porg-xxx> --json-out ./snap-yandex \
118
+ --start <区间起> --end <区间止>
119
+ siluzan-tso yandex-analysis insights -a <porg-xxx> --json-out ./snap-yandex \
120
+ --level keywords --start <区间起> --end <区间止>
121
+ ```
122
+
123
+ 异动根因(地域与设备须分别请求;全网加 `--network ALL`):
124
+
125
+ ```bash
126
+ siluzan-tso yandex-analysis insights -a <porg-xxx> --json-out ./snap-yandex \
127
+ --level geo --start <S> --end <D> --network ALL
128
+ siluzan-tso yandex-analysis insights -a <porg-xxx> --json-out ./snap-yandex \
129
+ --level devices --start <S> --end <D> --network ALL
130
+ ```
131
+
132
+ `/geo` 有 `limit`(默认 100)。截断后的地域 `spend` 合计通常低于全网,不宜直接拿去对 `/devices`。
133
+
134
+ ---
135
+
136
+ ## 7. 周期报告与多账户(给人看,不代替巡检告警)
137
+
138
+ ```bash
139
+ siluzan-tso accounts-digest -m Yandex --start <S> --end <D> --json-out ./snap-yandex
68
140
  siluzan-tso yandex-analysis -a <porg-xxx> --start <S> --end <D> --json-out ./snap-yandex
69
141
  ```
70
142
 
71
- HTML 按 `report-templates/yandex-period-report.md` 写 narrative 后再 `yandex-analysis render`。自动化任务里报告是可选交付物;阈值告警仍走上面 1–3 步。
143
+ `accounts-digest` / `balance-scan` 的消耗截至**昨天**。出 HTML 按 `report-templates/yandex-period-report.md` 写 narrative 后再 `yandex-analysis render`。阈值告警仍走上面 1–6 步。
72
144
 
73
145
  ---
74
146
 
75
147
  ## 做不到(勿编造命令)
76
148
 
77
- | 场景 | 现状 |
78
- | -------------- | ----------------------------------------- |
79
- | 账户封禁专用 | 无;`list-accounts` 状态仅供人工看 |
80
- | 素材拒审 | 无拒审列表 |
81
- | 落地页探活 | 无最终到达网址接口 |
82
- | 小时花费 | 无 |
83
- | 自动暂停 / 改价 | 无写接口;告警后人工在 Yandex 后台处理 |
149
+ | 场景 | 现状 |
150
+ | --------------- | ----------------------------------------- |
151
+ | 账户封禁原因 | 无;只能读 `archived` |
152
+ | 小时花费 / 小时 CPA | 无;改用按日或近 3 日区间 |
153
+ | 自动暂停 / 改价 / 改预算 | 无写接口;告警后人工在 Yandex 后台处理 |
@@ -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.48-beta.8'
12
+ $PKG_VERSION = '1.1.48-beta.9'
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.48-beta.8"
12
+ readonly PKG_VERSION="1.1.48-beta.9"
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"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.48-beta.8",
3
+ "version": "1.1.48-beta.9",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",