rushangle-cli 0.1.1 → 0.1.3
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/package.json +7 -4
- package/src/api.js +3 -0
- package/src/commands/login.js +6 -2
- package/src/commands/publish.js +62 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rushangle-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "SkillHub CLI - 数智凯航技能市场命令行工具",
|
|
5
5
|
"bin": {
|
|
6
6
|
"rushangle": "./bin/rushangle.js"
|
|
@@ -13,14 +13,17 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"start": "node bin/rushangle.js"
|
|
15
15
|
},
|
|
16
|
-
"keywords": [
|
|
16
|
+
"keywords": [
|
|
17
|
+
"rushangle",
|
|
18
|
+
"skillhub",
|
|
19
|
+
"cli",
|
|
20
|
+
"skills"
|
|
21
|
+
],
|
|
17
22
|
"license": "MIT",
|
|
18
23
|
"dependencies": {
|
|
19
24
|
"commander": "^12.0.0",
|
|
20
25
|
"chalk": "^4.1.2",
|
|
21
|
-
"ora": "^8.0.0",
|
|
22
26
|
"open": "^10.0.0",
|
|
23
|
-
"conf": "^12.0.0",
|
|
24
27
|
"cli-table3": "^0.6.5"
|
|
25
28
|
}
|
|
26
29
|
}
|
package/src/api.js
CHANGED
package/src/commands/login.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const { Command } = require('commander');
|
|
2
2
|
const chalk = require('chalk');
|
|
3
|
-
const open = require('open');
|
|
4
3
|
const http = require('http');
|
|
5
4
|
const auth = require('../auth');
|
|
6
5
|
const { SITE_URL } = require('../api');
|
|
@@ -65,7 +64,12 @@ p{color:#666;margin:4px 0}
|
|
|
65
64
|
|
|
66
65
|
// Open browser to DingTalk OAuth login with CLI callback params
|
|
67
66
|
const loginUrl = `${SITE_URL}/api/auth/dingtalk/login?cli=1&cli_port=${CLI_PORT}`;
|
|
68
|
-
|
|
67
|
+
try {
|
|
68
|
+
const open = (await import('open')).default;
|
|
69
|
+
await open(loginUrl);
|
|
70
|
+
} catch {
|
|
71
|
+
// open module unavailable, fall through to manual URL prompt
|
|
72
|
+
}
|
|
69
73
|
console.log(chalk.gray('请在浏览器中完成钉钉授权...'));
|
|
70
74
|
console.log(chalk.yellow('等待授权完成... (如浏览器未打开,请手动访问: ' + loginUrl + ')'));
|
|
71
75
|
});
|
package/src/commands/publish.js
CHANGED
|
@@ -2,11 +2,70 @@ const { Command } = require('commander');
|
|
|
2
2
|
const chalk = require('chalk');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
5
6
|
const auth = require('../auth');
|
|
6
7
|
const api = require('../api');
|
|
7
8
|
|
|
8
9
|
const VALID_TYPES = ['skill', 'mcp', 'code', 'doc'];
|
|
9
10
|
|
|
11
|
+
async function askQuestion(query) {
|
|
12
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
13
|
+
return new Promise(resolve => rl.question(query, ans => { rl.close(); resolve(ans.trim()); }));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Fetch preset categories from server and attempt to match/resolve user's category.
|
|
18
|
+
* @returns {{ categories: string[], resolved: string }} - resolved is the final category to use
|
|
19
|
+
*/
|
|
20
|
+
async function resolveCategory(type, userCategory, apiModule) {
|
|
21
|
+
const typeMap = { skill: 'skill', mcp: 'mcp', code: 'code', doc: 'doc' };
|
|
22
|
+
const t = typeMap[type] || 'skill';
|
|
23
|
+
|
|
24
|
+
let serverCategories = [];
|
|
25
|
+
try {
|
|
26
|
+
const data = await apiModule.getCategories();
|
|
27
|
+
serverCategories = data[t] || data.skill || [];
|
|
28
|
+
} catch {
|
|
29
|
+
// Server unreachable — just use whatever user provided
|
|
30
|
+
return { categories: [], resolved: userCategory };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!serverCategories.length) {
|
|
34
|
+
return { categories: [], resolved: userCategory };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Case 1: user already specified a category that matches → use it
|
|
38
|
+
if (userCategory) {
|
|
39
|
+
const exact = serverCategories.find(c => c.toLowerCase() === userCategory.toLowerCase());
|
|
40
|
+
if (exact) {
|
|
41
|
+
console.log(chalk.gray(` 分类: ${exact}`));
|
|
42
|
+
return { categories: serverCategories, resolved: exact };
|
|
43
|
+
}
|
|
44
|
+
// Case 2: user specified but doesn't match → show mismatch, let them pick
|
|
45
|
+
console.log(chalk.yellow(` ⚠ 指定的分类 "${userCategory}" 不在预设列表中,请从以下选择:`));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Show numbered list
|
|
49
|
+
console.log(chalk.cyan(`\n 请选择 ${type} 分类:`));
|
|
50
|
+
serverCategories.forEach((c, i) => {
|
|
51
|
+
console.log(chalk.white(` ${chalk.bold(String(i + 1))}. ${c}`));
|
|
52
|
+
});
|
|
53
|
+
console.log(chalk.gray(` ${serverCategories.length + 1}. 自定义(输入文本)`));
|
|
54
|
+
|
|
55
|
+
const answer = await askQuestion(chalk.green('\n 输入编号或分类名: '));
|
|
56
|
+
const num = parseInt(answer, 10);
|
|
57
|
+
if (num >= 1 && num <= serverCategories.length) {
|
|
58
|
+
console.log(chalk.gray(` 分类: ${serverCategories[num - 1]}`));
|
|
59
|
+
return { categories: serverCategories, resolved: serverCategories[num - 1] };
|
|
60
|
+
}
|
|
61
|
+
if (answer) {
|
|
62
|
+
console.log(chalk.gray(` 分类: ${answer}`));
|
|
63
|
+
return { categories: serverCategories, resolved: answer };
|
|
64
|
+
}
|
|
65
|
+
console.log(chalk.gray(` 分类: ${serverCategories[0]}`));
|
|
66
|
+
return { categories: serverCategories, resolved: serverCategories[0] };
|
|
67
|
+
}
|
|
68
|
+
|
|
10
69
|
module.exports = new Command('publish')
|
|
11
70
|
.description('发布技能/MCP配置/代码段/文档到 SkillHub 市场')
|
|
12
71
|
.argument('[dir]', '项目目录路径', '.')
|
|
@@ -52,10 +111,12 @@ module.exports = new Command('publish')
|
|
|
52
111
|
const name = opts.name || meta.name || path.basename(absDir);
|
|
53
112
|
const description = opts.description || meta.description || '';
|
|
54
113
|
const version = opts.version || meta.version || '0.1.0';
|
|
55
|
-
const category = opts.category || meta.category || '';
|
|
56
114
|
const tags = opts.tags ? opts.tags.split(',').map(t => t.trim()).filter(Boolean)
|
|
57
115
|
: (meta.tags || []);
|
|
58
116
|
|
|
117
|
+
// Resolve category via server presets
|
|
118
|
+
const { resolved: category } = await resolveCategory(type, opts.category || meta.category || '', api);
|
|
119
|
+
|
|
59
120
|
// Read README if exists
|
|
60
121
|
let readme = '';
|
|
61
122
|
for (const f of ['README.md', 'readme.md', 'README', 'readme']) {
|