@optima-chat/tiktok-cli 0.15.0 → 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 +4 -1
  2. package/dist/index.js +371 -71
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -21,11 +21,12 @@ tiktok <domain> <verb> --shop <shopId> [--flags]
21
21
  | `auth link` | 生成店铺绑定授权链接(无需 --shop) |
22
22
  | `shops` | 查当前登录用户已授权店铺 |
23
23
  | `orders list` | 订单列表(`--status --page-size --since`) |
24
- | `orders get --id <id>` | 订单详情 |
24
+ | `orders get --id <id>[,<id>]` | 订单详情(多个用英文逗号连) |
25
25
  | `analytics shop` | 店铺经营数据(`--from --to`,默认近30天) |
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
@@ -134,8 +134,9 @@ function classify(status, body, shop) {
134
134
  if (status === 0 || body?.code === 0) {
135
135
  return { ok: true, data: body.data, meta };
136
136
  }
137
- if (status === 404) return { ok: false, error: { code: "SHOP_NOT_AUTHORIZED", message: body?.error ?? "\u5E97\u94FA\u672A\u6388\u6743", retryable: false }, meta };
138
- if (status === 403) return { ok: false, error: { code: "VALIDATION", message: body?.error ?? "\u8DEF\u5F84\u4E0D\u5141\u8BB8", retryable: false }, meta };
137
+ const isBackendEnvelope = typeof body?.code !== "number" && typeof body?.error === "string";
138
+ if (status === 404 && isBackendEnvelope) return { ok: false, error: { code: "SHOP_NOT_AUTHORIZED", message: body.error, retryable: false }, meta };
139
+ if (status === 403 && isBackendEnvelope) return { ok: false, error: { code: "VALIDATION", message: body.error, retryable: false }, meta };
139
140
  const tc = body?.code;
140
141
  const msg = body?.message ?? body?.error ?? `HTTP ${status}`;
141
142
  if (tc === 36009002 || status === 429) return { ok: false, error: { code: "RATE_LIMITED", message: msg, retryable: true, retry_after_s: 30 }, meta };
@@ -309,11 +310,12 @@ function commandUsage(cmd) {
309
310
  const flags = cmd.flags?.length ? cmd.flags.map((f) => `--${f}`).join(" ") : "\uFF08\u6CA1\u6709\u4E13\u5C5E\u53C2\u6570\uFF09";
310
311
  const shop = cmd.noShop ? "" : " --shop <shopId>";
311
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" : "";
312
314
  return `tiktok ${cmd.name}${shop} [--flags]
313
315
 
314
316
  ${cmd.summary}
315
317
 
316
- \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
317
319
  `;
318
320
  }
319
321
  var today = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -321,6 +323,37 @@ var daysAgo = (n) => new Date(Date.now() - n * 864e5).toISOString().slice(0, 10)
321
323
  var toEpoch = (v) => /^\d+$/.test(v) ? Number(v) : Math.floor(Date.parse(v) / 1e3);
322
324
  var PROMOTION_STATUSES = /* @__PURE__ */ new Set(["ONGOING", "NOT_START", "EXPIRED", "DEACTIVATED"]);
323
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
+ };
324
357
  var COMMANDS = [
325
358
  {
326
359
  name: "orders list",
@@ -346,9 +379,11 @@ var COMMANDS = [
346
379
  },
347
380
  {
348
381
  name: "orders get",
349
- summary: "\u8BA2\u5355\u8BE6\u60C5\uFF08--id \u5FC5\u586B\uFF09",
382
+ summary: "\u8BA2\u5355\u8BE6\u60C5\uFF08--id \u5FC5\u586B\uFF0C\u591A\u4E2A\u7528\u82F1\u6587\u9017\u53F7\u8FDE\uFF09",
350
383
  flags: ["id"],
351
- run: ({ shop, args: args2 }) => proxy("GET", shop, "/order/202309/orders/detail", { query: { ids: args2.id } })
384
+ // 🔴 Get Order Detail 是 GET /order/202309/orders?ids=a,b(#42:原来写成 …/orders/detail,
385
+ // 平台回 36009009 Invalid path;cn-stage 测试店 2026-09-20 实打本路径 200)
386
+ run: ({ shop, args: args2 }) => proxy("GET", shop, "/order/202309/orders", { query: { ids: args2.id } })
352
387
  },
353
388
  // ── 售后(退货/取消)只读 ──
354
389
  {
@@ -950,20 +985,26 @@ var COMMANDS = [
950
985
  },
951
986
  {
952
987
  name: "promotions create-discount",
953
- 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",
954
- 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"],
955
990
  write: true,
956
991
  validate: validateCreateDiscount,
957
992
  preview: ({ args: args2 }) => {
958
993
  const { begin, end } = discountWindow(args2);
959
994
  return [
960
995
  { step: "1/2 \u5EFA\u6D3B\u52A8\u58F3", method: "POST", path: "/promotion/202309/activities", body: discountActivityBody(args2, begin, end) },
961
- { 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) }
962
997
  ];
963
998
  },
964
999
  run: async ({ shop, args: args2 }) => {
965
1000
  const { begin, end } = discountWindow(args2);
966
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) };
967
1008
  const created = await proxy("POST", shop, "/promotion/202309/activities", { body: discountActivityBody(args2, begin, end) });
968
1009
  if (!created.ok) {
969
1010
  const err = created.error;
@@ -971,7 +1012,7 @@ var COMMANDS = [
971
1012
  case "maybe_landed":
972
1013
  return { ...created, error: { ...err, message: `${err.message} ${DISCOUNT_TEXT.maybeLanded}` } };
973
1014
  case "dup":
974
- 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)}` } };
975
1016
  case "rejected":
976
1017
  return created;
977
1018
  }
@@ -993,20 +1034,21 @@ var COMMANDS = [
993
1034
  if (!withProducts.ok) {
994
1035
  const { retry_after_s: _dropped, ...err } = withProducts.error;
995
1036
  let note;
996
- const c = rb.ok ? compareReadback(submitted, rb.data) : null;
1037
+ const c = rb.ok ? compareReadback(submitted, rb.data, field) : null;
997
1038
  if (!c) note = DISCOUNT_TEXT.addFailReadbackFailed(shop, activityId);
998
1039
  else {
999
- if (c.missing.length === 0 && c.mismatched.length === 0) note = DISCOUNT_TEXT.addFailAllAttached(submitted.length, activityId);
1000
- else if (c.missing.length === submitted.length && c.productCount === 0) note = DISCOUNT_TEXT.addFailNoneAttached(shop, activityId);
1001
- 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);
1002
1044
  }
1003
1045
  return { ok: false, error: { ...err, retryable: false }, data: { activity_id: activityId, readback, note }, meta: withProducts.meta };
1004
1046
  }
1005
1047
  const ours = [];
1006
- const diff = rb.ok ? compareReadback(submitted, rb.data) : null;
1007
- 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));
1008
1050
  else {
1009
- const problems = problemsText(diff);
1051
+ const problems = problemsText(diff, field);
1010
1052
  if (problems) ours.push(DISCOUNT_TEXT.readbackDiffers(activityId, problems));
1011
1053
  }
1012
1054
  const putData = withProducts.data ?? {};
@@ -1015,6 +1057,25 @@ var COMMANDS = [
1015
1057
  return { ok: true, data, meta: withProducts.meta };
1016
1058
  }
1017
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
+ },
1018
1079
  {
1019
1080
  name: "promotions deactivate",
1020
1081
  summary: "\u7EC8\u6B62/\u505C\u7528\u4FC3\u9500\u6D3B\u52A8\uFF08--id \u5FC5\u586B\uFF09\u3002\u4E0D\u53EF\u9006",
@@ -1463,27 +1524,41 @@ function findCommand(tokens) {
1463
1524
  }
1464
1525
  return null;
1465
1526
  }
1466
- 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
+ }
1467
1534
  var PRICE_ITEM = /^(?:([^:,\s/]+)\/)?([^:,\s/]+):(0|[1-9]\d*)(?:\.(\d{1,2}))?$/;
1468
- function parsePriceItems(raw, defaultProduct) {
1535
+ var PCT_ITEM = /^(?:([^:,\s/]+)\/)?([^:,\s/]+):([1-9]\d?)$/;
1536
+ function parsePriceItems(raw, defaultProduct, flag = "price") {
1469
1537
  const items = [];
1470
1538
  const seen = /* @__PURE__ */ new Set();
1539
+ const unit = flag === "percent" ? "<\u51CFN%>" : "<\u4EF7>";
1471
1540
  for (const part of raw.split(",")) {
1472
- const m = PRICE_ITEM.exec(part);
1541
+ const m = (flag === "percent" ? PCT_ITEM : PRICE_ITEM).exec(part);
1473
1542
  if (!m) {
1474
- 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` };
1475
1544
  }
1476
- const [, explicitPid, sid, intPart, frac] = m;
1545
+ const [, explicitPid, sid] = m;
1477
1546
  if (explicitPid && defaultProduct && explicitPid !== defaultProduct) {
1478
- 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}` };
1479
1548
  }
1480
1549
  const pid = explicitPid ?? defaultProduct;
1481
1550
  if (!pid) {
1482
- 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` };
1483
1560
  }
1484
- const amount = frac === void 0 ? intPart : `${intPart}.${frac}`;
1485
- 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
- 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}` };
1487
1562
  seen.add(sid);
1488
1563
  items.push({ pid, sid, amount });
1489
1564
  }
@@ -1498,15 +1573,14 @@ function discountWindow(args2) {
1498
1573
  function discountActivityBody(args2, begin, end) {
1499
1574
  return {
1500
1575
  title: args2.title,
1501
- activity_type: "FIXED_PRICE",
1576
+ // 🔴 由常量变条件 —— 正是 #9 的根因形态(字段写死);P1/P2 钉 DD、P9 钉 FP 逐字不变
1577
+ activity_type: discountValueFlag(args2) === "percent" ? "DIRECT_DISCOUNT" : "FIXED_PRICE",
1502
1578
  product_level: "VARIATION",
1503
1579
  begin_time: begin,
1504
1580
  end_time: end
1505
1581
  };
1506
1582
  }
1507
- function discountProductsBody(args2) {
1508
- const parsed = parsePriceItems(args2.price ?? "", args2.product);
1509
- const items = parsed.ok ? parsed.items : [];
1583
+ function productsBodyFromItems(items, flag, productLimits) {
1510
1584
  const order = [];
1511
1585
  const byPid = /* @__PURE__ */ new Map();
1512
1586
  for (const it of items) {
@@ -1517,49 +1591,62 @@ function discountProductsBody(args2) {
1517
1591
  byPid.get(it.pid).push({ sid: it.sid, amount: it.amount });
1518
1592
  }
1519
1593
  return {
1520
- products: order.map((pid) => ({
1521
- id: pid,
1522
- quantity_limit: -1,
1523
- quantity_per_user: -1,
1524
- skus: byPid.get(pid).map((it) => ({
1525
- id: it.sid,
1526
- activity_price_amount: it.amount,
1527
- // 🔴 写侧裸串;读回叫 activity_price.amount(F4)
1528
- quantity_limit: -1,
1529
- quantity_per_user: -1
1530
- }))
1531
- }))
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
+ })
1532
1604
  };
1533
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
+ }
1534
1614
  function discountHuman(args2) {
1535
- const parsed = parsePriceItems(args2.price ?? "", args2.product);
1615
+ const flag = discountValueFlag(args2);
1616
+ const parsed = parsePriceItems(discountValueRaw(args2), args2.product, flag);
1536
1617
  const days = Number(args2.days ?? 180);
1537
- 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`;
1538
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`;
1539
1620
  }
1540
1621
  function validateCreateDiscount(ctx2) {
1541
1622
  const { args: args2, rest } = ctx2;
1542
1623
  for (const k of Object.keys(args2)) {
1543
1624
  if (DISCOUNT_FLAGS.includes(k)) continue;
1544
- if (k === "percent") {
1545
- 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>" };
1546
- }
1547
1625
  if (k === "sku") {
1548
- 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" };
1549
1627
  }
1550
1628
  const own = DISCOUNT_FLAGS.filter((f) => !GLOBAL_FLAGS.includes(f));
1551
1629
  const hint = suggestFlag(k, own, args2[k], Date.now(), "promotions create-discount");
1552
1630
  const tip = !hint ? "" : hint.startsWith("--") ? `\uFF08\u4F60\u662F\u60F3\u7528 ${hint} \u5417\uFF1F\uFF09` : `\uFF08${hint}\uFF09`;
1553
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(" ")}` };
1554
1632
  }
1633
+ const v3flag = "price" in args2 ? "price" : discountValueFlag(args2);
1555
1634
  if (rest && rest.length > 0) {
1556
- 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"` };
1557
1636
  }
1558
- for (const k of ["title", "price"]) {
1559
- const v = args2[k];
1560
- if (!v || v === "true" || !v.trim()) {
1561
- 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` };
1562
- }
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` };
1563
1650
  }
1564
1651
  if (args2.title !== args2.title.trim()) {
1565
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` };
@@ -1568,9 +1655,9 @@ function validateCreateDiscount(ctx2) {
1568
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` };
1569
1656
  }
1570
1657
  if (args2.product?.includes(",")) {
1571
- 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>"}` };
1572
1659
  }
1573
- const parsed = parsePriceItems(args2.price, args2.product);
1660
+ const parsed = parsePriceItems(val, args2.product, flag);
1574
1661
  if (!parsed.ok) return parsed;
1575
1662
  if ("days" in args2) {
1576
1663
  const raw = args2.days;
@@ -1589,18 +1676,21 @@ var DISCOUNT_TEXT = {
1589
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`,
1590
1677
  // ⑦ 撞重名后自查的六种结论(前面由调用方拼上平台原文 + 一个半角空格)
1591
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`,
1592
- 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`,
1593
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`,
1594
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`,
1595
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`,
1596
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`,
1597
1686
  // ⑧ 加品失败后按回读下结论
1598
- 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`,
1599
- 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`,
1600
- 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`,
1601
- 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`,
1602
1692
  // ⑨ 成功路径的 warnings
1603
- 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`,
1604
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`
1605
1695
  };
1606
1696
  var OBSERVED_REJECT_CODES = /* @__PURE__ */ new Set([
@@ -1634,9 +1724,10 @@ function createFailureVerdict(env2) {
1634
1724
  return "maybe_landed";
1635
1725
  }
1636
1726
  function submittedSkus(args2) {
1637
- 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 })));
1638
1729
  }
1639
- function compareReadback(submitted, readback) {
1730
+ function compareReadback(submitted, readback, field = "amount") {
1640
1731
  const products = readback?.products;
1641
1732
  if (!Array.isArray(products)) return null;
1642
1733
  const skus = products.flatMap((p) => Array.isArray(p?.skus) ? p.skus : []);
@@ -1655,17 +1746,19 @@ function compareReadback(submitted, readback) {
1655
1746
  diff.missing.push(sid);
1656
1747
  continue;
1657
1748
  }
1658
- const raw = hit.activity_price?.amount;
1749
+ const raw = field === "discount" ? hit.discount : hit.activity_price?.amount;
1659
1750
  const got = raw === void 0 || raw === null || raw === "" ? void 0 : String(raw);
1660
1751
  if (got === void 0 || Number(got) !== Number(amount)) diff.mismatched.push({ sid, submitted: amount, readback: got });
1661
1752
  }
1662
1753
  return diff;
1663
1754
  }
1664
- function problemsText(d) {
1755
+ function problemsText(d, field = "amount") {
1665
1756
  const parts = [];
1666
1757
  if (d.missing.length) parts.push(`\u7F3A ${d.missing.join("\u3001")}`);
1667
1758
  if (d.mismatched.length) {
1668
- 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")}`);
1669
1762
  }
1670
1763
  return parts.join("\uFF1B");
1671
1764
  }
@@ -1675,7 +1768,7 @@ function mergeWarnings(existing, ours) {
1675
1768
  return [existing, ...ours];
1676
1769
  }
1677
1770
  var DUP_SEARCH_MAX_PAGES = 10;
1678
- async function dupSelfCheck(shop, title, submitted) {
1771
+ async function dupSelfCheck(shop, title, submitted, field, intent) {
1679
1772
  const found = /* @__PURE__ */ new Map();
1680
1773
  for (const status of ["NOT_START", "ONGOING"]) {
1681
1774
  let token;
@@ -1698,13 +1791,220 @@ async function dupSelfCheck(shop, title, submitted) {
1698
1791
  const [hit] = found.values();
1699
1792
  const g = await proxy("GET", shop, `/promotion/202309/activities/${hit.id}`, {});
1700
1793
  if (!g.ok) return DISCOUNT_TEXT.dupUnclear(title, g.error.message);
1701
- const d = compareReadback(submitted, g.data);
1794
+ const d = compareReadback(submitted, g.data, field);
1702
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` : "";
1703
1799
  if (d.missing.length === 0 && d.mismatched.length === 0 && d.total === submitted.length) {
1704
1800
  return DISCOUNT_TEXT.dupSame(title, hit.id, hit.status);
1705
1801
  }
1706
- if (d.productCount === 0) return DISCOUNT_TEXT.dupEmpty(title, hit.id, hit.status, shop);
1707
- 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 };
1708
2008
  }
1709
2009
 
1710
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.0",
3
+ "version": "0.16.0",
4
4
  "description": "TikTok Shop CLI for Optima agents — 经 tiktok-backend 操作店铺",
5
5
  "type": "module",
6
6
  "bin": {