siluzan-tso-cli 1.1.14-beta.7 → 1.1.14-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.14-beta.7),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.14-beta.9),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -6936,6 +6936,12 @@ function toDisplayMoney(raw) {
6936
6936
  if (!Number.isFinite(n)) return null;
6937
6937
  return n / 100;
6938
6938
  }
6939
+ function toCentAmount(displayValue) {
6940
+ if (!Number.isFinite(displayValue)) {
6941
+ throw new Error(`toCentAmount: \u8F93\u5165\u5FC5\u987B\u662F\u6709\u9650\u6570\u5B57\uFF0C\u6536\u5230 ${displayValue}`);
6942
+ }
6943
+ return Math.round(displayValue * 100);
6944
+ }
6939
6945
  function adgroupListUrl(googleApiUrl, account, startDate, endDate) {
6940
6946
  const params = new URLSearchParams();
6941
6947
  params.set("startDate", toGoogleDate(startDate, -30));
@@ -7405,7 +7411,8 @@ async function runAdGroupCreate(opts) {
7405
7411
  campaignId: opts.campaignId,
7406
7412
  campaignName: opts.campaignName,
7407
7413
  manualCpc_EnhancedCpcEnabled: false,
7408
- maxCPCAmount: opts.maxCpc,
7414
+ // 用户传主币种金额,统一 toCentAmount 转「分」(与前端、adgroup-edit 一致)
7415
+ maxCPCAmount: toCentAmount(opts.maxCpc),
7409
7416
  name: opts.name,
7410
7417
  rotationMode: 1,
7411
7418
  statusV2: opts.status ?? "ENABLED",
@@ -8140,8 +8147,8 @@ async function runAdGroupEdit(opts) {
8140
8147
  }
8141
8148
  const body = { ...adgroup };
8142
8149
  if (hasName) body["name"] = opts.name.trim();
8143
- if (hasMax) body["maxCPCAmount"] = Math.round(opts.maxCPCAmount);
8144
- if (hasTcpa) body["targetCpaAmount"] = Math.round(opts.targetCpaAmount);
8150
+ if (hasMax) body["maxCPCAmount"] = toCentAmount(opts.maxCPCAmount);
8151
+ if (hasTcpa) body["targetCpaAmount"] = toCentAmount(opts.targetCpaAmount);
8145
8152
  const putUrl = `${googleApiUrl}/adgroupnmanagement/adgroup/${opts.account}/${opts.id}`;
8146
8153
  try {
8147
8154
  await apiFetch2(putUrl, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
@@ -8342,11 +8349,12 @@ async function runAdCampaignEdit(opts) {
8342
8349
  }
8343
8350
  const body = { ...campaign };
8344
8351
  if (opts.name !== void 0) body["name"] = opts.name;
8345
- if (opts.budget !== void 0) body["budget"] = opts.budget;
8352
+ if (opts.budget !== void 0) body["budget"] = toCentAmount(opts.budget);
8346
8353
  if (opts.biddingStrategy !== void 0) body["biddingStrategyTypeV2"] = opts.biddingStrategy;
8347
8354
  if (opts.targetSpendBidCeiling !== void 0)
8348
- body["targetSpend_BidCeilingAmount"] = opts.targetSpendBidCeiling;
8349
- if (opts.targetCpa !== void 0) body["targetCpa_BidingAmount"] = opts.targetCpa;
8355
+ body["targetSpend_BidCeilingAmount"] = toCentAmount(opts.targetSpendBidCeiling);
8356
+ if (opts.targetCpa !== void 0)
8357
+ body["targetCpa_BidingAmount"] = toCentAmount(opts.targetCpa);
8350
8358
  if (opts.targetSearchNetwork !== void 0)
8351
8359
  body["targetSearchNetwork"] = opts.targetSearchNetwork;
8352
8360
  if (opts.targetContentNetwork !== void 0)
@@ -13077,8 +13085,8 @@ adCmd.command("campaign-delete").description("\u5220\u9664\u5E7F\u544A\u7CFB\u52
13077
13085
  });
13078
13086
  adCmd.command("adgroup-create").description("\u5728\u5E7F\u544A\u7CFB\u5217\u4E0B\u521B\u5EFA\u641C\u7D22\u5E7F\u544A\u7EC4").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--campaign-id <id>", "\u6240\u5C5E\u5E7F\u544A\u7CFB\u5217 ID").requiredOption("--campaign-name <name>", "\u6240\u5C5E\u5E7F\u544A\u7CFB\u5217\u540D\u79F0").requiredOption("--name <name>", "\u5E7F\u544A\u7EC4\u540D\u79F0").requiredOption(
13079
13087
  "--max-cpc <amount>",
13080
- "\u6700\u9AD8\u5355\u6B21\u70B9\u51FB\u8D39\u7528\uFF08\u6700\u5C0F\u8D27\u5E01\u5355\u4F4D\uFF0C\u5982 100000 = 1 USD\uFF09",
13081
- parseInt
13088
+ "\u6700\u9AD8\u5355\u6B21\u70B9\u51FB\u8D39\u7528\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982 1.5 \u8868\u793A \xA51.50\uFF1BCLI \u5185\u90E8 \xD7100 \u5199\u5165\u300C\u5206\u300D\uFF09",
13089
+ parseFloat
13082
13090
  ).option("--status <status>", "\u72B6\u6001\uFF1AENABLED | PAUSED\uFF08\u9ED8\u8BA4 ENABLED\uFF09", "ENABLED").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
13083
13091
  async (opts) => {
13084
13092
  await runAdGroupCreate({
@@ -13388,7 +13396,19 @@ adCmd.command("campaign-create").description(
13388
13396
  });
13389
13397
  }
13390
13398
  );
13391
- adCmd.command("campaign-edit").description("\u7F16\u8F91\u5E7F\u544A\u7CFB\u5217\uFF08\u540D\u79F0/\u9884\u7B97/\u51FA\u4EF7\u7B56\u7565/\u6295\u653E\u7F51\u7EDC\uFF0C\u81F3\u5C11\u4F20\u4E00\u4E2A\u4FEE\u6539\u9879\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").option("--name <name>", "\u65B0\u540D\u79F0").option("--budget <amount>", "\u65B0\u9884\u7B97\uFF08\u6700\u5C0F\u8D27\u5E01\u5355\u4F4D\uFF0C\u5982 100000 = 1 USD\uFF09", parseInt).option("--bidding <strategy>", "\u51FA\u4EF7\u7B56\u7565\uFF1ATARGET_SPEND | TARGET_CPA | TARGET_ROAS | MANUAL_CPC").option("--bid-ceiling <amount>", "TARGET_SPEND \u51FA\u4EF7\u4E0A\u9650\uFF08\u6700\u5C0F\u8D27\u5E01\u5355\u4F4D\uFF0C0=\u4E0D\u9650\uFF09", parseInt).option("--target-cpa <amount>", "TARGET_CPA \u76EE\u6807 CPA\uFF08\u6700\u5C0F\u8D27\u5E01\u5355\u4F4D\uFF09", parseInt).option(
13399
+ adCmd.command("campaign-edit").description("\u7F16\u8F91\u5E7F\u544A\u7CFB\u5217\uFF08\u540D\u79F0/\u9884\u7B97/\u51FA\u4EF7\u7B56\u7565/\u6295\u653E\u7F51\u7EDC\uFF0C\u81F3\u5C11\u4F20\u4E00\u4E2A\u4FEE\u6539\u9879\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").option("--name <name>", "\u65B0\u540D\u79F0").option(
13400
+ "--budget <amount>",
13401
+ "\u65B0\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982 100 \u8868\u793A \xA5100\uFF1B\u652F\u6301\u5C0F\u6570\uFF1BCLI \u5185\u90E8\u81EA\u52A8 \xD7100 \u5199\u5165\u300C\u5206\u300D\u5B57\u6BB5\uFF09",
13402
+ parseFloat
13403
+ ).option("--bidding <strategy>", "\u51FA\u4EF7\u7B56\u7565\uFF1ATARGET_SPEND | TARGET_CPA | TARGET_ROAS | MANUAL_CPC").option(
13404
+ "--bid-ceiling <amount>",
13405
+ "TARGET_SPEND \u51FA\u4EF7\u4E0A\u9650\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF080 = \u4E0D\u9650\uFF1BCLI \u5185\u90E8 \xD7100 \u5199\u5165\uFF09",
13406
+ parseFloat
13407
+ ).option(
13408
+ "--target-cpa <amount>",
13409
+ "TARGET_CPA \u76EE\u6807 CPA\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08CLI \u5185\u90E8 \xD7100 \u5199\u5165\uFF09",
13410
+ parseFloat
13411
+ ).option(
13392
13412
  "--search-network <bool>",
13393
13413
  "\u6295\u653E\u5230 Google \u641C\u7D22\uFF1Atrue | false",
13394
13414
  (v) => v === "true"
@@ -13424,11 +13444,11 @@ adCmd.command("adgroup-rename").description("\u4FEE\u6539\u5E7F\u544A\u7EC4\u540
13424
13444
  );
13425
13445
  adCmd.command("adgroup-edit").description("\u7F16\u8F91\u5E7F\u544A\u7EC4\uFF08\u540D\u79F0\u3001\u6700\u9AD8 CPC\u3001\u7EC4\u7EA7\u76EE\u6807 CPA\uFF1B\u81F3\u5C11\u4F20\u4E00\u9879\uFF1B\u4E0E\u7F51\u5173 PUT \u4E00\u81F4\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <adgroupId>", "\u5E7F\u544A\u7EC4 ID").option("--name <name>", "\u65B0\u540D\u79F0").option(
13426
13446
  "--max-cpc <n>",
13427
- "\u6700\u9AD8 CPC\uFF0C\u4E0E ad groups --json \u7684 maxCPCAmount \u540C\u53E3\u5F84\uFF08\u4E3B\u5E01\u79CD\u91D1\u989D\xD7100\uFF0C\u89C1 references/google-ads.md\uFF09",
13447
+ "\u6700\u9AD8 CPC\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982 1.5 \u8868\u793A \xA51.50\uFF1BCLI \u5185\u90E8 \xD7100 \u5199\u5165\u300C\u5206\u300D\u5B57\u6BB5\uFF09",
13428
13448
  parseFloat
13429
13449
  ).option(
13430
13450
  "--target-cpa <n>",
13431
- "\u7EC4\u7EA7\u76EE\u6807 CPA\uFF0C\u4E0E ad groups --json \u7684 targetCpaAmount \u540C\u53E3\u5F84\uFF08\xD7100 \u5206\u5355\u4F4D\uFF09",
13451
+ "\u7EC4\u7EA7\u76EE\u6807 CPA\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08CLI \u5185\u90E8 \xD7100 \u5199\u5165\u300C\u5206\u300D\u5B57\u6BB5\uFF09",
13432
13452
  parseFloat
13433
13453
  ).option("--start <date>", "\u5217\u8868\u67E5\u8BE2\u8D77\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u5217\u8868\u67E5\u8BE2\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
13434
13454
  async (opts) => {
@@ -13488,7 +13508,10 @@ adCmd.command("keyword-delete").description("\u5220\u9664\u641C\u7D22\u5173\u952
13488
13508
  });
13489
13509
  }
13490
13510
  );
13491
- adCmd.command("keyword-edit").description("\u7F16\u8F91\u641C\u7D22\u5173\u952E\u8BCD\uFF08\u6570\u7EC4 body\uFF0C\u5148 list \u518D\u5408\u5E76\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <keywordId>", "\u5173\u952E\u8BCD ID\uFF08\u6765\u81EA ad keywords --json \u2192 id\uFF09").option("--text <text>", "\u65B0\u5173\u952E\u8BCD\u6587\u672C\uFF08\u5199\u5165 keywordText \u6570\u7EC4\uFF09").option("--match-type <type>", "\u65B0\u5339\u914D\u7C7B\u578B\uFF1ABroad | Phrase | Exact\uFF08\u5199\u5165 matchTypeV2\uFF09").option("--max-cpc <n>", "\u6700\u9AD8\u6BCF\u6B21\u70B9\u51FB\u8D39\u7528 maxCPC\uFF08\u6570\u5B57\uFF0C0 \u8868\u793A\u6309\u5E73\u53F0/\u8BA1\u5212\u9ED8\u8BA4\uFF09").option("--final-url <url>", "\u5173\u952E\u8BCD\u7EA7\u6700\u7EC8\u5230\u8FBE\u7F51\u5740 finalURL").option("--start <date>", "\u5217\u8868\u67E5\u8BE2\u8D77\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u5217\u8868\u67E5\u8BE2\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
13511
+ adCmd.command("keyword-edit").description("\u7F16\u8F91\u641C\u7D22\u5173\u952E\u8BCD\uFF08\u6570\u7EC4 body\uFF0C\u5148 list \u518D\u5408\u5E76\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <keywordId>", "\u5173\u952E\u8BCD ID\uFF08\u6765\u81EA ad keywords --json \u2192 id\uFF09").option("--text <text>", "\u65B0\u5173\u952E\u8BCD\u6587\u672C\uFF08\u5199\u5165 keywordText \u6570\u7EC4\uFF09").option("--match-type <type>", "\u65B0\u5339\u914D\u7C7B\u578B\uFF1ABroad | Phrase | Exact\uFF08\u5199\u5165 matchTypeV2\uFF09").option(
13512
+ "--max-cpc <n>",
13513
+ "\u6700\u9AD8\u6BCF\u6B21\u70B9\u51FB\u8D39\u7528 maxCPC\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982 5 \u8868\u793A \xA55\uFF1B\u26A0\uFE0F \u8FD9\u4E2A\u5B57\u6BB5\u540E\u7AEF\u5355\u4F4D\u5C31\u662F\u300C\u4E3B\u5E01\u79CD\u5143\u300D\uFF0CCLI \u76F4\u63A5\u900F\u4F20\u4E0D\u505A \xD7100\uFF0C\u4E0E budget / \u7EC4 maxCPCAmount \u4E0D\u540C\uFF1B0 \u8868\u793A\u6309\u5E73\u53F0/\u8BA1\u5212\u9ED8\u8BA4\uFF09"
13514
+ ).option("--final-url <url>", "\u5173\u952E\u8BCD\u7EA7\u6700\u7EC8\u5230\u8FBE\u7F51\u5740 finalURL").option("--start <date>", "\u5217\u8868\u67E5\u8BE2\u8D77\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u5217\u8868\u67E5\u8BE2\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
13492
13515
  async (opts) => {
13493
13516
  if (opts.matchType && !["Broad", "Phrase", "Exact"].includes(opts.matchType)) {
13494
13517
  console.error("\n\u274C --match-type \u53EA\u63A5\u53D7 Broad | Phrase | Exact\n");
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.14-beta.7",
4
- "publishedAt": 1777341955694
3
+ "version": "1.1.14-beta.9",
4
+ "publishedAt": 1777368873875
5
5
  }
@@ -511,10 +511,11 @@ siluzan-tso ad batch get --id <taskId>
511
511
  siluzan-tso ad campaigns -a <CID> --json
512
512
 
513
513
  # 切换到 Target CPA(数据充足后)
514
+ # --target-cpa 是「主币种金额」,CLI 内部自动 ×100 写入;50 = 50 USD(不是微单位 5000)
514
515
  siluzan-tso ad campaign-edit -a <CID> --id <系列ID> \
515
- --bidding TARGET_CPA --target-cpa 5000 # 50 USD (微单位)
516
+ --bidding TARGET_CPA --target-cpa 50
516
517
 
517
- # 切换到 Target ROAS
518
+ # 切换到 Target ROAS(ROAS 是比例,不是金额,无需 ×100)
518
519
  siluzan-tso ad campaign-edit -a <CID> --id <系列ID> \
519
520
  --bidding TARGET_ROAS --target-roas 3.0
520
521
  ```
@@ -200,36 +200,38 @@ siluzan-tso ad adgroup-create \
200
200
  --campaign-id <campaignId> \
201
201
  --campaign-name <campaignName> \
202
202
  --name <adGroupName> \
203
- --max-cpc <金额(微单位)>
203
+ --max-cpc <主币种金额>
204
204
  ```
205
205
 
206
- | 选项 | 说明 | 必填 |
207
- | ------------------------ | -------------------------------------------------- | ---- |
208
- | `-a, --account <id>` | Google mediaCustomerId | ✅ |
209
- | `--campaign-id <id>` | 所属广告系列 ID(来自 `campaigns --json`) | ✅ |
210
- | `--campaign-name <name>` | 所属广告系列名称 | ✅ |
211
- | `--name <name>` | 广告组名称 | ✅ |
212
- | `--max-cpc <amount>` | 最高 CPC 出价(最小货币单位,如 `100000` = 1 USD) | ✅ |
213
- | `--status <status>` | `ENABLED \| PAUSED`(默认 `ENABLED`) | |
206
+ > **金额单位**:`--max-cpc` **主币种金额**(如 `1.5` 表示 ¥1.50),CLI 内部 `Math.round(value × 100)` 写入「分」字段,与前端表单 `arithmetic.times(maxCPCAmount, 100)`、`ad adgroup-edit` 完全一致。**禁止** 按 Google micros(×1,000,000)填写。
207
+
208
+ | 选项 | 说明 | 必填 |
209
+ | ------------------------ | ---------------------------------------------------------- | ---- |
210
+ | `-a, --account <id>` | Google mediaCustomerId | ✅ |
211
+ | `--campaign-id <id>` | 所属广告系列 ID(来自 `campaigns --json`) | ✅ |
212
+ | `--campaign-name <name>` | 所属广告系列名称 | ✅ |
213
+ | `--name <name>` | 广告组名称 | |
214
+ | `--max-cpc <amount>` | 最高 CPC 出价,主币种金额(如 `1.5` = ¥1.50;CLI 内部 ×100)| ✅ |
215
+ | `--status <status>` | `ENABLED \| PAUSED`(默认 `ENABLED`) | |
214
216
 
215
217
  **示例:**
216
218
 
217
219
  ```bash
218
- # 在广告系列下创建广告组,最高 CPC 1 USD
220
+ # 在广告系列下创建广告组,最高 CPC ¥1
219
221
  siluzan-tso ad adgroup-create \
220
222
  -a 6326027735 \
221
223
  --campaign-id campaign_001 \
222
224
  --campaign-name "品牌推广_春季" \
223
225
  --name "核心词_跑步鞋" \
224
- --max-cpc 100000
226
+ --max-cpc 1
225
227
 
226
- # 创建时设为暂停状态
228
+ # 创建时设为暂停状态,最高 CPC ¥1.5
227
229
  siluzan-tso ad adgroup-create \
228
230
  -a 6326027735 \
229
231
  --campaign-id campaign_001 \
230
232
  --campaign-name "品牌推广_春季" \
231
233
  --name "竞品词" \
232
- --max-cpc 150000 \
234
+ --max-cpc 1.5 \
233
235
  --status PAUSED
234
236
  ```
235
237
 
@@ -258,32 +260,37 @@ siluzan-tso ad adgroup-status -a 6326027735 --id adgroup_001 --status Enabled
258
260
 
259
261
  ### 广告组编辑(名称 / 最高 CPC / 组级目标 CPA)
260
262
 
261
- `adgroup-status`、`adgroup-rename` 相同:先用 **`ad groups … --json`** 查看当前 **`maxCPCAmount`**、**`targetCpaAmount`** 等,再用本命令只改传入的字段(由 CLI 与 `siluzan-tso` 合并提交)。
263
+ 先用 **`ad groups … --json`** 查看当前的 **`maxCPCAmountDisplay`**、**`targetCpaAmountDisplay`**(主币种金额)等,再用本命令只改传入的字段。
262
264
 
263
265
  ```bash
264
266
  siluzan-tso ad adgroup-edit \
265
267
  -a <accountId> \
266
268
  --id <adGroupId> \
267
269
  [--name <新名称>] \
268
- [--max-cpc <n>] \
269
- [--target-cpa <n>] \
270
+ [--max-cpc <主币种金额>] \
271
+ [--target-cpa <主币种金额>] \
270
272
  [--start <YYYY-MM-DD>] [--end <YYYY-MM-DD>]
271
273
  ```
272
274
 
275
+ > **金额单位(必读)**:所有金额参数均按 **主币种金额** 传入(如 `1.5` 表示 ¥1.50);CLI 内部 `Math.round(value × 100)` 写入后端「分」字段,与 `ad campaign-edit`、前端表单完全一致。**禁止** 按 Google micros(×1,000,000)填写。
276
+
273
277
  | 选项 | 说明 |
274
278
  |------|------|
275
- | `--max-cpc` | **`ad groups --json`** 中 **`maxCPCAmount`** 同一整数口径;与主币种展示关系见 **`maxCPCAmountDisplay`**(`SKILL.md` 金额硬规范)。 |
276
- | `--target-cpa` | **`ad groups --json`** 中 **`targetCpaAmount`** 同一整数口径。写入 **> 0** 的值才会更新组级目标 CPA;为 `0` 或不传则通常不改变该项。 |
279
+ | `--max-cpc` | 最高 CPC,主币种金额(与 `ad groups --json` 中 **`maxCPCAmountDisplay`** 对齐)。 |
280
+ | `--target-cpa` | 组级目标 CPA,主币种金额(与 `ad groups --json` 中 **`targetCpaAmountDisplay`** 对齐)。写入 **> 0** 才会更新。 |
277
281
  | `--name` | 新名称(可选)。 |
278
282
 
279
283
  **示例:**
280
284
 
281
285
  ```bash
282
- # 先取 JSON,用 maxCPCAmount / targetCpaAmount 算好新值后再写回
286
+ # 先取 JSON,读取 maxCPCAmountDisplay / targetCpaAmountDisplay(主币种金额)算新值后再写回
283
287
  siluzan-tso ad groups -a 6326027735 --json
284
288
 
285
- siluzan-tso ad adgroup-edit -a 6326027735 --id 195548094874 --max-cpc 200
286
- siluzan-tso ad adgroup-edit -a 6326027735 --id 195548094874 --target-cpa 5000 --name "核心词_降价后"
289
+ # 把最高 CPC 调整到 ¥2
290
+ siluzan-tso ad adgroup-edit -a 6326027735 --id 195548094874 --max-cpc 2
291
+
292
+ # 设置目标 CPA = ¥50,并改名
293
+ siluzan-tso ad adgroup-edit -a 6326027735 --id 195548094874 --target-cpa 50 --name "核心词_降价后"
287
294
  ```
288
295
 
289
296
  ---
@@ -618,11 +625,11 @@ siluzan-tso ad batch get --id <recordId>
618
625
  siluzan-tso ad batch update --id <recordId> [选项]
619
626
  ```
620
627
 
621
- | 选项 | 说明 |
622
- | ------------------------ | --------------------------------------------- |
623
- | `--budget <amount>` | 新预算(最小货币单位,如 `8500` = 85 CNY) |
624
- | `--url <url>` | 新推广链接 |
625
- | `--campaign-name <name>` | 新广告系列名称 |
628
+ | 选项 | 说明 |
629
+ | ------------------------ | ------------------------------------------------------------------- |
630
+ | `--budget <amount>` | 新日预算,主币种金额(如 `85` 表示 ¥85;CLI 内部自动 ×100 写入「分」) |
631
+ | `--url <url>` | 新推广链接 |
632
+ | `--campaign-name <name>` | 新广告系列名称 |
626
633
 
627
634
  ### publish — 发布草稿
628
635
 
@@ -814,33 +821,42 @@ siluzan-tso ad campaign-edit \
814
821
  -a <accountId> \
815
822
  --id <campaignId> \
816
823
  [--name <新名称>] \
817
- [--budget <预算(最小货币单位)>] \
824
+ [--budget <主币种金额>] \
818
825
  [--bidding <出价策略>] \
819
- [--bid-ceiling <出价上限>] \
826
+ [--bid-ceiling <主币种金额>] \
827
+ [--target-cpa <主币种金额>] \
820
828
  [--search-network true|false] \
821
829
  [--content-network true|false]
822
830
  ```
823
831
 
832
+ > **金额单位(必读)**:所有金额参数均按 **主币种金额**(如 `100` 表示 ¥100,支持小数 `10.5`)传入;CLI 内部 `Math.round(value × 100)` 写入后端「分」字段,与前端表单、`ad campaign-create`、`ad batch update` 完全一致。**禁止** 按 Google micros(×1,000,000)口径填写,会被后端按「分」解读,导致金额放大 10000 倍。
833
+
824
834
  | 选项 | 说明 |
825
835
  | ------------------- | ------------------------------------------------------------------------- |
826
836
  | `--name` | 新广告系列名称 |
827
- | `--budget` | 新预算,最小货币单位(如 100000 = 1 USD |
837
+ | `--budget` | 新日预算,主币种金额(如 `100` = ¥100;`10.5` = ¥10.50 |
828
838
  | `--bidding` | 出价策略:`TARGET_SPEND` \| `TARGET_CPA` \| `TARGET_ROAS` \| `MANUAL_CPC` |
829
- | `--bid-ceiling` | `TARGET_SPEND` 出价上限(最小货币单位,0 = 不限) |
830
- | `--target-cpa` | `TARGET_CPA` 目标 CPA |
839
+ | `--bid-ceiling` | `TARGET_SPEND` 出价上限,主币种金额(`0` = 不限) |
840
+ | `--target-cpa` | `TARGET_CPA` 目标 CPA,主币种金额 |
831
841
  | `--search-network` | 投放 Google 搜索:`true` \| `false` |
832
842
  | `--content-network` | 投放展示网络:`true` \| `false` |
833
843
 
834
844
  **示例:**
835
845
 
836
846
  ```bash
837
- # 修改名称和预算
847
+ # 修改名称和预算(日预算 ¥5000)
838
848
  siluzan-tso ad campaign-edit -a 6326027735 --id 23509626948 \
839
- --name "搜索-春季促销-2026" --budget 500000
849
+ --name "搜索-春季促销-2026" --budget 5000
850
+
851
+ # 在原预算基础上 +10 元(典型相对运算流程):
852
+ # 1) 先 GET 系列详情,读出 budgetDisplay(主币种)
853
+ # 2) 计算新值(主币种)= budgetDisplay + 10
854
+ # 3) 把新值(主币种)作为 --budget 传入;CLI 内部自动 ×100
855
+ siluzan-tso ad campaign-edit -a 6326027735 --id 23509626948 --budget 10.01
840
856
 
841
857
  # 切换出价策略为 TARGET_CPA,目标 CPA 2 USD
842
858
  siluzan-tso ad campaign-edit -a 6326027735 --id 23509626948 \
843
- --bidding TARGET_CPA --target-cpa 200000
859
+ --bidding TARGET_CPA --target-cpa 2
844
860
  ```
845
861
 
846
862
  ---
@@ -944,6 +960,10 @@ siluzan-tso ad keyword-delete -a 6326027735 --id 2464982882313 --adgroup-id 1955
944
960
 
945
961
  与网页一致:**先** **`siluzan-tso ad keywords … --json`** 取该关键词完整对象,**再**用本命令提交修改;未改字段随列表结果保留。
946
962
 
963
+ > **⚠️ 金额单位(与系列 / 组的金额字段不同!)**:关键词的 `maxCPC` 字段单位是 **「主币种元」**(直接是元,不是「分」),CLI 直接透传 **不做 ×100 转换**。这与 `ad campaign-edit --budget` / `ad adgroup-edit --max-cpc` 等"用户传元、CLI 内部 ×100"的命令**口径相反**。
964
+ >
965
+ > 验证依据:前端 `KeywordList.vue:266` 用 `filterSpend(maxCPC)` 直接展示原值,对应 `useFormatter.js:86` 的 `filterSpend` 内部不 ÷100(对比 `filterBudget` 显式 ÷100)。
966
+
947
967
  ### `ad keywords --json` 常见键名与 CLI 选项(常用)
948
968
 
949
969
  | 列表 JSON 键名 | 说明 | CLI |
@@ -952,7 +972,7 @@ siluzan-tso ad keyword-delete -a 6326027735 --id 2464982882313 --adgroup-id 1955
952
972
  | `keywordText` | 关键词文案(数组,一般一项) | `--text` → `["..."]` |
953
973
  | `matchTypeV2` | 匹配类型(网页用 Broad / Phrase / Exact) | `--match-type` |
954
974
  | `matchType` | 另一套枚举(如 EXACT),列表里可能仍存在 | 一般随 list 结果保留,勿手改 |
955
- | `maxCPC` | 最高每次点击费用 | `--max-cpc` |
975
+ | `maxCPC` | 最高每次点击费用,**主币种金额**(直接是元,不要 ×100) | `--max-cpc` |
956
976
  | `finalURL` | 关键词级最终到达网址 | `--final-url` |
957
977
  | `adGroupId` / `campaignId` 等 | 层级与统计字段 | 从列表保留 |
958
978
 
@@ -1181,13 +1201,13 @@ siluzan-tso list-accounts -m Google --json -k [mediaCustomerId]
1181
1201
  siluzan-tso ad campaigns -a 6326027735 --json
1182
1202
  # 假设 campaignId = campaign_001,campaignName = "品牌推广_春季"
1183
1203
 
1184
- # 第四步:创建广告组(最高 CPC 1 USD = 100000 微单位)
1204
+ # 第四步:创建广告组(最高 CPC ¥1,主币种金额;CLI 内部 ×100 写入「分」)
1185
1205
  siluzan-tso ad adgroup-create \
1186
1206
  -a 6326027735 \
1187
1207
  --campaign-id campaign_001 \
1188
1208
  --campaign-name "品牌推广_春季" \
1189
1209
  --name "核心词_跑步鞋" \
1190
- --max-cpc 100000
1210
+ --max-cpc 1
1191
1211
 
1192
1212
  # 第五步:查询新建广告组 id
1193
1213
  siluzan-tso ad groups -a 6326027735 --json
@@ -52,25 +52,34 @@ siluzan-tso google-analysis campaign-hour -a <mediaCustomerId> --start <YYYY-MM-
52
52
 
53
53
  ## 最终操作
54
54
 
55
- **提高系列日预算**(主币种 / 微单位以 `google-ads.md`「ad campaign-edit」为准):
55
+ > **金额单位(关键)**:`ad campaign-edit` / `ad adgroup-edit` 所有金额参数均为 **主币种金额**(如 `100` 表示 ¥100,支持小数 `10.5`);CLI 内部自动 `Math.round(value × 100)` 写入「分」字段。详见 `references/google-ads.md`「ad campaign-edit」金额单位说明。
56
+ >
57
+ > **必须先用 `*Display` 字段取主币种当前值,加减后再传回**;不要把 `budget` / `targetCpaAmount` / `maxCPCAmount` 这些"分"字段当作主币种传给 CLI。
58
+
59
+ **提高系列日预算**(在原值基础上 +10 元的写法):
56
60
 
57
61
  ```bash
58
- siluzan-tso ad campaign-edit -a <mediaCustomerId> --id <campaignId> --budget <新值>
62
+ # 1) GET 取主币种当前值
63
+ CUR=$(siluzan-tso ad campaigns -a <mediaCustomerId> --json | node -e '...筛选 budgetDisplay')
64
+ # 2) 主币种 + 10
65
+ NEW=$(node -e "console.log((${CUR} + 10).toFixed(2))")
66
+ # 3) 主币种金额传回
67
+ siluzan-tso ad campaign-edit -a <mediaCustomerId> --id <campaignId> --budget ${NEW}
59
68
  ```
60
69
 
61
70
  **上调系列目标 CPA**(若策略为 tCPA 等):
62
71
 
63
72
  ```bash
64
- siluzan-tso ad campaign-edit -a <mediaCustomerId> --id <campaignId> --target-cpa <新值>
73
+ siluzan-tso ad campaign-edit -a <mediaCustomerId> --id <campaignId> --target-cpa <主币种金额>
65
74
  ```
66
75
 
67
76
  若策略在**组级**调目标 CPA:
68
77
 
69
78
  ```bash
70
- siluzan-tso ad adgroup-edit -a <mediaCustomerId> --id <adGroupId> --target-cpa <新值>
79
+ siluzan-tso ad adgroup-edit -a <mediaCustomerId> --id <adGroupId> --target-cpa <主币种金额>
71
80
  ```
72
81
 
73
- 写前务必先 **`ad campaigns --json` / `ad groups --json`** 取当前值,在宿主内按配置比例计算再写入。
82
+ 写前务必先 **`ad campaigns --json` / `ad groups --json`** 取当前值,**读取 `*Display` 字段**(主币种)后按配置比例计算,再以主币种金额传回。
74
83
 
75
84
  ---
76
85
 
@@ -107,28 +107,32 @@ siluzan-tso ad campaign-status -a <mediaCustomerId> --id <campaignId> --status E
107
107
  **系列(tCPA / 出价上限等)**:
108
108
 
109
109
  ```bash
110
- siluzan-tso ad campaign-edit -a <mediaCustomerId> --id <campaignId> --target-cpa <新值最小货币单位整数>
110
+ # 所有金额参数均为「主币种金额」(如 50 表示 ¥50);CLI 内部自动 ×100 写入「分」字段
111
+ siluzan-tso ad campaign-edit -a <mediaCustomerId> --id <campaignId> --target-cpa <新值(主币种)>
111
112
  # 或 eCPC / 最大化点击上限等:
112
- # siluzan-tso ad campaign-edit ... --bid-ceiling <整数> --bidding TARGET_SPEND
113
+ # siluzan-tso ad campaign-edit ... --bid-ceiling <主币种金额> --bidding TARGET_SPEND
113
114
  ```
114
115
 
115
- 参数与单位见 **`references/google-ads.md`**「ad campaign-edit」。
116
+ 参数与单位见 **`references/google-ads.md`**「ad campaign-edit」金额单位说明。
116
117
 
117
118
  **广告组**:
118
119
 
119
120
  ```bash
120
- siluzan-tso ad adgroup-edit -a <mediaCustomerId> --id <adGroupId> --target-cpa <与 groups --json 同口径>
121
+ siluzan-tso ad adgroup-edit -a <mediaCustomerId> --id <adGroupId> --target-cpa <主币种金额>
121
122
  # 或
122
- siluzan-tso ad adgroup-edit -a <mediaCustomerId> --id <adGroupId> --max-cpc <与 groups --json 同口径>
123
+ siluzan-tso ad adgroup-edit -a <mediaCustomerId> --id <adGroupId> --max-cpc <主币种金额>
123
124
  ```
124
125
 
125
126
  见 **`references/google-ads.md`**「广告组编辑」。
126
127
 
127
- 写前建议先 **`ad groups --json` / `ad campaigns --json`** 取当前值,在宿主内算新值(如下调 12%:`new = round(old * 0.88)`),再写入。
128
+ 写前**必须**先 **`ad groups --json` / `ad campaigns --json`** 取当前值,**读取 `*Display` 字段**(主币种金额,如 `targetCpaAmountDisplay`、`maxCPCAmountDisplay`、`budgetDisplay`),在宿主内按主币种算新值(如下调 12%:`newDisplay = round((oldDisplay * 0.88) * 100) / 100`),再以主币种金额作为 `--target-cpa` / `--max-cpc` / `--budget` 传回。
129
+ **严禁** 把 `targetCpaAmount` / `maxCPCAmount` / `budget` 这些「分」字段直接当作主币种金额传给 CLI——会被再 ×100 一次,金额放大 100 倍。
128
130
 
129
131
  ### 写后复核
130
132
 
131
- 再次 **`ad campaigns --json`** 或 **`ad groups --json`**,确认 `targetCpa_BidingAmount` / `targetCpaAmount` / `maxCPCAmount` 等与预期一致。
133
+ 再次 **`ad campaigns --json`** 或 **`ad groups --json`**:
134
+ - 优先比对 **`*Display` 字段**(主币种)与预期主币种金额一致;
135
+ - 整数字段 `targetCpa_BidingAmount` / `targetCpaAmount` / `maxCPCAmount` / `budget` 应等于 `主币种 × 100` 后的整数。
132
136
 
133
137
  ### CLI 能力摘要
134
138
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.14-beta.7",
3
+ "version": "1.1.14-beta.9",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",