eprolo-cli 1.0.32 → 1.0.34
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 +93 -273
- 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,80 +58,97 @@ 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) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
readFlag(argv, "--account") ||
|
|
96
|
-
process.env.DXB_ACCOUNT ||
|
|
97
|
-
defaultShop ||
|
|
98
|
-
"mock-account";
|
|
99
|
-
|
|
100
|
-
// 写凭据文件(D3 契约:顶层明文 account + version)
|
|
101
|
-
const config = {
|
|
102
|
-
account, // ← 顶层明文,框架读它做卡片标签(稳定契约,不可改名/挪位)
|
|
103
|
-
userCode,
|
|
104
|
-
version: 1
|
|
105
|
-
};
|
|
106
|
-
if (defaultShop) config.defaultShop = defaultShop;
|
|
107
|
-
|
|
108
|
-
writeConfig(config);
|
|
109
|
-
|
|
110
|
-
// 不打印任何敏感值
|
|
111
|
-
console.log(JSON.stringify({
|
|
112
|
-
success: true,
|
|
113
|
-
connected: true,
|
|
114
|
-
account: config.account,
|
|
115
|
-
defaultShop: config.defaultShop || null
|
|
116
|
-
}, null, 2));
|
|
115
|
+
async function login(argv) {
|
|
116
|
+
const userCode = process.env.YOUR_USER_CODE; // env 通道
|
|
117
|
+
if (!userCode) { console.error("缺少用户密文:请在授权弹窗中填写"); process.exit(2); }
|
|
118
|
+
|
|
119
|
+
const defaultShop = readFlag(argv, "--default-shop"); // arg 通道,可能不存在
|
|
120
|
+
|
|
121
|
+
const res = await fetch(LOGIN_API, {
|
|
122
|
+
method: "POST", headers: { "Content-Type": "application/json" },
|
|
123
|
+
body: JSON.stringify({ userCode }),
|
|
124
|
+
});
|
|
125
|
+
// if (!res.ok) { console.error("密文无效或已吊销,请到账号设置页重新复制"); process.exit(1); }
|
|
126
|
+
|
|
127
|
+
const { account, accessToken } = await res.json();
|
|
128
|
+
await writeCredentialFile({ account, accessToken, defaultShop }); // → D3
|
|
129
|
+
console.log("login ok"); // 不打印任何敏感值
|
|
117
130
|
process.exit(0);
|
|
118
131
|
}
|
|
119
132
|
|
|
120
133
|
// ---------------------------------------------------------------------------
|
|
121
|
-
// auth status
|
|
134
|
+
// auth status — 查看授权状态(凭据文件是否存在)
|
|
122
135
|
// ---------------------------------------------------------------------------
|
|
123
136
|
|
|
124
137
|
function authStatus() {
|
|
125
138
|
const config = readConfig();
|
|
139
|
+
const connected = fs.existsSync(CONFIG_FILE);
|
|
126
140
|
console.log(JSON.stringify({
|
|
127
|
-
connected
|
|
141
|
+
connected,
|
|
128
142
|
account: config.account || null,
|
|
129
143
|
defaultShop: config.defaultShop || null
|
|
130
144
|
}, null, 2));
|
|
131
145
|
process.exit(0);
|
|
132
146
|
}
|
|
133
147
|
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// auth logout — 退出授权(删除凭据文件)
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
|
|
134
152
|
function authLogout() {
|
|
135
153
|
if (fs.existsSync(CONFIG_FILE)) fs.unlinkSync(CONFIG_FILE);
|
|
136
154
|
console.log(JSON.stringify({ success: true }, null, 2));
|
|
@@ -138,174 +156,46 @@ function authLogout() {
|
|
|
138
156
|
}
|
|
139
157
|
|
|
140
158
|
// ---------------------------------------------------------------------------
|
|
141
|
-
//
|
|
159
|
+
// help
|
|
142
160
|
// ---------------------------------------------------------------------------
|
|
143
161
|
|
|
144
|
-
function
|
|
145
|
-
|
|
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
|
|
183
|
-
// ---------------------------------------------------------------------------
|
|
184
|
-
|
|
185
|
-
function startMcpServer() {
|
|
186
|
-
// MCP stub:读凭据文件,通过 stdio 协议响应 tools/list 和 tools/call
|
|
187
|
-
const config = readConfig();
|
|
188
|
-
if (!config.userCode) {
|
|
189
|
-
console.error("未连接或凭据失效,请在 Accio Work 中重新连接");
|
|
190
|
-
process.exit(1);
|
|
191
|
-
}
|
|
162
|
+
function printHelp() {
|
|
163
|
+
console.log(`DXB CLI (cli-login 形态)
|
|
192
164
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
}
|
|
165
|
+
Usage:
|
|
166
|
+
eprolo-cli auth login [--default-shop <shop>] 登录并写凭据文件(框架通过 env 注入 DXB_USER_CODE)
|
|
167
|
+
eprolo-cli auth status 查看授权状态
|
|
168
|
+
eprolo-cli auth logout 退出授权
|
|
207
169
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
} catch (_) {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
170
|
+
Authorization:
|
|
171
|
+
框架通过 fieldInjection 把表单值递交给 CLI:
|
|
172
|
+
userCode → env 通道 → process.env.DXB_USER_CODE
|
|
173
|
+
defaultShop → arg 通道 → --default-shop
|
|
215
174
|
|
|
216
|
-
|
|
175
|
+
连接成功的唯一判定:凭据文件 ~/.dxb-toolkit/config.json 出现
|
|
176
|
+
凭据文件顶层明文 account 字段用于授权卡片标签
|
|
217
177
|
|
|
218
|
-
|
|
219
|
-
sendMcpResult(id, {
|
|
220
|
-
protocolVersion: "2024-11-05",
|
|
221
|
-
serverInfo: { name: "dxb-mcp", version: "1.0.0" },
|
|
222
|
-
capabilities: { tools: {} }
|
|
223
|
-
});
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
178
|
+
登录时调用 ${LOGIN_API} 验证密文
|
|
226
179
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
}
|
|
259
|
-
|
|
260
|
-
if (method === "tools/call") {
|
|
261
|
-
const { name, arguments: args } = params;
|
|
262
|
-
if (name === "ai_release_products") {
|
|
263
|
-
sendMcpResult(id, {
|
|
264
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
265
|
-
success: true,
|
|
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;
|
|
180
|
+
Credential file (~/.dxb-toolkit/config.json):
|
|
181
|
+
{
|
|
182
|
+
"account": "user@example.com", // 顶层明文,框架读
|
|
183
|
+
"userCode": "...",
|
|
184
|
+
"defaultShop": "shop-001",
|
|
185
|
+
"version": 1
|
|
284
186
|
}
|
|
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");
|
|
187
|
+
`);
|
|
295
188
|
}
|
|
296
189
|
|
|
297
190
|
// ---------------------------------------------------------------------------
|
|
298
|
-
//
|
|
299
|
-
// argv 有 "auth login" → 走 D2 登录
|
|
300
|
-
// 否则默认起 stdio MCP(D4)
|
|
191
|
+
// 入口分发
|
|
301
192
|
// ---------------------------------------------------------------------------
|
|
302
193
|
|
|
303
194
|
async function main() {
|
|
304
195
|
const argv = process.argv.slice(2);
|
|
305
196
|
|
|
306
|
-
// D2 · login
|
|
307
197
|
if (argv[0] === "auth" && argv[1] === "login") {
|
|
308
|
-
login(argv.slice(2));
|
|
198
|
+
await login(argv.slice(2));
|
|
309
199
|
return;
|
|
310
200
|
}
|
|
311
201
|
|
|
@@ -319,84 +209,14 @@ async function main() {
|
|
|
319
209
|
return;
|
|
320
210
|
}
|
|
321
211
|
|
|
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
|
-
`);
|
|
212
|
+
printHelp();
|
|
394
213
|
}
|
|
395
214
|
|
|
396
215
|
main().catch((err) => {
|
|
397
216
|
console.error(JSON.stringify({
|
|
398
217
|
success: false,
|
|
399
|
-
errorMessage: err.message
|
|
218
|
+
errorMessage: err.message,
|
|
219
|
+
response: err.response || null
|
|
400
220
|
}, null, 2));
|
|
401
221
|
process.exit(1);
|
|
402
222
|
});
|
package/package.json
CHANGED