siluzan-tso-cli 1.1.18-beta.4 → 1.1.18-beta.6

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.18-beta.4),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.18-beta.6),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
@@ -69,7 +69,18 @@ siluzan-tso init --force # 强制覆盖已存在文件
69
69
 
70
70
  **若用户已安装 `siluzan-cso` 并完成登录,无需重复操作,直接跳到第 4 步。**
71
71
 
72
- ### 方式 A:交互式登录(推荐)
72
+ **推荐顺序**:① **手机号 + 验证码**(首选)完整参数与排错见 `references/setup.md`(随 Skill 安装到本地)。
73
+
74
+ ### 方式 A:手机号 + 验证码(**推荐**)
75
+
76
+ ```bash
77
+ siluzan-tso send-login-code --phone <手机号>
78
+ siluzan-tso login --phone <手机号> --code <6位验证码>
79
+ ```
80
+
81
+ 不向终端索要交互输入;Agent 先发码、用户回填验证码后再执行第二步。手机号须已在丝路赞注册。
82
+
83
+ ### 方式 B:交互式登录(需真人 TTY)
73
84
 
74
85
  ```bash
75
86
  siluzan-tso login
@@ -81,13 +92,13 @@ siluzan-tso login
81
92
  siluzan-tso login --api-key <YOUR_API_KEY>
82
93
  ```
83
94
 
84
- ### 方式 B:直接设置(适合自动化场景)
95
+ ### 方式 C:直接设置(适合自动化场景)
85
96
 
86
97
  ```bash
87
98
  siluzan-tso config set --api-key <你的ApiKey>
88
99
  ```
89
100
 
90
- ### 方式 C:Token 登录(已有 siluzan-cso 账号)
101
+ ### 方式 D:Token 登录(已有 siluzan-cso 账号)
91
102
 
92
103
  ```bash
93
104
  npm install -g siluzan-cso-cli
@@ -137,4 +148,4 @@ siluzan-tso init --ai xxx --force
137
148
 
138
149
  ## 6. Token 续期
139
150
 
140
- Token 过期后,重新运行 `siluzan-cso login` `siluzan-tso config set --api-key <新Key>` 即可,`siluzan-tso` 自动读取新凭据,无需额外操作。
151
+ Token 过期后:**优先**再走一遍**方式 A**(手机验证码)签发新 API Key;或 `siluzan-cso login` / `siluzan-tso config set --api-key <新Key>`,`siluzan-tso` 自动读取新凭据。
package/dist/index.js CHANGED
@@ -1974,6 +1974,7 @@ import { performance } from "perf_hooks";
1974
1974
  import * as fs2 from "fs";
1975
1975
  import * as path2 from "path";
1976
1976
  import { fileURLToPath } from "url";
1977
+ import { spawn } from "child_process";
1977
1978
  import Table from "cli-table3";
1978
1979
  import * as path3 from "path";
1979
1980
  import { chmod, mkdir, open, readdir, stat, unlink } from "fs/promises";
@@ -2375,6 +2376,29 @@ async function fetchNpmVersion(pkgName, tag, timeoutMs = 4e3) {
2375
2376
  return null;
2376
2377
  }
2377
2378
  }
2379
+ async function defaultRunMinRequiredGlobalInstall(ctx) {
2380
+ if (process.env.SILUZAN_SKIP_AUTO_GLOBAL_INSTALL === "1") {
2381
+ return { ok: false, stderr: "SILUZAN_SKIP_AUTO_GLOBAL_INSTALL=1" };
2382
+ }
2383
+ const spec = `${ctx.pkgName}@${ctx.tag}`;
2384
+ return await new Promise((resolve32) => {
2385
+ const child = spawn("npm", ["install", "-g", spec, "--no-fund", "--no-audit"], {
2386
+ stdio: "inherit",
2387
+ shell: true
2388
+ });
2389
+ child.on("error", (err) => {
2390
+ resolve32({ ok: false, stderr: err instanceof Error ? err.message : String(err) });
2391
+ });
2392
+ child.on("close", (code, signal) => {
2393
+ if (code === 0) {
2394
+ resolve32({ ok: true });
2395
+ } else {
2396
+ const sig = signal ? ` signal=${signal}` : "";
2397
+ resolve32({ ok: false, stderr: `exit code ${code}${sig}` });
2398
+ }
2399
+ });
2400
+ });
2401
+ }
2378
2402
  function createVersionNotifier(opts) {
2379
2403
  const {
2380
2404
  pkgName,
@@ -2383,6 +2407,7 @@ function createVersionNotifier(opts) {
2383
2407
  resolveTag,
2384
2408
  forceUpdateExtra = "",
2385
2409
  updateAvailableExtra = "",
2410
+ runMinRequiredGlobalInstall = defaultRunMinRequiredGlobalInstall,
2386
2411
  getCurrentVersion: getCurrentVersion22,
2387
2412
  mergeWriteConfig,
2388
2413
  readConfigRaw
@@ -2430,11 +2455,26 @@ function createVersionNotifier(opts) {
2430
2455
  await mergeWriteConfig({ [KEY_LAST_NOTIFIED]: (/* @__PURE__ */ new Date()).toISOString() });
2431
2456
  console.log(
2432
2457
  `
2433
- [SILUZAN_FORCE_UPDATE] \u5F53\u524D\u7248\u672C ${current} \u5DF2\u8FC7\u671F\uFF0C\u6700\u4F4E\u8981\u6C42\u7248\u672C ${minRequired}${tagLabel}
2434
- \u26D4 \u5FC5\u987B\u5148\u5347\u7EA7\u624D\u80FD\u7EE7\u7EED\u4F7F\u7528\uFF0CAgent \u8BF7\u7ACB\u5373\u6267\u884C\uFF1A${binName} update
2435
- \u6216\u624B\u52A8\uFF1Anpm install -g ${pkgName}@${tag}
2458
+ [SILUZAN_FORCE_UPDATE] \u5F53\u524D\u7248\u672C ${current} \u5DF2\u4F4E\u4E8E\u6700\u4F4E\u8981\u6C42 ${minRequired}${tagLabel}
2459
+ \u26D4 \u6B63\u5728\u81EA\u52A8\u6267\u884C\uFF1Anpm install -g ${pkgName}@${tag}
2436
2460
  ` + (forceUpdateExtra ? forceUpdateExtra + "\n" : "")
2437
2461
  );
2462
+ const installResult = await runMinRequiredGlobalInstall({ pkgName, tag });
2463
+ if (installResult.ok) {
2464
+ console.log(
2465
+ `
2466
+ [SILUZAN_AUTO_GLOBAL_INSTALL_OK] \u5168\u5C40\u5B89\u88C5\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C\u672C\u547D\u4EE4\u4EE5\u52A0\u8F7D\u65B0\u7248\u672C ${pkgName}@${tag}\u3002
2467
+ `
2468
+ );
2469
+ } else {
2470
+ console.log(
2471
+ `
2472
+ [SILUZAN_AUTO_GLOBAL_INSTALL_FAILED] \u81EA\u52A8\u5168\u5C40\u5B89\u88C5\u5931\u8D25\uFF1A${installResult.stderr ?? "unknown"}
2473
+ \u8BF7\u624B\u52A8\u6267\u884C\uFF1Anpm install -g ${pkgName}@${tag}
2474
+ \u6216\uFF1A${binName} update
2475
+ ` + (forceUpdateExtra ? forceUpdateExtra + "\n" : "")
2476
+ );
2477
+ }
2438
2478
  return;
2439
2479
  }
2440
2480
  if (latest && isNewer(current, latest)) {
@@ -13197,11 +13237,10 @@ async function runAdCampaigns(opts) {
13197
13237
  }
13198
13238
  const rawItems = data.data ?? [];
13199
13239
  const items = rawItems.map((item) => {
13200
- const budgetDisplay = toDisplayMoney(item.budget);
13240
+ const budgetYuan = toDisplayMoney(item.budget);
13201
13241
  return {
13202
13242
  ...item,
13203
- budgetDisplay,
13204
- budgetUnit: "display",
13243
+ budget: budgetYuan,
13205
13244
  statusDisplay: formatGoogleCampaignListStatus(item)
13206
13245
  };
13207
13246
  });
@@ -13245,7 +13284,7 @@ async function runAdCampaigns(opts) {
13245
13284
  const rows = items.map((item) => {
13246
13285
  const ctr = item.ctr != null ? (Number(item.ctr) * 100).toFixed(2) + "%" : "\u2014";
13247
13286
  const spend = item.spend != null ? Number(item.spend).toFixed(2) : "\u2014";
13248
- const budget = item.budgetDisplay != null ? item.budgetDisplay.toFixed(2) : "\u2014";
13287
+ const budget = item.budget != null ? Number(item.budget).toFixed(2) : "\u2014";
13249
13288
  return {
13250
13289
  name: (item.name ?? "").slice(0, nameW),
13251
13290
  status: item.statusDisplay ?? formatGoogleCampaignListStatus(item),
@@ -16404,7 +16443,7 @@ async function runAccountShareDetail(opts) {
16404
16443
 
16405
16444
  // src/commands/account-manage/oauth.ts
16406
16445
  init_auth();
16407
- import { spawn } from "child_process";
16446
+ import { spawn as spawn2 } from "child_process";
16408
16447
  function toApiMediaType(media) {
16409
16448
  return media === "Meta" ? "FacebookAds" : media;
16410
16449
  }
@@ -16415,17 +16454,17 @@ function tryOpenBrowser(url) {
16415
16454
  return false;
16416
16455
  }
16417
16456
  if (process.platform === "win32") {
16418
- const child = spawn("rundll32", ["url.dll,FileProtocolHandler", parsed.toString()], {
16457
+ const child = spawn2("rundll32", ["url.dll,FileProtocolHandler", parsed.toString()], {
16419
16458
  detached: true,
16420
16459
  stdio: "ignore",
16421
16460
  windowsHide: true
16422
16461
  });
16423
16462
  child.unref();
16424
16463
  } else if (process.platform === "darwin") {
16425
- const child = spawn("open", [parsed.toString()], { detached: true, stdio: "ignore" });
16464
+ const child = spawn2("open", [parsed.toString()], { detached: true, stdio: "ignore" });
16426
16465
  child.unref();
16427
16466
  } else {
16428
- const child = spawn("xdg-open", [parsed.toString()], { detached: true, stdio: "ignore" });
16467
+ const child = spawn2("xdg-open", [parsed.toString()], { detached: true, stdio: "ignore" });
16429
16468
  child.unref();
16430
16469
  }
16431
16470
  return true;
@@ -26,7 +26,7 @@ allowed-tools: Bash(siluzan-tso:*) Read Write
26
26
 
27
27
  Windows 注意:部分 Agent 客户端通过 PowerShell / cmd 代执行命令时存在兼容性问题。若上述命令异常失败,请先安装 [Git for Windows](https://git-scm.com/download/win),然后在 Git Bash 中执行 macOS / Linux / WSL 的 Bash 安装命令。
28
28
 
29
- 参照 `references/setup.md` 完成安装与配置。
29
+ 参照 `references/setup.md` 完成安装与配置。**需登录或我方 401 换凭据时,优先引导「手机号 + 验证码」**(`send-login-code` → `login --phone --code`),详见 `references/setup.md` 登录优先级说明。
30
30
 
31
31
  ---
32
32
 
@@ -34,7 +34,7 @@ Windows 注意:部分 Agent 客户端通过 PowerShell / cmd 代执行命令
34
34
 
35
35
  | 文档 | 功能 |
36
36
  |------|------|
37
- | `references/setup.md` | 安装、登录、配置、环境切换、更新 |
37
+ | `references/setup.md` | 安装、**登录(手机验证码优先)**、配置、环境切换、更新 |
38
38
  | `references/workflows.md` | 多步骤业务流程、跨命令串联 |
39
39
  | `references/tips.md` | **Agent 拉数一律 `--json-out`**(目录或 `*.json` 文件)+ 读 `cli-manifest[-<查询id>].json` / 各 `<section>[-<查询id>].json` + **`*.outline.txt`**(TS 式类型,**几百字节,描述完整字段结构**——写聚合脚本前先读它而不是 `Read` 整个 JSON,省 2~3 个数量级 context);stdout 一行摘要含 `manifestFile` / `writtenFiles` / `outlineFile` 等 |
40
40
  | `references/accounts.md` | 账户列表、余额、消耗、开户记录、授权/解绑/分享/MCC/BC/BM/邮箱授权 |
@@ -129,6 +129,7 @@ Windows 注意:部分 Agent 客户端通过 PowerShell / cmd 代执行命令
129
129
  ### 金额与品牌名
130
130
 
131
131
  - **永远使用 `*Display` 字段或表格展示值**做用户可见金额。报告/表格金额保留 2 位小数,写明货币代码(例如 `¥50.00 CNY`)。
132
+ - **`ad campaigns --json`/`--json-out`**:列表里的 **`budget` 已为 CLI 换算后的主币种「元」**(与 `ad campaign-edit --budget` 写参同口径),可直接作用户可见日预算;**勿再 ÷100**。见 `references/google-ads.md`「ad campaigns」。
132
133
  - **`google-analysis` 落盘 `campaigns-*.json`**:常仅有 **`budgetAmount`(分,元=÷100)**,**禁止**误作微元 **÷1_000_000**;同文件 `spend` 等已为元。见 `references/account-analytics.md`、`references/tips.md`「campaigns-*.json」。
133
134
  - **品牌名**必须来自(按优先级):(1) 用户明确提供 (2) `list-accounts` 返回的 `mag.advertiserName` (3) 用户提供的网址→域名占位并标注 `[待确认品牌名]`。**严禁**把英文域名自行翻译为虚构中文品牌。
134
135
 
@@ -146,7 +147,7 @@ Windows 注意:部分 Agent 客户端通过 PowerShell / cmd 代执行命令
146
147
  2. 中断后**必须**用 `resume --run-id <id>` 续跑,**禁止**重新 `run`。
147
148
  3. stdout 始终是单行 JSON(`kind=siluzan-tso-batch-summary`);进度读 `progress.json`、轨迹读 `state/tasks.jsonl`。
148
149
  4. 退出码:`0` 全成功 / `2` 部分成功 / `3` 全失败或 Token 失效 / `4` 用法错误。
149
- 5. 401 响应 → 整批终止 + `tokenInvalidated:true`,提示用户重新登录`references/setup.md` 后再 resume
150
+ 5. 401 响应 → 整批终止 + `tokenInvalidated:true`,提示用户按 `references/setup.md` **优先手机验证码**重新登录后再 `resume`。
150
151
 
151
152
  若无批量命令(如 117 个 Bing 账户剩余天数计算):先 `list-accounts --json-out <dir>` 一次性拿全量 → `node -e` 本地计算 → 只对命中账户做后续操作。
152
153
 
@@ -243,7 +244,7 @@ siluzan-tso accounts-digest -m Google -a id1,id2,... --start <S> --end <D> --jso
243
244
  ### 常见 HTTP 状态码
244
245
 
245
246
  - **400**:参数错误,查看对应 reference 或用 `-h` 了解命令用法
246
- - **401**:平台方返回则需用户重新授权;我方返回则让用户执行 `siluzan-tso login`
247
+ - **401**:平台方返回则需用户重新授权;**我方凭据失效**则优先 **`send-login-code` + `login --phone --code`**(或 TTY 下 `siluzan-tso login` / `config set …`),见 `references/setup.md`
247
248
  - **500**:服务可能正在部署/升级,建议提交给 Siluzan 相关人员
248
249
 
249
250
  ### 报告模板外部资源
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.18-beta.4",
4
- "publishedAt": 1778321128924
3
+ "version": "1.1.18-beta.6",
4
+ "publishedAt": 1778324246028
5
5
  }
@@ -74,14 +74,14 @@
74
74
 
75
75
  ### 金额单位
76
76
 
77
- - **永远使用 `*Display` 字段**(如 `budgetDisplay`),不得将 `budget`/`maxCPCAmount` 等 ×100 分单位直接当金额展示。
77
+ - **永远使用 `*Display` 字段**(如 `maxCPCAmountDisplay`),不得将 `maxCPCAmount` 等 ×100 分单位直接当金额展示。**例外**:`siluzan-tso ad campaigns` 列表的 **`--json`/`--json-out` 中 `budget` 已由 CLI 换为「元」**,可直接作用户可见日预算,勿再 ÷100。
78
78
  - **`google-analysis` 落盘 `campaigns-<id>.json`(`CampaignSectionData`)特例**:响应里常见只有 **`budgetAmount`(无 `budgetDisplay`)**。该字段与网关 **`Budget` 同为「分」整数**(主币种 ×100),换算为元须 **÷100**。**禁止**误用 Google Ads API 的**微元**去 **÷1_000_000**,否则日预算会约偏小 **一万倍**(例如 `15000` 应为 ¥150.00,误除微元会得到 ¥0.015)。同文件 **`spend`、`averageCpc`、`costPerConversion` 等已是「元」小数**,与 `budgetAmount` 单位不同,写 Excel/报告脚本时须分支处理。对应 `*.outline.txt` 内写有**同等硬提示**(`campaigns` 维度)。
79
79
  - 金额保留 2 位小数,带货币代码(如 `¥50.00 CNY`),`currencyCode` 从响应读取。跨币种账户分表。
80
80
  - CNY使用 ¥50,USD使用 $50 不要混用!!!
81
81
 
82
82
  ### 预算建议
83
83
 
84
- 基于当前实际预算(`budgetDisplay`)、历史日均消耗、用户给的预算上限给出建议。数据不足以判断时,在报告里写明「建议区间需用户确认」而非直接给高风险数字。
84
+ 基于当前实际预算(如 `ad campaigns --json` 的 **`budget`(元)** 或报表中的 `*Display`/分析字段)、历史日均消耗、用户给的预算上限给出建议。数据不足以判断时,在报告里写明「建议区间需用户确认」而非直接给高风险数字。
85
85
 
86
86
  ---
87
87
 
@@ -1,7 +1,5 @@
1
1
  # 账户管理命令详解
2
2
 
3
- > 所属 skill:`siluzan-tso`。通用选项 `--token <token>` 可覆盖配置文件中的 Token(通常无需传,直接使用 `siluzan-tso login` 保存的配置)。
4
-
5
3
  ---
6
4
 
7
5
  ## list-accounts — 查询广告账户列表
@@ -125,7 +125,7 @@ siluzan-tso ad campaigns -a 6326027735 --start 2026-03-01 --end 2026-03-31
125
125
  siluzan-tso ad campaigns -a 6326027735 --json
126
126
  ```
127
127
 
128
- 输出字段:名称、状态、类型、预算、点击数、展示数(具体字段名以 `--json` 为准;另有 CLI 派生的 `statusDisplay`、`budgetDisplay` 等便于阅读)。
128
+ 输出字段:名称、状态、类型、预算、点击数、展示数(具体字段名以 `--json` 为准)。**`--json` / `--json-out` 下 `budget` 为日预算,已由 CLI 统一为主币种「元」**(网关列表接口的「分」在 CLI 内 ÷100),与 `ad campaign-edit --budget` 等写参同口径;另有 CLI 派生的 **`statusDisplay`**(状态文案)等便于阅读。
129
129
 
130
130
  ---
131
131
 
@@ -849,9 +849,9 @@ siluzan-tso ad campaign-edit -a 6326027735 --id 23509626948 \
849
849
  --name "搜索-春季促销-2026" --budget 5000
850
850
 
851
851
  # 在原预算基础上 +10 元(典型相对运算流程):
852
- # 1) 先 GET 系列详情,读出 budgetDisplay(主币种)
853
- # 2) 计算新值(主币种)= budgetDisplay + 10
854
- # 3) 把新值(主币种)作为 --budget 传入;CLI 内部自动 ×100
852
+ # 1) 先 `ad campaigns --json` 读出该系列 `budget`(已为**主币种元**)
853
+ # 2) 计算新值(主币种)= budget + 10
854
+ # 3) 把新值(主币种)作为 --budget 传入;CLI 内部自动 ×100 写入网关「分」
855
855
  siluzan-tso ad campaign-edit -a 6326027735 --id 23509626948 --budget 10.01
856
856
 
857
857
  # 切换出价策略为 TARGET_CPA,目标 CPA 2 USD
@@ -32,7 +32,7 @@ mkdir -p ./snap-scale && siluzan-tso google-analysis -a <mediaCustomerId> --sect
32
32
  siluzan-tso ad campaigns -a <mediaCustomerId> --start <YYYY-MM-DD> --end <YYYY-MM-DD> --json
33
33
  ```
34
34
 
35
- 关注 **`budget`** / **`budgetDisplay`**、**`statusV2`**、**`id`**(系列 ID),与 `SKILL.md` 金额硬规范一致后再算「+20%」等新预算。
35
+ 关注 **`budget`**(`ad campaigns --json` 下**已为元**)、**`statusV2`**、**`id`**(系列 ID),与 `SKILL.md` 金额硬规范一致后再算「+20%」等新预算。
36
36
 
37
37
  ### 3. 消耗节奏(可选)
38
38
 
@@ -60,7 +60,7 @@ mkdir -p ./snap-scale && siluzan-tso google-analysis -a <mediaCustomerId> --sect
60
60
 
61
61
  ```bash
62
62
  # 1) GET 取主币种当前值
63
- CUR=$(siluzan-tso ad campaigns -a <mediaCustomerId> --json | node -e '...筛选 budgetDisplay')
63
+ CUR=$(siluzan-tso ad campaigns -a <mediaCustomerId> --json | node -e '...筛选 budget(元)')
64
64
  # 2) 主币种 + 10
65
65
  NEW=$(node -e "console.log((${CUR} + 10).toFixed(2))")
66
66
  # 3) 主币种金额传回
@@ -90,7 +90,7 @@ siluzan-tso ad campaigns -a <mediaCustomerId> --start <…> --end <…> --json
90
90
  mkdir -p ./snap-scale && siluzan-tso google-analysis -a <mediaCustomerId> --sections campaigns --start <…> --end <…> --json-out ./snap-scale
91
91
  ```
92
92
 
93
- 确认 **`budget`/`budgetDisplay`**、**`targetCpa_BidingAmount`**(或组上 **`targetCpaAmount`**)与 **`statusV2`** 符合预期。
93
+ 确认 **`budget`**(元)、**`targetCpa_BidingAmount`**(或组上 **`targetCpaAmount`**)与 **`statusV2`** 符合预期。
94
94
 
95
95
  ---
96
96
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  **必读交叉引用**:
8
8
 
9
- - 金额与展示:`SKILL.md`「金额与货币单位硬约束」;命令层 `budget` / `maxCPCAmount` 等与 `*Display` 关系见 `references/google-ads.md`、`ad campaigns` / `ad groups` 的 `--json` 说明。
9
+ - 金额与展示:`SKILL.md`「金额与货币单位硬约束」;**`ad campaigns --json` `budget` 已为元**;组级等 `*Display` 与原始分字段关系见 `references/google-ads.md`、`ad groups` 的 `--json` 说明。
10
10
  - `--json` 与 Node 过滤:`references/tips.md`
11
11
  - 写命令语法:`references/google-ads.md`(系列编辑、广告组编辑、启停等)
12
12
  - 账户/维度分析、时间窗:`references/account-analytics.md`
@@ -27,7 +27,7 @@
27
27
  | 检查意图 | 可关注的 JSON 字段(键名以实际 stdout 为准) |
28
28
  |----------|---------------------------------------------|
29
29
  | 周期内费用 | 系列/组/创意列表中的 **`spend`**;报表类子命令中带费用的指标字段 |
30
- | 日预算与费用比 | **`ad campaigns --json`**:`**budget**` / **`budgetDisplay`** 与 **`spend`**;比较时须**统一货币与单位**(见 `SKILL.md` 金额硬规范) |
30
+ | 日预算与费用比 | **`ad campaigns --json`**:`**budget`**(CLI 已为元)与 **`spend`**;比较时须**统一货币与单位**(见 `SKILL.md` 金额硬规范) |
31
31
  | 转化次数 | **`conversions`**(或报表里等价字段名) |
32
32
  | 实际 CPA | 宿主侧 **`spend / conversions`**(`conversions` 为 0 时不做 CPA 判断) |
33
33
  | 目标 CPA(系列 / 组) | 系列列表 JSON 中的 **`targetCpa_BidingAmount`** 等;组列表 **`ad groups --json`** 中的 **`targetCpaAmount`**(写入口径见 `google-ads.md`「广告组编辑」) |
@@ -50,7 +50,7 @@ siluzan-tso ad campaigns -a <mediaCustomerId> --start <YYYY-MM-DD> --end <YYYY-M
50
50
  ```
51
51
 
52
52
  3. 对 `items[]` 中每条系列(`id` 为系列 ID):
53
- - 读取 **`spend`**(或文档约定的费用字段)与 **`budgetDisplay`** / **`budget`**(二选一与 `spend` **统一口径**,见 `SKILL.md` 硬规范)。
53
+ - 读取 **`spend`**(或文档约定的费用字段)与 **`budget`**(`ad campaigns` JSON 下**已为元**,与 `spend` 同量级口径,见 `SKILL.md` 硬规范)。
54
54
  - 计算阈值:`threshold = budget_in_same_unit * (coefficient / 100)`,系数 `coefficient` 建议 **110–120**(配置项)。
55
55
  - **IF** `spend >= threshold` **且**宿主策略允许对该系列熔断(如排除白名单),则进入写操作。
56
56
 
@@ -125,7 +125,7 @@ siluzan-tso ad adgroup-edit -a <mediaCustomerId> --id <adGroupId> --max-cpc <主
125
125
 
126
126
  见 **`references/google-ads.md`**「广告组编辑」。
127
127
 
128
- 写前**必须**先 **`ad groups --json` / `ad campaigns --json`** 取当前值,**读取 `*Display` 字段**(主币种金额,如 `targetCpaAmountDisplay`、`maxCPCAmountDisplay`、`budgetDisplay`),在宿主内按主币种算新值(如下调 12%:`newDisplay = round((oldDisplay * 0.88) * 100) / 100`),再以主币种金额作为 `--target-cpa` / `--max-cpc` / `--budget` 传回。
128
+ 写前**必须**先 **`ad groups --json` / `ad campaigns --json`** 取当前值,**读取主币种金额**:组侧优先 `*Display`(如 `targetCpaAmountDisplay`、`maxCPCAmountDisplay`);系列列表侧 **`ad campaigns` 的 `budget` 已为元**(与写参 `--budget` 一致),在宿主内按主币种算新值(如下调 12%:`newDisplay = round((oldDisplay * 0.88) * 100) / 100`),再以主币种金额作为 `--target-cpa` / `--max-cpc` / `--budget` 传回。
129
129
  **严禁** 把 `targetCpaAmount` / `maxCPCAmount` / `budget` 这些「分」字段直接当作主币种金额传给 CLI——会被再 ×100 一次,金额放大 100 倍。
130
130
 
131
131
  ### 写后复核
@@ -40,8 +40,11 @@ siluzan-tso init -d /path/to-your/skills # 写入自定义目录
40
40
 
41
41
  `siluzan-tso` 与 `siluzan-cso` **共用同一份凭据**,存储在 `~/.siluzan/config.json`,配置一次两个 CLI 均可使用。
42
42
 
43
+ > **登录方式优先级**
44
+ > 1. **首选**:**手机号 + 短信验证码**两段式(`send-login-code` → `login --phone --code`)——无 TTY 不卡死、不依赖浏览器里复制 API Key,**对话式 AI / OpenClaw / CI 日志旁路**均适用。
43
45
 
44
- ### 通过手机号 + 验证码登录(对话式 AI 推荐)
46
+
47
+ ### 通过手机号 + 验证码登录(**首选**;对话式 AI / 无 TTY 与各 Agent 环境)
45
48
 
46
49
  **两段式调用**,专为 AI Agent 设计——任何一步都不会进入交互等待,绝不会卡住 stdout。
47
50
  拆分后单一职责:第 1 步只发码;第 2 步只用 code 换 API Key。这样 Agent 不会因为"看到 stdout 卡住就重试"而触发短信轰炸。
@@ -51,13 +54,12 @@ siluzan-tso init -d /path/to-your/skills # 写入自定义目录
51
54
  | 1 | `siluzan-tso send-login-code --phone <手机号>` | 仅向手机发送 6 位验证码 |
52
55
  | 2 | `siluzan-tso login --phone <手机号> --code <验证码>` | 用 code 完成登录并自动签发 API Key 写入 `~/.siluzan/config.json` |
53
56
 
54
- ## 其他方式登录
57
+ ## 其它登录方式(TTY 交互 / 已有 API Key / JWT)
58
+
55
59
  ```bash
56
- siluzan-tso login # 交互式登录,按提示创建 API Key 后粘贴
60
+ siluzan-tso login # 交互式登录(需 TTY),按提示创建 API Key 后粘贴
57
61
  siluzan-tso login --api-key <YOUR_API_KEY> # 直接设置 API Key(跳过交互)
58
- siluzan-tso send-login-code --phone 138xxxx # 两段式登录第 1 步:发送短信验证码
59
- siluzan-tso login --phone 138xxxx --code 123456 # 两段式登录第 2 步:用验证码完成登录
60
- siluzan-tso config set --api-key <Key> # 或通过 config set 直接写入
62
+ siluzan-tso config set --api-key <Key> # config 直接写入
61
63
  siluzan-tso config set --token <Token> # 备用:设置 JWT Token
62
64
  ```
63
65
 
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "id": "budget-display-not-raw-micros",
3
- "description": "系列预算展示须用 budgetDisplay,勿把分单位 budget 当主币种金额",
3
+ "description": "系列日预算须读 ad campaigns JSON 的 budget(CLI 已统一为元)",
4
4
  "turns": [
5
5
  "用 siluzan-tso 拉 Google 账户 {{EVAL_ACCOUNT_READ}} 在 2026-04-01~2026-04-15 的 ad campaigns --json,告诉我 Search - Brand 这条系列的「给用户看的日预算」应该是多少、依据哪个字段。"
6
6
  ],
7
- "judgeExpectation": "路径:基于 ad campaigns --json 汇报日预算时,应使用 budgetDisplay(及 budgetUnit/display 标识),不得将原始 budget 分单位数值直接当作「美元/人民币」展示给用户。\n输出:若引用 JSON,应体现正确金额尺度;不要求数值与真实一致。",
7
+ "judgeExpectation": "路径:`ad campaigns --json` **`budget` 已由 CLI 换算为主币种「元」**(与 `--budget` 写入口径一致),汇报用户可见日预算应直接取该字段;禁止将旧版网关「分」整数再当元展示,也禁止按微元 ÷1e6。\n输出:若引用 JSON,应体现正确金额尺度;不要求数值与真实一致。",
8
8
  "skillMapping": "SKILL.md「金额与货币单位硬约束」",
9
9
  "commandMustInclude": [
10
10
  [
@@ -4,7 +4,7 @@
4
4
  "turns": [
5
5
  "我在给 Google 账户 {{EVAL_ACCOUNT_WRITE}} 写 Cron 宿主编排「单日预算熔断」:当日累计费用相对**日预算**达到 **115%** 就自动暂停对应 Campaign(系数做成可配置,默认区间按文档建议写进方案)。统计日 **2026-04-24**(起止都是这一天)。**请不要在本轮执行任何 siluzan-tso 命令**;只输出一份可落地的**文字方案**:读哪些接口/JSON 字段、阈值公式、命中后的写命令、熔断后人工恢复步骤。可把示例命令写在方案里作说明。"
6
6
  ],
7
- "judgeExpectation": "评分对象:仅评 agent 最终回复里的**文字方案**(设计步骤、数据字段、判断条件、拟用写命令、复核与通知),**不**要求本轮对话中真的执行 CLI、也**不**依据命令 trace 给分。\n方案应可交给 Cron/OpenClaw 等宿主落地;示例命令可作为方案的一部分出现。\n方案中应写明:用 `siluzan-tso ad campaigns -a <id> --start/--end`(当日起止一致)加 `--json` 读取 **`spend`** 与 **`budgetDisplay`/`budget`**,并与 `SKILL.md` 金额硬规范**统一口径**后再比。\n方案中应写明:阈值 = 日预算(同单位)×(系数/100),系数建议 **110–120**(抗 API 延迟);**IF** spend ≥ 阈值则拟执行 **`ad campaign-status … --status Paused`**。\n方案中应写明:恢复须**人工**检查并调高预算后,再用 **`campaign-status … Enabled`** 或网页启用;不应写成无人值守自动恢复。",
7
+ "judgeExpectation": "评分对象:仅评 agent 最终回复里的**文字方案**(设计步骤、数据字段、判断条件、拟用写命令、复核与通知),**不**要求本轮对话中真的执行 CLI、也**不**依据命令 trace 给分。\n方案应可交给 Cron/OpenClaw 等宿主落地;示例命令可作为方案的一部分出现。\n方案中应写明:用 `siluzan-tso ad campaigns -a <id> --start/--end`(当日起止一致)加 `--json` 读取 **`spend`** 与 **`budget`**(日预算,**CLI 已统一为元**),并与 `SKILL.md` 金额硬规范**统一口径**后再比。\n方案中应写明:阈值 = 日预算(同单位)×(系数/100),系数建议 **110–120**(抗 API 延迟);**IF** spend ≥ 阈值则拟执行 **`ad campaign-status … --status Paused`**。\n方案中应写明:恢复须**人工**检查并调高预算后,再用 **`campaign-status … Enabled`** 或网页启用;不应写成无人值守自动恢复。",
8
8
  "skillMapping": "references/hosted-automation-self-control.md · 场景 1",
9
9
  "judgeReferencePaths": [
10
10
  "references/hosted-automation-self-control.md",
@@ -4,9 +4,7 @@
4
4
  "campaigns": [
5
5
  {
6
6
  "name": "Search - Brand",
7
- "budget": 500000,
8
- "budgetDisplay": 50.0,
9
- "budgetUnit": "display",
7
+ "budget": 50.0,
10
8
  "currencyCode": "USD"
11
9
  }
12
10
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.18-beta.4",
3
+ "version": "1.1.18-beta.6",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",