@optima-chat/tiktok-cli 0.16.1 → 0.17.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 +25 -0
  2. package/dist/index.js +188 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -115,6 +115,31 @@ tiktok products set-price --shop <id> --product <pid> --sku <sid> --price 29.90
115
115
 
116
116
  **秒杀字段坑**(OAS 未标、实测撞出):商品级 `quantity_limit` 在 VARIATION 模式必须 `-1`(限量由 SKU 级控制);秒杀价须**低于该 SKU 近 30 天最低成交价**且**不超折扣上限**。这些复杂度被 CLI 吸收,agent 一次成型。
117
117
 
118
+ ## 写命令参数清单(`tiktok schema --json`)
119
+
120
+ 给 Optima COO 的「写入数值守门」用(#56;optima-coo 的设计文档 `2026-10-01-optima-coo-write-value-guard-design.md` §4.2):COO 放行一条 `tiktok … --apply` 之前,按这份清单把命令里的数换算成店主口径,再和店主批过的 playbook 核对。
121
+
122
+ ```bash
123
+ tiktok schema --json > tiktok-cli@$(node -p "require('./package.json').version").json # 不需要 --shop;输出裸 JSON,不包 {ok,data}
124
+ ```
125
+
126
+ - 覆盖每条 `write: true` 的命令;每个参数恰好落在一处:`params`(带钱的,写 `semantic` 和单位:`type` / `pair` / `factor` / `means` / `scale` / `currency_flag` / `currency_default`)、`subject`(作用对象)、`unchecked`(不是钱:id、标题、小时数、库存)、`opaque`(数值藏在文件里:`--body-file`)。每条命令都认的 `--shop` / `--actor` / `--apply` / `--help` 在顶层 `global_flags`。
127
+ - 标注写在 `src/commands.ts` 每条写命令的 `values` 上(和 `flags` 挨着),`src/schema.ts` 生成并自查:漏写、一个参数写两处、钱没写币种、折扣没写 `means` ⇒ 当场抛错;`tests/schema.test.mts` 钉着。**加写命令或参数时一起改 `values`。**
128
+ - 词表(`semantic`)以 optima-coo `packages/contracts/src/value-guard.ts` 的 `VALUE_SEMANTICS` 为准。
129
+ - `version` 取自 `package.json`。COO 按版本固定清单副本,**改了任何写命令的参数就要发新版本**,否则 COO 那边按旧清单核对。
130
+ - 没有 `repeatable` 的参数:同一个参数写两次 CLI 取后者,COO 按写法错误退回;多个值用逗号连成一个(`--price A:12,B:15`)。
131
+
132
+ 数值范围(validate 里查,dry-run 与 `--apply` 共用、在发请求之前):
133
+
134
+ | 参数 | 范围 |
135
+ |---|---|
136
+ | `affiliate open-collab set-commission --rate` | 800–8000 的整数(单位百分比×100:1350 = 13.5%,8%–80%);必填 |
137
+ | `affiliate open-collab settings edit --rate` | 给了就必须是 1–10000 的整数(不超过 100%) |
138
+ | `promotions create-flashsale --hours` | 大于 0、小于 72(平台硬上限) |
139
+ | `products set-price --price`、`promotions create-flashsale --price` | 大于 0 的十进制数,最多两位小数,不要前导零和币种符号;必填 |
140
+
141
+ dry-run 的 `plan[].human` 写换算后的店主口径:改价「SKU S 售价改为 29.90 MYR」、秒杀「秒杀价 9.90 MYR,限量 100 件……持续 48 小时」、佣金「佣金率设为 13.5%(--rate 1350)」、折扣「减 85%(按原价 15% 售卖)」。
142
+
118
143
  ## 错误分类(agent 靠此决策)
119
144
 
120
145
  `error.code` + `retryable` 让 agent 判断重试还是停止:
package/dist/index.js CHANGED
@@ -354,6 +354,13 @@ var ADD_TEXT = {
354
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
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
356
  };
357
+ var DISCOUNT_VALUE_PARAMS = {
358
+ // 一口价不带币种,平台按店铺本币收;本 CLI 只接马来站 ⇒ MYR。PRICE_ITEM 只收两位小数
359
+ price: { semantic: "promo_price", type: "pairs", pair: ["sku_id", "decimal"], scale: 2, currency_default: "MYR" },
360
+ // PCT_ITEM:1–99 的整数,「减 N%」(85 = 按原价 15% 卖)
361
+ percent: { semantic: "discount_pct", type: "pairs", pair: ["sku_id", "int"], means: "off" }
362
+ };
363
+ var COMMISSION_RATE = { semantic: "commission_pct", type: "int", factor: 0.01 };
357
364
  var COMMANDS = [
358
365
  {
359
366
  name: "orders list",
@@ -478,6 +485,7 @@ var COMMANDS = [
478
485
  summary: "\u6279\u51C6\u53D6\u6D88\u5355\uFF08--id cancel_id\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u6279\u3002\u4E0D\u53EF\u9006",
479
486
  flags: ["id"],
480
487
  write: true,
488
+ values: { unchecked: ["id"] },
481
489
  preview: ({ args: args2 }) => [{
482
490
  step: "\u6279\u51C6\u53D6\u6D88\u5355",
483
491
  method: "POST",
@@ -491,6 +499,7 @@ var COMMANDS = [
491
499
  summary: "\u9A73\u56DE\u53D6\u6D88\u5355\uFF08--id --reason\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u9A73\u3002\u4E0D\u53EF\u9006",
492
500
  flags: ["id", "reason"],
493
501
  write: true,
502
+ values: { unchecked: ["id", "reason"] },
494
503
  preview: ({ args: args2 }) => [{
495
504
  step: "\u9A73\u56DE\u53D6\u6D88\u5355",
496
505
  method: "POST",
@@ -509,6 +518,7 @@ var COMMANDS = [
509
518
  summary: "\u6279\u51C6\u9000\u8D27\u5355\uFF08--id return_id\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u6279\u3002\u4E0D\u53EF\u9006",
510
519
  flags: ["id"],
511
520
  write: true,
521
+ values: { unchecked: ["id"] },
512
522
  preview: ({ args: args2 }) => [{
513
523
  step: "\u6279\u51C6\u9000\u8D27\u5355",
514
524
  method: "POST",
@@ -522,6 +532,7 @@ var COMMANDS = [
522
532
  summary: "\u9A73\u56DE\u9000\u8D27\u5355\uFF08--id --reason\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u9A73\u3002\u4E0D\u53EF\u9006",
523
533
  flags: ["id", "reason"],
524
534
  write: true,
535
+ values: { unchecked: ["id", "reason"] },
525
536
  preview: ({ args: args2 }) => [{
526
537
  step: "\u9A73\u56DE\u9000\u8D27\u5355",
527
538
  method: "POST",
@@ -540,6 +551,7 @@ var COMMANDS = [
540
551
  summary: "\u4E3A\u8BA2\u5355\u5EFA\u5305\u88F9\uFF08--order id --body-file pkg.json\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u5EFA\u3002\u4EC5\u76F4\u53D1\u5546\u5BB6\uFF08\u7269\u6D41\u9ED8\u8BA4 ERP \u7BA1\uFF09",
541
552
  flags: ["body-file", "order"],
542
553
  write: true,
554
+ values: { unchecked: ["order"], opaque: ["body-file"] },
543
555
  preview: ({ args: args2 }) => [{
544
556
  step: "\u5EFA\u5305\u88F9",
545
557
  method: "POST",
@@ -558,6 +570,7 @@ var COMMANDS = [
558
570
  summary: "\u53D1\u8D27\uFF08--package id --body-file ship.json\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u53D1\u3002\u4E0D\u53EF\u9006\uFF0C\u4EC5\u76F4\u53D1\u5546\u5BB6",
559
571
  flags: ["body-file", "package"],
560
572
  write: true,
573
+ values: { unchecked: ["package"], opaque: ["body-file"] },
561
574
  preview: ({ args: args2 }) => [{
562
575
  step: "\u53D1\u8D27",
563
576
  method: "POST",
@@ -590,6 +603,7 @@ var COMMANDS = [
590
603
  summary: "\u6807\u4E8B\u4EF6\u5DF2\u5904\u7406\uFF08--ids id1,id2 \u6216 --before <ts>\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u6807",
591
604
  flags: ["before", "ids"],
592
605
  write: true,
606
+ values: { unchecked: ["before", "ids"] },
593
607
  preview: ({ args: args2 }) => [{
594
608
  step: "\u6807\u4E8B\u4EF6\u5DF2\u5904\u7406",
595
609
  method: "POST",
@@ -946,28 +960,45 @@ var COMMANDS = [
946
960
  // ===== 写操作(Phase 2):默认 --dry-run,--apply 才真发,成功后回读 =====
947
961
  {
948
962
  name: "products set-price",
949
- summary: "\u6539 SKU \u552E\u4EF7\uFF08--product --sku --price [--currency MYR]\uFF09\u3002\u5199\u64CD\u4F5C,\u9ED8\u8BA4 dry-run",
963
+ summary: "\u6539 SKU \u552E\u4EF7\uFF08--product --sku --price [--currency MYR]\uFF0C\u4EF7\u683C\u5927\u4E8E 0\u3001\u6700\u591A\u4E24\u4F4D\u5C0F\u6570\uFF09\u3002\u5199\u64CD\u4F5C,\u9ED8\u8BA4 dry-run",
950
964
  flags: ["currency", "price", "product", "sku"],
951
965
  write: true,
966
+ validate: validateSetPrice,
967
+ values: {
968
+ subject: { flag: "sku", kind: "sku_id" },
969
+ params: {
970
+ price: { semantic: "list_price", type: "decimal", scale: 2, currency_flag: "currency", currency_default: "MYR" },
971
+ currency: { semantic: "currency" }
972
+ },
973
+ unchecked: ["product"]
974
+ },
952
975
  preview: ({ args: args2 }) => [{
953
976
  step: "\u6539\u4EF7",
954
977
  method: "POST",
955
978
  path: `/product/202309/products/${args2.product}/prices/update`,
956
- body: priceBody(args2)
979
+ body: priceBody(args2),
980
+ human: priceHuman(args2)
957
981
  }],
958
982
  run: ({ shop, args: args2 }) => proxy("POST", shop, `/product/202309/products/${args2.product}/prices/update`, { body: priceBody(args2) }),
959
983
  readback: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/products/${args2.product}`, {})
960
984
  },
961
985
  {
962
986
  name: "promotions create-flashsale",
963
- summary: "\u5EFA\u79D2\u6740\u6D3B\u52A8\uFF08--title --product --sku --price [--limit] [--hours 48]\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u771F\u5EFA\u3002\u4E24\u6B65:\u5EFA\u58F3+\u52A0\u54C1",
987
+ summary: "\u5EFA\u79D2\u6740\u6D3B\u52A8\uFF08--title --product --sku --price [--limit] [--hours 48]\uFF1B--price \u5927\u4E8E 0\u3001\u6700\u591A\u4E24\u4F4D\u5C0F\u6570\uFF0C--hours \u5C0F\u4E8E 72\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u771F\u5EFA\u3002\u4E24\u6B65:\u5EFA\u58F3+\u52A0\u54C1",
964
988
  flags: ["hours", "limit", "per-user", "price", "product", "sku", "start-in-min", "title"],
965
989
  write: true,
990
+ validate: validateFlashsale,
991
+ values: {
992
+ subject: { flag: "sku", kind: "sku_id" },
993
+ // 秒杀价不带币种,平台按店铺本币收;本 CLI 只接马来站 ⇒ MYR
994
+ params: { price: { semantic: "promo_price", type: "decimal", scale: 2, currency_default: "MYR" } },
995
+ unchecked: ["hours", "limit", "per-user", "product", "start-in-min", "title"]
996
+ },
966
997
  preview: ({ args: args2 }) => {
967
998
  const { begin, end } = flashWindow(args2);
968
999
  return [
969
1000
  { step: "1/2 \u5EFA\u6D3B\u52A8\u58F3", method: "POST", path: "/promotion/202309/activities", body: flashActivityBody(args2, begin, end) },
970
- { step: "2/2 \u52A0\u5546\u54C1+\u79D2\u6740\u4EF7", method: "PUT", path: "/promotion/202309/activities/{activity_id}/products", body: flashProductsBody(args2) }
1001
+ { step: "2/2 \u52A0\u5546\u54C1+\u79D2\u6740\u4EF7", method: "PUT", path: "/promotion/202309/activities/{activity_id}/products", body: flashProductsBody(args2), human: flashHuman(args2) }
971
1002
  ];
972
1003
  },
973
1004
  run: async ({ shop, args: args2 }) => {
@@ -988,6 +1019,7 @@ var COMMANDS = [
988
1019
  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\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u771F\u5EFA\u3002\u4E24\u6B65:\u5EFA\u58F3+\u52A0\u54C1\uFF0C\u53EF\u591A\u5546\u54C1",
989
1020
  flags: ["days", "percent", "price", "product", "title"],
990
1021
  write: true,
1022
+ values: { params: DISCOUNT_VALUE_PARAMS, unchecked: ["days", "product", "title"] },
991
1023
  validate: validateCreateDiscount,
992
1024
  preview: ({ args: args2 }) => {
993
1025
  const { begin, end } = discountWindow(args2);
@@ -1062,6 +1094,7 @@ var COMMANDS = [
1062
1094
  summary: ADD_TEXT.SUM_ADD,
1063
1095
  flags: ["id", "percent", "price", "product"],
1064
1096
  write: true,
1097
+ values: { params: DISCOUNT_VALUE_PARAMS, unchecked: ["id", "product"] },
1065
1098
  validate: validateAddProducts,
1066
1099
  // 🔴 命令块只字面读 args.id / args.product,值旗标经 discountValueFlag / discountValueRaw(它们字面读 args.percent / args.price);
1067
1100
  // ⛔ 不得提到 discountHuman / validateCreateDiscount / discountActivityBody / discountWindow(它们读 days / title,本命令没有)
@@ -1081,6 +1114,7 @@ var COMMANDS = [
1081
1114
  summary: "\u7EC8\u6B62/\u505C\u7528\u4FC3\u9500\u6D3B\u52A8\uFF08--id \u5FC5\u586B\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u7EC8\u6B62\u3002\u4E0D\u53EF\u9006",
1082
1115
  flags: ["id"],
1083
1116
  write: true,
1117
+ values: { unchecked: ["id"] },
1084
1118
  preview: ({ args: args2 }) => [{ step: "\u7EC8\u6B62\u6D3B\u52A8", method: "POST", path: `/promotion/202309/activities/${args2.id}/deactivate`, body: {} }],
1085
1119
  run: ({ shop, args: args2 }) => proxy("POST", shop, `/promotion/202309/activities/${args2.id}/deactivate`, { body: {} })
1086
1120
  },
@@ -1089,6 +1123,7 @@ var COMMANDS = [
1089
1123
  summary: "\u6539 SKU \u5E93\u5B58\uFF08--product --sku --warehouse --quantity\uFF09\u3002\u5199\u64CD\u4F5C,\u9ED8\u8BA4 dry-run",
1090
1124
  flags: ["product", "quantity", "sku", "warehouse"],
1091
1125
  write: true,
1126
+ values: { unchecked: ["product", "quantity", "sku", "warehouse"] },
1092
1127
  preview: ({ args: args2 }) => [{
1093
1128
  step: "\u6539\u5E93\u5B58",
1094
1129
  method: "POST",
@@ -1103,6 +1138,7 @@ var COMMANDS = [
1103
1138
  summary: "\u5EFA\u54C1\uFF08--body-file [--apply]\uFF09\u3002dry-run \u8DD1 listing_check \u9884\u68C0\uFF1B\u5199\u64CD\u4F5C\u3002\u26A0\uFE0F --apply \u5373\u5EFA\u5B8C\u76F4\u63A5\u4E0A\u67B6\u9001\u5BA1\uFF1B\u7528\u6237\u8981\u5B58\u8349\u7A3F\u5C31\u505C\u624B\u95EE\u7528\u6237\uFF08#1958\uFF09",
1104
1139
  flags: ["body-file"],
1105
1140
  write: true,
1141
+ values: { opaque: ["body-file"] },
1106
1142
  preview: () => [{ step: "listing_check \u9884\u68C0 \u2192 \u5EFA\u54C1", method: "POST", path: "/product/202309/products/listing_check", body: void 0 }],
1107
1143
  precheck: ({ shop, args: args2 }) => proxy("POST", shop, "/product/202309/products/listing_check", { body: readBodyFile(args2["body-file"]) }),
1108
1144
  run: async ({ shop, args: args2 }) => {
@@ -1118,6 +1154,7 @@ var COMMANDS = [
1118
1154
  summary: "\u7ED9\u5546\u54C1\u52A0\u89C4\u683C SKU\uFF08--product --body-file skus-delta.json [--apply]\uFF09\u3002\u26A0\uFE0F --apply \u5373\u9001\u5BA1\u4E0A\u67B6\uFF1B\u7528\u6237\u8981\u5B58\u8349\u7A3F\u5C31\u505C\u624B\u95EE\u7528\u6237\uFF08#1958\uFF09",
1119
1155
  flags: ["body-file", "product"],
1120
1156
  write: true,
1157
+ values: { unchecked: ["product"], opaque: ["body-file"] },
1121
1158
  preview: () => [{ step: "\u8BFB\u73B0\u6709 skus \u2192 append \u2192 partial_edit\u3002\u26A0\uFE0F \u9ED8\u8BA4 save_mode=LISTING\uFF1A--apply \u5373\u9001\u5BA1\u4E0A\u67B6\uFF08DRAFT \u5546\u54C1\u4F1A\u88AB\u63D0\u4EA4\uFF09\uFF1B\u672C\u547D\u4EE4\u81EA\u6784 body\uFF0Cbody-file \u91CC\u7684 save_mode \u4E0D\u900F\u4F20\u3001\u5199\u4E86\u4E5F\u65E0\u6548\u3002\u82E5\u7528\u6237\u8981\u6C42\u5B58\u8349\u7A3F/\u522B\u63D0\u4EA4 \u2192 \u4E0D\u8981 --apply\uFF0C\u505C\u624B\u95EE\u7528\u6237\uFF08#1958\uFF09", method: "POST", path: "/product/202309/products/{id}/partial_edit" }],
1122
1159
  run: async ({ shop, args: args2 }) => {
1123
1160
  const cur = await proxy("GET", shop, `/product/202309/products/${args2.product}`, {});
@@ -1132,6 +1169,8 @@ var COMMANDS = [
1132
1169
  summary: "\u6574\u7EC4\u6362\u4E3B\u56FE\uFF08--product --body-file images.json [--apply]\uFF09\u3002\u26A0\uFE0F --apply \u5373\u9001\u5BA1\u4E0A\u67B6\uFF1B\u7528\u6237\u8981\u5B58\u8349\u7A3F\u5C31\u505C\u624B\u95EE\u7528\u6237\uFF08#1958\uFF09",
1133
1170
  flags: ["body-file", "product"],
1134
1171
  write: true,
1172
+ values: { unchecked: ["body-file", "product"] },
1173
+ // 只从文件里取 main_images,带不进价格
1135
1174
  preview: () => [{ step: "main_images \u6574\u7EC4\u66FF\u6362 \u2192 partial_edit\u3002\u26A0\uFE0F \u9ED8\u8BA4 save_mode=LISTING\uFF1A--apply \u5373\u9001\u5BA1\u4E0A\u67B6\uFF08DRAFT \u5546\u54C1\u4F1A\u88AB\u63D0\u4EA4\uFF09\uFF1B\u672C\u547D\u4EE4\u81EA\u6784 body\uFF0Cbody-file \u91CC\u7684 save_mode \u4E0D\u900F\u4F20\u3001\u5199\u4E86\u4E5F\u65E0\u6548\u3002\u82E5\u7528\u6237\u8981\u6C42\u5B58\u8349\u7A3F/\u522B\u63D0\u4EA4 \u2192 \u4E0D\u8981 --apply\uFF0C\u505C\u624B\u95EE\u7528\u6237\uFF08#1958\uFF09", method: "POST", path: "/product/202309/products/{id}/partial_edit" }],
1136
1175
  run: ({ shop, args: args2 }) => proxy("POST", shop, `/product/202309/products/${args2.product}/partial_edit`, { body: { main_images: readBodyFile(args2["body-file"]).main_images } }),
1137
1176
  readback: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/products/${args2.product}`, {})
@@ -1141,6 +1180,7 @@ var COMMANDS = [
1141
1180
  summary: "\u6539\u5546\u54C1\u5B57\u6BB5\uFF08--product --body-file patch.json [--apply]\uFF09\u3002\u26A0\uFE0F --apply \u5373\u9001\u5BA1\u4E0A\u67B6\uFF1B\u7528\u6237\u8981\u5B58\u8349\u7A3F\u5C31\u505C\u624B\u95EE\u7528\u6237\uFF08#1958\uFF09",
1142
1181
  flags: ["body-file", "product"],
1143
1182
  write: true,
1183
+ values: { unchecked: ["product"], opaque: ["body-file"] },
1144
1184
  preview: ({ args: args2 }) => [{ step: "partial_edit\uFF08\u5D4C\u5957\u5B57\u6BB5\u672A\u5217\u5B50\u9879\u4F1A\u88AB\u6E05\u7A7A\uFF09\u3002\u26A0\uFE0F \u9ED8\u8BA4 save_mode=LISTING\uFF1A--apply \u5373\u9001\u5BA1\u4E0A\u67B6\uFF08DRAFT \u5546\u54C1\u4F1A\u88AB\u63D0\u4EA4\uFF09\u3002\u82E5\u7528\u6237\u8981\u6C42\u5B58\u8349\u7A3F/\u522B\u63D0\u4EA4 \u2192 \u4E0D\u8981 --apply\uFF0C\u505C\u624B\u95EE\u7528\u6237\uFF08#1958\uFF09", method: "POST", path: `/product/202309/products/${args2.product}/partial_edit`, body: readBodyFile(args2["body-file"]) }],
1145
1185
  run: ({ shop, args: args2 }) => proxy("POST", shop, `/product/202309/products/${args2.product}/partial_edit`, { body: readBodyFile(args2["body-file"]) }),
1146
1186
  readback: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/products/${args2.product}`, {})
@@ -1248,6 +1288,7 @@ var COMMANDS = [
1248
1288
  summary: "\u53D1\u5B9A\u5411\u9080\u7EA6\uFF08--body-file collab.json\uFF0C\u542B creator_user_open_ids[]/products[]/free_sample_rule\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u53D1",
1249
1289
  flags: ["body-file"],
1250
1290
  write: true,
1291
+ values: { opaque: ["body-file"] },
1251
1292
  preview: ({ args: args2 }) => [{
1252
1293
  step: "\u53D1\u5B9A\u5411\u9080\u7EA6",
1253
1294
  method: "POST",
@@ -1267,6 +1308,7 @@ var COMMANDS = [
1267
1308
  summary: "\u6539\u5B9A\u5411\u9080\u7EA6\uFF08--id --body-file collab.json\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u63D0\u4EA4",
1268
1309
  flags: ["body-file", "id"],
1269
1310
  write: true,
1311
+ values: { unchecked: ["id"], opaque: ["body-file"] },
1270
1312
  preview: ({ args: args2 }) => [{
1271
1313
  step: "\u6539\u5B9A\u5411\u9080\u7EA6",
1272
1314
  method: "PUT",
@@ -1283,6 +1325,7 @@ var COMMANDS = [
1283
1325
  summary: "\u64A4\u56DE\u5B9A\u5411\u9080\u7EA6\uFF08--id \u5FC5\u586B\uFF0C\u64A4\u56DE\u5B89\u5168\u7F51\uFF09\u3002\u4E0D\u53EF\u9006\uFF0C\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u53D1",
1284
1326
  flags: ["id"],
1285
1327
  write: true,
1328
+ values: { unchecked: ["id"] },
1286
1329
  preview: ({ args: args2 }) => [{
1287
1330
  step: "\u64A4\u56DE\u5B9A\u5411\u9080\u7EA6",
1288
1331
  method: "DELETE",
@@ -1296,6 +1339,7 @@ var COMMANDS = [
1296
1339
  summary: "\u751F\u6210\u5B9A\u5411\u9080\u7EA6\u5206\u4EAB\u94FE\u63A5\uFF08--id \u5FC5\u586B\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u53D1",
1297
1340
  flags: ["id"],
1298
1341
  write: true,
1342
+ values: { unchecked: ["id"] },
1299
1343
  preview: ({ args: args2 }) => [{
1300
1344
  step: "\u751F\u6210\u9080\u7EA6\u94FE\u63A5",
1301
1345
  method: "POST",
@@ -1309,6 +1353,7 @@ var COMMANDS = [
1309
1353
  summary: "\u5F00\u8FBE\u4EBA\u79C1\u4FE1\u4F1A\u8BDD\uFF08--creator \u5FC5\u586B\uFF0Ccreator_open_id\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u53D1",
1310
1354
  flags: ["creator"],
1311
1355
  write: true,
1356
+ values: { unchecked: ["creator"] },
1312
1357
  preview: ({ args: args2 }) => [{
1313
1358
  step: "\u5F00\u79C1\u4FE1\u4F1A\u8BDD",
1314
1359
  method: "POST",
@@ -1324,6 +1369,7 @@ var COMMANDS = [
1324
1369
  summary: "\u7ED9\u8FBE\u4EBA\u53D1\u79C1\u4FE1\uFF08--conversation --content [--msg-type TEXT]\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u53D1",
1325
1370
  flags: ["content", "conversation", "msg-type"],
1326
1371
  write: true,
1372
+ values: { unchecked: ["content", "conversation", "msg-type"] },
1327
1373
  preview: ({ args: args2 }) => [{
1328
1374
  step: "\u53D1\u79C1\u4FE1",
1329
1375
  method: "POST",
@@ -1340,6 +1386,7 @@ var COMMANDS = [
1340
1386
  flags: ["id", "reject-reason", "result"],
1341
1387
  listKey: "sample_applications",
1342
1388
  write: true,
1389
+ values: { unchecked: ["id", "reject-reason", "result"] },
1343
1390
  preview: ({ args: args2 }) => [{
1344
1391
  step: "\u5BA1\u6279\u6837\u54C1\u7533\u8BF7",
1345
1392
  method: "POST",
@@ -1360,11 +1407,14 @@ var COMMANDS = [
1360
1407
  summary: "\u6309\u54C1\u8BBE\u516C\u5F00\u5408\u4F5C\u4F63\u91D1\u7387\uFF08--product product_id --rate\uFF0C\u5355\u4F4D=\u767E\u5206\u6BD4\xD7100\uFF0C\u5982 1350=13.5%\uFF0C\u8303\u56F4 800-8000\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u5B58",
1361
1408
  flags: ["product", "rate"],
1362
1409
  write: true,
1410
+ validate: validateCommissionRate,
1411
+ values: { subject: { flag: "product", kind: "product_id" }, params: { rate: COMMISSION_RATE } },
1363
1412
  preview: ({ args: args2 }) => [{
1364
1413
  step: "\u8BBE\u6309\u54C1\u516C\u5F00\u5408\u4F5C\u4F63\u91D1",
1365
1414
  method: "POST",
1366
1415
  path: "/affiliate_seller/202412/open_collaborations",
1367
- body: { product_id: args2.product, commission_rate: Number(args2.rate) }
1416
+ body: { product_id: args2.product, commission_rate: Number(args2.rate) },
1417
+ human: `\u5546\u54C1 ${args2.product} \u7684\u516C\u5F00\u5408\u4F5C\u4F63\u91D1\u7387\u8BBE\u4E3A ${rateHuman(args2.rate)}`
1368
1418
  }],
1369
1419
  run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202412/open_collaborations", {
1370
1420
  body: { product_id: args2.product, commission_rate: Number(args2.rate) }
@@ -1379,6 +1429,7 @@ var COMMANDS = [
1379
1429
  summary: "\u79FB\u9664\u54C1\u7684\u516C\u5F00\u5408\u4F5C\uFF08--product product_id\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u53D1",
1380
1430
  flags: ["product"],
1381
1431
  write: true,
1432
+ values: { unchecked: ["product"] },
1382
1433
  preview: ({ args: args2 }) => [{
1383
1434
  step: "\u79FB\u9664\u516C\u5F00\u5408\u4F5C",
1384
1435
  method: "DELETE",
@@ -1389,14 +1440,17 @@ var COMMANDS = [
1389
1440
  },
1390
1441
  {
1391
1442
  name: "affiliate open-collab settings edit",
1392
- summary: "\u6539\u516C\u5F00\u5408\u4F5C\u5168\u5C40\u9ED8\u8BA4\uFF08\u81EA\u52A8\u65B0\u589E\u54C1\u4F63\u91D1\uFF0C--rate --enable true|false\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u5B58",
1443
+ summary: "\u6539\u516C\u5F00\u5408\u4F5C\u5168\u5C40\u9ED8\u8BA4\uFF08\u81EA\u52A8\u65B0\u589E\u54C1\u4F63\u91D1\uFF0C--rate --enable true|false\uFF1B--rate \u5355\u4F4D=\u767E\u5206\u6BD4\xD7100\uFF0C1\u201310000 \u7684\u6574\u6570\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u5B58",
1393
1444
  flags: ["enable", "rate"],
1394
1445
  write: true,
1446
+ validate: validateSettingsRate,
1447
+ values: { params: { rate: COMMISSION_RATE }, unchecked: ["enable"] },
1395
1448
  preview: ({ args: args2 }) => [{
1396
1449
  step: "\u6539\u5168\u5C40\u9ED8\u8BA4",
1397
1450
  method: "POST",
1398
1451
  path: "/affiliate_seller/202405/open_collaboration_settings",
1399
- body: { auto_add_product: { commission_rate: Number(args2.rate), enable: args2.enable === "true" } }
1452
+ body: { auto_add_product: { commission_rate: Number(args2.rate), enable: args2.enable === "true" } },
1453
+ human: `\u65B0\u4E0A\u67B6\u7684\u5546\u54C1${args2.enable === "true" ? "\u81EA\u52A8\u52A0\u5165" : "\u4E0D\u81EA\u52A8\u52A0\u5165"}\u516C\u5F00\u5408\u4F5C\uFF0C\u9ED8\u8BA4\u4F63\u91D1\u7387 ${rateHuman(args2.rate)}`
1400
1454
  }],
1401
1455
  run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202405/open_collaboration_settings", {
1402
1456
  body: { auto_add_product: { commission_rate: Number(args2.rate), enable: args2.enable === "true" } }
@@ -1515,6 +1569,42 @@ function priceBody(args2) {
1515
1569
  skus: [{ id: args2.sku, price: { amount: args2.price, currency: args2.currency ?? "MYR" } }]
1516
1570
  };
1517
1571
  }
1572
+ var PRICE_VALUE = /^(0|[1-9]\d*)(\.\d{1,2})?$/;
1573
+ var priceOk = (raw) => raw !== void 0 && PRICE_VALUE.test(raw) && Number(raw) > 0;
1574
+ var priceMessage = (raw) => `--price \u5FC5\u987B\u662F\u5927\u4E8E 0 \u7684\u5341\u8FDB\u5236\u6570\uFF0C\u6700\u591A\u4E24\u4F4D\u5C0F\u6570\u3001\u4E0D\u8981\u524D\u5BFC\u96F6\u548C\u5E01\u79CD\u7B26\u53F7\uFF08\u5982 29.90\uFF09\uFF1B\u6536\u5230\u300C${raw ?? "\uFF08\u6CA1\u4F20\uFF09"}\u300D`;
1575
+ function validateSetPrice({ args: args2 }) {
1576
+ return priceOk(args2.price) ? { ok: true } : { ok: false, message: priceMessage(args2.price) };
1577
+ }
1578
+ function validateFlashsale({ args: args2 }) {
1579
+ if (!priceOk(args2.price)) return { ok: false, message: priceMessage(args2.price) };
1580
+ if (args2.hours !== void 0) {
1581
+ const n = Number(args2.hours);
1582
+ if (!/^\d+(\.\d+)?$/.test(args2.hours) || !(n > 0 && n < 72)) {
1583
+ return { ok: false, message: `--hours \u662F\u79D2\u6740\u6301\u7EED\u7684\u5C0F\u65F6\u6570\uFF0C\u5FC5\u987B\u5927\u4E8E 0\u3001\u5C0F\u4E8E 72\uFF08\u5E73\u53F0\u786C\u4E0A\u9650\uFF0C\u9ED8\u8BA4 48\uFF09\uFF1B\u6536\u5230\u300C${args2.hours}\u300D` };
1584
+ }
1585
+ }
1586
+ return { ok: true };
1587
+ }
1588
+ function rateProblem(raw, min, max) {
1589
+ if (raw !== void 0 && /^\d+$/.test(raw) && Number(raw) >= min && Number(raw) <= max) return null;
1590
+ return `--rate \u7684\u5355\u4F4D\u662F\u300C\u767E\u5206\u6BD4\xD7100\u300D\uFF081350 = 13.5%\uFF09\uFF0C\u5FC5\u987B\u662F ${min}\u2013${max} \u7684\u6574\u6570\uFF08${min / 100}%\u2013${max / 100}%\uFF09\uFF1B\u6536\u5230\u300C${raw ?? "\uFF08\u6CA1\u4F20\uFF09"}\u300D\u3002\u5E97\u4E3B\u8BF4\u300C\u4F63\u91D1 13.5%\u300D\u8981\u4F20 1350`;
1591
+ }
1592
+ function validateCommissionRate({ args: args2 }) {
1593
+ const p = rateProblem(args2.rate, 800, 8e3);
1594
+ return p ? { ok: false, message: p } : { ok: true };
1595
+ }
1596
+ function validateSettingsRate({ args: args2 }) {
1597
+ if (args2.rate === void 0) return { ok: true };
1598
+ const p = rateProblem(args2.rate, 1, 1e4);
1599
+ return p ? { ok: false, message: p } : { ok: true };
1600
+ }
1601
+ function priceHuman(args2) {
1602
+ return `\u5546\u54C1 ${args2.product} \u7684 SKU ${args2.sku} \u552E\u4EF7\u6539\u4E3A ${args2.price} ${args2.currency ?? "MYR"}`;
1603
+ }
1604
+ function flashHuman(args2) {
1605
+ return `\u5546\u54C1 ${args2.product} \u7684 SKU ${args2.sku} \u79D2\u6740\u4EF7 ${args2.price} MYR\uFF0C\u9650\u91CF ${args2.limit ?? 100} \u4EF6\u3001\u6BCF\u4EBA\u9650\u8D2D ${args2["per-user"] ?? 1} \u4EF6\uFF1B${args2["start-in-min"] ?? 15} \u5206\u949F\u540E\u5F00\u59CB\uFF0C\u6301\u7EED ${args2.hours ?? 48} \u5C0F\u65F6`;
1606
+ }
1607
+ var rateHuman = (raw) => `${Number(raw) / 100}%\uFF08--rate ${raw}\uFF09`;
1518
1608
  function findCommand(tokens) {
1519
1609
  for (const cmd of [...COMMANDS].sort((a, b) => b.name.length - a.name.length)) {
1520
1610
  const parts = cmd.name.split(" ");
@@ -2007,6 +2097,86 @@ async function runAddProducts(shop, id, flag, raw, product) {
2007
2097
  return { ok: true, data, meta: put.meta };
2008
2098
  }
2009
2099
 
2100
+ // src/schema.ts
2101
+ import { readFileSync as readFileSync3 } from "fs";
2102
+ var VALUE_SEMANTICS = [
2103
+ "list_price",
2104
+ // SKU 挂牌价
2105
+ "promo_price",
2106
+ // 促销售价:折扣一口价、秒杀价
2107
+ "campaign_price",
2108
+ // 平台活动报价
2109
+ "discount_pct",
2110
+ // 降幅百分数(means 说是「减 N%」还是「按 N% 卖」)
2111
+ "ad_target_roi",
2112
+ // GMV Max 目标 ROI
2113
+ "ad_daily_budget",
2114
+ // GMV Max 日预算
2115
+ "commission_pct",
2116
+ // 达人佣金百分数
2117
+ "currency"
2118
+ // 同一条写里金额的币种
2119
+ ];
2120
+ var MONEY = ["list_price", "promo_price", "campaign_price", "ad_daily_budget"];
2121
+ function cliVersion() {
2122
+ return JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8")).version;
2123
+ }
2124
+ var dash = (f) => `--${f}`;
2125
+ var sorted = (xs) => [...xs].sort().map(dash);
2126
+ function checkParam(where, p, params, problems) {
2127
+ if (!VALUE_SEMANTICS.includes(p.semantic)) problems.push(`${where}\uFF1Asemantic ${JSON.stringify(p.semantic)} \u4E0D\u5728\u8BCD\u8868\u91CC`);
2128
+ if (p.semantic === "currency") {
2129
+ if (Object.keys(p).some((k) => k !== "semantic" && k !== "type") || p.type !== void 0 && p.type !== "string") problems.push(`${where}\uFF1A\u5E01\u79CD\u53C2\u6570\u53EA\u5199 semantic`);
2130
+ return;
2131
+ }
2132
+ if (MONEY.includes(p.semantic) && p.currency_flag === void 0 && p.currency_default === void 0) problems.push(`${where}\uFF1A\u94B1\u8981\u5199\u5E01\u79CD\uFF08currency_flag \u6216 currency_default\uFF09`);
2133
+ if (!MONEY.includes(p.semantic) && (p.currency_flag !== void 0 || p.currency_default !== void 0)) problems.push(`${where}\uFF1A\u53EA\u6709\u91D1\u989D\u624D\u6709\u5E01\u79CD`);
2134
+ if (p.semantic === "discount_pct" !== (p.means !== void 0)) problems.push(`${where}\uFF1A\u964D\u5E45\u5FC5\u987B\u5199 means\uFF0C\u522B\u7684\u4E0D\u8BB8\u5199`);
2135
+ if (p.round !== void 0 && p.scale === void 0) problems.push(`${where}\uFF1Around \u8981\u548C scale \u4E00\u8D77\u5199`);
2136
+ if (p.type === "pairs" !== (p.pair !== void 0)) problems.push(`${where}\uFF1Apairs \u548C pair \u8981\u4E00\u8D77\u5199`);
2137
+ if (p.factor !== void 0 && !(Number.isFinite(p.factor) && p.factor > 0)) problems.push(`${where}\uFF1Afactor \u8981\u662F\u6B63\u6570`);
2138
+ if (p.currency_flag !== void 0 && params[p.currency_flag]?.semantic !== "currency") problems.push(`${where}\uFF1Acurrency_flag ${p.currency_flag} \u4E0D\u662F\u672C\u547D\u4EE4\u7684\u5E01\u79CD\u53C2\u6570`);
2139
+ }
2140
+ function buildSchema(commands, version) {
2141
+ const problems = [];
2142
+ const out2 = { tool: "tiktok-cli", version, global_flags: GLOBAL_FLAGS.map(dash), commands: {} };
2143
+ for (const cmd of commands) {
2144
+ if (!cmd.write) continue;
2145
+ const v = cmd.values;
2146
+ if (!v) {
2147
+ problems.push(`${cmd.name}\uFF1A\u5199\u547D\u4EE4\u6CA1\u6709 values \u6807\u6CE8`);
2148
+ continue;
2149
+ }
2150
+ const params = v.params ?? {};
2151
+ const seen = /* @__PURE__ */ new Map();
2152
+ const claim = (flag, place) => {
2153
+ if (GLOBAL_FLAGS.includes(flag)) problems.push(`${cmd.name}\uFF1A--${flag} \u662F\u5168\u5C40\u53C2\u6570\uFF0C\u4E0D\u5199\u8FDB\u547D\u4EE4\u81EA\u5DF1\u7684\u6E05\u5355`);
2154
+ if (seen.has(flag)) problems.push(`${cmd.name}\uFF1A--${flag} \u540C\u65F6\u5728 ${seen.get(flag)} \u548C ${place}`);
2155
+ seen.set(flag, place);
2156
+ if (!cmd.flags.includes(flag)) problems.push(`${cmd.name}\uFF1A\u6E05\u5355\u91CC\u7684 --${flag} \u4E0D\u662F\u8FD9\u6761\u547D\u4EE4\u8BA4\u7684\u53C2\u6570`);
2157
+ };
2158
+ for (const f of Object.keys(params)) claim(f, "params");
2159
+ if (v.subject) claim(v.subject.flag, "subject");
2160
+ for (const f of v.unchecked ?? []) claim(f, "unchecked");
2161
+ for (const f of v.opaque ?? []) claim(f, "opaque");
2162
+ for (const f of cmd.flags) if (!seen.has(f)) problems.push(`${cmd.name}\uFF1A--${f} \u6CA1\u5199\u8FDB\u6E05\u5355\uFF08\u5E26\u94B1\u7684\u8FDB params\uFF0C\u4E0D\u662F\u94B1\u7684\u8FDB unchecked\uFF09`);
2163
+ for (const f of v.repeatable ?? []) if (!seen.has(f) || seen.get(f) === "opaque") problems.push(`${cmd.name}\uFF1Arepeatable --${f} \u4E0D\u662F\u5B83\u7684\u53C2\u6570`);
2164
+ for (const [f, p] of Object.entries(params)) checkParam(`${cmd.name} --${f}`, p, params, problems);
2165
+ const m = { write: true, params: {} };
2166
+ if (v.subject) m.subject = { flag: dash(v.subject.flag), kind: v.subject.kind };
2167
+ for (const [f, p] of Object.entries(params)) {
2168
+ m.params[dash(f)] = { ...p, ...p.semantic === "currency" ? { type: "string" } : {}, ...p.currency_flag ? { currency_flag: dash(p.currency_flag) } : {} };
2169
+ }
2170
+ if (v.repeatable?.length) m.repeatable = sorted(v.repeatable);
2171
+ if (v.opaque?.length) m.opaque = sorted(v.opaque);
2172
+ if (v.unchecked?.length) m.unchecked = sorted(v.unchecked);
2173
+ out2.commands[cmd.name] = m;
2174
+ }
2175
+ if (problems.length) throw new Error(`\u5199\u547D\u4EE4\u6E05\u5355\u4E0D\u5B8C\u6574\uFF1A
2176
+ - ${problems.join("\n- ")}`);
2177
+ return out2;
2178
+ }
2179
+
2010
2180
  // src/index.ts
2011
2181
  var NO_SHOP_COMMANDS = /* @__PURE__ */ new Set(["auth link", "apps add", "apps list", "apps disable", "apps available", "apps set-shop"]);
2012
2182
  var SHOP_OPTIONAL_BUT_READ = /* @__PURE__ */ new Set(["auth link", "apps set-shop"]);
@@ -2063,11 +2233,13 @@ function normalizeBooleanFlags(args2) {
2063
2233
  function out(obj) {
2064
2234
  process.stdout.write(JSON.stringify(obj, null, 2) + "\n");
2065
2235
  }
2236
+ var SCHEMA = { name: "schema", summary: "\u5199\u547D\u4EE4\u53C2\u6570\u6E05\u5355\uFF08--json\uFF1B\u4E0D\u9700\u8981 --shop\uFF09\uFF1A\u6BCF\u6761\u5199\u547D\u4EE4\u7684\u6BCF\u4E2A\u53C2\u6570\u662F\u4EC0\u4E48\u610F\u601D\u3001\u4EC0\u4E48\u5355\u4F4D\uFF0C\u4F9B Optima COO \u7684\u5199\u5165\u6570\u503C\u5B88\u95E8\u6309\u7248\u672C\u56FA\u5B9A", flags: ["json"], noShop: true };
2066
2237
  function usage() {
2067
2238
  const lines = ["tiktok <domain> <verb> --shop <shopId> [--flags]", "", "\u547D\u4EE4\uFF1A"];
2068
2239
  for (const c of COMMANDS) lines.push(` ${c.name.padEnd(22)} ${c.summary}`);
2069
2240
  lines.push(` ${"shops".padEnd(22)} \u67E5\u5F53\u524D\u767B\u5F55\u7528\u6237\u5DF2\u6388\u6743\u5E97\u94FA`);
2070
2241
  lines.push(` ${"images upload".padEnd(22)} \u4E0A\u4F20\u672C\u5730\u56FE\u7247\uFF08--file --use-case [--out]\uFF09`);
2242
+ lines.push(` ${"schema".padEnd(22)} ${SCHEMA.summary}`);
2071
2243
  lines.push("", "\u6BCF\u6761\u547D\u4EE4\u53EA\u8BA4\u5B83\u81EA\u5DF1\u7684\u53C2\u6570\uFF08`tiktok <\u547D\u4EE4> --help` \u770B\uFF09\uFF0C\u4E0D\u8BA4\u7684\u4F1A\u5F53\u573A\u62A5 VALIDATION \u2014\u2014 \u4E0D\u4F1A\u518D\u88AB\u9759\u9ED8\u4E22\u6389\u3002");
2072
2244
  lines.push("", "\u901A\u7528\uFF1A--shop \u5FC5\u586B\uFF08\u9664 shops / auth link / apps add|list|disable\uFF09\uFF1B\u9ED8\u8BA4 JSON \u8F93\u51FA\uFF1BTIKTOK_BACKEND_URL \u663E\u5F0F\u6307\u5B9A backend\uFF08\u672A\u8BBE\u5219\u6309 OPTIMA_ENV \u6620\u5C04 cn-stage/cn-prod\uFF0C\u5426\u5219 localhost\uFF09\u3002");
2073
2245
  process.stderr.write(lines.join("\n") + "\n");
@@ -2106,6 +2278,15 @@ if (positional[0] === "shops") {
2106
2278
  out(await listShops());
2107
2279
  process.exit(0);
2108
2280
  }
2281
+ if (positional[0] === "schema") {
2282
+ if (wantsHelp) {
2283
+ process.stderr.write(commandUsage(SCHEMA));
2284
+ process.exit(0);
2285
+ }
2286
+ rejectUnknownFlags(SCHEMA.name, SCHEMA.flags, positional.slice(1), true);
2287
+ out(buildSchema(COMMANDS, cliVersion()));
2288
+ process.exit(0);
2289
+ }
2109
2290
  if (positional[0] === "images" && positional[1] === "upload") {
2110
2291
  if (wantsHelp) {
2111
2292
  process.stderr.write(commandUsage(IMAGES_UPLOAD));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optima-chat/tiktok-cli",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "description": "TikTok Shop CLI for Optima agents — 经 tiktok-backend 操作店铺",
5
5
  "type": "module",
6
6
  "bin": {