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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # SkillGo CLI
2
2
 
3
- SkillGo 命令行工具 - 搜索、安装、管理 Agent 技能。https://skillgo.cn
3
+ SkillGo 命令行工具 - 搜索、安装、管理 Agent 技能。安装时会递归下载 SKILL.md 及同目录、子目录下的所有代码文件。支持中文技能名,若 slug 未匹配会自动按名称搜索。https://skillgo.cn
4
4
 
5
5
  ## 安装
6
6
 
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
- let data
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
- const skill = skills.find((s) => s.slug === slug)
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
- console.log(`已安装 ${skill.slug}@${skill.version} ${dir}/SKILL.md`)
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillgo",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "SkillGo 命令行工具 - 搜索、安装、管理 Agent 技能 (https://skillgo.cn)",
5
5
  "bin": {
6
6
  "skillgo": "bin/skillgo.js"