harveyz-skill 0.13.0 → 0.14.1

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 (160) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/bin/cli.js +28 -1
  3. package/lib/installer.js +15 -0
  4. package/package.json +3 -1
  5. package/skills/creative/add-todo/SKILL.md +52 -3
  6. package/skills/meta/npm-release/SKILL.md +15 -3
  7. package/skills/research/article-fetcher/references/__pycache__/article_utils.cpython-314.pyc +0 -0
  8. package/skills/research/youtube-learner/SKILL.md +12 -3
  9. package/skills/writing/doc-forge/scripts/__pycache__/md_to_pdf.cpython-314.pyc +0 -0
  10. package/skills-index.json +11 -1
  11. package/tools/hub/hub/__init__.py +0 -0
  12. package/tools/hub/hub/__main__.py +24 -0
  13. package/tools/hub/hub/__pycache__/__init__.cpython-314.pyc +0 -0
  14. package/tools/hub/hub/__pycache__/__main__.cpython-314.pyc +0 -0
  15. package/tools/hub/hub/cli/__init__.py +14 -0
  16. package/tools/hub/hub/cli/__pycache__/__init__.cpython-314.pyc +0 -0
  17. package/tools/hub/hub/cli/__pycache__/projects.cpython-314.pyc +0 -0
  18. package/tools/hub/hub/cli/__pycache__/tasks.cpython-314.pyc +0 -0
  19. package/tools/hub/hub/cli/projects.py +100 -0
  20. package/tools/hub/hub/cli/tasks.py +114 -0
  21. package/tools/hub/hub/core/__init__.py +0 -0
  22. package/tools/hub/hub/core/__pycache__/__init__.cpython-314.pyc +0 -0
  23. package/tools/hub/hub/core/__pycache__/db.cpython-314.pyc +0 -0
  24. package/tools/hub/hub/core/__pycache__/migrate.cpython-314.pyc +0 -0
  25. package/tools/hub/hub/core/__pycache__/projects.cpython-314.pyc +0 -0
  26. package/tools/hub/hub/core/__pycache__/tasks.cpython-314.pyc +0 -0
  27. package/tools/hub/hub/core/db.py +46 -0
  28. package/tools/hub/hub/core/migrate.py +49 -0
  29. package/tools/hub/hub/core/projects.py +65 -0
  30. package/tools/hub/hub/core/tasks.py +86 -0
  31. package/tools/hub/hub/tui/__init__.py +0 -0
  32. package/tools/hub/hub/tui/__pycache__/__init__.cpython-314.pyc +0 -0
  33. package/tools/hub/hub/tui/__pycache__/app.cpython-314.pyc +0 -0
  34. package/tools/hub/hub/tui/__pycache__/git.cpython-314.pyc +0 -0
  35. package/tools/hub/hub/tui/app.py +78 -0
  36. package/tools/hub/hub/tui/git.py +300 -0
  37. package/tools/hub/hub/tui/panels/__init__.py +0 -0
  38. package/tools/hub/hub/tui/panels/__pycache__/__init__.cpython-314.pyc +0 -0
  39. package/tools/hub/hub/tui/panels/__pycache__/git.cpython-314.pyc +0 -0
  40. package/tools/hub/hub/tui/panels/__pycache__/projects.cpython-314.pyc +0 -0
  41. package/tools/hub/hub/tui/panels/__pycache__/tasks.cpython-314.pyc +0 -0
  42. package/tools/hub/hub/tui/panels/git.py +118 -0
  43. package/tools/hub/hub/tui/panels/projects.py +58 -0
  44. package/tools/hub/hub/tui/panels/tasks.py +139 -0
  45. package/tools/hub/hub.sh +12 -0
  46. package/tools/hub/pyproject.toml +25 -0
  47. package/tools/hub/tests/__init__.py +0 -0
  48. package/tools/hub/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  49. package/tools/hub/tests/__pycache__/test_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  50. package/tools/hub/tests/__pycache__/test_db.cpython-314-pytest-9.0.2.pyc +0 -0
  51. package/tools/hub/tests/__pycache__/test_migrate.cpython-314-pytest-9.0.2.pyc +0 -0
  52. package/tools/hub/tests/__pycache__/test_projects.cpython-314-pytest-9.0.2.pyc +0 -0
  53. package/tools/hub/tests/__pycache__/test_tasks.cpython-314-pytest-9.0.2.pyc +0 -0
  54. package/tools/hub/tests/__pycache__/test_tui_app.cpython-314-pytest-9.0.2.pyc +0 -0
  55. package/tools/hub/tests/__pycache__/test_tui_git.cpython-314-pytest-9.0.2.pyc +0 -0
  56. package/tools/hub/tests/__pycache__/test_tui_git_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  57. package/tools/hub/tests/__pycache__/test_tui_projects_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  58. package/tools/hub/tests/__pycache__/test_tui_tasks_panel.cpython-314-pytest-9.0.2.pyc +0 -0
  59. package/tools/hub/tests/test_cli.py +93 -0
  60. package/tools/hub/tests/test_db.py +35 -0
  61. package/tools/hub/tests/test_migrate.py +102 -0
  62. package/tools/hub/tests/test_projects.py +50 -0
  63. package/tools/hub/tests/test_tasks.py +74 -0
  64. package/tools/hub/tests/test_tui_app.py +66 -0
  65. package/tools/hub/tests/test_tui_git.py +58 -0
  66. package/tools/hub/tests/test_tui_git_panel.py +56 -0
  67. package/tools/hub/tests/test_tui_projects_panel.py +63 -0
  68. package/tools/hub/tests/test_tui_tasks_panel.py +136 -0
  69. package/tools/hub/tool.json +8 -0
  70. package/tools/p-launch/__pycache__/p_launch.cpython-314.pyc +0 -0
  71. package/tools/p-launch/p_launch.py +126 -3
  72. package/tools/p-launch/tests/__pycache__/test_p_launch.cpython-314-pytest-9.0.2.pyc +0 -0
  73. package/tools/p-launch/tests/__pycache__/test_p_launch.cpython-314-pytest-9.1.0.pyc +0 -0
  74. package/tools/p-launch/tests/test_p_launch.py +139 -0
  75. package/tools/todo-tool/frontend/README.md +73 -0
  76. package/tools/todo-tool/frontend/components.json +25 -0
  77. package/tools/todo-tool/frontend/eslint.config.js +22 -0
  78. package/tools/todo-tool/frontend/index.html +13 -0
  79. package/tools/todo-tool/frontend/package-lock.json +6566 -0
  80. package/tools/todo-tool/frontend/package.json +42 -0
  81. package/tools/todo-tool/frontend/postcss.config.js +6 -0
  82. package/tools/todo-tool/frontend/public/favicon.svg +1 -0
  83. package/tools/todo-tool/frontend/public/icons.svg +24 -0
  84. package/tools/todo-tool/frontend/src/App.css +184 -0
  85. package/tools/todo-tool/frontend/src/App.tsx +78 -0
  86. package/tools/todo-tool/frontend/src/assets/hero.png +0 -0
  87. package/tools/todo-tool/frontend/src/assets/react.svg +1 -0
  88. package/tools/todo-tool/frontend/src/assets/vite.svg +1 -0
  89. package/tools/todo-tool/frontend/src/components/AddTaskDialog.tsx +92 -0
  90. package/tools/todo-tool/frontend/src/components/FilterBar.tsx +74 -0
  91. package/tools/todo-tool/frontend/src/components/TaskList.tsx +35 -0
  92. package/tools/todo-tool/frontend/src/components/TaskRow.tsx +44 -0
  93. package/tools/todo-tool/frontend/src/components/ui/badge.tsx +52 -0
  94. package/tools/todo-tool/frontend/src/components/ui/button.tsx +58 -0
  95. package/tools/todo-tool/frontend/src/components/ui/dialog.tsx +159 -0
  96. package/tools/todo-tool/frontend/src/components/ui/input.tsx +20 -0
  97. package/tools/todo-tool/frontend/src/components/ui/select.tsx +197 -0
  98. package/tools/todo-tool/frontend/src/index.css +90 -0
  99. package/tools/todo-tool/frontend/src/lib/api.ts +42 -0
  100. package/tools/todo-tool/frontend/src/lib/utils.ts +6 -0
  101. package/tools/todo-tool/frontend/src/main.tsx +10 -0
  102. package/tools/todo-tool/frontend/tailwind.config.js +66 -0
  103. package/tools/todo-tool/frontend/tsconfig.app.json +32 -0
  104. package/tools/todo-tool/frontend/tsconfig.json +7 -0
  105. package/tools/todo-tool/frontend/tsconfig.node.json +24 -0
  106. package/tools/todo-tool/frontend/vite.config.ts +17 -0
  107. package/tools/todo-tool/pyproject.toml +31 -0
  108. package/tools/todo-tool/tests/__init__.py +0 -0
  109. package/tools/todo-tool/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  110. package/tools/todo-tool/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  111. package/tools/todo-tool/tests/__pycache__/test_api.cpython-311-pytest-9.0.2.pyc +0 -0
  112. package/tools/todo-tool/tests/__pycache__/test_api.cpython-311-pytest-9.0.3.pyc +0 -0
  113. package/tools/todo-tool/tests/__pycache__/test_api.cpython-314-pytest-9.0.2.pyc +0 -0
  114. package/tools/todo-tool/tests/__pycache__/test_cli.cpython-311-pytest-9.0.2.pyc +0 -0
  115. package/tools/todo-tool/tests/__pycache__/test_cli.cpython-311-pytest-9.0.3.pyc +0 -0
  116. package/tools/todo-tool/tests/__pycache__/test_cli.cpython-314-pytest-9.0.2.pyc +0 -0
  117. package/tools/todo-tool/tests/__pycache__/test_cli.cpython-314.pyc +0 -0
  118. package/tools/todo-tool/tests/__pycache__/test_db.cpython-311-pytest-9.0.2.pyc +0 -0
  119. package/tools/todo-tool/tests/__pycache__/test_db.cpython-311-pytest-9.0.3.pyc +0 -0
  120. package/tools/todo-tool/tests/__pycache__/test_db.cpython-314-pytest-9.0.2.pyc +0 -0
  121. package/tools/todo-tool/tests/__pycache__/test_models.cpython-311-pytest-9.0.2.pyc +0 -0
  122. package/tools/todo-tool/tests/__pycache__/test_models.cpython-311-pytest-9.0.3.pyc +0 -0
  123. package/tools/todo-tool/tests/__pycache__/test_models.cpython-314-pytest-9.0.2.pyc +0 -0
  124. package/tools/todo-tool/tests/__pycache__/test_parser.cpython-311-pytest-9.0.3.pyc +0 -0
  125. package/tools/todo-tool/tests/__pycache__/test_parser.cpython-314-pytest-9.0.2.pyc +0 -0
  126. package/tools/todo-tool/tests/__pycache__/test_projects_index.cpython-311-pytest-9.0.3.pyc +0 -0
  127. package/tools/todo-tool/tests/__pycache__/test_sync.cpython-311-pytest-9.0.3.pyc +0 -0
  128. package/tools/todo-tool/tests/__pycache__/test_sync.cpython-314-pytest-9.0.2.pyc +0 -0
  129. package/tools/todo-tool/tests/test_api.py +229 -0
  130. package/tools/todo-tool/tests/test_cli.py +214 -0
  131. package/tools/todo-tool/tests/test_db.py +214 -0
  132. package/tools/todo-tool/tests/test_models.py +24 -0
  133. package/tools/todo-tool/tests/test_parser.py +120 -0
  134. package/tools/todo-tool/tests/test_projects_index.py +185 -0
  135. package/tools/todo-tool/tests/test_sync.py +101 -0
  136. package/tools/todo-tool/todo/__init__.py +0 -0
  137. package/tools/todo-tool/todo/__pycache__/__init__.cpython-311.pyc +0 -0
  138. package/tools/todo-tool/todo/__pycache__/__init__.cpython-314.pyc +0 -0
  139. package/tools/todo-tool/todo/__pycache__/cli.cpython-311.pyc +0 -0
  140. package/tools/todo-tool/todo/__pycache__/cli.cpython-314.pyc +0 -0
  141. package/tools/todo-tool/todo/__pycache__/db.cpython-311.pyc +0 -0
  142. package/tools/todo-tool/todo/__pycache__/db.cpython-314.pyc +0 -0
  143. package/tools/todo-tool/todo/__pycache__/models.cpython-311.pyc +0 -0
  144. package/tools/todo-tool/todo/__pycache__/models.cpython-314.pyc +0 -0
  145. package/tools/todo-tool/todo/__pycache__/parser.cpython-311.pyc +0 -0
  146. package/tools/todo-tool/todo/__pycache__/parser.cpython-314.pyc +0 -0
  147. package/tools/todo-tool/todo/__pycache__/projects_index.cpython-311.pyc +0 -0
  148. package/tools/todo-tool/todo/__pycache__/projects_index.cpython-314.pyc +0 -0
  149. package/tools/todo-tool/todo/__pycache__/server.cpython-311.pyc +0 -0
  150. package/tools/todo-tool/todo/__pycache__/server.cpython-314.pyc +0 -0
  151. package/tools/todo-tool/todo/cli.py +208 -0
  152. package/tools/todo-tool/todo/db.py +289 -0
  153. package/tools/todo-tool/todo/models.py +41 -0
  154. package/tools/todo-tool/todo/parser.py +122 -0
  155. package/tools/todo-tool/todo/projects_index.py +152 -0
  156. package/tools/todo-tool/todo/server.py +98 -0
  157. package/tools/todo-tool/todo/todo_format.yaml +29 -0
  158. package/tools/todo-tool/todo-tool.sh +23 -0
  159. package/tools/todo-tool/tool.json +12 -0
  160. package/tools/todo-tool/zshrc.snippet +4 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.14.1] - 2026-06-14
11
+
12
+ ### Fixed
13
+ - `hub` 安装失败(`source_not_found`):新增 `hub.sh` 启动脚本,安装器现在能正确找到 hub 入口
14
+ - `hub` tool.json `extraPaths` 路径错误(`core/cli` 不在工具根目录),修正为 `["hub", "pyproject.toml"]`
15
+ - `hub` 卸载后遗留 `~/.hskill/tools/hub/` Python 源码目录,将其加入 `uninstallPaths`
16
+ - hskill 交互流程次级 fzf 页面(action/target/scope)缺少 preview 面板,视觉风格与主列表页不一致
17
+
18
+ ### Added
19
+ - `docs/how-to/use-hub.md`:hub 人用操作指南(安装、TUI、projects/tasks 常用命令)
20
+ - `docs/reference/hub-reference.md`:hub CLI 完整参考(供 agent 调用,含 JSON 输出格式)
21
+
22
+ ## [0.14.0] - 2026-06-14
23
+
24
+ ### Added
25
+ - `hub` tool(`tools` bundle):个人开发者 OS,整合项目管理、git 状态追踪、任务系统,替代 p-launch + todo-tool
26
+ - Phase 1:core 库(SQLite DB、projects CRUD、tasks CRUD)+ CLI(`hub project`、`hub task` 子命令,支持 `--json` 输出)
27
+ - Phase 2:三栏 Textual TUI(ProjectsPanel | GitPanel | TasksPanel),支持键盘导航、git fetch、任务增删改、项目切换联动
28
+ - 首次启动自动从 todo-tool DB 迁移数据(`migrate.py`)
29
+ - 62 个测试覆盖 core、CLI、TUI 各层
30
+
10
31
  ## [0.13.0] - 2026-06-12
11
32
 
12
33
  ### Fixed
package/bin/cli.js CHANGED
@@ -2,7 +2,7 @@
2
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
+ import { existsSync, writeFileSync, unlinkSync } from 'fs'
6
6
  import { createRequire } from 'module'
7
7
  import os from 'os'
8
8
  import path from 'path'
@@ -629,6 +629,8 @@ function selectTargetThenScope() {
629
629
  '--layout=reverse',
630
630
  '--border=rounded',
631
631
  '--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
632
+ '--preview=t=$(echo {} | awk \'{print $1}\'); if [ "$t" = "all" ]; then echo ""; echo " 安装到所有平台:"; echo ""; for p in claude cursor codex openclaw hermes opencode; do printf " ~/.%s/skills/\\n" "$p"; done; else p="${HOME}/.${t}/skills/"; echo ""; echo " 安装路径:"; echo ""; printf " \\033[36m%s\\033[0m\\n" "$p"; echo ""; if [ -d "$p" ]; then echo " ✓ 目录已存在"; else echo " · 将自动新建"; fi; fi',
633
+ '--preview-window=right:45%:wrap',
632
634
  ], {
633
635
  input: targetInput,
634
636
  encoding: 'utf8',
@@ -651,6 +653,8 @@ function selectTargetThenScope() {
651
653
  '--layout=reverse',
652
654
  '--border=rounded',
653
655
  '--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
656
+ '--preview=s=$(echo {} | awk \'{print $1}\'); if [ "$s" = "user" ]; then echo ""; echo " ~/.{target}/skills/"; echo ""; echo " 所有项目共享,适合常用 skill"; echo ""; echo " ✓ 推荐选项"; else echo ""; echo " .{target}/skills/"; echo ""; echo " 仅当前项目可见"; echo ""; echo " 适合项目专属 skill"; fi',
657
+ '--preview-window=right:45%:wrap',
654
658
  ], {
655
659
  input: 'user — 所有项目共享 (~/.{target}/skills/)\nproject — 仅当前项目 (.{target}/skills/)',
656
660
  encoding: 'utf8',
@@ -898,17 +902,30 @@ try {
898
902
  })
899
903
 
900
904
  if (anyInstalled) {
905
+ const actionPreviewFile = `/tmp/hskill-action-preview-${process.pid}.txt`
906
+ const actionPreviewLines = [
907
+ '',
908
+ ' 已选中:',
909
+ '',
910
+ ...skillItems.map(s => ` skill ${s.skillName}`),
911
+ ...toolItems.map(t => ` tool ${t.toolName}`),
912
+ ...hookItems.map(h => ` hook ${h.name}`),
913
+ ]
914
+ writeFileSync(actionPreviewFile, actionPreviewLines.join('\n'))
901
915
  const actionResult = spawnSync('fzf', [
902
916
  '--prompt= › ',
903
917
  '--header= Action · enter 确认 · esc 取消',
904
918
  '--layout=reverse',
905
919
  '--border=rounded',
906
920
  '--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
921
+ `--preview=cat ${actionPreviewFile}`,
922
+ '--preview-window=right:45%:wrap',
907
923
  ], {
908
924
  input: `install 安装 / 重新安装\nuninstall 卸载并清理文件`,
909
925
  encoding: 'utf8',
910
926
  stdio: ['pipe', 'pipe', 'inherit'],
911
927
  })
928
+ try { unlinkSync(actionPreviewFile) } catch { /* ignore */ }
912
929
  if (!actionResult.stdout.trim()) {
913
930
  console.log(chalk.dim(' · Cancelled'))
914
931
  break
@@ -964,6 +981,8 @@ try {
964
981
  '--layout=reverse',
965
982
  '--border=rounded',
966
983
  '--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
984
+ '--preview=s=$(echo {} | awk \'{print $1}\'); if [ "$s" = "user" ]; then echo ""; echo " ~/.claude/hooks/"; echo " ~/.codex/hooks/"; echo ""; echo " 所有项目共享"; else echo ""; echo " .claude/hooks/"; echo " .codex/hooks/"; echo ""; echo " 仅当前项目"; fi',
985
+ '--preview-window=right:45%:wrap',
967
986
  ], {
968
987
  input: `user — ~/.{claude,codex}/hooks/ (所有项目共享)\nproject — .{claude,codex}/hooks/ (仅当前项目)`,
969
988
  encoding: 'utf8',
@@ -983,6 +1002,8 @@ try {
983
1002
  '--layout=reverse',
984
1003
  '--border=rounded',
985
1004
  '--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
1005
+ '--preview=t=$(echo {} | awk \'{print $1}\'); if [ "$t" = "all" ]; then echo ""; echo " ~/.claude/hooks/"; echo " ~/.codex/hooks/"; else echo ""; printf " ~/.%s/hooks/\\n" "$t"; fi',
1006
+ '--preview-window=right:45%:wrap',
986
1007
  ], {
987
1008
  input: `claude — ~/.claude/hooks/\ncodex — ~/.codex/hooks/\nall — claude + codex`,
988
1009
  encoding: 'utf8',
@@ -1033,6 +1054,8 @@ try {
1033
1054
  '--header= 从哪里卸载 · tab 多选 · enter 确认 · esc 取消',
1034
1055
  '--layout=reverse', '--border=rounded',
1035
1056
  '--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
1057
+ '--preview=t=$(echo {} | awk \'{print $1}\'); if [ "$t" = "all" ]; then echo ""; echo " 从所有平台卸载:"; echo ""; for p in claude cursor codex openclaw hermes opencode; do printf " ~/.%s/skills/\\n" "$p"; done; else echo ""; printf " ~/.%s/skills/\\n" "$t"; fi',
1058
+ '--preview-window=right:45%:wrap',
1036
1059
  ], {
1037
1060
  input: targetChoices2.map(c => c.name).join('\n') + '\nall — all targets',
1038
1061
  encoding: 'utf8', stdio: ['pipe', 'pipe', 'inherit'],
@@ -1046,6 +1069,8 @@ try {
1046
1069
  '--header= 卸载范围 · enter 确认 · esc 取消',
1047
1070
  '--layout=reverse', '--border=rounded',
1048
1071
  '--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
1072
+ '--preview=s=$(echo {} | awk \'{print $1}\'); if [ "$s" = "user" ]; then echo ""; echo " 从 user 级卸载:"; echo " ~/.{target}/skills/"; else echo ""; echo " 从 project 级卸载:"; echo " .{target}/skills/"; fi',
1073
+ '--preview-window=right:45%:wrap',
1049
1074
  ], {
1050
1075
  input: 'user — 所有项目 (~/.{target}/skills/)\nproject — 仅当前项目 (.{target}/skills/)',
1051
1076
  encoding: 'utf8', stdio: ['pipe', 'pipe', 'inherit'],
@@ -1065,6 +1090,8 @@ try {
1065
1090
  '--header= Hook scope · enter 确认',
1066
1091
  '--layout=reverse', '--border=rounded',
1067
1092
  '--color=header:italic:dim,prompt:cyan,pointer:cyan,hl:cyan,hl+:cyan:bold',
1093
+ '--preview=s=$(echo {} | awk \'{print $1}\'); if [ "$s" = "user" ]; then echo ""; echo " 从 user 级卸载 hook:"; echo " ~/.claude/hooks/"; else echo ""; echo " 从 project 级卸载 hook:"; echo " .claude/hooks/"; fi',
1094
+ '--preview-window=right:45%:wrap',
1068
1095
  ], {
1069
1096
  input: `user — ~/.claude/hooks/\nproject — .claude/hooks/`,
1070
1097
  encoding: 'utf8', stdio: ['pipe', 'pipe', 'inherit'],
package/lib/installer.js CHANGED
@@ -146,6 +146,21 @@ export async function installTools(tools, targetDir, force = false) {
146
146
  console.error(chalk.dim(` · Installed ${toolName}.py`))
147
147
  }
148
148
 
149
+ // Copy extra paths (files or dirs) declared in tool.json `extraPaths`
150
+ // Each entry is a relative path from the tool source; copied to ~/.hskill/tools/{toolName}/
151
+ try {
152
+ const toolMeta = await fs.readJson(path.join(srcPath, 'tool.json'))
153
+ for (const rel of (toolMeta.extraPaths ?? [])) {
154
+ const src = path.join(srcPath, rel)
155
+ const dest = path.join(dataDir, toolName, rel)
156
+ if (await fs.pathExists(src)) {
157
+ await fs.ensureDir(path.dirname(dest))
158
+ await fs.copy(src, dest, { overwrite: true })
159
+ console.error(chalk.dim(` · Installed ${toolName}/${rel}`))
160
+ }
161
+ }
162
+ } catch { /* tool.json missing or no extraPaths — skip silently */ }
163
+
149
164
  installed.push(toolName)
150
165
  await _patchZshrc(srcPath, toolName)
151
166
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harveyz-skill",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "Skill manager for Claude Code, Cursor, and Codex",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,6 +41,8 @@
41
41
  "skills/meta/npm-release/",
42
42
  "skills/meta/opencode-runner/",
43
43
  "tools/p-launch/",
44
+ "tools/todo-tool/",
45
+ "tools/hub/",
44
46
  "hooks/check-similar-branch/"
45
47
  ],
46
48
  "engines": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: add-todo
3
- version: "4.0.0"
3
+ version: "4.5.0"
4
4
  user_invocable: true
5
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
6
  ---
@@ -88,6 +88,36 @@ description: "Add a new requirement, task, or feature request to any project's T
88
88
  todo project set-path [项目名] [本地路径]
89
89
  ```
90
90
 
91
+ ### 切换到 chore/todo 分支
92
+
93
+ 在写入前,在项目目录操作 git:
94
+
95
+ ```bash
96
+ cd {local_path}
97
+
98
+ # 1. 记录当前分支
99
+ ORIGINAL_BRANCH=$(git rev-parse --abbrev-ref HEAD)
100
+
101
+ # 2. 确定 staging 基准分支(用于创建 chore/todo 及事后合并目标)
102
+ if git show-ref --verify --quiet refs/heads/staging; then
103
+ BASE_BRANCH=staging
104
+ else
105
+ BASE_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null \
106
+ | sed 's@^refs/remotes/origin/@@' || echo "main")
107
+ fi
108
+
109
+ # 3. 切换到 chore/todo(不存在则从 BASE_BRANCH 创建)
110
+ if git show-ref --verify --quiet refs/heads/chore/todo; then
111
+ git checkout chore/todo
112
+ else
113
+ git checkout -b chore/todo "$BASE_BRANCH"
114
+ fi
115
+ ```
116
+
117
+ **非 git 仓库**:若 `local_path` 不在 git 仓库中,跳过此步骤,直接写入文件。
118
+
119
+ **BRANCH_GUARD hook**:执行 `git checkout -b chore/todo` 时,BRANCH_GUARD hook 可能会提示与其他分支语义相似。`chore/todo` 是永久追踪分支,不是功能分支,**直接确认新建,忽略相似分支警告**,继续执行后续步骤。
120
+
91
121
  ### 重复检查
92
122
 
93
123
  加载文件,扫描是否有语义重叠的已有条目。若有:
@@ -118,6 +148,25 @@ todo project set-path [项目名] [本地路径]
118
148
  ## ✅ 已完成
119
149
  ```
120
150
 
121
- 写入后确认:
151
+ ### 提交、合并到 staging、切回原分支
152
+
153
+ 写入后在项目目录执行:
154
+
155
+ ```bash
156
+ cd {local_path}
157
+
158
+ # 1. 在 chore/todo 上提交
159
+ git add TODO.md
160
+ git commit -m "todo: add [任务标题]"
161
+
162
+ # 2. 合并到 staging(或主分支)
163
+ git checkout "$BASE_BRANCH"
164
+ git merge --no-ff chore/todo -m "Merge chore/todo: add [任务标题]"
165
+
166
+ # 3. 切回原分支
167
+ git checkout "$ORIGINAL_BRANCH"
168
+ ```
169
+
170
+ 完成后确认:
122
171
 
123
- > "✅ 已将 **[任务标题]** 写入 `{local_path}/TODO.md`。"
172
+ > "✅ 已将 **[任务标题]** 写入 `{local_path}/TODO.md`,提交到 `chore/todo`,合并到 `$BASE_BRANCH`,当前回到 `$ORIGINAL_BRANCH`。"
@@ -2,7 +2,7 @@
2
2
  name: npm-release
3
3
  description: "Complete npm publish workflow for harveyz-skill: bump version, update CHANGELOG, create release branch, merge to staging then main, tag, and publish to npm. Use this skill whenever the user wants to release, publish, cut a version, bump version, ship to npm, or deploy a new package version."
4
4
  user_invocable: true
5
- version: "1.1.0"
5
+ version: "1.2.0"
6
6
  ---
7
7
 
8
8
  # npm-release
@@ -100,14 +100,26 @@ npm version <new-version> --no-git-tag-version
100
100
 
101
101
  ---
102
102
 
103
+ ## Step 3.5 — 同步 .npmignore 和 package.json files 字段
104
+
105
+ 运行生成脚本,确保 `.npmignore` 和 `package.json` 中的 `files` 字段与当前 `skills-index.json` 保持一致:
106
+
107
+ ```bash
108
+ node scripts/generate-npmignore.js
109
+ ```
110
+
111
+ 检查输出有无报错。此步骤必须在提交前完成,否则新增或重命名的 skill 路径不会被正确包含/排除。
112
+
113
+ ---
114
+
103
115
  ## Step 4 — 创建 release 分支并提交
104
116
 
105
117
  ```bash
106
118
  # 从当前 staging 切出 release 分支
107
119
  git checkout -b release/<new-version>
108
120
 
109
- # 只提交 CHANGELOGpackage.json、package-lock.json
110
- git add CHANGELOG.md package.json package-lock.json
121
+ # 提交 CHANGELOGpackage.json、package-lock.json 和 .npmignore
122
+ git add CHANGELOG.md package.json package-lock.json .npmignore
111
123
  git commit -m "chore(release): bump version to <new-version>"
112
124
  ```
113
125
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: youtube-learner
3
- version: "1.3.0"
3
+ version: "1.3.1"
4
4
  description: "Process a YouTube video using the vdl CLI: transcribe, generate article and summary. Triggers when the user provides a YouTube URL and wants to learn from, summarize, transcribe, or extract key points from the video — e.g. 'help me understand this talk', 'summarize this YouTube video', 'get the transcript', 'process this video', 'summarize it'."
5
5
  user_invocable: true
6
6
  ---
@@ -35,7 +35,7 @@ npm link
35
35
  | `url` | YouTube 链接 | 从用户消息提取 |
36
36
  | `--focus` | 关注点(影响摘要内容) | 若用户未提供,主动询问:"你最想从这个视频中了解什么?(例如:核心论点、技术细节、行动项)" |
37
37
  | `--mode` | 处理模式 | **必须询问用户**(见下方「模式选择」) |
38
- | `--lang` | 输出语言 | 默认 `zh-CN`;若用户用英文交流则用 `en` |
38
+ | `--lang` | 输出语言 | 默认 `zh-CN`;若用户用英文交流则用 `en`;必须将解析结果赋给 `<LANG>` 占位符 |
39
39
 
40
40
  ### 模式选择
41
41
 
@@ -64,9 +64,18 @@ npm link
64
64
 
65
65
  ## 执行命令
66
66
 
67
+ 将以下占位符全部替换后再执行:
68
+
69
+ | 占位符 | 替换为 |
70
+ |--------|--------|
71
+ | `<URL>` | YouTube 链接 |
72
+ | `<FOCUS>` | 用户填写的关注点 |
73
+ | `<MODE>` | `transcript` / `audio` / `media` / `full` |
74
+ | `<LANG>` | `zh-CN`(中文对话)或 `en`(英文对话) |
75
+
67
76
  ```bash
68
77
  cd /Users/harveyzhang96/Projects/Video-Learner && \
69
- vdl "<URL>" --focus "<FOCUS>" --mode <MODE> --lang zh-CN
78
+ vdl "<URL>" --focus "<FOCUS>" --mode <MODE> --lang <LANG>
70
79
  ```
71
80
 
72
81
  `vdl` 会自动:
package/skills-index.json CHANGED
@@ -1,11 +1,21 @@
1
1
  {
2
2
  "toolBundleMeta": {
3
- "shell-tools": "终端工具(p-launch 本地仓库管理器)"
3
+ "shell-tools": "终端工具(p-launch 本地仓库管理器 + todo-tool 任务管理器)",
4
+ "tools": "开发者 OS 工具(hub — 项目管理 + git 状态 + 任务跟踪)"
4
5
  },
5
6
  "tools": [
6
7
  {
7
8
  "name": "p-launch",
8
9
  "bundle": "shell-tools"
10
+ },
11
+ {
12
+ "name": "todo-tool",
13
+ "bundle": "shell-tools"
14
+ },
15
+ {
16
+ "name": "hub",
17
+ "path": "tools/hub",
18
+ "bundle": "tools"
9
19
  }
10
20
  ],
11
21
  "bundleMeta": {
File without changes
@@ -0,0 +1,24 @@
1
+ import sys
2
+
3
+
4
+ def main():
5
+ from hub.core.db import HubDB
6
+ from hub.core.migrate import needs_migration, run_migration
7
+
8
+ db = HubDB()
9
+ if needs_migration():
10
+ n = run_migration(db)
11
+ if n:
12
+ import typer
13
+ typer.echo(f"hub: migrated {n} tasks from todo-tool ✓")
14
+
15
+ if len(sys.argv) == 1:
16
+ from hub.tui.app import HubApp
17
+ HubApp().run()
18
+ return
19
+ from hub.cli import app
20
+ app()
21
+
22
+
23
+ if __name__ == "__main__":
24
+ main()
@@ -0,0 +1,14 @@
1
+ import json
2
+ import sys
3
+ import typer
4
+
5
+ from hub.cli import projects as proj_cmd
6
+ from hub.cli import tasks as task_cmd
7
+
8
+ app = typer.Typer(
9
+ name="hub",
10
+ no_args_is_help=False,
11
+ add_completion=False,
12
+ )
13
+ app.add_typer(proj_cmd.app, name="projects")
14
+ app.add_typer(task_cmd.app, name="tasks")
@@ -0,0 +1,100 @@
1
+ import json
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ import typer
8
+
9
+ from hub.core.db import HubDB
10
+ from hub.core import projects as proj
11
+
12
+ app = typer.Typer(no_args_is_help=True)
13
+
14
+ _DEFAULT_MD = Path.home() / ".hskill" / "public" / "PROJECTS.md"
15
+
16
+
17
+ def _md_path() -> Path:
18
+ if env := os.environ.get("HUB_MD_PATH"):
19
+ return Path(env)
20
+ return _DEFAULT_MD
21
+
22
+
23
+ def _out(data, json_out: bool) -> None:
24
+ if json_out:
25
+ print(json.dumps({"ok": True, "data": data}))
26
+
27
+
28
+ def _err(msg: str, json_out: bool) -> None:
29
+ if json_out:
30
+ print(json.dumps({"ok": False, "error": msg}))
31
+ else:
32
+ typer.echo(f"Error: {msg}", err=True)
33
+ raise SystemExit(1)
34
+
35
+
36
+ @app.command("list")
37
+ def projects_list(json_out: bool = typer.Option(False, "--json")):
38
+ """List all registered projects."""
39
+ db = HubDB()
40
+ projects = proj.list_projects(db)
41
+ if json_out:
42
+ _out(projects, json_out)
43
+ else:
44
+ if not projects:
45
+ typer.echo("No projects. Use: hub projects add <name> --path <path>")
46
+ return
47
+ for p in projects:
48
+ typer.echo(f" {p['name']:<24} {p['path'] or ''}")
49
+ if p.get("description"):
50
+ typer.echo(f" {p['description']}")
51
+
52
+
53
+ @app.command("add")
54
+ def projects_add(
55
+ name: str = typer.Argument(..., help="Project name (GitHub repo name)"),
56
+ path: str = typer.Option("", "--path", help="Local directory path"),
57
+ description: str = typer.Option("", "--desc", help="Short description"),
58
+ json_out: bool = typer.Option(False, "--json"),
59
+ ):
60
+ """Register or update a project."""
61
+ db = HubDB()
62
+ result = proj.add_project(db, name, path=path, description=description, md_path=_md_path())
63
+ if json_out:
64
+ _out(result, json_out)
65
+ else:
66
+ typer.echo(f"✓ {name}")
67
+
68
+
69
+ @app.command("path")
70
+ def projects_path(
71
+ name: str = typer.Argument(...),
72
+ json_out: bool = typer.Option(False, "--json"),
73
+ ):
74
+ """Print the local path for a project (for shell cd / agent use)."""
75
+ db = HubDB()
76
+ p = proj.get_project_path(db, name)
77
+ if p is None:
78
+ _err(f"Project '{name}' not found", json_out)
79
+ if json_out:
80
+ _out(p, json_out)
81
+ else:
82
+ print(p)
83
+
84
+
85
+ @app.command("sync")
86
+ def projects_sync(json_out: bool = typer.Option(False, "--json")):
87
+ """Re-scan configured dirs and update PROJECTS.md. (Requires p-launch config.)"""
88
+ try:
89
+ import sys as _sys
90
+ _sys.path.insert(0, str(Path(__file__).parents[4] / "p-launch"))
91
+ from p_launch import read_project_dirs, collect_repos, sync_to_index
92
+ dirs = read_project_dirs()
93
+ repos = collect_repos(dirs)
94
+ sync_to_index(repos, _md_path())
95
+ if json_out:
96
+ _out({"scanned": len(repos)}, json_out)
97
+ else:
98
+ typer.echo(f"✓ Scanned {len(repos)} repos")
99
+ except ImportError:
100
+ _err("p-launch not installed; cannot auto-scan", json_out)
@@ -0,0 +1,114 @@
1
+ import json
2
+ import sys
3
+ from typing import Optional
4
+
5
+ import typer
6
+
7
+ from hub.core.db import HubDB
8
+ from hub.core import tasks as task
9
+
10
+ app = typer.Typer(no_args_is_help=True)
11
+
12
+
13
+ def _out(data, json_out: bool) -> None:
14
+ if json_out:
15
+ print(json.dumps({"ok": True, "data": data}))
16
+
17
+
18
+ def _err(msg: str, json_out: bool) -> None:
19
+ if json_out:
20
+ print(json.dumps({"ok": False, "error": msg}))
21
+ else:
22
+ typer.echo(f"Error: {msg}", err=True)
23
+ raise SystemExit(1)
24
+
25
+
26
+ @app.command("list")
27
+ def tasks_list(
28
+ project: Optional[str] = typer.Option(None, "--project", "-p"),
29
+ status: Optional[str] = typer.Option(None, "--status", "-s"),
30
+ priority: Optional[str] = typer.Option(None, "--priority", "-P"),
31
+ json_out: bool = typer.Option(False, "--json"),
32
+ ):
33
+ """List tasks, optionally filtered."""
34
+ db = HubDB()
35
+ tasks = task.list_tasks(db, project=project, status=status, priority=priority)
36
+ if json_out:
37
+ _out(tasks, json_out)
38
+ else:
39
+ if not tasks:
40
+ typer.echo("No tasks.")
41
+ return
42
+ for t in tasks:
43
+ check = "✓" if t["status"] == "done" else "○"
44
+ typer.echo(f" [{t['id']:>4}] {check} [{t['priority']}] {t['title']} ({t['project']})")
45
+
46
+
47
+ @app.command("add")
48
+ def tasks_add(
49
+ title: str = typer.Argument(...),
50
+ project: str = typer.Option(..., "--project", "-p"),
51
+ priority: str = typer.Option("P2", "--priority", "-P"),
52
+ json_out: bool = typer.Option(False, "--json"),
53
+ ):
54
+ """Add a new task."""
55
+ db = HubDB()
56
+ try:
57
+ t = task.add_task(db, title=title, project=project, priority=priority)
58
+ except ValueError as e:
59
+ _err(str(e), json_out)
60
+ if json_out:
61
+ _out(t, json_out)
62
+ else:
63
+ typer.echo(f"✓ [{t['id']}] {t['title']}")
64
+
65
+
66
+ @app.command("done")
67
+ def tasks_done(
68
+ task_id: int = typer.Argument(...),
69
+ json_out: bool = typer.Option(False, "--json"),
70
+ ):
71
+ """Mark a task as done."""
72
+ db = HubDB()
73
+ result = task.mark_done(db, task_id)
74
+ if result is None:
75
+ _err(f"Task {task_id} not found", json_out)
76
+ if json_out:
77
+ _out(result, json_out)
78
+ else:
79
+ typer.echo(f"✓ done: {result['title']}")
80
+
81
+
82
+ @app.command("update")
83
+ def tasks_update(
84
+ task_id: int = typer.Argument(...),
85
+ title: Optional[str] = typer.Option(None, "--title"),
86
+ priority: Optional[str] = typer.Option(None, "--priority"),
87
+ status: Optional[str] = typer.Option(None, "--status"),
88
+ json_out: bool = typer.Option(False, "--json"),
89
+ ):
90
+ """Update task fields."""
91
+ db = HubDB()
92
+ result = task.update_task(db, task_id, title=title, priority=priority, status=status)
93
+ if result is None:
94
+ _err(f"Task {task_id} not found", json_out)
95
+ if json_out:
96
+ _out(result, json_out)
97
+ else:
98
+ typer.echo(f"✓ updated [{task_id}]")
99
+
100
+
101
+ @app.command("rm")
102
+ def tasks_rm(
103
+ task_id: int = typer.Argument(...),
104
+ json_out: bool = typer.Option(False, "--json"),
105
+ ):
106
+ """Delete a task."""
107
+ db = HubDB()
108
+ deleted = task.delete_task(db, task_id)
109
+ if not deleted:
110
+ _err(f"Task {task_id} not found", json_out)
111
+ if json_out:
112
+ _out({"deleted": True}, json_out)
113
+ else:
114
+ typer.echo(f"✓ deleted [{task_id}]")
File without changes