harveyz-skill 0.9.0 → 0.11.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/CHANGELOG.md +42 -1
- package/bin/cli.js +160 -132
- package/bin/preview.mjs +7 -5
- package/lib/bundles.js +4 -3
- package/lib/targets.js +1 -1
- package/package.json +9 -1
- package/skills/analysis/git-cleanup/SKILL.md +1 -1
- package/skills/analysis/skill-analyzer/SKILL.md +1 -1
- package/skills/data-extraction/vision-extract/SKILL.md +154 -0
- package/skills/design/sync-design-html/SKILL.md +1 -0
- package/skills/harness/diataxis-docs/SKILL.md +1 -0
- package/skills/harness/dir-manage/SKILL.md +113 -0
- package/skills/harness/dir-manage/references/built-in/diataxis.md +88 -0
- package/skills/harness/dir-manage/references/built-in/role-based.md +108 -0
- package/skills/harness/dir-manage/references/methodology-spec.md +98 -0
- package/skills/harness/full-stack-debug-env/SKILL.md +1 -0
- package/skills/harness/git-workflow-init/SKILL.md +1 -1
- package/skills/harness/migrate-specs/SKILL.md +184 -0
- package/skills/meta/contribute-skill/SKILL.md +1 -1
- package/skills/meta/npm-release/SKILL.md +202 -0
- package/skills/meta/skill-publish/SKILL.md +171 -0
- package/skills/superpowers-fork/brainstorming/SKILL.md +1 -1
- package/skills/superpowers-fork/executing-plans/SKILL.md +1 -1
- package/skills/superpowers-fork/systematic-debugging/SKILL.md +1 -1
- package/skills/superpowers-fork/using-git-worktrees/SKILL.md +1 -1
- package/skills/superpowers-fork/writing-plans/SKILL.md +1 -1
- package/skills/task/pm-task-dispatch/SKILL.md +2 -1
- package/skills/task/task-close/SKILL.md +2 -1
- package/skills/web-fetch/article-fetcher/SKILL.md +2 -5
- package/skills/web-fetch/article-fetcher/references/__pycache__/article_utils.cpython-314.pyc +0 -0
- package/skills/web-fetch/url-extract/SKILL.md +218 -0
- package/skills/web-fetch/url-extract/platforms/SKILL.claude.md +30 -0
- package/skills/web-fetch/url-extract/platforms/SKILL.codex.md +19 -0
- package/skills/web-fetch/url-extract/platforms/SKILL.hermes.md +19 -0
- package/skills/web-fetch/url-extract/references/__pycache__/article_utils.cpython-314.pyc +0 -0
- package/skills/web-fetch/url-extract/references/article_utils.py +250 -0
- package/skills/web-fetch/url-extract/references/core-flow.md +188 -0
- package/skills/web-fetch/url-extract/references/file-format.md +73 -0
- package/skills/web-fetch/url-extract/scripts/dedup_check.py +41 -0
- package/skills/web-fetch/url-extract/scripts/playwright_web.py +172 -0
- package/skills/web-fetch/url-extract/scripts/playwright_xcom.py +221 -0
- package/skills/web-fetch/url-extract/scripts/validate_article.py +36 -0
- package/skills/web-fetch/url-extract/vars.json +12 -0
- package/skills/workflow/add-todo/SKILL.md +205 -0
- package/skills/writing/capture-insight/SKILL.md +112 -0
- package/skills/writing/mermaid-diagram/SKILL.md +1 -6
- package/skills-index.json +17 -5
- package/tools/p-launch/__pycache__/p_launch.cpython-314.pyc +0 -0
- package/tools/p-launch/tests/__pycache__/test_p_launch.cpython-314-pytest-9.0.2.pyc +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [0.
|
|
10
|
+
## [0.11.0] - 2026-06-09
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `hskill list`:新增 Bundle 列并按 bundle 名称排序(替换旧的分组标题格式);`hskill status` skills 列表同步增加 Bundle 列
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- `hskill` 内部重构:平台列表从 `SKILL_TARGETS` 单一来源派生,消除 `bundles.js` / `preview.mjs` 中的硬编码数组
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- `url-extract`:移除 `SKILL_DIR` 用户配置变量
|
|
20
|
+
- `preview`:安装状态侧边栏补全 openclaw、hermes 平台(USER LEVEL)
|
|
21
|
+
- `npm-release`:中间步骤跳过 push 操作,最终统一给出推送 + 发布指令清单
|
|
22
|
+
|
|
23
|
+
## [0.10.0] - 2026-06-08
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- `url-extract` skill(`data-extraction` bundle):跨平台 URL 存档工具,支持 Claude Code / Codex / Hermes,含 Playwright 抓取、SQLite 存储、VAULT_PATH / SKILL_DIR / CHROME_PROFILE 配置
|
|
27
|
+
- `vision-extract` skill(`data-extraction` bundle):从 hermes-skills 贡献的视觉数据提取 skill
|
|
28
|
+
- `skill-publish` skill(`meta` bundle):检查 skill 格式合规性与 skills-index.json 注册状态
|
|
29
|
+
- `npm-release` skill(`meta` bundle):完整 npm 发布工作流(版本号 → CHANGELOG → 分支 → tag → publish)
|
|
30
|
+
- `migrate-specs` skill(`harness` bundle):将旧 spec 文档迁移为 Diataxis 结构
|
|
31
|
+
- `dir-manage` skill(`document` bundle):从 writing-agent 贡献的目录管理 skill
|
|
32
|
+
- `capture-insight` skill(`writing` bundle):从 writing-agent 贡献的洞察捕获 skill
|
|
33
|
+
- `add-todo` skill(`workflow` bundle):从 harveyz-skill workflow 贡献的 TODO 追踪 skill
|
|
34
|
+
- Diataxis 结构文档:cache、config、bundle、info 模块的参考文档与指南
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- `hskill` 安装交互优化:改为两步式(先选 target,再选 scope),更符合操作直觉
|
|
38
|
+
- `url-extract`:通用 SKILL.md + 各平台 patch 文件,支持跨平台复用
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
- installer:`--force` 时按 `uninstallPaths` 精确清理,支持版本感知 venv 升级
|
|
42
|
+
- `url-extract`:以 `source_url` 为主键,支持存量 DB schema 迁移
|
|
43
|
+
- `url-extract`:命令注入安全加固,usability 改进
|
|
44
|
+
- SKILL.md frontmatter 跨所有 skill 规范化
|
|
45
|
+
|
|
46
|
+
## [0.9.0] - 2026-05-29
|
|
11
47
|
|
|
12
48
|
### Added
|
|
13
49
|
- `hskill uninstall <tool>` 命令:清理 binary、Python 模块、tool.json、venv 及 zshrc snippet
|
|
@@ -22,6 +58,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
22
58
|
### Changed
|
|
23
59
|
- p-launch `tool.json` 新增 `uninstallPaths`(`p-launch-venv`)和 `configPaths`(`~/.config/p-launch`)
|
|
24
60
|
|
|
61
|
+
### Fixed
|
|
62
|
+
- installer:`--force` 重装时根据 `uninstallPaths` 精确清理旧版本文件,避免残留
|
|
63
|
+
- installer:`--force` 重装时正确清理 venv,支持版本感知升级
|
|
64
|
+
- tool 版本对比逻辑:upgrade 前先读取已安装版本,跳过同版本重复安装
|
|
65
|
+
|
|
25
66
|
## [0.8.1] - 2026-05-24
|
|
26
67
|
|
|
27
68
|
### Fixed
|
package/bin/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
resolveSkills, resolveSkillsByName, resolveTools, resolveToolsByName,
|
|
13
13
|
TOOL_BUNDLE_CHOICES,
|
|
14
14
|
} from '../lib/bundles.js'
|
|
15
|
-
import { buildTargetChoices, resolveTargets, TARGETS } from '../lib/targets.js'
|
|
15
|
+
import { buildTargetChoices, resolveTargets, TARGETS, USER_ONLY_TARGETS } from '../lib/targets.js'
|
|
16
16
|
import { installSkills, installTools, installHooks, installHooksForTarget, uninstallHook, uninstallTool, uninstallSkill } from '../lib/installer.js'
|
|
17
17
|
|
|
18
18
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
@@ -158,29 +158,31 @@ if (subcommand === 'update') {
|
|
|
158
158
|
|
|
159
159
|
// ── List ─────────────────────────────────────────────────────────────────────
|
|
160
160
|
if (subcommand === 'list') {
|
|
161
|
-
const {
|
|
162
|
-
const
|
|
163
|
-
for (const s of skills) {
|
|
164
|
-
if (!byBundle[s.bundle]) byBundle[s.bundle] = []
|
|
165
|
-
byBundle[s.bundle].push(s.path)
|
|
166
|
-
}
|
|
161
|
+
const { skills, tools = [] } = require('../skills-index.json')
|
|
162
|
+
const sorted = [...skills].sort((a, b) => a.bundle.localeCompare(b.bundle) || a.path.split('/').pop().localeCompare(b.path.split('/').pop()))
|
|
167
163
|
if (jsonFlag) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
console.log(JSON.stringify({ bundles, tools: tools.map(t => t.name) }, null, 2))
|
|
164
|
+
console.log(JSON.stringify({
|
|
165
|
+
skills: sorted.map(s => ({ name: s.path.split('/').pop(), path: s.path, bundle: s.bundle })),
|
|
166
|
+
tools: tools.map(t => t.name),
|
|
167
|
+
}, null, 2))
|
|
173
168
|
process.exit(0)
|
|
174
169
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
170
|
+
const nw = Math.max(...sorted.map(s => s.path.split('/').pop().length), 4)
|
|
171
|
+
const bw = Math.max(...sorted.map(s => s.bundle.length), 6)
|
|
172
|
+
const sep = chalk.dim(' ' + '─'.repeat(nw + bw + 4))
|
|
173
|
+
console.log('')
|
|
174
|
+
console.log(' ' + chalk.bold('NAME'.padEnd(nw)) + ' ' + chalk.bold('BUNDLE'))
|
|
175
|
+
console.log(sep)
|
|
176
|
+
for (const s of sorted) {
|
|
177
|
+
console.log(' ' + s.path.split('/').pop().padEnd(nw) + ' ' + chalk.dim(s.bundle))
|
|
178
178
|
}
|
|
179
179
|
if (tools.length > 0) {
|
|
180
180
|
console.log('')
|
|
181
|
-
console.log(chalk.bold('
|
|
181
|
+
console.log(' ' + chalk.bold('SHELL TOOLS'))
|
|
182
|
+
console.log(sep)
|
|
182
183
|
for (const t of tools) console.log(' ' + t.name)
|
|
183
184
|
}
|
|
185
|
+
console.log('')
|
|
184
186
|
process.exit(0)
|
|
185
187
|
}
|
|
186
188
|
|
|
@@ -208,11 +210,11 @@ if (subcommand === 'status' || subcommand === 'outdated') {
|
|
|
208
210
|
const skillRows = skillItems.map(s => {
|
|
209
211
|
const inst = checkInstalled(s.skillName, s.version ?? '—')
|
|
210
212
|
return {
|
|
211
|
-
name: s.skillName, version: s.version ?? '—',
|
|
213
|
+
name: s.skillName, bundle: s.bundle ?? '—', version: s.version ?? '—',
|
|
212
214
|
userStatus: scopeSummary(inst.user), projectStatus: scopeSummary(inst.project),
|
|
213
215
|
userDetail: inst.user, projectDetail: inst.project,
|
|
214
216
|
}
|
|
215
|
-
})
|
|
217
|
+
}).sort((a, b) => a.bundle.localeCompare(b.bundle) || a.name.localeCompare(b.name))
|
|
216
218
|
const toolRows = toolItems.map(t => {
|
|
217
219
|
const inst = checkToolInstalled(t.toolName, t.srcPath)
|
|
218
220
|
return { name: t.toolName, version: t.version ?? '—', ...inst }
|
|
@@ -285,15 +287,16 @@ if (subcommand === 'status' || subcommand === 'outdated') {
|
|
|
285
287
|
const allVers = [...skillRows, ...toolRows].map(r => r.version)
|
|
286
288
|
const nw = Math.max(...allNames.map(n => n.length), 4)
|
|
287
289
|
const vw = Math.max(...allVers.map(v => v.length), 7)
|
|
288
|
-
const
|
|
290
|
+
const bw = Math.max(...skillRows.map(r => r.bundle.length), 6)
|
|
291
|
+
const sep = chalk.dim(' ' + '─'.repeat(nw + vw + bw + 22))
|
|
289
292
|
|
|
290
293
|
console.log('')
|
|
291
294
|
console.log(' ' + chalk.bold('SKILLS') + chalk.dim(` — ${skillRows.length} available`))
|
|
292
295
|
console.log(sep)
|
|
293
|
-
console.log(' ' + ''.padEnd(nw + vw +
|
|
296
|
+
console.log(' ' + ''.padEnd(nw + vw + bw + 5) + chalk.dim('user project'))
|
|
294
297
|
for (const r of skillRows) {
|
|
295
298
|
const u = icon(r.userStatus), p = icon(r.projectStatus)
|
|
296
|
-
console.log(' ' + r.name.padEnd(nw) + ' ' + chalk.dim(r.version.padEnd(vw)) + ' ' + u + ' ' + p)
|
|
299
|
+
console.log(' ' + r.name.padEnd(nw) + ' ' + chalk.dim(r.version.padEnd(vw)) + ' ' + chalk.dim(r.bundle.padEnd(bw)) + ' ' + u + ' ' + p)
|
|
297
300
|
}
|
|
298
301
|
|
|
299
302
|
console.log('')
|
|
@@ -594,6 +597,69 @@ const scopeArg = scopeIdx !== -1 ? installArgs[scopeIdx + 1] : undefined
|
|
|
594
597
|
|
|
595
598
|
const TOOL_BUNDLE_VALUES = new Set(TOOL_BUNDLE_CHOICES.map(c => c.value))
|
|
596
599
|
|
|
600
|
+
// ── Two-step target→scope selector ───────────────────────────────────────────
|
|
601
|
+
const ALL_SKILL_TARGETS = ['claude', 'cursor', 'codex', 'openclaw', 'hermes']
|
|
602
|
+
|
|
603
|
+
function selectTargetThenScope() {
|
|
604
|
+
// Step 1: platform (target)
|
|
605
|
+
const targetInput = [
|
|
606
|
+
'claude ~/.claude/skills/',
|
|
607
|
+
'cursor ~/.cursor/skills/',
|
|
608
|
+
'codex ~/.codex/skills/',
|
|
609
|
+
'openclaw ~/.openclaw/skills/',
|
|
610
|
+
'hermes ~/.hermes/skills/',
|
|
611
|
+
'all all 5 targets',
|
|
612
|
+
].join('\n')
|
|
613
|
+
|
|
614
|
+
const targetResult = spawnSync('fzf', [
|
|
615
|
+
'--multi',
|
|
616
|
+
'--prompt= › ',
|
|
617
|
+
'--header= 安装到 · tab 多选 · enter 确认 · esc 取消',
|
|
618
|
+
'--layout=reverse',
|
|
619
|
+
'--border=rounded',
|
|
620
|
+
'--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
|
|
621
|
+
], {
|
|
622
|
+
input: targetInput,
|
|
623
|
+
encoding: 'utf8',
|
|
624
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
if (!targetResult.stdout.trim()) return null
|
|
628
|
+
|
|
629
|
+
const rawTargets = targetResult.stdout.trim().split('\n')
|
|
630
|
+
.map(l => l.trim().split(/\s+/)[0])
|
|
631
|
+
const expandedTargets = rawTargets.includes('all') ? ALL_SKILL_TARGETS : rawTargets
|
|
632
|
+
|
|
633
|
+
// Step 2: scope (user/project) — skip if all selected targets are user-only
|
|
634
|
+
const allUserOnly = expandedTargets.every(t => USER_ONLY_TARGETS.has(t))
|
|
635
|
+
let scope = 'user'
|
|
636
|
+
if (!allUserOnly) {
|
|
637
|
+
const scopeResult = spawnSync('fzf', [
|
|
638
|
+
'--prompt= › ',
|
|
639
|
+
'--header= 安装范围 · enter 确认 · esc 取消',
|
|
640
|
+
'--layout=reverse',
|
|
641
|
+
'--border=rounded',
|
|
642
|
+
'--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
|
|
643
|
+
], {
|
|
644
|
+
input: 'user — 所有项目共享 (~/.{target}/skills/)\nproject — 仅当前项目 (.{target}/skills/)',
|
|
645
|
+
encoding: 'utf8',
|
|
646
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
647
|
+
})
|
|
648
|
+
if (!scopeResult.stdout.trim()) return null
|
|
649
|
+
scope = scopeResult.stdout.trim().startsWith('project') ? 'project' : 'user'
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Build final list — openclaw/hermes always resolve to user scope
|
|
653
|
+
const seen = new Set()
|
|
654
|
+
const result = []
|
|
655
|
+
for (const t of expandedTargets) {
|
|
656
|
+
const effectiveScope = USER_ONLY_TARGETS.has(t) ? 'user' : scope
|
|
657
|
+
const key = `${effectiveScope}/${t}`
|
|
658
|
+
if (!seen.has(key)) { seen.add(key); result.push({ scope: effectiveScope, target: t }) }
|
|
659
|
+
}
|
|
660
|
+
return result // [{ scope, target }]
|
|
661
|
+
}
|
|
662
|
+
|
|
597
663
|
function requireFzf() {
|
|
598
664
|
const probe = spawnSync('fzf', ['--version'], { encoding: 'utf8' })
|
|
599
665
|
if (probe.error || probe.status !== 0) {
|
|
@@ -804,77 +870,66 @@ try {
|
|
|
804
870
|
if (!skillItems.length && !toolItems.length && !hookItems.length) continue
|
|
805
871
|
|
|
806
872
|
// ── Action selection (install / uninstall) ─────────────────────────────
|
|
873
|
+
// Skip the prompt when nothing is installed yet — default to install.
|
|
807
874
|
let action = 'install'
|
|
808
875
|
if (process.env.HSKILL_TEST_ACTION) {
|
|
809
876
|
action = process.env.HSKILL_TEST_ACTION
|
|
810
877
|
} else {
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
})
|
|
822
|
-
if (!actionResult.stdout.trim()) {
|
|
823
|
-
console.log(chalk.dim(' · Cancelled'))
|
|
824
|
-
break
|
|
825
|
-
}
|
|
826
|
-
action = actionResult.stdout.trim().startsWith('uninstall') ? 'uninstall' : 'install'
|
|
827
|
-
}
|
|
878
|
+
const anyInstalled =
|
|
879
|
+
skillItems.some(s => {
|
|
880
|
+
const inst = checkInstalled(s.skillName, s.version ?? '—')
|
|
881
|
+
return scopeSummary(inst.user) !== 'none' || scopeSummary(inst.project) !== 'none'
|
|
882
|
+
}) ||
|
|
883
|
+
toolItems.some(t => checkToolInstalled(t.toolName, t.srcPath).status !== 'none') ||
|
|
884
|
+
hookItems.some(h => {
|
|
885
|
+
const inst = checkHookInstalled(h.name)
|
|
886
|
+
return inst.user.status !== 'none' || inst.project.status !== 'none'
|
|
887
|
+
})
|
|
828
888
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
if (skillItems.length > 0) {
|
|
832
|
-
// Scope selection
|
|
833
|
-
const scopeResult = spawnSync('fzf', [
|
|
889
|
+
if (anyInstalled) {
|
|
890
|
+
const actionResult = spawnSync('fzf', [
|
|
834
891
|
'--prompt= › ',
|
|
835
|
-
'--header=
|
|
892
|
+
'--header= Action · enter 确认 · esc 取消',
|
|
836
893
|
'--layout=reverse',
|
|
837
894
|
'--border=rounded',
|
|
838
895
|
'--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
|
|
839
896
|
], {
|
|
840
|
-
input: `
|
|
897
|
+
input: `install 安装 / 重新安装\nuninstall 卸载并清理文件`,
|
|
841
898
|
encoding: 'utf8',
|
|
842
899
|
stdio: ['pipe', 'pipe', 'inherit'],
|
|
843
900
|
})
|
|
844
|
-
if (!
|
|
901
|
+
if (!actionResult.stdout.trim()) {
|
|
845
902
|
console.log(chalk.dim(' · Cancelled'))
|
|
846
903
|
break
|
|
847
904
|
}
|
|
848
|
-
|
|
905
|
+
action = actionResult.stdout.trim().startsWith('uninstall') ? 'uninstall' : 'install'
|
|
906
|
+
}
|
|
907
|
+
}
|
|
849
908
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
'--header= Install to · tab 多选 · enter 确认 · esc 取消',
|
|
857
|
-
'--layout=reverse',
|
|
858
|
-
'--border=rounded',
|
|
859
|
-
'--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
|
|
860
|
-
], {
|
|
861
|
-
input: targetInput,
|
|
862
|
-
encoding: 'utf8',
|
|
863
|
-
stdio: ['pipe', 'pipe', 'inherit'],
|
|
864
|
-
})
|
|
865
|
-
if (!targetResult.stdout.trim()) {
|
|
909
|
+
if (action === 'install') {
|
|
910
|
+
let skillSummary = null
|
|
911
|
+
if (skillItems.length > 0) {
|
|
912
|
+
// Combined scope+target selection (one step instead of two)
|
|
913
|
+
const selectedST = selectTargetThenScope()
|
|
914
|
+
if (!selectedST) {
|
|
866
915
|
console.log(chalk.dim(' · Cancelled'))
|
|
867
916
|
break
|
|
868
917
|
}
|
|
869
|
-
const selectedTargets = targetResult.stdout.trim().split('\n')
|
|
870
|
-
.map(l => l.trim().split(/\s+/)[0])
|
|
871
918
|
|
|
872
|
-
|
|
919
|
+
// Group by scope so we make one installSkills call per scope
|
|
920
|
+
const byScope = {}
|
|
921
|
+
for (const { scope, target } of selectedST) {
|
|
922
|
+
if (!byScope[scope]) byScope[scope] = []
|
|
923
|
+
byScope[scope].push(target)
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
console.log('')
|
|
927
|
+
for (const [scope, selectedTargets] of Object.entries(byScope)) {
|
|
873
928
|
const targets = resolveTargets(selectedTargets, scope)
|
|
874
|
-
|
|
875
|
-
skillSummary =
|
|
876
|
-
console.log('')
|
|
929
|
+
const result = await installSkills(skillItems, targets, forceFlag)
|
|
930
|
+
skillSummary = skillSummary ? { ...skillSummary, ...result } : result
|
|
877
931
|
}
|
|
932
|
+
console.log('')
|
|
878
933
|
}
|
|
879
934
|
|
|
880
935
|
let toolSummary = null
|
|
@@ -960,30 +1015,33 @@ try {
|
|
|
960
1015
|
}
|
|
961
1016
|
|
|
962
1017
|
for (const item of uSkillItems) {
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1018
|
+
// Step 1: target (platform)
|
|
1019
|
+
const targetChoices2 = buildTargetChoices('user')
|
|
1020
|
+
const targetRes = spawnSync('fzf', [
|
|
1021
|
+
'--multi', '--prompt= › ',
|
|
1022
|
+
'--header= 从哪里卸载 · tab 多选 · enter 确认 · esc 取消',
|
|
966
1023
|
'--layout=reverse', '--border=rounded',
|
|
967
1024
|
'--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
|
|
968
1025
|
], {
|
|
969
|
-
input:
|
|
1026
|
+
input: targetChoices2.map(c => c.name).join('\n') + '\nall — all targets',
|
|
970
1027
|
encoding: 'utf8', stdio: ['pipe', 'pipe', 'inherit'],
|
|
971
1028
|
})
|
|
972
|
-
if (!
|
|
973
|
-
const
|
|
1029
|
+
if (!targetRes.stdout.trim()) { console.log(chalk.dim(' · Cancelled')); break }
|
|
1030
|
+
const selTargets2 = targetRes.stdout.trim().split('\n').map(l => l.trim().split(/\s+/)[0])
|
|
974
1031
|
|
|
975
|
-
|
|
976
|
-
const
|
|
977
|
-
'--
|
|
978
|
-
'--header=
|
|
1032
|
+
// Step 2: scope (user/project)
|
|
1033
|
+
const scopeRes = spawnSync('fzf', [
|
|
1034
|
+
'--prompt= › ',
|
|
1035
|
+
'--header= 卸载范围 · enter 确认 · esc 取消',
|
|
979
1036
|
'--layout=reverse', '--border=rounded',
|
|
980
1037
|
'--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
|
|
981
1038
|
], {
|
|
982
|
-
input:
|
|
1039
|
+
input: 'user — 所有项目 (~/.{target}/skills/)\nproject — 仅当前项目 (.{target}/skills/)',
|
|
983
1040
|
encoding: 'utf8', stdio: ['pipe', 'pipe', 'inherit'],
|
|
984
1041
|
})
|
|
985
|
-
if (!
|
|
986
|
-
const
|
|
1042
|
+
if (!scopeRes.stdout.trim()) { console.log(chalk.dim(' · Cancelled')); break }
|
|
1043
|
+
const scope2 = scopeRes.stdout.trim().startsWith('project') ? 'project' : 'user'
|
|
1044
|
+
|
|
987
1045
|
const targets2 = resolveTargets(selTargets2, scope2)
|
|
988
1046
|
for (const { dir } of targets2) {
|
|
989
1047
|
await uninstallSkill(item.skillName, dir)
|
|
@@ -1043,66 +1101,36 @@ try {
|
|
|
1043
1101
|
// ── Install skills ──────────────────────────────────────────────────────────
|
|
1044
1102
|
let skillSummary = null
|
|
1045
1103
|
if (skillItems.length > 0) {
|
|
1046
|
-
//
|
|
1047
|
-
|
|
1048
|
-
if (!scopeArg && !targetArg) {
|
|
1049
|
-
if (!process.stdout.isTTY) {
|
|
1050
|
-
console.error(chalk.red(' ✗ Interactive scope selection requires a TTY. Use --scope user|project.'))
|
|
1051
|
-
process.exit(1)
|
|
1052
|
-
}
|
|
1053
|
-
const scopeResult = spawnSync('fzf', [
|
|
1054
|
-
'--prompt= › ',
|
|
1055
|
-
'--header= Scope · enter 确认 · esc 取消',
|
|
1056
|
-
'--layout=reverse',
|
|
1057
|
-
'--border=rounded',
|
|
1058
|
-
'--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
|
|
1059
|
-
], {
|
|
1060
|
-
input: `user — ~/.claude/skills/ (所有项目共享)\nproject — .claude/skills/ (仅当前项目)`,
|
|
1061
|
-
encoding: 'utf8',
|
|
1062
|
-
stdio: ['pipe', 'pipe', 'inherit'],
|
|
1063
|
-
})
|
|
1064
|
-
if (!scopeResult.stdout.trim()) {
|
|
1065
|
-
console.log(chalk.dim(' · Cancelled'))
|
|
1066
|
-
process.exit(0)
|
|
1067
|
-
}
|
|
1068
|
-
scope = scopeResult.stdout.trim().startsWith('project') ? 'project' : 'user'
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
// Resolve target
|
|
1072
|
-
let selectedTargets
|
|
1104
|
+
// When --target is given, use it directly (with --scope or default 'user').
|
|
1105
|
+
// When only --skill is given and no --target, use the combined selector.
|
|
1073
1106
|
if (targetArg) {
|
|
1074
|
-
|
|
1107
|
+
const scope = scopeArg ?? 'user'
|
|
1108
|
+
const selectedTargets = targetArg === 'all' ? ['claude', 'cursor', 'codex', 'openclaw', 'hermes'] : [targetArg]
|
|
1109
|
+
const targets = resolveTargets(selectedTargets, scope)
|
|
1110
|
+
console.log('')
|
|
1111
|
+
skillSummary = await installSkills(skillItems, targets, forceFlag)
|
|
1112
|
+
console.log('')
|
|
1075
1113
|
} else {
|
|
1076
1114
|
if (!process.stdout.isTTY) {
|
|
1077
1115
|
console.error(chalk.red(' ✗ Interactive target selection requires a TTY. Use --target claude|cursor|codex|openclaw|hermes|all.'))
|
|
1078
1116
|
process.exit(1)
|
|
1079
1117
|
}
|
|
1080
|
-
const
|
|
1081
|
-
|
|
1082
|
-
const targetResult = spawnSync('fzf', [
|
|
1083
|
-
'--multi',
|
|
1084
|
-
'--prompt= › ',
|
|
1085
|
-
'--header= Install to · tab 多选 · enter 确认 · esc 取消',
|
|
1086
|
-
'--layout=reverse',
|
|
1087
|
-
'--border=rounded',
|
|
1088
|
-
'--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
|
|
1089
|
-
], {
|
|
1090
|
-
input: targetInput,
|
|
1091
|
-
encoding: 'utf8',
|
|
1092
|
-
stdio: ['pipe', 'pipe', 'inherit'],
|
|
1093
|
-
})
|
|
1094
|
-
if (!targetResult.stdout.trim()) {
|
|
1118
|
+
const selectedST = selectTargetThenScope()
|
|
1119
|
+
if (!selectedST) {
|
|
1095
1120
|
console.log(chalk.dim(' · Cancelled'))
|
|
1096
1121
|
process.exit(0)
|
|
1097
1122
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
const targets = resolveTargets(selectedTargets, scope)
|
|
1123
|
+
const byScope = {}
|
|
1124
|
+
for (const { scope, target } of selectedST) {
|
|
1125
|
+
if (!byScope[scope]) byScope[scope] = []
|
|
1126
|
+
byScope[scope].push(target)
|
|
1127
|
+
}
|
|
1104
1128
|
console.log('')
|
|
1105
|
-
|
|
1129
|
+
for (const [scope, selectedTargets] of Object.entries(byScope)) {
|
|
1130
|
+
const targets = resolveTargets(selectedTargets, scope)
|
|
1131
|
+
const result = await installSkills(skillItems, targets, forceFlag)
|
|
1132
|
+
skillSummary = skillSummary ? { ...skillSummary, ...result } : result
|
|
1133
|
+
}
|
|
1106
1134
|
console.log('')
|
|
1107
1135
|
}
|
|
1108
1136
|
}
|
package/bin/preview.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import fs from 'fs'
|
|
|
3
3
|
import os from 'os'
|
|
4
4
|
import path from 'path'
|
|
5
5
|
import { fileURLToPath } from 'url'
|
|
6
|
+
import { SKILL_TARGETS, USER_ONLY_TARGETS } from '../lib/targets.js'
|
|
6
7
|
|
|
7
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
9
|
|
|
@@ -72,10 +73,11 @@ function statusLine(version, status) {
|
|
|
72
73
|
return ver + ' ' + D + '— not installed' + R
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
const
|
|
76
|
+
const userTargets = SKILL_TARGETS
|
|
77
|
+
const projectTargets = SKILL_TARGETS.filter(t => !USER_ONLY_TARGETS.has(t))
|
|
76
78
|
const home = os.homedir()
|
|
77
79
|
|
|
78
|
-
function checkScope(dirFn) {
|
|
80
|
+
function checkScope(targets, dirFn) {
|
|
79
81
|
return targets.map(t => {
|
|
80
82
|
const ver = readVersion(path.join(dirFn(t), skillName))
|
|
81
83
|
const status = ver === '—' ? 'none'
|
|
@@ -86,10 +88,10 @@ function checkScope(dirFn) {
|
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
const cwd = process.cwd()
|
|
89
|
-
const userDetails = checkScope(t => path.join(home, `.${t}`, 'skills'))
|
|
91
|
+
const userDetails = checkScope(userTargets, t => path.join(home, `.${t}`, 'skills'))
|
|
90
92
|
const projectDetails = cwd === home
|
|
91
|
-
?
|
|
92
|
-
: checkScope(t => path.join(cwd, `.${t}`, 'skills'))
|
|
93
|
+
? projectTargets.map(t => ({ tool: t, version: '—', status: 'none' }))
|
|
94
|
+
: checkScope(projectTargets, t => path.join(cwd, `.${t}`, 'skills'))
|
|
93
95
|
|
|
94
96
|
console.log(B + skillName + R)
|
|
95
97
|
console.log(D + 'available: ' + R + availableVersion)
|
package/lib/bundles.js
CHANGED
|
@@ -3,7 +3,7 @@ import os from 'os'
|
|
|
3
3
|
import { createRequire } from 'module'
|
|
4
4
|
import path from 'path'
|
|
5
5
|
import { fileURLToPath } from 'url'
|
|
6
|
-
import { USER_ONLY_TARGETS } from './targets.js'
|
|
6
|
+
import { SKILL_TARGETS, USER_ONLY_TARGETS } from './targets.js'
|
|
7
7
|
|
|
8
8
|
const require = createRequire(import.meta.url)
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
@@ -101,13 +101,14 @@ export function buildAllChoices() {
|
|
|
101
101
|
return choices
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
// 所有 skill 展开为 { kind:'skill', skillName, srcPath } 列表(对应 all 选项)
|
|
104
|
+
// 所有 skill 展开为 { kind:'skill', skillName, bundle, srcPath } 列表(对应 all 选项)
|
|
105
105
|
export function getAllSkillItems() {
|
|
106
106
|
return skillDefs.map(skill => {
|
|
107
107
|
const srcPath = path.join(skillsRoot, skill.path)
|
|
108
108
|
return {
|
|
109
109
|
kind: 'skill',
|
|
110
110
|
skillName: skill.path.split('/').pop(),
|
|
111
|
+
bundle: skill.bundle,
|
|
111
112
|
srcPath,
|
|
112
113
|
version: readVersion(srcPath),
|
|
113
114
|
}
|
|
@@ -132,7 +133,7 @@ export function getAllToolItems() {
|
|
|
132
133
|
// 每个工具: { version: string, status: 'up-to-date'|'update'|'none' }
|
|
133
134
|
export function checkInstalled(skillName, availableVersion) {
|
|
134
135
|
const home = os.homedir()
|
|
135
|
-
const targets =
|
|
136
|
+
const targets = SKILL_TARGETS
|
|
136
137
|
|
|
137
138
|
function checkScope(dirFn) {
|
|
138
139
|
const result = {}
|
package/lib/targets.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os from 'os'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
|
|
4
|
-
const SKILL_TARGETS = ['claude', 'cursor', 'codex', 'openclaw', 'hermes']
|
|
4
|
+
export const SKILL_TARGETS = ['claude', 'cursor', 'codex', 'openclaw', 'hermes']
|
|
5
5
|
|
|
6
6
|
export const USER_ONLY_TARGETS = new Set(['openclaw', 'hermes'])
|
|
7
7
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harveyz-skill",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Skill manager for Claude Code, Cursor, and Codex",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,10 +28,18 @@
|
|
|
28
28
|
"skills/task/pm-task-dispatch/",
|
|
29
29
|
"skills/task/task-close/",
|
|
30
30
|
"skills/web-fetch/article-fetcher/",
|
|
31
|
+
"skills/web-fetch/url-extract/",
|
|
32
|
+
"skills/data-extraction/vision-extract/",
|
|
31
33
|
"skills/writing/mermaid-diagram/",
|
|
32
34
|
"skills/design/sync-design-html/",
|
|
33
35
|
"skills/analysis/git-cleanup/",
|
|
34
36
|
"skills/meta/contribute-skill/",
|
|
37
|
+
"skills/harness/migrate-specs/",
|
|
38
|
+
"skills/workflow/add-todo/",
|
|
39
|
+
"skills/harness/dir-manage/",
|
|
40
|
+
"skills/writing/capture-insight/",
|
|
41
|
+
"skills/meta/skill-publish/",
|
|
42
|
+
"skills/meta/npm-release/",
|
|
35
43
|
"tools/p-launch/",
|
|
36
44
|
"hooks/check-similar-branch/"
|
|
37
45
|
],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: git-cleanup
|
|
3
|
-
description: "
|
|
3
|
+
description: "Tidy and clean up local git branches using rule matching and LLM semantic analysis, then batch-delete stale branches after grouped confirmation. Triggers: clean up branches, branch cleanup, tidy branches, delete old branches, organize branches."
|
|
4
4
|
user_invocable: true
|
|
5
5
|
version: "1.0.0"
|
|
6
6
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skill-analyzer
|
|
3
|
-
description: "
|
|
3
|
+
description: "Performs systematic analysis of the skill repository. Triggers: analyze this skill repo, do a systematic study of the skill repo, output skill repo analysis report, understand the design intent of this skill system."
|
|
4
4
|
user_invocable: true
|
|
5
5
|
version: "1.0.0"
|
|
6
6
|
---
|