rushangle-cli 0.1.2 → 0.1.4
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 -2
- package/src/api.js +3 -0
- package/src/commands/publish.js +62 -1
- package/src/index.js +4 -0
- package/src/update-checker.js +107 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rushangle-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "SkillHub CLI - 数智凯航技能市场命令行工具",
|
|
5
5
|
"bin": {
|
|
6
6
|
"rushangle": "./bin/rushangle.js"
|
|
@@ -13,7 +13,12 @@
|
|
|
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",
|
package/src/api.js
CHANGED
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']) {
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
|
|
6
|
+
const CACHE_DIR = path.join(require('os').homedir(), '.rushangle');
|
|
7
|
+
const CACHE_FILE = path.join(CACHE_DIR, '.update-check');
|
|
8
|
+
const CHECK_INTERVAL = 1000 * 60 * 60 * 24; // 每 24 小时检查一次
|
|
9
|
+
|
|
10
|
+
function getCache() {
|
|
11
|
+
try {
|
|
12
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
13
|
+
return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
|
|
14
|
+
}
|
|
15
|
+
} catch {}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function setCache(data) {
|
|
20
|
+
try {
|
|
21
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
22
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(data));
|
|
23
|
+
} catch {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function npmLatest(pkgName) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const req = https.get(
|
|
29
|
+
`https://registry.npmjs.org/${encodeURIComponent(pkgName)}/latest`,
|
|
30
|
+
{ timeout: 5000 },
|
|
31
|
+
(res) => {
|
|
32
|
+
let body = '';
|
|
33
|
+
res.on('data', (chunk) => (body += chunk));
|
|
34
|
+
res.on('end', () => {
|
|
35
|
+
try {
|
|
36
|
+
const data = JSON.parse(body);
|
|
37
|
+
resolve(data.version);
|
|
38
|
+
} catch {
|
|
39
|
+
reject(new Error('Invalid response'));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
req.on('error', reject);
|
|
45
|
+
req.on('timeout', () => {
|
|
46
|
+
req.destroy();
|
|
47
|
+
reject(new Error('Timeout'));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function checkUpdate() {
|
|
53
|
+
try {
|
|
54
|
+
// 检查缓存是否有效
|
|
55
|
+
const cache = getCache();
|
|
56
|
+
if (cache && (Date.now() - cache.checkedAt) < CHECK_INTERVAL) {
|
|
57
|
+
return; // 缓存未过期,跳过
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const pkg = require('../package.json');
|
|
61
|
+
const latest = await npmLatest(pkg.name);
|
|
62
|
+
|
|
63
|
+
// 更新缓存
|
|
64
|
+
setCache({ checkedAt: Date.now(), latest });
|
|
65
|
+
|
|
66
|
+
// 版本对比:去掉 v 前缀,语义化版本逐段对比
|
|
67
|
+
const current = pkg.version.replace(/^v/, '');
|
|
68
|
+
const latestVer = latest.replace(/^v/, '');
|
|
69
|
+
|
|
70
|
+
if (current !== latestVer) {
|
|
71
|
+
const parts = {
|
|
72
|
+
cur: current.split('.').map(Number),
|
|
73
|
+
lat: latestVer.split('.').map(Number),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const level =
|
|
77
|
+
parts.lat[0] > parts.cur[0] ? 'major'
|
|
78
|
+
: parts.lat[1] > parts.cur[1] ? 'minor'
|
|
79
|
+
: 'patch';
|
|
80
|
+
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(
|
|
83
|
+
chalk.yellow('╔══════════════════════════════════════════════════════════╗')
|
|
84
|
+
);
|
|
85
|
+
console.log(
|
|
86
|
+
chalk.yellow('║ 新版可用!') +
|
|
87
|
+
chalk.white(` ${pkg.name} `) +
|
|
88
|
+
chalk.green(`v${latestVer}`) +
|
|
89
|
+
chalk.white(`(当前 v${current})`) +
|
|
90
|
+
chalk.yellow(' ║')
|
|
91
|
+
);
|
|
92
|
+
console.log(
|
|
93
|
+
chalk.yellow('║ 运行 ') +
|
|
94
|
+
chalk.cyan('npm update -g ' + pkg.name) +
|
|
95
|
+
chalk.yellow(' 更新到最新版本 ║')
|
|
96
|
+
);
|
|
97
|
+
console.log(
|
|
98
|
+
chalk.yellow('╚══════════════════════════════════════════════════════════╝')
|
|
99
|
+
);
|
|
100
|
+
console.log('');
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
// 网络错误等静默跳过,不影响正常使用
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = { checkUpdate };
|