skillgo 1.0.1 → 1.0.2
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/README.md +1 -1
- package/bin/skillgo.js +96 -23
- package/package.json +1 -1
package/README.md
CHANGED
package/bin/skillgo.js
CHANGED
|
@@ -62,48 +62,121 @@ const search = async (keyword) => {
|
|
|
62
62
|
})
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
const fetchRepoDir = async (project, relPath, localDir, branch, isGitee, fs, path) => {
|
|
66
|
+
const apiPath = relPath || (isGitee ? '.' : '')
|
|
67
|
+
let contents = []
|
|
68
|
+
if (isGitee) {
|
|
69
|
+
const apiUrl = `https://gitee.com/api/v5/repos/${project}/contents/${apiPath}`
|
|
70
|
+
debug('获取目录', apiUrl)
|
|
71
|
+
try {
|
|
72
|
+
contents = await fetch(apiUrl).then((r) => (r.ok ? r.json() : []))
|
|
73
|
+
} catch {
|
|
74
|
+
return 0
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
const apiUrl = `https://api.github.com/repos/${project}/contents/${apiPath}`
|
|
78
|
+
debug('获取目录', apiUrl)
|
|
79
|
+
try {
|
|
80
|
+
contents = await fetch(apiUrl).then((r) => (r.ok ? r.json() : []))
|
|
81
|
+
} catch {
|
|
82
|
+
return 0
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(contents)) contents = [contents]
|
|
86
|
+
let count = 0
|
|
87
|
+
const filePath = (p, name) => (p ? `${p}/${name}` : name)
|
|
88
|
+
for (const f of contents) {
|
|
89
|
+
const name = f.name
|
|
90
|
+
if (f.type === 'dir') {
|
|
91
|
+
const subRelPath = filePath(relPath, name)
|
|
92
|
+
const subLocalDir = path.join(localDir, name)
|
|
93
|
+
await fs.mkdir(subLocalDir, { recursive: true })
|
|
94
|
+
count += await fetchRepoDir(project, subRelPath, subLocalDir, branch, isGitee, fs, path)
|
|
95
|
+
} else if (f.type === 'file') {
|
|
96
|
+
if (name === 'SKILL.md') continue
|
|
97
|
+
const fullPath = filePath(relPath, name)
|
|
98
|
+
const rawUrl = isGitee
|
|
99
|
+
? `https://gitee.com/${project}/raw/${branch}/${fullPath}`
|
|
100
|
+
: `https://raw.githubusercontent.com/${project}/${branch}/${fullPath}`
|
|
101
|
+
try {
|
|
102
|
+
const content = await fetch(rawUrl).then((r) => (r.ok ? r.text() : null))
|
|
103
|
+
if (content !== null) {
|
|
104
|
+
await fs.writeFile(path.join(localDir, name), content, 'utf-8')
|
|
105
|
+
debug('已下载', fullPath)
|
|
106
|
+
count += 1
|
|
107
|
+
}
|
|
108
|
+
} catch {}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return count
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const fetchRepoFiles = async (repoUrl, sourcePath, dir, fs, path) => {
|
|
115
|
+
const m = repoUrl.match(/github\.com\/([^/]+\/[^/]+?)(?:\/|\.git)?$/i) || repoUrl.match(/gitee\.com\/([^/]+\/[^/]+?)(?:\/|\.git)?$/i)
|
|
116
|
+
if (!m) return 0
|
|
117
|
+
const project = m[1]
|
|
118
|
+
const dirPath = sourcePath.replace(/\/SKILL\.md$/i, '').replace(/^\/+/, '')
|
|
119
|
+
if (!dirPath) return 0
|
|
120
|
+
const isGitee = /gitee\.com/i.test(repoUrl)
|
|
121
|
+
let branch = isGitee ? 'master' : 'main'
|
|
122
|
+
if (!isGitee) {
|
|
123
|
+
try {
|
|
124
|
+
const repoInfo = await fetch(`https://api.github.com/repos/${project}`).then((r) => (r.ok ? r.json() : null))
|
|
125
|
+
if (repoInfo?.default_branch) branch = repoInfo.default_branch
|
|
126
|
+
} catch {}
|
|
127
|
+
}
|
|
128
|
+
return fetchRepoDir(project, dirPath, dir, branch, isGitee, fs, path)
|
|
129
|
+
}
|
|
130
|
+
|
|
65
131
|
const install = async (slug, version, cwd) => {
|
|
66
132
|
debug('install', { slug, version, cwd })
|
|
67
|
-
|
|
133
|
+
const fs = await import('fs/promises')
|
|
134
|
+
const path = await import('path')
|
|
135
|
+
let skill
|
|
68
136
|
if (version) {
|
|
69
|
-
data = await fetchJson(`/api/skills?status=published&slug=${encodeURIComponent(slug)}&version=${encodeURIComponent(version)}`)
|
|
137
|
+
let data = await fetchJson(`/api/skills?status=published&slug=${encodeURIComponent(slug)}&version=${encodeURIComponent(version)}`)
|
|
138
|
+
let items = data.items || []
|
|
139
|
+
if (items.length === 0) {
|
|
140
|
+
debug('slug 未匹配,尝试按名称搜索')
|
|
141
|
+
data = await fetchJson(`/api/skills?status=published&q=${encodeURIComponent(slug)}&pageSize=50`)
|
|
142
|
+
items = (data.items || []).filter((s) => s.version === version)
|
|
143
|
+
}
|
|
144
|
+
debug('查询结果:', items.length, '条')
|
|
145
|
+
skill = items[0]
|
|
146
|
+
if (!skill) {
|
|
147
|
+
debug('未找到匹配技能,slug=', slug, 'version=', version)
|
|
148
|
+
console.error(`未找到技能: ${slug}${version ? '@' + version : ''}`)
|
|
149
|
+
process.exit(1)
|
|
150
|
+
}
|
|
70
151
|
} else {
|
|
71
152
|
debug('从 MCP 获取技能列表')
|
|
72
153
|
const all = await fetchJson('/mcp/skills?latest=true')
|
|
73
154
|
const skills = all.skills || []
|
|
74
155
|
debug('MCP 返回', skills.length, '个技能')
|
|
75
|
-
|
|
156
|
+
skill = skills.find((s) => s.slug === slug || s.name === slug)
|
|
76
157
|
if (!skill) {
|
|
77
158
|
console.error(`未找到技能: ${slug}`)
|
|
78
159
|
process.exit(1)
|
|
79
160
|
}
|
|
80
|
-
const text = await fetchText(`/api/skills/${skill.id}/skill`)
|
|
81
|
-
const fs = await import('fs/promises')
|
|
82
|
-
const path = await import('path')
|
|
83
|
-
const dir = path.join(cwd, 'skills', skill.slug)
|
|
84
|
-
await fs.mkdir(dir, { recursive: true })
|
|
85
|
-
await fs.writeFile(path.join(dir, 'SKILL.md'), text, 'utf-8')
|
|
86
|
-
console.log(`已安装 ${skill.slug}@${skill.version} 到 ${dir}/SKILL.md`)
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
const items = data.items || []
|
|
90
|
-
debug('查询结果:', items.length, '条')
|
|
91
|
-
const skill = items[0]
|
|
92
|
-
if (!skill) {
|
|
93
|
-
debug('未找到匹配技能,slug=', slug, 'version=', version)
|
|
94
|
-
console.error(`未找到技能: ${slug}${version ? '@' + version : ''}`)
|
|
95
|
-
process.exit(1)
|
|
96
161
|
}
|
|
97
162
|
if (skill.review_tier === 'none' || !skill.review_tier) {
|
|
98
163
|
console.warn('警告:该技能未经审核,使用前请自行评估安全风险。')
|
|
99
164
|
}
|
|
100
165
|
const text = await fetchText(`/api/skills/${skill.id}/skill`)
|
|
101
|
-
const fs = await import('fs/promises')
|
|
102
|
-
const path = await import('path')
|
|
103
166
|
const dir = path.join(cwd, 'skills', skill.slug)
|
|
104
167
|
await fs.mkdir(dir, { recursive: true })
|
|
105
168
|
await fs.writeFile(path.join(dir, 'SKILL.md'), text, 'utf-8')
|
|
106
|
-
|
|
169
|
+
let extra = 0
|
|
170
|
+
if (skill.repo_url && skill.source_path) {
|
|
171
|
+
const detail = await fetchJson(`/api/skills/${skill.id}`)
|
|
172
|
+
const repoUrl = detail.repo_url || skill.repo_url
|
|
173
|
+
const sourcePath = detail.source_path || skill.source_path
|
|
174
|
+
if (repoUrl && sourcePath) {
|
|
175
|
+
extra = await fetchRepoFiles(repoUrl, sourcePath, dir, fs, path)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const files = extra > 0 ? ` (含 ${extra} 个代码文件)` : ''
|
|
179
|
+
console.log(`已安装 ${skill.slug}@${skill.version} 到 ${dir}${files}`)
|
|
107
180
|
}
|
|
108
181
|
|
|
109
182
|
const list = async (cwd) => {
|
|
@@ -208,7 +281,7 @@ SkillGo CLI - 技能管理工具 (https://skillgo.cn)
|
|
|
208
281
|
用法:
|
|
209
282
|
skillgo search <keyword> 搜索技能
|
|
210
283
|
skillgo install <slug> 安装技能到当前目录 skills/
|
|
211
|
-
skillgo install <slug>@<ver>
|
|
284
|
+
skillgo install <slug>@<ver> 安装指定版本(支持中文名/slug)
|
|
212
285
|
skillgo list 列出已安装技能
|
|
213
286
|
skillgo update --all 更新所有已安装技能
|
|
214
287
|
|