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.
Files changed (2) hide show
  1. package/bin/eprolo-cli.js +93 -273
  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,80 +58,97 @@ 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) {
81
- // env 通道:框架通过 fieldInjection 注入 DXB_USER_CODE
82
- const userCode = process.env.DXB_USER_CODE || readFlag(argv, "--user-code") || "mock-user-code";
83
-
84
- // arg 通道:框架通过 fieldInjection 追加 --default-shop
85
- const defaultShop =
86
- readFlag(argv, "--default-shop") ||
87
- readFlag(argv, "--default-store-name") ||
88
- readFlag(argv, "--store-name") ||
89
- process.env.DXB_STORE_NAME ||
90
- process.env.DXB_DEFAULT_STORE_NAME ||
91
- null;
92
-
93
- // 账号标识:用户在弹窗可填 accountAlias,或用 defaultShop 兜底
94
- const account =
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 / auth logout
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: fs.existsSync(CONFIG_FILE),
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
- // 业务命令(mock,不调用真实 API)
159
+ // help
142
160
  // ---------------------------------------------------------------------------
143
161
 
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
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
- 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
- }
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
- function handleMcpMessage(line) {
209
- let msg;
210
- try {
211
- msg = JSON.parse(line);
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
- const { id, method, params } = msg;
175
+ 连接成功的唯一判定:凭据文件 ~/.dxb-toolkit/config.json 出现
176
+ 凭据文件顶层明文 account 字段用于授权卡片标签
217
177
 
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
- }
178
+ 登录时调用 ${LOGIN_API} 验证密文
226
179
 
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
- }
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
- // 入口分发(D2 + D4)
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
- // 兼容旧版 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
- `);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eprolo-cli",
3
- "version": "1.0.32",
3
+ "version": "1.0.34",
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"