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.
Files changed (2) hide show
  1. package/bin/cli.js +172 -3
  2. 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 --version show version
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 update
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')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harveyz-skill",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Skill manager for Claude Code, Cursor, and Codex",
5
5
  "type": "module",
6
6
  "bin": {