siluzan-tso-cli 1.1.37-beta.17 → 1.1.37-beta.19

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.17),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.37-beta.19),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -2202,10 +2202,23 @@ function skipAuthSetupInAgentEnv(commandLabel) {
2202
2202
  }
2203
2203
  return true;
2204
2204
  }
2205
- function blockMediaAccountOAuthInAgentEnv(_commandLabel) {
2206
- if (!isSiluzanAgentEnv()) return false;
2207
- console.log("\u8BF7\u4F7F\u7528 present_reauth \u5B8C\u6210\u6388\u6743");
2208
- return true;
2205
+ function tipPreferAgentNativeMediaOAuth(kind) {
2206
+ if (!isSiluzanAgentEnv()) return;
2207
+ if (kind === "reauth") {
2208
+ console.log(
2209
+ `
2210
+ \u{1F4A1} Siluzan Agent \u73AF\u5883\uFF1A\u4F18\u5148\u4F7F\u7528 Agent \u81EA\u5E26\u7684**\u91CD\u65B0\u6388\u6743**\u5DE5\u5177\uFF08\u5982 present_reauth\uFF09\u5B8C\u6210\u5A92\u4F53\u8D26\u6237 OAuth\u3002
2211
+ CLI \`account reauth\` \u4ECD\u53EF\u7EE7\u7EED\u6267\u884C\uFF08\u5907\u9009\uFF09\uFF1B\u6C99\u7BB1\u4E0D\u4F1A\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF0C\u987B\u628A\u5B8C\u6574\u6388\u6743 URL \u4EA4\u7ED9\u7528\u6237\u3002
2212
+ `
2213
+ );
2214
+ return;
2215
+ }
2216
+ console.log(
2217
+ `
2218
+ \u{1F4A1} Siluzan Agent \u73AF\u5883\uFF1A\u4F18\u5148\u4F7F\u7528 Agent \u81EA\u5E26\u7684**\u6388\u6743 / \u6DFB\u52A0\u6388\u6743**\u5DE5\u5177\u5B8C\u6210\u5A92\u4F53\u8D26\u6237 OAuth\u3002
2219
+ CLI \`account auth\` \u4ECD\u53EF\u7EE7\u7EED\u6267\u884C\uFF08\u5907\u9009\uFF09\uFF1B\u6C99\u7BB1\u4E0D\u4F1A\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF0C\u987B\u628A\u5B8C\u6574\u6388\u6743 URL \u4EA4\u7ED9\u7528\u6237\u3002
2220
+ `
2221
+ );
2209
2222
  }
2210
2223
  function trimOrUndefined(value) {
2211
2224
  const trimmed = value?.trim();
@@ -4396,6 +4409,20 @@ function normalizeNumericMediaCustomerId(raw) {
4396
4409
  if (!looksLikeNumericMediaCustomerId(raw)) return null;
4397
4410
  return stripMediaCustomerIdDecorators(raw);
4398
4411
  }
4412
+ function looksLikeEntityIdUuid(raw) {
4413
+ return ENTITY_ID_UUID_RE.test(raw.trim());
4414
+ }
4415
+ function warnIfAccountIdsLookLikeEntityId(ids, command) {
4416
+ const bad = ids.map((id) => id.trim()).filter((id) => id && looksLikeEntityIdUuid(id));
4417
+ if (bad.length === 0) return;
4418
+ console.error(
4419
+ `
4420
+ \u26A0\uFE0F ${command} \u7684 -a/--accounts \u6536\u5230\u7591\u4F3C entityId/UUID\uFF1A${bad.join(", ")}
4421
+ \u672C\u547D\u4EE4\u9700\u8981 mediaCustomerId\uFF08list-accounts \u7684 ma.mediaCustomerId\uFF1BYandex \u5F62\u5982 porg-\u2026\uFF09\u3002
4422
+ \u8BF7\u5148 list-accounts -m <\u5A92\u4F53> -k <\u5A92\u4F53\u8D26\u6237\u53F7> \u6838\u5BF9\u540E\u518D\u91CD\u8BD5\uFF1B\u52FF\u636E\u6B64\u76F4\u63A5 reauth\u3002
4423
+ `
4424
+ );
4425
+ }
4399
4426
  function parseNumericMediaCustomerIds(input) {
4400
4427
  if (!input || !input.trim()) return [];
4401
4428
  const seen = /* @__PURE__ */ new Set();
@@ -4413,9 +4440,11 @@ function parseNumericMediaCustomerIds(input) {
4413
4440
  }
4414
4441
  return out;
4415
4442
  }
4443
+ var ENTITY_ID_UUID_RE;
4416
4444
  var init_media_customer_id = __esm({
4417
4445
  "src/utils/media-customer-id.ts"() {
4418
4446
  "use strict";
4447
+ ENTITY_ID_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4419
4448
  }
4420
4449
  });
4421
4450
 
@@ -106407,6 +106436,7 @@ async function runBalance(opts) {
106407
106436
  console.error("\n\u274C \u8BF7\u901A\u8FC7 --accounts \u63D0\u4F9B\u81F3\u5C11\u4E00\u4E2A\u8D26\u6237 ID\uFF08\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF09\n");
106408
106437
  process.exit(1);
106409
106438
  }
106439
+ warnIfAccountIdsLookLikeEntityId(accountIds, "balance");
106410
106440
  let balanceMap;
106411
106441
  try {
106412
106442
  balanceMap = await fetchBalanceMap(
@@ -106492,7 +106522,7 @@ function register8(program2) {
106492
106522
  "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai"
106493
106523
  ).requiredOption(
106494
106524
  "-a, --accounts <ids>",
106495
- "\u8D26\u6237 ID\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF08\u6765\u81EA list-accounts \u7684 mediaCustomerId\uFF09"
106525
+ "\u8D26\u6237 mediaCustomerId\uFF08list-accounts \u7684 ma.mediaCustomerId\uFF1BYandex \u5F62\u5982 porg-\u2026\uFF1B\u7981\u6B62 entityId/UUID\uFF09\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694"
106496
106526
  ).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option("--start-date <date>", "\u7EDF\u8BA1\u5F00\u59CB\u65E5\u671F yyyy-MM-dd\uFF08\u9ED8\u8BA4\uFF1A7 \u5929\u524D\uFF0CMetaAd \u4E0D\u9700\u8981\uFF09").option("--end-date <date>", "\u7EDF\u8BA1\u7ED3\u675F\u65E5\u671F yyyy-MM-dd\uFF08\u9ED8\u8BA4\uFF1A\u4ECA\u5929\uFF0CMetaAd \u4E0D\u9700\u8981\uFF09").option(
106497
106527
  "--json-out <path>",
106498
106528
  "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
@@ -107120,6 +107150,7 @@ async function runStats(opts) {
107120
107150
  process.exit(1);
107121
107151
  }
107122
107152
  const ids = opts.accounts.split(",").map((id) => id.trim()).filter(Boolean).map((id) => normalizeNumericMediaCustomerId(id) ?? id);
107153
+ warnIfAccountIdsLookLikeEntityId(ids, "stats");
107123
107154
  let items2;
107124
107155
  try {
107125
107156
  if (opts.byDay) {
@@ -107260,7 +107291,10 @@ function register11(program2) {
107260
107291
  ).requiredOption(
107261
107292
  "-m, --media <type>",
107262
107293
  "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2 | Kwai"
107263
- ).option("-a, --accounts <ids>", "\u8D26\u6237 mediaCustomerId\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF08\u5FC5\u586B\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(
107294
+ ).option(
107295
+ "-a, --accounts <ids>",
107296
+ "\u8D26\u6237 mediaCustomerId\uFF08list-accounts \u7684 ma.mediaCustomerId\uFF1BYandex \u5F62\u5982 porg-\u2026\uFF1B\u7981\u6B62 entityId/UUID\uFF09\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u5206\u9694\uFF08\u5FC5\u586B\uFF09"
107297
+ ).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(
107264
107298
  "--by-day",
107265
107299
  "\u6309\u65E5\u660E\u7EC6\uFF1A\u5BF9\u533A\u95F4\u5185\u6BCF\u4E00\u5929\u5206\u522B\u62C9\u6570\uFF0Citems[] \u6BCF\u884C\u5E26 date\uFF1B\u5BFC\u51FA Excel/\u6309\u65E5\u8D8B\u52BF\u5FC5\u52A0\u3002\u9ED8\u8BA4\u4E0D\u52A0\u5219\u53EA\u8FD4\u56DE\u533A\u95F4\u5408\u8BA1\u4E00\u884C",
107266
107300
  false
@@ -121943,7 +121977,10 @@ async function runAccountMccUnbind(opts) {
121943
121977
  // src/commands/account-manage/google-access-permission.ts
121944
121978
  function hintGoogleOAuth(kind) {
121945
121979
  if (isSiluzanAgentEnv()) {
121946
- return "\u8BF7\u4F7F\u7528 present_reauth \u5B8C\u6210\u6388\u6743";
121980
+ if (kind === "reauth") {
121981
+ return "\u4F18\u5148\u4F7F\u7528 Agent \u81EA\u5E26\u91CD\u65B0\u6388\u6743\u5DE5\u5177\uFF08present_reauth\uFF09\uFF1B\u5907\u9009\uFF1Alist-accounts \u53D6 ma.entityId \u540E siluzan-tso account reauth -m Google --id <entityId>";
121982
+ }
121983
+ return "\u4F18\u5148\u4F7F\u7528 Agent \u81EA\u5E26\u6388\u6743\u5DE5\u5177\uFF1B\u5907\u9009\uFF1Asiluzan-tso account auth -m Google";
121947
121984
  }
121948
121985
  if (kind === "reauth") {
121949
121986
  return "list-accounts --json-out \u53D6 ma.entityId \u540E\u6267\u884C\uFF1Asiluzan-tso account reauth -m Google --id <entityId>";
@@ -122290,8 +122327,8 @@ function toApiMediaType(media) {
122290
122327
  }
122291
122328
  var tryOpenBrowser = tryOpenHttpsUrlInBrowser;
122292
122329
  async function runAccountAuth(opts) {
122293
- if (blockMediaAccountOAuthInAgentEnv("account auth")) {
122294
- process.exit(1);
122330
+ if (!opts.skipAgentOAuthTip) {
122331
+ tipPreferAgentNativeMediaOAuth("auth");
122295
122332
  }
122296
122333
  const config = loadConfig(opts.token);
122297
122334
  const apiMediaType = toApiMediaType(opts.media);
@@ -122324,9 +122361,7 @@ async function runAccountAuth(opts) {
122324
122361
  });
122325
122362
  }
122326
122363
  async function runAccountReauth(opts) {
122327
- if (blockMediaAccountOAuthInAgentEnv("account reauth")) {
122328
- process.exit(1);
122329
- }
122364
+ tipPreferAgentNativeMediaOAuth("reauth");
122330
122365
  if (!opts.id && (!opts.ids || opts.ids.length === 0)) {
122331
122366
  console.error(
122332
122367
  "\n\u274C \u91CD\u65B0\u6388\u6743\u987B\u6307\u5B9A\u8981\u89E3\u7ED1\u7684\u8D26\u6237 entityId\uFF1A--id <entityId> \u6216 --ids id1,id2\n entityId \u6765\u81EA list-accounts --json-out \u7684 ma.entityId\uFF08\u4E0D\u662F mediaCustomerId\uFF09\n"
@@ -122349,7 +122384,8 @@ async function runAccountReauth(opts) {
122349
122384
  await runAccountAuth({
122350
122385
  token: opts.token,
122351
122386
  media: opts.media,
122352
- verbose: opts.verbose
122387
+ verbose: opts.verbose,
122388
+ skipAgentOAuthTip: true
122353
122389
  });
122354
122390
  }
122355
122391
 
@@ -44,7 +44,7 @@ siluzan-tso -h
44
44
 
45
45
  ## 即时规范
46
46
 
47
- - `entityId`(UUID)≠ `mediaCustomerId`(媒体数字 ID);MetaAd 列表 ID 常带 `act_`。
47
+ - `entityId`(UUID)≠ `mediaCustomerId`(`list-accounts` 的 `ma.mediaCustomerId`:Google/TikTok/Bing 多为数字;**Yandex=`porg-…`**;Meta 常带 `act_`)。`stats`/`balance`/`accounts-digest` 的 `-a` **只传 mediaCustomerId**;空结果或 verbose 打出 403 时**先核验 ID**,禁止把 UUID/`entityId` 传给 `-a`,也**禁止**据此直接 `reauth`。
48
48
  - `list-accounts` 无余额/消耗;列全部用 `--page-size 999`。
49
49
  - 多户余额用 `balance-scan`(P2),多户消耗用 `accounts-digest`(P3);禁止外层 for-loop 逐户拉数。
50
50
  - `stats` 的 `spend` = **区间合计**,不是日消耗。
@@ -75,7 +75,7 @@ siluzan-tso -h
75
75
 
76
76
  | 用户意图(关键词) | 工作流 | 必读文档 |
77
77
  | ------------------ | ------ | -------- |
78
- | 新建搜索系列 / 出投放方案 / Excel·表格方案 / 官网生成搜索广告 | **W3** | `references/google-ads/google-ads-campaign-plan.md` + **`assets/campaign-create-template.json`**(先 Read)+ `assets/campaign-create-template.md`;有方案文件时加 `references/google-ads/rules/google-ads-plan-source-fidelity.md`。**≠ P8 / ≠ P9 / ≠ W5** |
78
+ | 新建搜索系列 / 出投放方案 / Excel·表格方案 / 官网生成搜索广告 | **W3** | `references/google-ads/google-ads-campaign-plan.md`(§**仅出方案 vs 创建**:无账户也可先出方案)+ **`assets/campaign-create-template.json`**(先 Read)+ `assets/campaign-create-template.md`;有方案文件时加 `references/google-ads/rules/google-ads-plan-source-fidelity.md`。**≠ P8 / ≠ P9 / ≠ W5**;**禁止**因缺账户阻塞出方案 |
79
79
  | 广告系列/组/广告/关键词 **查询** / 拒审字段 | W3 | `references/google-ads/google-ads-read.md` |
80
80
  | 广告系列/组/广告/关键词 **创建·编辑·启停** | W3 | `references/google-ads/google-ads-write.md` |
81
81
  | PMax 系列 | W3 | **`assets/pmax-create-template.json`**(先 Read)+ `assets/pmax-create-template.md` + `references/google-ads/pmax-api.md` + `google-ads-write.md`(PMax 节) |
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.37-beta.17",
4
- "publishedAt": 1784626943255
3
+ "version": "1.1.37-beta.19",
4
+ "publishedAt": 1784691655606
5
5
  }
@@ -123,7 +123,7 @@ siluzan-tso ad batch diff --batch-id <taskId> --config-file ./campaign.json --js
123
123
 
124
124
  | 字段 | 类型 | 必填 | 说明 |
125
125
  | -------------------- | -------------- | :--: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
126
- | `account` | string | ✅ | 媒体账户 ID;提交时转为数字 `customerId`(勿依赖引号字符串) |
126
+ | `account` | string | ✅ | 媒体账户 ID;提交时转为数字 `customerId`(勿依赖引号字符串)。**仅出方案**(未确认创建、无账户)可填 `"[PENDING_ACCOUNT]"` 或 `""`,见 `google-ads-campaign-plan.md` §仅出方案;**create/validate 前**须换成真实 `mediaCustomerId` |
127
127
  | `customerName` | string | | 展示/智投用客户名;**可省略**——`campaign-create` / `batch publish` 会按 `account` 调 `list-accounts` 自动填入 `mediaCustomerName`。若填写则须与之一致,否则提交时 CLI 自动更正 |
128
128
  | `name` | string | | 智投 `campaignName`;缺省取 `campaign.Name`;账户内不得与已有在投/暂停系列重名,否则 BatchJob 系列创建失败 |
129
129
  | `url` | string | | 智投展示用 URL;后端只读,用于回显 |
@@ -56,7 +56,7 @@ siluzan-tso balance -m <媒体类型> -a <账户ID列表>
56
56
  | 选项 | 说明 |
57
57
  | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
58
58
  | `-m, --media <type>` | 媒体类型(必填):`Google \| TikTok \| Yandex \| MetaAd \| BingV2 \| Kwai`(MetaAd 走 `GetMediaAccountInfo`,余额字段为 `spend_cap`) |
59
- | `-a, --accounts <ids>` | 账户 `mediaCustomerId`(数字 ID),多个用逗号分隔(必填)。**注意:不是 `entityId`** |
59
+ | `-a, --accounts <ids>` | 账户 `mediaCustomerId`(来自 `list-accounts` 的 `ma.mediaCustomerId`),逗号分隔(必填)。**禁止**传 `entityId` / tokenId / 其它 UUID;Yandex 须传 `porg-…` |
60
60
  | `--json-out` | 输出原始 JSON;不支持或查询失败时 stdout 为 `{"ok":false,"error":"..."}` |
61
61
 
62
62
  **示例:**
@@ -65,6 +65,9 @@ siluzan-tso balance -m <媒体类型> -a <账户ID列表>
65
65
  # 查询单个 Google 账户余额(传 mediaCustomerId)
66
66
  siluzan-tso balance -m Google -a 6326027735
67
67
 
68
+ # Yandex:传 porg-…(mediaCustomerId),禁止 entityId/UUID
69
+ siluzan-tso balance -m Yandex -a porg-kqquuxx6
70
+
68
71
  # 查询多个 TikTok 账户余额
69
72
  siluzan-tso balance -m TikTok -a 1234567890,9876543210
70
73
 
@@ -162,7 +165,7 @@ siluzan-tso stats -m <媒体类型> [选项]
162
165
  | 选项 | 说明 | 默认 |
163
166
  | ----------------------------- | ----------------------------------------------------------------------------- | ------ |
164
167
  | `-m, --media <type>` | 媒体类型(必填) | — |
165
- | `-a, --accounts <ids>` | 账户 `mediaCustomerId`(数字 ID),逗号分隔(**必填**,接口不支持查全部账户) | — |
168
+ | `-a, --accounts <ids>` | 账户 `mediaCustomerId`(**必填**;与 `list-accounts` 的 `ma.mediaCustomerId` 一致;Yandex=`porg-…`;**禁止** UUID/`entityId`) | — |
166
169
  | `--start <YYYY-MM-DD>` | 开始日期 | 7 天前 |
167
170
  | `--end <YYYY-MM-DD>` | 结束日期 | 昨天 |
168
171
  | `--start-date` / `--end-date` | 与 `--start` / `--end` 同义(CLI 别名,与 SKILL Playbook 一致) | — |
@@ -171,14 +174,24 @@ siluzan-tso stats -m <媒体类型> [选项]
171
174
 
172
175
  **口径(易错)**:默认 **`spend` = 区间合计**(非日消耗)。按日加 `--by-day`;单日可用 `start=end`;日均用 `spend/天数` 或 `balance-scan` 的 `dailySpend`。
173
176
 
177
+ **空结果 / verbose `HTTP 403` 排查(必做,再谈 OAuth)**:
178
+
179
+ 1. 用户若已给出账户号(如 Yandex `porg-kqquuxx6`),`-a` **必须原样用该 mediaCustomerId**;先 `list-accounts -m <媒体> -k <mediaCustomerId>` 核验存在即可。
180
+ 2. **禁止**把 `ma.entityId`、`externalMediaAccountTokenId` 或会话里其它 UUID 传给 `-a`(会空结果,verbose 常打出被吞的 `HTTP 403`,**不等于** OAuth 过期)。
181
+ 3. 仅当 `list-accounts` 显示 `invalidOAuthToken=true`(或授权状态列为失效),且用户确认后,才走 `account reauth --id <entityId> --i-confirm --commit "…"`(见 `accounts-permissions.md`)。
182
+ 4. `list-accounts` 显示 `Linked` + `hasToken:1` + `invalidOAuthToken:false` 时,**禁止**因 stats 空结果自行 `reauth`/解绑。
183
+
174
184
  **示例:**
175
185
 
176
186
  ```bash
177
- siluzan-tso stats -m Google -a <id>
178
- siluzan-tso stats -m Google -a <id> --start 2026-03-01 --end 2026-03-31
179
- siluzan-tso stats -m Yandex -a <id> --start <S> --end <E> --by-day --json-out ./snap/daily.json
187
+ siluzan-tso stats -m Google -a <mediaCustomerId>
188
+ siluzan-tso stats -m Google -a <mediaCustomerId> --start 2026-03-01 --end 2026-03-31
189
+ # Yandex:-a 必须是 porg-…(mediaCustomerId),不是 entityId
190
+ siluzan-tso list-accounts -m Yandex -k porg-kqquuxx6 --json-out ./snap
191
+ siluzan-tso stats -m Yandex -a porg-kqquuxx6 --start 2026-07-21 --end 2026-07-21 --json-out ./snap
192
+ siluzan-tso stats -m Yandex -a porg-xxx --start <S> --end <E> --by-day --json-out ./snap/daily.json
180
193
  siluzan-tso stats -m BingV2 -a <id1>,<id2> --start 2026-03-01
181
- siluzan-tso stats -m Google -a <id> --json-out ./snap
194
+ siluzan-tso stats -m Google -a <mediaCustomerId> --json-out ./snap
182
195
  ```
183
196
 
184
197
  ---
@@ -76,7 +76,7 @@ siluzan-tso list-accounts -m Google --page 2 --page-size 999 --json-out ./snap-p
76
76
  | 字段 / JSON 路径 | 说明 |
77
77
  | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
78
78
  | `ma.entityId` | 丝路赞内部 ID,`delink`/`share`/`reauth`、**`account-active-bills`** 等操作使用此 ID(**不是** `mediaCustomerId`) |
79
- | `ma.mediaCustomerId` | 媒体平台账户数字 ID(Google Customer ID 等) |
79
+ | `ma.mediaCustomerId` | 媒体侧账户 ID(`stats`/`balance`/`accounts-digest` 的 `-a` 只用此字段):Google/TikTok/Bing 多为数字;**Yandex 形如 `porg-…`**;Meta 常带 `act_`。**不是** `entityId` |
80
80
  | `ma.currencyCode` | 账户主币种:`CNY` / `USD` 等;**表格有「币种」列**;报告/Excel 须与此一致,见 `references/accounts/currency.md` |
81
81
  | `ma.mediaCustomerName` | 账户名称(表格「账户名称」) |
82
82
  | `ma.mediaAccountState` 等 | 平台开户/审核态(如 Approved / Linked);**不是** OAuth 是否可用 |
@@ -43,9 +43,9 @@ siluzan-tso account check-access -a 4256317784 --json-out ./snap-access
43
43
  | HTTP | body | 含义 | CLI `status` |
44
44
  | ---- | --------------- | ---------------------------- | --------------------------------------------- |
45
45
  | 200 | `true` | 可访问 | `accessible`(exit 0) |
46
- | 200 | `false` | 已绑定但 Google OAuth 不可用 | `reauth_required` → `account reauth` |
47
- | 403 | 账户 ID | 无权限(多不在本账号下) | `no_permission`(exit 1) |
48
- | 403 | `token不能为空` | 未绑定 Google 媒体 | `google_not_bound` → `account auth -m Google` |
46
+ | 200 | `false` | 已绑定但 Google OAuth 不可用 | `reauth_required` → Agent 优先 `present_reauth`;备选 `account reauth` |
47
+ | 403 | 账户 ID | 无权限(多不在本账号下) | `no_permission`(exit 1) |
48
+ | 403 | `token不能为空` | 未绑定 Google 媒体 | `google_not_bound` → Agent 优先平台授权;备选 `account auth -m Google` |
49
49
  | 401 | 空 | 丝路赞 Token 失效 | `siluzan_token_invalid` → 重新 login |
50
50
 
51
51
  > 与 `list-accounts -k` 互补:`list-accounts` 查「是否出现在账户列表」;本接口查「Google 网关是否允许当前凭据访问该 mediaCustomerId」。
@@ -60,7 +60,9 @@ siluzan-tso account auth -m <媒体类型>
60
60
  | -------------------- | ------------------------------------------------------------------------ |
61
61
  | `-m, --media <type>` | 媒体类型(必填):`Google \| TikTok \| Meta \| Yandex \| BingV2 \| Kwai` |
62
62
 
63
- **示例(仅本地 / 非 Siluzan Agent CLI):**
63
+ > **Siluzan Agent**:优先使用 Agent 自带的**授权 / 添加授权**工具;CLI `account auth` **不禁止**,可作备选(沙箱不自动开浏览器,须把完整 URL 贴给用户)。
64
+
65
+ **示例:**
64
66
 
65
67
  ```bash
66
68
  # 首次授权 Google Ads 账户
@@ -79,7 +81,7 @@ siluzan-tso account auth -m Meta
79
81
 
80
82
  OAuth 失效时恢复授权。对齐 TSO 网页 **「重新授权」**:程序会先 **delink** 断开关联,再跳转媒体 OAuth。**禁止**对失效账户跳过解绑直接用 `account auth`。
81
83
 
82
- > **Siluzan Agent**:同 `auth`——**禁止**执行;须走 **connectors / 平台授权工具**。CLI 拦截时不会先解绑。
84
+ > **Siluzan Agent**:优先使用 Agent 自带的**重新授权**工具(如 `present_reauth`);CLI `account reauth` **不禁止**,可作备选(仍须 `--i-confirm` + `--commit`;沙箱不自动开浏览器,须把完整 URL 贴给用户)。
83
85
 
84
86
  ```bash
85
87
  siluzan-tso account reauth -m <媒体类型> --id <entityId> --i-confirm --commit "…"
@@ -93,7 +95,7 @@ siluzan-tso account reauth -m Google --ids <id1,id2> --i-confirm --commit "…"
93
95
  | `--ids <id1,id2>` | 批量 `entityId`,逗号分隔(与 `--id` 二选一) |
94
96
  | `--i-confirm` | **必填**:用户已确认「会先解绑再 OAuth」后附加 |
95
97
 
96
- **示例(仅本地 / 非 Siluzan Agent CLI):**
98
+ **示例:**
97
99
 
98
100
  ```bash
99
101
  # 1. 查失效账户的 entityId
@@ -112,7 +114,7 @@ siluzan-tso list-accounts -m BingV2 -k <mediaCustomerId>
112
114
  2. 说明:步骤 1 已 delink,账户可能暂时从 `list-accounts` 消失;用户完成授权前无法拉数。
113
115
  3. 若用户之后才说要绑定回来、且当时未贴链接:再跑 **`account auth -m <媒体>`**(勿再 `reauth`——已无 entityId),把**新**链接贴出。勿声称「还有刚才的链接」却不粘贴。
114
116
 
115
- > 手动两步等价于 `reauth`:`account delink --id … --i-confirm --commit "…"` → `account auth -m …`(须用户确认解绑风险;**Agent 环境仍勿用**)。
117
+ > 手动两步等价于 `reauth`:`account delink --id … --i-confirm --commit "…"` → `account auth -m …`(须用户确认解绑风险)。Siluzan Agent:**优先**平台授权/重新授权工具;CLI 为备选。
116
118
 
117
119
  ---
118
120
 
@@ -17,7 +17,8 @@
17
17
  ## Gotchas(账户域)
18
18
 
19
19
  - `list-accounts` **不含**余额/消耗;列全部账户用 `--page-size 999`,禁止默认 20 再翻页。
20
- - `entityId`(UUID,分享/delink/账单)≠ `mediaCustomerId`(媒体数字 ID,balance/stats/广告)。
20
+ - `entityId`(UUID,分享/delink/账单/`reauth`)≠ `mediaCustomerId`(`balance`/`stats`/`accounts-digest`/`ad` 的 `-a`)。**Yandex 的 mediaCustomerId 形如 `porg-…`,不是 UUID。**
21
21
  - MetaAd OAuth 户的 `mediaCustomerId` **须带 `act_` 前缀**。
22
+ - `stats`/`balance` 空结果 + verbose `HTTP 403`:先确认 `-a` 是否为 `ma.mediaCustomerId`;**禁止**把 `entityId` / tokenId 当 `-a`,也**禁止**未确认 `invalidOAuthToken=true` 就 `reauth`。
22
23
  - 多账户余额预警用 `balance-scan`(P2),多账户消耗汇总用 `accounts-digest`(P3);**禁止**外层 for-loop 逐户 `balance`/`stats`。
23
24
  - `stats` 默认 `spend` = **区间合计**,不是日消耗;日均看 `balance-scan.dailySpend` 或合计÷天数。
@@ -126,8 +126,9 @@
126
126
 
127
127
  - **账户状态 ≠ 系列状态**:`stats` / `balance` / `list-accounts` 的 `status` 只表示账户是否可用;系列状态必须来自 `ad campaigns`。
128
128
  - **数据时效性**:涉及「今天/当天/今日消耗」「实时消耗排行」前,必读 `references/analytics/account-analytics.md` 顶部「数据时效性」表。TikTok / Yandex / BingV2 / Kwai 是 `accountsoverview` 同步昨天数据,**不能查今天**。
129
- - **先查账户再操作**:`list-accounts -m [mediaType] -k [mediaCustomerId]`;用户给出的 `mediaCustomerId` 必须 `-k` 核验,无结果则告知用户并停止,**禁止**翻页 grep 自行换 ID(会导致报告错户);拉数、脚本、报告文件名全链路用同一 ID(以 stdout `accountId` 为准)。**Google 额外建议**:`list-accounts` 命中后、拉数前可 `account check-access -a <mediaCustomerId>` 校验 Google 网关访问权限;`no_permission`(403)通常表示账户不在当前丝路赞账号下。
130
- - **不猜测账户 ID**:`entityId` ≠ `mediaCustomerId`,两者均来自 `list-accounts`;**禁止**把 `entityId` 传给 `stats -a` / `balance -a`。
129
+ - **先查账户再操作**(拉数 / 改账户 / 报告 / **创建广告**):`list-accounts -m [mediaType] -k [mediaCustomerId]`;用户给出的 `mediaCustomerId` 必须 `-k` 核验,无结果则告知用户并停止,**禁止**翻页 grep 自行换 ID(会导致报告错户);拉数、脚本、报告文件名全链路用同一 ID(以 stdout `accountId` 为准)。**Google 额外建议**:`list-accounts` 命中后、拉数前可 `account check-access -a <mediaCustomerId>` 校验 Google 网关访问权限;`no_permission`(403)通常表示账户不在当前丝路赞账号下。
130
+ - **W3 仅出方案例外(覆盖上条)**:用户只要「投放方案 / 规划 / 表格 / 先别创建·开户·投钱」,或未给账户且未要求创建/发布时——**禁止**把「请先提供 Google 广告账户」当作第一步;按 `google-ads-campaign-plan.md` §「仅出方案 vs 创建」交付 JSON+Markdown(`account`=`[PENDING_ACCOUNT]`),跳过 `list-accounts` / `geo resolve` / `campaign-validate` / `campaign-create`。用户确认要创建后再要账户并续跑创建流水线。
131
+ - **不猜测账户 ID**:`entityId`(UUID,仅 delink/share/reauth/账单)≠ `mediaCustomerId`(`stats`/`balance`/`accounts-digest`/`ad` 的 `-a`)。两者均来自 `list-accounts` 的 `ma.*`。用户已给出媒体账户号时(Google 数字 CID、**Yandex `porg-…`**、Meta `act_…` 等)→ `-a` **原样使用**,再用 `-k` 核验;**禁止**改成 `entityId` / tokenId / 会话里其它 UUID。**禁止**把 `entityId` 传给 `stats -a` / `balance -a` / `accounts-digest -a`。
131
132
  - **媒体类型区分大小写**:`Google`、`TikTok`、`Yandex`、`MetaAd`、`BingV2`、`Kwai`。
132
133
  - **CLI 输出忠实**:数值与 ID 须与本次落盘 JSON / stdout 一致,不编造示例 ID;`data` 为空时只说「当前返回无记录」并附 JSON 路径。
133
134
  - **破坏性操作必须确认 + `--commit`**:账户解绑/关闭/取消分享、BC/MCC 解绑、删除预警/报告/广告/关键词、发票申请、广告发布等。
@@ -143,7 +144,7 @@
143
144
  - 用户**未指定手机号** → 不校验,按当前凭据正常执行。
144
145
  - 当前凭据未返回手机号且用户指定了手机号 → 视同未校验通过,引导重新用手机号登录。
145
146
  - **Google 新建搜索系列**:流程在 `references/google-ads/google-ads-campaign-plan.md`;填 JSON 前**必须先 Read** `assets/campaign-create-template.json`,再 Read `assets/campaign-create-template.md`。**禁止**只读 `.md` 手写 JSON。
146
- - **「根据官网生成 Google 搜索广告 / 表格格式」**:仍属新建搜索系列 → **W3 + 本文件上条**;用户要的「表格」是 `google-ads-launch-plan-template.md` 对 JSON 的投影,**不是**可跳过 JSON/`campaign-validate` 的独立交付物。**禁止**与 P8 网站诊断、P9 市场分析、W5 仅拓词混用。
147
+ - **「根据官网生成 Google 搜索广告 / 表格格式」**:仍属新建搜索系列 → **W3 + 本文件上条**;用户要的「表格」是 `google-ads-launch-plan-template.md` 对 JSON 的投影,**不是**可跳过 JSON 的独立交付物。**仅出方案**时可先交付 JSON+表格(跳过 validate);**创建前**必须 `campaign-validate`。**禁止**与 P8 网站诊断、P9 市场分析、W5 仅拓词混用;**禁止**因缺账户 ID 拒出方案。
147
148
  - **Excel/表格投放方案 → 创建广告**:必读 `references/google-ads/rules/google-ads-plan-source-fidelity.md`。Agent **写代码**直接转成 campaign-create JSON;地域用 **`ad geo resolve`**;**禁止**对话手填完整 JSON、**禁止**编造 geo id;有方案匹配类型时勿压成一律 BROAD。**方案不合规时必须询问**:「您自己改还是我帮您改?」——**禁止**未问就静默改用户方案内容后 create。用户选「我帮您改」时:代改同步落盘变更账本;**创建完成后必出报告**(创建了哪些 + 从 xxx→xxx + 原因)。
148
149
  - **「行业分析 / 行业分析报告 / 生成 XX 行业报告」**(例:「帮我生成一份电商行业的行业分析报告」)→ **P9 战略市场分析**。**必须**先 `siluzan-tso market-analysis collect … --json-out`,再 WebSearch 补数据、写 `market-report.json`,最后 `market-analysis render` 出 HTML。**禁止**不调用 CLI、仅在对话里用 WebSearch 写 Markdown 充当终稿。**不是** `google-analysis`、**不是** P8 网站诊断。
149
150
  - **开户首次响应**:对话内首次进入开户话题时,**必须先**按 `references/accounts/open-account-by-media.md` §「首次响应硬规范」输出**完整必填清单**(未指明媒体则列全平台六表),再收集资料;**禁止**未列清单就执行 `open-account` 或零散追问。
@@ -254,7 +255,8 @@
254
255
  ## 十一、常见 HTTP 状态码
255
256
 
256
257
  - **400**:参数错误,查看对应 reference 或 `-h`
257
- - **401 / OAuth 失效**:`account reauth -m <媒体> --id <entityId> --i-confirm --commit "…"`(内置 delink→OAuth,见 W9 / `accounts-permissions.md`;须用户确认)。**必须把 CLI 打印的完整授权 URL 贴给用户**,禁止只说「链接已生成」。解绑后若列表已无该户,恢复用 `account auth -m <媒体>` 拿新链接。**丝路赞登录凭据失效** → `send-login-code` + `login --phone --code`,见 `references/core/setup.md`
258
+ - **401 / OAuth 失效**:仅当 `list-accounts` 的 `invalidOAuthToken=true`(或表格「授权状态」为失效)且用户确认后——Siluzan Agent **优先**用平台重新授权工具(如 `present_reauth`);备选 `account reauth -m <媒体> --id <entityId> --i-confirm --commit "…"`(内置 delink→OAuth,见 W9 / `accounts-permissions.md`)。走 CLI 时**必须把完整授权 URL 贴给用户**,禁止只说「链接已生成」。解绑后若列表已无该户,恢复用平台授权工具或 `account auth -m <媒体>`。**丝路赞登录凭据失效** → `send-login-code` + `login --phone --code`,见 `references/core/setup.md`
259
+ - **403(拉数空结果)**:`stats`/`balance`/`accounts-digest` 在 `--verbose` 下打出 `[fetchOverviewMap] 异常被吞:HTTP 403` 且 `items=[]` 时,**优先怀疑 `-a` 传错 ID**(把 `entityId`/tokenId/UUID 当成了 mediaCustomerId;Yandex 正确形如 `porg-…`)。处理:`list-accounts -m <媒体> -k <用户给出的账户号>`,用返回的 `ma.mediaCustomerId` 重试。**禁止**把此类 403 直接当成 OAuth 过期并自行 `reauth`(账户仍显示 `Linked` + `invalidOAuthToken=false` 时尤其禁止解绑)。
258
260
  - **500**:服务可能正在部署/升级,建议反馈 Siluzan 相关人员
259
261
 
260
262
  ---
@@ -307,6 +309,7 @@
307
309
  | 场景 | 做法 |
308
310
  | ---- | ---- |
309
311
  | 查数、诊断、报告、列表 | **先拉数再汇报**;缺账户 ID 才问;缺时间范围按 §五(能默认则默认并注明) |
312
+ | **W3 仅出方案**(官网/规划/表格,未要求创建) | **先出 JSON+Markdown**;账户用占位;**禁止**先要广告账户再开工 |
310
313
  | 模糊意图 | 选最可能工作流先执行;结论后附「您是不是还想…」 |
311
314
  | campaign-create 后 Sitelink/国家 batch 漏建 | **自动** `batch get` → `batch diff` → `ad geo add` / `ad extension *`;**勿**反问是否补建 |
312
315
  | 用户方案(Excel 等)不合规 | 列出问题 → 二选一「您自己改还是我帮您改?」→ 停等;禁止未问静默改后 create |
@@ -324,6 +327,7 @@
324
327
  | 帮我优化一下 | W6 | 「我先看看哪些广告表现偏弱…」 |
325
328
  | 哪些账户快没钱了 | P2 | 「正在扫描全部账户余额…」 |
326
329
  | 帮我开个户 / 建广告 | W2 / W3 | 「我先告诉您需要准备哪些资料。」 |
330
+ | 根据官网出广告方案 / 先出方案别投钱 | W3 仅出方案 | 「我先根据官网整理一版投放方案(系列/词/文案),账户您稍后选定即可。」 |
327
331
  | 网站行不行 | P8 | 「我来给这个网站做个体检…」 |
328
332
 
329
333
  ### 14.5 禁止事项(机械感)
@@ -225,7 +225,7 @@
225
225
  | **P3** | **多个广告账户** 消耗/CPA/零转化巡检 | 多账户对比、消耗排名、**转化成本监控**、**CPA**、**零转化**、口语「零询盘」(无 CRM)——见 **§零·D** | 媒体 + 账户列表或全量 + 区间 | 表格 / 话术 |
226
226
  | **P2** | **多账户余额** 续航预警 | 余额扫描、快没钱了、充值预警 | 媒体(可选账户子集) | 列表 |
227
227
  | **P5** | **多账户 × 多维度** 批拉 | 批量分析、多户 google-analysis | ≥2 户 + ≥2 维度 + 区间 | 聚合报告 |
228
- | **W3** | 根据 **官网 / URL 生成 Google 搜索广告方案** | 根据网站**出广告**、搜索广告方案/计划/文案/关键词**表**、**生成**投放方案表格 | 官网 URL + **明确要广告方案** | JSON + 表格投影 |
228
+ | **W3** | 根据 **官网 / URL 生成 Google 搜索广告方案** | 根据网站**出广告**、搜索广告方案/计划/文案/关键词**表**、**生成**投放方案表格 | 官网 URL + **明确要广告方案**(**不**要求账户 ID) | JSON + 表格投影 |
229
229
  | **W5** | **Google Ads 关键词规划** / 拓词(含市场指标) | 见 **§零·C**:拓词、Keyword Planner、长尾词、**月搜索量**、**竞争度**、核心词扩词、读 URL/文章后出词表 | 种子词 / 核心词(URL 可选) | 词表(指标来自 `keyword` CLI) |
230
230
  | **W7** | **丝路赞平台内** 已生成的优化报告 / 推送 | Siluzan 优化报告列表、报告推送、邮件推送配置 | 按 `reporting.md` | 平台侧列表 |
231
231
 
@@ -236,7 +236,7 @@
236
236
  | **有网址** + 诊断/检测/监测/报告(含说「**网络**」) | **P8(§零)** | 误走 P9「行业/网络分析」、P1 账户诊断 |
237
237
  | 「这个**网站**好不好 / 能不能投」(无广告方案诉求) | **P8** | 误走 P9、W3 |
238
238
  | 「**电商/制造**行业怎么样 / 市场格局」(**无 URL**) | **P9** | 误走 P8 |
239
- | 「根据**官网**做 **Google 搜索广告** / 关键词表」 | **W3** | 误走 P8 只诊断不出方案 |
239
+ | 「根据**官网**做 **Google 搜索广告** / 关键词表」 | **W3**(可无账户先出方案) | 误走 P8;或卡在要账户才出方案 |
240
240
  | 「读**网址/文章** + **核心词** + 要 **月搜索量/竞争度** 长尾词表」 | **W5(§零·C)** | 纯 WebSearch 编数;误走 P8/P9/W3 |
241
241
  | 只要 **Keyword Planner / 拓词** 指标,不要 campaign JSON | **W5** | 误走 W3 走 validate/create |
242
242
 
@@ -26,8 +26,8 @@
26
26
  - **必读**:`references/accounts/accounts-list.md` + `accounts-balance-stats.md`。
27
27
  - **步骤**:
28
28
  1. 列表/数量:`list-accounts -m <媒体> --page-size 999 --json-out ./snap`,脚本读 `list-accounts-*.json` 的 `total` / `items[]`(**禁止**默认 20 条再翻页)。
29
- 2. 单户余额:`balance -m <媒体> -a <mediaCustomerId>`。
30
- 3. 单户消耗:`stats -m <媒体> -a <mediaCustomerId> --start <S> --end <D>`(按日 Excel → **P4-DAILY**;多账户对比 → **P3**)。
29
+ 2. 单户余额:`balance -m <媒体> -a <mediaCustomerId>`(Yandex 用 `porg-…`,**不是** `entityId`)。
30
+ 3. 单户消耗:`stats -m <媒体> -a <mediaCustomerId> --start <S> --end <D>`(用户已给账户号则原样入 `-a`;空/`403` 先核验 ID,见 `accounts-balance-stats.md`;按日 Excel → **P4-DAILY**;多账户对比 → **P3**)。
31
31
  4. 激活账单:先取 `entityId` → `account-active-bills -m <媒体> --id <entityId> --json-out ./snap`。
32
32
  - **产物**:多账户余额预警走 **P2**、消耗汇总走 **P3**。
33
33
 
@@ -56,15 +56,21 @@
56
56
 
57
57
  - **触发**:新建搜索系列、出投放方案、**按 Excel/表格投放方案创建**、**根据官网/网站/URL 生成 Google 搜索广告(含「表格格式」)**、搜索广告文案/关键词/计划表、系列/组/广告/关键词 CRUD、PMax、拒审处理、日常调价/启停。
58
58
  - **勿误判**:仅给官网 URL 且目标是「写/生成搜索广告」→ **本卡片(W3)**,不是 P8 网站诊断、不是 P9 市场分析;若用户只要拓词无系列结构 → **W5**。
59
+ - **仅出方案(默认优先)**:用户说「出方案 / 规划 / 表格 / 先别创建·开户·投钱」,或**未给账户且未要求创建** → 按 `google-ads-campaign-plan.md` §「仅出方案 vs 创建」:交付 JSON + Markdown,`account`=`[PENDING_ACCOUNT]`;**禁止**先要广告账户再开工;跳过 `list-accounts` / `geo resolve` / validate / create,直到用户确认创建。
59
60
  - **必读**:方案与门禁 `references/google-ads/google-ads-campaign-plan.md` + **`assets/campaign-create-template.json`**(先 Read)+ `assets/campaign-create-template.md`;**有 Excel/表格方案时另读** `references/google-ads/rules/google-ads-plan-source-fidelity.md`;写命令参数 `references/google-ads/google-ads-write.md`;查询/拒审 `google-ads-read.md`;batch 补建 `google-ads-batch.md`;PMax 加 **`assets/pmax-create-template.json`** + `assets/pmax-create-template.md` + `references/google-ads/pmax-api.md`。
60
61
  - **创建路径选择**:
61
62
  - 已有 AI 智投草稿 → 走 **W4**。
62
- - **PMax 出方案/创建** → **`assets/pmax-create-template.json`**(先 Read)+ `pmax-create-template.md` + `pmax-api.md`:`pmax-validate` → 用户确认 → `pmax-create`(**勿**用 Search `campaign-create`)。
63
- - **用户给了 Excel/表格方案** → **方案源轨**:见 `google-ads-plan-source-fidelity.md`——Agent **写脚本**直接转成 campaign-create JSON + **`ad geo resolve`** 取地域 id → validate → 确认 → create(**禁止**对话手填完整 JSON)。
64
- - 搜索系列从零出方案 → `google-ads-campaign-plan.md`:JSON → `campaign-validate` → 用户确认 → `campaign-create`。
65
- - 已有完整结构化 JSON → 对应 validate → create。
63
+ - **PMax 出方案/创建** → **`assets/pmax-create-template.json`**(先 Read)+ `pmax-create-template.md` + `pmax-api.md`:仅出方案先 JSON+Markdown;要创建再 `pmax-validate` → 用户确认 → `pmax-create`(**勿**用 Search `campaign-create`)。
64
+ - **用户给了 Excel/表格方案** → **方案源轨**:见 `google-ads-plan-source-fidelity.md`——Agent **写脚本**转 JSON;仅出方案停在 JSON+Markdown;要创建再 **`ad geo resolve`** → validate → 确认 → create(**禁止**对话手填完整 JSON)。
65
+ - 搜索系列从零出方案 → `google-ads-campaign-plan.md`:JSON + Markdown;要创建再 `campaign-validate` → 用户确认 → `campaign-create`。
66
+ - 已有完整结构化 JSON 且要创建 → validate → create。
67
+ - **步骤(Search · 仅出方案,无账户)**:
68
+ 1. Read `campaign-create-template.json`;官网/RAG 归纳产品与落地页。
69
+ 2. 填系列/组/词/RSA/预算假设;`account`=`[PENDING_ACCOUNT]`;地域用国家名占位(勿编造 geo 数字 id)。
70
+ 3. 按 `google-ads-launch-plan-template.md` 投影 Markdown → 交付;说明「选定账户后可继续 validate/create」。
71
+ 4. **停**;勿追问账户为前置条件。
66
72
  - **步骤(PMax 方案 → 创建)**:
67
- 1. 账户:`list-accounts -m Google -k <id>`;落地页与品牌从官网/RAG 归纳。
73
+ 1. **创建阶段**账户:`list-accounts -m Google -k <id>`;落地页与品牌从官网/RAG 归纳。仅出方案跳过本步。
68
74
  2. 地域/语言:多国用 `ad geo resolve`,单国用 `ad geo search`;语言 id 写入 JSON。
69
75
  3. 复制 `pmax-create-template.json` 填文案/预算/图片;**必须**含 `campaignExtensions`(至少 callouts + structuredSnippets);**Lead Gen/B2B 默认** `campaignExtensions.leadForm`(方案 Markdown 须单列表单节)。
70
76
  4. 门禁:`ad pmax-validate --config-file ./pmax.json --json-out ./snap-pmax`。
@@ -157,8 +163,8 @@
157
163
  - **步骤(按场景)**:
158
164
  - **分享**:`list-accounts --json-out` 取 `entityId` → `account share --id <entityId> --phone <手机号>`(`--phone` 裸 11 位补 `+86`,已有国家码则不补;见 `accounts-permissions.md` § share);查 `account share-detail --customer-id <mediaCustomerId>`;取消 `account unshare --id <entityId> --account-id <userId>`。
159
165
  - **解绑**:`account delink --id <entityId> --i-confirm --commit "…"` / `--ids id1,id2`(缺 `--i-confirm` CLI 拒绝执行)。
160
- - **OAuth 重授权**:`invalidOAuthToken=true` → `list-accounts --json-out` 取 `ma.entityId` → **`account reauth -m <媒体> --id <entityId>`**(内置先 delink 再 OAuth,对齐网页「重新授权」)→ `list-accounts` 验证。**禁止**对失效账户直接用 `account auth`(首次「添加授权」专用)。
161
- - **首次 OAuth 添加授权**:`account auth -m <媒体>`(无需先 delink)。
166
+ - **OAuth 重授权**:`invalidOAuthToken=true` → Siluzan Agent **优先**用平台重新授权工具(如 `present_reauth`);备选 `list-accounts --json-out` 取 `ma.entityId` → **`account reauth -m <媒体> --id <entityId>`**(内置先 delink 再 OAuth)→ `list-accounts` 验证。**禁止**对失效账户直接用 `account auth`(首次「添加授权」专用)。
167
+ - **首次 OAuth 添加授权**:Siluzan Agent **优先**用平台授权工具;备选 `account auth -m <媒体>`(无需先 delink)。
162
168
  - **MCC**:`account mcc-bind --customers <mediaCustomerId,...> --mcc <MCC客户ID>` / `mcc-unbind`(走 `googleApiUrl`,先 `config show`)。
163
169
  - **BC(TikTok)**:`account bc-bind --customers <id> --bc-ids <id>` / `bc-unbind --bc-id <id>`(解绑一次一个)。
164
170
  - **BM(Meta)**:`account bm-bind --account-id <mediaCustomerId> --bm-id <bmId>`。
@@ -10,47 +10,59 @@
10
10
  | ------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------- |
11
11
  | 「根据 www.example.com 官网生成 Google 搜索广告」 | **W3 · 本文件标准流水线** | 只输出一张手写关键词/RSA 表;走 P8 网站诊断 |
12
12
  | 「按这份 Excel / 投放方案创建广告」 | **方案源轨**(下表)+ **必读** `rules/google-ads-plan-source-fidelity.md` | 猜 geo id;关键词一律 BROAD;跳过 validate |
13
- | 「要表格格式 / 表格给我」 | 先 JSON → `campaign-validate` → 再按 `google-ads-launch-plan-template.md` **投影 Markdown 表格** | 跳过 JSON 直接填表;把表格当唯一交付物 |
13
+ | 「要表格格式 / 表格给我」 | 先 JSON →(有账户且要创建才 `campaign-validate`)→ 按 `google-ads-launch-plan-template.md` **投影 Markdown 表格** | 跳过 JSON 直接填表;因缺账户不给表格 |
14
14
  | 「帮我写搜索广告文案/关键词」且未指定已有系列 | **W3**(含官网/RAG 归纳背景) | 与 W5 纯拓词混淆(W5 无系列/组/RSA 结构) |
15
+ | 「先出方案 / 先别开户 / 先别投钱 / 只要方案」 | **仅出方案**(见下节;**不**索要账户) | 卡在 `list-accounts` / 要 mediaCustomerId 才肯出方案 |
15
16
  | 「分析这个网站能不能投广告」 | **P8** 网站诊断 | 本文件建户方案 |
16
17
 
17
- **缺参时**:用户只给官网 URL、未给预算/地域/账户 ID → 先从官网归纳产品/落地页(必要时 `google-ads-landing-page-discovery-via-webfetch.md`),**列出仍缺项并追问**,再进入下表「方案先行」轨;不得因信息不全就降级为「随便写几条广告」。
18
+ ### 仅出方案 vs 创建(硬门禁)
19
+
20
+ | 阶段 | 触发(满足其一即可) | 要不要 Google 账户 | Agent 必须做 | **禁止** |
21
+ | ---- | -------------------- | ------------------ | ------------ | -------- |
22
+ | **仅出方案** | 「出方案 / 规划 / 先别创建 / 先别开户 / 先别投钱 / 只要表格」;或用户**未**给账户且**未**说要创建/发布 | **不必须** | 交付同构 JSON + Markdown 投影;`account` / geo 数字 id 用占位并标注「待选定账户后补」 | **因缺账户 ID 阻塞交付**;未确认创建就跑 `list-accounts` 逼用户给号;未确认就 `campaign-create` |
23
+ | **创建** | 用户已确认方案并明确要创建/发布,或已提供可用 `mediaCustomerId` 且意图是落地 | **必须** | `list-accounts` → `geo resolve` → 填真 id → `campaign-validate` → 确认 → `campaign-create` → batch… | 编造 geo id;跳过 validate |
24
+
25
+ **缺参时(仅出方案)**:用户只给官网 URL、未给预算/地域/账户 → 先从官网归纳产品/落地页(必要时 `google-ads-landing-page-discovery-via-webfetch.md`),预算/地域可合理默认并在方案里写明假设;**账户 ID 一律不追问为前置条件**(写 `[PENDING_ACCOUNT]` 即可)。仅当缺官网且无法推断产品时才追问 1 项;不得因信息不全就降级为「随便写几条广告」,也**不得**把「请先提供广告账户」当成第一步。
26
+
27
+ **占位约定(仅出方案)**:外层 `account` 填 `"[PENDING_ACCOUNT]"`(或 `""`);`locations` 用国家中英文名;`targetedLocations` 可暂空数组或与 `locations` 同长的占位串——**交付时注明「选定账户后须 `ad geo resolve` 写回真 id,再 validate」**。此阶段**跳过** `list-accounts` / `ad geo resolve|search` / `campaign-validate` / `campaign-create`(无账户时这些命令无法合法完成)。
18
28
 
19
29
  ---
20
30
 
21
31
  | 轨 | 条件 | 动作 |
22
32
  | -------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
23
- | **方案源轨** | 用户已给 Excel/表格/结构化投放方案(含地域、词、匹配类型等) | **先 Read** `rules/google-ads-plan-source-fidelity.md` → Agent **写脚本**把方案直接转成 campaign-create JSON(+ **`ad geo resolve`** 取地域 id)→ validate → 确认 → create |
24
- | **直读直写** | 用户已给账户/预算/组/词/RSA 等结构化数据(非文件,或已是 JSON) | 通过代码转换为 campaign-create 直接可用的 JSON → validate → 确认 → create |
25
- | **方案先行** | 无完整结构,或要求「先出方案」 | 读本文件 + 必读规则 → 生成 JSON → validate → Markdown → 确认 → create |
33
+ | **方案源轨** | 用户已给 Excel/表格/结构化投放方案(含地域、词、匹配类型等) | **先 Read** `rules/google-ads-plan-source-fidelity.md` → Agent **写脚本**转 JSON;**有账户且要创建**时再 `ad geo resolve` → validate → 确认 → create;**仅出方案**则脚本产出后停在 JSON+Markdown |
34
+ | **直读直写** | 用户已给账户/预算/组/词/RSA 等结构化数据(非文件,或已是 JSON) | 转为 campaign-create JSON →(要创建则)validate → 确认 → create |
35
+ | **方案先行** | 无完整结构,或要求「先出方案」 | 读本文件 + 必读规则 → 生成 JSON + Markdown;**仅出方案到此为止**;用户确认创建且已有账户后再 validate → create |
26
36
 
27
37
  **硬约束**
28
38
 
29
39
  - 可执行真相只有 **JSON**(`assets/campaign-create-template.json` 同构);Markdown 只读投影。
30
- - **Agent Read 顺序(建系列前必做)**:① `assets/campaign-create-template.json`(复制/改写的结构真相源)→ ② `assets/campaign-create-template.md`(字段说明与踩坑)。**禁止**只读 `.md` 凭印象拼 JSON。
40
+ - **Agent Read 顺序(建系列 / 出方案前必做)**:① `assets/campaign-create-template.json`(复制/改写的结构真相源)→ ② `assets/campaign-create-template.md`(字段说明与踩坑)。**禁止**只读 `.md` 凭印象拼 JSON。
31
41
  - **方案文件(Excel 等)额外必读**:`references/google-ads/rules/google-ads-plan-source-fidelity.md`(Agent **写代码**直接转成 campaign-create JSON;禁止对话手填完整 JSON)。
32
- - 改需求 **改转换脚本重跑**,再 `campaign-validate`,再刷新 Markdown。
33
- - **PMax 系列创建**走独立流水线(勿用本文件 JSON 模板):**先 Read `assets/pmax-create-template.json`** + `assets/pmax-create-template.md` + `ad pmax-validate` / `ad pmax-create`;**Lead Gen/B2B 方案默认含 `campaignExtensions.leadForm`**(方案 Markdown 须单列表单);运营诊断见 `references/google-ads/rules/google-ads-pmax-guide.md`。
42
+ - 改需求 **改转换脚本重跑**;若已进入创建阶段则再 `campaign-validate`,再刷新 Markdown。
43
+ - **PMax 系列创建**走独立流水线(勿用本文件 JSON 模板):**先 Read `assets/pmax-create-template.json`** + `assets/pmax-create-template.md` + `ad pmax-validate` / `ad pmax-create`;**Lead Gen/B2B 方案默认含 `campaignExtensions.leadForm`**(方案 Markdown 须单列表单);运营诊断见 `references/google-ads/rules/google-ads-pmax-guide.md`。PMax **仅出方案**时同样**禁止**因缺账户阻塞;`account` 占位,跳过 validate/create。
34
44
  - 搜索网络:仅 Google 搜索(`TargetSearchNetwork`/`TargetContentNetwork`/`TargetPartnerSearchNetwork` 均为 false)。
35
- - **地域 id**:多国用 **`ad geo resolve`**(单国可用 `ad geo search`);**禁止**编造 / ISO 心算。外层 `locations` 与 `targetedLocations` 数量必须一致(validate 硬校验)。
45
+ - **地域 id(创建阶段)**:多国用 **`ad geo resolve`**(单国可用 `ad geo search`);**禁止**编造 / ISO 心算。外层 `locations` 与 `targetedLocations` 数量必须一致(validate 硬校验)。**仅出方案**可用国家名占位,勿为取 id 而先逼用户给账户。
36
46
  - **匹配类型**:转换脚本按方案写入 EXACT/PHRASE/BROAD 分块(有方案源时勿压成一律 BROAD)。
37
47
 
38
48
  ---
39
49
 
40
50
  ## 标准流水线
41
51
 
52
+ > **仅出方案**:做步 0(若有方案文件)→ **跳过**步 1~3 的账户/geo CLI → 做步 4~5(词与 JSON)→ **跳过**步 6 validate → 做步 7(Markdown,国家名即可)→ **停住等用户确认**;用户确认创建并给出账户后再从步 1 续跑。
53
+
42
54
  | 步 | 动作 | 文档/命令 |
43
55
  | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
44
- | 0 | **方案源轨**:Agent 写转换脚本:方案文件 → `campaign.json`;地域用 **`ad geo resolve --json-out`** 写入(**勿**对话手填) | **`rules/google-ads-plan-source-fidelity.md`** + **`assets/campaign-create-template.json`** |
45
- | 1 | `list-accounts` 锁定 `account` / `customerName` / 币种 | `references/accounts/currency.md` |
46
- | 2 | 可选 `rag query`;无现成词表时再 `keyword` / `keyword geo-list` 拓词 | `references/analytics/keyword-planner-workflows.md` |
47
- | 3 | 无方案文件时:多国 **`ad geo resolve`**(或单国 `ad geo search`)写入 `locations` + `targetedLocations` | **禁止编造 id** |
56
+ | 0 | **方案源轨**:Agent 写转换脚本:方案文件 → `campaign.json`;**创建阶段**地域用 **`ad geo resolve --json-out`** 写入(**勿**对话手填) | **`rules/google-ads-plan-source-fidelity.md`** + **`assets/campaign-create-template.json`** |
57
+ | 1 | **创建阶段**:`list-accounts` 锁定 `account` / `customerName` / 币种;**仅出方案且无账户 → 跳过** | `references/accounts/currency.md` |
58
+ | 2 | 可选 `rag query`;无现成词表时再 `keyword` / `keyword geo-list` 拓词(`keyword` **可不传** `-a`) | `references/analytics/keyword-planner-workflows.md` |
59
+ | 3 | **创建阶段**:无方案文件时多国 **`ad geo resolve`**(或单国 `ad geo search`)写入 `locations` + `targetedLocations`;**仅出方案 → 跳过,国家名占位** | **禁止编造 id** |
48
60
  | 4 | 无方案文件时:按分层写入 `KeywordsForBatchJob`(EXACT/PHRASE/BROAD);否词进 `NegativeKeywordsForBatchJob` | 参考 `google-ads-keyword-taxonomy.md`;有方案源则走步骤 0 |
49
- | 5 | 得到与模板同构的 `campaign-create` JSON | **`assets/campaign-create-template.json`** |
50
- | 6 | **`ad campaign-validate --config-file <json>`** | 下文「校验」 |
51
- | 6b | **方案来自用户且不合规**:列出问题 → **询问**「您自己改还是我帮您改?」→ 按选择处理后再 validate(**禁止**未问就静默改方案) | **`rules/google-ads-plan-source-fidelity.md`** § 用户方案不合规 |
61
+ | 5 | 得到与模板同构的 `campaign-create` JSON(仅出方案时 `account`=`[PENDING_ACCOUNT]`) | **`assets/campaign-create-template.json`** |
62
+ | 6 | **创建阶段**:`ad campaign-validate --config-file <json>`;**仅出方案 → 跳过**,交付时说明「有账户后再 validate」 | 下文「校验」 |
63
+ | 6b | **方案来自用户且不合规**(创建前):列出问题 → **询问**「您自己改还是我帮您改?」→ 按选择处理后再 validate(**禁止**未问就静默改方案) | **`rules/google-ads-plan-source-fidelity.md`** § 用户方案不合规 |
52
64
  | 6c | 用户选「我帮您改」:改 JSON 时**同步落盘变更账本**(from → to + reason) | 同上 § Agent 代改后:创建完成报告 |
53
- | 7 | 给人看:**国家↔id** + **匹配类型条数** + 可选 Markdown 投影;**勿**贴整份 JSON 当主交付 | `google-ads-launch-plan-template.md` |
65
+ | 7 | 给人看:国家(创建阶段补 **国家↔id**)+ **匹配类型条数** + Markdown 投影;**勿**贴整份 JSON 当主交付 | `google-ads-launch-plan-template.md` |
54
66
  | 8 | 用户确认后 **`ad campaign-create`** | `references/google-ads/google-ads-write.md` |
55
67
  | 9 | 每隔5s 获取创建结果 | `ad batch get --id <taskId> --config-file ./campaign.json` |
56
68
  | 10 | 成功或部分成功后 **`ad batch diff`** 对照 JSON 与账户实况 | |
@@ -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.17'
12
+ $PKG_VERSION = '1.1.37-beta.19'
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.17"
12
+ readonly PKG_VERSION="1.1.37-beta.19"
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
  "我卖户外露营装备,主要面向美国和欧洲市场,网站是 https://www.campgear.com ,每天预算 3000 美金,帮我规划一套 Google 搜索广告方案。先出方案别直接开户投钱。"
6
6
  ],
7
- "judgeExpectation": "路径:应先阅读 google-ads/google-ads.md 规则流程,再输出可确认的投放方案(地域/语言/预算/系列结构),不得跳过合规与确认直接执行 campaign-create。\n输出:须含可执行的 campaign-create JSON(唯一数据源)+ 从 JSON 推导的 Markdown 说明;关键词写在 KeywordsForBatchJob;方案阶段应说明须 ad campaign-validate 通过后再 create。",
7
+ "judgeExpectation": "路径:应先阅读 google-ads-campaign-plan(仅出方案轨),再输出可确认的投放方案(地域/语言/预算/系列结构);用户说先出方案时不得索要账户 ID;不得跳过确认直接执行 campaign-create。\n输出:须含 campaign-create JSON(唯一数据源,account 可 PENDING)+ 从 JSON 推导的 Markdown 说明;关键词写在 KeywordsForBatchJob;应说明选定账户后再 validate/create。",
8
8
  "skillMapping": "references/google-ads/google-ads-campaign-plan.md;google-ads-rules",
9
9
  "judgeReferencePaths": [
10
10
  "references/google-ads/google-ads-campaign-plan.md"
@@ -4,7 +4,7 @@
4
4
  "turns": [
5
5
  "根据 www.siluzan.com 这个官网的信息生成一份 Google 搜索广告给我,要表格格式。"
6
6
  ],
7
- "judgeExpectation": "路径:须识别为 W3 搜索广告方案(google-ads-campaign-plan.md),不是 P8 网站诊断、不是 P9 市场分析、不是 W5 仅拓词;须 Read campaign-create-template.json 后再出 JSON;方案阶段须 campaign-validate 门禁,不得跳过 validate 直接 create 或只交付手写创意表。\n输出:须含 campaign-create JSON(唯一数据源)+ 从 JSON 投影的 Markdown 表格(launch-plan-template 结构);缺预算/地域/账户时应先追问或标注待确认项,不得因只给官网就降级为几条文案。",
7
+ "judgeExpectation": "路径:须识别为 W3 搜索广告方案(google-ads-campaign-plan.md),不是 P8 网站诊断、不是 P9 市场分析、不是 W5 仅拓词;须 Read campaign-create-template.json 后再出 JSON;仅出方案阶段不得因缺账户阻塞,不得先逼 list-accounts/要 mediaCustomerId;不得跳过 JSON 直接 create 或只交付手写创意表;创建前才须 campaign-validate。\n输出:须含 campaign-create JSON(唯一数据源)+ 从 JSON 投影的 Markdown 表格(launch-plan-template 结构);缺账户用 [PENDING_ACCOUNT] 占位即可;缺预算/地域可默认并标注假设,不得因只给官网就降级为几条文案。",
8
8
  "skillMapping": "references/google-ads/google-ads-campaign-plan.md",
9
9
  "judgeReferencePaths": [
10
10
  "references/google-ads/google-ads-campaign-plan.md",
@@ -0,0 +1,31 @@
1
+ {
2
+ "id": "yandex-stats-use-porg-mediaccustomerid",
3
+ "description": "Yandex 拉数须用 porg- mediaCustomerId,禁止 UUID/reauth 误判",
4
+ "turns": [
5
+ "拉取账户 porg-kqquuxx6 的 Yandex 7 月 21 日投放数据,用 siluzan-tso,需要 JSON。"
6
+ ],
7
+ "judgeExpectation": "路径:用户给出 Yandex 账户 porg-kqquuxx6 时,stats/balance 的 -a 必须传 porg-kqquuxx6(mediaCustomerId);可先 list-accounts -m Yandex -k porg-kqquuxx6 核验。\n禁止:把 entityId 或其它 UUID 传给 stats/balance 的 -a;禁止因空结果或 verbose HTTP 403 直接 account reauth/delink。\n输出:交付 7 月 21 日投放数据或明确说明接口返回;stub 即可。",
8
+ "skillMapping": "references/accounts/accounts-balance-stats.md(stats 空结果/403 排查);agent-conventions §十一",
9
+ "judgeReferencePaths": [
10
+ "references/accounts/accounts-balance-stats.md",
11
+ "references/core/agent-conventions.md"
12
+ ],
13
+ "commandMustInclude": [
14
+ [
15
+ "stats"
16
+ ],
17
+ [
18
+ "Yandex"
19
+ ],
20
+ [
21
+ "porg-kqquuxx6"
22
+ ],
23
+ [
24
+ "--json-out"
25
+ ]
26
+ ],
27
+ "commandMustNotInclude": [
28
+ "reauth",
29
+ "delink"
30
+ ]
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.37-beta.17",
3
+ "version": "1.1.37-beta.19",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",