eprolo-cli 1.0.32 → 1.0.33
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/bin/eprolo-cli.js +107 -243
- package/package.json +1 -1
package/bin/eprolo-cli.js
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const os = require("os");
|
|
6
6
|
const path = require("path");
|
|
7
|
+
const http = require("http");
|
|
7
8
|
|
|
9
|
+
const LOGIN_API = "http://43.153.35.208:8081/aaa";
|
|
8
10
|
const CONFIG_DIR = path.join(os.homedir(), ".dxb-toolkit");
|
|
9
11
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
10
12
|
|
|
11
13
|
// ---------------------------------------------------------------------------
|
|
12
|
-
//
|
|
14
|
+
// 凭据文件读写(~/.dxb-toolkit/config.json)
|
|
13
15
|
// 框架判定连接成功的唯一依据:凭据文件出现
|
|
14
16
|
// 框架读顶层明文 account 字段做授权卡片标签
|
|
15
17
|
// ---------------------------------------------------------------------------
|
|
@@ -21,7 +23,6 @@ function readConfig() {
|
|
|
21
23
|
|
|
22
24
|
function writeConfig(config) {
|
|
23
25
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
24
|
-
// 原子写:临时文件 + rename,避免 MCP 进程读到半截 JSON
|
|
25
26
|
const tmp = CONFIG_FILE + ".tmp";
|
|
26
27
|
fs.writeFileSync(tmp, JSON.stringify(config, null, 2), "utf8");
|
|
27
28
|
fs.renameSync(tmp, CONFIG_FILE);
|
|
@@ -57,29 +58,63 @@ function readFlag(argv, flag) {
|
|
|
57
58
|
return argv[idx + 1];
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// HTTP POST 请求
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
64
|
|
|
65
|
-
function
|
|
66
|
-
return
|
|
65
|
+
function postJson(url, body) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const target = new URL(url);
|
|
68
|
+
const payload = JSON.stringify(body);
|
|
69
|
+
const req = http.request({
|
|
70
|
+
hostname: target.hostname,
|
|
71
|
+
port: target.port,
|
|
72
|
+
path: `${target.pathname}${target.search}`,
|
|
73
|
+
method: "POST",
|
|
74
|
+
headers: {
|
|
75
|
+
"content-type": "application/json",
|
|
76
|
+
"content-length": Buffer.byteLength(payload)
|
|
77
|
+
}
|
|
78
|
+
}, (res) => {
|
|
79
|
+
const chunks = [];
|
|
80
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
81
|
+
res.on("end", () => {
|
|
82
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
83
|
+
let data;
|
|
84
|
+
try {
|
|
85
|
+
data = text ? JSON.parse(text) : {};
|
|
86
|
+
} catch (_) {
|
|
87
|
+
data = { raw: text };
|
|
88
|
+
}
|
|
89
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
90
|
+
const err = new Error(`HTTP ${res.statusCode} ${res.statusMessage || ""}`.trim());
|
|
91
|
+
err.response = data;
|
|
92
|
+
reject(err);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
resolve(data);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
req.on("error", reject);
|
|
99
|
+
req.write(payload);
|
|
100
|
+
req.end();
|
|
101
|
+
});
|
|
67
102
|
}
|
|
68
103
|
|
|
69
104
|
// ---------------------------------------------------------------------------
|
|
70
105
|
// D2 · login 命令
|
|
71
106
|
// 输入契约(框架 → CLI):
|
|
72
107
|
// userCode ← env 通道 process.env.DXB_USER_CODE
|
|
73
|
-
// defaultShop ← arg 通道 --default-shop
|
|
108
|
+
// defaultShop ← arg 通道 --default-shop
|
|
74
109
|
// 行为契约:
|
|
75
110
|
// 成功 → 写凭据文件 + exit 0
|
|
76
111
|
// 失败 → 非零退出 + stderr 错误信息
|
|
77
|
-
// ❌ 禁止交互式 prompt
|
|
112
|
+
// ❌ 禁止交互式 prompt
|
|
78
113
|
// ---------------------------------------------------------------------------
|
|
79
114
|
|
|
80
|
-
function login(argv) {
|
|
115
|
+
async function login(argv) {
|
|
81
116
|
// env 通道:框架通过 fieldInjection 注入 DXB_USER_CODE
|
|
82
|
-
const userCode = process.env.DXB_USER_CODE || readFlag(argv, "--user-code") || "
|
|
117
|
+
const userCode = process.env.DXB_USER_CODE || readFlag(argv, "--user-code") || readFlag(argv, "--user-code");
|
|
83
118
|
|
|
84
119
|
// arg 通道:框架通过 fieldInjection 追加 --default-shop
|
|
85
120
|
const defaultShop =
|
|
@@ -87,27 +122,49 @@ function login(argv) {
|
|
|
87
122
|
readFlag(argv, "--default-store-name") ||
|
|
88
123
|
readFlag(argv, "--store-name") ||
|
|
89
124
|
process.env.DXB_STORE_NAME ||
|
|
90
|
-
process.env.DXB_DEFAULT_STORE_NAME ||
|
|
91
125
|
null;
|
|
92
126
|
|
|
93
|
-
//
|
|
127
|
+
// 账号标识
|
|
94
128
|
const account =
|
|
95
129
|
readFlag(argv, "--account") ||
|
|
96
130
|
process.env.DXB_ACCOUNT ||
|
|
97
131
|
defaultShop ||
|
|
98
|
-
|
|
132
|
+
null;
|
|
133
|
+
|
|
134
|
+
if (!userCode) {
|
|
135
|
+
console.error(JSON.stringify({
|
|
136
|
+
success: false,
|
|
137
|
+
errorMessage: "缺少用户密文:请在授权弹窗中填写"
|
|
138
|
+
}));
|
|
139
|
+
process.exit(2);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 调用登录接口验证密文
|
|
143
|
+
let loginResult;
|
|
144
|
+
try {
|
|
145
|
+
loginResult = await postJson(LOGIN_API, { userCode, defaultShop });
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error(JSON.stringify({
|
|
148
|
+
success: false,
|
|
149
|
+
errorMessage: "密文验证失败,请到账号设置页重新复制",
|
|
150
|
+
detail: err.message,
|
|
151
|
+
response: err.response || null
|
|
152
|
+
}));
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
99
155
|
|
|
100
156
|
// 写凭据文件(D3 契约:顶层明文 account + version)
|
|
157
|
+
const finalAccount = account || loginResult.account || "unknown";
|
|
101
158
|
const config = {
|
|
102
|
-
account,
|
|
159
|
+
account: finalAccount,
|
|
103
160
|
userCode,
|
|
104
161
|
version: 1
|
|
105
162
|
};
|
|
106
163
|
if (defaultShop) config.defaultShop = defaultShop;
|
|
164
|
+
if (loginResult.accessToken) config.accessToken = loginResult.accessToken;
|
|
107
165
|
|
|
108
166
|
writeConfig(config);
|
|
109
167
|
|
|
110
|
-
// 不打印任何敏感值
|
|
111
168
|
console.log(JSON.stringify({
|
|
112
169
|
success: true,
|
|
113
170
|
connected: true,
|
|
@@ -118,19 +175,24 @@ function login(argv) {
|
|
|
118
175
|
}
|
|
119
176
|
|
|
120
177
|
// ---------------------------------------------------------------------------
|
|
121
|
-
// auth status
|
|
178
|
+
// auth status — 查看授权状态(凭据文件是否存在)
|
|
122
179
|
// ---------------------------------------------------------------------------
|
|
123
180
|
|
|
124
181
|
function authStatus() {
|
|
125
182
|
const config = readConfig();
|
|
183
|
+
const connected = fs.existsSync(CONFIG_FILE);
|
|
126
184
|
console.log(JSON.stringify({
|
|
127
|
-
connected
|
|
185
|
+
connected,
|
|
128
186
|
account: config.account || null,
|
|
129
187
|
defaultShop: config.defaultShop || null
|
|
130
188
|
}, null, 2));
|
|
131
189
|
process.exit(0);
|
|
132
190
|
}
|
|
133
191
|
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// auth logout — 退出授权(删除凭据文件)
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
134
196
|
function authLogout() {
|
|
135
197
|
if (fs.existsSync(CONFIG_FILE)) fs.unlinkSync(CONFIG_FILE);
|
|
136
198
|
console.log(JSON.stringify({ success: true }, null, 2));
|
|
@@ -138,174 +200,46 @@ function authLogout() {
|
|
|
138
200
|
}
|
|
139
201
|
|
|
140
202
|
// ---------------------------------------------------------------------------
|
|
141
|
-
//
|
|
142
|
-
// ---------------------------------------------------------------------------
|
|
143
|
-
|
|
144
|
-
function requireAuth(args) {
|
|
145
|
-
const config = readConfig();
|
|
146
|
-
const userCode = args.userCode || args.user_code || process.env.DXB_USER_CODE || config.userCode || "mock-user-code";
|
|
147
|
-
return { userCode };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function requireTaskTarget(args) {
|
|
151
|
-
const config = readConfig();
|
|
152
|
-
const ptName = args.ptName;
|
|
153
|
-
const storeName =
|
|
154
|
-
args.storeName ||
|
|
155
|
-
args.defaultShop ||
|
|
156
|
-
args.defaultStoreName ||
|
|
157
|
-
process.env.DXB_STORE_NAME ||
|
|
158
|
-
config.defaultShop ||
|
|
159
|
-
config.defaultStoreName ||
|
|
160
|
-
"mock-store";
|
|
161
|
-
return { ptName, storeName };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async function postJson(baseUrl, pathName, body) {
|
|
165
|
-
const url = `${baseUrl}${pathName}`;
|
|
166
|
-
if (url.includes("/submit")) {
|
|
167
|
-
return { success: true, message: "Task submitted successfully", taskKey: body.taskKey };
|
|
168
|
-
}
|
|
169
|
-
if (url.includes("/aiReleaseStatus")) {
|
|
170
|
-
return { success: true, planId: body.planId, status: "processing" };
|
|
171
|
-
}
|
|
172
|
-
return { success: true, raw: body };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function printResult(data) {
|
|
176
|
-
console.log(JSON.stringify(data, null, 2));
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// ---------------------------------------------------------------------------
|
|
180
|
-
// D4 · stdio MCP server stub
|
|
181
|
-
// 连接成功后框架会执行 npx -y @eprolo/eprolo-cli(无子命令)拉起 MCP
|
|
182
|
-
// stdout 只能输出 MCP 协议帧,调试日志走 stderr
|
|
203
|
+
// help
|
|
183
204
|
// ---------------------------------------------------------------------------
|
|
184
205
|
|
|
185
|
-
function
|
|
186
|
-
|
|
187
|
-
const config = readConfig();
|
|
188
|
-
if (!config.userCode) {
|
|
189
|
-
console.error("未连接或凭据失效,请在 Accio Work 中重新连接");
|
|
190
|
-
process.exit(1);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
let inputBuffer = "";
|
|
194
|
-
process.stdin.setEncoding("utf8");
|
|
195
|
-
process.stdin.on("data", (chunk) => {
|
|
196
|
-
inputBuffer += chunk;
|
|
197
|
-
// MCP stdio 协议:消息以换行分隔的 JSON-RPC
|
|
198
|
-
let newlineIdx;
|
|
199
|
-
while ((newlineIdx = inputBuffer.indexOf("\n")) !== -1) {
|
|
200
|
-
const line = inputBuffer.slice(0, newlineIdx).trim();
|
|
201
|
-
inputBuffer = inputBuffer.slice(newlineIdx + 1);
|
|
202
|
-
if (line) handleMcpMessage(line);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
process.stdin.on("end", () => process.exit(0));
|
|
206
|
-
}
|
|
206
|
+
function printHelp() {
|
|
207
|
+
console.log(`DXB CLI (cli-login 形态)
|
|
207
208
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
} catch (_) {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
209
|
+
Usage:
|
|
210
|
+
eprolo-cli auth login [--default-shop <shop>] 登录并写凭据文件(框架通过 env 注入 DXB_USER_CODE)
|
|
211
|
+
eprolo-cli auth status 查看授权状态
|
|
212
|
+
eprolo-cli auth logout 退出授权
|
|
215
213
|
|
|
216
|
-
|
|
214
|
+
Authorization:
|
|
215
|
+
框架通过 fieldInjection 把表单值递交给 CLI:
|
|
216
|
+
userCode → env 通道 → process.env.DXB_USER_CODE
|
|
217
|
+
defaultShop → arg 通道 → --default-shop
|
|
217
218
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
protocolVersion: "2024-11-05",
|
|
221
|
-
serverInfo: { name: "dxb-mcp", version: "1.0.0" },
|
|
222
|
-
capabilities: { tools: {} }
|
|
223
|
-
});
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
219
|
+
连接成功的唯一判定:凭据文件 ~/.dxb-toolkit/config.json 出现
|
|
220
|
+
凭据文件顶层明文 account 字段用于授权卡片标签
|
|
226
221
|
|
|
227
|
-
|
|
228
|
-
sendMcpResult(id, {
|
|
229
|
-
tools: [
|
|
230
|
-
{
|
|
231
|
-
name: "ai_release_products",
|
|
232
|
-
description: "AI 发品:用货源链接在指定店铺下自动发布商品",
|
|
233
|
-
inputSchema: {
|
|
234
|
-
type: "object",
|
|
235
|
-
properties: {
|
|
236
|
-
sourceUrlList: { type: "array", items: { type: "string" }, description: "货源链接列表" },
|
|
237
|
-
storeName: { type: "string", description: "目标授权店铺名(缺省用凭据里的默认店铺)" },
|
|
238
|
-
ptName: { type: "string", description: "目标平台,如 icbu" }
|
|
239
|
-
},
|
|
240
|
-
required: ["sourceUrlList"]
|
|
241
|
-
}
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
name: "ai_release_status",
|
|
245
|
-
description: "查询 AI 发品计划状态",
|
|
246
|
-
inputSchema: {
|
|
247
|
-
type: "object",
|
|
248
|
-
properties: {
|
|
249
|
-
planId: { type: "string", description: "发品计划 ID" },
|
|
250
|
-
storeName: { type: "string", description: "目标授权店铺名" }
|
|
251
|
-
},
|
|
252
|
-
required: ["planId"]
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
]
|
|
256
|
-
});
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
222
|
+
登录时调用 ${LOGIN_API} 验证密文
|
|
259
223
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
message: "Task submitted successfully",
|
|
267
|
-
taskKey: taskKey("ai_release_products")
|
|
268
|
-
}) }]
|
|
269
|
-
});
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
if (name === "ai_release_status") {
|
|
273
|
-
sendMcpResult(id, {
|
|
274
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
275
|
-
success: true,
|
|
276
|
-
planId: args.planId,
|
|
277
|
-
status: "processing"
|
|
278
|
-
}) }]
|
|
279
|
-
});
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
sendMcpError(id, -32601, `Unknown tool: ${name}`);
|
|
283
|
-
return;
|
|
224
|
+
Credential file (~/.dxb-toolkit/config.json):
|
|
225
|
+
{
|
|
226
|
+
"account": "user@example.com", // 顶层明文,框架读
|
|
227
|
+
"userCode": "...",
|
|
228
|
+
"defaultShop": "shop-001",
|
|
229
|
+
"version": 1
|
|
284
230
|
}
|
|
285
|
-
|
|
286
|
-
sendMcpError(id, -32601, `Unknown method: ${method}`);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function sendMcpResult(id, result) {
|
|
290
|
-
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n");
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function sendMcpError(id, code, message) {
|
|
294
|
-
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }) + "\n");
|
|
231
|
+
`);
|
|
295
232
|
}
|
|
296
233
|
|
|
297
234
|
// ---------------------------------------------------------------------------
|
|
298
|
-
//
|
|
299
|
-
// argv 有 "auth login" → 走 D2 登录
|
|
300
|
-
// 否则默认起 stdio MCP(D4)
|
|
235
|
+
// 入口分发
|
|
301
236
|
// ---------------------------------------------------------------------------
|
|
302
237
|
|
|
303
238
|
async function main() {
|
|
304
239
|
const argv = process.argv.slice(2);
|
|
305
240
|
|
|
306
|
-
// D2 · login
|
|
307
241
|
if (argv[0] === "auth" && argv[1] === "login") {
|
|
308
|
-
login(argv.slice(2));
|
|
242
|
+
await login(argv.slice(2));
|
|
309
243
|
return;
|
|
310
244
|
}
|
|
311
245
|
|
|
@@ -319,84 +253,14 @@ async function main() {
|
|
|
319
253
|
return;
|
|
320
254
|
}
|
|
321
255
|
|
|
322
|
-
|
|
323
|
-
if (argv[0] === "batch" && argv[1] === "submit") {
|
|
324
|
-
const args = parseArgs(argv.slice(2));
|
|
325
|
-
if (!args.action) throw new Error("batch submit requires --action");
|
|
326
|
-
if (args.action !== "ai_release_products") {
|
|
327
|
-
throw new Error("Only ai_release_products is supported.");
|
|
328
|
-
}
|
|
329
|
-
const auth = requireAuth(args);
|
|
330
|
-
const target = requireTaskTarget(args);
|
|
331
|
-
const body = {
|
|
332
|
-
action: args.action,
|
|
333
|
-
taskKey: args.taskKey || taskKey(args.action),
|
|
334
|
-
userCode: auth.userCode,
|
|
335
|
-
storeName: target.storeName,
|
|
336
|
-
productIds: splitIds(args.productIds)
|
|
337
|
-
};
|
|
338
|
-
if (target.ptName) body.ptName = target.ptName;
|
|
339
|
-
if (args.dataJson) body.data = JSON.parse(args.dataJson);
|
|
340
|
-
if (args.sourceUrlList) body.sourceUrlList = splitIds(args.sourceUrlList);
|
|
341
|
-
printResult(await postJson("", "/submit", body));
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (argv[0] === "status" && argv[1] === "ai") {
|
|
346
|
-
const args = parseArgs(argv.slice(2));
|
|
347
|
-
if (!args.planId) throw new Error("status ai requires --plan-id");
|
|
348
|
-
const auth = requireAuth(args);
|
|
349
|
-
const target = requireTaskTarget(args);
|
|
350
|
-
printResult(await postJson("", "/aiReleaseStatus", {
|
|
351
|
-
planId: args.planId,
|
|
352
|
-
userCode: auth.userCode,
|
|
353
|
-
storeName: target.storeName
|
|
354
|
-
}));
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (argv[0] === "--help" || argv[0] === "-h" || argv.length === 0) {
|
|
359
|
-
printHelp();
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// D4 · 默认起 stdio MCP server
|
|
364
|
-
startMcpServer();
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function printHelp() {
|
|
368
|
-
console.log(`DXB CLI (cli-login 形态)
|
|
369
|
-
|
|
370
|
-
Usage:
|
|
371
|
-
eprolo-cli auth login [--default-shop <shop>] 登录并写凭据文件(框架通过 env 注入 DXB_USER_CODE)
|
|
372
|
-
eprolo-cli auth status 查看授权状态
|
|
373
|
-
eprolo-cli auth logout 退出授权
|
|
374
|
-
eprolo-cli batch submit --action ai_release_products --source-url-list <urls> [--store-name <shop>]
|
|
375
|
-
eprolo-cli status ai --plan-id <planId> [--store-name <shop>]
|
|
376
|
-
eprolo-cli 启动 stdio MCP server(框架连接成功后自动拉起)
|
|
377
|
-
|
|
378
|
-
Authorization:
|
|
379
|
-
框架通过 fieldInjection 把表单值递交给 CLI:
|
|
380
|
-
userCode → env 通道 → process.env.DXB_USER_CODE
|
|
381
|
-
defaultShop → arg 通道 → --default-shop
|
|
382
|
-
|
|
383
|
-
连接成功的唯一判定:凭据文件 ~/.dxb-toolkit/config.json 出现
|
|
384
|
-
凭据文件顶层明文 account 字段用于授权卡片标签
|
|
385
|
-
|
|
386
|
-
Credential file (~/.dxb-toolkit/config.json):
|
|
387
|
-
{
|
|
388
|
-
"account": "user@example.com", // 顶层明文,框架读
|
|
389
|
-
"userCode": "...",
|
|
390
|
-
"defaultShop": "shop-001",
|
|
391
|
-
"version": 1
|
|
392
|
-
}
|
|
393
|
-
`);
|
|
256
|
+
printHelp();
|
|
394
257
|
}
|
|
395
258
|
|
|
396
259
|
main().catch((err) => {
|
|
397
260
|
console.error(JSON.stringify({
|
|
398
261
|
success: false,
|
|
399
|
-
errorMessage: err.message
|
|
262
|
+
errorMessage: err.message,
|
|
263
|
+
response: err.response || null
|
|
400
264
|
}, null, 2));
|
|
401
265
|
process.exit(1);
|
|
402
266
|
});
|
package/package.json
CHANGED