siluzan-tso-cli 1.1.14-beta.1 → 1.1.14-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,7 +53,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
53
53
  siluzan-tso init --force # 强制覆盖已存在文件
54
54
  ```
55
55
 
56
- > **注意**:当前为测试版(1.1.14-beta.1),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
56
+ > **注意**:当前为测试版(1.1.14-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
57
57
 
58
58
  | 助手 | 建议 `--ai` |
59
59
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -3676,18 +3676,25 @@ var VALID_MEDIA_TYPES2 = ["Google", "TikTok", "Yandex", "MetaAd", "BingV2", "Kwa
3676
3676
  async function runBalance(opts) {
3677
3677
  const config = loadConfig(opts.token);
3678
3678
  if (!VALID_MEDIA_TYPES2.includes(opts.media)) {
3679
- console.error(
3680
- `
3681
- \u274C \u4E0D\u652F\u6301\u7684\u5A92\u4F53\u7C7B\u578B\uFF1A${opts.media}
3682
- \u53EF\u9009\u503C\uFF1A${VALID_MEDIA_TYPES2.join(" | ")}
3683
- `
3684
- );
3679
+ const msg = `\u4E0D\u652F\u6301\u7684\u5A92\u4F53\u7C7B\u578B\uFF1A${opts.media}\uFF08\u53EF\u9009\uFF1A${VALID_MEDIA_TYPES2.join(" | ")}\uFF09`;
3680
+ if (opts.json) {
3681
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
3682
+ process.exit(1);
3683
+ }
3684
+ console.error(`
3685
+ \u274C ${msg}
3686
+ `);
3685
3687
  process.exit(1);
3686
3688
  }
3687
3689
  const media = opts.media;
3688
3690
  if (!BALANCE_SUPPORTED_MEDIA.includes(media)) {
3691
+ const msg = `${media} \u6682\u4E0D\u652F\u6301\u4F59\u989D\u67E5\u8BE2`;
3692
+ if (opts.json) {
3693
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
3694
+ process.exit(1);
3695
+ }
3689
3696
  console.error(`
3690
- \u26A0\uFE0F ${media} \u6682\u4E0D\u652F\u6301\u4F59\u989D\u67E5\u8BE2
3697
+ \u26A0\uFE0F ${msg}
3691
3698
  `);
3692
3699
  process.exit(1);
3693
3700
  }
@@ -3707,8 +3714,13 @@ async function runBalance(opts) {
3707
3714
  opts.verbose
3708
3715
  );
3709
3716
  } catch (err) {
3717
+ const message = err instanceof Error ? err.message : String(err);
3718
+ if (opts.json) {
3719
+ console.log(JSON.stringify({ ok: false, error: message }, null, 2));
3720
+ process.exit(1);
3721
+ }
3710
3722
  console.error(`
3711
- \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
3723
+ \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${message}
3712
3724
  `);
3713
3725
  process.exit(1);
3714
3726
  }
@@ -3948,18 +3960,54 @@ async function runBalanceScan(opts) {
3948
3960
  const CHUNK = 100;
3949
3961
  const chunks = [];
3950
3962
  for (let i = 0; i < validIds.length; i += CHUNK) chunks.push(validIds.slice(i, i + CHUNK));
3963
+ process.stderr.write(
3964
+ `\u23F3 [balance-scan] \u6709\u6548\u8D26\u6237 ${validIds.length} \u4E2A\uFF0C\u5206 ${chunks.length} \u6279\uFF1B\u4F59\u989D\u4E0E\u8FD1 7 \u65E5\u6D88\u8017\u5E76\u884C\u8BF7\u6C42\uFF08\u5355\u8BF7\u6C42\u6700\u957F\u7EA6 10 \u5206\u949F\uFF09\u3002
3965
+ `
3966
+ );
3967
+ const logBalanceChunk = (idx, ids, m) => {
3968
+ process.stderr.write(
3969
+ ` \u2713 [\u4F59\u989D] \u7B2C ${idx + 1}/${chunks.length} \u6279\u5B8C\u6210\uFF08${ids.length} \u6237 \u2192 ${m.size} \u6761\uFF09
3970
+ `
3971
+ );
3972
+ };
3973
+ const logOverviewChunk = (idx, ids, m) => {
3974
+ process.stderr.write(
3975
+ ` \u2713 [\u8FD17\u65E5\u6D88\u8017] \u7B2C ${idx + 1}/${chunks.length} \u6279\u5B8C\u6210\uFF08${ids.length} \u6237 \u2192 ${m.size} \u6761\uFF09
3976
+ `
3977
+ );
3978
+ };
3951
3979
  const [bMaps, oMaps] = await Promise.all([
3952
3980
  Promise.all(
3953
- chunks.map(
3954
- (ids) => fetchBalanceMap(media, ids, config, void 0, void 0, opts.verbose)
3955
- )
3981
+ chunks.map((ids, chunkIdx) => {
3982
+ process.stderr.write(
3983
+ ` \u2192 [\u4F59\u989D] \u7B2C ${chunkIdx + 1}/${chunks.length} \u6279\u8BF7\u6C42\u4E2D\uFF08${ids.length} \u6237\uFF09\u2026
3984
+ `
3985
+ );
3986
+ return fetchBalanceMap(media, ids, config, void 0, void 0, opts.verbose).then(
3987
+ (m) => {
3988
+ logBalanceChunk(chunkIdx, ids, m);
3989
+ return m;
3990
+ }
3991
+ );
3992
+ })
3956
3993
  ),
3957
3994
  Promise.all(
3958
- chunks.map(
3959
- (ids) => fetchOverviewMap(media, ids, config, void 0, void 0, opts.verbose)
3960
- )
3995
+ chunks.map((ids, chunkIdx) => {
3996
+ process.stderr.write(
3997
+ ` \u2192 [\u8FD17\u65E5\u6D88\u8017] \u7B2C ${chunkIdx + 1}/${chunks.length} \u6279\u8BF7\u6C42\u4E2D\uFF08${ids.length} \u6237\uFF09\u2026
3998
+ `
3999
+ );
4000
+ return fetchOverviewMap(media, ids, config, void 0, void 0, opts.verbose).then(
4001
+ (m) => {
4002
+ logOverviewChunk(chunkIdx, ids, m);
4003
+ return m;
4004
+ }
4005
+ );
4006
+ })
3961
4007
  )
3962
4008
  ]);
4009
+ process.stderr.write(`\u23F3 [balance-scan] \u4F59\u989D\u4E0E\u6D88\u8017\u5DF2\u9F50\uFF0C\u6B63\u5728\u6309\u9608\u503C\u7B5B\u9009\u2026
4010
+ `);
3963
4011
  for (const m of bMaps) for (const [k, v] of m) balanceMap.set(k, v);
3964
4012
  for (const m of oMaps) for (const [k, v] of m) overviewMap.set(k, v);
3965
4013
  }
@@ -4382,8 +4430,13 @@ async function runStats(opts) {
4382
4430
  try {
4383
4431
  raw = await apiFetch2(url, config, {}, opts.verbose);
4384
4432
  } catch (err) {
4433
+ const message = err instanceof Error ? err.message : String(err);
4434
+ if (opts.json) {
4435
+ console.log(JSON.stringify({ ok: false, error: message }, null, 2));
4436
+ process.exit(1);
4437
+ }
4385
4438
  console.error(`
4386
- \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
4439
+ \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${message}
4387
4440
  `);
4388
4441
  process.exit(1);
4389
4442
  }
@@ -9510,8 +9563,13 @@ async function fetchTikTokClues(opts, config) {
9510
9563
  );
9511
9564
  leads = Array.isArray(res) ? res : res.data ?? [];
9512
9565
  } catch (err) {
9566
+ const message = err instanceof Error ? err.message : String(err);
9567
+ if (opts.json) {
9568
+ console.log(JSON.stringify({ ok: false, error: message, items: [] }, null, 2));
9569
+ process.exit(1);
9570
+ }
9513
9571
  console.error(`
9514
- \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
9572
+ \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${message}
9515
9573
  `);
9516
9574
  process.exit(1);
9517
9575
  }
@@ -9558,8 +9616,13 @@ async function fetchMetaClues(opts, config) {
9558
9616
  try {
9559
9617
  raw = await apiFetch2(url, config, {}, opts.verbose);
9560
9618
  } catch (err) {
9619
+ const message = err instanceof Error ? err.message : String(err);
9620
+ if (opts.json) {
9621
+ console.log(JSON.stringify({ ok: false, error: message, items: [] }, null, 2));
9622
+ process.exit(1);
9623
+ }
9561
9624
  console.error(`
9562
- \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
9625
+ \u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${message}
9563
9626
  `);
9564
9627
  process.exit(1);
9565
9628
  }
@@ -12216,14 +12279,14 @@ program.command("accounts-digest").description(
12216
12279
  program.command("stats").description("\u67E5\u8BE2\u5E7F\u544A\u6D88\u8017\u3001\u70B9\u51FB\u3001\u8F6C\u5316\u7B49\u6295\u653E\u6570\u636E\uFF08\u9ED8\u8BA4\u8FD1 7 \u5929\uFF0C\u4E0D\u542B\u4ECA\u5929\uFF09").requiredOption(
12217
12280
  "-m, --media <type>",
12218
12281
  "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai"
12219
- ).option("-a, --accounts <ids>", "\u8D26\u6237 ID\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF08\u7559\u7A7A\u5219\u67E5\u5168\u90E8\u8D26\u6237\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4 7 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u6628\u5929\uFF09").option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u539F\u59CB\u54CD\u5E94", false).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
12282
+ ).option("-a, --accounts <ids>", "\u8D26\u6237 ID\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF08\u7559\u7A7A\u5219\u67E5\u5168\u90E8\u8D26\u6237\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4 7 \u5929\u524D\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F\uFF0C\u683C\u5F0F YYYY-MM-DD\uFF08\u9ED8\u8BA4\u6628\u5929\uFF09").option("--start-date <date>", "\u540C --start\uFF08\u6587\u6863/Playbook \u517C\u5BB9\u522B\u540D\uFF09").option("--end-date <date>", "\u540C --end\uFF08\u6587\u6863/Playbook \u517C\u5BB9\u522B\u540D\uFF09").option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u539F\u59CB\u54CD\u5E94", false).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
12220
12283
  async (opts) => {
12221
12284
  await runStats({
12222
12285
  token: opts.token,
12223
12286
  media: opts.media,
12224
12287
  accounts: opts.accounts,
12225
- startDate: opts.start,
12226
- endDate: opts.end,
12288
+ startDate: opts.start ?? opts.startDate,
12289
+ endDate: opts.end ?? opts.endDate,
12227
12290
  json: opts.json,
12228
12291
  verbose: opts.verbose
12229
12292
  });
@@ -126,7 +126,9 @@ allowed-tools: Bash(siluzan-tso:*) Read
126
126
  - **出报告时账户状态 ≠ 广告系列状态**:`stats` / `balance` / `list-accounts` 中的 `status`(如 Enabled)只表示**广告账户**关联/可用,**绝不能**据此填写或推断**各广告系列**是否「启用/在投」。已暂停或移除的系列若被写成启用,属于严重错误。系列是否启用**必须**来自 `ad campaigns`(或系列维报表中的系列状态)。详见 `references/account-analytics.md`「账户状态 ≠ 广告系列状态」。
127
127
  - **不确定时读文档**:遇到不熟悉的命令,先读对应 references 文件或使用-h查看命令帮助,不要猜参数。
128
128
  - **先查账户再操作**:对具体账户做操作前,先通过 `list-accounts -m [mediaType] -k [mediaCustomerId]` 确认。特别是不确定是Google/Bing/TikTok这些媒体平台中的哪一个的时候
129
- - **使用 --json 处理数据**:需对返回数据做计算或筛选时,加 `--json`,再用 `node -e` 过滤提取(见 `references/tips.md`)。
129
+ - **使用 --json 处理数据**:需对返回数据做计算或筛选时,加 `--json`,再用 `node -e` 过滤提取(见 `references/tips.md`)。若用户**已有一份 JSON**(文件或剪贴板),只问如何筛选时:优先给「stdin / 读本地文件 + `node -e`」的通用写法,不必默认再跑一遍业务命令。
130
+ - **CLI 输出忠实**:向用户引用 JSON 时,账户 ID、金额等须与**本次命令 stdout 一致**,不得换成别的示例 ID。禁止编造「stub/示例环境/登录异常」等**未在 CLI 输出或 stderr 中出现**的解释;`data` 为空时只说明「当前返回无记录」并附上原始 JSON。
131
+ - **用户明确要求原始 JSON**:须在回复中给出 CLI 返回的 JSON(或完整代码块),不得以长篇推测替代交付。
130
132
  - **不要猜测账户 ID**:`entityId` ≠ `mediaCustomerId`,两者均来自 `list-accounts`。
131
133
  - **媒体类型区分大小写**:`Google`、`TikTok`、`MetaAd`、`BingV2`、`Kwai`。
132
134
  - **命令透明性**:以简洁的方式向用户说明即将执行的操作意图(如「正在查询您的 Google 账户列表」「正在为账户 xxx 创建预警规则」),让用户了解操作进度。用户主动要求查看执行细节时,应如实提供完整命令。
@@ -142,9 +144,22 @@ allowed-tools: Bash(siluzan-tso:*) Read
142
144
  > (A)最近完整自然周(周一到周日)
143
145
  > (B)本月 1 号到昨天
144
146
  > (C)自定义起止日(请告诉我 `YYYY-MM-DD` 起止)
145
- 2. 用户给出范围后,**在报告首行显式标注"统计区间:YYYY-MM-DD ~ YYYY-MM-DD(时区:用户本地/UTC)"**,与调用参数保持完全一致。
147
+ 2. 用户给出范围后,**在报告首行显式标注** `统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:XXX)`(与 `references/account-analytics.md` 一致),与调用参数保持完全一致。
146
148
  3. **只有在用户明确说"按你默认来 / 你决定"**时,才使用下方默认值白名单。
147
149
 
150
+ ### 时间范围反问的例外(不要机械套用到下列任务)
151
+
152
+ - **`list-accounts` 全量**:用户要「所有 Google 账户」且 JSON 时,看返回里的 `total` / `page` / `pageSize`;若 `itemCount < total`,须翻页(增大 `--page-size` 或循环 `--page`)或说明**当前仅为第 N 页**,不得把单页当成全量结论。
153
+ - **「昨天」单日 stats**:用户已给出 Google `mediaCustomerId` 且只说「昨天」时:默认按 **`Asia/Shanghai` 日历日**换算昨日起止;**先** `list-accounts -m Google -k <id> --quick --json` **再** `stats -m Google -a <id> --start … --end … --json`,不要为时区再打断一轮(除非用户明确要求 UTC/其他时区)。
154
+ - **仅持有 `entityId`**:**禁止**把 `entityId` 传给 `stats -a` / `balance -a`。应先 `list-accounts`(必要时 `--json` + 本地筛选或 `-k` 缩小范围)解析出 `mediaCustomerId` 后再调 `stats`。
155
+ - **`forewarning records`**:见 `references/forewarning.md`,不要求先做投放类日期反问。
156
+ - **`invoice list` /「本月」**:见 `references/finance.md`,可用当月 1 日~昨天直接查。
157
+ - **TikTok `clue`「最近一周」**:见 `references/clue.md`,可直接 `--json` 查询,不要先做 A/B/C 日期口径反问。
158
+
159
+ ### 仅输出 JSON 的交付
160
+
161
+ 用户明确要求「**只输出**一个 JSON / 除 JSON 外不要文字」时:回复中**仅含一个** JSON 代码块,内容与本次 CLI **stdout 完全一致**(分页、失败体均如此);失败时 CLI 已保证 `--json` 下 stdout 仍为 JSON 对象。
162
+
148
163
  ### 默认值白名单(仅在用户明确授权"你决定"时才能使用)
149
164
 
150
165
  | 场景 | 允许的默认窗口 |
@@ -169,6 +184,7 @@ allowed-tools: Bash(siluzan-tso:*) Read
169
184
  2. `list-accounts` 返回的 `mag.advertiserName`
170
185
  3. 用户提供的网址 → 明确告诉用户"使用域名作为占位"(例如 `hy-steelpipe.com`)并在交付物里标注 `[待确认品牌名]`
171
186
  - **严禁**"hy-steelpipe.com"这样的英文域名被输出成类似"海悦钢管"这种虚构中文品牌。
187
+ - 正文里写出品牌名时,应能回溯到上述来源(可写「来自 list-accounts 的 `mag.advertiserName`:…」);**禁止**写看似真实但未在上述来源或 CLI 输出中出现的名称(包括常见 Demo 风格占位名)。
172
188
 
173
189
  ### 批量任务硬约束(≥ 5 个账户或系列)
174
190
 
@@ -209,6 +225,8 @@ allowed-tools: Bash(siluzan-tso:*) Read
209
225
  - 发票申请(`invoice apply`)— 涉及财务
210
226
  - 广告发布(`ad batch publish` / `ad campaign-create`)— 涉及预算消耗
211
227
  - **只读操作可自主执行**:查询类命令(`list-accounts`、`balance`、`stats`、`report list`、`config show` 等)可直接执行,无需额外确认
228
+ - **Google 广告结构性改动(否定词/系列/关键词等)**:须遵守 `references/google-ads.md` 开篇流程——先阅读规则文档、再列计划与将参考的文档清单;**仅在用户明确确认方案后**才可执行写命令。用户未确认前,不得使用「拿到参数就立刻执行」等措辞暗示可跳过确认。
229
+ - **Google 开户(CLI 指引)**:`open-account google-wizard` 仅适用于真实 TTY;**Agent/自动化环境一律用非交互** `open-account google ...`,审核进度用 `account-history`(详见 `references/workflows.md` 流程一)。向用户写步骤时,把上述禁令放在指引**最前**,再给可复制命令。
212
230
 
213
231
  ---
214
232
 
@@ -220,15 +238,16 @@ allowed-tools: Bash(siluzan-tso:*) Read
220
238
 
221
239
  > 触发关键词:某账户数据 / 投放情况 / 整理账户 / 看下某个账户的表现
222
240
 
223
- 1. **反问时间范围**(参见"时间范围强制反问")。
241
+ 1. **反问时间范围**(参见"时间范围强制反问";用户已授权「按默认」时用默认值白名单并写明区间)。
224
242
  2. `list-accounts -m Google -k <mediaCustomerId> --quick --json`
225
243
  - 一次拿齐:账户基础信息、创建日期、当前状态、公司名(`mag.advertiserName` 作为品牌名)
226
- 3. `stats --media Google --accounts <id> --start-date <S> --end-date <D> --json`
227
- - 拿该区间消耗、点击、转化等;**直接读响应中的主币种数值**,不要再 ×100。
228
- 4. `ad campaigns --account <id> --start-date <S> --end-date <D> --json`
244
+ 3. `siluzan-tso stats -m Google -a <mediaCustomerId> --start <S> --end <D> --json`(`--start-date` / `--end-date` 与 `--start` / `--end` 等价)
245
+ - 拿该区间消耗、点击、转化等;**直接读响应中的主币种数值**,不要再 ×100。若返回 `{"ok":false,"error":...}`,须原样展示并**不得**用系列数据编造账户级 stats
246
+ 4. `siluzan-tso ad campaigns -a <mediaCustomerId> --start <S> --end <D> --json`
229
247
  - 拿广告系列类型(Search / PMax / Display 等)、日预算(**用 `budgetDisplay`**)、优化得分相关字段。
230
248
  5. `stats` 结合 `accountsoverview` 字段派生"开始投放时间 / 有效投放天数 / 地区消耗分布"(如接口暂未直出,在 node 里聚合)。
231
249
  6. 用 `report-templates/google-account-diagnosis-report.md` 模板输出,**首行标注统计区间和货币**。
250
+ 7. 用户要求「先拉 JSON 再文字总结」时:回复中须包含实际 `--json` 输出(完整或明确声明截断),再写总结;**禁止**用未出现在这些 JSON / `list-accounts` 中的字段写品牌名或接口状态。
232
251
 
233
252
  ### P2 · 多账户余额扫描 / 预算预警(典型指令:"117 个 Bing 账户不足 7 天的挑出来")
234
253
 
@@ -279,8 +298,9 @@ siluzan-tso accounts-digest -m Google \
279
298
 
280
299
  1. **反问时间范围**(P 级硬约束),拿到用户回复后再执行 `accounts-digest`。
281
300
  2. 如命令已返回 `--json`,直接基于其中 `data.items` 与 `meta.totals` 生成报告;**不要**再逐账户 `stats`。
282
- 3. 跨币种账户:按 `item.currencyCode` 分表或在 meta.currencyNote 提示的前提下分币种小计。
283
- 4. 金额字段严格按"金额与货币单位硬约束"处理。
301
+ 3. **多账户 `-a id1,id2,...` 时**:表格须覆盖用户请求的**每一个** ID;若某 ID 在 `data.items` 中未出现,仍占一行并标注「未返回/无数据」,并说明与 `meta.totals` 的关系;**禁止**只展示子集却声称已完成多账户汇总。
302
+ 4. 跨币种账户:按 `item.currencyCode` 分表或在 meta.currencyNote 提示的前提下分币种小计。
303
+ 5. 金额字段严格按"金额与货币单位硬约束"处理。
284
304
 
285
305
  ### P4 · Google 账户周期报告(典型指令:"生成 2026.1.1-2026.4.15 的报告")
286
306
 
@@ -289,6 +309,7 @@ siluzan-tso accounts-digest -m Google \
289
309
  3. 使用 `report-templates/google-period-report.md` 模板输出。
290
310
  4. 首行必须有:`统计区间:2026-01-01 ~ 2026-04-15` + `货币:XXX`。
291
311
  5. 报告必须包含:账户概览、投放趋势、Top 关键词/系列 / 地区分布 / 优化建议;不得编造未拉取到的指标(例如没拉取到的关键词就写"未提供"而不是估算)。
312
+ 6. 品牌名遵守上文「品牌名 / 公司名来源硬约束」;描述系列启停时只引用 `ad campaigns`(或系列维报表)中的系列级状态字段,勿与账户 `status` 混用。
292
313
 
293
314
  ---
294
315
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.14-beta.1",
4
- "publishedAt": 1776765041399
3
+ "version": "1.1.14-beta.2",
4
+ "publishedAt": 1776838977656
5
5
  }
@@ -16,7 +16,7 @@
16
16
  5. 输出 HTML 时:**默认**以 `report-templates/report-template.html` 为样式基准(适用于一切总结性、报告性、汇总性成稿);若场景更适合作正式件、深色、单页等,再从 `report-templates/report-template*.html` 中选或询问用户,并对照 `report-templates/README.md`
17
17
  6. 注意最终交付的是用html生成的PDF
18
18
  7. 使用浏览器或能够打开html/pdf的插件帮用户打开报告
19
- 8. **报告首行**必须标注:`统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:XXX)`,与实际调用 `--start` / `--end` 一致。
19
+ 8. **报告首行**必须标注:`统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:XXX)`,与实际调用 `--start` / `--end` 一致。**禁止**用「上周 / 本月 / 近 7 天」等口语替代首行日期;若用户口语指区间,先在首行写换算后的起止日。
20
20
 
21
21
  用于按账户维度拉取 Google Ads 报表、结构类数据。
22
22
 
@@ -86,6 +86,8 @@
86
86
 
87
87
  使用已配置的 `googleApiUrl` 与 Token,无需手写 curl。
88
88
 
89
+ > **路由(重要)**:用户要「关键词花费 / 点击 / 转化 / CPA」等**搜索关键词维度**表现时,须用 **`siluzan-tso google-analysis keywords -a <mediaCustomerId> [--start/--end] --json`**。不要用 `ad keywords` 代替(后者偏结构/出价与列表字段,与命题中的「账户内关键词报表」不是同一路径)。
90
+
89
91
  ```text
90
92
  siluzan-tso google-analysis <子命令> -a <mediaCustomerId> [选项]
91
93
  ```
@@ -133,6 +135,8 @@ siluzan-tso google-analysis keywords -a 6326027735 --limit 50
133
135
  siluzan-tso google-analysis final-urls -a 6326027735 --json
134
136
  ```
135
137
 
138
+ **CPC / 花费异常巡检(先查后停)**:用户要先定位异常再考虑暂停时,在确认统计区间后,优先用 `google-analysis` 拉**带花费/CPC 粒度**的数据(如 `campaigns`、`daily-metrics`、`keywords` 等,按 CLI `siluzan-tso google-analysis --help` 选择子命令),**不要**在未拉数前就把暂停阈值与写操作一并推进;暂停类命令须遵守 `references/google-ads.md` 的确认流程。
139
+
136
140
  ---
137
141
 
138
142
  ## Meta 账户分析总览(TSO)
@@ -108,7 +108,7 @@ siluzan-tso balance -m <媒体类型> -a <账户ID列表>
108
108
  | ---------------------- | ------------------------------------------------------------------------------------ |
109
109
  | `-m, --media <type>` | 媒体类型(必填) |
110
110
  | `-a, --accounts <ids>` | 账户 `mediaCustomerId`(数字 ID),多个用逗号分隔(必填)。**注意:不是 `entityId`** |
111
- | `--json` | 输出原始 JSON |
111
+ | `--json` | 输出原始 JSON;不支持或查询失败时 stdout 为 `{"ok":false,"error":"..."}` |
112
112
 
113
113
  **示例:**
114
114
 
@@ -123,6 +123,8 @@ siluzan-tso balance -m TikTok -a 1234567890,9876543210
123
123
  siluzan-tso balance -m Google -a 6326027735 --json
124
124
  ```
125
125
 
126
+ **单户余额与续航**:`balance` 只反映当前余额;判断「还能跑几天 / 是否够花」需结合 `stats`(或业务侧日均消耗)等数据。向用户展示 JSON 时,`mediaCustomerId` 须与本次 `-a` 查询的 ID 及命令输出一致。
127
+
126
128
  ---
127
129
 
128
130
  ## stats — 查询投放消耗数据
@@ -137,7 +139,8 @@ siluzan-tso stats -m <媒体类型> [选项]
137
139
  | `-a, --accounts <ids>` | 账户 `mediaCustomerId`(数字 ID),逗号分隔(**必填**,接口不支持查全部账户) | — |
138
140
  | `--start <YYYY-MM-DD>` | 开始日期 | 7 天前 |
139
141
  | `--end <YYYY-MM-DD>` | 结束日期 | 昨天 |
140
- | `--json` | 输出原始 JSON | — |
142
+ | `--start-date` / `--end-date` | `--start` / `--end` 同义(CLI 别名,与 SKILL Playbook 一致) | — |
143
+ | `--json` | 输出原始 JSON;**失败时 stdout 仍为 JSON**(`{"ok":false,"error":"..."}`) | — |
141
144
 
142
145
  **示例:**
143
146
 
@@ -19,6 +19,11 @@ siluzan-tso clue -m <媒体> -a <账户ID> [选项]
19
19
  | `--end <date>` | Meta 专用:结束日期(YYYY-MM-DD) |
20
20
  | `--json` | 输出原始 JSON |
21
21
 
22
+ **AI 交付**:用户要求「原始 JSON / 自己筛」时,回复中须包含 **`--json` 命令打印的完整 JSON**(或等价完整代码块),并可按上表说明 `custom_fields` / `system_fields`(TikTok)或 `field_data`(Meta)。**禁止**用未出现在本次 CLI 输出中的账户 ID、媒体或「环境异常」类推测替代 JSON 交付。
23
+ 若本次查询失败:CLI 在 `--json` 下会输出 **`{"ok":false,"error":"...","items":[]}`**(stdout),请**原样**贴出该 JSON,不要改成纯文字描述。
24
+
25
+ **时间范围(TikTok)**:用户说「最近一周」且要拉线索、未给起止日时,**不要**再按投放报表类任务做 A/B/C 反问;直接按 CLI 默认窗口执行 `clue -m TikTok -a <advertiserId> --json`(需自定义区间时再用 Meta 同款 `--start/--end` 仅适用于 Meta,TikTok 以接口返回为准)。
26
+
22
27
  **TikTok 示例:**
23
28
 
24
29
  ```bash
@@ -24,6 +24,8 @@ siluzan-tso invoice-info list -k "公司名关键字" # 按公司名搜
24
24
  siluzan-tso invoice-info list --json # 原始 JSON(含 id 字段)
25
25
  ```
26
26
 
27
+ 查询结果若 `data` 为空数组或无可核对字段,表示当前账号下**尚无已保存抬头**:向用户展示**原始 JSON** 并如实说明即可,勿推测「测试桩」等未经 CLI 报错佐证的原因。
28
+
27
29
  ### 新增发票抬头
28
30
 
29
31
  **PI(形式发票,境外英文):**
@@ -208,6 +210,8 @@ siluzan-tso transfer create -m Google --out 1234567890 --in 9876543210 --amount
208
210
  siluzan-tso invoice list [选项]
209
211
  ```
210
212
 
213
+ > **与投放报表类任务不同**:用户只说「本月开票记录」而未写起止日时,可用**当月 1 日 ~ 昨天**(`Asia/Shanghai` 日历)直接执行 `invoice list --start … --end … --json`,**不必**先做 SKILL「投放数据」类的时间范围 A/B/C 反问;若用户随后纠正区间再重查即可。
214
+
211
215
  | 选项 | 说明 |
212
216
  | ------------------------ | ---------------------- |
213
217
  | `-k, --keyword <text>` | 发票号/关键字 |
@@ -135,6 +135,8 @@ siluzan-tso forewarning list -m Google
135
135
  # JSON 格式(含完整配置)
136
136
  siluzan-tso forewarning list -m Google --json
137
137
 
138
+ 若用户要求「仅 JSON / 只输出一个 JSON 对象」,回复中只贴本次命令 stdout(含 `total` > `itemCount` 时的分页说明亦在 JSON 内,勿在 JSON 外再写中文解释)。
139
+
138
140
  # 按账户筛选
139
141
  siluzan-tso forewarning list -m Google --account <mediaCustomerId>
140
142
 
@@ -146,6 +148,8 @@ siluzan-tso forewarning list -m Google --keyword "消耗"
146
148
 
147
149
  ## 查询触发记录
148
150
 
151
+ > **不属于「投放数据时间范围强制反问」**:用户要查「最近有没有触发记录」并要 JSON 时,**直接**执行 `forewarning records -m Google --json`(可加 `--rule-id` / `--status`);不要先套用 SKILL 里针对消耗/报表的日期反问,也不要强行索要 Google `mediaCustomerId`(本命令不按媒体账户筛)。
152
+
149
153
  ```bash
150
154
  # 查询所有规则的触发记录
151
155
  siluzan-tso forewarning records -m Google
@@ -158,6 +162,8 @@ siluzan-tso forewarning records -m Google --status Success
158
162
  siluzan-tso forewarning records -m Google --status Failed
159
163
  ```
160
164
 
165
+ **创建规则(AI)**:用户已给出阈值、频率、媒体并确认要创建时:① `list-accounts -m Google -k <mediaCustomerId> --json` 取账户 **`entityId`**(`forewarning create --accounts` 用);② `forewarning notify-accounts` 取 **`--notify`** 的微信对象 `entityId`;③ 再执行 `forewarning create ...`。创建属写操作,须用户明确确认后再跑命令。
166
+
161
167
  ---
162
168
 
163
169
  ## 更新已有规则
@@ -32,6 +32,7 @@
32
32
  > - 若当前会话中尚未阅读上述任一文件,AI 必须先主动阅读,再继续下一步流程,而不是直接生成广告计划或文案。
33
33
  > - 在首次阅读后,AI 需用自己的话向用户**简要复述**上述文档中与本次任务强相关的 3~5 条关键合规/策略要点,并询问用户是否有本地特殊限制需要补充。
34
34
  > - 后续生成的关键词、文案、出价与结构,必须**显式遵守这些规则**;一旦与规则冲突,应以规则优先,并向用户说明原因(例如:某些词因合规或商标问题被自动剔除)。
35
+ > - **方案阶段措辞**:输出结构/投放方案时,用「方案草案 / 结构蓝图 / 待你确认后再执行」等表述;**避免**「可立即执行 / 拿到参数就立刻跑命令」等暗示可跳过用户确认的说法。回复开头宜有一行**已读规则文档清单**(本 `google-ads.md` + 上表 `references/google-ads-rules/` 中实际打开的文件名)。
35
36
 
36
37
  ### 第二步:向用户补齐关键信息
37
38
 
@@ -27,6 +27,8 @@ siluzan-tso report list -m <媒体> [选项]
27
27
  | `--start / --end <date>` | 日期范围(YYYY-MM-DD) |
28
28
  | `--json` | 输出原始 JSON |
29
29
 
30
+ **仅 JSON 交付**:若用户明确要求「只输出一个 JSON / 不要解释」,回复中**只放一个** JSON 代码块(与本次 CLI stdout 一致),前后不加说明文字;分页结果以 JSON 内 `page` / `pageSize` / `total` / `itemCount` 为准,需全量时再翻页执行。
31
+
30
32
  **示例:**
31
33
 
32
34
  ```bash
@@ -5,6 +5,12 @@
5
5
 
6
6
  ---
7
7
 
8
+ ## 已有 JSON 时(不必先重跑 CLI)
9
+
10
+ 用户已保存输出或只问「怎么从一大坨 JSON 里筛字段」时:直接用 **stdin / 本地文件** 喂给 `node -e` 即可,不必为示例再执行 `list-accounts` 等业务命令(字段路径以实际响应为准,常见为 `data.items` 或顶层数组)。
11
+
12
+ ---
13
+
8
14
  ## 基础模式:`--json` + `node -e` 管道
9
15
 
10
16
  ### 过滤特定字段
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.14-beta.1",
3
+ "version": "1.1.14-beta.2",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",