@optima-chat/tiktok-cli 0.2.0 → 0.4.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 +23 -1
  2. package/dist/index.js +422 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -19,7 +19,7 @@ tiktok <domain> <verb> --shop <shopId> [--flags]
19
19
  | 命令 | 说明 |
20
20
  |---|---|
21
21
  | `auth link` | 生成店铺绑定授权链接(无需 --shop) |
22
- | `shops --merchant <id>` | 查商家已授权店铺 |
22
+ | `shops` | 查当前登录用户已授权店铺 |
23
23
  | `orders list` | 订单列表(`--status --page-size --since`) |
24
24
  | `orders get --id <id>` | 订单详情 |
25
25
  | `analytics shop` | 店铺经营数据(`--from --to`,默认近30天) |
@@ -28,6 +28,22 @@ tiktok <domain> <verb> --shop <shopId> [--flags]
28
28
  | `promotions list` | 促销活动(`--status`) |
29
29
  | `finance statements` | 结算单 |
30
30
 
31
+ ### 建品前置只读辅助(拼商品体用)
32
+
33
+ | 命令 | 说明 |
34
+ |---|---|
35
+ | `products prerequisites` | 建品前置检查(店铺就绪状态) |
36
+ | `categories recommend --title [--desc]` | 类目推荐 |
37
+ | `categories tree [--version v2]` | 类目树 |
38
+ | `categories attributes --category <id>` | 类目属性(看 is_requried 必填项) |
39
+ | `categories rules --category <id>` | 类目规则(尺码表/认证/包装是否强制) |
40
+ | `brands list [--category] [--name]` | 品牌列表 |
41
+ | `brands recommend --title <t>` | 品牌推荐 |
42
+ | `warehouses list` | 仓库列表(跨境认 sub_type CB_*) |
43
+ | `shipping-templates list` | 运费模板列表 |
44
+ | `delivery-options --warehouse <id>` | 仓库交付方式 |
45
+ | `images upload --file <path> --use-case MAIN_IMAGE [--out <f>]` | 上传本地图 → 返回 TikTok CDN `uri`(multipart,供拼 `main_images` 用) |
46
+
31
47
  ## 写操作(Phase 2,安全护栏)
32
48
 
33
49
  写命令三道阀,把 A 轨「填完回读」纪律做进 CLI:
@@ -49,9 +65,15 @@ tiktok products set-price --shop <id> --product <pid> --sku <sid> --price 29.90
49
65
  | `products set-inventory` | 改 SKU 库存(`--product --sku --warehouse --quantity`) | ✓ 49→55→49 可逆 |
50
66
  | `promotions create-flashsale` | 建秒杀(`--title --product --sku --price [--limit --per-user --hours]`)。两步:建壳+加品 | ✓ 真实建成 |
51
67
  | `promotions deactivate` | 终止促销活动(`--id`)。不可逆 | ✓ |
68
+ | `products create --body-file <json>` | **生产级建品**(完整商品体 JSON)。dry-run 内嵌 `listing_check` 预检 → `--apply` 建品 → 回读高亮 warnings | ✓ 真实上架 V.V.Love(ACTIVATE) |
69
+ | `products add-variant --product <id> --body-file <json>` | 加规格 SKU(读现有 skus → merge → partial_edit;新 SKU 自动继承 sku_dimensions/sku_weight) | ✓ 真加变体成功 |
70
+ | `products replace-images --product <id> --body-file <json>` | 整组换主图(main_images 整组替换,非追加) | — |
71
+ | `products partial-edit --product <id> --body-file <json>` | 改商品字段(嵌套字段整组覆盖,未列子项会被清空) | — |
52
72
 
53
73
  均在跨境测试店真实验证(2026-09-02):`ok/applied` → 回读确认 → 审计落库。
54
74
 
75
+ **建品字段坑**(实测坐实,见 [`../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 路由)。
76
+
55
77
  **秒杀字段坑**(OAS 未标、实测撞出):商品级 `quantity_limit` 在 VARIATION 模式必须 `-1`(限量由 SKU 级控制);秒杀价须**低于该 SKU 近 30 天最低成交价**且**不超折扣上限**。这些复杂度被 CLI 吸收,agent 一次成型。
56
78
 
57
79
  ## 错误分类(agent 靠此决策)
package/dist/index.js CHANGED
@@ -1,8 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/commands.ts
4
+ import { readFileSync as readFileSync2 } from "fs";
5
+
3
6
  // src/client.ts
4
7
  import { readFileSync } from "fs";
5
8
  import { homedir } from "os";
9
+ import { basename } from "path";
6
10
  import { join } from "path";
7
11
 
8
12
  // src/backendUrl.ts
@@ -81,8 +85,37 @@ async function createAuthLink() {
81
85
  return { ok: false, error: { code: "BACKEND_UNREACHABLE", message: e.message, retryable: true } };
82
86
  }
83
87
  }
88
+ async function uploadImage(shop, filePath, useCase) {
89
+ try {
90
+ const buf = readFileSync(filePath);
91
+ const form = new FormData();
92
+ form.append("data", new Blob([buf]), basename(filePath));
93
+ form.append("use_case", useCase);
94
+ const r = await fetch(`${BACKEND}/proxy-upload/${shop}/image`, { method: "POST", headers: authHeaders(), body: form });
95
+ const body = await r.json().catch(() => ({}));
96
+ if (body?.code === 0) return { ok: true, data: { uri: body.data?.uri, url: body.data?.url, use_case: useCase }, meta: { shop } };
97
+ return { ok: false, error: { code: r.status === 401 ? "AUTH_EXPIRED" : "TIKTOK_ERROR", message: body?.message ?? body?.error ?? `HTTP ${r.status}`, retryable: false }, meta: { shop } };
98
+ } catch (e) {
99
+ return { ok: false, error: { code: "BACKEND_UNREACHABLE", message: e.message, retryable: true } };
100
+ }
101
+ }
84
102
 
85
103
  // src/commands.ts
104
+ function readBodyFile(path) {
105
+ return JSON.parse(readFileSync2(path, "utf8"));
106
+ }
107
+ function mergeSkus(existing, add) {
108
+ const template = existing?.[0];
109
+ const filledAdd = add.map((sku) => {
110
+ if (!template) return sku;
111
+ return {
112
+ ...sku,
113
+ sku_dimensions: sku.sku_dimensions ?? template.sku_dimensions,
114
+ sku_weight: sku.sku_weight ?? template.sku_weight
115
+ };
116
+ });
117
+ return [...existing, ...filledAdd];
118
+ }
86
119
  var today = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
87
120
  var daysAgo = (n) => new Date(Date.now() - n * 864e5).toISOString().slice(0, 10);
88
121
  var COMMANDS = [
@@ -152,6 +185,78 @@ var COMMANDS = [
152
185
  }
153
186
  })
154
187
  },
188
+ // ===== 建品前置只读辅助命令 =====
189
+ {
190
+ name: "products prerequisites",
191
+ summary: "\u5EFA\u54C1\u524D\u7F6E\u68C0\u67E5",
192
+ run: ({ shop }) => proxy("GET", shop, "/product/202312/prerequisites", {})
193
+ },
194
+ {
195
+ name: "categories recommend",
196
+ summary: "\u7C7B\u76EE\u63A8\u8350\uFF08--title [--desc]\uFF09",
197
+ run: ({ shop, args: args2 }) => proxy("POST", shop, "/product/202309/categories/recommend", {
198
+ body: {
199
+ product_title: args2.title,
200
+ ...args2.desc ? { description: args2.desc } : {}
201
+ }
202
+ })
203
+ },
204
+ {
205
+ name: "categories tree",
206
+ summary: "\u7C7B\u76EE\u6811\uFF08--version v2\uFF09",
207
+ run: ({ shop, args: args2 }) => proxy("GET", shop, "/product/202309/categories", {
208
+ query: { category_version: args2.version ?? "v2" }
209
+ })
210
+ },
211
+ {
212
+ name: "categories attributes",
213
+ summary: "\u7C7B\u76EE\u5C5E\u6027\uFF08--category\uFF09",
214
+ run: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/categories/${args2.category}/attributes`, {
215
+ query: { category_version: args2.version ?? "v2" }
216
+ })
217
+ },
218
+ {
219
+ name: "categories rules",
220
+ summary: "\u7C7B\u76EE\u89C4\u5219\uFF08--category\uFF09",
221
+ run: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/categories/${args2.category}/rules`, {
222
+ query: { category_version: args2.version ?? "v2" }
223
+ })
224
+ },
225
+ {
226
+ name: "brands list",
227
+ summary: "\u54C1\u724C\u5217\u8868\uFF08[--category] [--name]\uFF09",
228
+ run: ({ shop, args: args2 }) => proxy("GET", shop, "/product/202309/brands", {
229
+ query: {
230
+ page_size: args2["page-size"] ?? "50",
231
+ ...args2.category ? { category_id: args2.category } : {},
232
+ ...args2.name ? { brand_name: args2.name } : {}
233
+ }
234
+ })
235
+ },
236
+ {
237
+ name: "brands recommend",
238
+ summary: "\u54C1\u724C\u63A8\u8350\uFF08--title\uFF09",
239
+ run: ({ shop, args: args2 }) => proxy("POST", shop, "/product/202309/brands/recommend", {
240
+ body: { product_title: args2.title }
241
+ })
242
+ },
243
+ {
244
+ name: "warehouses list",
245
+ summary: "\u4ED3\u5E93\u5217\u8868\uFF08\u8DE8\u5883\u8BA4 sub_type CB_*\uFF09",
246
+ run: ({ shop }) => proxy("GET", shop, "/logistics/202309/warehouses", {})
247
+ },
248
+ {
249
+ name: "shipping-templates list",
250
+ summary: "\u8FD0\u8D39\u6A21\u677F\u5217\u8868",
251
+ run: ({ shop }) => proxy("GET", shop, "/logistics/202510/seller_templates", {})
252
+ },
253
+ {
254
+ name: "delivery-options",
255
+ summary: "\u4ED3\u5E93\u4EA4\u4ED8\u65B9\u5F0F\uFF08--warehouse\uFF09",
256
+ run: ({ shop, args: args2 }) => proxy("GET", shop, `/logistics/202309/warehouses/${args2.warehouse}/delivery_options`, {
257
+ query: { scope: "PRODUCT" }
258
+ })
259
+ },
155
260
  // ===== 写操作(Phase 2):默认 --dry-run,--apply 才真发,成功后回读 =====
156
261
  {
157
262
  name: "products set-price",
@@ -209,8 +314,286 @@ var COMMANDS = [
209
314
  }],
210
315
  run: ({ shop, args: args2 }) => proxy("POST", shop, `/product/202309/products/${args2.product}/inventory/update`, { body: inventoryBody(args2) }),
211
316
  readback: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/products/${args2.product}`, {})
317
+ },
318
+ {
319
+ name: "products create",
320
+ summary: "\u5EFA\u54C1\uFF08--body-file [--apply]\uFF09\u3002dry-run \u8DD1 listing_check \u9884\u68C0\uFF1B\u5199\u64CD\u4F5C",
321
+ write: true,
322
+ preview: () => [{ step: "listing_check \u9884\u68C0 \u2192 \u5EFA\u54C1", method: "POST", path: "/product/202309/products/listing_check", body: void 0 }],
323
+ precheck: ({ shop, args: args2 }) => proxy("POST", shop, "/product/202309/products/listing_check", { body: readBodyFile(args2["body-file"]) }),
324
+ run: async ({ shop, args: args2 }) => {
325
+ const body = readBodyFile(args2["body-file"]);
326
+ return proxy("POST", shop, "/product/202309/products", { body });
327
+ },
328
+ readback: async ({ shop, args: args2 }) => {
329
+ return proxy("POST", shop, "/product/202309/products/search", { query: { page_size: "5" }, body: {} });
330
+ }
331
+ },
332
+ {
333
+ name: "products add-variant",
334
+ summary: "\u7ED9\u5546\u54C1\u52A0\u89C4\u683C SKU\uFF08--product --body-file skus-delta.json [--apply]\uFF09",
335
+ write: true,
336
+ preview: () => [{ step: "\u8BFB\u73B0\u6709 skus \u2192 append \u2192 partial_edit", method: "POST", path: "/product/202309/products/{id}/partial_edit" }],
337
+ run: async ({ shop, args: args2 }) => {
338
+ const cur = await proxy("GET", shop, `/product/202309/products/${args2.product}`, {});
339
+ if (!cur.ok) return cur;
340
+ const merged = mergeSkus(cur.data?.skus ?? [], readBodyFile(args2["body-file"]).skus ?? []);
341
+ return proxy("POST", shop, `/product/202309/products/${args2.product}/partial_edit`, { body: { skus: merged } });
342
+ },
343
+ readback: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/products/${args2.product}`, {})
344
+ },
345
+ {
346
+ name: "products replace-images",
347
+ summary: "\u6574\u7EC4\u6362\u4E3B\u56FE\uFF08--product --body-file images.json [--apply]\uFF09",
348
+ write: true,
349
+ preview: () => [{ step: "main_images \u6574\u7EC4\u66FF\u6362 \u2192 partial_edit", method: "POST", path: "/product/202309/products/{id}/partial_edit" }],
350
+ run: ({ shop, args: args2 }) => proxy("POST", shop, `/product/202309/products/${args2.product}/partial_edit`, { body: { main_images: readBodyFile(args2["body-file"]).main_images } }),
351
+ readback: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/products/${args2.product}`, {})
352
+ },
353
+ {
354
+ name: "products partial-edit",
355
+ summary: "\u6539\u5546\u54C1\u5B57\u6BB5\uFF08--product --body-file patch.json [--apply]\uFF09\u3002\u5D4C\u5957\u5B57\u6BB5\u6574\u7EC4\u8986\u76D6",
356
+ write: true,
357
+ 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"]) }],
358
+ run: ({ shop, args: args2 }) => proxy("POST", shop, `/product/202309/products/${args2.product}/partial_edit`, { body: readBodyFile(args2["body-file"]) }),
359
+ readback: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/products/${args2.product}`, {})
360
+ },
361
+ // ===== 联盟只读命令(affiliate_seller,版本号取自 docs/batch2-affiliate-shapes.md 真调结果) =====
362
+ {
363
+ name: "affiliate creators search",
364
+ summary: "\u8054\u76DF\u5E7F\u573A\u627E\u8FBE\u4EBA\uFF08--keyword\uFF0C--page-size 12|20\uFF0C\u9ED8\u8BA4 12\uFF09",
365
+ run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202508/marketplace_creators/search", {
366
+ query: { page_size: String(args2["page-size"] ?? 12) },
367
+ body: args2.keyword ? { keyword: args2.keyword } : {}
368
+ })
369
+ },
370
+ {
371
+ name: "affiliate creators perf",
372
+ summary: "\u8FBE\u4EBA\u5E26\u8D27\u8868\u73B0\u8BE6\u60C5\uFF08--creator \u5FC5\u586B\uFF0Ccreator_open_id\uFF09",
373
+ run: ({ shop, args: args2 }) => proxy("GET", shop, `/affiliate_seller/202508/marketplace_creators/${args2.creator}`, {})
374
+ },
375
+ {
376
+ name: "affiliate target-collab search",
377
+ summary: "\u5B9A\u5411\u9080\u7EA6\u5217\u8868\uFF08--status collaboration_status\uFF0C\u5982 ONGOING/EXPIRING/VALID/CANCELING/COMPLETED\uFF1B--page-size\uFF09",
378
+ run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202508/target_collaborations/search", {
379
+ query: { page_size: String(args2["page-size"] ?? 20) },
380
+ body: args2.status ? { collaboration_status: args2.status } : {}
381
+ })
382
+ },
383
+ {
384
+ name: "affiliate target-collab query",
385
+ summary: "\u5B9A\u5411\u9080\u7EA6\u8BE6\u60C5\uFF08--id \u5FC5\u586B\uFF09",
386
+ run: ({ shop, args: args2 }) => proxy("GET", shop, `/affiliate_seller/202508/target_collaborations/${args2.id}`, {})
387
+ },
388
+ {
389
+ name: "affiliate samples search",
390
+ summary: "\u6837\u54C1\u7533\u8BF7\u5217\u8868\uFF08--page-size\uFF09",
391
+ run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202508/sample_applications/search", {
392
+ query: { page_size: String(args2["page-size"] ?? 20) },
393
+ body: {}
394
+ })
395
+ },
396
+ {
397
+ name: "affiliate dm list",
398
+ summary: "\u79C1\u4FE1\u4F1A\u8BDD\u5217\u8868\uFF08--page-size\uFF0C--status conversation_status \u53EF\u9009\uFF09\u3002\u26A0\uFE0F\u7248\u672C\u56FA\u5B9A 202412",
399
+ run: ({ shop, args: args2 }) => proxy("GET", shop, "/affiliate_seller/202412/conversations", {
400
+ query: {
401
+ page_size: String(args2["page-size"] ?? 20),
402
+ ...args2.status ? { conversation_status: args2.status } : {}
403
+ }
404
+ })
405
+ },
406
+ {
407
+ name: "affiliate dm messages",
408
+ summary: "\u4F1A\u8BDD\u6D88\u606F\u8BB0\u5F55\uFF08--conversation \u5FC5\u586B\uFF0Cconversation_id\uFF09",
409
+ run: ({ shop, args: args2 }) => proxy("GET", shop, `/affiliate_seller/202412/conversation/${args2.conversation}/messages`, {})
410
+ },
411
+ {
412
+ name: "affiliate orders search",
413
+ summary: "\u8054\u76DF\u8BA2\u5355\u5217\u8868\uFF08\u4F63\u91D1\u660E\u7EC6\uFF0C--page-size\uFF09",
414
+ run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202410/orders/search", {
415
+ query: { page_size: String(args2["page-size"] ?? 20) },
416
+ body: {}
417
+ })
418
+ },
419
+ {
420
+ name: "affiliate open-collab products-search",
421
+ summary: "\u516C\u5F00\u5408\u4F5C\u53EF\u8BBE\u4F63\u54C1\u5217\u8868\uFF08--page-size\uFF09",
422
+ run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202405/open_collaborations/products/search", {
423
+ query: { page_size: String(args2["page-size"] ?? 20) },
424
+ body: {}
425
+ })
426
+ },
427
+ {
428
+ name: "affiliate open-collab search",
429
+ summary: "\u516C\u5F00\u5408\u4F5C\u751F\u6548\u5217\u8868\uFF08\u542B current_commission\uFF0C--page-size\uFF09",
430
+ run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202412/open_collaborations/search", {
431
+ query: { page_size: String(args2["page-size"] ?? 20) },
432
+ body: {}
433
+ })
434
+ },
435
+ {
436
+ name: "affiliate open-collab settings get",
437
+ summary: "\u516C\u5F00\u5408\u4F5C\u5168\u5C40\u8BBE\u7F6E\uFF08\u81EA\u52A8\u65B0\u589E\u54C1\u9ED8\u8BA4\u4F63\u91D1\uFF09",
438
+ run: ({ shop }) => proxy("GET", shop, "/affiliate_seller/202409/open_collaboration_settings", {})
439
+ },
440
+ // ===== 联盟写命令(managing-affiliate-program,三道阀:dry-run 预览 → --apply 提交 → 回读) =====
441
+ {
442
+ name: "affiliate target-collab create",
443
+ 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",
444
+ write: true,
445
+ preview: ({ args: args2 }) => [{
446
+ step: "\u53D1\u5B9A\u5411\u9080\u7EA6",
447
+ method: "POST",
448
+ path: "/affiliate_seller/202508/target_collaborations",
449
+ body: readBodyFile(args2["body-file"])
450
+ }],
451
+ run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202508/target_collaborations", {
452
+ body: readBodyFile(args2["body-file"])
453
+ }),
454
+ readback: ({ shop }) => proxy("POST", shop, "/affiliate_seller/202508/target_collaborations/search", {
455
+ query: { page_size: "12" },
456
+ body: { collaboration_status: "ONGOING" }
457
+ })
458
+ },
459
+ {
460
+ name: "affiliate target-collab update",
461
+ summary: "\u6539\u5B9A\u5411\u9080\u7EA6\uFF08--id --body-file collab.json\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u63D0\u4EA4",
462
+ write: true,
463
+ preview: ({ args: args2 }) => [{
464
+ step: "\u6539\u5B9A\u5411\u9080\u7EA6",
465
+ method: "PUT",
466
+ path: `/affiliate_seller/202508/target_collaborations/${args2.id}`,
467
+ body: readBodyFile(args2["body-file"])
468
+ }],
469
+ run: ({ shop, args: args2 }) => proxy("PUT", shop, `/affiliate_seller/202508/target_collaborations/${args2.id}`, {
470
+ body: readBodyFile(args2["body-file"])
471
+ }),
472
+ readback: ({ shop, args: args2 }) => proxy("GET", shop, `/affiliate_seller/202508/target_collaborations/${args2.id}`, {})
473
+ },
474
+ {
475
+ name: "affiliate target-collab remove",
476
+ 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",
477
+ write: true,
478
+ preview: ({ args: args2 }) => [{
479
+ step: "\u64A4\u56DE\u5B9A\u5411\u9080\u7EA6",
480
+ method: "DELETE",
481
+ path: `/affiliate_seller/202409/target_collaborations/${args2.id}`,
482
+ body: void 0
483
+ }],
484
+ run: ({ shop, args: args2 }) => proxy("DELETE", shop, `/affiliate_seller/202409/target_collaborations/${args2.id}`, {})
485
+ },
486
+ {
487
+ name: "affiliate target-collab link",
488
+ 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",
489
+ write: true,
490
+ preview: ({ args: args2 }) => [{
491
+ step: "\u751F\u6210\u9080\u7EA6\u94FE\u63A5",
492
+ method: "POST",
493
+ path: `/affiliate_seller/202509/target_collaboration/${args2.id}/link`,
494
+ body: void 0
495
+ }],
496
+ run: ({ shop, args: args2 }) => proxy("POST", shop, `/affiliate_seller/202509/target_collaboration/${args2.id}/link`, {})
497
+ },
498
+ {
499
+ name: "affiliate dm open",
500
+ 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",
501
+ write: true,
502
+ preview: ({ args: args2 }) => [{
503
+ step: "\u5F00\u79C1\u4FE1\u4F1A\u8BDD",
504
+ method: "POST",
505
+ path: "/affiliate_seller/202508/conversations",
506
+ body: { creator_open_id: args2.creator }
507
+ }],
508
+ run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202508/conversations", {
509
+ body: { creator_open_id: args2.creator }
510
+ })
511
+ },
512
+ {
513
+ name: "affiliate dm send",
514
+ summary: "\u7ED9\u8FBE\u4EBA\u53D1\u79C1\u4FE1\uFF08--conversation --content [--msg-type TEXT]\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u53D1",
515
+ write: true,
516
+ preview: ({ args: args2 }) => [{
517
+ step: "\u53D1\u79C1\u4FE1",
518
+ method: "POST",
519
+ path: `/affiliate_seller/202412/conversations/${args2.conversation}/messages`,
520
+ body: { content: args2.content, msg_type: args2["msg-type"] ?? "TEXT" }
521
+ }],
522
+ run: ({ shop, args: args2 }) => proxy("POST", shop, `/affiliate_seller/202412/conversations/${args2.conversation}/messages`, {
523
+ body: { content: args2.content, msg_type: args2["msg-type"] ?? "TEXT" }
524
+ })
525
+ },
526
+ {
527
+ name: "affiliate samples review",
528
+ summary: "\u5BA1\u6279\u6837\u54C1\u7533\u8BF7\uFF08--id application_id --result review_result [--reject-reason]\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u53D1",
529
+ write: true,
530
+ preview: ({ args: args2 }) => [{
531
+ step: "\u5BA1\u6279\u6837\u54C1\u7533\u8BF7",
532
+ method: "POST",
533
+ path: `/affiliate_seller/202507/sample_applications/${args2.id}/review`,
534
+ body: sampleReviewBody(args2)
535
+ }],
536
+ run: ({ shop, args: args2 }) => proxy("POST", shop, `/affiliate_seller/202507/sample_applications/${args2.id}/review`, {
537
+ body: sampleReviewBody(args2)
538
+ }),
539
+ readback: ({ shop }) => proxy("POST", shop, "/affiliate_seller/202508/sample_applications/search", {
540
+ query: { page_size: "20" },
541
+ body: {}
542
+ })
543
+ },
544
+ // ===== 联盟写命令(managing-open-collaboration,三道阀:dry-run 预览 → --apply 提交 → 回读) =====
545
+ {
546
+ name: "affiliate open-collab set-commission",
547
+ 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",
548
+ write: true,
549
+ preview: ({ args: args2 }) => [{
550
+ step: "\u8BBE\u6309\u54C1\u516C\u5F00\u5408\u4F5C\u4F63\u91D1",
551
+ method: "POST",
552
+ path: "/affiliate_seller/202412/open_collaborations",
553
+ body: { product_id: args2.product, commission_rate: Number(args2.rate) }
554
+ }],
555
+ run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202412/open_collaborations", {
556
+ body: { product_id: args2.product, commission_rate: Number(args2.rate) }
557
+ }),
558
+ readback: ({ shop }) => proxy("POST", shop, "/affiliate_seller/202405/open_collaborations/products/search", {
559
+ query: { page_size: "20" },
560
+ body: {}
561
+ })
562
+ },
563
+ {
564
+ name: "affiliate open-collab remove",
565
+ summary: "\u79FB\u9664\u54C1\u7684\u516C\u5F00\u5408\u4F5C\uFF08--product product_id\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u53D1",
566
+ write: true,
567
+ preview: ({ args: args2 }) => [{
568
+ step: "\u79FB\u9664\u516C\u5F00\u5408\u4F5C",
569
+ method: "DELETE",
570
+ path: `/affiliate_seller/202409/open_collaborations/products/${args2.product}`,
571
+ body: void 0
572
+ }],
573
+ run: ({ shop, args: args2 }) => proxy("DELETE", shop, `/affiliate_seller/202409/open_collaborations/products/${args2.product}`, {})
574
+ },
575
+ {
576
+ name: "affiliate open-collab settings edit",
577
+ 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",
578
+ write: true,
579
+ preview: ({ args: args2 }) => [{
580
+ step: "\u6539\u5168\u5C40\u9ED8\u8BA4",
581
+ method: "POST",
582
+ path: "/affiliate_seller/202405/open_collaboration_settings",
583
+ body: { auto_add_product: { commission_rate: Number(args2.rate), enable: args2.enable === "true" } }
584
+ }],
585
+ run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202405/open_collaboration_settings", {
586
+ body: { auto_add_product: { commission_rate: Number(args2.rate), enable: args2.enable === "true" } }
587
+ }),
588
+ readback: ({ shop }) => proxy("GET", shop, "/affiliate_seller/202409/open_collaboration_settings", {})
212
589
  }
213
590
  ];
591
+ function sampleReviewBody(args2) {
592
+ return {
593
+ review_result: args2.result,
594
+ ...args2["reject-reason"] ? { reject_reason: args2["reject-reason"] } : {}
595
+ };
596
+ }
214
597
  function inventoryBody(args2) {
215
598
  return {
216
599
  skus: [{
@@ -292,7 +675,8 @@ function usage() {
292
675
  for (const c of COMMANDS) lines.push(` ${c.name.padEnd(22)} ${c.summary}`);
293
676
  lines.push(` ${"shops".padEnd(22)} \u67E5\u5F53\u524D\u767B\u5F55\u7528\u6237\u5DF2\u6388\u6743\u5E97\u94FA`);
294
677
  lines.push(` ${"auth link".padEnd(22)} \u751F\u6210\u5E97\u94FA\u7ED1\u5B9A\u6388\u6743\u94FE\u63A5\uFF08\u53D1\u7ED9\u5546\u5BB6\u70B9\u51FB\uFF09`);
295
- lines.push("", "\u901A\u7528\uFF1A--shop \u5FC5\u586B\uFF08\u9664 shops\uFF09\uFF1B\u9ED8\u8BA4 JSON \u8F93\u51FA\uFF1BTIKTOK_BACKEND_URL \u6307\u5411 backend\u3002");
678
+ lines.push(` ${"images upload".padEnd(22)} \u4E0A\u4F20\u672C\u5730\u56FE\u7247\uFF08--file --use-case [--out]\uFF09`);
679
+ lines.push("", "\u901A\u7528\uFF1A--shop \u5FC5\u586B\uFF08\u9664 shops / auth link\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");
296
680
  process.stderr.write(lines.join("\n") + "\n");
297
681
  }
298
682
  var argv = process.argv.slice(2);
@@ -309,6 +693,23 @@ if (positional[0] === "auth" && positional[1] === "link") {
309
693
  out(await createAuthLink());
310
694
  process.exit(0);
311
695
  }
696
+ if (positional[0] === "images" && positional[1] === "upload") {
697
+ if (!args.file) {
698
+ out({ ok: false, error: { code: "VALIDATION", message: "\u7F3A\u5C11 --file", retryable: false } });
699
+ process.exit(1);
700
+ }
701
+ if (!args.shop) {
702
+ out({ ok: false, error: { code: "VALIDATION", message: "\u7F3A\u5C11 --shop", retryable: false } });
703
+ process.exit(1);
704
+ }
705
+ const r = await uploadImage(args.shop, args.file, args["use-case"] ?? "MAIN_IMAGE");
706
+ if (r.ok && args.out) {
707
+ const { appendFileSync } = await import("fs");
708
+ appendFileSync(args.out, JSON.stringify(r.data) + "\n");
709
+ }
710
+ out(r);
711
+ process.exit(0);
712
+ }
312
713
  var match = findCommand(positional);
313
714
  if (!match) {
314
715
  out({ ok: false, error: { code: "VALIDATION", message: `\u672A\u77E5\u547D\u4EE4: ${positional.join(" ")}`, retryable: false } });
@@ -321,6 +722,16 @@ if (!args.shop) {
321
722
  }
322
723
  var ctx = { shop: args.shop, args, actor: args.actor };
323
724
  if (match.cmd.write && !("apply" in args)) {
725
+ if (match.cmd.precheck) {
726
+ const pc = await match.cmd.precheck(ctx);
727
+ out({
728
+ ok: true,
729
+ dry_run: true,
730
+ precheck: pc.ok ? pc.data : { error: pc.error },
731
+ message: "listing_check \u9884\u68C0\u7ED3\u679C\uFF0C\u786E\u8BA4\u540E\u52A0 --apply \u6267\u884C\u3002"
732
+ });
733
+ process.exit(0);
734
+ }
324
735
  const plan = match.cmd.preview?.(ctx);
325
736
  out({
326
737
  ok: true,
@@ -331,9 +742,16 @@ if (match.cmd.write && !("apply" in args)) {
331
742
  process.exit(0);
332
743
  }
333
744
  var env = await match.cmd.run(ctx);
334
- if (match.cmd.write && env.ok && match.cmd.readback) {
335
- const rb = await match.cmd.readback(ctx);
336
- out({ ...env, applied: true, readback: rb.ok ? rb.data : { error: rb.error } });
745
+ if (match.cmd.write && env.ok) {
746
+ const result = { ...env, applied: true };
747
+ if (env.data?.warnings?.length) {
748
+ result.warnings_alert = "\u26A0\uFE0F \u6709\u5B57\u6BB5\u88AB\u9759\u9ED8\u5904\u7406\uFF0C\u89C1 warnings";
749
+ }
750
+ if (match.cmd.readback) {
751
+ const rb = await match.cmd.readback(ctx);
752
+ result.readback = rb.ok ? rb.data : { error: rb.error };
753
+ }
754
+ out(result);
337
755
  } else {
338
756
  out(env);
339
757
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optima-chat/tiktok-cli",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "TikTok Shop CLI for Optima agents — 经 tiktok-backend 操作店铺",
5
5
  "type": "module",
6
6
  "bin": {