@optima-chat/tiktok-cli 0.15.1 → 0.16.0

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.
Files changed (3) hide show
  1. package/README.md +3 -0
  2. package/dist/index.js +364 -67
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -26,6 +26,7 @@ tiktok <domain> <verb> --shop <shopId> [--flags]
26
26
  | `analytics products` | 商品维度数据(GMV 降序)。`--version` 默认 `202509`;`202605` 返回 `total_performance` + 分渠道 `*_performance` 组(**组是可选的、字段名三套变体、`overall_performance` 在新版不存在**,见 spec §3.0);未知版本本地拒,平台出新版可 `--force-version` 直接试;`--page-token` 翻页,**末页 `next_page_token` 为空串**(不是缺席) |
27
27
  | `products list` | 商品列表 |
28
28
  | `promotions list` | 促销活动。`--status` **省略 = 全部状态**(`ONGOING`/`NOT_START`/`EXPIRED`/`DEACTIVATED`;⚠️ 值写错平台**静默忽略并返回全部**,故 CLI **本地硬拒未知值**;平台将来新增值可用 `--force-status` 越过);`--page-size`(默认 20);`--page-token` 翻页,**末页无 `next_page_token`** |
29
+ | `promotions get` | 按 id 读单个促销活动(`--id`):类型、状态、时间窗、挂的商品与每个 SKU 的活动价(一口价 / 秒杀价读 `skus[].activity_price.amount`,百分比读 `skus[].discount`)。平台没回 `products` 时补成 `[]` |
29
30
  | `finance statements` | 结算单 |
30
31
 
31
32
  ### 建品前置只读辅助(拼商品体用)
@@ -96,6 +97,8 @@ tiktok products set-price --shop <id> --product <pid> --sku <sid> --price 29.90
96
97
  | `products set-price` | 改 SKU 售价(`--product --sku --price [--currency]`)。仅 `ACTIVATE` 商品,且不在进行中的促销里(`update-price-202309` 原文)。改价不触发商品重审、立即生效(依据是 `partial-edit-product-202309` 的「`price`/`inventory` 不需 reaudit」,**本端点自己的文档没写这句、也未单独实测**)。 | ✓ 改价+回读 |
97
98
  | `products set-inventory` | 改 SKU 库存(`--product --sku --warehouse --quantity`)。只要商品不在 `FREEZE`/`DELETED` 状态都能改(`update-inventory-202309` 原文)。改库存不触发商品重审、立即生效(同上依据,未单独实测)。⚠️ `DRAFT` 商品**未实测** —— 官方只写了「非 `FREEZE`/`DELETED`」,没说 DRAFT 可以;且草稿 SKU 常常还没分仓,会撞 `12052096`。 | ✓ 49→55→49 可逆 |
98
99
  | `promotions create-flashsale` | 建秒杀(`--title --product --sku --price [--limit --per-user --hours]`)。两步:建壳+加品 | ✓ 真实建成 |
100
+ | `promotions create-discount` | 建商品折扣活动:`--price` 一口价(`FIXED_PRICE`)或 `--percent` 减 N%(`DIRECT_DISCOUNT`;`85` = 减 85%、按原价 15% 卖,**打 85 折要传 15**),`--title [--product] [--days 180]`。两步:建壳+加品,可多商品;撞重名 / 加品失败时给可粘贴的下一步命令 | ✓ 真实建成(FP;--percent 待活体) |
101
+ | `promotions add-products` | 往已有折扣活动加新 SKU(`--id <activityId> --price|--percent … [--product]`)。预读先于 PUT:类型 / 状态 / 已在活动里的 SKU 都会被拦下;只加不改 | — |
99
102
  | `promotions deactivate` | 终止促销活动(`--id`)。不可逆 | ✓ |
100
103
  | `products create --body-file <json>` | **生产级建品**(完整商品体 JSON)。dry-run 内嵌 `listing_check` 预检 → `--apply` 建品 → 回读高亮 warnings。⚠️ `save_mode` 默认 `LISTING`:**不写就是建完直接上架送审**(#1958,见下方「送审语义」)。可在 body 里写 `"save_mode": "AS_DRAFT"` 留草稿(**未实测**)。 | ✓ 真实上架 V.V.Love(ACTIVATE) |
101
104
  | `products add-variant --product <id> --body-file <json>` | 加规格 SKU(读现有 skus → merge → partial_edit;新 SKU 自动继承 sku_dimensions/sku_weight)。⚠️ **默认送审**(#1958,见下方「送审语义」)。本命令**自构 body**,body-file 里的 `save_mode` **不透传、写了也无效**;要留草稿只能用 `products partial-edit`。 | ✓ 真加变体成功 |
package/dist/index.js CHANGED
@@ -310,11 +310,12 @@ function commandUsage(cmd) {
310
310
  const flags = cmd.flags?.length ? cmd.flags.map((f) => `--${f}`).join(" ") : "\uFF08\u6CA1\u6709\u4E13\u5C5E\u53C2\u6570\uFF09";
311
311
  const shop = cmd.noShop ? "" : " --shop <shopId>";
312
312
  const globals = cmd.noShop ? "--apply" : "--shop / --apply";
313
+ const dry = cmd.write ? "\u672C\u547D\u4EE4\u662F\u5199\u64CD\u4F5C\uFF1A\u9ED8\u8BA4 dry-run\uFF08\u53EA\u6253\u5370\u5C06\u53D1\u7684\u8BF7\u6C42\u3001\u4E0D\u53D1\uFF09\uFF0C\u52A0 --apply \u624D\u771F\u53D1\u3002\n" : "";
313
314
  return `tiktok ${cmd.name}${shop} [--flags]
314
315
 
315
316
  ${cmd.summary}
316
317
 
317
- \u8BA4\u7684\u53C2\u6570\uFF1A${flags}\uFF08\u53E6\u6709\u5168\u5C40 ${globals}\uFF09\uFF1B\u4E0D\u8BA4\u7684\u4F1A\u5F53\u573A\u62A5\u9519\u3002
318
+ ${dry}\u8BA4\u7684\u53C2\u6570\uFF1A${flags}\uFF08\u53E6\u6709\u5168\u5C40 ${globals}\uFF09\uFF1B\u4E0D\u8BA4\u7684\u4F1A\u5F53\u573A\u62A5\u9519\u3002
318
319
  `;
319
320
  }
320
321
  var today = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -322,6 +323,37 @@ var daysAgo = (n) => new Date(Date.now() - n * 864e5).toISOString().slice(0, 10)
322
323
  var toEpoch = (v) => /^\d+$/.test(v) ? Number(v) : Math.floor(Date.parse(v) / 1e3);
323
324
  var PROMOTION_STATUSES = /* @__PURE__ */ new Set(["ONGOING", "NOT_START", "EXPIRED", "DEACTIVATED"]);
324
325
  var ANALYTICS_PRODUCT_VERSIONS = /* @__PURE__ */ new Set(["202509", "202605"]);
326
+ var ADD_TEXT = {
327
+ SUM_ADD: "\u5F80\u5DF2\u6709\u6298\u6263\u6D3B\u52A8\u52A0\u65B0 SKU\uFF08--id <activityId> --price <skuId>:<\u4E00\u53E3\u4EF7>|<productId>/<skuId>:<\u4E00\u53E3\u4EF7>[,...] \u6216 --percent <skuId>:<\u51CFN%>[,...] [--product]\uFF09\u3002\u5148\u8BFB\u6D3B\u52A8\u6838\u5BF9\u7C7B\u578B\u4E0E\u5DF2\u6709 SKU \u518D\u52A0\u54C1\u3001\u56DE\u8BFB\uFF1B\u53EA\u52A0\u4E0D\u6539\uFF0C\u5DF2\u5728\u6D3B\u52A8\u91CC\u7684 SKU \u4F1A\u88AB\u62E6\u4E0B\u3002--percent 85 = \u51CF85%\uFF08\u625385\u6298\u8981\u4F2015\uFF09",
328
+ SUM_GET: "\u6309 id \u8BFB\u5355\u4E2A\u4FC3\u9500\u6D3B\u52A8\uFF08--id <activityId>\uFF09\uFF1A\u7C7B\u578B\u3001\u72B6\u6001\u3001\u65F6\u95F4\u7A97\u3001\u6302\u7684\u5546\u54C1\u4E0E\u6BCF\u4E2A SKU \u7684\u6D3B\u52A8\u4EF7\uFF08\u4E00\u53E3\u4EF7 / \u79D2\u6740\u4EF7\u8BFB skus[].activity_price.amount\uFF0C\u767E\u5206\u6BD4\u8BFB skus[].discount\uFF09",
329
+ HUMAN_ADD: (id, list) => `\u5F80\u6D3B\u52A8 ${id} \u52A0\uFF1A${list}\u3002\u53EA\u52A0\u65B0 SKU\uFF1B\u5DF2\u5728\u6D3B\u52A8\u91CC\u7684\u4F1A\u5728 --apply \u9884\u8BFB\u65F6\u88AB\u62E6\u4E0B\u3002`,
330
+ AV2: "\u672C\u547D\u4EE4\u7528 --id \u4F20\u6D3B\u52A8 id\uFF08\u6CA1\u6709 --activity-id\uFF09\uFF0C\u5982 --id 7686081985965900001",
331
+ AV3: (rest, flag) => `\u591A\u4F59\u7684\u4F4D\u7F6E\u53C2\u6570\u300C${rest}\u300D\u2014\u2014\u6BCF\u4E2A\u53C2\u6570\u53EA\u80FD\u5E26\u4E00\u4E2A\u503C\u3002\u591A\u4E2A SKU \u8981\u7528\u9017\u53F7\u8FDE\u6210\u4E00\u4E2A --${flag}\uFF0C\u5982 --${flag} A:12,B:15\uFF08\u4E0D\u80FD\u7528\u7A7A\u683C\u5206\u9694\uFF09`,
332
+ GV3: (rest) => `\u591A\u4F59\u7684\u4F4D\u7F6E\u53C2\u6570\u300C${rest}\u300D\u2014\u2014\u6BCF\u4E2A\u53C2\u6570\u53EA\u80FD\u5E26\u4E00\u4E2A\u503C\u3002`,
333
+ AP1: (shop, id) => `\u6CA1\u8BFB\u5230\u6D3B\u52A8 ${id}\uFF0C\u672A\u52A0\u4EFB\u4F55\u5546\u54C1\uFF1B\u5148\u7528 \`tiktok promotions get --shop ${shop} --id ${id}\` \u786E\u8BA4\u6D3B\u52A8\u5B58\u5728\u3002`,
334
+ AP1N: (id) => `\u8BFB\u53D6\u6D3B\u52A8 ${id} \u5931\u8D25\uFF08\u7F51\u7EDC / \u670D\u52A1\u7AEF\u6682\u65F6\u6027\u9519\u8BEF\uFF09\uFF0C\u672A\u5199\u4EFB\u4F55\u4E1C\u897F\uFF0C\u53EF\u4EE5\u539F\u6837\u91CD\u8DD1\u672C\u547D\u4EE4\u3002`,
335
+ AP1M: (shop, id) => `\u9884\u8BFB\u5230\u7684\u6D3B\u52A8 ${id} \u5F62\u72B6\u5F02\u5E38\uFF08\u7F3A activity_type \u6216 products\uFF09\uFF0C\u672A\u5199\u4EFB\u4F55\u4E1C\u897F\uFF1B\u5148\u7528 \`tiktok promotions get --shop ${shop} --id ${id}\` \u770B\u6D3B\u52A8\u8BE6\u60C5\uFF08\u6CE8\u610F get \u4F1A\u628A\u5E73\u53F0\u6CA1\u56DE\u7684 products \u8865\u6210 []\uFF0C\u8981\u770B\u5E73\u53F0\u539F\u6837\u54CD\u5E94\u5F97\u76F4\u8FDE\uFF09\u3002`,
336
+ AT1: (id) => `\u6D3B\u52A8 ${id} \u662F FIXED_PRICE\uFF08\u4E00\u53E3\u4EF7\uFF09\uFF0C\u8981\u7528 --price <skuId>:<\u4E00\u53E3\u4EF7>\uFF1B--percent \u53EA\u7528\u4E8E DIRECT_DISCOUNT \u6D3B\u52A8\u3002`,
337
+ AT2: (id) => `\u6D3B\u52A8 ${id} \u662F DIRECT_DISCOUNT\uFF08\u767E\u5206\u6BD4\uFF09\uFF0C\u8981\u7528 --percent <skuId>:<\u51CFN%>\uFF1B--price \u53EA\u7528\u4E8E FIXED_PRICE \u6D3B\u52A8\u3002`,
338
+ AT3: (id) => `\u6D3B\u52A8 ${id} \u662F FLASHSALE\uFF08\u79D2\u6740\uFF09\uFF1A\u79D2\u6740\u6D3B\u52A8\u52A0\u54C1\u4E0D\u5728\u672C\u547D\u4EE4\u8303\u56F4\uFF0C\u53E6\u5EFA\u4E00\u573A\u7528 tiktok promotions create-flashsale\u3002`,
339
+ AT4: (id, type) => `\u6D3B\u52A8 ${id} \u7684\u7C7B\u578B\u662F ${type}\uFF0C\u672C\u547D\u4EE4\u53EA\u652F\u6301\u5F80 FIXED_PRICE / DIRECT_DISCOUNT \u6D3B\u52A8\u52A0\u54C1\u3002`,
340
+ AS1: (id, status, allowed) => `\u6D3B\u52A8 ${id} \u7684\u72B6\u6001\u662F ${status}\uFF0C\u53EA\u6709 ${allowed} \u7684\u6D3B\u52A8\u80FD\u7528\u672C\u547D\u4EE4\u52A0\u54C1\u3002`,
341
+ AL1: (id, level) => `\u6D3B\u52A8 ${id} \u7684 product_level \u662F ${level}\uFF0C\u672C\u547D\u4EE4\u53EA\u652F\u6301 VARIATION\uFF08\u6309 SKU \u5B9A\u4EF7\uFF09\u7684\u6D3B\u52A8\u3002`,
342
+ AD1: (id, sids) => `\u8FD9\u4E9B SKU \u5DF2\u5728\u6D3B\u52A8 ${id} \u91CC\u3001\u4F46\u503C\u4E0E\u672C\u6B21\u63D0\u4EA4\u4E0D\u540C\uFF1A${sids}\u3002\u6539\u5DF2\u5728\u6D3B\u52A8\u91CC\u7684 SKU \u7684\u4EF7 / \u6298\u6263\u4E0D\u5728\u672C\u547D\u4EE4\u8303\u56F4\uFF08\u91CD\u590D\u63D0\u4EA4\u7684\u5E73\u53F0\u884C\u4E3A\u672A\u5B9E\u6D4B\uFF09\uFF0C\u4ECD\u8D70\u6D4F\u89C8\u5668\u3002`,
343
+ AD1_REST: (addCmd) => `\u6CA1\u5728\u6D3B\u52A8\u91CC\u7684\u53EF\u4EE5\u8FD9\u6837\u52A0\uFF1A${addCmd}`,
344
+ AD2: (id, sids, addCmd) => `\u8FD9\u4E9B SKU \u5DF2\u6309\u672C\u6B21\u63D0\u4EA4\u7684\u503C\u5728\u6D3B\u52A8 ${id} \u91CC\uFF0C\u65E0\u9700\u518D\u52A0\uFF1A${sids}\u3002\u5176\u4F59\u7684\u8FD9\u6837\u52A0\uFF1A${addCmd}`,
345
+ AD3: (n, id) => `\u672C\u6B21\u63D0\u4EA4\u7684 ${n} \u4E2A SKU \u90FD\u5DF2\u6309\u63D0\u4EA4\u7684\u503C\u5728\u6D3B\u52A8 ${id} \u91CC\u2014\u2014\u65E0\u9700\u518D\u52A0\uFF0C\u522B\u91CD\u8DD1\u3002`,
346
+ AW1: (shop, id) => `\u5DF2\u63D0\u4EA4\u5230\u6D3B\u52A8 ${id}\uFF0C\u4F46\u56DE\u8BFB\u5931\u8D25\uFF0C\u672A\u80FD\u9A8C\u8BC1\u672C\u6B21\u7684 SKU \u662F\u5426\u6302\u4E0A\u2014\u2014\u4E0D\u8981\u91CD\u8DD1\u672C\u547D\u4EE4\uFF1B\u5148\u7528 \`tiktok promotions get --shop ${shop} --id ${id}\` \u6838\u5BF9\uFF0C\u5982\u5B9E\u544A\u8BC9\u5E97\u4E3B\u54EA\u4E9B SKU \u672A\u786E\u8BA4\u6302\u4E0A\u3002`,
347
+ AW2: (id, problems) => `\u5DF2\u63D0\u4EA4\u5230\u6D3B\u52A8 ${id}\uFF0C\u4F46\u56DE\u8BFB\u4E0E\u63D0\u4EA4\u4E0D\u4E00\u81F4\uFF1A${problems}\u2014\u2014\u4E0D\u8981\u91CD\u8DD1\u672C\u547D\u4EE4\uFF1B\u5982\u5B9E\u544A\u8BC9\u5E97\u4E3B\u54EA\u4E9B SKU \u672A\u786E\u8BA4\u6302\u4E0A\u3002`,
348
+ AW2_MISS: (addCmd) => `\u7F3A\u7684\u53EF\u4EE5\u8FD9\u6837\u8865\uFF1A${addCmd}`,
349
+ AW3: (shop, id, lost) => `\u26A0\uFE0F \u6D3B\u52A8 ${id} \u539F\u6709\u7684\u5546\u54C1\u5728\u56DE\u8BFB\u91CC\u5BF9\u4E0D\u4E0A\uFF1A${lost}\u2014\u2014\u52A0\u54C1\u53EF\u80FD\u8986\u76D6\u4E86\u539F\u6709\u5546\u54C1\u3002\u5982\u5B9E\u544A\u8BC9\u5E97\u4E3B\uFF0C\u5E76\u7528 \`tiktok promotions get --shop ${shop} --id ${id}\` \u6838\u5BF9\u3002`,
350
+ AN1: (n, id) => `\u52A0\u5546\u54C1\u62A5\u4E86\u9519\uFF0C\u4F46\u56DE\u8BFB\u663E\u793A\u63D0\u4EA4\u7684 ${n} \u4E2A SKU \u90FD\u5DF2\u6302\u4E0A\u6D3B\u52A8 ${id}\u2014\u2014\u522B\u91CD\u8DD1\u3002`,
351
+ AN2: (id) => `\u52A0\u5546\u54C1\u5931\u8D25\uFF0C\u56DE\u8BFB\u663E\u793A\u672C\u6B21\u63D0\u4EA4\u7684 SKU \u4E00\u4E2A\u90FD\u6CA1\u6302\u4E0A\u6D3B\u52A8 ${id}\u3002\u6309\u4E0A\u9762\u7684\u5E73\u53F0\u62A5\u9519\u6539\u6B63\u53C2\u6570\u540E\u91CD\u8DD1\u672C\u547D\u4EE4\u5373\u53EF\uFF08\u53EA\u6709\u9650\u6D41\u3001\u7F51\u7EDC\u7C7B\u62A5\u9519\u624D\u539F\u6837\u91CD\u8DD1\uFF09\u3002`,
352
+ AN3: (problems) => `\u52A0\u5546\u54C1\u62A5\u4E86\u9519\uFF0C\u56DE\u8BFB\u663E\u793A\u672C\u6B21\u63D0\u4EA4\u7684\u6CA1\u6709\u5168\u90E8\u6302\u4E0A\uFF1A${problems}\u3002`,
353
+ AN3_MISS: (addCmd) => `\u7F3A\u7684\u8865\u4E0A\uFF1A${addCmd}\uFF08\u6309\u4E0A\u9762\u7684\u5E73\u53F0\u62A5\u9519\u6539\u6B63\u540E\u518D\u8865\uFF09\u3002`,
354
+ AN3_MIS: "\u503C\u4E0D\u7B26\u7684\u6309\u8BFB\u56DE\u7684\u503C\u751F\u6548\uFF0C\u6539\u4EF7 / \u6539\u6298\u6263\u4E0D\u5728\u672C\u547D\u4EE4\u8303\u56F4\u3002",
355
+ AN4: (shop, id) => `\u52A0\u5546\u54C1\u5931\u8D25\uFF0C\u56DE\u8BFB\u4E5F\u5931\u8D25\u4E86\uFF0C\u67E5\u4E0D\u5230\u6D3B\u52A8 ${id} \u91CC\u672C\u6B21\u7684 SKU \u6709\u6CA1\u6709\u6302\u4E0A\u3002\u5148\u7528 \`tiktok promotions get --shop ${shop} --id ${id}\` \u6838\u5BF9\uFF0C\u518D\u51B3\u5B9A\u662F\u5426\u91CD\u8DD1\uFF08\u5DF2\u6302\u4E0A\u7684\u4F1A\u5728\u9884\u8BFB\u90A3\u6B65\u88AB\u8BC6\u522B\uFF0C\u91CD\u8DD1\u4E0D\u4F1A\u91CD\u590D\u52A0\uFF09\u3002`
356
+ };
325
357
  var COMMANDS = [
326
358
  {
327
359
  name: "orders list",
@@ -953,20 +985,26 @@ var COMMANDS = [
953
985
  },
954
986
  {
955
987
  name: "promotions create-discount",
956
- summary: "\u5EFA\u5546\u54C1\u6298\u6263\u6D3B\u52A8 FIXED_PRICE\uFF08--title [--product] --price <skuId>:<\u4E00\u53E3\u4EF7>|<productId>/<skuId>:<\u4E00\u53E3\u4EF7>[,...] [--days 180]\uFF09\u3002\u4E24\u6B65:\u5EFA\u58F3+\u52A0\u54C1\uFF0C\u53EF\u591A\u5546\u54C1",
957
- flags: ["days", "price", "product", "title"],
988
+ summary: "\u5EFA\u5546\u54C1\u6298\u6263\u6D3B\u52A8 FIXED_PRICE\uFF08--price \u4E00\u53E3\u4EF7\uFF09\u6216 DIRECT_DISCOUNT\uFF08--percent \u51CFN%\uFF09\uFF08--title [--product] --price <skuId>:<\u4E00\u53E3\u4EF7>|<productId>/<skuId>:<\u4E00\u53E3\u4EF7>[,...] \u6216 --percent <skuId>:<\u51CFN%>|<productId>/<skuId>:<\u51CFN%>[,...] [--days 180]\uFF09\u3002--percent 85 = \u51CF85%\u3001\u6309\u539F\u4EF715%\u5356\uFF08\u625385\u6298\u8981\u4F2015\uFF09\u3002\u4E24\u6B65:\u5EFA\u58F3+\u52A0\u54C1\uFF0C\u53EF\u591A\u5546\u54C1",
989
+ flags: ["days", "percent", "price", "product", "title"],
958
990
  write: true,
959
991
  validate: validateCreateDiscount,
960
992
  preview: ({ args: args2 }) => {
961
993
  const { begin, end } = discountWindow(args2);
962
994
  return [
963
995
  { step: "1/2 \u5EFA\u6D3B\u52A8\u58F3", method: "POST", path: "/promotion/202309/activities", body: discountActivityBody(args2, begin, end) },
964
- { step: "2/2 \u52A0\u5546\u54C1+\u4E00\u53E3\u4EF7", method: "PUT", path: "/promotion/202309/activities/{activity_id}/products", body: discountProductsBody(args2), human: discountHuman(args2) }
996
+ { step: discountValueFlag(args2) === "percent" ? "2/2 \u52A0\u5546\u54C1+\u51CFN%" : "2/2 \u52A0\u5546\u54C1+\u4E00\u53E3\u4EF7", method: "PUT", path: "/promotion/202309/activities/{activity_id}/products", body: discountProductsBody(args2), human: discountHuman(args2) }
965
997
  ];
966
998
  },
967
999
  run: async ({ shop, args: args2 }) => {
968
1000
  const { begin, end } = discountWindow(args2);
969
1001
  const submitted = submittedSkus(args2);
1002
+ const dd = discountValueFlag(args2) === "percent";
1003
+ const field = dd ? "discount" : "amount";
1004
+ const flag = discountValueFlag(args2);
1005
+ const rawVal = discountValueRaw(args2);
1006
+ const allItems = rawVal.split(",");
1007
+ const intent = { type: dd ? "DIRECT_DISCOUNT" : "FIXED_PRICE", flag, days: Number(args2.days ?? 180), addCmd: (aid) => addProductsCmd(shop, aid, args2.product, flag, allItems, true) };
970
1008
  const created = await proxy("POST", shop, "/promotion/202309/activities", { body: discountActivityBody(args2, begin, end) });
971
1009
  if (!created.ok) {
972
1010
  const err = created.error;
@@ -974,7 +1012,7 @@ var COMMANDS = [
974
1012
  case "maybe_landed":
975
1013
  return { ...created, error: { ...err, message: `${err.message} ${DISCOUNT_TEXT.maybeLanded}` } };
976
1014
  case "dup":
977
- return { ...created, error: { ...err, message: `${err.message} ${await dupSelfCheck(shop, args2.title, submitted)}` } };
1015
+ return { ...created, error: { ...err, message: `${err.message} ${await dupSelfCheck(shop, args2.title, submitted, field, intent)}` } };
978
1016
  case "rejected":
979
1017
  return created;
980
1018
  }
@@ -996,20 +1034,21 @@ var COMMANDS = [
996
1034
  if (!withProducts.ok) {
997
1035
  const { retry_after_s: _dropped, ...err } = withProducts.error;
998
1036
  let note;
999
- const c = rb.ok ? compareReadback(submitted, rb.data) : null;
1037
+ const c = rb.ok ? compareReadback(submitted, rb.data, field) : null;
1000
1038
  if (!c) note = DISCOUNT_TEXT.addFailReadbackFailed(shop, activityId);
1001
1039
  else {
1002
- if (c.missing.length === 0 && c.mismatched.length === 0) note = DISCOUNT_TEXT.addFailAllAttached(submitted.length, activityId);
1003
- else if (c.missing.length === submitted.length && c.productCount === 0) note = DISCOUNT_TEXT.addFailNoneAttached(shop, activityId);
1004
- else note = DISCOUNT_TEXT.addFailPartial(problemsText(c), shop, activityId);
1040
+ if (c.missing.length === 0 && c.mismatched.length === 0) note = DISCOUNT_TEXT.addFailAllAttached(submitted.length, activityId, dd);
1041
+ else if (c.missing.length === submitted.length && c.productCount === 0) note = DISCOUNT_TEXT.addFailNoneAttached(shop, activityId, addProductsCmd(shop, activityId, args2.product, flag, allItems, false));
1042
+ else if (c.missing.length === 0) note = DISCOUNT_TEXT.addFailPartialMismatch(problemsText(c, field), shop, activityId, dd);
1043
+ else note = DISCOUNT_TEXT.addFailPartial(problemsText(c, field), shop, activityId, addProductsCmd(shop, activityId, args2.product, flag, rawItemsFor(rawVal, c.missing), false), dd);
1005
1044
  }
1006
1045
  return { ok: false, error: { ...err, retryable: false }, data: { activity_id: activityId, readback, note }, meta: withProducts.meta };
1007
1046
  }
1008
1047
  const ours = [];
1009
- const diff = rb.ok ? compareReadback(submitted, rb.data) : null;
1010
- if (!diff) ours.push(DISCOUNT_TEXT.readbackFailed(activityId));
1048
+ const diff = rb.ok ? compareReadback(submitted, rb.data, field) : null;
1049
+ if (!diff) ours.push(DISCOUNT_TEXT.readbackFailed(shop, activityId));
1011
1050
  else {
1012
- const problems = problemsText(diff);
1051
+ const problems = problemsText(diff, field);
1013
1052
  if (problems) ours.push(DISCOUNT_TEXT.readbackDiffers(activityId, problems));
1014
1053
  }
1015
1054
  const putData = withProducts.data ?? {};
@@ -1018,6 +1057,25 @@ var COMMANDS = [
1018
1057
  return { ok: true, data, meta: withProducts.meta };
1019
1058
  }
1020
1059
  },
1060
+ {
1061
+ name: "promotions add-products",
1062
+ summary: ADD_TEXT.SUM_ADD,
1063
+ flags: ["id", "percent", "price", "product"],
1064
+ write: true,
1065
+ validate: validateAddProducts,
1066
+ // 🔴 命令块只字面读 args.id / args.product,值旗标经 discountValueFlag / discountValueRaw(它们字面读 args.percent / args.price);
1067
+ // ⛔ 不得提到 discountHuman / validateCreateDiscount / discountActivityBody / discountWindow(它们读 days / title,本命令没有)
1068
+ preview: ({ args: args2 }) => addProductsPlan(args2.id, discountValueFlag(args2), discountValueRaw(args2), args2.product),
1069
+ run: ({ shop, args: args2 }) => runAddProducts(shop, args2.id, discountValueFlag(args2), discountValueRaw(args2), args2.product)
1070
+ },
1071
+ {
1072
+ name: "promotions get",
1073
+ summary: ADD_TEXT.SUM_GET,
1074
+ flags: ["id"],
1075
+ listKey: "products",
1076
+ validate: validateGet,
1077
+ run: ({ shop, args: args2 }) => runGet(shop, args2.id)
1078
+ },
1021
1079
  {
1022
1080
  name: "promotions deactivate",
1023
1081
  summary: "\u7EC8\u6B62/\u505C\u7528\u4FC3\u9500\u6D3B\u52A8\uFF08--id \u5FC5\u586B\uFF09\u3002\u4E0D\u53EF\u9006",
@@ -1466,27 +1524,41 @@ function findCommand(tokens) {
1466
1524
  }
1467
1525
  return null;
1468
1526
  }
1469
- var DISCOUNT_FLAGS = ["shop", "apply", "actor", "title", "product", "price", "days"];
1527
+ var DISCOUNT_FLAGS = ["shop", "apply", "actor", "title", "product", "price", "percent", "days"];
1528
+ function discountValueFlag(args2) {
1529
+ return "percent" in args2 ? "percent" : "price";
1530
+ }
1531
+ function discountValueRaw(args2) {
1532
+ return discountValueFlag(args2) === "percent" ? args2.percent ?? "" : args2.price ?? "";
1533
+ }
1470
1534
  var PRICE_ITEM = /^(?:([^:,\s/]+)\/)?([^:,\s/]+):(0|[1-9]\d*)(?:\.(\d{1,2}))?$/;
1471
- function parsePriceItems(raw, defaultProduct) {
1535
+ var PCT_ITEM = /^(?:([^:,\s/]+)\/)?([^:,\s/]+):([1-9]\d?)$/;
1536
+ function parsePriceItems(raw, defaultProduct, flag = "price") {
1472
1537
  const items = [];
1473
1538
  const seen = /* @__PURE__ */ new Set();
1539
+ const unit = flag === "percent" ? "<\u51CFN%>" : "<\u4EF7>";
1474
1540
  for (const part of raw.split(",")) {
1475
- const m = PRICE_ITEM.exec(part);
1541
+ const m = (flag === "percent" ? PCT_ITEM : PRICE_ITEM).exec(part);
1476
1542
  if (!m) {
1477
- return { ok: false, message: `--price \u6BCF\u4E00\u9879\u5FC5\u987B\u662F <skuId>:<\u4E00\u53E3\u4EF7> \u6216 <productId>/<skuId>:<\u4E00\u53E3\u4EF7>\uFF08\u5341\u8FDB\u5236\u6570\u5B57\uFF0C\u6700\u591A\u4E24\u4F4D\u5C0F\u6570\u3001\u4E0D\u8981\u524D\u5BFC\u96F6\uFF0C\u5982 25.42\uFF09\uFF0C\u591A\u9879\u7528\u9017\u53F7\u8FDE\u63A5\u3001\u5404\u9879\u4E4B\u95F4\u4E0D\u8981\u6709\u7A7A\u683C\uFF0C\u5982 --price 173707:29.90,173708:32.00\uFF1B\u6536\u5230\u300C${part}\u300D` };
1543
+ return flag === "percent" ? { ok: false, message: `--percent \u6BCF\u4E00\u9879\u5FC5\u987B\u662F <skuId>:<N> \u6216 <productId>/<skuId>:<N>\uFF0CN \u662F 1\u201399 \u7684\u6574\u6570\uFF0C\u8868\u793A\u300C\u51CF N%\u300D\uFF0885 = \u51CF 85%\uFF0C\u6309\u539F\u4EF7 15% \u5356\uFF1B\u5E97\u4E3B\u8BF4\u300C\u6253 85 \u6298\u300D\u8981\u4F20 15\uFF09\uFF1B\u6536\u5230\u300C${part}\u300D` } : { ok: false, message: `--price \u6BCF\u4E00\u9879\u5FC5\u987B\u662F <skuId>:<\u4E00\u53E3\u4EF7> \u6216 <productId>/<skuId>:<\u4E00\u53E3\u4EF7>\uFF08\u5341\u8FDB\u5236\u6570\u5B57\uFF0C\u6700\u591A\u4E24\u4F4D\u5C0F\u6570\u3001\u4E0D\u8981\u524D\u5BFC\u96F6\uFF0C\u5982 25.42\uFF09\uFF0C\u591A\u9879\u7528\u9017\u53F7\u8FDE\u63A5\u3001\u5404\u9879\u4E4B\u95F4\u4E0D\u8981\u6709\u7A7A\u683C\uFF0C\u5982 --price 173707:29.90,173708:32.00\uFF1B\u6536\u5230\u300C${part}\u300D` };
1478
1544
  }
1479
- const [, explicitPid, sid, intPart, frac] = m;
1545
+ const [, explicitPid, sid] = m;
1480
1546
  if (explicitPid && defaultProduct && explicitPid !== defaultProduct) {
1481
- return { ok: false, message: `--price \u7684\u300C${part}\u300D\u6307\u5B9A\u4E86\u5546\u54C1 ${explicitPid}\uFF0C\u4E0E --product ${defaultProduct} \u4E0D\u4E00\u81F4\uFF1B\u591A\u5546\u54C1\u8BF7\u628A --product \u53BB\u6389\u3001\u6BCF\u9879\u90FD\u5199\u6210 <productId>/<skuId>:<\u4EF7>` };
1547
+ return { ok: false, message: `--${flag} \u7684\u300C${part}\u300D\u6307\u5B9A\u4E86\u5546\u54C1 ${explicitPid}\uFF0C\u4E0E --product ${defaultProduct} \u4E0D\u4E00\u81F4\uFF1B\u591A\u5546\u54C1\u8BF7\u628A --product \u53BB\u6389\u3001\u6BCF\u9879\u90FD\u5199\u6210 <productId>/<skuId>:${unit}` };
1482
1548
  }
1483
1549
  const pid = explicitPid ?? defaultProduct;
1484
1550
  if (!pid) {
1485
- return { ok: false, message: `--price \u7684\u300C${part}\u300D\u6CA1\u5E26 productId\uFF0C\u800C\u4F60\u4E5F\u6CA1\u4F20 --product\uFF1B\u8981\u4E48\u52A0 --product\uFF0C\u8981\u4E48\u5199\u6210 <productId>/<skuId>:<\u4EF7>` };
1551
+ return { ok: false, message: `--${flag} \u7684\u300C${part}\u300D\u6CA1\u5E26 productId\uFF0C\u800C\u4F60\u4E5F\u6CA1\u4F20 --product\uFF1B\u8981\u4E48\u52A0 --product\uFF0C\u8981\u4E48\u5199\u6210 <productId>/<skuId>:${unit}` };
1552
+ }
1553
+ let amount;
1554
+ if (flag === "percent") {
1555
+ amount = m[3];
1556
+ } else {
1557
+ const [, , , intPart, frac] = m;
1558
+ amount = frac === void 0 ? intPart : `${intPart}.${frac}`;
1559
+ if (!(Number(amount) > 0)) return { ok: false, message: `--price \u7684\u4E00\u53E3\u4EF7\u5FC5\u987B\u5927\u4E8E 0\uFF1B\u6536\u5230\u300C${part}\u300D` };
1486
1560
  }
1487
- const amount = frac === void 0 ? intPart : `${intPart}.${frac}`;
1488
- if (!(Number(amount) > 0)) return { ok: false, message: `--price \u7684\u4E00\u53E3\u4EF7\u5FC5\u987B\u5927\u4E8E 0\uFF1B\u6536\u5230\u300C${part}\u300D` };
1489
- if (seen.has(sid)) return { ok: false, message: `--price \u91CC\u540C\u4E00\u4E2A skuId \u91CD\u590D\u51FA\u73B0\uFF1A${sid}` };
1561
+ if (seen.has(sid)) return { ok: false, message: `--${flag} \u91CC\u540C\u4E00\u4E2A skuId \u91CD\u590D\u51FA\u73B0\uFF1A${sid}` };
1490
1562
  seen.add(sid);
1491
1563
  items.push({ pid, sid, amount });
1492
1564
  }
@@ -1501,15 +1573,14 @@ function discountWindow(args2) {
1501
1573
  function discountActivityBody(args2, begin, end) {
1502
1574
  return {
1503
1575
  title: args2.title,
1504
- activity_type: "FIXED_PRICE",
1576
+ // 🔴 由常量变条件 —— 正是 #9 的根因形态(字段写死);P1/P2 钉 DD、P9 钉 FP 逐字不变
1577
+ activity_type: discountValueFlag(args2) === "percent" ? "DIRECT_DISCOUNT" : "FIXED_PRICE",
1505
1578
  product_level: "VARIATION",
1506
1579
  begin_time: begin,
1507
1580
  end_time: end
1508
1581
  };
1509
1582
  }
1510
- function discountProductsBody(args2) {
1511
- const parsed = parsePriceItems(args2.price ?? "", args2.product);
1512
- const items = parsed.ok ? parsed.items : [];
1583
+ function productsBodyFromItems(items, flag, productLimits) {
1513
1584
  const order = [];
1514
1585
  const byPid = /* @__PURE__ */ new Map();
1515
1586
  for (const it of items) {
@@ -1520,49 +1591,62 @@ function discountProductsBody(args2) {
1520
1591
  byPid.get(it.pid).push({ sid: it.sid, amount: it.amount });
1521
1592
  }
1522
1593
  return {
1523
- products: order.map((pid) => ({
1524
- id: pid,
1525
- quantity_limit: -1,
1526
- quantity_per_user: -1,
1527
- skus: byPid.get(pid).map((it) => ({
1528
- id: it.sid,
1529
- activity_price_amount: it.amount,
1530
- // 🔴 写侧裸串;读回叫 activity_price.amount(F4)
1531
- quantity_limit: -1,
1532
- quantity_per_user: -1
1533
- }))
1534
- }))
1594
+ products: order.map((pid) => {
1595
+ const [ql, qpu] = productLimits?.get(pid) ?? [-1, -1];
1596
+ return {
1597
+ id: pid,
1598
+ quantity_limit: ql,
1599
+ quantity_per_user: qpu,
1600
+ skus: byPid.get(pid).map((it) => flag === "percent" ? { id: it.sid, discount: it.amount, quantity_limit: -1, quantity_per_user: -1 } : { id: it.sid, activity_price_amount: it.amount, quantity_limit: -1, quantity_per_user: -1 })
1601
+ // 🔴 写侧裸串;读回叫 activity_price.amount
1602
+ };
1603
+ })
1535
1604
  };
1536
1605
  }
1606
+ function discountProductsBody(args2) {
1607
+ const flag = discountValueFlag(args2);
1608
+ const parsed = parsePriceItems(discountValueRaw(args2), args2.product, flag);
1609
+ return productsBodyFromItems(parsed.ok ? parsed.items : [], flag);
1610
+ }
1611
+ function discountItemsHuman(items, flag) {
1612
+ return items.map((it) => flag === "percent" ? `\u5546\u54C1 ${it.pid} \u7684 SKU ${it.sid} \u51CF ${it.amount}%\uFF08\u6309\u539F\u4EF7 ${100 - Number(it.amount)}% \u552E\u5356\uFF09` : `\u5546\u54C1 ${it.pid} \u7684 SKU ${it.sid} \u552E\u4EF7\u5B9A\u4E3A ${it.amount}`).join("\uFF1B");
1613
+ }
1537
1614
  function discountHuman(args2) {
1538
- const parsed = parsePriceItems(args2.price ?? "", args2.product);
1615
+ const flag = discountValueFlag(args2);
1616
+ const parsed = parsePriceItems(discountValueRaw(args2), args2.product, flag);
1539
1617
  const days = Number(args2.days ?? 180);
1540
- const list = parsed.ok ? parsed.items.map((it) => `\u5546\u54C1 ${it.pid} \u7684 SKU ${it.sid} \u552E\u4EF7\u5B9A\u4E3A ${it.amount}`).join("\uFF1B") : "\uFF08--price \u89E3\u6790\u5931\u8D25\uFF09";
1618
+ const list = parsed.ok ? discountItemsHuman(parsed.items, flag) : `\uFF08--${flag} \u89E3\u6790\u5931\u8D25\uFF09`;
1541
1619
  return `${list}\u3002\u539F\u4EF7\u5C06\u663E\u793A\u4E3A\u5212\u7EBF\u4EF7\u3002\u6D3B\u52A8\u5171 ${days} \u5929\uFF0C\u4ECE 15 \u5206\u949F\u540E\u5F00\u59CB \u2014\u2014 \u521A\u5EFA\u5B8C\u53BB\u5546\u54C1\u9875\u770B\u4EF7\u683C\u6CA1\u53D8\u662F\u6B63\u5E38\u7684\uFF0C\u90A3\u65F6\u6D3B\u52A8\u8FD8\u662F NOT_START\u3002`;
1542
1620
  }
1543
1621
  function validateCreateDiscount(ctx2) {
1544
1622
  const { args: args2, rest } = ctx2;
1545
1623
  for (const k of Object.keys(args2)) {
1546
1624
  if (DISCOUNT_FLAGS.includes(k)) continue;
1547
- if (k === "percent") {
1548
- return { ok: false, message: "\u672C\u7248\u4E0D\u652F\u6301\u767E\u5206\u6BD4\u6298\u6263\uFF08--percent\uFF09\u3002\u8BF7\u5148\u7528 tiktok products get --shop <id> --product <pid> \u53D6 SKU \u6302\u724C\u4EF7\uFF0C\u81EA\u884C\u6298\u7B97\uFF08\u4FDD\u7559\u4E24\u4F4D\u5C0F\u6570\uFF09\u540E\u8D70 --price <skuId>:<\u6298\u540E\u4EF7>" };
1549
- }
1550
1625
  if (k === "sku") {
1551
- return { ok: false, message: "\u672C\u547D\u4EE4\u6CA1\u6709 --sku\uFF1AskuId \u5199\u5728 --price \u91CC\uFF0C\u5982 --price 173707:29.90\uFF08\u6CA1\u4F20 --product \u65F6\u6BCF\u9879\u90FD\u8981\u5E26 productId\uFF0C\u5982 --price 1737048/173707:29.90\uFF09" };
1626
+ return "percent" in args2 ? { ok: false, message: "\u672C\u547D\u4EE4\u6CA1\u6709 --sku\uFF1AskuId \u5199\u5728 --percent \u91CC\uFF0C\u5982 --percent 173707:15\uFF08\u6CA1\u4F20 --product \u65F6\u6BCF\u9879\u90FD\u8981\u5E26 productId\uFF0C\u5982 --percent 1737048/173707:15\uFF09" } : { ok: false, message: "\u672C\u547D\u4EE4\u6CA1\u6709 --sku\uFF1AskuId \u5199\u5728 --price \u91CC\uFF0C\u5982 --price 173707:29.90\uFF08\u6CA1\u4F20 --product \u65F6\u6BCF\u9879\u90FD\u8981\u5E26 productId\uFF0C\u5982 --price 1737048/173707:29.90\uFF09" };
1552
1627
  }
1553
1628
  const own = DISCOUNT_FLAGS.filter((f) => !GLOBAL_FLAGS.includes(f));
1554
1629
  const hint = suggestFlag(k, own, args2[k], Date.now(), "promotions create-discount");
1555
1630
  const tip = !hint ? "" : hint.startsWith("--") ? `\uFF08\u4F60\u662F\u60F3\u7528 ${hint} \u5417\uFF1F\uFF09` : `\uFF08${hint}\uFF09`;
1556
1631
  return { ok: false, message: `\u4E0D\u652F\u6301\u7684\u53C2\u6570 --${k}${tip}\uFF1B\u672C\u547D\u4EE4\u53EA\u8BA4 ${DISCOUNT_FLAGS.map((f) => "--" + f).join(" ")}` };
1557
1632
  }
1633
+ const v3flag = "price" in args2 ? "price" : discountValueFlag(args2);
1558
1634
  if (rest && rest.length > 0) {
1559
- return { ok: false, message: `\u591A\u4F59\u7684\u4F4D\u7F6E\u53C2\u6570\u300C${rest.join(" ")}\u300D\u2014\u2014\u6BCF\u4E2A\u53C2\u6570\u53EA\u80FD\u5E26\u4E00\u4E2A\u503C\u3002\u591A\u4E2A SKU \u8981\u7528\u9017\u53F7\u8FDE\u6210\u4E00\u4E2A --price\uFF0C\u5982 --price A:12,B:15\uFF08\u4E0D\u80FD\u7528\u7A7A\u683C\u5206\u9694\uFF09\uFF1B\u82E5\u662F\u6807\u9898\u7B49\u503C\u91CC\u542B\u7A7A\u683C\uFF0C\u7ED9\u6574\u4E2A\u503C\u52A0\u5F15\u53F7\uFF0C\u5982 --title "Momo Sale"` };
1635
+ return { ok: false, message: `\u591A\u4F59\u7684\u4F4D\u7F6E\u53C2\u6570\u300C${rest.join(" ")}\u300D\u2014\u2014\u6BCF\u4E2A\u53C2\u6570\u53EA\u80FD\u5E26\u4E00\u4E2A\u503C\u3002\u591A\u4E2A SKU \u8981\u7528\u9017\u53F7\u8FDE\u6210\u4E00\u4E2A --${v3flag}\uFF0C\u5982 --${v3flag} A:12,B:15\uFF08\u4E0D\u80FD\u7528\u7A7A\u683C\u5206\u9694\uFF09\uFF1B\u82E5\u662F\u6807\u9898\u7B49\u503C\u91CC\u542B\u7A7A\u683C\uFF0C\u7ED9\u6574\u4E2A\u503C\u52A0\u5F15\u53F7\uFF0C\u5982 --title "Momo Sale"` };
1560
1636
  }
1561
- for (const k of ["title", "price"]) {
1562
- const v = args2[k];
1563
- if (!v || v === "true" || !v.trim()) {
1564
- return { ok: false, message: `--${k} \u5FC5\u586B\uFF0C\u4E14\u5FC5\u987B\u5E26\u503C\uFF08\u53EA\u5199 --${k} \u4E0D\u7ED9\u503C\u4F1A\u88AB\u89E3\u6790\u6210\u5B57\u7B26\u4E32 "true"\uFF0C\u5199\u6210 --${k}= \u5219\u662F\u7A7A\u503C\uFF0C\u4E24\u79CD\u90FD\u4E0D\u7B97\uFF09` };
1565
- }
1637
+ if ("price" in args2 && "percent" in args2) {
1638
+ return { ok: false, message: "--price \u4E0E --percent \u53EA\u80FD\u4E8C\u9009\u4E00\uFF1A\u4E00\u53E3\u4EF7\u7528 --price\uFF0C\u767E\u5206\u6BD4\u6298\u6263\u7528 --percent\u3002" };
1639
+ }
1640
+ if (!("price" in args2) && !("percent" in args2)) {
1641
+ return { ok: false, message: `--price \u5FC5\u586B\uFF08\u767E\u5206\u6BD4\u6298\u6263\u6539\u7528 --percent\uFF0C\u4E24\u8005\u4E8C\u9009\u4E00\uFF09\uFF0C\u4E14\u5FC5\u987B\u5E26\u503C\uFF08\u53EA\u5199 --price \u4E0D\u7ED9\u503C\u4F1A\u88AB\u89E3\u6790\u6210\u5B57\u7B26\u4E32 "true"\uFF0C\u5199\u6210 --price= \u5219\u662F\u7A7A\u503C\uFF0C\u4E24\u79CD\u90FD\u4E0D\u7B97\uFF09` };
1642
+ }
1643
+ if (!args2.title || args2.title === "true" || !args2.title.trim()) {
1644
+ return { ok: false, message: `--title \u5FC5\u586B\uFF0C\u4E14\u5FC5\u987B\u5E26\u503C\uFF08\u53EA\u5199 --title \u4E0D\u7ED9\u503C\u4F1A\u88AB\u89E3\u6790\u6210\u5B57\u7B26\u4E32 "true"\uFF0C\u5199\u6210 --title= \u5219\u662F\u7A7A\u503C\uFF0C\u4E24\u79CD\u90FD\u4E0D\u7B97\uFF09` };
1645
+ }
1646
+ const flag = discountValueFlag(args2);
1647
+ const val = flag === "percent" ? args2.percent : args2.price;
1648
+ if (!val || val === "true" || !val.trim()) {
1649
+ return { ok: false, message: `--${flag} \u5FC5\u586B\uFF0C\u4E14\u5FC5\u987B\u5E26\u503C\uFF08\u53EA\u5199 --${flag} \u4E0D\u7ED9\u503C\u4F1A\u88AB\u89E3\u6790\u6210\u5B57\u7B26\u4E32 "true"\uFF0C\u5199\u6210 --${flag}= \u5219\u662F\u7A7A\u503C\uFF0C\u4E24\u79CD\u90FD\u4E0D\u7B97\uFF09` };
1566
1650
  }
1567
1651
  if (args2.title !== args2.title.trim()) {
1568
1652
  return { ok: false, message: `--title \u9996\u5C3E\u4E0D\u80FD\u6709\u7A7A\u767D\uFF08\u6536\u5230\u300C${args2.title}\u300D\uFF09\uFF1A\u649E\u91CD\u540D\u65F6 CLI \u6309\u6807\u9898\u4E25\u683C\u76F8\u7B49\u67E5\u540C\u540D\u6D3B\u52A8\uFF0C\u5E73\u53F0\u82E5\u53BB\u6389\u9996\u5C3E\u7A7A\u767D\u5C31\u4F1A\u5BF9\u4E0D\u4E0A\u3002\u8BF7\u53BB\u6389\u9996\u5C3E\u7A7A\u767D\u540E\u91CD\u8DD1` };
@@ -1571,9 +1655,9 @@ function validateCreateDiscount(ctx2) {
1571
1655
  return { ok: false, message: `--product \u5FC5\u586B\uFF0C\u4E14\u5FC5\u987B\u5E26\u503C\uFF08\u53EA\u5199 --product \u4E0D\u7ED9\u503C\u4F1A\u88AB\u89E3\u6790\u6210\u5B57\u7B26\u4E32 "true"\uFF0C\u5199\u6210 --product= \u5219\u662F\u7A7A\u503C\uFF0C\u4E24\u79CD\u90FD\u4E0D\u7B97\uFF09` };
1572
1656
  }
1573
1657
  if (args2.product?.includes(",")) {
1574
- return { ok: false, message: "--product \u53EA\u6536\u4E00\u4E2A\u5546\u54C1 id\uFF1B\u591A\u5546\u54C1\u8BF7\u5728 --price \u91CC\u5199 <productId>/<skuId>:<\u4EF7>" };
1658
+ return { ok: false, message: `--product \u53EA\u6536\u4E00\u4E2A\u5546\u54C1 id\uFF1B\u591A\u5546\u54C1\u8BF7\u5728 --${flag} \u91CC\u5199 <productId>/<skuId>:${flag === "percent" ? "<\u51CFN%>" : "<\u4EF7>"}` };
1575
1659
  }
1576
- const parsed = parsePriceItems(args2.price, args2.product);
1660
+ const parsed = parsePriceItems(val, args2.product, flag);
1577
1661
  if (!parsed.ok) return parsed;
1578
1662
  if ("days" in args2) {
1579
1663
  const raw = args2.days;
@@ -1592,18 +1676,21 @@ var DISCOUNT_TEXT = {
1592
1676
  noCreateId: (title) => `\u5EFA\u58F3\u8FD4\u56DE\u6210\u529F\u4F46\u6CA1\u62FF\u5230 activity_id \u2014\u2014 \u26A0\uFE0F \u5E73\u53F0\u4E0A\u5F88\u53EF\u80FD\u5DF2\u7ECF\u6709\u8FD9\u6761\u58F3\u4E86\uFF08\u6807\u9898\u300C${title}\u300D\uFF09\u3002\u522B\u6362\u6807\u9898\uFF0C\u76F4\u63A5\u7528\u539F\u6807\u9898\u91CD\u8DD1\u539F\u547D\u4EE4\uFF1A\u91CD\u8DD1\u4F1A\u649E\u91CD\u540D\uFF0C\u90A3\u6761\u63D0\u793A\u4F1A\u544A\u8BC9\u4F60\u4E0B\u4E00\u6B65\u3002`,
1593
1677
  // ⑦ 撞重名后自查的六种结论(前面由调用方拼上平台原文 + 一个半角空格)
1594
1678
  dupSame: (title, aid, status) => `\u26A0\uFE0F \u540C\u540D\u6D3B\u52A8\u300C${title}\u300D\u5DF2\u5B58\u5728\uFF08\u6D3B\u52A8 ${aid}\uFF0C${status}\uFF09\uFF0C\u6302\u7684\u5546\u54C1\u4E0E\u672C\u6B21\u63D0\u4EA4\u5B8C\u5168\u4E00\u81F4\u2014\u2014\u8FD9\u5C31\u662F\u4F60\u8981\u7684\u6D3B\u52A8\uFF0C\u5DF2\u7ECF\u5EFA\u597D\u4E86\uFF1A\u522B\u91CD\u5EFA\u3002`,
1595
- dupEmpty: (title, aid, status, shop) => `\u26A0\uFE0F \u540C\u540D\u6D3B\u52A8\u300C${title}\u300D\u5DF2\u5B58\u5728\uFF08\u6D3B\u52A8 ${aid}\uFF0C${status}\uFF09\uFF0C\u4F46\u5B83\u662F\u4E00\u6761\u7A7A\u58F3\uFF08\u6CA1\u6709\u4EFB\u4F55\u5546\u54C1\uFF09\u2014\u2014\u5148\u6E05\u7406\uFF1A${deactivateCmd(shop, aid)}\uFF0C\u518D\u7528\u539F\u6807\u9898\u91CD\u8DD1\u539F\u547D\u4EE4\uFF1B\u91CD\u8DD1\u82E5\u53C8\u649E\u91CD\u540D\uFF0C\u6362\u4E2A\u6807\u9898\u3002`,
1679
+ dupEmpty: (title, aid, status, addCmd) => `\u26A0\uFE0F \u540C\u540D\u6D3B\u52A8\u300C${title}\u300D\u5DF2\u5B58\u5728\uFF08\u6D3B\u52A8 ${aid}\uFF0C${status}\uFF09\uFF0C\u4F46\u5B83\u662F\u4E00\u6761\u7A7A\u58F3\uFF08\u6CA1\u6709\u4EFB\u4F55\u5546\u54C1\uFF09\u2014\u2014\u76F4\u63A5\u628A\u5546\u54C1\u6302\u4E0A\u53BB\uFF1A${addCmd}\uFF1B\u522B\u91CD\u5EFA\u3002`,
1680
+ // v7 D4 / #36:否定分支 —— ⛔ 不指名具体哪一项(status 那格类型时长可能都对,写「类型或时长不符」是假原因);措辞由 create-discount 线二轮拍板
1681
+ dupEmptyOther: (title, aid, status, shop) => `\u26A0\uFE0F \u540C\u540D\u6D3B\u52A8\u300C${title}\u300D\u5DF2\u5B58\u5728\uFF08\u6D3B\u52A8 ${aid}\uFF0C${status}\uFF09\uFF0C\u5B83\u662F\u4E00\u6761\u7A7A\u58F3\uFF0C\u4F46\u4E0D\u662F\u4F60\u8FD9\u6B21\u8981\u5EFA\u7684\u90A3\u6761\uFF08\u7C7B\u578B\u3001\u65F6\u957F\u6216\u72B6\u6001\u6709\u51FA\u5165\uFF09\u2014\u2014\u522B\u5F80\u5B83\u91CC\u9762\u52A0\u54C1\uFF08\u6302\u8FDB\u53BB\u7684\u5546\u54C1\u4F1A\u8DDF\u7740\u5B83\u7684\u7C7B\u578B\u3001\u65F6\u957F\u548C\u751F\u6548\u72B6\u6001\u8D70\uFF0C\u4E0D\u662F\u4F60\u8FD9\u6B21\u8981\u7684\uFF09\uFF0C\u4E5F\u522B\u5148\u6E05\u7406\u518D\u7528\u539F\u6807\u9898\u91CD\u8DD1\uFF08\u6E05\u7406\u4E0D\u4F1A\u91CA\u653E\u6807\u9898\uFF09\uFF1A\u6362\u4E00\u4E2A\u6807\u9898\u91CD\u8DD1\u539F\u547D\u4EE4\uFF1B\u4E0D\u8981\u8FD9\u6761\u58F3\u4E86\u518D\u6E05\u7406\uFF1A${deactivateCmd(shop, aid)}\u3002`,
1596
1682
  dupOwner: (title, aid, status, problems) => `\u26A0\uFE0F \u540C\u540D\u6D3B\u52A8\u300C${title}\u300D\u5DF2\u5B58\u5728\uFF08\u6D3B\u52A8 ${aid}\uFF0C${status}\uFF09\uFF0C\u6302\u7684\u5546\u54C1\u4E0E\u672C\u6B21\u63D0\u4EA4\u4E0D\u4E00\u81F4\uFF1A${problems}\u2014\u2014\u522B\u52A8\u5B83\uFF0C\u4E5F\u5148\u522B\u91CD\u5EFA\uFF1A\u628A\u5B83\u544A\u8BC9\u5E97\u4E3B\uFF0C\u95EE\u662F\u5C31\u7528\u8FD9\u6761\uFF0C\u8FD8\u662F\u6362\u4E2A\u6807\u9898\u53E6\u5EFA\u3002`,
1597
1683
  dupRetitle: (title) => `\u26A0\uFE0F \u672A\u5F00\u59CB / \u8FDB\u884C\u4E2D\u7684\u6D3B\u52A8\u91CC\u6CA1\u6709\u300C${title}\u300D\u2014\u2014\u8FD9\u4E2A\u6807\u9898\u88AB\u4E00\u6761\u5DF2\u7EC8\u6B62\u6216\u5DF2\u8FC7\u671F\u7684\u6D3B\u52A8\u5360\u7740\uFF0C\u6362\u4E2A\u6807\u9898\u91CD\u8DD1\u539F\u547D\u4EE4\u3002`,
1598
1684
  dupUnclear: (title, queryError) => `\u26A0\uFE0F \u540C\u540D\u6D3B\u52A8\u300C${title}\u300D\u5DF2\u5B58\u5728\uFF0C\u4F46\u6CA1\u80FD\u67E5\u6E05\u5B83\u7684\u72B6\u6001\uFF08${queryError}\uFF09\u2014\u2014\u522B\u6362\u6807\u9898\u3001\u522B\u91CD\u5EFA\uFF0C\u7A0D\u540E\u7528\u539F\u6807\u9898\u91CD\u8DD1\u539F\u547D\u4EE4\u518D\u770B\uFF1B\u8FDE\u7EED\u4E24\u6B21\u90FD\u662F\u8FD9\u53E5\uFF0C\u5C31\u5982\u5B9E\u544A\u8BC9\u5E97\u4E3B\u300C\u6D3B\u52A8\u53EF\u80FD\u5DF2\u5EFA\uFF0C\u4F46\u67E5\u4E0D\u6E05\u300D\u3002`,
1599
1685
  dupMulti: (title, found) => `\u26A0\uFE0F \u672A\u5F00\u59CB / \u8FDB\u884C\u4E2D\u7684\u6D3B\u52A8\u91CC\u6709 ${found.length} \u6761\u53EB\u300C${title}\u300D\uFF08${found.map((a) => `${a.id}\uFF1A${a.status}`).join("\uFF1B")}\uFF09\u2014\u2014\u522B\u52A8\u5B83\u4EEC\uFF0C\u4E5F\u5148\u522B\u91CD\u5EFA\uFF1A\u628A\u5B83\u4EEC\u544A\u8BC9\u5E97\u4E3B\uFF0C\u95EE\u600E\u4E48\u5904\u7406\u3002`,
1600
1686
  // ⑧ 加品失败后按回读下结论
1601
- addFailAllAttached: (n, id) => `\u52A0\u5546\u54C1\u62A5\u4E86\u9519\uFF0C\u4F46\u56DE\u8BFB\u663E\u793A\u63D0\u4EA4\u7684 ${n} \u4E2A SKU \u90FD\u5DF2\u6309\u4EF7\u6302\u4E0A\uFF08\u6D3B\u52A8 ${id} \u7EA6 15 \u5206\u949F\u540E\u5F00\u59CB\uFF09\u2014\u2014\u522B\u6E05\u7406\uFF0C\u4E5F\u522B\u91CD\u8DD1\u672C\u547D\u4EE4\u3002`,
1602
- addFailNoneAttached: (shop, id) => `\u52A0\u5546\u54C1\u5931\u8D25\uFF0C\u56DE\u8BFB\u663E\u793A\u6CA1\u6709\u4EFB\u4F55\u5546\u54C1\u6302\u4E0A\u2014\u2014\u6D3B\u52A8 ${id} \u662F\u4E00\u6761\u7A7A\u58F3\u3002\u5148\u6E05\u7406\uFF1A${deactivateCmd(shop, id)}\uFF1B\u518D\u6309\u4E0A\u9762\u7684\u5E73\u53F0\u62A5\u9519\u6539\u6B63\u53C2\u6570\u540E\u91CD\u8DD1\u539F\u547D\u4EE4\uFF08\u53EA\u6709\u9650\u6D41\u3001\u7F51\u7EDC\u7C7B\u62A5\u9519\u624D\u539F\u6837\u91CD\u8DD1\uFF09\u3002\u522B\u76F4\u63A5\u91CD\u8DD1\uFF1A\u4F1A\u649E\u91CD\u540D\u6216\u518D\u5EFA\u4E00\u4E2A\u58F3\u3002`,
1603
- addFailPartial: (problems, shop, id) => `\u52A0\u5546\u54C1\u62A5\u4E86\u9519\uFF0C\u56DE\u8BFB\u663E\u793A\u6CA1\u6709\u5168\u90E8\u6302\u4E0A\uFF1A${problems}\u3002\u672C\u547D\u4EE4\u4E0D\u80FD\u5F80\u5DF2\u6709\u6D3B\u52A8\u8865\u5546\u54C1\uFF0C\u8BF7\u5E97\u4E3B\u4E8C\u9009\u4E00\uFF1A\u4FDD\u7559\u6D3B\u52A8 ${id}\uFF08\u7F3A\u7684 SKU \u4E0D\u6253\u6298\uFF0C\u4EF7\u683C\u4E0D\u7B26\u7684\u6309\u8BFB\u56DE\u7684\u4EF7\u683C\u751F\u6548\uFF09\uFF0C\u6216\u6E05\u7406\u540E\u6574\u6761\u91CD\u5EFA\uFF1A${deactivateCmd(shop, id)}\uFF0C\u6E05\u7406\u540E\u6309\u4E0A\u9762\u7684\u5E73\u53F0\u62A5\u9519\u6539\u6B63\u53C2\u6570\u518D\u91CD\u8DD1\u539F\u547D\u4EE4\u3002\u522B\u76F4\u63A5\u91CD\u8DD1\u672C\u547D\u4EE4\u3002`,
1604
- addFailReadbackFailed: (shop, id) => `\u52A0\u5546\u54C1\u5931\u8D25\uFF0C\u56DE\u8BFB\u4E5F\u5931\u8D25\u4E86\uFF0C\u67E5\u4E0D\u5230\u6D3B\u52A8 ${id} \u91CC\u5546\u54C1\u6709\u6CA1\u6709\u6302\u4E0A\u3002\u522B\u91CD\u8DD1\u672C\u547D\u4EE4\uFF08\u4F1A\u518D\u5EFA\u4E00\u4E2A\u58F3\uFF09\uFF0C\u4E5F\u5148\u522B\u6E05\u7406\uFF1A\u5982\u5B9E\u544A\u8BC9\u5E97\u4E3B\u300C\u6D3B\u52A8\u5DF2\u5EFA\uFF0C\u5546\u54C1\u662F\u5426\u6302\u4E0A\u672A\u80FD\u786E\u8BA4\u300D\uFF0C\u8BF7\u5E97\u4E3B\u5728\u5356\u5BB6\u540E\u53F0\u770B\u8FD9\u6761\u6D3B\u52A8\u91CC\u6709\u6CA1\u6709\u5546\u54C1\uFF1B\u786E\u8BA4\u6CA1\u6302\u4E0A\u518D\u6E05\u7406\uFF1A${deactivateCmd(shop, id)}\u3002`,
1687
+ addFailAllAttached: (n, id, dd = false) => `\u52A0\u5546\u54C1\u62A5\u4E86\u9519\uFF0C\u4F46\u56DE\u8BFB\u663E\u793A\u63D0\u4EA4\u7684 ${n} \u4E2A SKU \u90FD\u5DF2\u6309${dd ? "\u6298\u6263" : "\u4EF7"}\u6302\u4E0A\uFF08\u6D3B\u52A8 ${id} \u7EA6 15 \u5206\u949F\u540E\u5F00\u59CB\uFF09\u2014\u2014\u522B\u6E05\u7406\uFF0C\u4E5F\u522B\u91CD\u8DD1\u672C\u547D\u4EE4\u3002`,
1688
+ addFailNoneAttached: (shop, id, addCmd) => `\u52A0\u5546\u54C1\u5931\u8D25\uFF0C\u56DE\u8BFB\u663E\u793A\u6CA1\u6709\u4EFB\u4F55\u5546\u54C1\u6302\u4E0A\u2014\u2014\u6D3B\u52A8 ${id} \u662F\u4E00\u6761\u7A7A\u58F3\u3002\u522B\u91CD\u8DD1\u672C\u547D\u4EE4\uFF08\u4F1A\u649E\u91CD\u540D\u6216\u518D\u5EFA\u4E00\u4E2A\u58F3\uFF09\uFF1A\u6309\u4E0A\u9762\u7684\u5E73\u53F0\u62A5\u9519\u6539\u6B63\u53C2\u6570\u540E\uFF0C\u7528 ${addCmd} \u628A\u5546\u54C1\u6302\u5230\u8FD9\u6761\u6D3B\u52A8\u4E0A\uFF08\u53EA\u6709\u9650\u6D41\u3001\u7F51\u7EDC\u7C7B\u62A5\u9519\u624D\u539F\u6837\u91CD\u8BD5\uFF09\uFF1B\u4E0D\u6253\u7B97\u7528\u8FD9\u6761\u6D3B\u52A8\u4E86\u518D\u6E05\u7406\uFF1A${deactivateCmd(shop, id)}\u3002`,
1689
+ addFailPartial: (problems, shop, id, addCmd, dd = false) => `\u52A0\u5546\u54C1\u62A5\u4E86\u9519\uFF0C\u56DE\u8BFB\u663E\u793A\u6CA1\u6709\u5168\u90E8\u6302\u4E0A\uFF1A${problems}\u3002\u8BF7\u5E97\u4E3B\u4E09\u9009\u4E00\uFF1A\u8865\u6302\u7F3A\u7684 SKU\uFF1A${addCmd}\uFF08\u6309\u4E0A\u9762\u7684\u5E73\u53F0\u62A5\u9519\u6539\u6B63\u540E\u518D\u8865\uFF09\uFF1B\u4FDD\u7559\u6D3B\u52A8 ${id}\uFF08\u7F3A\u7684 SKU \u4E0D\u6253\u6298\uFF0C${dd ? "\u6298\u6263\u4E0D\u7B26\u7684\u6309\u8BFB\u56DE\u7684\u6298\u6263\u751F\u6548" : "\u4EF7\u683C\u4E0D\u7B26\u7684\u6309\u8BFB\u56DE\u7684\u4EF7\u683C\u751F\u6548"}\uFF09\uFF1B\u6216\u6E05\u7406\u540E\u6574\u6761\u91CD\u5EFA\uFF1A${deactivateCmd(shop, id)}\uFF0C\u6E05\u7406\u540E\u6309\u4E0A\u9762\u7684\u5E73\u53F0\u62A5\u9519\u6539\u6B63\u53C2\u6570\u3001\u6362\u4E00\u4E2A\u6807\u9898\u91CD\u8DD1\u539F\u547D\u4EE4\uFF08\u26A0\uFE0F deactivate \u4E0D\u91CA\u653E\u6807\u9898\uFF0C\u7528\u539F\u6807\u9898\u91CD\u8DD1\u5FC5\u649E\u91CD\u540D\uFF09\u3002\u522B\u76F4\u63A5\u91CD\u8DD1\u672C\u547D\u4EE4\u3002`,
1690
+ addFailPartialMismatch: (problems, shop, id, dd = false) => `\u52A0\u5546\u54C1\u62A5\u4E86\u9519\uFF0C\u56DE\u8BFB\u663E\u793A\u5546\u54C1\u90FD\u6302\u4E0A\u4E86\uFF0C\u4F46\u503C\u4E0E\u63D0\u4EA4\u4E0D\u4E00\u81F4\uFF1A${problems}\u3002\u8BF7\u5E97\u4E3B\u4E8C\u9009\u4E00\uFF1A\u4FDD\u7559\u6D3B\u52A8 ${id}\uFF08${dd ? "\u6298\u6263\u4E0D\u7B26\u7684\u6309\u8BFB\u56DE\u7684\u6298\u6263\u751F\u6548" : "\u4EF7\u683C\u4E0D\u7B26\u7684\u6309\u8BFB\u56DE\u7684\u4EF7\u683C\u751F\u6548"}\uFF09\uFF0C\u6216\u6E05\u7406\u540E\u6574\u6761\u91CD\u5EFA\uFF1A${deactivateCmd(shop, id)}\uFF0C\u6E05\u7406\u540E\u6309\u4E0A\u9762\u7684\u5E73\u53F0\u62A5\u9519\u6539\u6B63\u53C2\u6570\u3001\u6362\u4E00\u4E2A\u6807\u9898\u91CD\u8DD1\u539F\u547D\u4EE4\uFF08\u26A0\uFE0F deactivate \u4E0D\u91CA\u653E\u6807\u9898\uFF0C\u7528\u539F\u6807\u9898\u91CD\u8DD1\u5FC5\u649E\u91CD\u540D\uFF09\u3002\u522B\u76F4\u63A5\u91CD\u8DD1\u672C\u547D\u4EE4\u3002`,
1691
+ addFailReadbackFailed: (shop, id) => `\u52A0\u5546\u54C1\u5931\u8D25\uFF0C\u56DE\u8BFB\u4E5F\u5931\u8D25\u4E86\uFF0C\u67E5\u4E0D\u5230\u6D3B\u52A8 ${id} \u91CC\u5546\u54C1\u6709\u6CA1\u6709\u6302\u4E0A\u3002\u522B\u91CD\u8DD1\u672C\u547D\u4EE4\uFF08\u4F1A\u518D\u5EFA\u4E00\u4E2A\u58F3\uFF09\uFF0C\u4E5F\u5148\u522B\u6E05\u7406\uFF1A\u5148\u7528 \`tiktok promotions get --shop ${shop} --id ${id}\` \u6838\u5BF9\uFF1B\u4ECD\u67E5\u4E0D\u6E05\u5C31\u5982\u5B9E\u544A\u8BC9\u5E97\u4E3B\u300C\u6D3B\u52A8\u5DF2\u5EFA\uFF0C\u5546\u54C1\u662F\u5426\u6302\u4E0A\u672A\u80FD\u786E\u8BA4\u300D\uFF1B\u786E\u8BA4\u6CA1\u6302\u4E0A\u518D\u6E05\u7406\uFF1A${deactivateCmd(shop, id)}\u3002`,
1605
1692
  // ⑨ 成功路径的 warnings
1606
- readbackFailed: (id) => `\u6D3B\u52A8 ${id} \u5DF2\u5EFA\u6210\u3001\u5546\u54C1\u5DF2\u63D0\u4EA4\uFF0C\u4F46\u56DE\u8BFB\u5931\u8D25\uFF0C\u672A\u80FD\u9A8C\u8BC1\u5546\u54C1\u662F\u5426\u6302\u4E0A\u2014\u2014\u4E0D\u8981\u91CD\u5EFA\u6D3B\u52A8\uFF1B\u5982\u5B9E\u544A\u8BC9\u5E97\u4E3B\u300C\u5DF2\u5EFA\uFF0C\u5546\u54C1\u662F\u5426\u6302\u4E0A\u672A\u80FD\u786E\u8BA4\u300D\u3002`,
1693
+ readbackFailed: (shop, id) => `\u6D3B\u52A8 ${id} \u5DF2\u5EFA\u6210\u3001\u5546\u54C1\u5DF2\u63D0\u4EA4\uFF0C\u4F46\u56DE\u8BFB\u5931\u8D25\uFF0C\u672A\u80FD\u9A8C\u8BC1\u5546\u54C1\u662F\u5426\u6302\u4E0A\u2014\u2014\u4E0D\u8981\u91CD\u5EFA\u6D3B\u52A8\uFF1B\u5148\u7528 \`tiktok promotions get --shop ${shop} --id ${id}\` \u6838\u5BF9\uFF0C\u4ECD\u67E5\u4E0D\u6E05\u518D\u5982\u5B9E\u544A\u8BC9\u5E97\u4E3B\u300C\u5DF2\u5EFA\uFF0C\u5546\u54C1\u662F\u5426\u6302\u4E0A\u672A\u80FD\u786E\u8BA4\u300D\u3002`,
1607
1694
  readbackDiffers: (id, problems) => `\u6D3B\u52A8 ${id} \u5DF2\u5EFA\u6210\uFF0C\u4F46\u56DE\u8BFB\u4E0E\u63D0\u4EA4\u4E0D\u4E00\u81F4\uFF1A${problems}\u2014\u2014\u4E0D\u8981\u91CD\u5EFA\u6D3B\u52A8\uFF1B\u5982\u5B9E\u544A\u8BC9\u5E97\u4E3B\u54EA\u4E9B SKU \u672A\u786E\u8BA4\u6302\u4E0A\u3002`
1608
1695
  };
1609
1696
  var OBSERVED_REJECT_CODES = /* @__PURE__ */ new Set([
@@ -1637,9 +1724,10 @@ function createFailureVerdict(env2) {
1637
1724
  return "maybe_landed";
1638
1725
  }
1639
1726
  function submittedSkus(args2) {
1640
- return discountProductsBody(args2).products.flatMap((p) => p.skus.map((s) => ({ sid: s.id, amount: s.activity_price_amount })));
1727
+ const dd = discountValueFlag(args2) === "percent";
1728
+ return discountProductsBody(args2).products.flatMap((p) => p.skus.map((s) => ({ sid: s.id, amount: dd ? s.discount : s.activity_price_amount })));
1641
1729
  }
1642
- function compareReadback(submitted, readback) {
1730
+ function compareReadback(submitted, readback, field = "amount") {
1643
1731
  const products = readback?.products;
1644
1732
  if (!Array.isArray(products)) return null;
1645
1733
  const skus = products.flatMap((p) => Array.isArray(p?.skus) ? p.skus : []);
@@ -1658,17 +1746,19 @@ function compareReadback(submitted, readback) {
1658
1746
  diff.missing.push(sid);
1659
1747
  continue;
1660
1748
  }
1661
- const raw = hit.activity_price?.amount;
1749
+ const raw = field === "discount" ? hit.discount : hit.activity_price?.amount;
1662
1750
  const got = raw === void 0 || raw === null || raw === "" ? void 0 : String(raw);
1663
1751
  if (got === void 0 || Number(got) !== Number(amount)) diff.mismatched.push({ sid, submitted: amount, readback: got });
1664
1752
  }
1665
1753
  return diff;
1666
1754
  }
1667
- function problemsText(d) {
1755
+ function problemsText(d, field = "amount") {
1668
1756
  const parts = [];
1669
1757
  if (d.missing.length) parts.push(`\u7F3A ${d.missing.join("\u3001")}`);
1670
1758
  if (d.mismatched.length) {
1671
- parts.push(`\u91D1\u989D\u4E0D\u7B26 ${d.mismatched.map((m) => `${m.sid}\uFF08\u63D0\u4EA4 ${m.submitted}\uFF0C\u8BFB\u56DE ${m.readback ?? "\u65E0\u91D1\u989D"}\uFF09`).join("\u3001")}`);
1759
+ const label = field === "discount" ? "\u6298\u6263\u4E0D\u7B26" : "\u91D1\u989D\u4E0D\u7B26";
1760
+ const none = field === "discount" ? "\u65E0\u6298\u6263" : "\u65E0\u91D1\u989D";
1761
+ parts.push(`${label} ${d.mismatched.map((m) => `${m.sid}\uFF08\u63D0\u4EA4 ${m.submitted}\uFF0C\u8BFB\u56DE ${m.readback ?? none}\uFF09`).join("\u3001")}`);
1672
1762
  }
1673
1763
  return parts.join("\uFF1B");
1674
1764
  }
@@ -1678,7 +1768,7 @@ function mergeWarnings(existing, ours) {
1678
1768
  return [existing, ...ours];
1679
1769
  }
1680
1770
  var DUP_SEARCH_MAX_PAGES = 10;
1681
- async function dupSelfCheck(shop, title, submitted) {
1771
+ async function dupSelfCheck(shop, title, submitted, field, intent) {
1682
1772
  const found = /* @__PURE__ */ new Map();
1683
1773
  for (const status of ["NOT_START", "ONGOING"]) {
1684
1774
  let token;
@@ -1701,13 +1791,220 @@ async function dupSelfCheck(shop, title, submitted) {
1701
1791
  const [hit] = found.values();
1702
1792
  const g = await proxy("GET", shop, `/promotion/202309/activities/${hit.id}`, {});
1703
1793
  if (!g.ok) return DISCOUNT_TEXT.dupUnclear(title, g.error.message);
1704
- const d = compareReadback(submitted, g.data);
1794
+ const d = compareReadback(submitted, g.data, field);
1705
1795
  if (!d) return DISCOUNT_TEXT.dupUnclear(title, "\u56DE\u8BFB\u7F3A products \u5B57\u6BB5");
1796
+ const gd = g.data;
1797
+ const shellType = gd?.activity_type;
1798
+ const typeNote = (shellType === "FIXED_PRICE" || shellType === "DIRECT_DISCOUNT") && shellType !== intent.type ? `\u7C7B\u578B\u4E0D\u7B26\uFF1A\u540C\u540D\u6D3B\u52A8\u662F ${shellType}\uFF0C\u672C\u6B21 --${intent.flag} \u5BF9\u5E94 ${intent.type}\uFF08\u4E0B\u9762\u7684\u4E0D\u4E00\u81F4\u662F\u7C7B\u578B\u5DEE\u5F02\u3001\u4E0D\u662F\u503C\u5DEE\u5F02\uFF09\uFF1B` : "";
1706
1799
  if (d.missing.length === 0 && d.mismatched.length === 0 && d.total === submitted.length) {
1707
1800
  return DISCOUNT_TEXT.dupSame(title, hit.id, hit.status);
1708
1801
  }
1709
- if (d.productCount === 0) return DISCOUNT_TEXT.dupEmpty(title, hit.id, hit.status, shop);
1710
- return DISCOUNT_TEXT.dupOwner(title, hit.id, hit.status, problemsText(d) || `\u591A\u51FA ${d.extra.join("\u3001")}`);
1802
+ if (d.productCount === 0) {
1803
+ const fits = gd?.activity_type === intent.type && Number(gd?.end_time) - Number(gd?.begin_time) === intent.days * 86400 && gd?.status === "NOT_START";
1804
+ const echoStatus = String(gd?.status ?? hit.status);
1805
+ return fits ? DISCOUNT_TEXT.dupEmpty(title, hit.id, echoStatus, intent.addCmd(hit.id)) : DISCOUNT_TEXT.dupEmptyOther(title, hit.id, echoStatus, shop);
1806
+ }
1807
+ return DISCOUNT_TEXT.dupOwner(title, hit.id, hit.status, typeNote + (problemsText(d, field) || `\u591A\u51FA ${d.extra.join("\u3001")}`));
1808
+ }
1809
+ var GET_FLAGS = ["shop", "actor", "id"];
1810
+ function validateGet(ctx2) {
1811
+ const { args: args2, rest } = ctx2;
1812
+ for (const k of Object.keys(args2)) {
1813
+ if (k === "activity-id") return { ok: false, message: ADD_TEXT.AV2 };
1814
+ }
1815
+ for (const k of Object.keys(args2)) {
1816
+ if (GET_FLAGS.includes(k) || k === "help") continue;
1817
+ if (k === "apply") return { ok: false, message: "\u672C\u547D\u4EE4\u662F\u53EA\u8BFB\u547D\u4EE4\uFF0C\u6CA1\u6709 --apply\uFF08\u4E0D\u52A0 --apply \u4E5F\u4F1A\u76F4\u63A5\u8BFB\uFF09\uFF1B\u53BB\u6389\u5B83\u5373\u53EF" };
1818
+ const hint = suggestFlag(k, ["id"], args2[k], Date.now(), "promotions get");
1819
+ const tip = !hint ? "" : hint.startsWith("--") ? `\uFF08\u4F60\u662F\u60F3\u7528 ${hint} \u5417\uFF1F\uFF09` : `\uFF08${hint}\uFF09`;
1820
+ return { ok: false, message: `\u4E0D\u652F\u6301\u7684\u53C2\u6570 --${k}${tip}\uFF1B\u672C\u547D\u4EE4\u53EA\u8BA4 ${GET_FLAGS.map((f) => "--" + f).join(" ")}` };
1821
+ }
1822
+ if (rest && rest.length > 0) return { ok: false, message: ADD_TEXT.GV3(rest.join(" ")) };
1823
+ if (!args2.id || args2.id === "true" || !args2.id.trim()) {
1824
+ return { ok: false, message: `--id \u5FC5\u586B\uFF0C\u4E14\u5FC5\u987B\u5E26\u503C\uFF08\u6D3B\u52A8 id\uFF0C\u7EAF\u6570\u5B57\uFF0C\u5982 --id 7686081985965900001\uFF09` };
1825
+ }
1826
+ if (!/^\d+$/.test(args2.id)) return { ok: false, message: `--id \u5FC5\u987B\u662F\u7EAF\u6570\u5B57\u7684\u6D3B\u52A8 id\uFF08\u6536\u5230\u300C${args2.id}\u300D\uFF09` };
1827
+ return { ok: true };
1828
+ }
1829
+ async function runGet(shop, id) {
1830
+ return proxy("GET", shop, `/promotion/202309/activities/${id}`, {});
1831
+ }
1832
+ var ADD_PRODUCTS_FLAGS = ["shop", "apply", "actor", "id", "product", "price", "percent"];
1833
+ var ADD_ALLOWED_STATUS = ["NOT_START", "ONGOING"];
1834
+ var addProductsCmd = (shop, aid, product, flag, items, apply) => `\`tiktok promotions add-products --shop ${shop} --id ${aid}${product ? ` --product ${product}` : ""} --${flag} ${items.join(",")}${apply ? " --apply" : ""}\``;
1835
+ function rawItemsFor(raw, sids) {
1836
+ const want = new Set(sids);
1837
+ return raw.split(",").filter((part) => want.has(part.split(":")[0].split("/").pop() ?? ""));
1838
+ }
1839
+ function existingSkuValues(data, field) {
1840
+ const out2 = /* @__PURE__ */ new Map();
1841
+ const products = data?.products;
1842
+ for (const p of Array.isArray(products) ? products : []) {
1843
+ for (const s of Array.isArray(p?.skus) ? p.skus : []) {
1844
+ const raw = field === "discount" ? s?.discount : s?.activity_price?.amount;
1845
+ const id = String(s?.id);
1846
+ if (!out2.has(id)) out2.set(id, raw === void 0 || raw === null || raw === "" ? void 0 : String(raw));
1847
+ }
1848
+ }
1849
+ return out2;
1850
+ }
1851
+ function productLimitsOf(data) {
1852
+ const out2 = /* @__PURE__ */ new Map();
1853
+ const products = data?.products;
1854
+ for (const p of Array.isArray(products) ? products : []) {
1855
+ if (typeof p?.quantity_limit === "number" && typeof p?.quantity_per_user === "number") out2.set(String(p.id), [p.quantity_limit, p.quantity_per_user]);
1856
+ }
1857
+ return out2;
1858
+ }
1859
+ function lostText(pre, rb, field) {
1860
+ const parts = [];
1861
+ const rbProducts = Array.isArray(rb?.products) ? rb.products : [];
1862
+ const rbProd = new Map(rbProducts.map((p) => [String(p?.id), p]));
1863
+ const rbVals = existingSkuValues(rb, field);
1864
+ const rbHas = new Set(rbProducts.flatMap((p) => (Array.isArray(p?.skus) ? p.skus : []).map((s) => String(s?.id))));
1865
+ const preVals = existingSkuValues(pre, field);
1866
+ for (const p of Array.isArray(pre?.products) ? pre.products : []) {
1867
+ for (const s of Array.isArray(p?.skus) ? p.skus : []) {
1868
+ const sid = String(s?.id);
1869
+ if (!rbHas.has(sid)) {
1870
+ parts.push(`\u539F\u6709 ${sid} \u7F3A\u5931`);
1871
+ continue;
1872
+ }
1873
+ const was = preVals.get(sid), now = rbVals.get(sid);
1874
+ if (was !== void 0 && (now === void 0 || Number(now) !== Number(was))) parts.push(`\u539F\u6709 ${sid} \u503C\u53D8\u4E86\uFF08\u539F ${was}\uFF0C\u73B0 ${now ?? "\u65E0"}\uFF09`);
1875
+ }
1876
+ const q = rbProd.get(String(p?.id));
1877
+ if (!q) continue;
1878
+ if (typeof p?.quantity_limit === "number" && typeof p?.quantity_per_user === "number" && (q.quantity_limit !== p.quantity_limit || q.quantity_per_user !== p.quantity_per_user)) {
1879
+ parts.push(`\u539F\u6709\u5546\u54C1 ${p.id} \u9650\u8D2D\u53D8\u4E86\uFF08\u539F ${p.quantity_limit}/${p.quantity_per_user}\uFF0C\u73B0 ${q.quantity_limit}/${q.quantity_per_user}\uFF09`);
1880
+ }
1881
+ }
1882
+ return parts.join("\uFF1B");
1883
+ }
1884
+ function validateAddProducts(ctx2) {
1885
+ const { args: args2, rest } = ctx2;
1886
+ for (const k of Object.keys(args2)) {
1887
+ if (k === "activity-id") return { ok: false, message: ADD_TEXT.AV2 };
1888
+ }
1889
+ for (const k of Object.keys(args2)) {
1890
+ if (ADD_PRODUCTS_FLAGS.includes(k)) continue;
1891
+ const own = ADD_PRODUCTS_FLAGS.filter((f) => !GLOBAL_FLAGS.includes(f));
1892
+ const hint = suggestFlag(k, own, args2[k], Date.now(), "promotions add-products");
1893
+ const tip = !hint ? "" : hint.startsWith("--") ? `\uFF08\u4F60\u662F\u60F3\u7528 ${hint} \u5417\uFF1F\uFF09` : `\uFF08${hint}\uFF09`;
1894
+ return { ok: false, message: `\u4E0D\u652F\u6301\u7684\u53C2\u6570 --${k}${tip}\uFF1B\u672C\u547D\u4EE4\u53EA\u8BA4 ${ADD_PRODUCTS_FLAGS.map((f) => "--" + f).join(" ")}` };
1895
+ }
1896
+ const v3flag = "price" in args2 ? "price" : discountValueFlag(args2);
1897
+ if (rest && rest.length > 0) return { ok: false, message: ADD_TEXT.AV3(rest.join(" "), v3flag) };
1898
+ if ("price" in args2 && "percent" in args2) {
1899
+ return { ok: false, message: "--price \u4E0E --percent \u53EA\u80FD\u4E8C\u9009\u4E00\uFF1A\u4E00\u53E3\u4EF7\u7528 --price\uFF0C\u767E\u5206\u6BD4\u6298\u6263\u7528 --percent\u3002" };
1900
+ }
1901
+ if (!("price" in args2) && !("percent" in args2)) {
1902
+ return { ok: false, message: `--price \u5FC5\u586B\uFF08\u767E\u5206\u6BD4\u6298\u6263\u6539\u7528 --percent\uFF0C\u4E24\u8005\u4E8C\u9009\u4E00\uFF09\uFF0C\u4E14\u5FC5\u987B\u5E26\u503C\uFF08\u53EA\u5199 --price \u4E0D\u7ED9\u503C\u4F1A\u88AB\u89E3\u6790\u6210\u5B57\u7B26\u4E32 "true"\uFF0C\u5199\u6210 --price= \u5219\u662F\u7A7A\u503C\uFF0C\u4E24\u79CD\u90FD\u4E0D\u7B97\uFF09` };
1903
+ }
1904
+ if (!args2.id || args2.id === "true" || !args2.id.trim()) {
1905
+ return { ok: false, message: `--id \u5FC5\u586B\uFF0C\u4E14\u5FC5\u987B\u5E26\u503C\uFF08\u6D3B\u52A8 id\uFF0C\u7EAF\u6570\u5B57\uFF0C\u5982 --id 7686081985965900001\uFF09` };
1906
+ }
1907
+ if (!/^\d+$/.test(args2.id)) {
1908
+ return { ok: false, message: `--id \u5FC5\u987B\u662F\u7EAF\u6570\u5B57\u7684\u6D3B\u52A8 id\uFF08\u6536\u5230\u300C${args2.id}\u300D\uFF09` };
1909
+ }
1910
+ const flag = discountValueFlag(args2);
1911
+ const val = flag === "percent" ? args2.percent : args2.price;
1912
+ if (!val || val === "true" || !val.trim()) {
1913
+ return { ok: false, message: `--${flag} \u5FC5\u586B\uFF0C\u4E14\u5FC5\u987B\u5E26\u503C\uFF08\u53EA\u5199 --${flag} \u4E0D\u7ED9\u503C\u4F1A\u88AB\u89E3\u6790\u6210\u5B57\u7B26\u4E32 "true"\uFF0C\u5199\u6210 --${flag}= \u5219\u662F\u7A7A\u503C\uFF0C\u4E24\u79CD\u90FD\u4E0D\u7B97\uFF09` };
1914
+ }
1915
+ if ("product" in args2 && (!args2.product || args2.product === "true")) {
1916
+ return { ok: false, message: `--product \u5FC5\u586B\uFF0C\u4E14\u5FC5\u987B\u5E26\u503C\uFF08\u53EA\u5199 --product \u4E0D\u7ED9\u503C\u4F1A\u88AB\u89E3\u6790\u6210\u5B57\u7B26\u4E32 "true"\uFF0C\u5199\u6210 --product= \u5219\u662F\u7A7A\u503C\uFF0C\u4E24\u79CD\u90FD\u4E0D\u7B97\uFF09` };
1917
+ }
1918
+ if (args2.product?.includes(",")) {
1919
+ return { ok: false, message: `--product \u53EA\u6536\u4E00\u4E2A\u5546\u54C1 id\uFF1B\u591A\u5546\u54C1\u8BF7\u5728 --${flag} \u91CC\u5199 <productId>/<skuId>:${flag === "percent" ? "<\u51CFN%>" : "<\u4EF7>"}` };
1920
+ }
1921
+ const parsed = parsePriceItems(val, args2.product, flag);
1922
+ if (!parsed.ok) return parsed;
1923
+ return { ok: true };
1924
+ }
1925
+ function addProductsPlan(id, flag, raw, product) {
1926
+ const parsed = parsePriceItems(raw, product, flag);
1927
+ const items = parsed.ok ? parsed.items : [];
1928
+ return [
1929
+ { step: "1/3 \u9884\u8BFB\u6D3B\u52A8", method: "GET", path: `/promotion/202309/activities/${id}` },
1930
+ {
1931
+ step: flag === "percent" ? "2/3 \u52A0\u5546\u54C1+\u51CFN%" : "2/3 \u52A0\u5546\u54C1+\u4E00\u53E3\u4EF7",
1932
+ method: "PUT",
1933
+ path: `/promotion/202309/activities/${id}/products`,
1934
+ body: { activity_id: id, ...productsBodyFromItems(items, flag) },
1935
+ human: ADD_TEXT.HUMAN_ADD(id, discountItemsHuman(items, flag))
1936
+ },
1937
+ { step: "3/3 \u56DE\u8BFB", method: "GET", path: `/promotion/202309/activities/${id}` }
1938
+ ];
1939
+ }
1940
+ async function runAddProducts(shop, id, flag, raw, product) {
1941
+ const parsed = parsePriceItems(raw, product, flag);
1942
+ const items = parsed.ok ? parsed.items : [];
1943
+ const field = flag === "percent" ? "discount" : "amount";
1944
+ const submitted = items.map((it) => ({ sid: it.sid, amount: it.amount }));
1945
+ const reject = (message, meta) => ({ ok: false, error: { code: "VALIDATION", message, retryable: false }, ...meta ? { meta } : {} });
1946
+ const pre = await proxy("GET", shop, `/promotion/202309/activities/${id}`, {});
1947
+ if (!pre.ok) {
1948
+ const err = pre.error;
1949
+ const status = pre.meta?.http_status;
1950
+ const transient = status === void 0 || status >= 500 || err.code === "RATE_LIMITED";
1951
+ return { ...pre, error: { ...err, message: `${err.message} ${transient ? ADD_TEXT.AP1N(id) : ADD_TEXT.AP1(shop, id)}` } };
1952
+ }
1953
+ const d = pre.data ?? {};
1954
+ if (typeof d.activity_type !== "string" || !Array.isArray(d.products)) return reject(ADD_TEXT.AP1M(shop, id), pre.meta);
1955
+ const type = String(d.activity_type);
1956
+ if (type === "FIXED_PRICE" && flag === "percent") return reject(ADD_TEXT.AT1(id), pre.meta);
1957
+ if (type === "DIRECT_DISCOUNT" && flag === "price") return reject(ADD_TEXT.AT2(id), pre.meta);
1958
+ if (type === "FLASHSALE") return reject(ADD_TEXT.AT3(id), pre.meta);
1959
+ if (type !== "FIXED_PRICE" && type !== "DIRECT_DISCOUNT") return reject(ADD_TEXT.AT4(id, type), pre.meta);
1960
+ if (!ADD_ALLOWED_STATUS.includes(String(d.status))) return reject(ADD_TEXT.AS1(id, String(d.status), ADD_ALLOWED_STATUS.join(" / ")), pre.meta);
1961
+ if (d.product_level !== "VARIATION") return reject(ADD_TEXT.AL1(id, String(d.product_level)), pre.meta);
1962
+ const existing = existingSkuValues(d, field);
1963
+ const same = [], diff = [];
1964
+ const rest = [];
1965
+ for (const it of items) {
1966
+ if (!existing.has(it.sid)) {
1967
+ rest.push(it);
1968
+ continue;
1969
+ }
1970
+ const cur = existing.get(it.sid);
1971
+ if (cur !== void 0 && Number(cur) === Number(it.amount)) same.push(it.sid);
1972
+ else diff.push(it.sid);
1973
+ }
1974
+ const restCmd = addProductsCmd(shop, id, product, flag, rawItemsFor(raw, rest.map((r) => r.sid)), true);
1975
+ if (diff.length) return reject(ADD_TEXT.AD1(id, diff.join("\u3001")) + (rest.length ? ADD_TEXT.AD1_REST(restCmd) : ""), pre.meta);
1976
+ if (rest.length === 0) {
1977
+ return { ok: true, data: { activity_id: id, readback: pre.data, note: ADD_TEXT.AD3(items.length, id) }, meta: pre.meta };
1978
+ }
1979
+ if (same.length) return reject(ADD_TEXT.AD2(id, same.join("\u3001"), restCmd), pre.meta);
1980
+ const body = { activity_id: id, ...productsBodyFromItems(items, flag, productLimitsOf(d)) };
1981
+ const put = await proxy("PUT", shop, `/promotion/202309/activities/${id}/products`, { body });
1982
+ const rb = await proxy("GET", shop, `/promotion/202309/activities/${id}`, {});
1983
+ const readback = rb.ok ? rb.data : { error: rb.error };
1984
+ const c = rb.ok ? compareReadback(submitted, rb.data, field) : null;
1985
+ const lost = c ? lostText(d, rb.data, field) : "";
1986
+ const missingCmd = (apply) => addProductsCmd(shop, id, product, flag, rawItemsFor(raw, c?.missing ?? []), apply);
1987
+ if (!put.ok) {
1988
+ const { retry_after_s: _dropped, ...err } = put.error;
1989
+ let note;
1990
+ if (!c) note = ADD_TEXT.AN4(shop, id);
1991
+ else if (c.missing.length === 0 && c.mismatched.length === 0) note = ADD_TEXT.AN1(submitted.length, id);
1992
+ else if (c.missing.length === submitted.length) note = ADD_TEXT.AN2(id);
1993
+ else note = ADD_TEXT.AN3(problemsText(c, field)) + (c.missing.length ? ADD_TEXT.AN3_MISS(missingCmd(false)) : "") + (c.mismatched.length ? ADD_TEXT.AN3_MIS : "");
1994
+ if (lost) note += " " + ADD_TEXT.AW3(shop, id, lost);
1995
+ return { ok: false, error: { ...err, retryable: false }, data: { activity_id: id, readback, note }, meta: put.meta };
1996
+ }
1997
+ const ours = [];
1998
+ if (!c) ours.push(ADD_TEXT.AW1(shop, id));
1999
+ else {
2000
+ const problems = problemsText(c, field);
2001
+ if (problems) ours.push(ADD_TEXT.AW2(id, problems) + (c.missing.length ? ADD_TEXT.AW2_MISS(missingCmd(true)) : ""));
2002
+ if (lost) ours.push(ADD_TEXT.AW3(shop, id, lost));
2003
+ }
2004
+ const putData = put.data ?? {};
2005
+ const data = { ...putData, activity_id: id, readback };
2006
+ if (ours.length > 0) data.warnings = mergeWarnings(putData.warnings, ours);
2007
+ return { ok: true, data, meta: put.meta };
1711
2008
  }
1712
2009
 
1713
2010
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optima-chat/tiktok-cli",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "TikTok Shop CLI for Optima agents — 经 tiktok-backend 操作店铺",
5
5
  "type": "module",
6
6
  "bin": {