siluzan-tso-cli 1.1.20-beta.13 → 1.1.20-beta.15

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.20-beta.13),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.20-beta.15),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
package/dist/index.js CHANGED
@@ -2418,12 +2418,12 @@ function createVersionNotifier(opts) {
2418
2418
  const HOURS_24 = 24 * 60 * 60 * 1e3;
2419
2419
  const TTL_MAIN_TAG_MS = 60 * 60 * 1e3;
2420
2420
  const TTL_MIN_REQUIRED_MS = HOURS_24;
2421
- async function fetchVersionByTag(tag, cacheKey, fetchAtKey, cfg, maxAgeMs) {
2421
+ async function fetchVersionByTag(tag, cacheKey2, fetchAtKey, cfg, maxAgeMs) {
2422
2422
  const lastAt = cfg[fetchAtKey];
2423
- if (typeof lastAt === "string" && cacheKey in cfg) {
2423
+ if (typeof lastAt === "string" && cacheKey2 in cfg) {
2424
2424
  const lastMs = new Date(lastAt).getTime();
2425
2425
  if (Date.now() - lastMs < maxAgeMs) {
2426
- const v = cfg[cacheKey];
2426
+ const v = cfg[cacheKey2];
2427
2427
  const sv = typeof v === "string" && v ? v : null;
2428
2428
  return { version: sv, hitNetwork: false };
2429
2429
  }
@@ -4118,7 +4118,7 @@ async function fetchBalanceMap(media, accountIds, config, startDate, endDate, ve
4118
4118
  return result;
4119
4119
  }
4120
4120
  function parseGoogleAccountSpendOverviewRows(raw) {
4121
- const microsToYuan2 = (v) => {
4121
+ const microsToYuan = (v) => {
4122
4122
  if (v == null) return null;
4123
4123
  const n = Number(v);
4124
4124
  if (!Number.isFinite(n)) return null;
@@ -4138,8 +4138,8 @@ function parseGoogleAccountSpendOverviewRows(raw) {
4138
4138
  currencyCode: it.currencyCode ?? void 0,
4139
4139
  status: it.status ?? void 0,
4140
4140
  remainingAccountBudget: typeof it.remainingAccountBudget === "number" ? it.remainingAccountBudget : void 0,
4141
- adjustedSpendingLimitYuan: microsToYuan2(it.adjustedSpendingLimitMicros),
4142
- lastCostYuan: microsToYuan2(it.lastCostMicros)
4141
+ adjustedSpendingLimitYuan: microsToYuan(it.adjustedSpendingLimitMicros),
4142
+ lastCostYuan: microsToYuan(it.lastCostMicros)
4143
4143
  };
4144
4144
  };
4145
4145
  if (Array.isArray(raw)) {
@@ -114107,6 +114107,59 @@ function register20(program2) {
114107
114107
 
114108
114108
  // src/commands/keyword.ts
114109
114109
  init_auth();
114110
+
114111
+ // src/utils/usd-cny-rate.ts
114112
+ init_auth();
114113
+ var RATE_CACHE_TTL_MS = 10 * 60 * 1e3;
114114
+ var rateCache = null;
114115
+ function cacheKey(config) {
114116
+ return `${config.apiBaseUrl.replace(/\/$/, "")}|USD|CNY`;
114117
+ }
114118
+ function parseExrate(data) {
114119
+ if (data == null || typeof data !== "object") return null;
114120
+ const raw = data.exrate;
114121
+ const n = typeof raw === "string" ? Number(raw) : Number(raw);
114122
+ if (!Number.isFinite(n) || n <= 0) return null;
114123
+ return n;
114124
+ }
114125
+ async function getUsdToCnyExchangeRate(config, verbose) {
114126
+ const key = cacheKey(config);
114127
+ const now = Date.now();
114128
+ if (rateCache && rateCache.key === key && now - rateCache.fetchedAt < RATE_CACHE_TTL_MS) {
114129
+ if (verbose) {
114130
+ console.error(` [\u6C47\u7387] \u4F7F\u7528\u7F13\u5B58 USD\u2192CNY=${rateCache.rate}\uFF08${Math.round((now - rateCache.fetchedAt) / 1e3)}s \u524D\uFF09`);
114131
+ }
114132
+ return rateCache.rate;
114133
+ }
114134
+ const base = config.apiBaseUrl.replace(/\/$/, "");
114135
+ const url = `${base}/allinpay/GetRate?srcccy=USD&dstccy=CNY`;
114136
+ try {
114137
+ const data = await apiFetch2(url, config, { method: "GET" }, verbose);
114138
+ const rate = parseExrate(data);
114139
+ if (rate == null) {
114140
+ console.error("\n\u26A0 \u83B7\u53D6\u6C47\u7387\u5931\u8D25\uFF1AGetRate \u672A\u8FD4\u56DE\u6709\u6548 exrate\uFF0C\u51FA\u4EF7 CNY \u5B57\u6BB5\u5C06\u7701\u7565\u3002\n");
114141
+ return null;
114142
+ }
114143
+ rateCache = { key, rate, fetchedAt: now };
114144
+ if (verbose) {
114145
+ console.error(` [\u6C47\u7387] USD\u2192CNY=${rate}\uFF08\u5DF2\u7F13\u5B58 ${RATE_CACHE_TTL_MS / 6e4} \u5206\u949F\uFF09`);
114146
+ }
114147
+ return rate;
114148
+ } catch (err) {
114149
+ const msg = err instanceof Error ? err.message : String(err);
114150
+ console.error(`
114151
+ \u26A0 \u83B7\u53D6 USD\u2192CNY \u6C47\u7387\u5931\u8D25\uFF08\u51FA\u4EF7 CNY \u5B57\u6BB5\u5C06\u7701\u7565\uFF09\uFF1A${msg}
114152
+ `);
114153
+ return null;
114154
+ }
114155
+ }
114156
+ function convertUsdToCny(amountUsd, rate) {
114157
+ if (amountUsd == null || !Number.isFinite(amountUsd)) return null;
114158
+ if (!Number.isFinite(rate) || rate <= 0) return null;
114159
+ return Math.round(amountUsd * rate * 100) / 100;
114160
+ }
114161
+
114162
+ // src/commands/keyword.ts
114110
114163
  init_cli_json_snapshot();
114111
114164
  init_cli_table();
114112
114165
 
@@ -114169,12 +114222,25 @@ function requireGoogleApi2(config) {
114169
114222
  return config.googleApiUrl;
114170
114223
  }
114171
114224
  var KEYWORD_SUGGEST_BID_AMOUNT_CURRENCY = "USD";
114172
- function buildKeywordSuggestJsonPayload(items) {
114173
- const yuanItems = items.map(microsItemToYuan);
114174
- const n = yuanItems.length;
114225
+ function applyUsdBidAmountsInCny(item, usdToCnyRate) {
114226
+ return {
114227
+ ...item,
114228
+ averageCpcCNY: convertUsdToCny(item.averageCpcUSD, usdToCnyRate),
114229
+ lowTopOfPageBidCNY: convertUsdToCny(item.lowTopOfPageBidUSD, usdToCnyRate),
114230
+ highTopOfPageBidCNY: convertUsdToCny(item.highTopOfPageBidUSD, usdToCnyRate)
114231
+ };
114232
+ }
114233
+ function buildKeywordSuggestJsonPayload(items, opts) {
114234
+ let usdItems = items.map(microsItemToUSD);
114235
+ const rate = opts?.usdToCnyRate;
114236
+ if (rate != null && Number.isFinite(rate) && rate > 0) {
114237
+ usdItems = usdItems.map((row) => applyUsdBidAmountsInCny(row, rate));
114238
+ }
114239
+ const n = usdItems.length;
114175
114240
  return {
114176
- ...wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items: yuanItems }),
114177
- bidAmountCurrency: KEYWORD_SUGGEST_BID_AMOUNT_CURRENCY
114241
+ ...wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items: usdItems }),
114242
+ bidAmountCurrency: KEYWORD_SUGGEST_BID_AMOUNT_CURRENCY,
114243
+ ...rate != null && Number.isFinite(rate) && rate > 0 ? { usdToCnyExchangeRate: rate } : {}
114178
114244
  };
114179
114245
  }
114180
114246
  async function fetchUrlKeywords(url, keywords, verbose) {
@@ -114329,8 +114395,9 @@ async function runKeywordSuggest(opts) {
114329
114395
  });
114330
114396
  }
114331
114397
  const n = items.length;
114332
- const kwPayload = buildKeywordSuggestJsonPayload(items);
114333
- const yuanItems = kwPayload.items;
114398
+ const usdToCnyRate = await getUsdToCnyExchangeRate(config, opts.verbose);
114399
+ const kwPayload = buildKeywordSuggestJsonPayload(items, { usdToCnyRate });
114400
+ const usdItems = kwPayload.items;
114334
114401
  if (await emitCliJsonOrSnapshot(opts, {
114335
114402
  section: "keyword-suggest",
114336
114403
  commandLabel: "keyword suggest",
@@ -114345,49 +114412,107 @@ async function runKeywordSuggest(opts) {
114345
114412
  console.log(" \u6682\u65E0\u63A8\u8350\u5173\u952E\u8BCD\u3002\n");
114346
114413
  return;
114347
114414
  }
114348
- const cols = [
114415
+ const fmtAmt = (v) => v != null && Number.isFinite(v) ? v.toFixed(2) : "\u2014";
114416
+ const hasCny = usdToCnyRate != null && usdToCnyRate > 0;
114417
+ const cols = hasCny ? [
114349
114418
  { key: "keyword", header: "\u5173\u952E\u8BCD" },
114350
114419
  { key: "montlySearch", header: "\u6708\u5747\u641C\u7D22" },
114351
- { key: "cpc", header: "\u5E73\u5747CPC(USD)" },
114352
- { key: "bidRange", header: "\u9875\u9996\u51FA\u4EF7\u533A\u95F4(USD)" },
114420
+ { key: "cpcUsd", header: "\u5E73\u5747CPC(USD)" },
114421
+ { key: "cpcCny", header: "\u5E73\u5747CPC(CNY)" },
114422
+ { key: "bidRangeUsd", header: "\u9875\u9996\u51FA\u4EF7(USD)" },
114423
+ { key: "bidRangeCny", header: "\u9875\u9996\u51FA\u4EF7(CNY)" },
114424
+ { key: "competition", header: "\u7ADE\u4E89\u5EA6" }
114425
+ ] : [
114426
+ { key: "keyword", header: "\u5173\u952E\u8BCD" },
114427
+ { key: "montlySearch", header: "\u6708\u5747\u641C\u7D22" },
114428
+ { key: "cpcUsd", header: "\u5E73\u5747CPC(USD)" },
114429
+ { key: "bidRangeUsd", header: "\u9875\u9996\u51FA\u4EF7(USD)" },
114353
114430
  { key: "competition", header: "\u7ADE\u4E89\u5EA6" }
114354
114431
  ];
114355
- const fmtYuan = (v) => v != null && Number.isFinite(v) ? v.toFixed(2) : "\u2014";
114356
- const rows = yuanItems.map((item) => {
114357
- const cpc = fmtYuan(item.averageCpcYuan);
114358
- const low = fmtYuan(item.lowTopOfPageBidYuan);
114359
- const high = fmtYuan(item.highTopOfPageBidYuan);
114360
- const bidRange = low === "\u2014" && high === "\u2014" ? "\u2014" : `${low} ~ ${high}`;
114432
+ const rows = usdItems.map((item) => {
114433
+ const cpcUsd = fmtAmt(item.averageCpcUSD);
114434
+ const lowUsd = fmtAmt(item.lowTopOfPageBidUSD);
114435
+ const highUsd = fmtAmt(item.highTopOfPageBidUSD);
114436
+ const bidRangeUsd = lowUsd === "\u2014" && highUsd === "\u2014" ? "\u2014" : `${lowUsd} ~ ${highUsd}`;
114361
114437
  const competitionDisplay = item.competitionV2 ?? (item.competition != null ? item.competition.toFixed(2) : "\u2014");
114362
- return {
114438
+ const row = {
114363
114439
  keyword: item.keyword ?? "",
114364
114440
  montlySearch: String(item.montlySearch ?? "\u2014"),
114365
- cpc,
114366
- bidRange,
114441
+ cpcUsd,
114442
+ bidRangeUsd,
114367
114443
  competition: competitionDisplay
114368
114444
  };
114445
+ if (hasCny) {
114446
+ const cpcCny = fmtAmt(item.averageCpcCNY);
114447
+ const lowCny = fmtAmt(item.lowTopOfPageBidCNY);
114448
+ const highCny = fmtAmt(item.highTopOfPageBidCNY);
114449
+ row.cpcCny = cpcCny;
114450
+ row.bidRangeCny = lowCny === "\u2014" && highCny === "\u2014" ? "\u2014" : `${lowCny} ~ ${highCny}`;
114451
+ }
114452
+ return row;
114369
114453
  });
114370
114454
  printCliTable(rows, cols);
114371
- console.log();
114455
+ if (hasCny) {
114456
+ console.log(` \uFF08USD\u2192CNY \u6C47\u7387 ${usdToCnyRate}\uFF0C\u7F13\u5B58 10 \u5206\u949F\uFF1BJSON \u542B averageCpcCNY \u7B49\u5B57\u6BB5\uFF09
114457
+ `);
114458
+ } else {
114459
+ console.log();
114460
+ }
114372
114461
  }
114373
- function microsToYuan(v) {
114462
+ function microsToUSD(v) {
114374
114463
  if (v == null) return null;
114375
114464
  const n = Number(v);
114376
114465
  if (!Number.isFinite(n)) return null;
114377
114466
  return Math.round(n / 1e6 * 100) / 100;
114378
114467
  }
114379
- function microsItemToYuan(item) {
114468
+ function legacyUsdAmount(...candidates) {
114469
+ for (const v of candidates) {
114470
+ if (v != null && Number.isFinite(v)) return v;
114471
+ }
114472
+ return null;
114473
+ }
114474
+ function microsItemToUSD(item) {
114380
114475
  const {
114381
114476
  averageCpc: _legacyCpc,
114382
114477
  lowTopOfPageBidMicros: _legacyLow,
114383
114478
  highTopOfPageBidMicros: _legacyHigh,
114479
+ averageCpcYuan: _legacyCpcYuan,
114480
+ lowTopOfPageBidYuan: _legacyLowYuan,
114481
+ highTopOfPageBidYuan: _legacyHighYuan,
114482
+ averageCpcUSD: _existingCpcUsd,
114483
+ lowTopOfPageBidUSD: _existingLowUsd,
114484
+ highTopOfPageBidUSD: _existingHighUsd,
114485
+ averageCpcDollar: _legacyCpcDollar,
114486
+ lowTopOfPageBidDollar: _legacyLowDollar,
114487
+ highTopOfPageBidDollar: _legacyHighDollar,
114488
+ averageCpcCny: _dropCny1,
114489
+ lowTopOfPageBidCny: _dropCny2,
114490
+ highTopOfPageBidCny: _dropCny3,
114491
+ averageCpcCNY: _dropCny4,
114492
+ lowTopOfPageBidCNY: _dropCny5,
114493
+ highTopOfPageBidCNY: _dropCny6,
114384
114494
  ...rest
114385
114495
  } = item;
114386
114496
  return {
114387
114497
  ...rest,
114388
- averageCpcYuan: microsToYuan(_legacyCpc),
114389
- lowTopOfPageBidYuan: microsToYuan(_legacyLow),
114390
- highTopOfPageBidYuan: microsToYuan(_legacyHigh)
114498
+ averageCpcUSD: legacyUsdAmount(
114499
+ microsToUSD(_legacyCpc),
114500
+ _existingCpcUsd,
114501
+ _legacyCpcDollar,
114502
+ _legacyCpcYuan
114503
+ ),
114504
+ lowTopOfPageBidUSD: legacyUsdAmount(
114505
+ microsToUSD(_legacyLow),
114506
+ _existingLowUsd,
114507
+ _legacyLowDollar,
114508
+ _legacyLowYuan
114509
+ ),
114510
+ highTopOfPageBidUSD: legacyUsdAmount(
114511
+ microsToUSD(_legacyHigh),
114512
+ _existingHighUsd,
114513
+ _legacyHighDollar,
114514
+ _legacyHighYuan
114515
+ )
114391
114516
  };
114392
114517
  }
114393
114518
  function attachBaseCliOptions(cmd) {
@@ -115575,8 +115700,8 @@ async function runAccountWithdrawSubmit(opts) {
115575
115700
  });
115576
115701
  const feeRateCache = /* @__PURE__ */ new Map();
115577
115702
  for (const { currency, availableAmount } of accountDetails) {
115578
- const cacheKey = `${currency}-${availableAmount.toFixed(2)}`;
115579
- if (feeRateCache.has(cacheKey)) continue;
115703
+ const cacheKey2 = `${currency}-${availableAmount.toFixed(2)}`;
115704
+ if (feeRateCache.has(cacheKey2)) continue;
115580
115705
  try {
115581
115706
  const feeParams = new URLSearchParams({
115582
115707
  mediaType: "Google",
@@ -115589,16 +115714,16 @@ async function runAccountWithdrawSubmit(opts) {
115589
115714
  {},
115590
115715
  opts.verbose
115591
115716
  );
115592
- feeRateCache.set(cacheKey, feeData?.feeRate ?? 0);
115717
+ feeRateCache.set(cacheKey2, feeData?.feeRate ?? 0);
115593
115718
  } catch {
115594
115719
  console.warn(` \u26A0\uFE0F ${currency} \u7BA1\u7406\u8D39\u67E5\u8BE2\u5931\u8D25\uFF0C\u5C06\u4EE5 0 \u8D39\u7387\u63D0\u4EA4\u3002`);
115595
- feeRateCache.set(cacheKey, 0);
115720
+ feeRateCache.set(cacheKey2, 0);
115596
115721
  }
115597
115722
  }
115598
115723
  const body = accountDetails.map(
115599
115724
  ({ ma, mai, currency, balance, adjustments, availableAmount }) => {
115600
- const cacheKey = `${currency}-${availableAmount.toFixed(2)}`;
115601
- const feeRate = feeRateCache.get(cacheKey) ?? 0;
115725
+ const cacheKey2 = `${currency}-${availableAmount.toFixed(2)}`;
115726
+ const feeRate = feeRateCache.get(cacheKey2) ?? 0;
115602
115727
  const taxRate = currency === "CNY" ? 0.06 : 0;
115603
115728
  const totalAmounts = availableAmount * (1 + feeRate) * (1 + taxRate);
115604
115729
  return {
@@ -148,11 +148,11 @@ Windows 注意:部分 Agent 客户端通过 PowerShell / cmd 代执行命令
148
148
 
149
149
  ### 金额与品牌名
150
150
 
151
- - **金额单位统一为「元」**:CLI 出口的所有 JSON / 表格里,金额字段一律以**元**为单位;带 `*Yuan` 后缀的字段(如 `budgetAmountYuan`、`maxCPCAmountYuan`、`averageCpcYuan`)尤其表明这一约定。报告/表格金额保留 2 位小数,格式示例:`¥50.00 CNY`、`$50.00 USD`(符号与 `currencyCode` 一致)。
151
+ - **金额单位统一为「元」**:CLI 出口的所有 JSON / 表格里,账户/投放类金额字段一般以**元**为单位(如 `budgetAmountYuan`、`maxCPCAmountYuan`)。报告/表格金额保留 2 位小数,格式示例:`¥50.00 CNY`、`$50.00 USD`(符号与 `currencyCode` 一致)。
152
152
  - **`ad campaigns --json`/`--json-out`**:列表里的 `budget` 已是主币种「元」(与 `ad campaign-edit --budget` 写参同口径),可直接作用户可见日预算。
153
153
  - **`ad groups --json`**:日预算/CPA 读 `maxCPCAmountYuan` / `targetCpaAmountYuan`(元)。
154
154
  - **`google-analysis` 落盘 `campaigns-*.json`**:日预算读 `budgetAmountYuan`(元),系列目标 CPA 读 `campaignTargetCpaYuan` / `maximizeConversionsTargetCpaYuan`(元);同文件 `spend` / `averageCpc` / `costPerConversion` 也是元。
155
- - **`keyword --json`**:CPC 与页首出价读 `averageCpcYuan` / `lowTopOfPageBidYuan` / `highTopOfPageBidYuan`(已 ÷1,000,000);根级 **`bidAmountCurrency: "USD"`** 标明出价为**美元**(共享 MCC Keyword Planner)。限定市场地区:`keyword geo-list` 查国家 `geoTargetConstant` ID,拓词时 `--geo 2840,2826`(对应 `keywordidea/google?geoTargetConstantIds=...`)。
155
+ - **`keyword --json`**:美元出价读 `averageCpcUSD` / `lowTopOfPageBidUSD` / `highTopOfPageBidUSD`;人民币读 `averageCpcCNY` / `lowTopOfPageBidCNY` / `highTopOfPageBidCNY`(根级 `usdToCnyExchangeRate`)。根级 `bidAmountCurrency: "USD"`。限定市场:`keyword geo-list` + `--geo 2840,2826`。
156
156
  - **品牌名**必须来自(按优先级):(1) 用户明确提供 (2) `list-accounts` 返回的 `mag.advertiserName` (3) 用户提供的网址→域名占位并标注 `[待确认品牌名]`。**严禁**把英文域名自行翻译为虚构中文品牌。
157
157
 
158
158
  ### 批量任务硬约束
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.20-beta.13",
4
- "publishedAt": 1779264565877
3
+ "version": "1.1.20-beta.15",
4
+ "publishedAt": 1779268611318
5
5
  }
@@ -83,7 +83,7 @@ CLI 出口的所有 JSON / 表格金额已统一为**元**,关键字段:
83
83
  | `ad campaigns --json` | `budget`(元,与 `--budget` 写参同口径) |
84
84
  | `ad groups --json` | `maxCPCAmountYuan`、`targetCpaAmountYuan` |
85
85
  | `google-analysis campaigns` 落盘 `campaigns-*.json` | `budgetAmountYuan`、`campaignTargetCpaYuan`、`maximizeConversionsTargetCpaYuan`;同行 `spend` / `averageCpc` / `costPerConversion` 也是元 |
86
- | `keyword suggest --json` | `averageCpcYuan`、`lowTopOfPageBidYuan`、`highTopOfPageBidYuan` |
86
+ | `keyword suggest --json` | `averageCpcUSD`、`lowTopOfPageBidUSD`、`highTopOfPageBidUSD`;`averageCpcCNY` |
87
87
  | `balance` 等账户余额接口 | `remainingAccountBudget`(元) |
88
88
 
89
89
  旧字段 `budgetAmount`(分)、`maxCPCAmountDisplay`、`*Micros`(微元)**已不再落盘**,下游脚本无需做单位换算。金额保留 2 位小数,带货币代码(如 `¥50.00 CNY`、`$50.00 USD`),`currencyCode` 从响应读取,跨币种账户分表;细则见 `references/currency.md`。
@@ -46,7 +46,7 @@
46
46
 
47
47
  ## 金额单位约定
48
48
 
49
- - **CLI 出口的所有 JSON / 表格金额一律以「元」为单位**:`budget`、`*Yuan` 后缀字段(`budgetAmountYuan`、`maxCPCAmountYuan`、`averageCpcYuan` 等)、`spend` / `averageCpc` / `costPerConversion`、`remainingAccountBudget` 等都是元。
49
+ - **CLI 出口的大多数 JSON / 表格金额以「元」为单位**:`budget`、`*Yuan` 后缀(`budgetAmountYuan`、`maxCPCAmountYuan` 等)、`spend` / `averageCpc` / `costPerConversion` 等。**例外**:`keyword suggest` 的 Planner 出价为美元,字段名为 `averageCpcUSD` / `lowTopOfPageBidUSD` / `highTopOfPageBidUSD`,并可选附带 `averageCpcCNY` 等换算值。
50
50
  - **写 CLI 参数**(`--budget`、`--max-cpc`、`--target-cpa`、`--amount` 等):同样传**主币种元**,与账户 `currencyCode` 一致;CLI 内部按需 ×100 / ×1_000_000 写后端。
51
51
  - 旧版网关字段(`budgetAmount` 分、`*Micros` 微元、`maxCPCAmountDisplay` 等)**已不再落盘到 CLI 输出**,下游脚本无需做单位换算。
52
52
 
@@ -70,7 +70,7 @@
70
70
 
71
71
  `class` 取值:`scenario` | `geo` | `tech` | `question`。
72
72
 
73
- 拓词编排见 `references/keyword-planner-workflows.md`;Planner 数据须标注 `bidAmountCurrency`(美元)并按账户币种换算后写入说明。
73
+ 拓词编排见 `references/keyword-planner-workflows.md`;Planner 出价用 `*USD` 与 `*CNY`,根级 `bidAmountCurrency` / `usdToCnyExchangeRate`。
74
74
 
75
75
  ---
76
76
 
@@ -3,7 +3,7 @@
3
3
  > **拓词命令**读本文;**建户 JSON + validate + create** 读 `references/google-ads-campaign-plan.md`。关键词数量规则:`google-ads-rules/google-ads-keyword-taxonomy.md`。
4
4
  >
5
5
  > **数据口径**:`siluzan-tso keyword` 走网关 `keywordidea/google`(Google Keyword Planner 市场指标);可选 `--url` 叠加 **网址拓词**(`websitereco`,非 Google API)。与账户内 **`google-analysis` 投放表现**不是同一套数据,文档与回复中须区分「市场参考」与「账户实际花费/转化」。
6
- > 用户提供了google广告账户时,需要获取到广告账户的币种,因为关键词推荐返回的关键词最高最低出价都是美金,所以当账户为人民币账户时,需要你按当前汇率转换为人民币,如果当前账户是美金则不需要转换
6
+
7
7
  ---
8
8
 
9
9
  ## 路径选择(拓词前先定分支)
@@ -22,7 +22,7 @@
22
22
 
23
23
  1. (可选)宿主 **联网搜索** 或 `rag query` 归纳 **2–8 个英文种子词**(RAG 见 §0,联网搜索勿与 Google 指标混写在同一列)。
24
24
  2. `siluzan-tso keyword -k "种子1,种子2,..." [--url "<落地页或竞品站>"] --json-out ./snap-kw`
25
- 3. 对落盘 `items`:以 **`keyword` 返回行为准**(有 `montlySearch` / `averageCpcYuan` 等);联网搜索得到的词若无对应行,可并入词包但须标注 **「无 Google 市场数据」**,或丢弃。
25
+ 3. 对落盘 `items`:以 **`keyword` 返回行为准**(有 `montlySearch` / `averageCpcUSD` 等);联网搜索得到的词若无对应行,可并入词包但须标注 **「无 Google 市场数据」**,或丢弃。
26
26
 
27
27
  ### 分支 B — 仅 Google 市场数据(默认)
28
28
 
@@ -158,7 +158,7 @@ siluzan-tso keyword -k "structural adhesive,SG-200,curtain wall bonding" --url "
158
158
  ### 5)高商业意图粗筛(出价 + 竞争度 + 搜索量)
159
159
 
160
160
  1. `siluzan-tso keyword -k "..." --json-out ./snap-kw`
161
- 2. 对落盘 `items` 按 `averageCpcYuan`(CLI 出口已 ÷1,000,000,货币见根级 **`bidAmountCurrency: "USD"`**)、`competition` / `competitionV2`、`montlySearch` 综合排序截断;此外 `lowTopOfPageBidYuan` / `highTopOfPageBidYuan`(页首出价 20/80 分位,**美元**)可用于评估合理出价区间。
161
+ 2. 对落盘 `items` 按 `averageCpcUSD`(CLI 出口已 ÷1,000,000,货币见根级 **`bidAmountCurrency: "USD"`**)、`competition` / `competitionV2`、`montlySearch` 综合排序截断;此外 `lowTopOfPageBidUSD` / `highTopOfPageBidUSD`(页首出价 20/80 分位,**美元**)可用于评估合理出价区间。
162
162
 
163
163
  ### 6)词包 → campaign-create JSON
164
164
 
@@ -182,6 +182,6 @@ siluzan-tso keyword geo-list [--country-code <codes>] [--name-contains <text>] [
182
182
 
183
183
  `--google-only`:只调 `keywordidea/google`,不叠加 `--url` 的网址拓词;**分支 B(仅 Google)必加**。
184
184
 
185
- **返回字段**(与后端 `Samm.Core.Service.KeywordRecommendation` 对齐):根级 `bidAmountCurrency: "USD"`(出价货币);`keyword` / `montlySearch` / `averageCpcYuan` / `competition`(0~1 数值)/ `competitionV2`(`Low/Medium/High/Unknown`)/ `keywordPlanCompetitionLevel`(1~4 整数)/ `lowTopOfPageBidYuan` / `highTopOfPageBidYuan`(页首出价 P20/P80,**美元**)/ `source`(`"Google"` `"Cgy"`)。金额字段已由网关微元 ÷1,000,000。后端已按 `keywordPlanCompetitionLevel` 降序排序。
185
+ **返回字段**(与后端 `Samm.Core.Service.KeywordRecommendation` 对齐):根级 `bidAmountCurrency: "USD"`、`usdToCnyExchangeRate`(可选);每条 `averageCpcUSD` / `lowTopOfPageBidUSD` / `highTopOfPageBidUSD`(**美元**)及对应 `averageCpcCNY` / `lowTopOfPageBidCNY` / `highTopOfPageBidCNY`(CLI 换算);另有 `keyword` / `montlySearch` / `competition` / `competitionV2` / `source` 等。金额已由网关微元 ÷1,000,000
186
186
 
187
187
  与只读账户关键词列表、否词 CRUD 的对照仍归 **`references/google-ads.md`** 中 `ad keywords` / `ad keyword-*` 各节。
@@ -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.20-beta.13'
12
+ $PKG_VERSION = '1.1.20-beta.15'
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.20-beta.13"
12
+ readonly PKG_VERSION="1.1.20-beta.15"
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"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.20-beta.13",
3
+ "version": "1.1.20-beta.15",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",