skillgo 1.0.0 → 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.
Files changed (3) hide show
  1. package/README.md +3 -2
  2. package/bin/skillgo.js +161 -33
  3. package/package.json +8 -2
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
 
@@ -23,4 +23,5 @@ skillgo update --all # 更新所有已安装技能
23
23
 
24
24
  ## 环境变量
25
25
 
26
- - `SKILLGO_API`: API 地址,默认 `http://localhost:3100`,线上使用 `https://skillgo.cn`
26
+ - `SKILLGO_API`: API 地址,默认 `http://skillgo.cn`
27
+ - `SKILLGO_DEBUG`: 设为 `1` 启用调试模式,或使用 `--debug` / `-d` 参数
package/bin/skillgo.js CHANGED
@@ -1,20 +1,53 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict'
3
3
 
4
- const API_BASE = process.env.SKILLGO_API || 'http://localhost:3100'
4
+ const isDebug = () => process.env.SKILLGO_DEBUG === '1' || process.env.SKILLGO_DEBUG === 'true'
5
+ const API_BASE = process.env.SKILLGO_API || 'http://skillgo.cn'
5
6
 
6
- const fetchJson = async (path) => {
7
- const res = await fetch(`${API_BASE}${path}`)
8
- if (!res.ok) throw new Error(`请求失败: ${res.status}`)
9
- return res.json()
7
+ const debug = (...args) => {
8
+ if (isDebug()) console.error('[skillgo]', ...args)
10
9
  }
11
10
 
12
- const fetchText = async (path) => {
13
- const res = await fetch(`${API_BASE}${path}`)
14
- if (!res.ok) throw new Error(`请求失败: ${res.status}`)
15
- return res.text()
11
+ const wrapFetch = (fn) => async (...args) => {
12
+ try {
13
+ return await fn(...args)
14
+ } catch (e) {
15
+ if (isDebug()) {
16
+ console.error('[skillgo] 请求失败:', e.message)
17
+ if (e.cause) console.error('[skillgo] cause:', e.cause)
18
+ if (e.stack) console.error('[skillgo] stack:', e.stack)
19
+ }
20
+ const msg = e.message || ''
21
+ const isNetworkErr = msg.includes('fetch failed') || e.cause?.code === 'ECONNREFUSED' || e.cause?.code === 'ENOTFOUND'
22
+ if (isNetworkErr) {
23
+ throw new Error(
24
+ `无法连接 SkillGo API (${API_BASE})。请检查网络或设置 SKILLGO_API`
25
+ )
26
+ }
27
+ throw e
28
+ }
16
29
  }
17
30
 
31
+ const fetchJson = wrapFetch(async (path) => {
32
+ const url = `${API_BASE}${path}`
33
+ debug('GET', url)
34
+ const start = Date.now()
35
+ const res = await fetch(url)
36
+ debug('响应', res.status, `${Date.now() - start}ms`)
37
+ if (!res.ok) throw new Error(`请求失败: ${res.status} ${res.statusText}`)
38
+ return res.json()
39
+ })
40
+
41
+ const fetchText = wrapFetch(async (path) => {
42
+ const url = `${API_BASE}${path}`
43
+ debug('GET', url)
44
+ const start = Date.now()
45
+ const res = await fetch(url)
46
+ debug('响应', res.status, `${Date.now() - start}ms`)
47
+ if (!res.ok) throw new Error(`请求失败: ${res.status} ${res.statusText}`)
48
+ return res.text()
49
+ })
50
+
18
51
  const search = async (keyword) => {
19
52
  const data = await fetchJson(`/api/skills?status=published&q=${encodeURIComponent(keyword)}&pageSize=20`)
20
53
  const items = data.items || []
@@ -29,43 +62,121 @@ const search = async (keyword) => {
29
62
  })
30
63
  }
31
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
+
32
131
  const install = async (slug, version, cwd) => {
33
- let data
132
+ debug('install', { slug, version, cwd })
133
+ const fs = await import('fs/promises')
134
+ const path = await import('path')
135
+ let skill
34
136
  if (version) {
35
- 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
+ }
36
151
  } else {
152
+ debug('从 MCP 获取技能列表')
37
153
  const all = await fetchJson('/mcp/skills?latest=true')
38
154
  const skills = all.skills || []
39
- const skill = skills.find((s) => s.slug === slug)
155
+ debug('MCP 返回', skills.length, '个技能')
156
+ skill = skills.find((s) => s.slug === slug || s.name === slug)
40
157
  if (!skill) {
41
158
  console.error(`未找到技能: ${slug}`)
42
159
  process.exit(1)
43
160
  }
44
- const text = await fetchText(`/api/skills/${skill.id}/skill`)
45
- const fs = await import('fs/promises')
46
- const path = await import('path')
47
- const dir = path.join(cwd, 'skills', skill.slug)
48
- await fs.mkdir(dir, { recursive: true })
49
- await fs.writeFile(path.join(dir, 'SKILL.md'), text, 'utf-8')
50
- console.log(`已安装 ${skill.slug}@${skill.version} 到 ${dir}/SKILL.md`)
51
- return
52
- }
53
- const items = data.items || []
54
- const skill = items[0]
55
- if (!skill) {
56
- console.error(`未找到技能: ${slug}${ver ? '@' + ver : ''}`)
57
- process.exit(1)
58
161
  }
59
162
  if (skill.review_tier === 'none' || !skill.review_tier) {
60
163
  console.warn('警告:该技能未经审核,使用前请自行评估安全风险。')
61
164
  }
62
165
  const text = await fetchText(`/api/skills/${skill.id}/skill`)
63
- const fs = await import('fs/promises')
64
- const path = await import('path')
65
166
  const dir = path.join(cwd, 'skills', skill.slug)
66
167
  await fs.mkdir(dir, { recursive: true })
67
168
  await fs.writeFile(path.join(dir, 'SKILL.md'), text, 'utf-8')
68
- 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}`)
69
180
  }
70
181
 
71
182
  const list = async (cwd) => {
@@ -119,7 +230,15 @@ const update = async (cwd) => {
119
230
  }
120
231
 
121
232
  const main = async () => {
122
- const args = process.argv.slice(2)
233
+ let args = process.argv.slice(2)
234
+ if (args.includes('--debug') || args.includes('-d')) {
235
+ args = args.filter((a) => a !== '--debug' && a !== '-d')
236
+ process.env.SKILLGO_DEBUG = '1'
237
+ }
238
+ if (isDebug()) {
239
+ debug('API_BASE:', process.env.SKILLGO_API || '(默认)', '->', API_BASE)
240
+ debug('命令:', args[0], '参数:', args.slice(1))
241
+ }
123
242
  const cmd = args[0]
124
243
  const cwd = process.cwd()
125
244
 
@@ -162,16 +281,25 @@ SkillGo CLI - 技能管理工具 (https://skillgo.cn)
162
281
  用法:
163
282
  skillgo search <keyword> 搜索技能
164
283
  skillgo install <slug> 安装技能到当前目录 skills/
165
- skillgo install <slug>@<ver> 安装指定版本
284
+ skillgo install <slug>@<ver> 安装指定版本(支持中文名/slug)
166
285
  skillgo list 列出已安装技能
167
286
  skillgo update --all 更新所有已安装技能
168
287
 
288
+ 选项:
289
+ --debug, -d 调试模式,输出请求详情和错误堆栈
290
+
169
291
  环境变量:
170
- SKILLGO_API API 地址,默认 http://localhost:3100
292
+ SKILLGO_API API 地址,默认 http://skillgo.cn
293
+ SKILLGO_DEBUG 设为 1 启用调试模式
171
294
  `)
172
295
  }
173
296
 
174
297
  main().catch((e) => {
175
- console.error(e.message)
298
+ if (isDebug()) {
299
+ console.error('[skillgo] 错误:', e.message)
300
+ if (e.stack) console.error(e.stack)
301
+ } else {
302
+ console.error(e.message)
303
+ }
176
304
  process.exit(1)
177
305
  })
package/package.json CHANGED
@@ -1,11 +1,17 @@
1
1
  {
2
2
  "name": "skillgo",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "SkillGo 命令行工具 - 搜索、安装、管理 Agent 技能 (https://skillgo.cn)",
5
5
  "bin": {
6
6
  "skillgo": "bin/skillgo.js"
7
7
  },
8
- "keywords": ["skillgo", "cli", "skill", "agent", "skillgo.cn"],
8
+ "keywords": [
9
+ "skillgo",
10
+ "cli",
11
+ "skill",
12
+ "agent",
13
+ "skillgo.cn"
14
+ ],
9
15
  "author": "",
10
16
  "license": "Apache-2.0",
11
17
  "repository": {