harveyz-skill 0.1.1 → 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.
Files changed (31) hide show
  1. package/bin/cli.js +116 -44
  2. package/lib/bundles.js +97 -22
  3. package/lib/installer.js +126 -5
  4. package/lib/targets.js +1 -0
  5. package/lib/vars.js +34 -0
  6. package/package.json +6 -3
  7. package/skills/harness/diataxis-docs/SKILL.md +128 -0
  8. package/skills/harness/diataxis-docs/references/custom-categories.md +32 -0
  9. package/skills/harness/diataxis-docs/references/index-template.md +29 -0
  10. package/skills/web-fetch/article-fetcher/SKILL.md +625 -0
  11. package/skills/web-fetch/article-fetcher/references/article_utils.py +225 -0
  12. package/skills/web-fetch/article-fetcher/references/file-format.md +73 -0
  13. package/skills/web-fetch/article-fetcher/scripts/import_reading.py +75 -0
  14. package/skills/web-fetch/article-fetcher/scripts/init_db.py +33 -0
  15. package/skills/web-fetch/article-fetcher/scripts/url-index.db +0 -0
  16. package/skills/web-fetch/article-fetcher/scripts/url-index.db.bak +0 -0
  17. package/skills/web-fetch/article-fetcher/vars.json +12 -0
  18. package/skills/writing/mermaid-diagram/SKILL.md +220 -0
  19. package/skills/writing/mermaid-diagram/references/color-templates.md +75 -0
  20. package/skills/writing/mermaid-diagram/references/flowchart.md +88 -0
  21. package/skills/writing/mermaid-diagram/references/gantt-timeline.md +62 -0
  22. package/skills/writing/mermaid-diagram/references/other-charts.md +55 -0
  23. package/skills/writing/mermaid-diagram/references/sequence.md +143 -0
  24. package/skills/writing/mermaid-diagram/references/statediagram.md +54 -0
  25. package/skills/writing/mermaid-diagram/scripts/check_mermaid.py +84 -0
  26. package/tools/p-launch/p-launch.sh +275 -0
  27. package/tools/p-launch/tests/e2e.bats +164 -0
  28. package/tools/p-launch/tests/unit.bats +151 -0
  29. package/tools/p-launch/vars.json +9 -0
  30. package/tools/p-launch/zshrc.snippet +5 -0
  31. package/bundles.json +0 -24
package/bin/cli.js CHANGED
@@ -1,9 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { checkbox } from '@inquirer/prompts'
3
3
  import chalk from 'chalk'
4
- import { BUNDLE_CHOICES, resolveSkills } from '../lib/bundles.js'
4
+ import {
5
+ buildAllChoices, getAllSkillItems, getAllToolItems,
6
+ resolveSkills, resolveTools,
7
+ TOOL_BUNDLE_CHOICES,
8
+ } from '../lib/bundles.js'
5
9
  import { TARGET_CHOICES, resolveTargets, TARGETS } from '../lib/targets.js'
6
- import { installSkills } from '../lib/installer.js'
10
+ import { installSkills, installTools } from '../lib/installer.js'
7
11
 
8
12
  const args = process.argv.slice(2)
9
13
  const forceFlag = args.includes('--force')
@@ -12,70 +16,138 @@ const targetIdx = args.indexOf('--target')
12
16
  const bundleArg = bundleIdx !== -1 ? args[bundleIdx + 1] : undefined
13
17
  const targetArg = targetIdx !== -1 ? args[targetIdx + 1] : undefined
14
18
 
15
- // list 子命令
16
19
  if (args[0] === 'list') {
17
20
  const { createRequire } = await import('module')
18
21
  const require = createRequire(import.meta.url)
19
- const bundles = require('../bundles.json')
20
- for (const b of bundles) {
21
- console.log(chalk.bold(b.name) + ' ' + b.description)
22
- if (b.skills) for (const s of b.skills) console.log(' ' + s)
22
+ const { bundleMeta, skills, toolBundleMeta = {}, tools = [] } = require('../skills-index.json')
23
+ const byBundle = {}
24
+ for (const s of skills) {
25
+ if (!byBundle[s.bundle]) byBundle[s.bundle] = []
26
+ byBundle[s.bundle].push(s.path)
27
+ }
28
+ for (const [name, paths] of Object.entries(byBundle)) {
29
+ console.log(chalk.bold(name) + ' — ' + (bundleMeta[name] ?? name))
30
+ for (const p of paths) console.log(' ' + p)
31
+ }
32
+ if (tools.length > 0) {
33
+ console.log('')
34
+ console.log(chalk.bold('shell tools:'))
35
+ for (const t of tools) console.log(' ' + t.name)
23
36
  }
24
37
  process.exit(0)
25
38
  }
26
39
 
40
+ const TOOL_BUNDLE_VALUES = new Set(TOOL_BUNDLE_CHOICES.map(c => c.value))
41
+
42
+ function buildInteractiveChoices() {
43
+ const toolItems = getAllToolItems()
44
+ return [
45
+ ...buildAllChoices(),
46
+ ...(toolItems.length > 0
47
+ ? [
48
+ { type: 'separator', separator: '── shell tools ──' },
49
+ ...toolItems.map(t => ({ name: t.toolName, value: t })),
50
+ ]
51
+ : []),
52
+ { type: 'separator', separator: '────────────────' },
53
+ { name: 'all skills', value: 'all' },
54
+ ]
55
+ }
56
+
27
57
  try {
28
- // 解析 bundle 选择
29
- let selectedBundles
58
+ let skillItems = []
59
+ let toolItems = []
60
+
30
61
  if (bundleArg) {
31
- selectedBundles = bundleArg.split(',').map(s => s.trim()).filter(Boolean)
62
+ const bundles = bundleArg.split(',').map(s => s.trim()).filter(Boolean)
63
+ const skillBundles = bundles.filter(b => !TOOL_BUNDLE_VALUES.has(b))
64
+ const toolBundles = bundles.filter(b => TOOL_BUNDLE_VALUES.has(b))
65
+ if (skillBundles.length) skillItems = resolveSkills(skillBundles).map(s => ({ kind: 'skill', ...s }))
66
+ if (toolBundles.length) toolItems = resolveTools(toolBundles).map(t => ({ kind: 'tool', ...t }))
32
67
  } else {
33
- selectedBundles = await checkbox({
34
- message: '选择要安装的 bundle(空格多选):',
35
- choices: BUNDLE_CHOICES,
68
+ const selected = await checkbox({
69
+ message: 'Select items to install (space to select, enter to confirm):',
70
+ choices: buildInteractiveChoices(),
36
71
  })
37
- }
38
72
 
39
- if (!selectedBundles.length) {
40
- console.log(chalk.red('未选择任何 bundle,退出。'))
41
- process.exit(1)
42
- }
73
+ if (!selected.length) {
74
+ console.log(chalk.dim(' · Nothing selected, exiting'))
75
+ process.exit(0)
76
+ }
43
77
 
44
- // 解析 target 选择
45
- let selectedTargets
46
- if (targetArg) {
47
- selectedTargets = targetArg === 'all' ? Object.keys(TARGETS) : [targetArg]
48
- } else {
49
- selectedTargets = await checkbox({
50
- message: '安装到哪些工具(空格多选):',
51
- choices: [
52
- ...TARGET_CHOICES,
53
- { name: 'all 全部工具', value: 'all' },
54
- ],
78
+ const hasAll = selected.includes('all')
79
+ const items = selected.filter(s => s !== 'all')
80
+
81
+ const selectedSkills = hasAll ? getAllSkillItems() : items.filter(s => s.kind === 'skill')
82
+ toolItems = items.filter(s => s.kind === 'tool')
83
+
84
+ const seen = new Set()
85
+ skillItems = selectedSkills.filter(s => {
86
+ if (seen.has(s.skillName)) return false
87
+ seen.add(s.skillName); return true
55
88
  })
56
89
  }
57
90
 
58
- if (!selectedTargets.length) {
59
- console.log(chalk.red('未选择任何目标工具,退出。'))
60
- process.exit(1)
91
+ if (!skillItems.length && !toolItems.length) {
92
+ console.log(chalk.dim(' · Nothing selected, exiting'))
93
+ process.exit(0)
61
94
  }
62
95
 
63
- const skills = resolveSkills(selectedBundles)
64
- const targets = resolveTargets(selectedTargets)
96
+ // ── Install skills ───────────────────────────────────────────────────────────
97
+ if (skillItems.length > 0) {
98
+ let selectedTargets
99
+ if (targetArg) {
100
+ selectedTargets = targetArg === 'all' ? Object.keys(TARGETS) : [targetArg]
101
+ } else {
102
+ selectedTargets = await checkbox({
103
+ message: 'Install to which tools (space to select, enter to confirm):',
104
+ choices: [
105
+ ...TARGET_CHOICES,
106
+ { name: 'all — all tools', value: 'all' },
107
+ ],
108
+ })
109
+ }
65
110
 
66
- console.log('')
67
- const summary = await installSkills(skills, targets, forceFlag)
111
+ if (selectedTargets.length > 0) {
112
+ const targets = resolveTargets(selectedTargets)
113
+ console.log('')
114
+ const summary = await installSkills(skillItems, targets, forceFlag)
115
+ console.log('')
116
+ if (Object.keys(summary).length === 0) {
117
+ console.log(chalk.dim(' · No skills installed'))
118
+ } else {
119
+ console.log(chalk.green.bold('✔ Skills installed:'))
120
+ for (const [target, names] of Object.entries(summary)) {
121
+ console.log(` ${chalk.bold(target)} ← ${names.join(', ')}`)
122
+ }
123
+ }
124
+ }
125
+ }
68
126
 
69
- console.log('')
70
- if (Object.keys(summary).length === 0) {
71
- console.log(chalk.yellow(' 没有 skill 被安装。'))
72
- } else {
73
- console.log(chalk.green('✔ 安装完成:'))
74
- for (const [target, names] of Object.entries(summary)) {
75
- console.log(` ${chalk.bold(target)} ← ${names.join(', ')}`)
127
+ // ── Install shell tools ──────────────────────────────────────────────────────
128
+ if (toolItems.length > 0) {
129
+ console.log('')
130
+ const installed = await installTools(
131
+ toolItems.map(t => ({ toolName: t.toolName, srcPath: t.srcPath })),
132
+ TARGETS.shell,
133
+ forceFlag,
134
+ )
135
+ console.log('')
136
+ if (installed.length === 0) {
137
+ console.log(chalk.dim(' · No shell tools installed'))
138
+ } else {
139
+ console.log(chalk.green.bold('✔ Shell tools installed:'))
140
+ for (const name of installed) {
141
+ console.log(` ${chalk.bold('~/.local/bin')} ← ${name}`)
142
+ }
143
+ console.log('')
144
+ console.log(chalk.yellow.bold(' ⚡ Reload your shell to apply changes:'))
145
+ console.log('')
146
+ console.log(` ${chalk.bold.cyan('source ~/.zshrc')}`)
147
+ console.log('')
76
148
  }
77
149
  }
78
150
  } catch (err) {
79
- console.error(chalk.red('Error: ' + err.message))
151
+ console.error(chalk.red(' ' + err.message))
80
152
  process.exit(1)
81
153
  }
package/lib/bundles.js CHANGED
@@ -5,34 +5,109 @@ import { fileURLToPath } from 'url'
5
5
  const require = createRequire(import.meta.url)
6
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
7
 
8
- const bundleDefs = require('../bundles.json')
9
- const skillsRoot = path.join(__dirname, '..', 'skills')
8
+ const { bundleMeta, skills: skillDefs, toolBundleMeta = {}, tools: toolDefs = [] } = require('../skills-index.json')
9
+ const repoRoot = path.join(__dirname, '..')
10
+ const skillsRoot = path.join(repoRoot, 'skills')
10
11
 
11
- export const BUNDLE_CHOICES = bundleDefs.map(b => ({
12
- name: `${b.name.padEnd(16)} ${b.description}`,
13
- value: b.name,
14
- }))
12
+ // ── Skills ──────────────────────────────────────────────────────────────────
13
+ const bundleSkills = {}
14
+ for (const skill of skillDefs) {
15
+ if (!bundleSkills[skill.bundle]) bundleSkills[skill.bundle] = []
16
+ bundleSkills[skill.bundle].push(skill)
17
+ }
18
+
19
+ const namedBundles = Object.keys(bundleSkills)
20
+
21
+ export const BUNDLE_CHOICES = [
22
+ ...namedBundles.map(name => ({
23
+ name: `${name.padEnd(16)} — ${bundleMeta[name] ?? name}`,
24
+ value: name,
25
+ })),
26
+ { name: `${'all'.padEnd(16)} — 全部 skill`, value: 'all' },
27
+ ]
15
28
 
16
- // 返回选中 bundles 对应的去重 skill 路径列表 { skillName, srcPath }
17
29
  export function resolveSkills(selectedBundles) {
18
- // 展开 dynamic bundle(all)为所有非 dynamic bundle union
19
- const expanded = selectedBundles.flatMap(name => {
20
- const def = bundleDefs.find(b => b.name === name)
21
- if (!def) throw new Error(`Unknown bundle: "${name}"`)
22
- if (def.dynamic) return bundleDefs.filter(b => !b.dynamic).map(b => b.name)
23
- return name
24
- })
30
+ const expanded = selectedBundles.includes('all') ? namedBundles : selectedBundles
31
+
32
+ const seen = new Set()
33
+ const result = []
34
+ for (const bundleName of expanded) {
35
+ const skills = bundleSkills[bundleName]
36
+ if (!skills) throw new Error(`Unknown bundle: "${bundleName}"`)
37
+ for (const skill of skills) {
38
+ if (seen.has(skill.path)) continue
39
+ seen.add(skill.path)
40
+ const skillName = skill.path.split('/').pop()
41
+ result.push({ skillName, srcPath: path.join(skillsRoot, skill.path) })
42
+ }
43
+ }
44
+ return result
45
+ }
46
+
47
+ // 单个 skill 粒度的选项列表(用于交互式选择)
48
+ // 每个值是 { kind:'skill', skillName, srcPath } 或特殊字符串 'all'
49
+ export function buildAllChoices() {
50
+ const choices = []
51
+
52
+ // ── skill ── 分组
53
+ for (const [bundleName, skills] of Object.entries(bundleSkills)) {
54
+ choices.push({ type: 'separator', separator: `── ${bundleName} ──` })
55
+ for (const skill of skills) {
56
+ const skillName = skill.path.split('/').pop()
57
+ choices.push({
58
+ name: skillName,
59
+ value: { kind: 'skill', skillName, srcPath: path.join(skillsRoot, skill.path) },
60
+ })
61
+ }
62
+ }
63
+
64
+ return choices
65
+ }
66
+
67
+ // 所有 skill 展开为 { kind:'skill', skillName, srcPath } 列表(对应 all 选项)
68
+ export function getAllSkillItems() {
69
+ return skillDefs.map(skill => ({
70
+ kind: 'skill',
71
+ skillName: skill.path.split('/').pop(),
72
+ srcPath: path.join(skillsRoot, skill.path),
73
+ }))
74
+ }
75
+
76
+ // 所有 tool 展开为 { kind:'tool', toolName, srcPath } 列表
77
+ export function getAllToolItems() {
78
+ return toolDefs.map(tool => ({
79
+ kind: 'tool',
80
+ toolName: tool.name,
81
+ srcPath: path.join(repoRoot, 'tools', tool.name),
82
+ }))
83
+ }
84
+
85
+ // ── Tools ────────────────────────────────────────────────────────────────────
86
+ const bundleTools = {}
87
+ for (const tool of toolDefs) {
88
+ if (!bundleTools[tool.bundle]) bundleTools[tool.bundle] = []
89
+ bundleTools[tool.bundle].push(tool)
90
+ }
91
+
92
+ const namedToolBundles = Object.keys(bundleTools)
93
+
94
+ export const TOOL_BUNDLE_CHOICES = namedToolBundles.map(name => ({
95
+ name: `${name.padEnd(16)} — ${toolBundleMeta[name] ?? name}`,
96
+ value: name,
97
+ }))
98
+
99
+ export function resolveTools(selectedBundles) {
100
+ const expanded = selectedBundles.includes('all') ? namedToolBundles : selectedBundles
25
101
 
26
102
  const seen = new Set()
27
103
  const result = []
28
- for (const name of expanded) {
29
- const def = bundleDefs.find(b => b.name === name)
30
- if (!def) continue
31
- for (const rel of def.skills) {
32
- if (seen.has(rel)) continue
33
- seen.add(rel)
34
- const skillName = rel.split('/').pop()
35
- result.push({ skillName, srcPath: path.join(skillsRoot, rel) })
104
+ for (const bundleName of expanded) {
105
+ const tools = bundleTools[bundleName]
106
+ if (!tools) throw new Error(`Unknown tool bundle: "${bundleName}"`)
107
+ for (const tool of tools) {
108
+ if (seen.has(tool.name)) continue
109
+ seen.add(tool.name)
110
+ result.push({ toolName: tool.name, srcPath: path.join(repoRoot, 'tools', tool.name) })
36
111
  }
37
112
  }
38
113
  return result
package/lib/installer.js CHANGED
@@ -1,7 +1,117 @@
1
1
  import fs from 'fs-extra'
2
+ import os from 'os'
2
3
  import path from 'path'
3
4
  import { confirm } from '@inquirer/prompts'
4
5
  import chalk from 'chalk'
6
+ import { loadVarDefs, buildAutoVars, resolveVars, substituteVars } from './vars.js'
7
+
8
+ const BINARY_EXTS = new Set(['.db', '.pyc', '.jpg', '.jpeg', '.png', '.gif', '.webp', '.pdf'])
9
+ const SKIP_NAMES = new Set(['vars.json', '__pycache__', '.DS_Store'])
10
+
11
+ async function copyDir(srcDir, destDir, varsMap) {
12
+ await fs.ensureDir(destDir)
13
+ const entries = await fs.readdir(srcDir, { withFileTypes: true })
14
+ for (const entry of entries) {
15
+ if (SKIP_NAMES.has(entry.name)) continue
16
+ const src = path.join(srcDir, entry.name)
17
+ const dest = path.join(destDir, entry.name)
18
+ if (entry.isDirectory()) {
19
+ await copyDir(src, dest, varsMap)
20
+ } else if (Object.keys(varsMap).length > 0 && !BINARY_EXTS.has(path.extname(entry.name).toLowerCase())) {
21
+ const content = await fs.readFile(src, 'utf-8').catch(e => { throw new Error(`Failed to read ${src}: ${e.message}`) })
22
+ await fs.writeFile(dest, substituteVars(content, varsMap), 'utf-8')
23
+ } else {
24
+ await fs.copy(src, dest, { overwrite: true })
25
+ }
26
+ }
27
+ }
28
+
29
+ // tools: [{ toolName, srcPath }]
30
+ // targetDir: string — path to ~/.local/bin
31
+ // force: boolean
32
+ export async function installTools(tools, targetDir, force = false) {
33
+ const exists = await fs.pathExists(targetDir)
34
+ if (!exists) {
35
+ console.log(chalk.dim(` · Creating directory: ${targetDir}`))
36
+ await fs.ensureDir(targetDir)
37
+ }
38
+
39
+ const installed = []
40
+ for (const { toolName, srcPath } of tools) {
41
+ const scriptSrc = path.join(srcPath, `${toolName}.sh`)
42
+ const destPath = path.join(targetDir, toolName)
43
+
44
+ if (!await fs.pathExists(scriptSrc)) {
45
+ console.log(chalk.red(` ✗ Script not found: ${scriptSrc}`))
46
+ continue
47
+ }
48
+
49
+ const destExists = await fs.pathExists(destPath)
50
+ if (destExists && !force) {
51
+ const ok = await confirm({ message: `${destPath} already exists. Overwrite?`, default: true })
52
+ if (!ok) {
53
+ console.log(chalk.dim(` · Skipped ${toolName}`))
54
+ continue
55
+ }
56
+ }
57
+
58
+ try {
59
+ const varDefs = await loadVarDefs(srcPath)
60
+ let varsMap = {}
61
+ if (varDefs.length > 0) {
62
+ console.log(chalk.bold(`\n Configure ${toolName}:`))
63
+ varsMap = await resolveVars(varDefs, buildAutoVars())
64
+ for (const def of varDefs) {
65
+ if (def.configFile && def.configKey) {
66
+ await _writeToolConfigVar(def, varsMap[def.name])
67
+ }
68
+ }
69
+ }
70
+ const content = await fs.readFile(scriptSrc, 'utf-8')
71
+ await fs.writeFile(destPath, substituteVars(content, varsMap), { encoding: 'utf-8', mode: 0o755 })
72
+ installed.push(toolName)
73
+ await _patchZshrc(srcPath, toolName)
74
+ } catch (err) {
75
+ console.log(chalk.red(` ✗ Failed to install ${toolName}: ${err.message}`))
76
+ }
77
+ }
78
+
79
+ return installed
80
+ }
81
+
82
+ async function _writeToolConfigVar(def, value) {
83
+ const configPath = def.configFile.replace(/^~/, os.homedir())
84
+ await fs.ensureDir(path.dirname(configPath))
85
+ await fs.writeFile(configPath, `${def.configKey}=("${value}")\n`, 'utf-8')
86
+ console.log(chalk.green(` ✓ Config written to ${def.configFile}`))
87
+ }
88
+
89
+ async function _patchZshrc(srcPath, toolName) {
90
+ const snippetPath = path.join(srcPath, 'zshrc.snippet')
91
+ if (!await fs.pathExists(snippetPath)) return
92
+
93
+ const zshrcPath = path.join(os.homedir(), '.zshrc')
94
+ const marker = `# >>> ${toolName}`
95
+
96
+ const existing = await fs.pathExists(zshrcPath)
97
+ ? await fs.readFile(zshrcPath, 'utf-8')
98
+ : ''
99
+
100
+ if (existing.includes(marker)) {
101
+ console.log(chalk.dim(` · ~/.zshrc already has ${toolName} config, skipping`))
102
+ return
103
+ }
104
+
105
+ const ok = await confirm({
106
+ message: `Add ${toolName} PATH and alias to ~/.zshrc?`,
107
+ default: true,
108
+ })
109
+ if (!ok) return
110
+
111
+ const snippet = await fs.readFile(snippetPath, 'utf-8')
112
+ await fs.appendFile(zshrcPath, snippet, 'utf-8')
113
+ console.log(chalk.green(` ✓ Written to ~/.zshrc`))
114
+ }
5
115
 
6
116
  // skills: [{ skillName, srcPath }]
7
117
  // targets: [{ name, dir }]
@@ -12,7 +122,7 @@ export async function installSkills(skills, targets, force = false) {
12
122
  for (const { name: targetName, dir: targetDir } of targets) {
13
123
  const exists = await fs.pathExists(targetDir)
14
124
  if (!exists) {
15
- console.log(chalk.yellow(` 跳过 ${targetName}(目录不存在:${targetDir})`))
125
+ console.log(chalk.dim(` · Skipping ${targetName} (directory not found: ${targetDir})`))
16
126
  continue
17
127
  }
18
128
 
@@ -22,18 +132,29 @@ export async function installSkills(skills, targets, force = false) {
22
132
  const destExists = await fs.pathExists(destPath)
23
133
 
24
134
  if (destExists && !force) {
25
- const ok = await confirm({ message: `${targetName}/${skillName} 已存在,覆盖?`, default: false })
135
+ const ok = await confirm({ message: `${targetName}/${skillName} already exists. Overwrite?`, default: false })
26
136
  if (!ok) {
27
- console.log(chalk.gray(` 跳过 ${targetName}/${skillName}`))
137
+ console.log(chalk.dim(` · Skipped ${targetName}/${skillName}`))
28
138
  continue
29
139
  }
30
140
  }
31
141
 
142
+ if (!await fs.pathExists(srcPath)) {
143
+ console.log(chalk.red(` ✗ Source not found: ${srcPath}`))
144
+ continue
145
+ }
146
+
32
147
  try {
33
- await fs.copy(srcPath, destPath, { overwrite: true })
148
+ const varDefs = await loadVarDefs(srcPath)
149
+ let varsMap = {}
150
+ if (varDefs.length > 0) {
151
+ console.log(chalk.bold(`\n Configure ${skillName}:`))
152
+ varsMap = await resolveVars(varDefs, buildAutoVars())
153
+ }
154
+ await copyDir(srcPath, destPath, varsMap)
34
155
  installed.push(skillName)
35
156
  } catch (err) {
36
- console.log(chalk.red(` 复制失败 ${targetName}/${skillName}: ${err.message}`))
157
+ console.log(chalk.red(` Failed to copy ${targetName}/${skillName}: ${err.message}`))
37
158
  }
38
159
  }
39
160
 
package/lib/targets.js CHANGED
@@ -5,6 +5,7 @@ export const TARGETS = {
5
5
  claude: path.join(os.homedir(), '.claude', 'skills'),
6
6
  cursor: path.join(os.homedir(), '.cursor', 'skills'),
7
7
  codex: path.join(os.homedir(), '.codex', 'skills'),
8
+ shell: path.join(os.homedir(), '.local', 'bin'),
8
9
  }
9
10
 
10
11
  export const TARGET_CHOICES = Object.entries(TARGETS).map(([name, dir]) => ({
package/lib/vars.js ADDED
@@ -0,0 +1,34 @@
1
+ // lib/vars.js
2
+ import fs from 'fs-extra'
3
+ import path from 'path'
4
+ import os from 'os'
5
+ import { input } from '@inquirer/prompts'
6
+
7
+ export function buildAutoVars() {
8
+ return { HOME: os.homedir() }
9
+ }
10
+
11
+ export async function loadVarDefs(skillSrcPath) {
12
+ const varsFile = path.join(skillSrcPath, 'vars.json')
13
+ if (!await fs.pathExists(varsFile)) return []
14
+ const defs = await fs.readJson(varsFile)
15
+ if (!Array.isArray(defs)) throw new Error(`vars.json must be an array (got ${typeof defs})`)
16
+ return defs
17
+ }
18
+
19
+ export function substituteVars(text, varsMap) {
20
+ return text.replace(/\{\{(\w+)\}\}/g, (_, name) => varsMap[name] ?? `{{${name}}}`)
21
+ }
22
+
23
+ export async function resolveVars(varDefs = [], autoVars) {
24
+ const result = { ...autoVars }
25
+ for (const def of varDefs) {
26
+ const defaultVal = substituteVars(def.default ?? '', autoVars)
27
+ const value = await input({
28
+ message: `${def.description}:`,
29
+ default: defaultVal,
30
+ })
31
+ result[def.name] = value
32
+ }
33
+ return result
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harveyz-skill",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "One-click skill installer for Claude Code, Cursor, and Codex",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  "bundles.json",
16
16
  "skills/analysis/skill-analyzer/CHANGELOG.md",
17
17
  "skills/analysis/skill-analyzer/SKILL.md",
18
- "skills/document/diataxis-docs/",
18
+ "skills/harness/diataxis-docs/",
19
19
  "skills/harness/full-stack-debug-env/",
20
20
  "skills/superpowers-fork/brainstorming/",
21
21
  "skills/superpowers-fork/executing-plans/",
@@ -23,7 +23,10 @@
23
23
  "skills/superpowers-fork/using-git-worktrees/",
24
24
  "skills/superpowers-fork/writing-plans/",
25
25
  "skills/task/pm-task-dispatch/",
26
- "skills/task/task-close/"
26
+ "skills/task/task-close/",
27
+ "skills/web-fetch/article-fetcher/",
28
+ "skills/writing/mermaid-diagram/",
29
+ "tools/p-launch/"
27
30
  ],
28
31
  "engines": {
29
32
  "node": ">=18"