harveyz-skill 0.11.0 → 0.13.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 +18 -0
- package/bin/cli.js +23 -10
- package/bin/preview.mjs +1 -1
- package/lib/bundles.js +3 -3
- package/lib/installer.js +3 -3
- package/lib/targets.js +12 -2
- package/lib/vars.js +40 -1
- package/package.json +21 -21
- package/skills/creative/add-todo/SKILL.md +123 -0
- package/skills/{writing/capture-insight → creative/insight}/SKILL.md +1 -1
- package/skills/design/brand-scout/SKILL.md +387 -0
- package/skills/design/brand-scout/references/TEMPLATE.md +418 -0
- package/skills/design/style-build/SKILL.md +452 -0
- package/skills/design/style-build/references/FORMAT-MAPPING.md +231 -0
- package/skills/meta/opencode-runner/SKILL.md +117 -0
- package/skills/{web-fetch → research}/article-fetcher/references/__pycache__/article_utils.cpython-314.pyc +0 -0
- package/skills/{web-fetch → research}/url-extract/SKILL.md +1 -1
- package/skills/research/url-extract/references/__pycache__/article_utils.cpython-314.pyc +0 -0
- package/skills/{web-fetch → research}/url-extract/references/article_utils.py +79 -13
- package/skills/research/url-extract/scripts/detect_chrome_profile.py +105 -0
- package/skills/{web-fetch → research}/url-extract/scripts/playwright_xcom.py +30 -6
- package/skills/{web-fetch → research}/url-extract/vars.json +3 -2
- package/skills/research/youtube-learner/SKILL.md +187 -0
- package/skills/writing/{mermaid-diagram → diagram}/SKILL.md +68 -24
- package/skills/writing/diagram/tests/bain-style-test.md +177 -0
- package/skills/writing/diagram/themes/bain.json +111 -0
- package/skills/writing/diagram/themes/bcg.json +112 -0
- package/skills/writing/diagram/themes/rb.json +46 -0
- package/skills/writing/diagram/themes/rolandberger.json +189 -0
- package/skills/writing/doc-forge/SKILL.md +208 -0
- package/skills/writing/doc-forge/assets/bain-style.json +86 -0
- package/skills/writing/doc-forge/assets/bain.css +134 -0
- package/skills/writing/doc-forge/assets/bcg-style.json +86 -0
- package/skills/writing/doc-forge/assets/bcg.css +219 -0
- package/skills/writing/doc-forge/assets/rolandberger-style.json +84 -0
- package/skills/writing/doc-forge/assets/rolandberger.css +149 -0
- package/skills/writing/doc-forge/assets/template.html +19 -0
- package/skills/writing/doc-forge/assets/thesis-style.json +60 -0
- package/skills/writing/doc-forge/assets/thesis.css +222 -0
- package/skills/writing/doc-forge/preview/style-preview.html +627 -0
- package/skills/writing/doc-forge/scripts/__pycache__/md_to_pdf.cpython-314.pyc +0 -0
- package/skills/writing/doc-forge/scripts/generate_style_preview.py +345 -0
- package/skills/writing/doc-forge/scripts/md_to_docx.py +606 -0
- package/skills/writing/doc-forge/scripts/md_to_pdf.py +145 -0
- package/skills/writing/doc-forge/tests/__pycache__/test_md_to_pdf.cpython-314-pytest-9.0.2.pyc +0 -0
- package/skills/writing/doc-forge/tests/fixtures/full-test-bain.docx +0 -0
- package/skills/writing/doc-forge/tests/fixtures/full-test-bain.pdf +0 -0
- package/skills/writing/doc-forge/tests/fixtures/full-test-bcg.docx +0 -0
- package/skills/writing/doc-forge/tests/fixtures/full-test-bcg.pdf +0 -0
- package/skills/writing/doc-forge/tests/fixtures/full-test-custom.docx +0 -0
- package/skills/writing/doc-forge/tests/fixtures/full-test-custom.pdf +0 -0
- package/skills/writing/doc-forge/tests/fixtures/full-test-rb.docx +0 -0
- package/skills/writing/doc-forge/tests/fixtures/full-test-rb.pdf +0 -0
- package/skills/writing/doc-forge/tests/fixtures/full-test-thesis.docx +0 -0
- package/skills/writing/doc-forge/tests/fixtures/full-test-thesis.pdf +0 -0
- package/skills/writing/doc-forge/tests/fixtures/full-test.md +307 -0
- package/skills/writing/doc-forge/tests/fixtures/sample.png +0 -0
- package/skills/writing/doc-forge/tests/test_md_to_pdf.py +76 -0
- package/skills-index.json +30 -35
- package/tools/p-launch/__pycache__/p_launch.cpython-314.pyc +0 -0
- package/tools/p-launch/p-launch.sh +3 -3
- package/tools/p-launch/tests/__pycache__/test_p_launch.cpython-314-pytest-9.0.2.pyc +0 -0
- package/tools/p-launch/tool.json +1 -1
- package/skills/superpowers-fork/brainstorming/SKILL.md +0 -148
- package/skills/superpowers-fork/executing-plans/SKILL.md +0 -104
- package/skills/superpowers-fork/systematic-debugging/SKILL.md +0 -217
- package/skills/superpowers-fork/using-git-worktrees/SKILL.md +0 -192
- package/skills/superpowers-fork/writing-plans/SKILL.md +0 -187
- package/skills/web-fetch/url-extract/references/__pycache__/article_utils.cpython-314.pyc +0 -0
- package/skills/workflow/add-todo/SKILL.md +0 -205
- /package/skills/{harness → coding}/full-stack-debug-env/SKILL.md +0 -0
- /package/skills/{harness → coding}/full-stack-debug-env/evals/evals.json +0 -0
- /package/skills/{harness → coding}/full-stack-debug-env/references/README.md +0 -0
- /package/skills/{harness → coding}/full-stack-debug-env/references/tech-stacks/browser-spa.md +0 -0
- /package/skills/{harness → coding}/full-stack-debug-env/references/tech-stacks/docker-compose.md +0 -0
- /package/skills/{harness → coding}/full-stack-debug-env/references/tech-stacks/electron.md +0 -0
- /package/skills/{harness → coding}/full-stack-debug-env/references/tech-stacks/node-process.md +0 -0
- /package/skills/{harness → coding}/git-workflow-init/SKILL.md +0 -0
- /package/skills/{harness → coding}/git-workflow-init/references/acceptance-test.md +0 -0
- /package/skills/{harness → coding}/git-workflow-init/references/conflict-analysis.md +0 -0
- /package/skills/{harness → coding}/git-workflow-init/references/git-workflow-template.md +0 -0
- /package/skills/{harness → coding}/git-workflow-init/references/hook-templates.md +0 -0
- /package/skills/{harness → coding}/git-workflow-init/references/lock-file-format.md +0 -0
- /package/skills/{harness → coding}/git-workflow-init/references/render_docs.py +0 -0
- /package/skills/{harness → coding}/git-workflow-init/references/workflow-config.yml +0 -0
- /package/skills/{task → coding}/pm-task-dispatch/SKILL.md +0 -0
- /package/skills/{task → coding}/pm-task-dispatch/references/agent-mapping.md +0 -0
- /package/skills/{task → coding}/pm-task-dispatch/references/task-template.md +0 -0
- /package/skills/{task → coding}/task-close/SKILL.md +0 -0
- /package/skills/{task → coding}/task-close/references/output-templates.md +0 -0
- /package/skills/{analysis → meta}/git-cleanup/SKILL.md +0 -0
- /package/skills/{analysis → meta}/skill-analyzer/CHANGELOG.md +0 -0
- /package/skills/{analysis → meta}/skill-analyzer/SKILL.md +0 -0
- /package/skills/{analysis → meta}/skill-analyzer/references/evaluation-template.md +0 -0
- /package/skills/{analysis → meta}/skill-analyzer/references/output-template.md +0 -0
- /package/skills/{analysis → meta}/skill-analyzer/references/prohibitions.md +0 -0
- /package/skills/{web-fetch → research}/article-fetcher/SKILL.md +0 -0
- /package/skills/{web-fetch → research}/article-fetcher/references/article_utils.py +0 -0
- /package/skills/{web-fetch → research}/article-fetcher/references/file-format.md +0 -0
- /package/skills/{web-fetch → research}/article-fetcher/scripts/import_reading.py +0 -0
- /package/skills/{web-fetch → research}/article-fetcher/scripts/init_db.py +0 -0
- /package/skills/{web-fetch → research}/article-fetcher/scripts/playwright_web.py +0 -0
- /package/skills/{web-fetch → research}/article-fetcher/scripts/playwright_xcom.py +0 -0
- /package/skills/{web-fetch → research}/article-fetcher/scripts/url-index.db +0 -0
- /package/skills/{web-fetch → research}/article-fetcher/scripts/url-index.db.bak +0 -0
- /package/skills/{web-fetch → research}/article-fetcher/scripts/validate_article.py +0 -0
- /package/skills/{web-fetch → research}/article-fetcher/tests/test_security.py +0 -0
- /package/skills/{web-fetch → research}/article-fetcher/vars.json +0 -0
- /package/skills/{web-fetch → research}/url-extract/platforms/SKILL.claude.md +0 -0
- /package/skills/{web-fetch → research}/url-extract/platforms/SKILL.codex.md +0 -0
- /package/skills/{web-fetch → research}/url-extract/platforms/SKILL.hermes.md +0 -0
- /package/skills/{web-fetch → research}/url-extract/references/core-flow.md +0 -0
- /package/skills/{web-fetch → research}/url-extract/references/file-format.md +0 -0
- /package/skills/{web-fetch → research}/url-extract/scripts/dedup_check.py +0 -0
- /package/skills/{web-fetch → research}/url-extract/scripts/playwright_web.py +0 -0
- /package/skills/{web-fetch → research}/url-extract/scripts/validate_article.py +0 -0
- /package/skills/{data-extraction → research}/vision-extract/SKILL.md +0 -0
- /package/skills/writing/{mermaid-diagram → diagram}/references/color-templates.md +0 -0
- /package/skills/writing/{mermaid-diagram → diagram}/references/flowchart.md +0 -0
- /package/skills/writing/{mermaid-diagram → diagram}/references/gantt-timeline.md +0 -0
- /package/skills/writing/{mermaid-diagram → diagram}/references/other-charts.md +0 -0
- /package/skills/writing/{mermaid-diagram → diagram}/references/sequence.md +0 -0
- /package/skills/writing/{mermaid-diagram → diagram}/references/statediagram.md +0 -0
- /package/skills/writing/{mermaid-diagram → diagram}/scripts/check_mermaid.py +0 -0
- /package/skills/{harness → writing}/diataxis-docs/SKILL.md +0 -0
- /package/skills/{harness → writing}/diataxis-docs/references/custom-categories.md +0 -0
- /package/skills/{harness → writing}/diataxis-docs/references/index-template.md +0 -0
- /package/skills/{harness → writing}/dir-manage/SKILL.md +0 -0
- /package/skills/{harness → writing}/dir-manage/references/built-in/diataxis.md +0 -0
- /package/skills/{harness → writing}/dir-manage/references/built-in/role-based.md +0 -0
- /package/skills/{harness → writing}/dir-manage/references/methodology-spec.md +0 -0
- /package/skills/{harness → writing}/migrate-specs/SKILL.md +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.13.0] - 2026-06-12
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `opencode-runner`:description 移除中文字符,符合 F3 英文规范
|
|
14
|
+
- `doc-forge`:测试 CSS 路径改为动态选取首个可用文件,不再依赖不存在的 `default.css`
|
|
15
|
+
|
|
16
|
+
## [0.12.1] - 2026-06-10
|
|
17
|
+
|
|
18
|
+
## [0.12.0] - 2026-06-09
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- `url-extract`:新增 `detect_chrome_profile.py` 脚本,扫描本机 Chrome profile 并检测 X.com 登录态
|
|
22
|
+
- `url-extract`:安装时 `CHROME_PROFILE` 变量改为 select 选择列表(自动列出所有 profile + 对应 Google 账号),保留手动输入兜底
|
|
23
|
+
- `hskill`:安装/卸载完成后新增 "按 Enter 返回列表" 提示,避免 summary 被 fzf 立即覆盖
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- `url-extract` `vars.json`:`CHROME_PROFILE` 默认值从 `Profile 2` 更新为 `Default`
|
|
27
|
+
|
|
10
28
|
## [0.11.0] - 2026-06-09
|
|
11
29
|
|
|
12
30
|
### Added
|
package/bin/cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { select } from '@inquirer/prompts'
|
|
2
|
+
import { select, input } from '@inquirer/prompts'
|
|
3
3
|
import chalk from 'chalk'
|
|
4
4
|
import { execSync, spawnSync } from 'child_process'
|
|
5
|
+
import { existsSync } from 'fs'
|
|
5
6
|
import { createRequire } from 'module'
|
|
6
7
|
import os from 'os'
|
|
7
8
|
import path from 'path'
|
|
@@ -26,7 +27,7 @@ const jsonFlag = args.includes('--json')
|
|
|
26
27
|
// ── Help ─────────────────────────────────────────────────────────────────────
|
|
27
28
|
function printHelp() {
|
|
28
29
|
console.log(`
|
|
29
|
-
${chalk.bold('hskill')} — skill manager for Claude Code, Cursor, Codex, OpenClaw, and
|
|
30
|
+
${chalk.bold('hskill')} — skill manager for Claude Code, Cursor, Codex, OpenClaw, Hermes, and OpenCode v${version}
|
|
30
31
|
|
|
31
32
|
${chalk.cyan('Usage:')}
|
|
32
33
|
hskill interactive install (requires TTY + fzf)
|
|
@@ -34,7 +35,7 @@ function printHelp() {
|
|
|
34
35
|
hskill install --bundle <b> install a skill bundle
|
|
35
36
|
hskill install --skill <s> install specific skill(s)
|
|
36
37
|
hskill install --tool <t> install shell tool(s)
|
|
37
|
-
hskill install --target <t> set target (claude/cursor/codex/openclaw/hermes/all)
|
|
38
|
+
hskill install --target <t> set target (claude/cursor/codex/openclaw/hermes/opencode/all)
|
|
38
39
|
hskill install --scope <s> set scope: user (default) or project
|
|
39
40
|
hskill install --force overwrite existing installs
|
|
40
41
|
hskill list [--json] list available skills and bundles
|
|
@@ -86,7 +87,7 @@ if (args[0] === '--help' || args[0] === '-h') {
|
|
|
86
87
|
{ name: '--bundle', arg: '<name>', description: 'Install a skill bundle (comma-separated)' },
|
|
87
88
|
{ name: '--skill', arg: '<name>', description: 'Install specific skill(s) (comma-separated)' },
|
|
88
89
|
{ name: '--tool', arg: '<name>', description: 'Install shell tool(s) (comma-separated)' },
|
|
89
|
-
{ name: '--target', arg: '<target>', description: 'Install target', enum: ['claude','cursor','codex','openclaw','hermes','all'] },
|
|
90
|
+
{ name: '--target', arg: '<target>', description: 'Install target', enum: ['claude','cursor','codex','openclaw','hermes','opencode','all'] },
|
|
90
91
|
{ name: '--scope', arg: '<scope>', description: 'Install scope', enum: ['user','project'], default: 'user' },
|
|
91
92
|
{ name: '--force', description: 'Overwrite existing installs' },
|
|
92
93
|
],
|
|
@@ -153,6 +154,15 @@ if (subcommand === 'update') {
|
|
|
153
154
|
console.error(chalk.red(' ✗ Update failed. Try: npm update -g harveyz-skill'))
|
|
154
155
|
process.exit(1)
|
|
155
156
|
}
|
|
157
|
+
const legacyDir = path.join(os.homedir(), '.local', 'share', 'hskill')
|
|
158
|
+
if (existsSync(legacyDir)) {
|
|
159
|
+
console.log('')
|
|
160
|
+
console.log(chalk.yellow(' ⚠ Legacy data detected at ~/.local/share/hskill/'))
|
|
161
|
+
console.log(chalk.dim(' Run the migration script to move data to the new location:'))
|
|
162
|
+
console.log('')
|
|
163
|
+
console.log(' ' + chalk.bold.cyan('bash "$(npm root -g)/harveyz-skill/scripts/migrate-data-dir.sh"'))
|
|
164
|
+
console.log('')
|
|
165
|
+
}
|
|
156
166
|
process.exit(0)
|
|
157
167
|
}
|
|
158
168
|
|
|
@@ -221,7 +231,7 @@ if (subcommand === 'status' || subcommand === 'outdated') {
|
|
|
221
231
|
})
|
|
222
232
|
|
|
223
233
|
if (jsonFlag) {
|
|
224
|
-
const targets = ['claude', 'cursor', 'codex', 'openclaw', 'hermes']
|
|
234
|
+
const targets = ['claude', 'cursor', 'codex', 'openclaw', 'hermes', 'opencode']
|
|
225
235
|
const jsonSkills = skillRows.map(r => ({
|
|
226
236
|
name: r.name,
|
|
227
237
|
version: r.version,
|
|
@@ -253,7 +263,7 @@ if (subcommand === 'status' || subcommand === 'outdated') {
|
|
|
253
263
|
process.exit(0)
|
|
254
264
|
}
|
|
255
265
|
|
|
256
|
-
const targets = ['claude', 'cursor', 'codex', 'openclaw', 'hermes']
|
|
266
|
+
const targets = ['claude', 'cursor', 'codex', 'openclaw', 'hermes', 'opencode']
|
|
257
267
|
|
|
258
268
|
if (outdatedSkills.length) {
|
|
259
269
|
console.log('\n ' + chalk.bold('SKILLS WITH UPDATES'))
|
|
@@ -438,7 +448,7 @@ if (subcommand === 'uninstall') {
|
|
|
438
448
|
const scope = scopeArg2
|
|
439
449
|
const selectedTargets = targetArg2
|
|
440
450
|
? [targetArg2]
|
|
441
|
-
: ['claude', 'cursor', 'codex', 'openclaw', 'hermes']
|
|
451
|
+
: ['claude', 'cursor', 'codex', 'openclaw', 'hermes', 'opencode']
|
|
442
452
|
const targets = resolveTargets(selectedTargets, scope)
|
|
443
453
|
|
|
444
454
|
let anyRemoved = false
|
|
@@ -598,7 +608,7 @@ const scopeArg = scopeIdx !== -1 ? installArgs[scopeIdx + 1] : undefined
|
|
|
598
608
|
const TOOL_BUNDLE_VALUES = new Set(TOOL_BUNDLE_CHOICES.map(c => c.value))
|
|
599
609
|
|
|
600
610
|
// ── Two-step target→scope selector ───────────────────────────────────────────
|
|
601
|
-
const ALL_SKILL_TARGETS = ['claude', 'cursor', 'codex', 'openclaw', 'hermes']
|
|
611
|
+
const ALL_SKILL_TARGETS = ['claude', 'cursor', 'codex', 'openclaw', 'hermes', 'opencode']
|
|
602
612
|
|
|
603
613
|
function selectTargetThenScope() {
|
|
604
614
|
// Step 1: platform (target)
|
|
@@ -608,7 +618,8 @@ function selectTargetThenScope() {
|
|
|
608
618
|
'codex ~/.codex/skills/',
|
|
609
619
|
'openclaw ~/.openclaw/skills/',
|
|
610
620
|
'hermes ~/.hermes/skills/',
|
|
611
|
-
'
|
|
621
|
+
'opencode ~/.config/opencode/skills/',
|
|
622
|
+
'all all 6 targets',
|
|
612
623
|
].join('\n')
|
|
613
624
|
|
|
614
625
|
const targetResult = spawnSync('fzf', [
|
|
@@ -1069,6 +1080,8 @@ try {
|
|
|
1069
1080
|
// In test mode with HSKILL_TEST_ACTION, run once and exit
|
|
1070
1081
|
if (process.env.HSKILL_TEST_ACTION) break
|
|
1071
1082
|
|
|
1083
|
+
if (process.stdout.isTTY) await input({ message: chalk.dim('按 Enter 返回列表…'), default: '' })
|
|
1084
|
+
|
|
1072
1085
|
// Loop back to skill selector automatically
|
|
1073
1086
|
}
|
|
1074
1087
|
|
|
@@ -1105,7 +1118,7 @@ try {
|
|
|
1105
1118
|
// When only --skill is given and no --target, use the combined selector.
|
|
1106
1119
|
if (targetArg) {
|
|
1107
1120
|
const scope = scopeArg ?? 'user'
|
|
1108
|
-
const selectedTargets = targetArg === 'all' ? ['claude', 'cursor', 'codex', 'openclaw', 'hermes'] : [targetArg]
|
|
1121
|
+
const selectedTargets = targetArg === 'all' ? ['claude', 'cursor', 'codex', 'openclaw', 'hermes', 'opencode'] : [targetArg]
|
|
1109
1122
|
const targets = resolveTargets(selectedTargets, scope)
|
|
1110
1123
|
console.log('')
|
|
1111
1124
|
skillSummary = await installSkills(skillItems, targets, forceFlag)
|
package/bin/preview.mjs
CHANGED
|
@@ -19,7 +19,7 @@ if (!skillName) process.exit(0)
|
|
|
19
19
|
if (kind !== 'skill') {
|
|
20
20
|
const home = os.homedir()
|
|
21
21
|
const installedBin = path.join(home, '.local', 'bin', skillName)
|
|
22
|
-
const installedMeta = path.join(home, '.
|
|
22
|
+
const installedMeta = path.join(home, '.hskill', 'tools', `${skillName}.json`)
|
|
23
23
|
|
|
24
24
|
function readToolMeta(jsonPath) {
|
|
25
25
|
try {
|
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 { SKILL_TARGETS, USER_ONLY_TARGETS } from './targets.js'
|
|
6
|
+
import { SKILL_TARGETS, USER_ONLY_TARGETS, userSkillDir } from './targets.js'
|
|
7
7
|
|
|
8
8
|
const require = createRequire(import.meta.url)
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
@@ -154,7 +154,7 @@ export function checkInstalled(skillName, availableVersion) {
|
|
|
154
154
|
const noneScope = Object.fromEntries(targets.map(t => [t, { version: '—', status: 'none' }]))
|
|
155
155
|
|
|
156
156
|
return {
|
|
157
|
-
user: checkScope(t =>
|
|
157
|
+
user: checkScope(t => userSkillDir(t)),
|
|
158
158
|
project: cwd === home ? noneScope : checkScope(t =>
|
|
159
159
|
USER_ONLY_TARGETS.has(t) ? null : path.join(cwd, `.${t}`, 'skills')
|
|
160
160
|
),
|
|
@@ -263,7 +263,7 @@ export function checkHookInstalled(hookName) {
|
|
|
263
263
|
|
|
264
264
|
export { formatChoice, readVersion, readHookVersion }
|
|
265
265
|
|
|
266
|
-
const HSKILL_TOOLS_DATA = path.join(os.homedir(), '.
|
|
266
|
+
const HSKILL_TOOLS_DATA = path.join(os.homedir(), '.hskill', 'tools')
|
|
267
267
|
|
|
268
268
|
function readToolMeta(jsonPath) {
|
|
269
269
|
try {
|
package/lib/installer.js
CHANGED
|
@@ -55,7 +55,7 @@ export async function installTools(tools, targetDir, force = false) {
|
|
|
55
55
|
if (destExists && !force) {
|
|
56
56
|
// Version-aware upgrade logic (mirrors skill behavior)
|
|
57
57
|
const home = os.homedir()
|
|
58
|
-
const dataDir = path.join(home, '.
|
|
58
|
+
const dataDir = path.join(home, '.hskill', 'tools')
|
|
59
59
|
const installedMeta = path.join(dataDir, `${toolName}.json`)
|
|
60
60
|
const sourceMeta = path.join(srcPath, 'tool.json')
|
|
61
61
|
|
|
@@ -130,7 +130,7 @@ export async function installTools(tools, targetDir, force = false) {
|
|
|
130
130
|
const content = await fs.readFile(scriptSrc, 'utf-8')
|
|
131
131
|
await fs.writeFile(destPath, substituteVars(content, varsMap), { encoding: 'utf-8', mode: 0o755 })
|
|
132
132
|
|
|
133
|
-
const dataDir = path.join(os.homedir(), '.
|
|
133
|
+
const dataDir = path.join(os.homedir(), '.hskill', 'tools')
|
|
134
134
|
|
|
135
135
|
const toolJsonSrc = path.join(srcPath, 'tool.json')
|
|
136
136
|
if (await fs.pathExists(toolJsonSrc)) {
|
|
@@ -537,7 +537,7 @@ export async function uninstallTool(toolName, opts = {}) {
|
|
|
537
537
|
const { yes = false } = opts
|
|
538
538
|
const home = os.homedir()
|
|
539
539
|
const binPath = path.join(home, '.local', 'bin', toolName)
|
|
540
|
-
const dataDir = path.join(home, '.
|
|
540
|
+
const dataDir = path.join(home, '.hskill', 'tools')
|
|
541
541
|
const jsonPath = path.join(dataDir, `${toolName}.json`)
|
|
542
542
|
const pyPath = path.join(dataDir, `${toolName}.py`)
|
|
543
543
|
|
package/lib/targets.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import os from 'os'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
|
|
4
|
-
export const SKILL_TARGETS = ['claude', 'cursor', 'codex', 'openclaw', 'hermes']
|
|
4
|
+
export const SKILL_TARGETS = ['claude', 'cursor', 'codex', 'openclaw', 'hermes', 'opencode']
|
|
5
5
|
|
|
6
6
|
export const USER_ONLY_TARGETS = new Set(['openclaw', 'hermes'])
|
|
7
7
|
|
|
8
|
+
// Targets whose user-level skill dir doesn't follow ~/.{name}/skills
|
|
9
|
+
const USER_DIR_OVERRIDES = {
|
|
10
|
+
opencode: path.join(os.homedir(), '.config', 'opencode', 'skills'),
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function userSkillDir(name) {
|
|
14
|
+
return USER_DIR_OVERRIDES[name] ?? path.join(os.homedir(), `.${name}`, 'skills')
|
|
15
|
+
}
|
|
16
|
+
|
|
8
17
|
function skillDir(name, scope) {
|
|
9
18
|
if (USER_ONLY_TARGETS.has(name) || scope !== 'project')
|
|
10
|
-
return
|
|
19
|
+
return userSkillDir(name)
|
|
11
20
|
return path.join(process.cwd(), `.${name}`, 'skills')
|
|
12
21
|
}
|
|
13
22
|
|
|
@@ -17,6 +26,7 @@ export const TARGETS = {
|
|
|
17
26
|
codex: path.join(os.homedir(), '.codex', 'skills'),
|
|
18
27
|
openclaw: path.join(os.homedir(), '.openclaw', 'skills'),
|
|
19
28
|
hermes: path.join(os.homedir(), '.hermes', 'skills'),
|
|
29
|
+
opencode: path.join(os.homedir(), '.config', 'opencode', 'skills'),
|
|
20
30
|
shell: path.join(os.homedir(), '.local', 'bin'),
|
|
21
31
|
}
|
|
22
32
|
|
package/lib/vars.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from 'fs-extra'
|
|
3
3
|
import path from 'path'
|
|
4
4
|
import os from 'os'
|
|
5
|
-
import { input } from '@inquirer/prompts'
|
|
5
|
+
import { input, select } from '@inquirer/prompts'
|
|
6
6
|
|
|
7
7
|
export function buildAutoVars() {
|
|
8
8
|
return { HOME: os.homedir() }
|
|
@@ -20,10 +20,49 @@ export function substituteVars(text, varsMap) {
|
|
|
20
20
|
return text.replace(/\{\{(\w+)\}\}/g, (_, name) => varsMap[name] ?? `{{${name}}}`)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
async function detectChromeProfiles() {
|
|
24
|
+
const chromeBase = path.join(os.homedir(), 'Library/Application Support/Google/Chrome')
|
|
25
|
+
if (!await fs.pathExists(chromeBase)) return []
|
|
26
|
+
|
|
27
|
+
const entries = await fs.readdir(chromeBase)
|
|
28
|
+
const profileDirs = entries.filter(e => e === 'Default' || e.startsWith('Profile '))
|
|
29
|
+
profileDirs.sort((a, b) => (a === 'Default' ? -1 : b === 'Default' ? 1 : a.localeCompare(b)))
|
|
30
|
+
|
|
31
|
+
const profiles = []
|
|
32
|
+
for (const name of profileDirs) {
|
|
33
|
+
const dir = path.join(chromeBase, name)
|
|
34
|
+
let email = ''
|
|
35
|
+
try {
|
|
36
|
+
const prefs = await fs.readJson(path.join(dir, 'Preferences'))
|
|
37
|
+
const accounts = prefs.account_info ?? []
|
|
38
|
+
email = accounts[0]?.email ?? prefs.user_name ?? ''
|
|
39
|
+
} catch { /* unreadable */ }
|
|
40
|
+
profiles.push({ name, dir, email })
|
|
41
|
+
}
|
|
42
|
+
return profiles
|
|
43
|
+
}
|
|
44
|
+
|
|
23
45
|
export async function resolveVars(varDefs = [], autoVars) {
|
|
24
46
|
const result = { ...autoVars }
|
|
25
47
|
for (const def of varDefs) {
|
|
26
48
|
const defaultVal = substituteVars(def.default ?? '', autoVars)
|
|
49
|
+
|
|
50
|
+
if (def.type === 'chrome_profile_select') {
|
|
51
|
+
const profiles = await detectChromeProfiles()
|
|
52
|
+
if (profiles.length > 0) {
|
|
53
|
+
const choices = profiles.map(p => ({
|
|
54
|
+
name: p.email ? `${p.name} (${p.email})` : p.name,
|
|
55
|
+
value: p.dir,
|
|
56
|
+
}))
|
|
57
|
+
choices.push({ name: '手动输入路径…', value: '__manual__' })
|
|
58
|
+
const selected = await select({ message: `${def.description}:`, choices })
|
|
59
|
+
if (selected !== '__manual__') {
|
|
60
|
+
result[def.name] = selected
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
27
66
|
const value = await input({
|
|
28
67
|
message: `${def.description}:`,
|
|
29
68
|
default: defaultVal,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harveyz-skill",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Skill manager for Claude Code, Cursor, and Codex",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,30 +16,30 @@
|
|
|
16
16
|
"bundles.json",
|
|
17
17
|
"skills-index.json",
|
|
18
18
|
"CHANGELOG.md",
|
|
19
|
-
"skills/
|
|
20
|
-
"skills/
|
|
21
|
-
"skills/
|
|
22
|
-
"skills/
|
|
23
|
-
"skills/
|
|
24
|
-
"skills/
|
|
25
|
-
"skills/
|
|
26
|
-
"skills/
|
|
27
|
-
"skills/
|
|
28
|
-
"skills/
|
|
29
|
-
"skills/
|
|
30
|
-
"skills/
|
|
31
|
-
"skills/
|
|
32
|
-
"skills/
|
|
33
|
-
"skills/writing/
|
|
19
|
+
"skills/research/article-fetcher/",
|
|
20
|
+
"skills/research/url-extract/",
|
|
21
|
+
"skills/research/vision-extract/",
|
|
22
|
+
"skills/research/youtube-learner/",
|
|
23
|
+
"skills/creative/add-todo/",
|
|
24
|
+
"skills/creative/insight/",
|
|
25
|
+
"skills/coding/git-workflow-init/",
|
|
26
|
+
"skills/coding/full-stack-debug-env/",
|
|
27
|
+
"skills/coding/pm-task-dispatch/",
|
|
28
|
+
"skills/coding/task-close/",
|
|
29
|
+
"skills/writing/doc-forge/",
|
|
30
|
+
"skills/writing/diagram/",
|
|
31
|
+
"skills/writing/diataxis-docs/",
|
|
32
|
+
"skills/writing/dir-manage/",
|
|
33
|
+
"skills/writing/migrate-specs/",
|
|
34
|
+
"skills/design/brand-scout/",
|
|
35
|
+
"skills/design/style-build/",
|
|
34
36
|
"skills/design/sync-design-html/",
|
|
35
|
-
"skills/
|
|
37
|
+
"skills/meta/skill-analyzer/",
|
|
38
|
+
"skills/meta/git-cleanup/",
|
|
36
39
|
"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
40
|
"skills/meta/skill-publish/",
|
|
42
41
|
"skills/meta/npm-release/",
|
|
42
|
+
"skills/meta/opencode-runner/",
|
|
43
43
|
"tools/p-launch/",
|
|
44
44
|
"hooks/check-similar-branch/"
|
|
45
45
|
],
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-todo
|
|
3
|
+
version: "4.0.0"
|
|
4
|
+
user_invocable: true
|
|
5
|
+
description: "Add a new requirement, task, or feature request to any project's TODO.md — from any working directory. Triggers whenever the user wants to capture a new need — even phrased casually like 'we should do X later', 'add this to the backlog', 'note this down', 'remember to build X', 'we need to do Y at some point', or 'record this for later'."
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# 写入 TODO
|
|
9
|
+
|
|
10
|
+
快速捕获需求:先用 2-3 个问题把需求说清楚,再根据完整的需求信息匹配项目、生成标题,最后写入 TODO.md。
|
|
11
|
+
|
|
12
|
+
## 核心原则
|
|
13
|
+
|
|
14
|
+
**先把需求说清楚,再定项目归属。** 完整的需求描述是项目匹配最好的材料。问题只聚焦需求本身,不聚焦方案。够写一条清晰的任务条目就停,不要拖长。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 阶段一 — 需求确认(2-3 轮)
|
|
19
|
+
|
|
20
|
+
**按需提问,已从上下文明确的直接跳过:**
|
|
21
|
+
|
|
22
|
+
1. **需求** — 要解决什么问题、实现什么功能?(用户已清晰描述则复述确认即可)
|
|
23
|
+
2. **紧急程度** — 有多急?(默认 P2,只在用户暗示紧急或不重要时调整)
|
|
24
|
+
3. **背景** — 为什么现在提出来?(可选,有助于写出有上下文的描述)
|
|
25
|
+
|
|
26
|
+
**提问方式:**
|
|
27
|
+
- 每次只问一个问题
|
|
28
|
+
- 优先给选项而非开放题:"这个更像 (A) 缺失功能 还是 (B) 现有功能的问题?"
|
|
29
|
+
- 2 轮够用时不要凑到 3 轮
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 阶段二 — 确认项目归属
|
|
34
|
+
|
|
35
|
+
需求收集完毕后,用 Read 工具读取项目注册表:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
~/.hskill/todo-tool/PROJECTS.md
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
同时用 Bash 运行 `pwd` 获取当前目录。
|
|
42
|
+
|
|
43
|
+
### 匹配逻辑
|
|
44
|
+
|
|
45
|
+
综合以下信号推断候选项目:
|
|
46
|
+
|
|
47
|
+
1. **当前目录**:`pwd` 输出是否是某个项目 `local_path` 的子路径 → 强信号
|
|
48
|
+
2. **需求关键词 + 项目描述**:需求描述中的词汇是否与项目名或描述匹配 → 中信号
|
|
49
|
+
3. **语义推断**:需求内容在哪个项目的业务范围内 → 弱信号
|
|
50
|
+
|
|
51
|
+
### 确认策略
|
|
52
|
+
|
|
53
|
+
| 情况 | 行动 |
|
|
54
|
+
|------|------|
|
|
55
|
+
| 唯一高置信匹配 | 告知并请确认:"这是 **[项目名]** 的需求,对吗?" |
|
|
56
|
+
| 2-3 个候选 | 列出候选,请用户选择 |
|
|
57
|
+
| 无匹配或不确定 | 展示完整项目列表,让用户选 |
|
|
58
|
+
|
|
59
|
+
> **谨慎原则**:宁可多问一次,不写入错误项目。
|
|
60
|
+
|
|
61
|
+
### 生成标题
|
|
62
|
+
|
|
63
|
+
项目确认后,根据完整需求生成任务标题:
|
|
64
|
+
|
|
65
|
+
- 祈使句,动词开头,直接说明要做什么
|
|
66
|
+
- ≤20 字,超出则提炼核心、去掉修饰语
|
|
67
|
+
- 足够具体,让人一眼看出任务核心,避免泛泛的"优化"、"修复"
|
|
68
|
+
- 不堆砌细节,只抓最关键的区分点
|
|
69
|
+
|
|
70
|
+
**示例:**
|
|
71
|
+
- ✗ "优化系统" → ✓ "重构视频解析模块以支持多格式输入"
|
|
72
|
+
- ✗ "修复 bug" → ✓ "修复字幕导出时 UTF-8 编码乱码"
|
|
73
|
+
- ✗ "添加功能" → ✓ "为 add-todo skill 添加 SQLite 持久化层"
|
|
74
|
+
|
|
75
|
+
展示摘要等用户确认:
|
|
76
|
+
> "记录为:**[项目名]** — [生成的标题],[优先级]。确认吗?"
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 阶段三 — 写入 TODO.md
|
|
81
|
+
|
|
82
|
+
### 确定文件路径
|
|
83
|
+
|
|
84
|
+
从阶段二确认的项目的 `local_path` 取出路径,TODO.md 位于 `{local_path}/TODO.md`。
|
|
85
|
+
|
|
86
|
+
若该项目无 `local_path`,询问用户本地目录,写入后提示注册:
|
|
87
|
+
```bash
|
|
88
|
+
todo project set-path [项目名] [本地路径]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 重复检查
|
|
92
|
+
|
|
93
|
+
加载文件,扫描是否有语义重叠的已有条目。若有:
|
|
94
|
+
> "发现可能重叠的条目:**[已有标题]**。这是同一件事还是独立需求?"
|
|
95
|
+
|
|
96
|
+
由用户决定:跳过、替换或独立写入。
|
|
97
|
+
|
|
98
|
+
### 写入格式
|
|
99
|
+
|
|
100
|
+
追加到 `## 🚧 待开发` 末尾:
|
|
101
|
+
|
|
102
|
+
```markdown
|
|
103
|
+
### [任务标题(≤20 字)]
|
|
104
|
+
**优先级**: P? | **日期**: YYYY-MM-DD
|
|
105
|
+
|
|
106
|
+
[描述:做什么、为什么。不写怎么做。篇幅以说清楚为准,不限长短。]
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
文件不存在则创建,初始结构:
|
|
112
|
+
|
|
113
|
+
```markdown
|
|
114
|
+
# TODO / Backlog
|
|
115
|
+
|
|
116
|
+
## 🚧 待开发
|
|
117
|
+
|
|
118
|
+
## ✅ 已完成
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
写入后确认:
|
|
122
|
+
|
|
123
|
+
> "✅ 已将 **[任务标题]** 写入 `{local_path}/TODO.md`。"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: insight
|
|
3
3
|
version: "1.1.0"
|
|
4
4
|
user_invocable: true
|
|
5
5
|
description: "Capture and record a fleeting insight or observation. Triggers immediately when the user expresses an observation, judgment, or idea — phrases like 'I noticed', 'just an observation', 'record this', 'insight', 'I have a judgment', or tossing out an opinionated thought. Distinct from 'I have an idea to execute' (that's a task). Clarifies the core with 2-3 quick Q&As, then saves to the insights/ folder. Trigger early — insights are fragile."
|