siluzan-tso-cli 1.1.37-beta.2 → 1.1.37-beta.3

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.37-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.37-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -113039,6 +113039,10 @@ var VALID_LEAD_FORM_INPUT_TYPES = [
113039
113039
  "POSTAL_CODE",
113040
113040
  "STREET_ADDRESS"
113041
113041
  ];
113042
+ var LEAD_FORM_HEADLINE_MAX_LEN = 30;
113043
+ var LEAD_FORM_DESCRIPTION_MAX_LEN = 200;
113044
+ var LEAD_FORM_BUSINESS_NAME_MAX_LEN = 25;
113045
+ var LEAD_FORM_CTA_DESCRIPTION_MAX_LEN = 30;
113042
113046
  function loadLeadFormExtensionConfig(configFile) {
113043
113047
  let raw;
113044
113048
  try {
@@ -113066,10 +113070,30 @@ function validateLeadFormPayload(lf, prefix = "leadForm") {
113066
113070
  return errors;
113067
113071
  }
113068
113072
  if (!lf.businessName?.trim()) errors.push(`${prefix}.businessName \u4E0D\u80FD\u4E3A\u7A7A`);
113073
+ else if (lf.businessName.trim().length > LEAD_FORM_BUSINESS_NAME_MAX_LEN) {
113074
+ errors.push(
113075
+ `${prefix}.businessName \u8D85\u957F\uFF08${lf.businessName.trim().length}/${LEAD_FORM_BUSINESS_NAME_MAX_LEN}\uFF09\uFF1A\u300C${lf.businessName.trim()}\u300D`
113076
+ );
113077
+ }
113069
113078
  if (!lf.headline?.trim()) errors.push(`${prefix}.headline \u4E0D\u80FD\u4E3A\u7A7A`);
113079
+ else if (lf.headline.trim().length > LEAD_FORM_HEADLINE_MAX_LEN) {
113080
+ errors.push(
113081
+ `${prefix}.headline \u8D85\u957F\uFF08${lf.headline.trim().length}/${LEAD_FORM_HEADLINE_MAX_LEN}\uFF09\uFF1A\u300C${lf.headline.trim()}\u300D`
113082
+ );
113083
+ }
113070
113084
  if (!lf.description?.trim()) errors.push(`${prefix}.description \u4E0D\u80FD\u4E3A\u7A7A`);
113085
+ else if (lf.description.trim().length > LEAD_FORM_DESCRIPTION_MAX_LEN) {
113086
+ errors.push(
113087
+ `${prefix}.description \u8D85\u957F\uFF08${lf.description.trim().length}/${LEAD_FORM_DESCRIPTION_MAX_LEN}\uFF09`
113088
+ );
113089
+ }
113071
113090
  if (!lf.privacyPolicyUrl?.trim()) errors.push(`${prefix}.privacyPolicyUrl \u4E0D\u80FD\u4E3A\u7A7A`);
113072
113091
  if (!lf.finalUrl?.trim()) errors.push(`${prefix}.finalUrl \u4E0D\u80FD\u4E3A\u7A7A`);
113092
+ if (lf.callToActionDescription?.trim() && lf.callToActionDescription.trim().length > LEAD_FORM_CTA_DESCRIPTION_MAX_LEN) {
113093
+ errors.push(
113094
+ `${prefix}.callToActionDescription \u8D85\u957F\uFF08${lf.callToActionDescription.trim().length}/${LEAD_FORM_CTA_DESCRIPTION_MAX_LEN}\uFF09`
113095
+ );
113096
+ }
113073
113097
  if (!Array.isArray(lf.fields) || lf.fields.length === 0) {
113074
113098
  errors.push(`${prefix}.fields \u81F3\u5C11 1 \u9879`);
113075
113099
  } else {
@@ -116555,7 +116579,10 @@ function classifyExtensionAttachError(error) {
116555
116579
  return `${raw}\uFF08\u591A\u4E3A header \u4E0D\u5728\u5408\u6CD5\u679A\u4E3E\uFF1B\u8FD0\u884C ad extension snippet-headers \u6838\u5BF9\uFF09`;
116556
116580
  }
116557
116581
  if (/lead form|lead_form|LEAD_FORM/i.test(raw)) {
116558
- return `${raw}\uFF08\u68C0\u67E5 Google Ads UI \u662F\u5426\u5DF2\u63A5\u53D7 Lead Form ToS\u3001fields \u662F\u5426\u542B FULL_NAME/EMAIL/PHONE_NUMBER\uFF09`;
116582
+ return `${raw}\uFF08\u68C0\u67E5\uFF1Aheadline\u226430\u3001description\u2264200\u3001businessName\u226425\uFF1Bfields \u52FF\u7528 MESSAGE\uFF1BGoogle Ads UI \u662F\u5426\u5DF2\u63A5\u53D7 Lead Form ToS\uFF09`;
116583
+ }
116584
+ if (/^HTTP 400\b/i.test(raw)) {
116585
+ return `${raw}\uFF08\u6269\u5C55\u5B57\u6BB5\u8FDD\u89C4\u5E38\u89C1\uFF1Asnippet header \u975E\u679A\u4E3E\u3001Lead Form \u6587\u6848\u8D85\u957F\u3001URL \u65E0\u6548\uFF1B\u52A0 --verbose \u770B\u7F51\u5173\u539F\u6587\uFF09`;
116559
116586
  }
116560
116587
  return raw;
116561
116588
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.37-beta.2",
4
- "publishedAt": 1783912835109
3
+ "version": "1.1.37-beta.3",
4
+ "publishedAt": 1783922347102
5
5
  }
@@ -40,14 +40,14 @@ siluzan-tso ad extension update -a <accountId> --id <extensionId> --config-file
40
40
  | ---------------------------------- | -------- | :--: | -------------------------------------------------------------- |
41
41
  | `account` | string | ✅ | Google 媒体客户 ID |
42
42
  | `campaignId` | string | ✅ | PMax 活动 ID(`channelTypeV2=PERFORMANCE_MAX`) |
43
- | `leadForm.businessName` | string | ✅ | 商家名称 |
44
- | `leadForm.headline` | string | ✅ | 表单标题 |
45
- | `leadForm.description` | string | ✅ | 表单描述 |
43
+ | `leadForm.businessName` | string | ✅ | 商家名称(≤25 字符) |
44
+ | `leadForm.headline` | string | ✅ | 表单标题(**≤30 字符**,超长网关常直接 HTTP 400) |
45
+ | `leadForm.description` | string | ✅ | 表单描述(≤200 字符) |
46
46
  | `leadForm.privacyPolicyUrl` | string | ✅ | 隐私政策 URL |
47
47
  | `leadForm.finalUrl` | string | ✅ | 落地页 URL |
48
48
  | `leadForm.callToActionType` | string | | 默认 `LEARN_MORE` |
49
- | `leadForm.callToActionDescription` | string | | 默认 `Contact us` |
50
- | `leadForm.fields` | object[] | ✅ | 至少 1 项;`inputType` 如 `FULL_NAME`、`EMAIL`、`PHONE_NUMBER` |
49
+ | `leadForm.callToActionDescription` | string | | 默认 `Contact us`(≤30 字符) |
50
+ | `leadForm.fields` | object[] | ✅ | 至少 1 项;`inputType` 如 `FULL_NAME`、`EMAIL`、`PHONE_NUMBER`(**无** `MESSAGE`) |
51
51
  | `leadForm.webhook` | object | | 写入 Google 的 Webhook(非平台接收端) |
52
52
 
53
53
  ---
@@ -203,7 +203,7 @@ siluzan-tso accounts-digest -m Google -a id1,id2 --min-spend 10 \
203
203
  --start 2026-04-01 --end 2026-04-15 --json-out ./snap-p3
204
204
  ```
205
205
 
206
- **`data.items[]` 主要字段**:`mediaCustomerId`、`name`、`advertiserName`、`currencyCode`、`balance`、`spend`、`impressions`、`clicks`、`conversions`、`ctr`(%)、`cpc`、`cpa`。跨币种汇总见 `references/accounts/currency.md`(**禁止**对 `meta.totals` 跨币种直接当最终结论)。
206
+ **`data.items[]` 主要字段**:`mediaCustomerId`、`name`、`advertiserName`、`currencyCode`、`balance`、`spend`(**区间合计**,同 `stats`)、`impressions`、`clicks`、`conversions`、`ctr`(%)、`cpc`、`cpa`。跨币种汇总见 `references/accounts/currency.md`(**禁止**对 `meta.totals` 跨币种直接当最终结论)。
207
207
 
208
208
  ---
209
209
 
@@ -230,6 +230,8 @@ siluzan-tso stats -m <媒体类型> [选项]
230
230
  | `--start-date` / `--end-date` | 与 `--start` / `--end` 同义(CLI 别名,与 SKILL Playbook 一致) | — |
231
231
  | `--json-out` | 输出原始 JSON;**失败时 stdout 仍为 JSON**(`{"ok":false,"error":"..."}`) | — |
232
232
 
233
+ **口径(易错)**:返回的 **`spend` = `--start`~`--end` 闭区间合计消耗**,默认近 7 天即约 **7 日之和**,**不是**「一天的消耗」。要日消耗须 `start=end`;要日均用 `spend/天数` 或 `balance-scan` 的 `dailySpend`。**禁止**把默认窗口的 `spend` 当成日消耗去比日预算或写「今天花了 X」。
234
+
233
235
  **示例:**
234
236
 
235
237
  ```bash
@@ -194,10 +194,13 @@
194
194
 
195
195
  | 任务 | 推荐命令 | 禁止 |
196
196
  | --------------------------- | -------------------------------------------------------------------------------------- | -------------------------------- |
197
- | 多账户余额 / 预算不足预警 | `balance-scan -m <媒体> --threshold-days 7` | 逐账户 `balance --accounts ...` |
198
- | 多账户投放画像 | `accounts-digest -m <媒体> [-a ...] --start --end --json-out` | 逐账户 `stats` |
197
+ | 多账户余额 / 预算不足预警 | `balance-scan -m <媒体> --threshold-days 7`(用 **`dailySpend`** / `remainingDays`) | 逐账户 `balance --accounts ...`;把 `stats` 多日 **`spend` 当「日消耗」** |
198
+ | 多账户投放画像 | `accounts-digest -m <媒体> [-a ...] --start --end --json-out`(**`spend`=区间合计**) | 逐账户 `stats`;把合计 `spend` 说成「每天花了」 |
199
199
  | 多账户 × 多维度 Google 数据 | 全量:`google-analysis-batch run`(省略 `-a`);2~10:`google-analysis -a id1,id2,...` | 外层 for-loop;先 list 再拼 `-a` |
200
200
  | 多系列诊断 | `ad campaigns --json-out` + node 读文件过滤 | 逐系列 `ad campaign-get` |
201
+ | 单日预算熔断 / 当日花费比日预算 | `ad campaigns --start <当日> --end <当日>`(**start=end**)后再比 `spend` 与 `budget` | 用默认近 7 天窗口的 `spend` 对比日预算 |
202
+
203
+ **消耗口径(自动化必读)**:凡 `--start`/`--end` 读命令的 `spend` = **闭区间合计**,默认近 7 天 ≠ 一天。日均用 `balance-scan.dailySpend` 或 `合计÷天数`;详见 `references/operations/hosted-automation-self-control.md`「检查项常用 JSON 字段」。
201
204
 
202
205
  **`google-analysis-batch` 纪律**(详见 `references/analytics/google-analysis-batch.md`):拉全量时省略 `-a`;中断后**必须** `resume --run-id <id>`,**禁止**重新 `run`;stdout 始终单行 JSON(`kind=siluzan-tso-batch-summary`);退出码 `0` 全成功 / `2` 部分成功 / `3` 全失败或 Token 失效 / `4` 用法错误;401 → 整批终止,按 `references/core/setup.md` 重登录后 `resume`。
203
206
 
@@ -93,16 +93,17 @@ low.forEach((a) => console.log(' ', a.mediaCustomerId, a.name, '余额:', a.rema
93
93
 
94
94
  ### 3. 汇总消耗数据
95
95
 
96
- `stats` 的 `AccountOverviewItem` 使用 **`spend`**(非 `cost`):
96
+ `stats` 的 `AccountOverviewItem` 使用 **`spend`**(非 `cost`)。**`spend` = `--start`~`--end` 区间合计**(默认近 7 天 ≈ 7 日之和,**不是**「一天的消耗」):
97
97
 
98
98
  ```bash
99
- siluzan-tso stats -m Google --days 7 --json-out ./snap-cli
99
+ siluzan-tso stats -m Google -a <id> --start 2026-07-06 --end 2026-07-12 --json-out ./snap-cli
100
100
  node -e "
101
101
  const d = require('./snap-cli/stats-google.json');
102
102
  const list = d.items || [];
103
+ // 「总消耗」= 区间合计;日均请 total/天数,或 balance-scan.dailySpend;勿说成每天花了 total
103
104
  const total = list.reduce((s, a) => s + Number(a.spend ?? 0), 0);
104
105
  const clicks = list.reduce((s, a) => s + Number(a.clicks ?? 0), 0);
105
- console.log('总消耗:', total.toFixed(2), ' 总点击:', clicks);
106
+ console.log('区间总消耗:', total.toFixed(2), ' 总点击:', clicks);
106
107
  list
107
108
  .slice()
108
109
  .sort((a, b) => Number(b.spend) - Number(a.spend))
@@ -97,7 +97,9 @@
97
97
  siluzan-tso ad campaigns -a <accountId> [--start <YYYY-MM-DD>] [--end <YYYY-MM-DD>] [--json-out ./snap]
98
98
  ```
99
99
 
100
- 落盘 JSON 中 `budget` 为日预算(主币种「元」,CLI 已 ÷100),另有 `statusDisplay`(状态文案)。
100
+ 落盘 JSON 中 **`budget` 为日预算**(主币种「元」,CLI 已 ÷100),另有 `statusDisplay`(状态文案)。
101
+ 带 `--start` / `--end` 时,同行的 **`spend` / `impressions` / `clicks` / `conversions` 为该闭区间合计**(**不是**日消耗)。
102
+ 与日预算比较「是否超预算」时,**必须** `start=end=统计日`;多日窗口须先 ÷ 天数或改用 `balance-scan.dailySpend` / `overview.averageDailyCost` 再谈「日均」。
101
103
 
102
104
  ### 启停
103
105
 
@@ -26,11 +26,15 @@
26
26
 
27
27
  | 检查意图 | 可关注的 JSON 字段(键名以实际 stdout 为准) |
28
28
  | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
29
- | 周期内费用 | 系列/组/创意列表中的 **`spend`**;报表类子命令中带费用的指标字段 |
30
- | 日预算与费用比 | **`ad campaigns --json-out ./snap`**:`**budget`**(CLI 已为元)与 **`spend`**;比较时须**统一货币与单位\*\*(见 `SKILL.md` 金额硬规范) |
31
- | 转化次数 | **`conversions`**(或报表里等价字段名) |
32
- | 实际 CPA | 宿主侧 **`spend / conversions`**(`conversions` 为 0 时不做 CPA 判断) |
29
+ | **区间合计费用** | 系列/组/创意列表中的 **`spend`** = **`--start`~`--end` 整段合计**(**不是**「一天的消耗」;窗口 7 天则约为 7 日之和) |
30
+ | **日预算** | **`ad campaigns --json-out ./snap`** 的 **`budget`**(CLI 已为元)= **每日预算上限**,与 `spend` **量纲不同** |
31
+ | 日预算与「当日」费用比 | **仅当** `--start` 与 `--end` 为**同一统计日**时,才可用 `spend` 对比 `budget`;**禁止**用多日 `spend` 去除以或对比日预算(会把周/月合计误当成「一天花了这么多」) |
32
+ | 转化次数 | **`conversions`**(窗口内合计) |
33
+ | 实际 CPA | 宿主侧 **`spend / conversions`**(同窗口合计;`conversions` 为 0 时不做 CPA 判断) |
33
34
  | 目标 CPA(系列 / 组) | 系列列表 JSON 中的 **`targetCpa_BidingAmount`** 等;组列表 **`ad groups --json-out ./snap`** 中的 **`targetCpaAmount`**(写入口径见 `references/google-ads/google-ads.md`「广告组编辑」) |
35
+ | 日均消耗(续航用) | 用 `balance-scan` 的 **`dailySpend`**(近 7 日合计/7),或自算 `spend / 活跃天数`;**禁止**把多日 `spend` 直接当「日消耗」 |
36
+
37
+ > **硬约束(自动化 / 预警必读)**:凡带 `--start` / `--end` 的读命令,返回的 `spend` / `impressions` / `clicks` / `conversions` 均为**该闭区间合计**。要「一天的消耗」必须 `start=end=该日`,或显式 `合计 ÷ 天数`;要「日均」用 `balance-scan.dailySpend` / `overview.averageDailyCost`。
34
38
 
35
39
  若某条检查所需字段在**当前**落盘 JSON 中不存在,**禁止猜测**:先换用 `references/analytics/account-analytics.md` 中其它子命令拉数,或与维护方确认是否需扩展 `siluzan-tso`;宿主侧不得编造字段。
36
40
 
@@ -38,21 +42,22 @@
38
42
 
39
43
  ## 场景 1:单日预算熔断(Campaign)
40
44
 
41
- **业务目标**:当周期内费用相对日预算达到设定比例(如 **≥ 110%**,建议 **110%–120%** 缓冲 API 延迟)时,**将系列状态置为 Paused**;恢复由人工加预算后在网页或 CLI 再启用。
45
+ **业务目标**:当**当日**系列花费相对**日预算**达到设定比例(如 **≥ 110%**,建议 **110%–120%** 缓冲 API 延迟)时,**将系列状态置为 Paused**;恢复由人工加预算后在网页或 CLI 再启用。
42
46
 
43
47
  ### 检查项(宿主每轮执行)
44
48
 
45
49
  1. 解析 `mediaCustomerId`(见上文前置)。
46
- 2. 拉系列列表(**统计日 = 当日**,起止同为该日或业务日定义):
50
+ 2. 拉系列列表(**必须** `--start` = `--end` = **当日**统计日;**禁止**用默认近 7 天窗口做本场景):
47
51
 
48
52
  ```bash
49
- siluzan-tso ad campaigns -a <mediaCustomerId> --start <YYYY-MM-DD> --end <YYYY-MM-DD> --json-out ./snap
53
+ siluzan-tso ad campaigns -a <mediaCustomerId> --start <当日YYYY-MM-DD> --end <当日YYYY-MM-DD> --json-out ./snap
50
54
  ```
51
55
 
52
56
  3. 对 `items[]` 中每条系列(`id` 为系列 ID):
53
- - 读取 **`spend`**(或文档约定的费用字段)与 **`budget`**(`ad campaigns` JSON 下**已为元**,与 `spend` 同量级口径,见 `SKILL.md` 硬规范)。
54
- - 计算阈值:`threshold = budget_in_same_unit * (coefficient / 100)`,系数 `coefficient` 建议 **110–120**(配置项)。
57
+ - 读取 **`spend`**(= **仅当日**合计费用)与 **`budget`**(**日预算**,元)。
58
+ - 计算阈值:`threshold = budget * (coefficient / 100)`,系数 `coefficient` 建议 **110–120**(配置项)。
55
59
  - **IF** `spend >= threshold` **且**宿主策略允许对该系列熔断(如排除白名单),则进入写操作。
60
+ - **禁止**:`--start`≠`--end` 时仍用 `spend` 对比 `budget`(会把多日合计误判成单日超预算)。
56
61
 
57
62
  ### 最终操作
58
63
 
@@ -14,6 +14,8 @@
14
14
 
15
15
  **总索引**:[`references/operations/hosted-automation-scenarios.md`](references/operations/hosted-automation-scenarios.md) · **自控全文**:[`references/operations/hosted-automation-self-control.md`](references/operations/hosted-automation-self-control.md) · **金额硬规范**(展示用 `*Display` 等):[`SKILL.md`](../SKILL.md)「金额与货币单位硬约束」。
16
16
 
17
+ > **消耗 ≠ 日消耗**:`ad campaigns` / `stats` / `accounts-digest` 的 **`spend` 是 `--start`~`--end` 合计**。单日熔断必须 `start=end=当日`;续航日均用 **`balance-scan.dailySpend`**,禁止把默认近 7 天 `spend` 当成「一天的花费」去比日预算。
18
+
17
19
  ## 异常监控与报警系统
18
20
 
19
21
  编排(定时、HTTP 探活、通知)在宿主;**读哪些 JSON 字段、用什么命令** 以 **[`references/operations/hosted-automation-monitoring-json.md`](references/operations/hosted-automation-monitoring-json.md)** 为准(键名以当次 `--json-out` 落盘 JSON 为准,勿套用 Google Ads API 官方字段名解析本 CLI 输出)。
@@ -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.37-beta.2'
12
+ $PKG_VERSION = '1.1.37-beta.3'
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.37-beta.2"
12
+ readonly PKG_VERSION="1.1.37-beta.3"
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.37-beta.2",
3
+ "version": "1.1.37-beta.3",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",