@optima-chat/tiktok-cli 0.1.0 → 0.3.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 +26 -3
- package/dist/index.js +216 -5
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -10,15 +10,16 @@ Optima agent 用来操作 TikTok 店铺的 CLI。**薄客户端**:不直连 Ti
|
|
|
10
10
|
tiktok <domain> <verb> --shop <shopId> [--flags]
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
- `--shop` **必填**(多店显式路由,对应 A
|
|
13
|
+
- `--shop` **必填**(多店显式路由,对应 A 轨「多店必须显式选店」教训;`auth link` 除外)
|
|
14
14
|
- 默认 **JSON 输出**,统一信封 `{ ok, data?, error?, meta? }`
|
|
15
|
-
- `TIKTOK_BACKEND_URL`
|
|
15
|
+
- `TIKTOK_BACKEND_URL` 解析顺序:显式环境变量 > `OPTIMA_ENV` 映射(`cn-stage` → `https://tiktok-api.stage.optima.chat`;`cn-prod` → `https://tiktok-api.yzsgo.com`)> 本地默认 `http://127.0.0.1:3000`
|
|
16
16
|
|
|
17
17
|
## 命令(v1 只读)
|
|
18
18
|
|
|
19
19
|
| 命令 | 说明 |
|
|
20
20
|
|---|---|
|
|
21
|
-
| `
|
|
21
|
+
| `auth link` | 生成店铺绑定授权链接(无需 --shop) |
|
|
22
|
+
| `shops` | 查当前登录用户已授权店铺 |
|
|
22
23
|
| `orders list` | 订单列表(`--status --page-size --since`) |
|
|
23
24
|
| `orders get --id <id>` | 订单详情 |
|
|
24
25
|
| `analytics shop` | 店铺经营数据(`--from --to`,默认近30天) |
|
|
@@ -27,6 +28,22 @@ tiktok <domain> <verb> --shop <shopId> [--flags]
|
|
|
27
28
|
| `promotions list` | 促销活动(`--status`) |
|
|
28
29
|
| `finance statements` | 结算单 |
|
|
29
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
|
+
|
|
30
47
|
## 写操作(Phase 2,安全护栏)
|
|
31
48
|
|
|
32
49
|
写命令三道阀,把 A 轨「填完回读」纪律做进 CLI:
|
|
@@ -48,9 +65,15 @@ tiktok products set-price --shop <id> --product <pid> --sku <sid> --price 29.90
|
|
|
48
65
|
| `products set-inventory` | 改 SKU 库存(`--product --sku --warehouse --quantity`) | ✓ 49→55→49 可逆 |
|
|
49
66
|
| `promotions create-flashsale` | 建秒杀(`--title --product --sku --price [--limit --per-user --hours]`)。两步:建壳+加品 | ✓ 真实建成 |
|
|
50
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>` | 改商品字段(嵌套字段整组覆盖,未列子项会被清空) | — |
|
|
51
72
|
|
|
52
73
|
均在跨境测试店真实验证(2026-09-02):`ok/applied` → 回读确认 → 审计落库。
|
|
53
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
|
+
|
|
54
77
|
**秒杀字段坑**(OAS 未标、实测撞出):商品级 `quantity_limit` 在 VARIATION 模式必须 `-1`(限量由 SKU 级控制);秒杀价须**低于该 SKU 近 30 天最低成交价**且**不超折扣上限**。这些复杂度被 CLI 吸收,agent 一次成型。
|
|
55
78
|
|
|
56
79
|
## 错误分类(agent 靠此决策)
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
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
|
+
|
|
12
|
+
// src/backendUrl.ts
|
|
13
|
+
var ENV_URLS = {
|
|
14
|
+
"cn-stage": "https://tiktok-api.stage.optima.chat",
|
|
15
|
+
"cn-prod": "https://tiktok-api.yzsgo.com"
|
|
16
|
+
};
|
|
17
|
+
function resolveBackendUrl(env2) {
|
|
18
|
+
if (env2.TIKTOK_BACKEND_URL) return env2.TIKTOK_BACKEND_URL;
|
|
19
|
+
const mapped = env2.OPTIMA_ENV && ENV_URLS[env2.OPTIMA_ENV];
|
|
20
|
+
return mapped || "http://127.0.0.1:3000";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/client.ts
|
|
24
|
+
var BACKEND = resolveBackendUrl(process.env);
|
|
8
25
|
function authHeaders() {
|
|
9
26
|
const explicit = process.env.OPTIMA_TOKEN;
|
|
10
27
|
if (explicit) return { Authorization: `Bearer ${explicit}` };
|
|
@@ -58,8 +75,47 @@ async function listShops() {
|
|
|
58
75
|
return { ok: false, error: { code: "BACKEND_UNREACHABLE", message: e.message, retryable: true } };
|
|
59
76
|
}
|
|
60
77
|
}
|
|
78
|
+
async function createAuthLink() {
|
|
79
|
+
try {
|
|
80
|
+
const r = await fetch(`${BACKEND}/auth/link`, { method: "POST", headers: authHeaders() });
|
|
81
|
+
if (r.status === 401) return { ok: false, error: { code: "AUTH_EXPIRED", message: "\u672A\u767B\u5F55\u6216 token \u5931\u6548", retryable: false } };
|
|
82
|
+
const body = await r.json();
|
|
83
|
+
return { ok: true, data: { authorize_url: body.authorize_url, expires_in_minutes: 15 } };
|
|
84
|
+
} catch (e) {
|
|
85
|
+
return { ok: false, error: { code: "BACKEND_UNREACHABLE", message: e.message, retryable: true } };
|
|
86
|
+
}
|
|
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
|
+
}
|
|
61
102
|
|
|
62
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
|
+
}
|
|
63
119
|
var today = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
64
120
|
var daysAgo = (n) => new Date(Date.now() - n * 864e5).toISOString().slice(0, 10);
|
|
65
121
|
var COMMANDS = [
|
|
@@ -129,6 +185,78 @@ var COMMANDS = [
|
|
|
129
185
|
}
|
|
130
186
|
})
|
|
131
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
|
+
},
|
|
132
260
|
// ===== 写操作(Phase 2):默认 --dry-run,--apply 才真发,成功后回读 =====
|
|
133
261
|
{
|
|
134
262
|
name: "products set-price",
|
|
@@ -186,6 +314,49 @@ var COMMANDS = [
|
|
|
186
314
|
}],
|
|
187
315
|
run: ({ shop, args: args2 }) => proxy("POST", shop, `/product/202309/products/${args2.product}/inventory/update`, { body: inventoryBody(args2) }),
|
|
188
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}`, {})
|
|
189
360
|
}
|
|
190
361
|
];
|
|
191
362
|
function inventoryBody(args2) {
|
|
@@ -268,7 +439,9 @@ function usage() {
|
|
|
268
439
|
const lines = ["tiktok <domain> <verb> --shop <shopId> [--flags]", "", "\u547D\u4EE4\uFF1A"];
|
|
269
440
|
for (const c of COMMANDS) lines.push(` ${c.name.padEnd(22)} ${c.summary}`);
|
|
270
441
|
lines.push(` ${"shops".padEnd(22)} \u67E5\u5F53\u524D\u767B\u5F55\u7528\u6237\u5DF2\u6388\u6743\u5E97\u94FA`);
|
|
271
|
-
lines.push(""
|
|
442
|
+
lines.push(` ${"auth link".padEnd(22)} \u751F\u6210\u5E97\u94FA\u7ED1\u5B9A\u6388\u6743\u94FE\u63A5\uFF08\u53D1\u7ED9\u5546\u5BB6\u70B9\u51FB\uFF09`);
|
|
443
|
+
lines.push(` ${"images upload".padEnd(22)} \u4E0A\u4F20\u672C\u5730\u56FE\u7247\uFF08--file --use-case [--out]\uFF09`);
|
|
444
|
+
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");
|
|
272
445
|
process.stderr.write(lines.join("\n") + "\n");
|
|
273
446
|
}
|
|
274
447
|
var argv = process.argv.slice(2);
|
|
@@ -281,6 +454,27 @@ if (positional[0] === "shops") {
|
|
|
281
454
|
out(await listShops());
|
|
282
455
|
process.exit(0);
|
|
283
456
|
}
|
|
457
|
+
if (positional[0] === "auth" && positional[1] === "link") {
|
|
458
|
+
out(await createAuthLink());
|
|
459
|
+
process.exit(0);
|
|
460
|
+
}
|
|
461
|
+
if (positional[0] === "images" && positional[1] === "upload") {
|
|
462
|
+
if (!args.file) {
|
|
463
|
+
out({ ok: false, error: { code: "VALIDATION", message: "\u7F3A\u5C11 --file", retryable: false } });
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
if (!args.shop) {
|
|
467
|
+
out({ ok: false, error: { code: "VALIDATION", message: "\u7F3A\u5C11 --shop", retryable: false } });
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
const r = await uploadImage(args.shop, args.file, args["use-case"] ?? "MAIN_IMAGE");
|
|
471
|
+
if (r.ok && args.out) {
|
|
472
|
+
const { appendFileSync } = await import("fs");
|
|
473
|
+
appendFileSync(args.out, JSON.stringify(r.data) + "\n");
|
|
474
|
+
}
|
|
475
|
+
out(r);
|
|
476
|
+
process.exit(0);
|
|
477
|
+
}
|
|
284
478
|
var match = findCommand(positional);
|
|
285
479
|
if (!match) {
|
|
286
480
|
out({ ok: false, error: { code: "VALIDATION", message: `\u672A\u77E5\u547D\u4EE4: ${positional.join(" ")}`, retryable: false } });
|
|
@@ -293,6 +487,16 @@ if (!args.shop) {
|
|
|
293
487
|
}
|
|
294
488
|
var ctx = { shop: args.shop, args, actor: args.actor };
|
|
295
489
|
if (match.cmd.write && !("apply" in args)) {
|
|
490
|
+
if (match.cmd.precheck) {
|
|
491
|
+
const pc = await match.cmd.precheck(ctx);
|
|
492
|
+
out({
|
|
493
|
+
ok: true,
|
|
494
|
+
dry_run: true,
|
|
495
|
+
precheck: pc.ok ? pc.data : { error: pc.error },
|
|
496
|
+
message: "listing_check \u9884\u68C0\u7ED3\u679C\uFF0C\u786E\u8BA4\u540E\u52A0 --apply \u6267\u884C\u3002"
|
|
497
|
+
});
|
|
498
|
+
process.exit(0);
|
|
499
|
+
}
|
|
296
500
|
const plan = match.cmd.preview?.(ctx);
|
|
297
501
|
out({
|
|
298
502
|
ok: true,
|
|
@@ -303,9 +507,16 @@ if (match.cmd.write && !("apply" in args)) {
|
|
|
303
507
|
process.exit(0);
|
|
304
508
|
}
|
|
305
509
|
var env = await match.cmd.run(ctx);
|
|
306
|
-
if (match.cmd.write && env.ok
|
|
307
|
-
const
|
|
308
|
-
|
|
510
|
+
if (match.cmd.write && env.ok) {
|
|
511
|
+
const result = { ...env, applied: true };
|
|
512
|
+
if (env.data?.warnings?.length) {
|
|
513
|
+
result.warnings_alert = "\u26A0\uFE0F \u6709\u5B57\u6BB5\u88AB\u9759\u9ED8\u5904\u7406\uFF0C\u89C1 warnings";
|
|
514
|
+
}
|
|
515
|
+
if (match.cmd.readback) {
|
|
516
|
+
const rb = await match.cmd.readback(ctx);
|
|
517
|
+
result.readback = rb.ok ? rb.data : { error: rb.error };
|
|
518
|
+
}
|
|
519
|
+
out(result);
|
|
309
520
|
} else {
|
|
310
521
|
out(env);
|
|
311
522
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optima-chat/tiktok-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "TikTok Shop CLI for Optima agents — 经 tiktok-backend 操作店铺",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"build": "tsup src/index.ts --format esm --clean",
|
|
11
11
|
"dev": "tsx src/index.ts",
|
|
12
12
|
"typecheck": "tsc --noEmit",
|
|
13
|
+
"test": "node --import tsx --test tests/*.test.mts",
|
|
13
14
|
"prepublishOnly": "npm run build"
|
|
14
15
|
},
|
|
15
16
|
"author": "Optima Commerce Team",
|