rushangle-cli 0.2.3 → 0.2.5
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 +1 -1
- package/src/commands/publish.js +51 -4
- package/src/commands/unpublish.js +101 -0
- package/src/index.js +1 -0
package/package.json
CHANGED
package/src/commands/publish.js
CHANGED
|
@@ -8,6 +8,46 @@ const api = require('../api');
|
|
|
8
8
|
|
|
9
9
|
const VALID_TYPES = ['skill', 'mcp', 'code', 'doc'];
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Simple YAML frontmatter parser for SKILL.md.
|
|
13
|
+
* Extracts key-value pairs from --- delimited frontmatter block.
|
|
14
|
+
*/
|
|
15
|
+
function parseFrontmatter(content) {
|
|
16
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
17
|
+
if (!match) return {};
|
|
18
|
+
const fm = {};
|
|
19
|
+
const lines = match[1].split(/\r?\n/);
|
|
20
|
+
for (const line of lines) {
|
|
21
|
+
const kv = line.match(/^(\w[\w-]*)\s*:\s*(.+)$/);
|
|
22
|
+
if (kv) {
|
|
23
|
+
const key = kv[1].trim();
|
|
24
|
+
let value = kv[2].trim();
|
|
25
|
+
// Strip quotes
|
|
26
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
27
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
28
|
+
value = value.slice(1, -1);
|
|
29
|
+
}
|
|
30
|
+
fm[key] = value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return fm;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read SKILL.md and extract frontmatter + body content.
|
|
38
|
+
*/
|
|
39
|
+
function readSkillMd(absDir) {
|
|
40
|
+
for (const f of ['SKILL.md', 'skill.md', 'Skill.md']) {
|
|
41
|
+
const fp = path.join(absDir, f);
|
|
42
|
+
if (fs.existsSync(fp)) {
|
|
43
|
+
const content = fs.readFileSync(fp, 'utf-8');
|
|
44
|
+
const frontmatter = parseFrontmatter(content);
|
|
45
|
+
return { content, frontmatter, path: fp };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { content: '', frontmatter: {}, path: null };
|
|
49
|
+
}
|
|
50
|
+
|
|
11
51
|
async function askQuestion(query) {
|
|
12
52
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
13
53
|
return new Promise(resolve => rl.question(query, ans => { rl.close(); resolve(ans.trim()); }));
|
|
@@ -108,21 +148,28 @@ module.exports = new Command('publish')
|
|
|
108
148
|
}
|
|
109
149
|
}
|
|
110
150
|
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
const
|
|
151
|
+
// Read SKILL.md frontmatter for skill metadata
|
|
152
|
+
const skillMd = type === 'skill' ? readSkillMd(absDir) : null;
|
|
153
|
+
const fm = (skillMd && skillMd.frontmatter) || {};
|
|
154
|
+
|
|
155
|
+
const name = opts.name || meta.name || fm.name || path.basename(absDir);
|
|
156
|
+
const description = opts.description || meta.description || fm.description || '';
|
|
157
|
+
const version = opts.version || meta.version || fm.version || '0.1.0';
|
|
114
158
|
const tags = opts.tags ? opts.tags.split(',').map(t => t.trim()).filter(Boolean)
|
|
115
159
|
: (meta.tags || []);
|
|
116
160
|
|
|
117
161
|
// Resolve category via server presets
|
|
118
162
|
const { resolved: category } = await resolveCategory(type, opts.category || meta.category || '', api);
|
|
119
163
|
|
|
120
|
-
// Read README if exists
|
|
164
|
+
// Read README if exists, otherwise fall back to SKILL.md body
|
|
121
165
|
let readme = '';
|
|
122
166
|
for (const f of ['README.md', 'readme.md', 'README', 'readme']) {
|
|
123
167
|
const fp = path.join(absDir, f);
|
|
124
168
|
if (fs.existsSync(fp)) { readme = fs.readFileSync(fp, 'utf-8'); break; }
|
|
125
169
|
}
|
|
170
|
+
if (!readme && skillMd && skillMd.content) {
|
|
171
|
+
readme = skillMd.content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '').trim();
|
|
172
|
+
}
|
|
126
173
|
|
|
127
174
|
console.log(chalk.cyan(`正在发布 ${type}: ${name}@${version} ...`));
|
|
128
175
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const { Command } = require('commander');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const auth = require('../auth');
|
|
5
|
+
const api = require('../api');
|
|
6
|
+
|
|
7
|
+
const VALID_TYPES = ['skill', 'mcp', 'code', 'doc'];
|
|
8
|
+
|
|
9
|
+
async function askConfirm(query) {
|
|
10
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
11
|
+
return new Promise(resolve => rl.question(query, ans => { rl.close(); resolve(ans.toLowerCase()); }));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const TYPE_MAP = {
|
|
15
|
+
skill: { label: '技能', my: 'mySkills', get: 'getSkill', del: 'deleteSkill', search: 'listSkills', field: 'skills' },
|
|
16
|
+
mcp: { label: 'MCP代码段', my: 'myMcp', get: 'getMcp', del: 'deleteMcp', search: 'listMcp', field: 'items' },
|
|
17
|
+
code: { label: '代码段', my: 'myCode', get: 'getCode', del: 'deleteCode', search: 'listCode', field: 'items' },
|
|
18
|
+
doc: { label: '文档', my: 'myDocs', get: 'getDoc', del: 'deleteDoc', search: 'listDocs', field: 'items' },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
module.exports = new Command('unpublish')
|
|
22
|
+
.alias('rm')
|
|
23
|
+
.description('从 SkillHub 下架已发布的内容(仅限自己发布的)')
|
|
24
|
+
.argument('<name-or-id>', '名称或 ID')
|
|
25
|
+
.option('-t, --type <type>', '类型: skill | mcp | code | doc', 'skill')
|
|
26
|
+
.option('-y, --yes', '跳过确认,直接删除')
|
|
27
|
+
.action(async (nameOrId, opts) => {
|
|
28
|
+
const type = opts.type.toLowerCase();
|
|
29
|
+
|
|
30
|
+
if (!VALID_TYPES.includes(type)) {
|
|
31
|
+
console.log(chalk.red(`不支持的类型: ${type},可选: ${VALID_TYPES.join(' | ')}`));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!auth.getToken()) {
|
|
36
|
+
console.log(chalk.red('请先登录: rushangle login'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const t = TYPE_MAP[type];
|
|
41
|
+
|
|
42
|
+
let item;
|
|
43
|
+
// Try by ID first
|
|
44
|
+
try {
|
|
45
|
+
item = await api[t.get](nameOrId);
|
|
46
|
+
} catch {
|
|
47
|
+
// Not a UUID — search by name in user's own items
|
|
48
|
+
try {
|
|
49
|
+
const myData = await api[t.my]();
|
|
50
|
+
const list = myData[t.field] || myData.skills || [];
|
|
51
|
+
const match = list.find(
|
|
52
|
+
s => s.name === nameOrId || s.title === nameOrId || s.id === nameOrId
|
|
53
|
+
);
|
|
54
|
+
if (match) {
|
|
55
|
+
item = match;
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// Fallback: public search
|
|
59
|
+
try {
|
|
60
|
+
const data = await api[t.search]({ search: nameOrId });
|
|
61
|
+
const list = data[t.field] || data.skills || [];
|
|
62
|
+
const match = list.find(
|
|
63
|
+
s => s.name === nameOrId || s.title === nameOrId
|
|
64
|
+
);
|
|
65
|
+
if (match) item = match;
|
|
66
|
+
} catch { /* ignore */ }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!item) {
|
|
71
|
+
console.log(chalk.red(`未找到${t.label}: ${nameOrId}`));
|
|
72
|
+
console.log(chalk.gray(' 提示: 只能下架自己发布的内容,请检查名称是否正确'));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const displayName = item.name || item.title || item.id;
|
|
77
|
+
|
|
78
|
+
console.log('');
|
|
79
|
+
console.log(chalk.yellow(`即将下架 ${t.label}: ${chalk.bold(displayName)}`));
|
|
80
|
+
if (item.description) console.log(chalk.gray(` 描述: ${item.description}`));
|
|
81
|
+
console.log('');
|
|
82
|
+
|
|
83
|
+
if (!opts.yes) {
|
|
84
|
+
const answer = await askConfirm(chalk.red('确认下架?此操作不可撤销 (y/N): '));
|
|
85
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
86
|
+
console.log(chalk.gray('已取消'));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
await api[t.del](item.id);
|
|
93
|
+
console.log(chalk.green(`✓ 已下架: ${displayName}`));
|
|
94
|
+
} catch (err) {
|
|
95
|
+
if (err.status === 403) {
|
|
96
|
+
console.log(chalk.red(`权限不足: 只能下架自己发布的${t.label}`));
|
|
97
|
+
} else {
|
|
98
|
+
console.log(chalk.red(`下架失败: ${err.message}`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
package/src/index.js
CHANGED
|
@@ -16,6 +16,7 @@ program.addCommand(require('./commands/login'));
|
|
|
16
16
|
program.addCommand(require('./commands/whoami'));
|
|
17
17
|
program.addCommand(require('./commands/logout'));
|
|
18
18
|
program.addCommand(require('./commands/publish'));
|
|
19
|
+
program.addCommand(require('./commands/unpublish'));
|
|
19
20
|
program.addCommand(require('./commands/install'));
|
|
20
21
|
program.addCommand(require('./commands/list'));
|
|
21
22
|
program.addCommand(require('./commands/search'));
|