harveyz-skill 0.6.0 → 0.6.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/bin/cli.js +172 -3
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -37,15 +37,20 @@ function printHelp() {
|
|
|
37
37
|
hskill install --scope <s> set scope: user (default) or project
|
|
38
38
|
hskill install --force overwrite existing installs
|
|
39
39
|
hskill list list available skills and bundles
|
|
40
|
+
hskill status show install status for all skills and tools
|
|
41
|
+
hskill outdated list skills and tools with available updates
|
|
42
|
+
hskill info <name> show install detail for a skill or tool
|
|
40
43
|
hskill update update hskill to the latest version
|
|
41
|
-
hskill
|
|
44
|
+
hskill version show version
|
|
42
45
|
hskill --help show this help
|
|
43
46
|
|
|
44
47
|
${chalk.cyan('Examples:')}
|
|
45
48
|
hskill install --bundle dev --target claude
|
|
46
49
|
hskill install --skill git-workflow-init --target claude --scope project
|
|
47
50
|
hskill install --tool p-launch
|
|
48
|
-
hskill
|
|
51
|
+
hskill status
|
|
52
|
+
hskill outdated
|
|
53
|
+
hskill info git-workflow-init
|
|
49
54
|
`)
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -54,7 +59,7 @@ if (args[0] === '--help' || args[0] === '-h') {
|
|
|
54
59
|
process.exit(0)
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
if (args[0] === '--version' || args[0] === '-v') {
|
|
62
|
+
if (args[0] === '--version' || args[0] === '-v' || subcommand === 'version') {
|
|
58
63
|
console.log(version)
|
|
59
64
|
process.exit(0)
|
|
60
65
|
}
|
|
@@ -92,6 +97,155 @@ if (subcommand === 'list') {
|
|
|
92
97
|
process.exit(0)
|
|
93
98
|
}
|
|
94
99
|
|
|
100
|
+
// ── Status / Outdated ─────────────────────────────────────────────────────────
|
|
101
|
+
if (subcommand === 'status' || subcommand === 'outdated') {
|
|
102
|
+
const outdatedOnly = subcommand === 'outdated'
|
|
103
|
+
const skillItems = getAllSkillItems()
|
|
104
|
+
const toolItems = getAllToolItems()
|
|
105
|
+
|
|
106
|
+
function icon(status) {
|
|
107
|
+
if (status === 'up-to-date') return chalk.green('✓')
|
|
108
|
+
if (status === 'update') return chalk.yellow('↑')
|
|
109
|
+
return chalk.dim('—')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const skillRows = skillItems.map(s => {
|
|
113
|
+
const inst = checkInstalled(s.skillName, s.version ?? '—')
|
|
114
|
+
return {
|
|
115
|
+
name: s.skillName, version: s.version ?? '—',
|
|
116
|
+
userStatus: scopeSummary(inst.user), projectStatus: scopeSummary(inst.project),
|
|
117
|
+
userDetail: inst.user, projectDetail: inst.project,
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
const toolRows = toolItems.map(t => {
|
|
121
|
+
const inst = checkToolInstalled(t.toolName, t.srcPath)
|
|
122
|
+
return { name: t.toolName, version: t.version ?? '—', ...inst }
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
if (outdatedOnly) {
|
|
126
|
+
const outdatedSkills = skillRows.filter(r => r.userStatus === 'update' || r.projectStatus === 'update')
|
|
127
|
+
const outdatedTools = toolRows.filter(r => r.status === 'update')
|
|
128
|
+
|
|
129
|
+
if (!outdatedSkills.length && !outdatedTools.length) {
|
|
130
|
+
console.log(chalk.green.bold('\n ✓ All installed skills and tools are up to date\n'))
|
|
131
|
+
process.exit(0)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const targets = ['claude', 'cursor', 'codex']
|
|
135
|
+
|
|
136
|
+
if (outdatedSkills.length) {
|
|
137
|
+
console.log('\n ' + chalk.bold('SKILLS WITH UPDATES'))
|
|
138
|
+
const nw = Math.max(...outdatedSkills.map(r => r.name.length))
|
|
139
|
+
for (const r of outdatedSkills) {
|
|
140
|
+
console.log(' ' + r.name.padEnd(nw) + ' available: ' + chalk.yellow(r.version))
|
|
141
|
+
for (const scope of ['user', 'project']) {
|
|
142
|
+
const detail = scope === 'user' ? r.userDetail : r.projectDetail
|
|
143
|
+
for (const t of targets) {
|
|
144
|
+
if (detail[t].status === 'update') {
|
|
145
|
+
console.log(' ' + chalk.dim(scope.padEnd(8) + t.padEnd(8)) + detail[t].version + ' → ' + chalk.yellow(r.version))
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (outdatedTools.length) {
|
|
153
|
+
console.log('\n ' + chalk.bold('TOOLS WITH UPDATES'))
|
|
154
|
+
const nw = Math.max(...outdatedTools.map(r => r.name.length))
|
|
155
|
+
for (const r of outdatedTools) {
|
|
156
|
+
console.log(' ' + r.name.padEnd(nw) + ' ' + chalk.dim(r.version) + ' → ' + chalk.yellow(r.version))
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
console.log('')
|
|
160
|
+
process.exit(0)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Full status table
|
|
164
|
+
const allNames = [...skillRows, ...toolRows].map(r => r.name)
|
|
165
|
+
const allVers = [...skillRows, ...toolRows].map(r => r.version)
|
|
166
|
+
const nw = Math.max(...allNames.map(n => n.length), 4)
|
|
167
|
+
const vw = Math.max(...allVers.map(v => v.length), 7)
|
|
168
|
+
const sep = chalk.dim(' ' + '─'.repeat(nw + vw + 20))
|
|
169
|
+
|
|
170
|
+
console.log('')
|
|
171
|
+
console.log(' ' + chalk.bold('SKILLS') + chalk.dim(` — ${skillRows.length} available`))
|
|
172
|
+
console.log(sep)
|
|
173
|
+
console.log(' ' + ''.padEnd(nw + vw + 3) + chalk.dim('user project'))
|
|
174
|
+
for (const r of skillRows) {
|
|
175
|
+
const u = icon(r.userStatus), p = icon(r.projectStatus)
|
|
176
|
+
console.log(' ' + r.name.padEnd(nw) + ' ' + chalk.dim(r.version.padEnd(vw)) + ' ' + u + ' ' + p)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log('')
|
|
180
|
+
console.log(' ' + chalk.bold('TOOLS') + chalk.dim(` — ${toolRows.length} available`))
|
|
181
|
+
console.log(sep)
|
|
182
|
+
for (const r of toolRows) {
|
|
183
|
+
console.log(' ' + r.name.padEnd(nw) + ' ' + chalk.dim(r.version.padEnd(vw)) + ' ' + icon(r.status))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const installedSkills = skillRows.filter(r => r.userStatus !== 'none' || r.projectStatus !== 'none').length
|
|
187
|
+
const installedTools = toolRows.filter(r => r.status !== 'none').length
|
|
188
|
+
const outdatedCount = skillRows.filter(r => r.userStatus === 'update' || r.projectStatus === 'update').length
|
|
189
|
+
+ toolRows.filter(r => r.status === 'update').length
|
|
190
|
+
const outdatedNote = outdatedCount ? ' · ' + chalk.yellow(outdatedCount + ' outdated') : ''
|
|
191
|
+
console.log('')
|
|
192
|
+
console.log(chalk.dim(` ${installedSkills} of ${skillRows.length} skills installed · ${installedTools} of ${toolRows.length} tools installed${outdatedNote}`))
|
|
193
|
+
console.log('')
|
|
194
|
+
process.exit(0)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── Info ──────────────────────────────────────────────────────────────────────
|
|
198
|
+
if (subcommand === 'info') {
|
|
199
|
+
const name = args[1]
|
|
200
|
+
if (!name) {
|
|
201
|
+
console.error(chalk.red(' ✗ Usage: hskill info <skill-or-tool-name>'))
|
|
202
|
+
process.exit(1)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const skillItems = getAllSkillItems()
|
|
206
|
+
const toolItems = getAllToolItems()
|
|
207
|
+
const skill = skillItems.find(s => s.skillName === name)
|
|
208
|
+
const tool = toolItems.find(t => t.toolName === name)
|
|
209
|
+
|
|
210
|
+
if (!skill && !tool) {
|
|
211
|
+
console.error(chalk.red(` ✗ Unknown: "${name}"`))
|
|
212
|
+
process.exit(1)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const targets = ['claude', 'cursor', 'codex']
|
|
216
|
+
|
|
217
|
+
function statusLabel(status) {
|
|
218
|
+
if (status === 'up-to-date') return chalk.green('✓ up to date')
|
|
219
|
+
if (status === 'update') return chalk.yellow('↑ update available')
|
|
220
|
+
return chalk.dim('— not installed')
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
console.log('')
|
|
224
|
+
if (skill) {
|
|
225
|
+
const inst = checkInstalled(skill.skillName, skill.version ?? '—')
|
|
226
|
+
console.log(' ' + chalk.bold(skill.skillName) + chalk.dim(' skill'))
|
|
227
|
+
console.log(' ' + chalk.dim('available: ') + (skill.version ?? '—'))
|
|
228
|
+
console.log('')
|
|
229
|
+
for (const [scopeLabel, detail] of [['USER LEVEL', inst.user], ['PROJECT LEVEL', inst.project]]) {
|
|
230
|
+
console.log(' ' + chalk.bold(scopeLabel))
|
|
231
|
+
for (const t of targets) {
|
|
232
|
+
const { version: v, status } = detail[t]
|
|
233
|
+
console.log(' ' + t.padEnd(8) + chalk.dim(v.padEnd(10)) + statusLabel(status))
|
|
234
|
+
}
|
|
235
|
+
console.log('')
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
const inst = checkToolInstalled(tool.toolName, tool.srcPath)
|
|
239
|
+
console.log(' ' + chalk.bold(tool.toolName) + chalk.dim(' shell tool'))
|
|
240
|
+
console.log(' ' + chalk.dim('available: ') + (tool.version ?? '—'))
|
|
241
|
+
console.log('')
|
|
242
|
+
console.log(' ' + chalk.bold('INSTALL STATUS'))
|
|
243
|
+
console.log(' ' + '~/.local/bin'.padEnd(14) + chalk.dim(inst.version.padEnd(10)) + statusLabel(inst.status))
|
|
244
|
+
console.log('')
|
|
245
|
+
}
|
|
246
|
+
process.exit(0)
|
|
247
|
+
}
|
|
248
|
+
|
|
95
249
|
// ── Install ───────────────────────────────────────────────────────────────────
|
|
96
250
|
// subcommand is 'install' or omitted (default behavior)
|
|
97
251
|
const installArgs = subcommand === 'install' ? args.slice(1) : args
|
|
@@ -110,8 +264,23 @@ const scopeArg = scopeIdx !== -1 ? installArgs[scopeIdx + 1] : undefined
|
|
|
110
264
|
|
|
111
265
|
const TOOL_BUNDLE_VALUES = new Set(TOOL_BUNDLE_CHOICES.map(c => c.value))
|
|
112
266
|
|
|
267
|
+
function requireFzf() {
|
|
268
|
+
const probe = spawnSync('fzf', ['--version'], { encoding: 'utf8' })
|
|
269
|
+
if (probe.error || probe.status !== 0) {
|
|
270
|
+
console.error(chalk.red(' ✗ fzf is required but not installed.'))
|
|
271
|
+
console.error('')
|
|
272
|
+
console.error(' Install it with:')
|
|
273
|
+
console.error(chalk.cyan(' brew install fzf') + chalk.dim(' # macOS'))
|
|
274
|
+
console.error(chalk.cyan(' sudo apt install fzf') + chalk.dim(' # Debian/Ubuntu'))
|
|
275
|
+
console.error(chalk.cyan(' sudo dnf install fzf') + chalk.dim(' # Fedora'))
|
|
276
|
+
console.error('')
|
|
277
|
+
process.exit(1)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
113
281
|
// 用 fzf 交互式选择 skill/tool,返回选中的 item 列表
|
|
114
282
|
function fzfSelect() {
|
|
283
|
+
requireFzf()
|
|
115
284
|
const skillItems = getAllSkillItems()
|
|
116
285
|
const toolItems = getAllToolItems()
|
|
117
286
|
const previewPath = path.join(__dirname, 'preview.mjs')
|