@guozhi-fe/skills 0.1.0 → 0.2.0
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 +25 -3
- package/bin/gzfe-skills.js +77 -11
- package/package.json +21 -5
package/README.md
CHANGED
|
@@ -9,14 +9,36 @@
|
|
|
9
9
|
|
|
10
10
|
## 首次安装
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
可用 `npm i -g @guozhi-fe/skills` 安装已发布的 CLI,或通过 `npx --package=@guozhi-fe/skills gzfe-skills init` 直接接入。试用本地修改时,在本仓库运行 `npm ci` 和 `npm pack`,再运行 `npm i -g /绝对路径/guozhi-fe-skills-0.2.0.tgz`。
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
gzfe-skills init
|
|
15
|
+
gzfe-skills init
|
|
16
16
|
gzfe-skills list
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
从 0.2.0 开始,省略技能名会打开交互列表,逐项显示技能名及 `SKILL.md` 中的简介。使用上下键移动、空格勾选、回车确认;勾选一项安装一个技能,勾选多项批量安装。按 `a` 全选或取消全选,按 `Ctrl+C` 取消。未勾选任何技能时按回车会取消安装。
|
|
20
|
+
|
|
21
|
+
直接指定技能名可以跳过交互。脚本中可使用 `--all` 安装全部技能,无终端时需要显式指定技能名或 `--all`。
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
gzfe-skills init microfrontend-memory-assessment
|
|
25
|
+
gzfe-skills init microfrontend-memory-assessment microfrontend-memory-lifecycle
|
|
26
|
+
gzfe-skills init --all
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
文件和版本记录位于当前项目的 `.agents/skills/<技能名>/`,建议一起提交到项目 Git 仓库。重复 init 会保留已安装版本,未勾选的已安装技能也会保留。
|
|
30
|
+
|
|
31
|
+
## 卸载
|
|
32
|
+
|
|
33
|
+
不带参数运行 `uninstall` 会列出本工具管理的已安装技能,使用与 init 相同的勾选方式。也可直接指定技能名,或使用 `--all` 批量卸载。
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
gzfe-skills uninstall
|
|
37
|
+
gzfe-skills uninstall microfrontend-memory-assessment
|
|
38
|
+
gzfe-skills uninstall --all
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
卸载会移除所选技能目录及其安装记录。如果检测到本地文件有改动,会列出文件并停止。全量卸载只处理本工具管理的技能。卸载后可再次运行 `gzfe-skills init` 安装。
|
|
20
42
|
|
|
21
43
|
## 更新
|
|
22
44
|
|
package/bin/gzfe-skills.js
CHANGED
|
@@ -3,6 +3,10 @@ import fs from 'node:fs'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
import { createHash } from 'node:crypto'
|
|
6
|
+
import checkbox from '@inquirer/checkbox'
|
|
7
|
+
import { parse } from 'yaml'
|
|
8
|
+
import colors from 'yoctocolors-cjs'
|
|
9
|
+
import wrapAnsi from 'wrap-ansi'
|
|
6
10
|
|
|
7
11
|
const root = fileURLToPath(new URL('../', import.meta.url))
|
|
8
12
|
const source = path.join(root, 'skills')
|
|
@@ -51,15 +55,62 @@ function stage(name, dir) {
|
|
|
51
55
|
fs.writeFileSync(path.join(dir, marker), JSON.stringify({ name, version, files: manifest(dir) }, null, 2) + '\n')
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
function
|
|
58
|
+
async function selectSkills(command, names) {
|
|
59
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
60
|
+
throw Error(`交互选择需要终端,请指定技能名,或使用 gzfe-skills ${command} --all。`)
|
|
61
|
+
}
|
|
62
|
+
const choices = names.map(name => {
|
|
63
|
+
const file = path.join(source, name, 'SKILL.md')
|
|
64
|
+
const frontmatter = fs.readFileSync(file, 'utf8').match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)
|
|
65
|
+
const metadata = frontmatter && parse(frontmatter[1])
|
|
66
|
+
if (typeof metadata?.description !== 'string' || !metadata.description.trim()) {
|
|
67
|
+
throw Error('技能缺少有效简介:' + file)
|
|
68
|
+
}
|
|
69
|
+
const description = metadata.description.replace(/\s+/g, ' ').trim()
|
|
70
|
+
const summary = description.match(/^[^。!?]+[。!?]?/u)?.[0] || description
|
|
71
|
+
const width = Math.max(16, Math.min(88, (process.stdout.columns || 80) - 4))
|
|
72
|
+
const lines = wrapAnsi(summary, width, { hard: true }).split('\n')
|
|
73
|
+
const detail = lines.map(line => ' ' + colors.dim(line)).join('\n')
|
|
74
|
+
return { name: `${colors.bold(name)}\n${detail}\n`, short: name, value: name }
|
|
75
|
+
})
|
|
76
|
+
return checkbox({
|
|
77
|
+
message: `选择要${command === 'init' ? '安装' : '卸载'}的技能(可单选或多选)`,
|
|
78
|
+
choices,
|
|
79
|
+
pageSize: 12,
|
|
80
|
+
theme: {
|
|
81
|
+
style: {
|
|
82
|
+
highlight: text => {
|
|
83
|
+
const [title, ...detail] = text.split('\n')
|
|
84
|
+
return [colors.cyan(title), ...detail].join('\n')
|
|
85
|
+
},
|
|
86
|
+
keysHelpTip: () => '↑↓ 移动 · 空格 勾选 · a 全选/取消全选 · 回车 确认 · Ctrl+C 取消',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function run([command, ...args]) {
|
|
55
93
|
if (!command || ['--help', '-h'].includes(command)) {
|
|
56
|
-
console.log(
|
|
94
|
+
console.log([
|
|
95
|
+
'gzfe-skills init 交互选择技能,支持单选和多选',
|
|
96
|
+
'gzfe-skills init <技能名 ...> 直接安装指定技能',
|
|
97
|
+
'gzfe-skills init --all 安装全部技能,可用于脚本',
|
|
98
|
+
'gzfe-skills list 查看项目安装状态',
|
|
99
|
+
'gzfe-skills update [技能名 ...] [--dry-run]',
|
|
100
|
+
'gzfe-skills uninstall 交互选择要卸载的已安装技能',
|
|
101
|
+
'gzfe-skills uninstall <技能名 ...> 卸载指定技能',
|
|
102
|
+
'gzfe-skills uninstall --all 卸载本工具管理的全部技能',
|
|
103
|
+
'',
|
|
104
|
+
...catalog,
|
|
105
|
+
].join('\n'))
|
|
57
106
|
return
|
|
58
107
|
}
|
|
59
108
|
if (command === '--version') return console.log(version)
|
|
60
|
-
if (!['init', 'update', 'list'].includes(command)) throw Error('未知命令:' + command)
|
|
109
|
+
if (!['init', 'update', 'list', 'uninstall'].includes(command)) throw Error('未知命令:' + command)
|
|
61
110
|
const dry = command === 'update' && args.includes('--dry-run')
|
|
62
|
-
const
|
|
111
|
+
const all = ['init', 'uninstall'].includes(command) && args.includes('--all')
|
|
112
|
+
let names = [...new Set(args.filter(x => !(dry && x === '--dry-run') && !(all && x === '--all')))]
|
|
113
|
+
if (all && names.length) throw Error('--all 不能与技能名同时使用')
|
|
63
114
|
if (names.some(x => !catalog.includes(x)) || (command === 'list' && names.length)) throw Error('参数无效,请查看 --help')
|
|
64
115
|
for (const dir of [path.dirname(dest), dest]) {
|
|
65
116
|
const entry = fs.lstatSync(dir, { throwIfNoEntry: false })
|
|
@@ -69,12 +120,18 @@ function run([command, ...args]) {
|
|
|
69
120
|
for (const name of catalog) console.log(name, status(name).label)
|
|
70
121
|
return
|
|
71
122
|
}
|
|
123
|
+
if (['init', 'uninstall'].includes(command) && !names.length) {
|
|
124
|
+
const available = command === 'uninstall' ? catalog.filter(name => status(name).saved) : catalog
|
|
125
|
+
if (!available.length) return console.log('当前项目没有由 gzfe-skills 管理的已安装技能')
|
|
126
|
+
names = all ? available : await selectSkills(command, available)
|
|
127
|
+
if (!names.length) return console.log('未选择技能,操作已取消')
|
|
128
|
+
}
|
|
72
129
|
const plan = () => (names.length ? names : catalog).filter(name => {
|
|
73
130
|
const state = status(name)
|
|
74
131
|
if (command === 'update' && !names.length && !state.saved) return false
|
|
75
|
-
if (!state.saved && (command
|
|
132
|
+
if (!state.saved && (command !== 'init' || state.label !== '未安装')) throw Error(name + ':' + state.label)
|
|
76
133
|
if (state.changes?.length) throw Error(name + ' 有本地修改:\n' + state.changes.join('\n'))
|
|
77
|
-
return !state.saved || (command === 'update' && state.saved.version !== version)
|
|
134
|
+
return command === 'uninstall' || !state.saved || (command === 'update' && state.saved.version !== version)
|
|
78
135
|
})
|
|
79
136
|
if (dry) {
|
|
80
137
|
const selected = plan()
|
|
@@ -87,17 +144,21 @@ function run([command, ...args]) {
|
|
|
87
144
|
let cleanup = true
|
|
88
145
|
try {
|
|
89
146
|
const selected = plan()
|
|
90
|
-
|
|
147
|
+
if (command !== 'uninstall') {
|
|
148
|
+
for (const name of selected) stage(name, path.join(work, name))
|
|
149
|
+
}
|
|
91
150
|
for (const name of selected) {
|
|
92
151
|
const target = path.join(dest, name)
|
|
93
152
|
const backup = path.join(work, name + '.old')
|
|
94
153
|
const existed = fs.existsSync(target)
|
|
95
154
|
if (existed) fs.renameSync(target, backup)
|
|
96
155
|
applied.push({ target, backup, existed, installed: false })
|
|
156
|
+
if (command === 'uninstall') continue
|
|
97
157
|
fs.renameSync(path.join(work, name), target)
|
|
98
158
|
applied.at(-1).installed = true
|
|
99
159
|
}
|
|
100
|
-
|
|
160
|
+
const result = command === 'uninstall' ? '已卸载:' + selected.join(', ') : selected.join(', ') + ' → ' + version
|
|
161
|
+
console.log(selected.length ? result : '无需变更')
|
|
101
162
|
} catch (error) {
|
|
102
163
|
cleanup = false
|
|
103
164
|
for (const item of applied.reverse()) {
|
|
@@ -110,7 +171,12 @@ function run([command, ...args]) {
|
|
|
110
171
|
if (cleanup) fs.rmSync(work, { recursive: true })
|
|
111
172
|
}
|
|
112
173
|
}
|
|
113
|
-
try { run(process.argv.slice(2)) } catch (error) {
|
|
114
|
-
|
|
115
|
-
|
|
174
|
+
try { await run(process.argv.slice(2)) } catch (error) {
|
|
175
|
+
if (error.name === 'ExitPromptError') {
|
|
176
|
+
console.log('操作已取消')
|
|
177
|
+
process.exitCode = 130
|
|
178
|
+
} else {
|
|
179
|
+
console.error(error.message)
|
|
180
|
+
process.exitCode = 1
|
|
181
|
+
}
|
|
116
182
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guozhi-fe/skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "国智前端项目级 Codex 技能集",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
6
|
+
"bin": {
|
|
7
|
+
"gzfe-skills": "bin/gzfe-skills.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"skills"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"repository": "github:soleilLulu/gz-fe-skills",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "node --test"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@inquirer/checkbox": "^4.3.2",
|
|
22
|
+
"wrap-ansi": "^6.2.0",
|
|
23
|
+
"yaml": "^2.9.1",
|
|
24
|
+
"yoctocolors-cjs": "^2.1.3"
|
|
25
|
+
}
|
|
10
26
|
}
|