create-dwy 0.16.1 → 0.17.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 (105) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/bin/index.js +23 -19
  3. package/package.json +1 -1
  4. package/src/cli-help.js +44 -0
  5. package/src/sync-all.js +303 -59
  6. package/src/sync-global-skills.js +167 -0
  7. package/src/sync.js +21 -11
  8. package/templates/ai-tools/CHANGELOG.md +134 -0
  9. package/templates/ai-tools/VERSION +1 -1
  10. package/templates/ai-tools/rules/Android/dwy-android-core.md +65 -1
  11. package/templates/ai-tools/rules/Python/dwy-python-core.md +47 -0
  12. package/templates/ai-tools/rules/Vue/dwy-vue-core.md +55 -0
  13. package/templates/ai-tools/rules/iOS/dwy-swift-core.md +46 -0
  14. package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-oss-commercial-license.md +128 -0
  15. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/SKILL.md +65 -0
  16. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/assets/appstore-submission.yaml +43 -0
  17. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/api-workflow.md +17 -0
  18. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/in-app-purchase-review.md +16 -0
  19. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/screenshot-workflow.md +16 -0
  20. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/scripts/appstore_config.sh +140 -0
  21. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/SKILL.md +11 -8
  22. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/android.md +2 -1
  23. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/backend-deploy.md +2 -1
  24. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/changelog.md +24 -4
  25. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/frontend-deploy.md +2 -1
  26. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/generic-release.md +2 -1
  27. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/git-tag.md +82 -0
  28. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/harmony.md +2 -1
  29. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/ios.md +3 -2
  30. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/monitor-notify.md +2 -2
  31. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/SKILL.md +1 -1
  32. package/templates/ai-tools/skills//345/237/272/347/241/200/345/272/223/dwy-eapi/references/tasks-integration-guide.md +2 -2
  33. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/README.md +57 -0
  34. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/SKILL.md +147 -0
  35. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/references/api.md +43 -0
  36. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/references/config.example.yaml +33 -0
  37. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/scripts/global_config.py +89 -0
  38. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/scripts/setup_config.py +175 -0
  39. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/scripts/synthesize.py +339 -0
  40. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/SKILL.md +127 -0
  41. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/assets/cover-style-master-v1.png +0 -0
  42. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/references/platform-copy-examples.md +37 -0
  43. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/references/platform-posting-times.md +193 -0
  44. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0005-ideation/SKILL.md +42 -0
  45. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0005-ideation/templates/topic-backlog.template.md +50 -0
  46. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0005-ideation/templates//351/200/211/351/242/230/346/261/240.template.md +16 -0
  47. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0010-product/SKILL.md +106 -0
  48. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0010-product/templates/channel-profile.template.md +52 -0
  49. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0010-product/templates//344/272/247/345/223/201/345/215/241.template.md +132 -0
  50. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/SKILL.md +60 -0
  51. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/scripts/build_facts_html.py +430 -0
  52. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/scripts/generate_html.py +1287 -0
  53. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/scripts/generate_report.py +189 -0
  54. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0030-facts/templates//344/272/213/345/256/236/350/265/204/346/226/231/345/272/223.template.md +55 -0
  55. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/SKILL.md +36 -0
  56. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates/README.md +10 -0
  57. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates//344/272/213/345/256/236.template.md +82 -0
  58. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates//345/215/225/351/233/206/346/226/207/346/241/243.template.md +175 -0
  59. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates//350/204/232/346/234/254.template.md +69 -0
  60. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0040-script/templates//351/252/250/346/236/266-v1.1.md +174 -0
  61. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0050-design/SKILL.md +55 -0
  62. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0060-assets/SKILL.md +29 -0
  63. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package/SKILL.md +42 -0
  64. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package/scripts/validate_shots.py +207 -0
  65. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package/shots.schema.json +186 -0
  66. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package//344/272/272/345/256/241/345/214/205.template.md +1 -0
  67. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package//344/272/272/345/256/241/350/257/264/346/230/216.md +15 -0
  68. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0070-package//346/210/220/347/211/207/345/214/205.template.md +49 -0
  69. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-0080-factory/SKILL.md +58 -0
  70. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-comic-kit/SKILL.md +424 -0
  71. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-comic-kit/evals.md +41 -0
  72. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-flow/CONTRACT.md +123 -0
  73. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-flow/SKILL.md +81 -0
  74. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-flow//346/211/247/350/241/214/346/270/205/345/215/225.template.md +60 -0
  75. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/SKILL.md +78 -0
  76. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/agents/openai.yaml +4 -0
  77. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/knowledge-town-paper-film-v1.json +80 -0
  78. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/remotion/AudioTracks.tsx +20 -0
  79. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/remotion/CaptionLayer.tsx +18 -0
  80. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/remotion/CueTimeline.ts +24 -0
  81. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/assets/remotion/KnowledgeTownComposition.tsx +21 -0
  82. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/references/cinematic-direction.md +73 -0
  83. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/references/sticker-asset-production.md +30 -0
  84. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/references/style-guide.md +43 -0
  85. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/scripts/build_cue_timeline.py +75 -0
  86. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/scripts/validate_cue_timeline.py +68 -0
  87. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/uc-media-knowledge-town/scripts/validate_style_preset.py +22 -0
  88. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/SKILL.md +0 -511
  89. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/checks-data.md +0 -126
  90. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/checks-network.md +0 -88
  91. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/checks-runtime.md +0 -195
  92. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/security-doc-search.md +0 -179
  93. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_base.sh +0 -39
  94. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_capacity.sh +0 -278
  95. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_db.sh +0 -147
  96. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_docker.sh +0 -230
  97. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_env.sh +0 -64
  98. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_https.sh +0 -60
  99. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_logs.sh +0 -209
  100. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_nginx.sh +0 -100
  101. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_resilience.sh +0 -157
  102. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_secrets.sh +0 -265
  103. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_services.sh +0 -94
  104. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_ssh.sh +0 -56
  105. package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/run_all.sh +0 -105
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # create-dwy
2
2
 
3
+ ## 0.17.1
4
+
5
+ ### Patch Changes
6
+
7
+ - `dwy --help` 正式列出 `dwy skills install` / `dwy skills --help`,不再标成隐藏命令。`dwy skills` 无参数时打印 skills 子命令说明。
8
+
9
+ ---
10
+
11
+ ## 0.17.0
12
+
13
+ ### Minor Changes
14
+
15
+ - `dwy` 交互同步开头增加「同步范围」:完整同步(默认)或仅 Skills。仅 Skills 不写、不删 Rules / Commands / Hooks。
16
+ - 最后增加「Skill 同步位置」:默认仅项目目录;可另选 Claude / Grok / Codex / Cursor / OpenCode 全局目录,并勾选要额外拷贝的 skill。只覆盖点名 skill,不删用户 home 里其它个人 skill。选择写入 `/.dwy/sync-state.json` 的 `skillScope` / `syncMode`。
17
+
18
+ ---
19
+
3
20
  ## 0.16.1
4
21
 
5
22
  ### Patch Changes
package/bin/index.js CHANGED
@@ -5,36 +5,40 @@ import { fileURLToPath } from 'url'
5
5
  import { dirname, resolve } from 'path'
6
6
  import { syncAll } from '../src/sync-all.js'
7
7
  import { installSkills } from '../src/skills-install.js'
8
+ import { buildHelpText, buildSkillsHelpText } from '../src/cli-help.js'
8
9
 
9
10
  const __dirname = dirname(fileURLToPath(import.meta.url))
10
11
  const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'))
11
12
  const argv = process.argv.slice(2)
12
13
 
14
+ /** 是否请求帮助(含 -h / --help / help) */
15
+ function wantsHelp(args) {
16
+ return args.includes('--help') || args.includes('-h') || args[0] === 'help'
17
+ }
18
+
13
19
  function printHelp() {
14
- const lines = [
15
- `dwy ${pkg.version}`,
16
- '',
17
- 'Usage:',
18
- ' dwy 运行交互式同步(自动更新全局外部 skill)',
19
- ' dwy --help 显示本说明',
20
- ' dwy -h 显示本说明',
21
- ' dwy --version 显示当前版本',
22
- ' dwy -V 显示当前版本',
23
- '',
24
- '以下为隐藏命令(向后兼容保留,日常无需手动调用):',
25
- ' dwy sync 同上的别名',
26
- ' dwy skills install 强制刷新全局外部 skill',
27
- '',
28
- ]
29
- console.log(lines.join('\n'))
20
+ console.log(buildHelpText(pkg.version))
21
+ }
22
+
23
+ function printSkillsHelp() {
24
+ console.log(buildSkillsHelpText())
30
25
  }
31
26
 
32
- if (argv.includes('--help') || argv.includes('-h') || argv[0] === 'help') {
27
+ if (argv[0] === 'skills') {
28
+ const rest = argv.slice(1)
29
+ if (rest.length === 0 || wantsHelp(rest) || rest[0] === 'help') {
30
+ printSkillsHelp()
31
+ } else if (rest[0] === 'install') {
32
+ await installSkills()
33
+ } else {
34
+ console.error(`未知命令: dwy skills ${rest.join(' ')}`)
35
+ printSkillsHelp()
36
+ process.exitCode = 1
37
+ }
38
+ } else if (wantsHelp(argv)) {
33
39
  printHelp()
34
40
  } else if (argv.includes('--version') || argv.includes('-V')) {
35
41
  console.log(pkg.version)
36
- } else if (argv[0] === 'skills' && argv[1] === 'install') {
37
- await installSkills()
38
42
  } else if (argv.length === 0 || argv[0] === 'sync') {
39
43
  await syncAll()
40
44
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-dwy",
3
- "version": "0.16.1",
3
+ "version": "0.17.1",
4
4
  "description": "项目脚手架与多平台 AI 配置同步工具",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,44 @@
1
+ /**
2
+ * dwy 命令行帮助文案。
3
+ * 已实现的入口必须出现在这里;禁止把可用命令藏进「隐藏」段。
4
+ */
5
+
6
+ /**
7
+ * 主帮助:`dwy --help`。
8
+ * version 来自 package.json,调用方传入,避免本模块读盘。
9
+ *
10
+ * @param {string} version
11
+ */
12
+ export function buildHelpText(version) {
13
+ return [
14
+ `dwy ${version}`,
15
+ '',
16
+ 'Usage:',
17
+ ' dwy 交互同步:选范围(完整/仅 Skills)、平台、skill 落点',
18
+ ' dwy sync 同上',
19
+ ' dwy skills install 强制刷新全局外部 skill 到 ~/.dwy/skills',
20
+ ' dwy skills --help 显示 skills 子命令',
21
+ ' dwy --help 显示本说明',
22
+ ' dwy -h 显示本说明',
23
+ ' dwy --version 显示当前版本',
24
+ ' dwy -V 显示当前版本',
25
+ '',
26
+ 'Skills:',
27
+ ' dwy skills install 按清单安装/覆盖更新 pm-skills、superpowers(一台机一份)',
28
+ '',
29
+ ].join('\n')
30
+ }
31
+
32
+ /**
33
+ * skills 子命令帮助:`dwy skills` / `dwy skills --help`。
34
+ */
35
+ export function buildSkillsHelpText() {
36
+ return [
37
+ 'dwy skills',
38
+ '',
39
+ 'Usage:',
40
+ ' dwy skills install 强制刷新全局外部 skill 到 ~/.dwy/skills',
41
+ ' dwy skills --help 显示本说明',
42
+ '',
43
+ ].join('\n')
44
+ }
package/src/sync-all.js CHANGED
@@ -1,7 +1,15 @@
1
1
  import fs from 'fs-extra'
2
2
  import path from 'path'
3
3
  import { confirm, isCancel } from '@clack/prompts'
4
- import { SEARCH_PLACEHOLDER, searchableMultiselect } from './searchable-select.js'
4
+ import { SEARCH_PLACEHOLDER, searchableMultiselect, searchableSelect } from './searchable-select.js'
5
+ import {
6
+ DEFAULT_SKILL_DESTINATIONS,
7
+ copySkillsToGlobalDirs,
8
+ copySkillsToProjectPlatforms,
9
+ listSkillDestOptions,
10
+ normalizeSkillScope,
11
+ resolveProjectSkillDir,
12
+ } from './sync-global-skills.js'
5
13
  import { chalk } from './utils.js'
6
14
  import {
7
15
  interactiveSelect,
@@ -55,6 +63,24 @@ const TYPE_LABELS = {
55
63
  hooks: 'hooks',
56
64
  }
57
65
 
66
+ /** 完整同步:skills / rules / commands / hooks 都走平台写入 */
67
+ export const SYNC_MODE_ALL = 'all'
68
+ /** 仅 Skills:不写、不删 rules / commands / hooks */
69
+ export const SYNC_MODE_SKILLS = 'skills'
70
+ export const DEFAULT_SYNC_MODE = SYNC_MODE_ALL
71
+
72
+ const SKILL_ONLY_CATEGORIES = [{ key: 'skills', label: 'Skills' }]
73
+
74
+ /**
75
+ * 规范化同步范围。未知值回退完整同步,避免误进仅 Skills 删不该删的东西。
76
+ *
77
+ * @param {unknown} raw
78
+ * @returns {'all' | 'skills'}
79
+ */
80
+ export function normalizeSyncMode(raw) {
81
+ return raw === SYNC_MODE_SKILLS ? SYNC_MODE_SKILLS : DEFAULT_SYNC_MODE
82
+ }
83
+
58
84
  function unionSets(...sets) {
59
85
  return new Set(sets.flatMap(set => [...set]))
60
86
  }
@@ -144,6 +170,132 @@ export async function buildPlatformDefaultsFromLocalState(projectDir) {
144
170
  return defaults.length > 0 ? defaults : ['claude', 'codex']
145
171
  }
146
172
 
173
+ /**
174
+ * 开头选同步范围。仅 Skills 时后面不再问 rules / commands / hooks。
175
+ *
176
+ * @param {'all' | 'skills'} initialValue
177
+ * @returns {Promise<'all' | 'skills' | null>}
178
+ */
179
+ async function promptSyncMode(initialValue) {
180
+ const result = await searchableSelect({
181
+ message: '选择同步范围',
182
+ options: [
183
+ {
184
+ value: SYNC_MODE_ALL,
185
+ label: '完整同步',
186
+ description: '同步 Skills / Rules / Commands / Hooks',
187
+ },
188
+ {
189
+ value: SYNC_MODE_SKILLS,
190
+ label: '仅 Skills',
191
+ description: '只处理 skill,不改动已有 Rules / Commands / Hooks',
192
+ },
193
+ ],
194
+ initialValue,
195
+ placeholder: SEARCH_PLACEHOLDER,
196
+ })
197
+ if (isCancel(result)) return null
198
+ return normalizeSyncMode(result)
199
+ }
200
+
201
+ /**
202
+ * 仅 Skills 模式:只写项目/全局 skill,并清理未选中的模板 skill。
203
+ * 禁止调用完整平台 sync,否则空 rules/commands 会删掉项目已有项。
204
+ *
205
+ * @param {{ projectDir: string, selectedPlatforms: string[], skills: Array<{ name: string, sourcePath: string }>, templateSkillNames: Set<string>, staleSkillNames: Set<string>, skillScope: { destinations: string[] } }} opts
206
+ */
207
+ async function syncSkillsOnly({
208
+ projectDir,
209
+ selectedPlatforms,
210
+ skills,
211
+ templateSkillNames,
212
+ staleSkillNames,
213
+ skillScope,
214
+ }) {
215
+ const writeProject = skillScope.destinations.includes('project')
216
+ if (!writeProject) return
217
+
218
+ const skillPlatforms = selectedPlatforms.filter(platform => resolveProjectSkillDir(projectDir, platform))
219
+ if (skillPlatforms.length === 0) {
220
+ console.log(chalk.yellow('已选平台不支持项目内 skills,跳过项目写入。'))
221
+ return
222
+ }
223
+
224
+ console.log(chalk.blue('\nSyncing skills only (rules / commands / hooks untouched)...\n'))
225
+ await copySkillsToProjectPlatforms({
226
+ projectDir,
227
+ platforms: skillPlatforms,
228
+ skills,
229
+ })
230
+
231
+ const selectedNames = new Set(skills.map(item => item.name))
232
+ const toRemove = new Set(
233
+ [...templateSkillNames, ...staleSkillNames].filter(name => !selectedNames.has(name)),
234
+ )
235
+ for (const platform of skillPlatforms) {
236
+ const destRoot = resolveProjectSkillDir(projectDir, platform)
237
+ const existingNames = await scanExisting(path.dirname(destRoot), 'skills')
238
+ await removeManagedItems(
239
+ destRoot,
240
+ existingNames,
241
+ toRemove,
242
+ path.relative(projectDir, destRoot),
243
+ )
244
+ }
245
+ }
246
+
247
+ /**
248
+ * 交互最后一步:选 skill 同步位置,默认仅项目。
249
+ * 勾了全局 dest 后再问哪些 skill 额外拷到全局;不删用户 home 里其它 skill。
250
+ *
251
+ * @param {{ selectedSkills: Array<{ name: string }>, syncState: { skillScope?: object } }} args
252
+ * @returns {Promise<{ destinations: string[], globalSkills: string[] } | null>}
253
+ */
254
+ async function promptSkillScope({ selectedSkills, syncState }) {
255
+ const previous = normalizeSkillScope(syncState.skillScope)
256
+ const destInitial = previous.destinations.length > 0
257
+ ? previous.destinations
258
+ : [...DEFAULT_SKILL_DESTINATIONS]
259
+
260
+ console.log(chalk.gray('\nSkill 默认同步到项目目录。勾选全局目录可把部分 skill 额外拷到本机 home。'))
261
+
262
+ const destResult = await searchableMultiselect({
263
+ message: '选择 skill 同步位置(默认项目)',
264
+ options: listSkillDestOptions(),
265
+ initialValues: destInitial,
266
+ required: true,
267
+ maxItems: 8,
268
+ placeholder: SEARCH_PLACEHOLDER,
269
+ })
270
+ if (isCancel(destResult)) return null
271
+
272
+ const destinations = destResult
273
+ const globalDests = destinations.filter(id => id !== 'project')
274
+ if (globalDests.length === 0 || selectedSkills.length === 0) {
275
+ return { destinations, globalSkills: [] }
276
+ }
277
+
278
+ const selectedNames = selectedSkills.map(item => item.name)
279
+ const globalInitial = previous.globalSkills.filter(name => selectedNames.includes(name))
280
+ const globalResult = await searchableMultiselect({
281
+ message: '哪些 skill 额外同步到全局?',
282
+ options: selectedSkills.map(item => ({
283
+ value: item.name,
284
+ label: item.name,
285
+ description: item.description,
286
+ })),
287
+ initialValues: globalInitial,
288
+ required: false,
289
+ placeholder: SEARCH_PLACEHOLDER,
290
+ })
291
+ if (isCancel(globalResult)) return null
292
+
293
+ return {
294
+ destinations,
295
+ globalSkills: globalResult,
296
+ }
297
+ }
298
+
147
299
  async function selectPlatforms(projectDir) {
148
300
  const initialValues = await buildPlatformDefaultsFromLocalState(projectDir)
149
301
 
@@ -197,6 +349,8 @@ async function loadSyncState(projectDir) {
197
349
  return {
198
350
  version: 1,
199
351
  platforms: {},
352
+ skillScope: normalizeSkillScope(undefined),
353
+ syncMode: DEFAULT_SYNC_MODE,
200
354
  }
201
355
  }
202
356
 
@@ -204,6 +358,8 @@ async function loadSyncState(projectDir) {
204
358
  return {
205
359
  version: 1,
206
360
  platforms: state.platforms || {},
361
+ skillScope: normalizeSkillScope(state.skillScope),
362
+ syncMode: normalizeSyncMode(state.syncMode),
207
363
  }
208
364
  }
209
365
 
@@ -328,6 +484,19 @@ async function promptStaleRemovals(staleEntries) {
328
484
  return approvals
329
485
  }
330
486
 
487
+ /**
488
+ * 仅 Skills 模式:陈旧项提示只保留 skills,避免问 rules/commands/hooks。
489
+ *
490
+ * @param {Record<string, Record<string, string[]>>} staleEntries
491
+ */
492
+ function retainSkillStaleEntries(staleEntries) {
493
+ const next = createEmptyRemovalState()
494
+ for (const [platform, types] of Object.entries(staleEntries)) {
495
+ if (types.skills?.length) next[platform] = { skills: types.skills }
496
+ }
497
+ return next
498
+ }
499
+
331
500
  function getPreservedStaleNames(staleEntries, approvedRemovals, platform, type) {
332
501
  const staleNames = staleEntries[platform]?.[type] || []
333
502
  const approvedNames = approvedRemovals[platform]?.[type] || new Set()
@@ -556,6 +725,12 @@ export async function syncAll({
556
725
  selected: selectedOverride,
557
726
  selectedPlatforms: selectedPlatformsOverride,
558
727
  staleRemovals: staleRemovalsOverride,
728
+ // 测试/脚本可注入;交互模式在平台选择之后再问
729
+ skillScope: skillScopeOverride,
730
+ // all = 完整同步;skills = 只写 skill,测试/脚本可注入
731
+ syncMode: syncModeOverride,
732
+ // 全局 skill 写入的 home,测试注入假目录,禁止默认写真实 $HOME
733
+ homeDir,
559
734
  } = {}) {
560
735
  const sourceDir = sourceDirOverride || await resolveSourceDir()
561
736
  if (!await fs.pathExists(sourceDir)) {
@@ -605,7 +780,20 @@ export async function syncAll({
605
780
  console.log(chalk.gray('首次同步,进入交互式选择...\n'))
606
781
  }
607
782
 
608
- const selected = selectedOverride || await interactiveSelect(scans, selectionDefaults)
783
+ const skipInteractive = !!(selectedOverride || selectedPlatformsOverride)
784
+ const syncMode = syncModeOverride
785
+ ? normalizeSyncMode(syncModeOverride)
786
+ : (skipInteractive
787
+ ? normalizeSyncMode(syncState.syncMode)
788
+ : await promptSyncMode(normalizeSyncMode(syncState.syncMode)))
789
+ if (syncMode === null) {
790
+ console.log(chalk.yellow('\n已取消同步。'))
791
+ return
792
+ }
793
+
794
+ const selected = selectedOverride || await interactiveSelect(scans, selectionDefaults, {
795
+ categories: syncMode === SYNC_MODE_SKILLS ? SKILL_ONLY_CATEGORIES : undefined,
796
+ })
609
797
  if (selected === null) {
610
798
  console.log(chalk.yellow('\n已取消同步。'))
611
799
  return
@@ -617,7 +805,10 @@ export async function syncAll({
617
805
  return
618
806
  }
619
807
 
620
- const staleEntries = await collectStaleEntries(projectDir, scans, syncState, selectedPlatforms)
808
+ const staleEntriesRaw = await collectStaleEntries(projectDir, scans, syncState, selectedPlatforms)
809
+ const staleEntries = syncMode === SYNC_MODE_SKILLS
810
+ ? retainSkillStaleEntries(staleEntriesRaw)
811
+ : staleEntriesRaw
621
812
  const approvedStaleRemovals = staleRemovalsOverride
622
813
  ? normalizeRemovalInput(staleRemovalsOverride)
623
814
  : await promptStaleRemovals(staleEntries)
@@ -626,79 +817,132 @@ export async function syncAll({
626
817
  return
627
818
  }
628
819
 
820
+ const skillScope = skillScopeOverride
821
+ ? normalizeSkillScope(skillScopeOverride)
822
+ : (selectedOverride
823
+ ? normalizeSkillScope(syncState.skillScope)
824
+ : await promptSkillScope({ selectedSkills: selected.skills || [], syncState }))
825
+ if (skillScope === null) {
826
+ console.log(chalk.yellow('\n已取消同步。'))
827
+ return
828
+ }
829
+
830
+ const selectedSkillNames = new Set((selected.skills || []).map(item => item.name))
831
+ const skillsToWrite = scans.skills.filter(item => selectedSkillNames.has(item.name))
629
832
  let syncedAnySupportedPlatform = false
630
833
 
631
- if (selectedPlatforms.includes('claude')) {
632
- syncedAnySupportedPlatform = true
633
- console.log(chalk.blue('\nSyncing Claude Code configuration...\n'))
634
- await syncClaude({
635
- sourceDir,
834
+ if (syncMode === SYNC_MODE_SKILLS) {
835
+ const staleSkillNames = new Set()
836
+ for (const platform of selectedPlatforms) {
837
+ for (const name of approvedStaleRemovals[platform]?.skills || []) staleSkillNames.add(name)
838
+ }
839
+ await syncSkillsOnly({
636
840
  projectDir,
637
- selected,
638
- staleRemovals: approvedStaleRemovals.claude,
841
+ selectedPlatforms,
842
+ skills: skillsToWrite,
843
+ templateSkillNames: new Set(scans.skills.map(item => item.name)),
844
+ staleSkillNames,
845
+ skillScope,
639
846
  })
640
- }
847
+ syncedAnySupportedPlatform = selectedPlatforms.some(platform => resolveProjectSkillDir(projectDir, platform))
848
+ || skillScope.destinations.some(id => id !== 'project')
849
+ } else {
850
+ if (selectedPlatforms.includes('claude')) {
851
+ syncedAnySupportedPlatform = true
852
+ console.log(chalk.blue('\nSyncing Claude Code configuration...\n'))
853
+ await syncClaude({
854
+ sourceDir,
855
+ projectDir,
856
+ selected,
857
+ staleRemovals: approvedStaleRemovals.claude,
858
+ })
859
+ }
641
860
 
642
- if (selectedPlatforms.includes('codex')) {
643
- syncedAnySupportedPlatform = true
644
- console.log(chalk.blue('\nSyncing Codex configuration...\n'))
645
- await syncCodex({
646
- sourceDir,
647
- projectDir,
648
- selected,
649
- staleRemovals: approvedStaleRemovals.codex,
650
- preserveMissingRules: getPreservedStaleNames(staleEntries, approvedStaleRemovals, 'codex', 'rules'),
651
- })
652
- }
861
+ if (selectedPlatforms.includes('codex')) {
862
+ syncedAnySupportedPlatform = true
863
+ console.log(chalk.blue('\nSyncing Codex configuration...\n'))
864
+ await syncCodex({
865
+ sourceDir,
866
+ projectDir,
867
+ selected,
868
+ staleRemovals: approvedStaleRemovals.codex,
869
+ preserveMissingRules: getPreservedStaleNames(staleEntries, approvedStaleRemovals, 'codex', 'rules'),
870
+ })
871
+ }
653
872
 
654
- if (selectedPlatforms.includes('cursor')) {
655
- syncedAnySupportedPlatform = true
656
- await syncCursor({
657
- sourceDir,
658
- projectDir,
659
- selected,
660
- staleRemovals: approvedStaleRemovals.cursor,
661
- })
662
- }
873
+ if (selectedPlatforms.includes('cursor')) {
874
+ syncedAnySupportedPlatform = true
875
+ await syncCursor({
876
+ sourceDir,
877
+ projectDir,
878
+ selected,
879
+ staleRemovals: approvedStaleRemovals.cursor,
880
+ })
881
+ }
663
882
 
664
- if (selectedPlatforms.includes('opencode')) {
665
- syncedAnySupportedPlatform = true
666
- await syncOpenCode({
667
- sourceDir,
668
- projectDir,
669
- selected,
670
- staleRemovals: approvedStaleRemovals.opencode,
671
- preserveMissingRules: getPreservedStaleNames(staleEntries, approvedStaleRemovals, 'opencode', 'rules'),
672
- })
673
- }
883
+ if (selectedPlatforms.includes('opencode')) {
884
+ syncedAnySupportedPlatform = true
885
+ await syncOpenCode({
886
+ sourceDir,
887
+ projectDir,
888
+ selected,
889
+ staleRemovals: approvedStaleRemovals.opencode,
890
+ preserveMissingRules: getPreservedStaleNames(staleEntries, approvedStaleRemovals, 'opencode', 'rules'),
891
+ })
892
+ }
674
893
 
675
- if (!selectedPlatforms.includes('codex') && !selectedPlatforms.includes('opencode')) {
676
- await clearProjectAgentsMd(projectDir)
677
- }
894
+ if (!selectedPlatforms.includes('codex') && !selectedPlatforms.includes('opencode')) {
895
+ await clearProjectAgentsMd(projectDir)
896
+ }
678
897
 
679
- if (!selectedPlatforms.includes('claude')) {
680
- await cleanupClaudePlatform(projectDir, scans, claudeManagedHookNames, baseline)
681
- }
898
+ if (!selectedPlatforms.includes('claude')) {
899
+ await cleanupClaudePlatform(projectDir, scans, claudeManagedHookNames, baseline)
900
+ }
682
901
 
683
- if (!selectedPlatforms.includes('cursor')) {
684
- await cleanupCursorPlatform(projectDir, scans, baseline)
685
- }
902
+ if (!selectedPlatforms.includes('cursor')) {
903
+ await cleanupCursorPlatform(projectDir, scans, baseline)
904
+ }
686
905
 
687
- if (!selectedPlatforms.includes('codex')) {
688
- await cleanupCodexPlatform(projectDir, scans, codexManagedHookNames)
906
+ if (!selectedPlatforms.includes('codex')) {
907
+ await cleanupCodexPlatform(projectDir, scans, codexManagedHookNames)
908
+ }
909
+
910
+ if (!selectedPlatforms.includes('opencode')) {
911
+ await cleanupOpenCodePlatform(projectDir, scans)
912
+ }
689
913
  }
690
914
 
691
- if (!selectedPlatforms.includes('opencode')) {
692
- await cleanupOpenCodePlatform(projectDir, scans)
915
+ const globalSkillNameSet = new Set(skillScope.globalSkills)
916
+ const globalSkills = scans.skills.filter(item => globalSkillNameSet.has(item.name))
917
+ if (globalSkills.length > 0 && skillScope.destinations.some(id => id !== 'project')) {
918
+ console.log(chalk.blue('\nSyncing selected skills to global directories...\n'))
919
+ await copySkillsToGlobalDirs({
920
+ skills: globalSkills,
921
+ destIds: skillScope.destinations,
922
+ homeDir,
923
+ })
693
924
  }
694
925
 
695
- for (const platform of Object.keys(PLATFORM_SYNC_TYPES)) {
696
- if (selectedPlatforms.includes(platform)) {
697
- syncState.platforms[platform] = buildNextPlatformState(selected, staleEntries, approvedStaleRemovals, platform)
698
- } else {
699
- syncState.platforms[platform] = createEmptyPlatformState()
926
+ if (syncMode === SYNC_MODE_SKILLS) {
927
+ for (const platform of selectedPlatforms) {
928
+ const previous = syncState.platforms[platform] || createEmptyPlatformState()
929
+ const skillsOnlySelected = { skills: selected.skills || [], rules: [], commands: [], hooks: [] }
930
+ syncState.platforms[platform] = {
931
+ ...previous,
932
+ skills: buildNextPlatformState(skillsOnlySelected, staleEntries, approvedStaleRemovals, platform).skills,
933
+ }
934
+ }
935
+ } else {
936
+ for (const platform of Object.keys(PLATFORM_SYNC_TYPES)) {
937
+ if (selectedPlatforms.includes(platform)) {
938
+ syncState.platforms[platform] = buildNextPlatformState(selected, staleEntries, approvedStaleRemovals, platform)
939
+ } else {
940
+ syncState.platforms[platform] = createEmptyPlatformState()
941
+ }
700
942
  }
701
943
  }
944
+ syncState.skillScope = skillScope
945
+ syncState.syncMode = syncMode
702
946
  await saveSyncState(projectDir, syncState)
703
947
 
704
948
  if (!syncedAnySupportedPlatform) {