@optima-chat/tiktok-cli 0.16.1 → 0.18.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 -0
- package/dist/index.js +206 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ tiktok <domain> <verb> --shop <shopId> [--flags]
|
|
|
13
13
|
- `--shop` **必填**(多店显式路由,对应 A 轨「多店必须显式选店」教训;`auth link` 除外)
|
|
14
14
|
- 默认 **JSON 输出**,统一信封 `{ ok, data?, error?, meta? }`
|
|
15
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
|
+
- `OPTIMA_TRACE`(可选,COO 会话设为 `coo/<taskId>`):非空(trim 后)时每个打 backend 的请求都带 `X-Optima-Trace: <值>`,供用量按任务归因(Optima-Chat/optima-coo#645);没设 / 空白则不带。只发 backend,CLI 本就不直连第三方
|
|
16
17
|
|
|
17
18
|
## 命令(v1 只读)
|
|
18
19
|
|
|
@@ -115,6 +116,31 @@ tiktok products set-price --shop <id> --product <pid> --sku <sid> --price 29.90
|
|
|
115
116
|
|
|
116
117
|
**秒杀字段坑**(OAS 未标、实测撞出):商品级 `quantity_limit` 在 VARIATION 模式必须 `-1`(限量由 SKU 级控制);秒杀价须**低于该 SKU 近 30 天最低成交价**且**不超折扣上限**。这些复杂度被 CLI 吸收,agent 一次成型。
|
|
117
118
|
|
|
119
|
+
## 写命令参数清单(`tiktok schema --json`)
|
|
120
|
+
|
|
121
|
+
给 Optima COO 的「写入数值守门」用(#56;optima-coo 的设计文档 `2026-10-01-optima-coo-write-value-guard-design.md` §4.2):COO 放行一条 `tiktok … --apply` 之前,按这份清单把命令里的数换算成店主口径,再和店主批过的 playbook 核对。
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
tiktok schema --json > tiktok-cli@$(node -p "require('./package.json').version").json # 不需要 --shop;输出裸 JSON,不包 {ok,data}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- 覆盖每条 `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`。
|
|
128
|
+
- 标注写在 `src/commands.ts` 每条写命令的 `values` 上(和 `flags` 挨着),`src/schema.ts` 生成并自查:漏写、一个参数写两处、钱没写币种、折扣没写 `means` ⇒ 当场抛错;`tests/schema.test.mts` 钉着。**加写命令或参数时一起改 `values`。**
|
|
129
|
+
- 词表(`semantic`)以 optima-coo `packages/contracts/src/value-guard.ts` 的 `VALUE_SEMANTICS` 为准。
|
|
130
|
+
- `version` 取自 `package.json`。COO 按版本固定清单副本,**改了任何写命令的参数就要发新版本**,否则 COO 那边按旧清单核对。
|
|
131
|
+
- 没有 `repeatable` 的参数:同一个参数写两次 CLI 取后者,COO 按写法错误退回;多个值用逗号连成一个(`--price A:12,B:15`)。
|
|
132
|
+
|
|
133
|
+
数值范围(validate 里查,dry-run 与 `--apply` 共用、在发请求之前):
|
|
134
|
+
|
|
135
|
+
| 参数 | 范围 |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `affiliate open-collab set-commission --rate` | 800–8000 的整数(单位百分比×100:1350 = 13.5%,8%–80%);必填 |
|
|
138
|
+
| `affiliate open-collab settings edit --rate` | 给了就必须是 1–10000 的整数(不超过 100%) |
|
|
139
|
+
| `promotions create-flashsale --hours` | 大于 0、小于 72(平台硬上限) |
|
|
140
|
+
| `products set-price --price`、`promotions create-flashsale --price` | 大于 0 的十进制数,最多两位小数,不要前导零和币种符号;必填 |
|
|
141
|
+
|
|
142
|
+
dry-run 的 `plan[].human` 写换算后的店主口径:改价「SKU S 售价改为 29.90 MYR」、秒杀「秒杀价 9.90 MYR,限量 100 件……持续 48 小时」、佣金「佣金率设为 13.5%(--rate 1350)」、折扣「减 85%(按原价 15% 售卖)」。
|
|
143
|
+
|
|
118
144
|
## 错误分类(agent 靠此决策)
|
|
119
145
|
|
|
120
146
|
`error.code` + `retryable` 让 agent 判断重试还是停止:
|
package/dist/index.js
CHANGED
|
@@ -129,6 +129,15 @@ function authHeaders() {
|
|
|
129
129
|
}
|
|
130
130
|
return {};
|
|
131
131
|
}
|
|
132
|
+
function traceHeaders(env2 = process.env) {
|
|
133
|
+
const v = env2.OPTIMA_TRACE?.trim();
|
|
134
|
+
return v ? { "X-Optima-Trace": v } : {};
|
|
135
|
+
}
|
|
136
|
+
function backendFetch(url, init = {}) {
|
|
137
|
+
const headers = new Headers(init.headers);
|
|
138
|
+
for (const [k, v] of Object.entries(traceHeaders())) headers.set(k, v);
|
|
139
|
+
return fetch(url, { ...init, headers });
|
|
140
|
+
}
|
|
132
141
|
function classify(status, body, shop) {
|
|
133
142
|
const meta = { shop, tiktok_request_id: body?.request_id, http_status: status };
|
|
134
143
|
if (status === 0 || body?.code === 0) {
|
|
@@ -148,7 +157,7 @@ async function proxy(method, shop, path, opts = {}) {
|
|
|
148
157
|
const qs = opts.query ? "?" + new URLSearchParams(opts.query) : "";
|
|
149
158
|
const url = `${BACKEND}/proxy/${shop}${path}${qs}`;
|
|
150
159
|
try {
|
|
151
|
-
const r = await
|
|
160
|
+
const r = await backendFetch(url, {
|
|
152
161
|
method,
|
|
153
162
|
headers: {
|
|
154
163
|
"Content-Type": "application/json",
|
|
@@ -165,7 +174,7 @@ async function proxy(method, shop, path, opts = {}) {
|
|
|
165
174
|
}
|
|
166
175
|
async function listShops() {
|
|
167
176
|
try {
|
|
168
|
-
const r = await
|
|
177
|
+
const r = await backendFetch(`${BACKEND}/auth/shops`, { headers: authHeaders() });
|
|
169
178
|
if (r.status === 401) return { ok: false, error: { code: "AUTH_EXPIRED", message: "\u672A\u767B\u5F55\u6216 token \u5931\u6548", retryable: false } };
|
|
170
179
|
const body = await r.json();
|
|
171
180
|
return { ok: true, data: body.shops };
|
|
@@ -176,7 +185,7 @@ async function listShops() {
|
|
|
176
185
|
async function setShopApp(shopId, serviceId, note) {
|
|
177
186
|
const token = process.env.TIKTOK_ADMIN_TOKEN;
|
|
178
187
|
try {
|
|
179
|
-
const r = await
|
|
188
|
+
const r = await backendFetch(`${BACKEND}/admin/apps/shop-map`, {
|
|
180
189
|
method: "PUT",
|
|
181
190
|
headers: { "Content-Type": "application/json", ...token ? { "x-admin-token": token } : {} },
|
|
182
191
|
body: JSON.stringify({ shopId, serviceId, note })
|
|
@@ -190,7 +199,7 @@ async function setShopApp(shopId, serviceId, note) {
|
|
|
190
199
|
}
|
|
191
200
|
async function availableApps() {
|
|
192
201
|
try {
|
|
193
|
-
const r = await
|
|
202
|
+
const r = await backendFetch(`${BACKEND}/auth/apps`, { headers: authHeaders() });
|
|
194
203
|
if (r.status === 401) return { ok: false, error: { code: "AUTH_EXPIRED", message: "\u672A\u767B\u5F55\u6216 token \u5931\u6548", retryable: false } };
|
|
195
204
|
const body = await r.json().catch(() => ({}));
|
|
196
205
|
if (!r.ok || body?.error) {
|
|
@@ -203,7 +212,7 @@ async function availableApps() {
|
|
|
203
212
|
}
|
|
204
213
|
async function authLink(opts) {
|
|
205
214
|
try {
|
|
206
|
-
const r = await
|
|
215
|
+
const r = await backendFetch(`${BACKEND}/auth/link`, {
|
|
207
216
|
method: "POST",
|
|
208
217
|
headers: { "Content-Type": "application/json", ...authHeaders() },
|
|
209
218
|
body: JSON.stringify({ shop: opts.shop, app: opts.app })
|
|
@@ -222,7 +231,7 @@ async function adminApps(method, opts = {}) {
|
|
|
222
231
|
const url = `${BACKEND}/admin/apps${opts.id ? `/${opts.id}` : ""}`;
|
|
223
232
|
const token = process.env.TIKTOK_ADMIN_TOKEN;
|
|
224
233
|
try {
|
|
225
|
-
const r = await
|
|
234
|
+
const r = await backendFetch(url, {
|
|
226
235
|
method,
|
|
227
236
|
headers: {
|
|
228
237
|
"Content-Type": "application/json",
|
|
@@ -245,7 +254,7 @@ async function adminApps(method, opts = {}) {
|
|
|
245
254
|
async function fetchEvents(shop, query) {
|
|
246
255
|
try {
|
|
247
256
|
const qs = new URLSearchParams(query).toString();
|
|
248
|
-
const r = await
|
|
257
|
+
const r = await backendFetch(`${BACKEND}/events/${shop}${qs ? "?" + qs : ""}`, { headers: authHeaders() });
|
|
249
258
|
if (r.status === 401) return { ok: false, error: { code: "AUTH_EXPIRED", message: "\u672A\u767B\u5F55\u6216 token \u5931\u6548", retryable: false } };
|
|
250
259
|
if (r.status === 404) return { ok: false, error: { code: "SHOP_NOT_AUTHORIZED", message: "\u672A\u627E\u5230\u4F60\u540D\u4E0B\u7684\u5DF2\u6388\u6743\u5E97\u94FA", retryable: false }, meta: { shop } };
|
|
251
260
|
const body = await r.json();
|
|
@@ -256,7 +265,7 @@ async function fetchEvents(shop, query) {
|
|
|
256
265
|
}
|
|
257
266
|
async function ackEvents(shop, body) {
|
|
258
267
|
try {
|
|
259
|
-
const r = await
|
|
268
|
+
const r = await backendFetch(`${BACKEND}/events/${shop}/ack`, {
|
|
260
269
|
method: "POST",
|
|
261
270
|
headers: { "Content-Type": "application/json", ...authHeaders() },
|
|
262
271
|
body: JSON.stringify(body)
|
|
@@ -275,7 +284,7 @@ async function uploadImage(shop, filePath, useCase) {
|
|
|
275
284
|
const form = new FormData();
|
|
276
285
|
form.append("data", new Blob([buf]), basename(filePath));
|
|
277
286
|
form.append("use_case", useCase);
|
|
278
|
-
const r = await
|
|
287
|
+
const r = await backendFetch(`${BACKEND}/proxy-upload/${shop}/image`, { method: "POST", headers: authHeaders(), body: form });
|
|
279
288
|
const body = await r.json().catch(() => ({}));
|
|
280
289
|
if (body?.code === 0) return { ok: true, data: { uri: body.data?.uri, url: body.data?.url, use_case: useCase }, meta: { shop } };
|
|
281
290
|
return { ok: false, error: { code: r.status === 401 ? "AUTH_EXPIRED" : "TIKTOK_ERROR", message: body?.message ?? body?.error ?? `HTTP ${r.status}`, retryable: false }, meta: { shop } };
|
|
@@ -354,6 +363,13 @@ var ADD_TEXT = {
|
|
|
354
363
|
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
364
|
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
365
|
};
|
|
366
|
+
var DISCOUNT_VALUE_PARAMS = {
|
|
367
|
+
// 一口价不带币种,平台按店铺本币收;本 CLI 只接马来站 ⇒ MYR。PRICE_ITEM 只收两位小数
|
|
368
|
+
price: { semantic: "promo_price", type: "pairs", pair: ["sku_id", "decimal"], scale: 2, currency_default: "MYR" },
|
|
369
|
+
// PCT_ITEM:1–99 的整数,「减 N%」(85 = 按原价 15% 卖)
|
|
370
|
+
percent: { semantic: "discount_pct", type: "pairs", pair: ["sku_id", "int"], means: "off" }
|
|
371
|
+
};
|
|
372
|
+
var COMMISSION_RATE = { semantic: "commission_pct", type: "int", factor: 0.01 };
|
|
357
373
|
var COMMANDS = [
|
|
358
374
|
{
|
|
359
375
|
name: "orders list",
|
|
@@ -478,6 +494,7 @@ var COMMANDS = [
|
|
|
478
494
|
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
495
|
flags: ["id"],
|
|
480
496
|
write: true,
|
|
497
|
+
values: { unchecked: ["id"] },
|
|
481
498
|
preview: ({ args: args2 }) => [{
|
|
482
499
|
step: "\u6279\u51C6\u53D6\u6D88\u5355",
|
|
483
500
|
method: "POST",
|
|
@@ -491,6 +508,7 @@ var COMMANDS = [
|
|
|
491
508
|
summary: "\u9A73\u56DE\u53D6\u6D88\u5355\uFF08--id --reason\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u9A73\u3002\u4E0D\u53EF\u9006",
|
|
492
509
|
flags: ["id", "reason"],
|
|
493
510
|
write: true,
|
|
511
|
+
values: { unchecked: ["id", "reason"] },
|
|
494
512
|
preview: ({ args: args2 }) => [{
|
|
495
513
|
step: "\u9A73\u56DE\u53D6\u6D88\u5355",
|
|
496
514
|
method: "POST",
|
|
@@ -509,6 +527,7 @@ var COMMANDS = [
|
|
|
509
527
|
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
528
|
flags: ["id"],
|
|
511
529
|
write: true,
|
|
530
|
+
values: { unchecked: ["id"] },
|
|
512
531
|
preview: ({ args: args2 }) => [{
|
|
513
532
|
step: "\u6279\u51C6\u9000\u8D27\u5355",
|
|
514
533
|
method: "POST",
|
|
@@ -522,6 +541,7 @@ var COMMANDS = [
|
|
|
522
541
|
summary: "\u9A73\u56DE\u9000\u8D27\u5355\uFF08--id --reason\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u9A73\u3002\u4E0D\u53EF\u9006",
|
|
523
542
|
flags: ["id", "reason"],
|
|
524
543
|
write: true,
|
|
544
|
+
values: { unchecked: ["id", "reason"] },
|
|
525
545
|
preview: ({ args: args2 }) => [{
|
|
526
546
|
step: "\u9A73\u56DE\u9000\u8D27\u5355",
|
|
527
547
|
method: "POST",
|
|
@@ -540,6 +560,7 @@ var COMMANDS = [
|
|
|
540
560
|
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
561
|
flags: ["body-file", "order"],
|
|
542
562
|
write: true,
|
|
563
|
+
values: { unchecked: ["order"], opaque: ["body-file"] },
|
|
543
564
|
preview: ({ args: args2 }) => [{
|
|
544
565
|
step: "\u5EFA\u5305\u88F9",
|
|
545
566
|
method: "POST",
|
|
@@ -558,6 +579,7 @@ var COMMANDS = [
|
|
|
558
579
|
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
580
|
flags: ["body-file", "package"],
|
|
560
581
|
write: true,
|
|
582
|
+
values: { unchecked: ["package"], opaque: ["body-file"] },
|
|
561
583
|
preview: ({ args: args2 }) => [{
|
|
562
584
|
step: "\u53D1\u8D27",
|
|
563
585
|
method: "POST",
|
|
@@ -590,6 +612,7 @@ var COMMANDS = [
|
|
|
590
612
|
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
613
|
flags: ["before", "ids"],
|
|
592
614
|
write: true,
|
|
615
|
+
values: { unchecked: ["before", "ids"] },
|
|
593
616
|
preview: ({ args: args2 }) => [{
|
|
594
617
|
step: "\u6807\u4E8B\u4EF6\u5DF2\u5904\u7406",
|
|
595
618
|
method: "POST",
|
|
@@ -946,28 +969,45 @@ var COMMANDS = [
|
|
|
946
969
|
// ===== 写操作(Phase 2):默认 --dry-run,--apply 才真发,成功后回读 =====
|
|
947
970
|
{
|
|
948
971
|
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",
|
|
972
|
+
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
973
|
flags: ["currency", "price", "product", "sku"],
|
|
951
974
|
write: true,
|
|
975
|
+
validate: validateSetPrice,
|
|
976
|
+
values: {
|
|
977
|
+
subject: { flag: "sku", kind: "sku_id" },
|
|
978
|
+
params: {
|
|
979
|
+
price: { semantic: "list_price", type: "decimal", scale: 2, currency_flag: "currency", currency_default: "MYR" },
|
|
980
|
+
currency: { semantic: "currency" }
|
|
981
|
+
},
|
|
982
|
+
unchecked: ["product"]
|
|
983
|
+
},
|
|
952
984
|
preview: ({ args: args2 }) => [{
|
|
953
985
|
step: "\u6539\u4EF7",
|
|
954
986
|
method: "POST",
|
|
955
987
|
path: `/product/202309/products/${args2.product}/prices/update`,
|
|
956
|
-
body: priceBody(args2)
|
|
988
|
+
body: priceBody(args2),
|
|
989
|
+
human: priceHuman(args2)
|
|
957
990
|
}],
|
|
958
991
|
run: ({ shop, args: args2 }) => proxy("POST", shop, `/product/202309/products/${args2.product}/prices/update`, { body: priceBody(args2) }),
|
|
959
992
|
readback: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/products/${args2.product}`, {})
|
|
960
993
|
},
|
|
961
994
|
{
|
|
962
995
|
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",
|
|
996
|
+
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
997
|
flags: ["hours", "limit", "per-user", "price", "product", "sku", "start-in-min", "title"],
|
|
965
998
|
write: true,
|
|
999
|
+
validate: validateFlashsale,
|
|
1000
|
+
values: {
|
|
1001
|
+
subject: { flag: "sku", kind: "sku_id" },
|
|
1002
|
+
// 秒杀价不带币种,平台按店铺本币收;本 CLI 只接马来站 ⇒ MYR
|
|
1003
|
+
params: { price: { semantic: "promo_price", type: "decimal", scale: 2, currency_default: "MYR" } },
|
|
1004
|
+
unchecked: ["hours", "limit", "per-user", "product", "start-in-min", "title"]
|
|
1005
|
+
},
|
|
966
1006
|
preview: ({ args: args2 }) => {
|
|
967
1007
|
const { begin, end } = flashWindow(args2);
|
|
968
1008
|
return [
|
|
969
1009
|
{ 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) }
|
|
1010
|
+
{ 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
1011
|
];
|
|
972
1012
|
},
|
|
973
1013
|
run: async ({ shop, args: args2 }) => {
|
|
@@ -988,6 +1028,7 @@ var COMMANDS = [
|
|
|
988
1028
|
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
1029
|
flags: ["days", "percent", "price", "product", "title"],
|
|
990
1030
|
write: true,
|
|
1031
|
+
values: { params: DISCOUNT_VALUE_PARAMS, unchecked: ["days", "product", "title"] },
|
|
991
1032
|
validate: validateCreateDiscount,
|
|
992
1033
|
preview: ({ args: args2 }) => {
|
|
993
1034
|
const { begin, end } = discountWindow(args2);
|
|
@@ -1062,6 +1103,7 @@ var COMMANDS = [
|
|
|
1062
1103
|
summary: ADD_TEXT.SUM_ADD,
|
|
1063
1104
|
flags: ["id", "percent", "price", "product"],
|
|
1064
1105
|
write: true,
|
|
1106
|
+
values: { params: DISCOUNT_VALUE_PARAMS, unchecked: ["id", "product"] },
|
|
1065
1107
|
validate: validateAddProducts,
|
|
1066
1108
|
// 🔴 命令块只字面读 args.id / args.product,值旗标经 discountValueFlag / discountValueRaw(它们字面读 args.percent / args.price);
|
|
1067
1109
|
// ⛔ 不得提到 discountHuman / validateCreateDiscount / discountActivityBody / discountWindow(它们读 days / title,本命令没有)
|
|
@@ -1081,6 +1123,7 @@ var COMMANDS = [
|
|
|
1081
1123
|
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
1124
|
flags: ["id"],
|
|
1083
1125
|
write: true,
|
|
1126
|
+
values: { unchecked: ["id"] },
|
|
1084
1127
|
preview: ({ args: args2 }) => [{ step: "\u7EC8\u6B62\u6D3B\u52A8", method: "POST", path: `/promotion/202309/activities/${args2.id}/deactivate`, body: {} }],
|
|
1085
1128
|
run: ({ shop, args: args2 }) => proxy("POST", shop, `/promotion/202309/activities/${args2.id}/deactivate`, { body: {} })
|
|
1086
1129
|
},
|
|
@@ -1089,6 +1132,7 @@ var COMMANDS = [
|
|
|
1089
1132
|
summary: "\u6539 SKU \u5E93\u5B58\uFF08--product --sku --warehouse --quantity\uFF09\u3002\u5199\u64CD\u4F5C,\u9ED8\u8BA4 dry-run",
|
|
1090
1133
|
flags: ["product", "quantity", "sku", "warehouse"],
|
|
1091
1134
|
write: true,
|
|
1135
|
+
values: { unchecked: ["product", "quantity", "sku", "warehouse"] },
|
|
1092
1136
|
preview: ({ args: args2 }) => [{
|
|
1093
1137
|
step: "\u6539\u5E93\u5B58",
|
|
1094
1138
|
method: "POST",
|
|
@@ -1103,6 +1147,7 @@ var COMMANDS = [
|
|
|
1103
1147
|
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
1148
|
flags: ["body-file"],
|
|
1105
1149
|
write: true,
|
|
1150
|
+
values: { opaque: ["body-file"] },
|
|
1106
1151
|
preview: () => [{ step: "listing_check \u9884\u68C0 \u2192 \u5EFA\u54C1", method: "POST", path: "/product/202309/products/listing_check", body: void 0 }],
|
|
1107
1152
|
precheck: ({ shop, args: args2 }) => proxy("POST", shop, "/product/202309/products/listing_check", { body: readBodyFile(args2["body-file"]) }),
|
|
1108
1153
|
run: async ({ shop, args: args2 }) => {
|
|
@@ -1118,6 +1163,7 @@ var COMMANDS = [
|
|
|
1118
1163
|
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
1164
|
flags: ["body-file", "product"],
|
|
1120
1165
|
write: true,
|
|
1166
|
+
values: { unchecked: ["product"], opaque: ["body-file"] },
|
|
1121
1167
|
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
1168
|
run: async ({ shop, args: args2 }) => {
|
|
1123
1169
|
const cur = await proxy("GET", shop, `/product/202309/products/${args2.product}`, {});
|
|
@@ -1132,6 +1178,8 @@ var COMMANDS = [
|
|
|
1132
1178
|
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
1179
|
flags: ["body-file", "product"],
|
|
1134
1180
|
write: true,
|
|
1181
|
+
values: { unchecked: ["body-file", "product"] },
|
|
1182
|
+
// 只从文件里取 main_images,带不进价格
|
|
1135
1183
|
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
1184
|
run: ({ shop, args: args2 }) => proxy("POST", shop, `/product/202309/products/${args2.product}/partial_edit`, { body: { main_images: readBodyFile(args2["body-file"]).main_images } }),
|
|
1137
1185
|
readback: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/products/${args2.product}`, {})
|
|
@@ -1141,6 +1189,7 @@ var COMMANDS = [
|
|
|
1141
1189
|
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
1190
|
flags: ["body-file", "product"],
|
|
1143
1191
|
write: true,
|
|
1192
|
+
values: { unchecked: ["product"], opaque: ["body-file"] },
|
|
1144
1193
|
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
1194
|
run: ({ shop, args: args2 }) => proxy("POST", shop, `/product/202309/products/${args2.product}/partial_edit`, { body: readBodyFile(args2["body-file"]) }),
|
|
1146
1195
|
readback: ({ shop, args: args2 }) => proxy("GET", shop, `/product/202309/products/${args2.product}`, {})
|
|
@@ -1248,6 +1297,7 @@ var COMMANDS = [
|
|
|
1248
1297
|
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
1298
|
flags: ["body-file"],
|
|
1250
1299
|
write: true,
|
|
1300
|
+
values: { opaque: ["body-file"] },
|
|
1251
1301
|
preview: ({ args: args2 }) => [{
|
|
1252
1302
|
step: "\u53D1\u5B9A\u5411\u9080\u7EA6",
|
|
1253
1303
|
method: "POST",
|
|
@@ -1267,6 +1317,7 @@ var COMMANDS = [
|
|
|
1267
1317
|
summary: "\u6539\u5B9A\u5411\u9080\u7EA6\uFF08--id --body-file collab.json\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u63D0\u4EA4",
|
|
1268
1318
|
flags: ["body-file", "id"],
|
|
1269
1319
|
write: true,
|
|
1320
|
+
values: { unchecked: ["id"], opaque: ["body-file"] },
|
|
1270
1321
|
preview: ({ args: args2 }) => [{
|
|
1271
1322
|
step: "\u6539\u5B9A\u5411\u9080\u7EA6",
|
|
1272
1323
|
method: "PUT",
|
|
@@ -1283,6 +1334,7 @@ var COMMANDS = [
|
|
|
1283
1334
|
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
1335
|
flags: ["id"],
|
|
1285
1336
|
write: true,
|
|
1337
|
+
values: { unchecked: ["id"] },
|
|
1286
1338
|
preview: ({ args: args2 }) => [{
|
|
1287
1339
|
step: "\u64A4\u56DE\u5B9A\u5411\u9080\u7EA6",
|
|
1288
1340
|
method: "DELETE",
|
|
@@ -1296,6 +1348,7 @@ var COMMANDS = [
|
|
|
1296
1348
|
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
1349
|
flags: ["id"],
|
|
1298
1350
|
write: true,
|
|
1351
|
+
values: { unchecked: ["id"] },
|
|
1299
1352
|
preview: ({ args: args2 }) => [{
|
|
1300
1353
|
step: "\u751F\u6210\u9080\u7EA6\u94FE\u63A5",
|
|
1301
1354
|
method: "POST",
|
|
@@ -1309,6 +1362,7 @@ var COMMANDS = [
|
|
|
1309
1362
|
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
1363
|
flags: ["creator"],
|
|
1311
1364
|
write: true,
|
|
1365
|
+
values: { unchecked: ["creator"] },
|
|
1312
1366
|
preview: ({ args: args2 }) => [{
|
|
1313
1367
|
step: "\u5F00\u79C1\u4FE1\u4F1A\u8BDD",
|
|
1314
1368
|
method: "POST",
|
|
@@ -1324,6 +1378,7 @@ var COMMANDS = [
|
|
|
1324
1378
|
summary: "\u7ED9\u8FBE\u4EBA\u53D1\u79C1\u4FE1\uFF08--conversation --content [--msg-type TEXT]\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u53D1",
|
|
1325
1379
|
flags: ["content", "conversation", "msg-type"],
|
|
1326
1380
|
write: true,
|
|
1381
|
+
values: { unchecked: ["content", "conversation", "msg-type"] },
|
|
1327
1382
|
preview: ({ args: args2 }) => [{
|
|
1328
1383
|
step: "\u53D1\u79C1\u4FE1",
|
|
1329
1384
|
method: "POST",
|
|
@@ -1340,6 +1395,7 @@ var COMMANDS = [
|
|
|
1340
1395
|
flags: ["id", "reject-reason", "result"],
|
|
1341
1396
|
listKey: "sample_applications",
|
|
1342
1397
|
write: true,
|
|
1398
|
+
values: { unchecked: ["id", "reject-reason", "result"] },
|
|
1343
1399
|
preview: ({ args: args2 }) => [{
|
|
1344
1400
|
step: "\u5BA1\u6279\u6837\u54C1\u7533\u8BF7",
|
|
1345
1401
|
method: "POST",
|
|
@@ -1360,11 +1416,14 @@ var COMMANDS = [
|
|
|
1360
1416
|
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
1417
|
flags: ["product", "rate"],
|
|
1362
1418
|
write: true,
|
|
1419
|
+
validate: validateCommissionRate,
|
|
1420
|
+
values: { subject: { flag: "product", kind: "product_id" }, params: { rate: COMMISSION_RATE } },
|
|
1363
1421
|
preview: ({ args: args2 }) => [{
|
|
1364
1422
|
step: "\u8BBE\u6309\u54C1\u516C\u5F00\u5408\u4F5C\u4F63\u91D1",
|
|
1365
1423
|
method: "POST",
|
|
1366
1424
|
path: "/affiliate_seller/202412/open_collaborations",
|
|
1367
|
-
body: { product_id: args2.product, commission_rate: Number(args2.rate) }
|
|
1425
|
+
body: { product_id: args2.product, commission_rate: Number(args2.rate) },
|
|
1426
|
+
human: `\u5546\u54C1 ${args2.product} \u7684\u516C\u5F00\u5408\u4F5C\u4F63\u91D1\u7387\u8BBE\u4E3A ${rateHuman(args2.rate)}`
|
|
1368
1427
|
}],
|
|
1369
1428
|
run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202412/open_collaborations", {
|
|
1370
1429
|
body: { product_id: args2.product, commission_rate: Number(args2.rate) }
|
|
@@ -1379,6 +1438,7 @@ var COMMANDS = [
|
|
|
1379
1438
|
summary: "\u79FB\u9664\u54C1\u7684\u516C\u5F00\u5408\u4F5C\uFF08--product product_id\uFF09\u3002\u9ED8\u8BA4 dry-run\uFF0C--apply \u624D\u53D1",
|
|
1380
1439
|
flags: ["product"],
|
|
1381
1440
|
write: true,
|
|
1441
|
+
values: { unchecked: ["product"] },
|
|
1382
1442
|
preview: ({ args: args2 }) => [{
|
|
1383
1443
|
step: "\u79FB\u9664\u516C\u5F00\u5408\u4F5C",
|
|
1384
1444
|
method: "DELETE",
|
|
@@ -1389,14 +1449,17 @@ var COMMANDS = [
|
|
|
1389
1449
|
},
|
|
1390
1450
|
{
|
|
1391
1451
|
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",
|
|
1452
|
+
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
1453
|
flags: ["enable", "rate"],
|
|
1394
1454
|
write: true,
|
|
1455
|
+
validate: validateSettingsRate,
|
|
1456
|
+
values: { params: { rate: COMMISSION_RATE }, unchecked: ["enable"] },
|
|
1395
1457
|
preview: ({ args: args2 }) => [{
|
|
1396
1458
|
step: "\u6539\u5168\u5C40\u9ED8\u8BA4",
|
|
1397
1459
|
method: "POST",
|
|
1398
1460
|
path: "/affiliate_seller/202405/open_collaboration_settings",
|
|
1399
|
-
body: { auto_add_product: { commission_rate: Number(args2.rate), enable: args2.enable === "true" } }
|
|
1461
|
+
body: { auto_add_product: { commission_rate: Number(args2.rate), enable: args2.enable === "true" } },
|
|
1462
|
+
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
1463
|
}],
|
|
1401
1464
|
run: ({ shop, args: args2 }) => proxy("POST", shop, "/affiliate_seller/202405/open_collaboration_settings", {
|
|
1402
1465
|
body: { auto_add_product: { commission_rate: Number(args2.rate), enable: args2.enable === "true" } }
|
|
@@ -1515,6 +1578,42 @@ function priceBody(args2) {
|
|
|
1515
1578
|
skus: [{ id: args2.sku, price: { amount: args2.price, currency: args2.currency ?? "MYR" } }]
|
|
1516
1579
|
};
|
|
1517
1580
|
}
|
|
1581
|
+
var PRICE_VALUE = /^(0|[1-9]\d*)(\.\d{1,2})?$/;
|
|
1582
|
+
var priceOk = (raw) => raw !== void 0 && PRICE_VALUE.test(raw) && Number(raw) > 0;
|
|
1583
|
+
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`;
|
|
1584
|
+
function validateSetPrice({ args: args2 }) {
|
|
1585
|
+
return priceOk(args2.price) ? { ok: true } : { ok: false, message: priceMessage(args2.price) };
|
|
1586
|
+
}
|
|
1587
|
+
function validateFlashsale({ args: args2 }) {
|
|
1588
|
+
if (!priceOk(args2.price)) return { ok: false, message: priceMessage(args2.price) };
|
|
1589
|
+
if (args2.hours !== void 0) {
|
|
1590
|
+
const n = Number(args2.hours);
|
|
1591
|
+
if (!/^\d+(\.\d+)?$/.test(args2.hours) || !(n > 0 && n < 72)) {
|
|
1592
|
+
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` };
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
return { ok: true };
|
|
1596
|
+
}
|
|
1597
|
+
function rateProblem(raw, min, max) {
|
|
1598
|
+
if (raw !== void 0 && /^\d+$/.test(raw) && Number(raw) >= min && Number(raw) <= max) return null;
|
|
1599
|
+
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`;
|
|
1600
|
+
}
|
|
1601
|
+
function validateCommissionRate({ args: args2 }) {
|
|
1602
|
+
const p = rateProblem(args2.rate, 800, 8e3);
|
|
1603
|
+
return p ? { ok: false, message: p } : { ok: true };
|
|
1604
|
+
}
|
|
1605
|
+
function validateSettingsRate({ args: args2 }) {
|
|
1606
|
+
if (args2.rate === void 0) return { ok: true };
|
|
1607
|
+
const p = rateProblem(args2.rate, 1, 1e4);
|
|
1608
|
+
return p ? { ok: false, message: p } : { ok: true };
|
|
1609
|
+
}
|
|
1610
|
+
function priceHuman(args2) {
|
|
1611
|
+
return `\u5546\u54C1 ${args2.product} \u7684 SKU ${args2.sku} \u552E\u4EF7\u6539\u4E3A ${args2.price} ${args2.currency ?? "MYR"}`;
|
|
1612
|
+
}
|
|
1613
|
+
function flashHuman(args2) {
|
|
1614
|
+
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`;
|
|
1615
|
+
}
|
|
1616
|
+
var rateHuman = (raw) => `${Number(raw) / 100}%\uFF08--rate ${raw}\uFF09`;
|
|
1518
1617
|
function findCommand(tokens) {
|
|
1519
1618
|
for (const cmd of [...COMMANDS].sort((a, b) => b.name.length - a.name.length)) {
|
|
1520
1619
|
const parts = cmd.name.split(" ");
|
|
@@ -2007,6 +2106,86 @@ async function runAddProducts(shop, id, flag, raw, product) {
|
|
|
2007
2106
|
return { ok: true, data, meta: put.meta };
|
|
2008
2107
|
}
|
|
2009
2108
|
|
|
2109
|
+
// src/schema.ts
|
|
2110
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2111
|
+
var VALUE_SEMANTICS = [
|
|
2112
|
+
"list_price",
|
|
2113
|
+
// SKU 挂牌价
|
|
2114
|
+
"promo_price",
|
|
2115
|
+
// 促销售价:折扣一口价、秒杀价
|
|
2116
|
+
"campaign_price",
|
|
2117
|
+
// 平台活动报价
|
|
2118
|
+
"discount_pct",
|
|
2119
|
+
// 降幅百分数(means 说是「减 N%」还是「按 N% 卖」)
|
|
2120
|
+
"ad_target_roi",
|
|
2121
|
+
// GMV Max 目标 ROI
|
|
2122
|
+
"ad_daily_budget",
|
|
2123
|
+
// GMV Max 日预算
|
|
2124
|
+
"commission_pct",
|
|
2125
|
+
// 达人佣金百分数
|
|
2126
|
+
"currency"
|
|
2127
|
+
// 同一条写里金额的币种
|
|
2128
|
+
];
|
|
2129
|
+
var MONEY = ["list_price", "promo_price", "campaign_price", "ad_daily_budget"];
|
|
2130
|
+
function cliVersion() {
|
|
2131
|
+
return JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
2132
|
+
}
|
|
2133
|
+
var dash = (f) => `--${f}`;
|
|
2134
|
+
var sorted = (xs) => [...xs].sort().map(dash);
|
|
2135
|
+
function checkParam(where, p, params, problems) {
|
|
2136
|
+
if (!VALUE_SEMANTICS.includes(p.semantic)) problems.push(`${where}\uFF1Asemantic ${JSON.stringify(p.semantic)} \u4E0D\u5728\u8BCD\u8868\u91CC`);
|
|
2137
|
+
if (p.semantic === "currency") {
|
|
2138
|
+
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`);
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
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`);
|
|
2142
|
+
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`);
|
|
2143
|
+
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`);
|
|
2144
|
+
if (p.round !== void 0 && p.scale === void 0) problems.push(`${where}\uFF1Around \u8981\u548C scale \u4E00\u8D77\u5199`);
|
|
2145
|
+
if (p.type === "pairs" !== (p.pair !== void 0)) problems.push(`${where}\uFF1Apairs \u548C pair \u8981\u4E00\u8D77\u5199`);
|
|
2146
|
+
if (p.factor !== void 0 && !(Number.isFinite(p.factor) && p.factor > 0)) problems.push(`${where}\uFF1Afactor \u8981\u662F\u6B63\u6570`);
|
|
2147
|
+
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`);
|
|
2148
|
+
}
|
|
2149
|
+
function buildSchema(commands, version) {
|
|
2150
|
+
const problems = [];
|
|
2151
|
+
const out2 = { tool: "tiktok-cli", version, global_flags: GLOBAL_FLAGS.map(dash), commands: {} };
|
|
2152
|
+
for (const cmd of commands) {
|
|
2153
|
+
if (!cmd.write) continue;
|
|
2154
|
+
const v = cmd.values;
|
|
2155
|
+
if (!v) {
|
|
2156
|
+
problems.push(`${cmd.name}\uFF1A\u5199\u547D\u4EE4\u6CA1\u6709 values \u6807\u6CE8`);
|
|
2157
|
+
continue;
|
|
2158
|
+
}
|
|
2159
|
+
const params = v.params ?? {};
|
|
2160
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2161
|
+
const claim = (flag, place) => {
|
|
2162
|
+
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`);
|
|
2163
|
+
if (seen.has(flag)) problems.push(`${cmd.name}\uFF1A--${flag} \u540C\u65F6\u5728 ${seen.get(flag)} \u548C ${place}`);
|
|
2164
|
+
seen.set(flag, place);
|
|
2165
|
+
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`);
|
|
2166
|
+
};
|
|
2167
|
+
for (const f of Object.keys(params)) claim(f, "params");
|
|
2168
|
+
if (v.subject) claim(v.subject.flag, "subject");
|
|
2169
|
+
for (const f of v.unchecked ?? []) claim(f, "unchecked");
|
|
2170
|
+
for (const f of v.opaque ?? []) claim(f, "opaque");
|
|
2171
|
+
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`);
|
|
2172
|
+
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`);
|
|
2173
|
+
for (const [f, p] of Object.entries(params)) checkParam(`${cmd.name} --${f}`, p, params, problems);
|
|
2174
|
+
const m = { write: true, params: {} };
|
|
2175
|
+
if (v.subject) m.subject = { flag: dash(v.subject.flag), kind: v.subject.kind };
|
|
2176
|
+
for (const [f, p] of Object.entries(params)) {
|
|
2177
|
+
m.params[dash(f)] = { ...p, ...p.semantic === "currency" ? { type: "string" } : {}, ...p.currency_flag ? { currency_flag: dash(p.currency_flag) } : {} };
|
|
2178
|
+
}
|
|
2179
|
+
if (v.repeatable?.length) m.repeatable = sorted(v.repeatable);
|
|
2180
|
+
if (v.opaque?.length) m.opaque = sorted(v.opaque);
|
|
2181
|
+
if (v.unchecked?.length) m.unchecked = sorted(v.unchecked);
|
|
2182
|
+
out2.commands[cmd.name] = m;
|
|
2183
|
+
}
|
|
2184
|
+
if (problems.length) throw new Error(`\u5199\u547D\u4EE4\u6E05\u5355\u4E0D\u5B8C\u6574\uFF1A
|
|
2185
|
+
- ${problems.join("\n- ")}`);
|
|
2186
|
+
return out2;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2010
2189
|
// src/index.ts
|
|
2011
2190
|
var NO_SHOP_COMMANDS = /* @__PURE__ */ new Set(["auth link", "apps add", "apps list", "apps disable", "apps available", "apps set-shop"]);
|
|
2012
2191
|
var SHOP_OPTIONAL_BUT_READ = /* @__PURE__ */ new Set(["auth link", "apps set-shop"]);
|
|
@@ -2063,11 +2242,13 @@ function normalizeBooleanFlags(args2) {
|
|
|
2063
2242
|
function out(obj) {
|
|
2064
2243
|
process.stdout.write(JSON.stringify(obj, null, 2) + "\n");
|
|
2065
2244
|
}
|
|
2245
|
+
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
2246
|
function usage() {
|
|
2067
2247
|
const lines = ["tiktok <domain> <verb> --shop <shopId> [--flags]", "", "\u547D\u4EE4\uFF1A"];
|
|
2068
2248
|
for (const c of COMMANDS) lines.push(` ${c.name.padEnd(22)} ${c.summary}`);
|
|
2069
2249
|
lines.push(` ${"shops".padEnd(22)} \u67E5\u5F53\u524D\u767B\u5F55\u7528\u6237\u5DF2\u6388\u6743\u5E97\u94FA`);
|
|
2070
2250
|
lines.push(` ${"images upload".padEnd(22)} \u4E0A\u4F20\u672C\u5730\u56FE\u7247\uFF08--file --use-case [--out]\uFF09`);
|
|
2251
|
+
lines.push(` ${"schema".padEnd(22)} ${SCHEMA.summary}`);
|
|
2071
2252
|
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
2253
|
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
2254
|
process.stderr.write(lines.join("\n") + "\n");
|
|
@@ -2106,6 +2287,15 @@ if (positional[0] === "shops") {
|
|
|
2106
2287
|
out(await listShops());
|
|
2107
2288
|
process.exit(0);
|
|
2108
2289
|
}
|
|
2290
|
+
if (positional[0] === "schema") {
|
|
2291
|
+
if (wantsHelp) {
|
|
2292
|
+
process.stderr.write(commandUsage(SCHEMA));
|
|
2293
|
+
process.exit(0);
|
|
2294
|
+
}
|
|
2295
|
+
rejectUnknownFlags(SCHEMA.name, SCHEMA.flags, positional.slice(1), true);
|
|
2296
|
+
out(buildSchema(COMMANDS, cliVersion()));
|
|
2297
|
+
process.exit(0);
|
|
2298
|
+
}
|
|
2109
2299
|
if (positional[0] === "images" && positional[1] === "upload") {
|
|
2110
2300
|
if (wantsHelp) {
|
|
2111
2301
|
process.stderr.write(commandUsage(IMAGES_UPLOAD));
|