@optima-chat/tiktok-cli 0.10.2 → 0.11.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.
package/README.md CHANGED
@@ -68,6 +68,8 @@ tiktok promotions create-flashsale --shop=<id> --price=11.9 # 等号式
68
68
  `--apply=true` / `=1` / `=yes` / `=on` 才真发;给它一个看不懂的值(`--apply=maybe`)当场报错、不猜
69
69
  3. **成功后自动回读** —— 写完 GET 回资源,输出 `readback` 供 agent/人核对是否生效
70
70
 
71
+ ⚠️ **三道阀管的是「发出去的 payload 对不对」,管不了「这个操作本身该不该做」** —— `partial_edit` / `create` 默认送审上架,**dry-run 通过不等于店主同意上架**(#1958 就是三道阀全过、结果依然错:dry-run 过了、`--apply` 是故意加的、回读也确认生效了)。
72
+
71
73
  ```bash
72
74
  # 预览(安全)
73
75
  tiktok products set-price --shop <id> --product <pid> --sku <sid> --price 29.90
@@ -77,17 +79,21 @@ tiktok products set-price --shop <id> --product <pid> --sku <sid> --price 29.90
77
79
 
78
80
  | 命令 | 说明 | 验证 |
79
81
  |---|---|---|
80
- | `products set-price` | 改 SKU 售价(`--product --sku --price [--currency]`) | ✓ 改价+回读 |
81
- | `products set-inventory` | 改 SKU 库存(`--product --sku --warehouse --quantity`) | ✓ 49→55→49 可逆 |
82
+ | `products set-price` | 改 SKU 售价(`--product --sku --price [--currency]`)。仅 `ACTIVATE` 商品,且不在进行中的促销里(`update-price-202309` 原文)。改价不触发商品重审、立即生效(依据是 `partial-edit-product-202309` 的「`price`/`inventory` 不需 reaudit」,**本端点自己的文档没写这句、也未单独实测**)。 | ✓ 改价+回读 |
83
+ | `products set-inventory` | 改 SKU 库存(`--product --sku --warehouse --quantity`)。只要商品不在 `FREEZE`/`DELETED` 状态都能改(`update-inventory-202309` 原文)。改库存不触发商品重审、立即生效(同上依据,未单独实测)。⚠️ `DRAFT` 商品**未实测** —— 官方只写了「非 `FREEZE`/`DELETED`」,没说 DRAFT 可以;且草稿 SKU 常常还没分仓,会撞 `12052096`。 | ✓ 49→55→49 可逆 |
82
84
  | `promotions create-flashsale` | 建秒杀(`--title --product --sku --price [--limit --per-user --hours]`)。两步:建壳+加品 | ✓ 真实建成 |
83
85
  | `promotions deactivate` | 终止促销活动(`--id`)。不可逆 | ✓ |
84
- | `products create --body-file <json>` | **生产级建品**(完整商品体 JSON)。dry-run 内嵌 `listing_check` 预检 → `--apply` 建品 → 回读高亮 warnings | ✓ 真实上架 V.V.Love(ACTIVATE) |
85
- | `products add-variant --product <id> --body-file <json>` | 加规格 SKU(读现有 skus → merge → partial_edit;新 SKU 自动继承 sku_dimensions/sku_weight) | ✓ 真加变体成功 |
86
- | `products replace-images --product <id> --body-file <json>` | 整组换主图(main_images 整组替换,非追加) | — |
87
- | `products partial-edit --product <id> --body-file <json>` | 改商品字段(嵌套字段整组覆盖,未列子项会被清空) | — |
86
+ | `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) |
87
+ | `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`。 | ✓ 真加变体成功 |
88
+ | `products replace-images --product <id> --body-file <json>` | 整组换主图(main_images 整组替换,非追加)。⚠️ **默认送审**(#1958,见下方「送审语义」)。本命令**自构 body**,body-file 里的 `save_mode` **不透传、写了也无效**;要留草稿只能用 `products partial-edit`。 | — |
89
+ | `products partial-edit --product <id> --body-file <json>` | 改商品字段(嵌套字段整组覆盖,未列子项会被清空)。⚠️ **默认送审**(#1958,见下方「送审语义」)。整个 body 原样透传,可写 `"save_mode": "AS_DRAFT"` 留草稿(**未实测**)。 | — |
88
90
 
89
91
  均在跨境测试店真实验证(2026-09-02):`ok/applied` → 回读确认 → 审计落库。
90
92
 
93
+ **`partial_edit` / `create` 的送审语义**(官方 202309 实取):两者的 `save_mode` **默认都是 `LISTING`**(两份文档都写着 `Default: LISTING` 与 `LISTING: Immediately list the product in the shop`;⚠️ 注意 `LISTING` 的枚举描述只写「Immediately list the product in the shop」、**字面读不出审核**,但「**送审**」是一手写死的:`create-product-202309` 自己的 Function Description 就是「After creation, **it will be sent for audit review** by TikTok Shop.」,`partial-edit-product-202309` 那份则写 **v2 会重新送审**(见下一段);#1958 是实况坐实)。要留草稿得显式写 `"save_mode": "AS_DRAFT"`,而它有三条限制 —— **只出自 `partial-edit-product-202309` 那一份**(`create` 那份只给了枚举值与默认值):① 商品状态是 `DELETED` 时不支持;② 商品在任一 listing platform 上是 `PENDING` 或 `FREEZE` 时不支持;③ 对 post-live draft 用 `AS_DRAFT` 时,`inventory` 与 `delivery_option_ids` **不保存**。⚠️ 本仓**没有记录在案的 `AS_DRAFT` 实测锚点**(`LISTING` 那半有:上面 `products create` 那行的「✓ 真实上架 V.V.Love(ACTIVATE)」;`AS_DRAFT` 只出现在 2026-09-02 那批 plan/spec 的前瞻描述里,执行记录没落)—— 所以 skill 侧按「**有真机实测之前别走**」处理(见 `optima-store-skills#1958`)。**要用这条路,先在跨境测试店实测一次**(建草稿 → 回读确认 `product_status` 仍是 `DRAFT` → 把结论和 `product_id` 写回上面那张表的「验证」列),再考虑放开 skill 侧。
94
+
95
+ 🔴 **还有一句更承重的**(同样出自 `partial-edit-product-202309` 的 Function Description,比 `save_mode` 那一格更该先读):`partial_edit` 之后**最新内容(v2)会重新送审**;审过则 v2 发布到店铺,审不过则线上**保持 v1 不变**;**但 `price` / `inventory` 字段的修改不需重审、立即生效**。三个推论:① 上面 `set-price` / `set-inventory` 两行的「不触发商品重审」就是这一句(注意它是 `partial_edit` 文档的表述,那两个专用端点自己的文档并没写);② **对 `ACTIVATE` 商品跑 `partial-edit` 也会送审** —— 不是只有 `DRAFT` 才有后果,差别在于 `DRAFT` 审过会变成在售(#1958 的伤害),`ACTIVATE` 审不过只是保持原样;③ 挂多个 listing platform 时若一个过一个没过,**没过的那个平台上商品会被下架隐藏**。
96
+
91
97
  **建品字段坑**(实测坐实,见 [`../docs/建品字段权威规格.md`](../docs/建品字段权威规格.md)):MY 跨境店价格用 `sale_price` 非 `amount`;`package_dimensions` 类目必填;仓库认 `sub_type=CB_*`;**品牌合规**——标题/主图的品牌须与 `brand_id` 一致,否则审核报「品牌不一致违规」(用第三方品牌名却不申报会被拒);`add-variant` 加的新 SKU 必须带 `sku_dimensions`/`sku_weight`(建品时平台从 package 自动回填,加变体无此回填,CLI 已自动继承)。图片经 `images upload` 传本地文件(backend multipart 路由)。
92
98
 
93
99
  **秒杀字段坑**(OAS 未标、实测撞出):商品级 `quantity_limit` 在 VARIATION 模式必须 `-1`(限量由 SKU 级控制);秒杀价须**低于该 SKU 近 30 天最低成交价**且**不超折扣上限**。这些复杂度被 CLI 吸收,agent 一次成型。
package/dist/index.js CHANGED
@@ -764,7 +764,7 @@ var COMMANDS = [
764
764
  },
765
765
  {
766
766
  name: "products create",
767
- summary: "\u5EFA\u54C1\uFF08--body-file [--apply]\uFF09\u3002dry-run \u8DD1 listing_check \u9884\u68C0\uFF1B\u5199\u64CD\u4F5C",
767
+ 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",
768
768
  write: true,
769
769
  preview: () => [{ step: "listing_check \u9884\u68C0 \u2192 \u5EFA\u54C1", method: "POST", path: "/product/202309/products/listing_check", body: void 0 }],
770
770
  precheck: ({ shop, args: args2 }) => proxy("POST", shop, "/product/202309/products/listing_check", { body: readBodyFile(args2["body-file"]) }),
@@ -778,9 +778,9 @@ var COMMANDS = [
778
778
  },
779
779
  {
780
780
  name: "products add-variant",
781
- summary: "\u7ED9\u5546\u54C1\u52A0\u89C4\u683C SKU\uFF08--product --body-file skus-delta.json [--apply]\uFF09",
781
+ 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",
782
782
  write: true,
783
- preview: () => [{ step: "\u8BFB\u73B0\u6709 skus \u2192 append \u2192 partial_edit", method: "POST", path: "/product/202309/products/{id}/partial_edit" }],
783
+ 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" }],
784
784
  run: async ({ shop, args: args2 }) => {
785
785
  const cur = await proxy("GET", shop, `/product/202309/products/${args2.product}`, {});
786
786
  if (!cur.ok) return cur;
@@ -791,17 +791,17 @@ var COMMANDS = [
791
791
  },
792
792
  {
793
793
  name: "products replace-images",
794
- summary: "\u6574\u7EC4\u6362\u4E3B\u56FE\uFF08--product --body-file images.json [--apply]\uFF09",
794
+ 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",
795
795
  write: true,
796
- preview: () => [{ step: "main_images \u6574\u7EC4\u66FF\u6362 \u2192 partial_edit", method: "POST", path: "/product/202309/products/{id}/partial_edit" }],
796
+ 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" }],
797
797
  run: ({ shop, args: args2 }) => proxy("POST", shop, `/product/202309/products/${args2.product}/partial_edit`, { body: { main_images: readBodyFile(args2["body-file"]).main_images } }),
798
798
  readback: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/products/${args2.product}`, {})
799
799
  },
800
800
  {
801
801
  name: "products partial-edit",
802
- summary: "\u6539\u5546\u54C1\u5B57\u6BB5\uFF08--product --body-file patch.json [--apply]\uFF09\u3002\u5D4C\u5957\u5B57\u6BB5\u6574\u7EC4\u8986\u76D6",
802
+ 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",
803
803
  write: true,
804
- preview: ({ args: args2 }) => [{ step: "partial_edit\uFF08\u5D4C\u5957\u5B57\u6BB5\u672A\u5217\u5B50\u9879\u4F1A\u88AB\u6E05\u7A7A\uFF09", method: "POST", path: `/product/202309/products/${args2.product}/partial_edit`, body: readBodyFile(args2["body-file"]) }],
804
+ 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"]) }],
805
805
  run: ({ shop, args: args2 }) => proxy("POST", shop, `/product/202309/products/${args2.product}/partial_edit`, { body: readBodyFile(args2["body-file"]) }),
806
806
  readback: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/products/${args2.product}`, {})
807
807
  },
@@ -1272,7 +1272,7 @@ if (match.cmd.write && !("apply" in args)) {
1272
1272
  ok: true,
1273
1273
  dry_run: true,
1274
1274
  precheck: pc.ok ? pc.data : { error: pc.error },
1275
- message: "listing_check \u9884\u68C0\u7ED3\u679C\uFF0C\u786E\u8BA4\u540E\u52A0 --apply \u6267\u884C\u3002"
1275
+ message: "listing_check \u9884\u68C0\u7ED3\u679C\uFF0C\u786E\u8BA4\u540E\u52A0 --apply \u6267\u884C\u3002\u26A0\uFE0F \u9ED8\u8BA4 save_mode=LISTING\uFF1A--apply \u5373\u5EFA\u5B8C\u76F4\u63A5\u4E0A\u67B6\u9001\u5BA1\uFF1B\u7528\u6237\u8981\u6C42\u5B58\u8349\u7A3F/\u522B\u63D0\u4EA4\u65F6\u505C\u624B\u95EE\u7528\u6237\uFF08#1958\uFF09"
1276
1276
  });
1277
1277
  process.exit(0);
1278
1278
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optima-chat/tiktok-cli",
3
- "version": "0.10.2",
3
+ "version": "0.11.0",
4
4
  "description": "TikTok Shop CLI for Optima agents — 经 tiktok-backend 操作店铺",
5
5
  "type": "module",
6
6
  "bin": {