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.
Files changed (2) hide show
  1. package/bin/eprolo-cli.js +107 -243
  2. 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
- // D3 · 凭据文件读写(~/.dxb-toolkit/config.json)
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
- function splitIds(value) {
61
- if (!value) return [];
62
- return String(value).split(",").map((item) => item.trim()).filter(Boolean);
63
- }
61
+ // ---------------------------------------------------------------------------
62
+ // HTTP POST 请求
63
+ // ---------------------------------------------------------------------------
64
64
 
65
- function taskKey(action) {
66
- return `${action}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
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(或 --default-store-name 兼容旧版)
108
+ // defaultShop ← arg 通道 --default-shop
74
109
  // 行为契约:
75
110
  // 成功 → 写凭据文件 + exit 0
76
111
  // 失败 → 非零退出 + stderr 错误信息
77
- // ❌ 禁止交互式 prompt(CLI 跑在非交互终端)
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") || "mock-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
- // 账号标识:用户在弹窗可填 accountAlias,或用 defaultShop 兜底
127
+ // 账号标识
94
128
  const account =
95
129
  readFlag(argv, "--account") ||
96
130
  process.env.DXB_ACCOUNT ||
97
131
  defaultShop ||
98
- "mock-account";
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 / auth logout
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: fs.existsSync(CONFIG_FILE),
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
- // 业务命令(mock,不调用真实 API)
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 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
- }
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
- function handleMcpMessage(line) {
209
- let msg;
210
- try {
211
- msg = JSON.parse(line);
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
- const { id, method, params } = msg;
214
+ Authorization:
215
+ 框架通过 fieldInjection 把表单值递交给 CLI:
216
+ userCode → env 通道 → process.env.DXB_USER_CODE
217
+ defaultShop → arg 通道 → --default-shop
217
218
 
218
- if (method === "initialize") {
219
- sendMcpResult(id, {
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
- if (method === "tools/list") {
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
- 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;
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
- // 入口分发(D2 + D4)
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
- // 兼容旧版 CLI 命令(batch submit / status ai)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eprolo-cli",
3
- "version": "1.0.32",
3
+ "version": "1.0.33",
4
4
  "description": "CLI for Dianxiaobao ICBU AI publishing, authorization profile management, task submission, and task status checks.",
5
5
  "bin": {
6
6
  "eprolo": "bin/eprolo-cli.js"