siluzan-tso-cli 1.1.53-beta.8 → 1.1.53-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.53-beta.8),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.53-beta.9),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -4479,6 +4479,78 @@ var init_cli_json_snapshot = __esm({
4479
4479
  }
4480
4480
  });
4481
4481
 
4482
+ // src/utils/media-customer-id.ts
4483
+ function stripMediaCustomerIdDecorators(raw) {
4484
+ return raw.trim().replace(/[\s-]+/g, "");
4485
+ }
4486
+ function looksLikeNumericMediaCustomerId(raw) {
4487
+ return /^\d+$/.test(stripMediaCustomerIdDecorators(raw));
4488
+ }
4489
+ function mediaCustomerIdsEqual(left, right) {
4490
+ const a = left.trim();
4491
+ const b = right.trim();
4492
+ if (!a || !b) return false;
4493
+ if (a === b) return true;
4494
+ const na = normalizeNumericMediaCustomerId(a);
4495
+ const nb = normalizeNumericMediaCustomerId(b);
4496
+ return na != null && nb != null && na === nb;
4497
+ }
4498
+ function normalizeNumericMediaCustomerId(raw) {
4499
+ if (!looksLikeNumericMediaCustomerId(raw)) return null;
4500
+ return stripMediaCustomerIdDecorators(raw);
4501
+ }
4502
+ function requireNumericMediaCustomerId(raw, label = "-a, --account") {
4503
+ const trimmed = raw?.toString().trim() ?? "";
4504
+ if (!trimmed) {
4505
+ throw new Error(`\u8BF7\u6307\u5B9A\u5A92\u4F53\u8D26\u6237 ID\uFF08${label}\uFF09`);
4506
+ }
4507
+ const id = normalizeNumericMediaCustomerId(trimmed);
4508
+ if (!id) {
4509
+ throw new Error(
4510
+ `\u5A92\u4F53\u8D26\u6237 ID \u5FC5\u987B\u4E3A\u7EAF\u6570\u5B57\uFF08Google CID \u53EF\u542B\u8FDE\u5B57\u7B26\uFF0C\u5982 123-456-7890\uFF1B\u52FF\u4F20 entityId/UUID\uFF09\uFF1A${trimmed}`
4511
+ );
4512
+ }
4513
+ return id;
4514
+ }
4515
+ function looksLikeEntityIdUuid(raw) {
4516
+ return ENTITY_ID_UUID_RE.test(raw.trim());
4517
+ }
4518
+ function warnIfAccountIdsLookLikeEntityId(ids, command) {
4519
+ const bad = ids.map((id) => id.trim()).filter((id) => id && looksLikeEntityIdUuid(id));
4520
+ if (bad.length === 0) return;
4521
+ console.error(
4522
+ `
4523
+ \u26A0\uFE0F ${command} \u7684 -a/--accounts \u6536\u5230\u7591\u4F3C entityId/UUID\uFF1A${bad.join(", ")}
4524
+ \u672C\u547D\u4EE4\u9700\u8981 mediaCustomerId\uFF08list-accounts \u7684 ma.mediaCustomerId\uFF1BYandex \u5F62\u5982 porg-\u2026\uFF09\u3002
4525
+ \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
4526
+ `
4527
+ );
4528
+ }
4529
+ function parseNumericMediaCustomerIds(input) {
4530
+ if (!input || !input.trim()) return [];
4531
+ const seen = /* @__PURE__ */ new Set();
4532
+ const out = [];
4533
+ for (const raw of input.split(",")) {
4534
+ const piece = raw.trim();
4535
+ if (!piece) continue;
4536
+ const id = normalizeNumericMediaCustomerId(piece);
4537
+ if (!id) {
4538
+ throw new Error(`mediaCustomerId \u5FC5\u987B\u4E3A\u7EAF\u6570\u5B57\uFF08\u53EF\u542B\u8FDE\u5B57\u7B26\uFF09\uFF1A${piece}`);
4539
+ }
4540
+ if (seen.has(id)) continue;
4541
+ seen.add(id);
4542
+ out.push(id);
4543
+ }
4544
+ return out;
4545
+ }
4546
+ var ENTITY_ID_UUID_RE;
4547
+ var init_media_customer_id = __esm({
4548
+ "src/utils/media-customer-id.ts"() {
4549
+ "use strict";
4550
+ 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;
4551
+ }
4552
+ });
4553
+
4482
4554
  // src/utils/currency-display.ts
4483
4555
  function formatMoneyDisplay(amount, currencyCode, options) {
4484
4556
  if (amount == null || Number.isNaN(Number(amount))) return "-";
@@ -5025,69 +5097,6 @@ var init_balance = __esm({
5025
5097
  }
5026
5098
  });
5027
5099
 
5028
- // src/utils/media-customer-id.ts
5029
- function stripMediaCustomerIdDecorators(raw) {
5030
- return raw.trim().replace(/[\s-]+/g, "");
5031
- }
5032
- function looksLikeNumericMediaCustomerId(raw) {
5033
- return /^\d+$/.test(stripMediaCustomerIdDecorators(raw));
5034
- }
5035
- function normalizeNumericMediaCustomerId(raw) {
5036
- if (!looksLikeNumericMediaCustomerId(raw)) return null;
5037
- return stripMediaCustomerIdDecorators(raw);
5038
- }
5039
- function requireNumericMediaCustomerId(raw, label = "-a, --account") {
5040
- const trimmed = raw?.toString().trim() ?? "";
5041
- if (!trimmed) {
5042
- throw new Error(`\u8BF7\u6307\u5B9A\u5A92\u4F53\u8D26\u6237 ID\uFF08${label}\uFF09`);
5043
- }
5044
- const id = normalizeNumericMediaCustomerId(trimmed);
5045
- if (!id) {
5046
- throw new Error(
5047
- `\u5A92\u4F53\u8D26\u6237 ID \u5FC5\u987B\u4E3A\u7EAF\u6570\u5B57\uFF08Google CID \u53EF\u542B\u8FDE\u5B57\u7B26\uFF0C\u5982 123-456-7890\uFF1B\u52FF\u4F20 entityId/UUID\uFF09\uFF1A${trimmed}`
5048
- );
5049
- }
5050
- return id;
5051
- }
5052
- function looksLikeEntityIdUuid(raw) {
5053
- return ENTITY_ID_UUID_RE.test(raw.trim());
5054
- }
5055
- function warnIfAccountIdsLookLikeEntityId(ids, command) {
5056
- const bad = ids.map((id) => id.trim()).filter((id) => id && looksLikeEntityIdUuid(id));
5057
- if (bad.length === 0) return;
5058
- console.error(
5059
- `
5060
- \u26A0\uFE0F ${command} \u7684 -a/--accounts \u6536\u5230\u7591\u4F3C entityId/UUID\uFF1A${bad.join(", ")}
5061
- \u672C\u547D\u4EE4\u9700\u8981 mediaCustomerId\uFF08list-accounts \u7684 ma.mediaCustomerId\uFF1BYandex \u5F62\u5982 porg-\u2026\uFF09\u3002
5062
- \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
5063
- `
5064
- );
5065
- }
5066
- function parseNumericMediaCustomerIds(input) {
5067
- if (!input || !input.trim()) return [];
5068
- const seen = /* @__PURE__ */ new Set();
5069
- const out = [];
5070
- for (const raw of input.split(",")) {
5071
- const piece = raw.trim();
5072
- if (!piece) continue;
5073
- const id = normalizeNumericMediaCustomerId(piece);
5074
- if (!id) {
5075
- throw new Error(`mediaCustomerId \u5FC5\u987B\u4E3A\u7EAF\u6570\u5B57\uFF08\u53EF\u542B\u8FDE\u5B57\u7B26\uFF09\uFF1A${piece}`);
5076
- }
5077
- if (seen.has(id)) continue;
5078
- seen.add(id);
5079
- out.push(id);
5080
- }
5081
- return out;
5082
- }
5083
- var ENTITY_ID_UUID_RE;
5084
- var init_media_customer_id = __esm({
5085
- "src/utils/media-customer-id.ts"() {
5086
- "use strict";
5087
- 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;
5088
- }
5089
- });
5090
-
5091
5100
  // src/utils/media-account-batch.ts
5092
5101
  function mediaAccountDataBatchSize(media) {
5093
5102
  switch (media) {
@@ -107485,6 +107494,7 @@ function register5(program2) {
107485
107494
  // src/commands/list-accounts/_shared.ts
107486
107495
  init_dist();
107487
107496
  init_auth();
107497
+ init_media_customer_id();
107488
107498
  var SCOPE_ACTIVATION_BYPASSED_HEADER = "x-scope-activation-bypassed";
107489
107499
  function isScopeActivationBypassed(headers) {
107490
107500
  if (!headers) return false;
@@ -107503,11 +107513,20 @@ var VALID_MEDIA_TYPES = [
107503
107513
  "MetaAd",
107504
107514
  "BingV2"
107505
107515
  ];
107516
+ var DEFAULT_LIST_ACCOUNTS_PAGE_SIZE = 20;
107517
+ var AGENT_LIST_ACCOUNTS_PAGE_SIZE = 999;
107518
+ function resolveListAccountsPageSize(opts) {
107519
+ const n = opts.pageSize;
107520
+ if (typeof n === "number" && Number.isFinite(n) && n > 0) {
107521
+ return n;
107522
+ }
107523
+ if (opts.jsonOut) return AGENT_LIST_ACCOUNTS_PAGE_SIZE;
107524
+ return DEFAULT_LIST_ACCOUNTS_PAGE_SIZE;
107525
+ }
107506
107526
  function applyListAccountsSearchKeyword(params, keyword) {
107507
107527
  const kw = (keyword ?? "").trim();
107508
- if (kw) {
107509
- params.set("keyword", kw);
107510
- }
107528
+ if (!kw) return;
107529
+ params.set("keyword", normalizeNumericMediaCustomerId(kw) ?? kw);
107511
107530
  }
107512
107531
  var PLATFORM_CONFIG = {
107513
107532
  Google: {
@@ -107632,12 +107651,14 @@ async function fetchGoogleAccountByMediaCustomerId(config, mediaCustomerId, verb
107632
107651
  }
107633
107652
  params.set(cfg.pageParam, "1");
107634
107653
  params.set("pageSize", "50");
107635
- params.set(cfg.idSearchParam, mediaCustomerId.trim());
107654
+ const id = normalizeNumericMediaCustomerId(mediaCustomerId) ?? mediaCustomerId.trim();
107655
+ params.set(cfg.idSearchParam, id);
107636
107656
  const url = `${config.apiBaseUrl}${cfg.path}?${params}`;
107637
107657
  const res = await apiFetchWithHeaders2(url, config, {}, verbose);
107638
107658
  const items3 = Array.isArray(res.data) ? res.data : [];
107639
- const id = mediaCustomerId.trim();
107640
- const match = items3.find((it) => String(it.ma?.mediaCustomerId ?? "") === id);
107659
+ const match = items3.find(
107660
+ (it) => mediaCustomerIdsEqual(String(it.ma?.mediaCustomerId ?? ""), mediaCustomerId)
107661
+ );
107641
107662
  return match ?? null;
107642
107663
  }
107643
107664
 
@@ -107996,7 +108017,7 @@ async function runListAccounts(opts) {
107996
108017
  process.exit(1);
107997
108018
  }
107998
108019
  const page = opts.page ?? 1;
107999
- const pageSize = opts.pageSize ?? 20;
108020
+ const pageSize = resolveListAccountsPageSize(opts);
108000
108021
  const platformCfg = opts.media ? PLATFORM_CONFIG[opts.media] : null;
108001
108022
  let items3;
108002
108023
  let total;
@@ -108075,6 +108096,8 @@ async function runListAccounts(opts) {
108075
108096
  const allFail = items3.every((it) => it.ma?.invalidOAuthToken === true);
108076
108097
  return allFail ? "\u68C0\u6D4B\u5230\u672C\u9875\u6240\u6709\u8D26\u6237\u5747\u6807\u8BB0\u4E3A OAuth \u5931\u6548\uFF0C\u8FD9\u901A\u5E38\u662F\u670D\u52A1\u7AEF\u4F1A\u8BDD\u5F02\u5E38\u6216\u6743\u9650\u6F02\u79FB\uFF08\u975E\u771F\u5931\u6548\uFF09\u3002\u5EFA\u8BAE\u6309\u987A\u5E8F\u6392\u67E5\uFF1A\n 1) siluzan-tso list-accounts --refresh-dp # \u5F3A\u5236\u5237\u65B0 Datapermission\n 2) siluzan-tso login # \u91CD\u65B0\u767B\u5F55\u83B7\u53D6\u65B0 authToken\n 3) 1 \u5206\u949F\u540E\u91CD\u8BD5\uFF1B\u82E5\u4ECD\u590D\u73B0\uFF0C\u8BF7\u5E26\u4E0A --verbose \u8F93\u51FA\u53CD\u9988\u7ED9\u5E73\u53F0\u3002" : null;
108077
108098
  })();
108099
+ const incompletePageWarning = total !== void 0 && items3.length < total ? "\u672C\u9875\u4E0D\u5B8C\u6574\uFF08itemCount < total\uFF09\u3002\u7981\u6B62\u636E\u6B64\u5224\u5B9A\u8D26\u6237\u672A\u7ED1\u5B9A\u6216\u672A\u6388\u6743\u3002\u7528\u6237\u7ED9\u4E86\u8D26\u6237\u53F7\u65F6\u5FC5\u987B list-accounts -m <\u5A92\u4F53> -k <\u7528\u6237\u539F\u6837ID>\uFF08Google CID \u5E26\u6A2A\u6760\u4E5F\u53EF\uFF0CCLI \u4F1A\u53BB\u8FDE\u5B57\u7B26\uFF09\u3002\u5217\u5168\u90E8\u987B --page-size 999\u3002" : null;
108100
+ const combinedWarning = [oauthFailWarning, incompletePageWarning].filter(Boolean).join("\n") || null;
108078
108101
  const listPayload = {
108079
108102
  ...wrapListJson({
108080
108103
  page,
@@ -108082,7 +108105,7 @@ async function runListAccounts(opts) {
108082
108105
  total: total ?? null,
108083
108106
  items: items3
108084
108107
  }),
108085
- ...oauthFailWarning ? { warning: oauthFailWarning } : {}
108108
+ ...combinedWarning ? { warning: combinedWarning } : {}
108086
108109
  };
108087
108110
  if (await emitCliJsonOrSnapshot(opts, {
108088
108111
  section: `list-accounts-${(opts.media ?? "unknown").replace(/\s+/g, "-")}`,
@@ -108132,9 +108155,9 @@ async function runListAccounts(opts) {
108132
108155
  );
108133
108156
  console.log(` siluzan-tso account withdraw-list`);
108134
108157
  }
108135
- if (oauthFailWarning) {
108158
+ if (combinedWarning) {
108136
108159
  console.log(`
108137
- \u26A0\uFE0F ${oauthFailWarning}`);
108160
+ \u26A0\uFE0F ${combinedWarning}`);
108138
108161
  }
108139
108162
  console.log();
108140
108163
  }
@@ -108146,11 +108169,18 @@ function register6(program2) {
108146
108169
  ).option(
108147
108170
  "-m, --media <type>",
108148
108171
  "\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | MetaAd | BingV2\uFF08\u7559\u7A7A\u5219\u67E5\u8BE2\u5168\u90E8\u5A92\u4F53\uFF09"
108149
- ).option("-k, --keyword <text>", "\u5173\u952E\u5B57\uFF08\u516C\u53F8/\u8D26\u6237\u540D/\u8D26\u6237ID \u6A21\u7CCA\u5339\u914D\uFF09").option(
108172
+ ).option(
108173
+ "-k, --keyword <text>",
108174
+ "\u5173\u952E\u5B57\uFF08\u516C\u53F8/\u8D26\u6237\u540D/\u8D26\u6237ID\uFF1BGoogle CID \u5E26\u6A2A\u6760\u5982 928-780-1878 \u4F1A\u53BB\u8FDE\u5B57\u7B26\uFF09"
108175
+ ).option(
108150
108176
  "-s, --status <status>",
108151
108177
  "\u8D26\u6237\u72B6\u6001\uFF1Anormal\uFF08\u6B63\u5E38\uFF09| invalid\uFF08\u5931\u6548\uFF09| all\uFF08\u5168\u90E8\uFF0C\u9ED8\u8BA4\uFF09",
108152
108178
  "all"
108153
- ).option("-p, --page <n>", "\u9875\u7801\uFF08\u9ED8\u8BA4 1\uFF09", parseInt).option("--page-size <n>", "\u6BCF\u9875\u6570\u91CF\uFF08\u9ED8\u8BA4 20\uFF09", parseInt).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option(
108179
+ ).option("-p, --page <n>", "\u9875\u7801\uFF08\u9ED8\u8BA4 1\uFF09", parseInt).option(
108180
+ "--page-size <n>",
108181
+ "\u6BCF\u9875\u6570\u91CF\uFF08\u4EA4\u4E92\u9ED8\u8BA4 20\uFF1B\u5E26 --json-out \u4E14\u672A\u6307\u5B9A\u65F6 999\uFF09",
108182
+ parseInt
108183
+ ).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option(
108154
108184
  "--json-out <path>",
108155
108185
  "\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",
108156
108186
  void 0
@@ -39,13 +39,13 @@ siluzan-tso -h
39
39
  ## 即时规范
40
40
 
41
41
  - `entityId`(UUID)≠ `mediaCustomerId`(`list-accounts` 的 `ma.mediaCustomerId`:Google/TikTok/Bing 多为数字;**Yandex=`porg-…`**;Meta 常带 `act_`,`balance`/`stats`/`balance-scan`/`accounts-digest` 裸数字会补前缀)。`stats`/`balance`/`accounts-digest`/`ad *` 的 `-a` **只传 mediaCustomerId**;空结果或 verbose 打出 403 时**先核验 ID**,禁止把 UUID/`entityId` 传给 `-a`,也**禁止**据此直接 `reauth`。
42
- - **Google CID 特殊性**:UI 常写 `123-456-7890`(带连字符),平台 `mediaCustomerId` 为纯数字 `1234567890`。CLI 会自动去连字符;**勿**把带横杠 403 误判为 OAuth 失效。优先用 `list-accounts` 的纯数字 ID。
42
+ - **Google CID 特殊性**:UI 常写 `123-456-7890`(带连字符),平台 `mediaCustomerId` 为纯数字 `1234567890`。用户给了账户号时必须 `list-accounts -m Google -k <用户原样>`(带横杠也可,CLI 去连字符);**禁止**无 `-k` 拉默认 20 条再判定未绑定/引导授权。`stats`/`balance`/`ad *`/`google-analysis` 的 `-a` 也可带横杠。**勿**把带横杠 403 误判为 OAuth 失效。
43
43
  - **禁止臆测授权过期**:见 403/`HTTP 403`/拉数失败/「可能 OAuth 失效」时,**禁止**口头推断授权过期或直接 `reauth`。须先核验 ID(及 Google 连字符)。**`account check-access` 仅支持 Google**(无 `-m`,禁止对 TikTok/Meta/Bing/Yandex 调用):Google 跑 `account check-access -a <mediaCustomerId>`,以 `accessible` / `reauth_required` / `no_permission` 为准;非 Google 看 `list-accounts` 的 `invalidOAuthToken`。仅当 `list-accounts` 输出含激活字段且可判定未激活时才跳过用其判断授权过期;细则见 `accounts-permissions.md`。
44
44
  - **Google 广告命令名**:列表是 `ad campaigns`(不是 `ad-campaigns` / `campaign list`);`balance`/`stats` 须带 `-m Google`;单国地域用 `ad geo search … --json-out`(或 `geo resolve`)。
45
45
  - **广告搭建媒体边界**:仅 **Google**(`ad`,Search + PMax)与 **Meta**(`meta-ad`,Instant Form 线索)。TikTok / BingV2 / Yandex **不能**创建或改广告结构;用户要搭建时须说明不支持,可改做账户分析(P4)或开户(W2)。**禁止**用 `ad` 冒充 Meta,**禁止**用 `meta-ad` 冒充 Google。
46
46
  - **Google 广告类型边界**:CLI/Skill **仅支持** Google **搜索广告(Search)** 与 **Performance Max(PMax)** 的方案、创建与精细管理;**不支持展示广告(Display)**(含自适应展示广告 RDA、独立 Display 系列)。用户要做展示广告时须明确告知不支持,可改推 Search / PMax;**禁止**用 `campaign-create` / `ad-create` 伪装创建 Display。
47
47
  - **创建完成后交付(硬性)**:`ad batch diff` 的 stdout 含 `BEGIN_USER_DELIVERY_MARKDOWN`…`END_USER_DELIVERY_MARKDOWN`——**立刻**把中间全文原样发给用户(可先于补建);禁止只摘要、禁止只说「创建成功/详情已交付/未发现缺失」、禁止等全部补建结束再交付。
48
- - `list-accounts` 无余额/消耗;列全部用 `--page-size 999`。
48
+ - `list-accounts` 无余额/消耗;列全部用 `--page-size 999`。查单户必须 `-k`;`itemCount < total` 时**禁止**判定账户未绑定。
49
49
  - 多户余额用 `balance-scan`(P2),多户消耗 / **转化成本·CPA 巡检**用 `accounts-digest`(P3,`--max-cpa`;**没有** `cpa-risk-scan`)。禁止外层 for-loop 逐户拉数。
50
50
  - `stats` 的 `spend` = **区间合计**,不是日消耗。
51
51
  - 行业/市场报告禁止纯 WebSearch 当终稿;须 P9 `market-analysis collect`+`render`。
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.53-beta.8",
4
- "publishedAt": 1789975139832
3
+ "version": "1.1.53-beta.9",
4
+ "publishedAt": 1790064329339
5
5
  }
@@ -17,10 +17,10 @@ siluzan-tso list-accounts [选项]
17
17
  | 选项 | 说明 |
18
18
  | ----------------------- | ---------------------------------------------------------------------------------- | ---------------------- |
19
19
  | `-m, --media <type>` | 媒体类型(留空查全部):`Google \| TikTok \| Yandex \| MetaAd \| BingV2` |
20
- | `-k, --keyword <text>` | 统一模糊搜索:账户名称 **或** ID(含 Google CID 带横杠);透传 Sammamish `keyword` |
20
+ | `-k, --keyword <text>` | 统一模糊搜索:账户名称 **或** ID。数字型 ID(含 Google `928-780-1878`)CLI 先去连字符再搜;**查单户必须带 `-k`** |
21
21
  | `-s, --status <status>` | 账户状态:`normal \| invalid \| all`(默认 all) |
22
22
  | `-p, --page <n>` | 页码(默认 1) |
23
- | `--page-size <n>` | 每页数量(默认 20) |
23
+ | `--page-size <n>` | 每页数量(交互默认 20;`--json-out` 未指定时 999) |
24
24
  | `--json-out` | 输出原始 JSON |
25
25
  | `--unicode` | 表格使用 Unicode 线框;**默认**为 ASCII `+- | ` 线框(兼容各类终端) |
26
26
  | `--plain` | 已默认 ASCII,无需再传;保留兼容旧脚本 |
@@ -37,7 +37,7 @@ siluzan-tso list-accounts [选项]
37
37
  | 列出全部某媒体账户 | `list-accounts -m <媒体> --page-size 999 --json-out <dir>` | `items[]`(`ma.mediaCustomerId`、`ma.mediaCustomerName`、`ma.currencyCode`、`ma.invalidOAuthToken` 等) |
38
38
  | 哪些账户 OAuth / 授权失效 | 同上(或 `-s invalid`);表格看「授权状态」列 | **`ma.invalidOAuthToken === true`**(勿用审核状态 / `mediaAccountState`) |
39
39
  | 有多少个账户 | 同上 | **`total`**(无需翻页;`itemCount < total` 时说明 page-size 不够大) |
40
- | 只查某一个户 | `list-accounts -m <媒体> -k <id或名称> --json-out <dir>` | 无需大 page-size |
40
+ | 只查某一个户 | `list-accounts -m <媒体> -k <用户原样ID或名称> --json-out <dir>` | Google CID 带横杠可原样;**禁止**无 `-k` 拉首页再判定未绑定 |
41
41
  | **Meta 全部账户 + 余额/消耗** | **`accounts-digest -m MetaAd --json-out <dir>`**(一步;内部翻页+分批) | `accounts-digest-metaad.json` → `data.items[]`(含 `balance`、`spend`) |
42
42
  | Meta 余额续航预警 | `balance-scan -m MetaAd --json-out <dir>` | `balance-scan-metaad.json` |
43
43
 
@@ -19,7 +19,7 @@
19
19
  - `list-accounts` **不含**余额/消耗;列全部账户用 `--page-size 999`,禁止默认 20 再翻页。
20
20
  - `entityId`(UUID,分享/delink/账单/`reauth`)≠ `mediaCustomerId`(`balance`/`stats`/`accounts-digest`/`ad` 的 `-a`)。**Yandex 的 mediaCustomerId 形如 `porg-…`,不是 UUID。**
21
21
  - MetaAd OAuth 户官方 `mediaCustomerId` 带 `act_`。`balance` / `stats` / `balance-scan` / `accounts-digest` 的 `-a` 传裸数字或 `act_` 均可,CLI 会规范成清单官方 ID 再打 TSO。
22
- - **Google CID**:广告后台展示常为 `XXX-XXX-XXXX`(连字符),`list-accounts` / 网关要的是**纯数字**(无横杠)。`stats`/`balance`/`ad *`/`google-analysis` 的 `-a` 可带连字符(CLI 会去掉);若仍见 `HTTP 403:123-456-7890` 这类**回显带横杠 ID**,优先改成纯数字重试,**不要**直接当 OAuth 失效去 `reauth`。
22
+ - **Google CID**:广告后台展示常为 `XXX-XXX-XXXX`(连字符),平台 `mediaCustomerId` 是**纯数字**。查单户必须 `list-accounts -m Google -k <用户原样>`(CLI 去横杠);**禁止**无 `-k` 拉首页 20 条再判定未绑定。`stats`/`balance`/`ad *`/`google-analysis` 的 `-a` 可带连字符(CLI 会去掉);若仍见 `HTTP 403:123-456-7890` 这类**回显带横杠 ID**,优先改成纯数字重试,**不要**直接当 OAuth 失效去 `reauth`。
23
23
  - **禁止臆测授权过期**:403 / 拉数失败时**禁止**口头说「可能授权过期」。须 ID 核验后,**仅 Google** 执行 `account check-access -a <mediaCustomerId>`(无 `-m`,其他媒体禁止调用),以返回 `status` 为准(见 [`accounts-permissions.md`](accounts-permissions.md));仅当 `list-accounts` 输出含激活字段且可判定未激活时,勿用其判断授权过期;非 Google 看 `invalidOAuthToken`。
24
24
  - `stats`/`balance` 空结果 + verbose `HTTP 403`:先确认 `-a` 是否为 `ma.mediaCustomerId`(且 Google 已去连字符);**禁止**把 `entityId` / tokenId 当 `-a`;未跑 `account check-access`(或未见 `invalidOAuthToken=true`)**禁止** `reauth`。
25
25
  - 多账户余额预警用 `balance-scan`(P2),多账户消耗汇总用 `accounts-digest`(P3);**禁止**外层 for-loop 逐户 `balance`/`stats`。
@@ -184,7 +184,7 @@
184
184
 
185
185
  **例外**(不反问):
186
186
 
187
- - `list-accounts` 列全部 / 数个数:一次 `list-accounts -m <媒体> --page-size 999 --json-out <dir>`,脚本读落盘 `total` / `items[]`;**禁止**默认 page-size 20 再翻页(详见 `references/accounts/accounts-list.md` § Agent 意图速查)。
187
+ - `list-accounts` 列全部 / 数个数:一次 `list-accounts -m <媒体> --page-size 999 --json-out <dir>`,脚本读落盘 `total` / `items[]`;**禁止**默认 page-size 20 再翻页(详见 `references/accounts/accounts-list.md` § Agent 意图速查)。用户给了账户号(含 Google `123-456-7890`)必须加 `-k <原样>`;`itemCount < total` 时**禁止**判定未绑定或引导授权。
188
188
  - 「昨天」单日 stats:默认 `Asia/Shanghai` 日历日;先 `list-accounts` 再 `stats`。
189
189
  - `forewarning records`、`invoice list`「本月」、TikTok `clue`「最近一周」:见对应 references。
190
190
 
@@ -138,7 +138,7 @@
138
138
  - **必读**:`report-templates/okki-weekly-google-client.md`(**全文**)+ `references/analytics/account-analytics.md`;可选 `references/core/subagent-orchestration.md`。
139
139
  - **步骤**:
140
140
  1. (可选)Read `subagent-orchestration.md` § P6 决定是否分阶段委派。
141
- 2. 确认 `mediaCustomerId` 与 `--start` / `--end`。
141
+ 2. `list-accounts -m Google -k <用户给的账户号>`(`928-780-1878` 可原样,CLI 去连字符)确认 `mediaCustomerId`;无 `-k` 的默认首页或 `itemCount < total` **禁止**判定未绑定。确认 `--start` / `--end`。
142
142
  3. 同一 `--json-out` 目录执行模板命令组合:`stats`、`balance`、`google-analysis --sections overview,campaigns,keywords,search-terms,campaign-device,campaign-geo-matched`。
143
143
  4. 脚本读盘 → 只写 `okki-weekly-report.json` 的 `meta` + `narrative`(5 Sheet 表末分析 + 账户 Sheet 固定 5 维数据复盘;表为空也须写)。`deviceType` 是 `Desktop`/`Mobile`,禁止 `.get("DESKTOP")`,按大小写不敏感分类累加后再写设备金额。
144
144
  5. `google-analysis okki-render --data ./okki-weekly-report.json --snapshot-dir ./snap-okki --out <xlsx>`。缺分析 / 缺 5 维复盘 / 缺 `campaign-device` 或 `campaign-geo-matched` 会拒绝出文件;按 stderr 补 JSON 后重跑。**禁止** Agent 手写 xlsx。
@@ -29,7 +29,7 @@
29
29
 
30
30
  ## 须先确认
31
31
 
32
- 1. **账户**:`mediaCustomerId` 来自 `list-accounts -m Google -k <id>`,禁止猜 ID。
32
+ 1. **账户**:`mediaCustomerId` 来自 `list-accounts -m Google -k <用户原样账户号>`(`928-780-1878` 可原样,CLI 去连字符),禁止猜 ID;**禁止**无 `-k` 拉默认 20 条后判定未绑定。
33
33
  2. **统计区间**:`--start` / `--end`(用户未给齐时按 SKILL 反问;授权默认时可用「上一完整自然周」白名单并写明)。
34
34
  3. **询盘口径**:默认使用区间内 `conversions`(转化次数,来自 `stats` 或 `overview`)。若运营定义为某类转化,须在首段脚注说明(必要时补拉 `google-analysis --sections conversion-actions` 仅作说明)。
35
35
  4. **日期写法**:`2026.4.1`、`2026/4/1` 等先规范为 `YYYY-MM-DD`(如 `2026-04-01`)再传 `--start` / `--end`。
@@ -29,7 +29,7 @@
29
29
 
30
30
  ## 须先确认
31
31
 
32
- 1. **账户**:`mediaCustomerId` 来自 `list-accounts -m Google -k <id>`,禁止猜 ID。
32
+ 1. **账户**:`mediaCustomerId` 来自 `list-accounts -m Google -k <用户原样账户号>`(`928-780-1878` 可原样,CLI 去连字符),禁止猜 ID;**禁止**无 `-k` 拉默认 20 条后判定未绑定。
33
33
  2. **统计区间**:`--start` / `--end`(用户未给齐时按 SKILL 反问;授权默认时可用「上一完整自然周」白名单并写明)。
34
34
  3. **询盘口径**:默认使用区间内 `conversions`(转化次数,来自 `stats` 或 `overview`)。若运营定义为某类转化,须在首段脚注说明(必要时补拉 `google-analysis --sections conversion-actions` 仅作说明)。
35
35
  4. **日期写法**:`2026.4.1`、`2026/4/1` 等先规范为 `YYYY-MM-DD`(如 `2026-04-01`)再传 `--start` / `--end`。
@@ -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.53-beta.8'
12
+ $PKG_VERSION = '1.1.53-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.53-beta.8"
12
+ readonly PKG_VERSION="1.1.53-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"
@@ -0,0 +1,26 @@
1
+ {
2
+ "id": "accounts-google-cid-hyphen-must-keyword",
3
+ "description": "带横杠 Google CID 必须 -k 查单户,禁止默认 20 条后判定未绑定",
4
+ "turns": [
5
+ "帮我查一下谷歌账户开禾科技(宁波)有限公司:928-780-1878 有没有绑在丝路赞上,用 siluzan-tso,需要 JSON。"
6
+ ],
7
+ "judgeExpectation": "路径:用户给出带连字符的 Google CID(如 928-780-1878)时,必须 `list-accounts -m Google -k <该ID>`(带横杠或去横杠均可);禁止无 -k 只拉默认 20 条再本地 grep。\n禁止:因首页 20 条未出现该户就判定未绑定、引导授权/present_connect。\n输出:说明是否查到该户即可;stub 即可。",
8
+ "skillMapping": "SKILL.md Google CID;references/accounts/accounts-list.md(查单户必须 -k)",
9
+ "judgeReferencePaths": [
10
+ "references/accounts/accounts-list.md",
11
+ "references/core/agent-conventions.md"
12
+ ],
13
+ "commandMustInclude": [
14
+ [
15
+ "list-accounts"
16
+ ],
17
+ [
18
+ "-k",
19
+ "--keyword"
20
+ ],
21
+ [
22
+ "928-780-1878",
23
+ "9287801878"
24
+ ]
25
+ ]
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.53-beta.8",
3
+ "version": "1.1.53-beta.9",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",