create-dwy 0.22.0 → 0.23.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 +12 -0
- package/package.json +1 -1
- package/src/cli-help.js +20 -11
- package/src/skill-store.js +15 -0
- package/src/skills-install.js +47 -11
- package/src/sync-all.js +167 -658
- package/src/sync-codex.js +38 -25
- package/src/sync-global-skills.js +4 -5
- package/src/sync-opencode.js +2 -2
- package/src/sync-plan.js +278 -0
- package/src/sync.js +25 -19
- package/src/tab-multiselect.js +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# create-dwy
|
|
2
2
|
|
|
3
|
+
## [0.23.0] - 2026-11-02
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 同步改为「范围 → 平台 → 本次处理内容 → 逐项选择 → 清单确认」。默认只选 Skills;项目 Rules、全局通用规范和 Hooks 必须显式选择,未选择的类型及其记录保持原样。全局不提供 Cursor。
|
|
8
|
+
- 默认勾选只读取当前范围及所选平台。项目 Skills 全部适用平台已有有效全局入口时显示“全局已启用”并禁用重复勾选;部分平台覆盖时只为缺少入口的平台安装。
|
|
9
|
+
- 确认页和执行共用操作清单,列出共享副本、入口、旧项清理、配置和同步记录。项目去重只备份或移除项目入口,不改写全局提供者;用户主目录及其别名不能作为项目范围同步。
|
|
10
|
+
- `sync` / `migrate` 只准备缺失或损坏的共享副本,已有内容由 `refresh` / `upgrade` 刷新。产品流程的外部依赖补装也纳入确认清单,只拉取缺失内容并保留已有副本。
|
|
11
|
+
- 全局 Codex Hooks 遵循自定义 `CODEX_HOME`,命令路径正确引用空格和单引号;同步保留个人配置,补充平台兼容、取消无写入和真实终端交互回归。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
3
15
|
## [0.22.0] - 2026-11-02
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/package.json
CHANGED
package/src/cli-help.js
CHANGED
|
@@ -14,7 +14,7 @@ export function buildHelpText(version) {
|
|
|
14
14
|
`dwy ${version}`,
|
|
15
15
|
'',
|
|
16
16
|
'用法:',
|
|
17
|
-
' dwy
|
|
17
|
+
' dwy 选择范围、平台和处理内容,预览清单后同步',
|
|
18
18
|
' dwy sync 同上',
|
|
19
19
|
' dwy upgrade 升级到 npm 最新正式版,并刷新共享 Skills',
|
|
20
20
|
' dwy refresh 用当前 CLI 模板刷新已安装的共享 Skills',
|
|
@@ -23,20 +23,23 @@ export function buildHelpText(version) {
|
|
|
23
23
|
' dwy --version 显示版本',
|
|
24
24
|
'',
|
|
25
25
|
'同步步骤:',
|
|
26
|
-
' 1.
|
|
27
|
-
' 2.
|
|
28
|
-
' 3.
|
|
29
|
-
'
|
|
30
|
-
'
|
|
26
|
+
' 1. 选择同步范围:当前项目或全局。',
|
|
27
|
+
' 2. 选择该范围支持的平台;全局不提供 Cursor。',
|
|
28
|
+
' 3. 选择本次处理内容,默认只选 Skills;未选择的类型保持原样。',
|
|
29
|
+
' 项目:Skills、项目 Rules、Hooks;全局:Skills、全局通用规范、Hooks。',
|
|
30
|
+
' 内容选项按平台能力过滤。Hooks 选中类型后按平台自动处理。',
|
|
31
|
+
' 4. 逐项选择 Skills / 项目 Rules;全局通用规范显示摘要。',
|
|
32
|
+
' 5. 查看写入、备份和清理清单,确认后执行;取消不写文件。',
|
|
33
|
+
' Commands 不参与同步。',
|
|
31
34
|
'',
|
|
32
35
|
'交互按键:',
|
|
33
36
|
' Skills / Rules:←/→ 切分类,↑/↓ 移动,Space 勾选,a 全选/清空当前分类。',
|
|
34
|
-
'
|
|
37
|
+
' 范围 / 平台 / 内容:↑/↓ 移动;多选用 Space 勾选,可输入关键字筛选。',
|
|
35
38
|
' Enter 确认当前步骤,Esc 或 Ctrl+C 取消。',
|
|
36
39
|
'',
|
|
37
40
|
'常见用法:',
|
|
38
|
-
' 同步当前项目:cd your-project 后执行 dwy
|
|
39
|
-
'
|
|
41
|
+
' 同步当前项目:cd your-project 后执行 dwy,选择「当前项目」及本次处理内容。',
|
|
42
|
+
' 同步全局:执行 dwy,选择「全局」及 Skills、全局通用规范或 Hooks。',
|
|
40
43
|
' 更新或调整配置:再次执行 dwy sync,检查勾选项,再确认同步。',
|
|
41
44
|
' 更新 CLI:dwy upgrade(全局安装时可用;更新后执行 dwy --version 查看版本)。',
|
|
42
45
|
' 迁移老项目:cd your-project 后执行 dwy migrate,无需重新勾选。',
|
|
@@ -49,9 +52,15 @@ export function buildHelpText(version) {
|
|
|
49
52
|
' OpenCode:全局 ~/.config/opencode/AGENTS.md;项目根 AGENTS.md;当前不支持 Hooks。',
|
|
50
53
|
' Cursor:仅项目 Rules;全局规则在 Cursor 设置中管理。',
|
|
51
54
|
'',
|
|
52
|
-
'
|
|
53
|
-
'
|
|
55
|
+
'同步仅处理当前范围、所选平台和内容;未选择的类型及其记录保持原样。',
|
|
56
|
+
'用户主目录不能作为项目范围同步,请选择全局或进入项目目录。',
|
|
57
|
+
'取消已有勾选会在确认后移除当前范围的托管入口;不勾选整个类型则不处理。',
|
|
54
58
|
'Skills 副本集中在 ~/.dwy/skills;项目和全局使用目录链接,同平台全局入口优先。',
|
|
59
|
+
'项目 Skills 全部适用平台均有全局入口时显示「全局已启用」,不能重复勾选。',
|
|
60
|
+
'部分平台已有全局入口时仍可选,只为缺少全局入口的平台安装项目链接。',
|
|
61
|
+
'项目同步只清理项目重复入口,不改全局提供者;真实目录先备份。',
|
|
62
|
+
'sync / migrate 仅准备缺失或损坏副本,不刷新已有共享内容;refresh / upgrade 才刷新。',
|
|
63
|
+
'产品流程缺失的外部依赖也列入清单,确认后联网补齐,已有内容保留。',
|
|
55
64
|
'旧真实目录备份到同步根的 .dwy/skill-backups;个人 Skills、Rules、Hooks 不参与 migrate。',
|
|
56
65
|
'旧项目首次需执行 dwy migrate;迁移后无需逐项目刷新 Skills。',
|
|
57
66
|
'Rules / Hooks 的模板更新仍需执行 dwy sync;已有会话缓存的 Skill 可能需要重新加载。',
|
package/src/skill-store.js
CHANGED
|
@@ -20,6 +20,21 @@ async function readInstalledNames(homeDir) {
|
|
|
20
20
|
return names
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** 仅为缺失或损坏的 Skill 准备共享副本;已有有效内容只登记,不随入口同步刷新。 */
|
|
24
|
+
export async function ensureSharedSkills({ skills, homeDir = os.homedir() }) {
|
|
25
|
+
const sources = []
|
|
26
|
+
for (const skill of skills) {
|
|
27
|
+
validateName(skill.name)
|
|
28
|
+
const cached = path.join(homeDir, '.dwy', 'skills', skill.name)
|
|
29
|
+
const stat = await fs.stat(path.join(cached, 'SKILL.md')).catch(error => {
|
|
30
|
+
if (['ENOENT', 'ENOTDIR', 'ELOOP'].includes(error.code)) return null
|
|
31
|
+
throw error
|
|
32
|
+
})
|
|
33
|
+
sources.push(stat?.isFile() ? { ...skill, sourcePath: cached } : skill)
|
|
34
|
+
}
|
|
35
|
+
return prepareSharedSkills({ skills: sources, homeDir })
|
|
36
|
+
}
|
|
37
|
+
|
|
23
38
|
/**
|
|
24
39
|
* 将包内 Skills 准备到稳定用户目录,返回指向共享副本的条目。
|
|
25
40
|
* 同一用户的刷新互斥;完整副本准备失败时不替换旧内容。外部 Skills 缓存不纳入本模块清单。
|
package/src/skills-install.js
CHANGED
|
@@ -71,24 +71,54 @@ async function shallowCloneTag(repoUrl, tag, destDir) {
|
|
|
71
71
|
await runGit(['clone', '--depth', '1', '--branch', tag, repoUrl, destDir])
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
async function validExternalSkill(dest) {
|
|
75
|
+
const stat = await fs.stat(path.join(dest, 'SKILL.md')).catch(error => {
|
|
76
|
+
if (['ENOENT', 'ENOTDIR', 'ELOOP'].includes(error.code)) return null
|
|
77
|
+
throw error
|
|
78
|
+
})
|
|
79
|
+
return Boolean(stat?.isFile())
|
|
80
|
+
}
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
async function missingExternalSources(targetDir) {
|
|
83
|
+
const sources = []
|
|
84
|
+
for (const [name, source] of Object.entries(SKILL_SOURCES)) {
|
|
85
|
+
const skills = {}
|
|
86
|
+
for (const [skill, repoPath] of Object.entries(source.skills)) {
|
|
87
|
+
if (!await validExternalSkill(path.join(targetDir, skill))) skills[skill] = repoPath
|
|
88
|
+
}
|
|
89
|
+
if (Object.keys(skills).length) sources.push([name, { ...source, skills }])
|
|
90
|
+
}
|
|
91
|
+
return sources
|
|
92
|
+
}
|
|
81
93
|
|
|
82
|
-
|
|
94
|
+
/** 只读列出产品流程缺失的外部依赖;确认后再联网补齐,已有副本不刷新。 */
|
|
95
|
+
export async function planMissingExternalSkills(targetDir) {
|
|
96
|
+
const sources = await missingExternalSources(targetDir)
|
|
97
|
+
const paths = sources.flatMap(([, source]) => Object.keys(source.skills).map(name => path.join(targetDir, name)))
|
|
98
|
+
for (const [, source] of sources) {
|
|
99
|
+
const license = path.join(targetDir, source.licenseDest)
|
|
100
|
+
if (!await fs.pathExists(license)) paths.push(license)
|
|
101
|
+
}
|
|
102
|
+
if (sources.length) paths.push(path.join(targetDir, 'VERSIONS.json'))
|
|
103
|
+
return { paths, run: () => installSkillSources(sources, targetDir, true) }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function installSkillSources(sources, targetDir, missingOnly) {
|
|
107
|
+
if (sources.length === 0) return
|
|
108
|
+
const versionsFile = path.join(targetDir, 'VERSIONS.json')
|
|
109
|
+
const previous = missingOnly && await fs.pathExists(versionsFile) ? await fs.readJson(versionsFile) : {}
|
|
110
|
+
const versions = { ...previous.versions }
|
|
111
|
+
await fs.ensureDir(targetDir)
|
|
112
|
+
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'dwy-skills-'))
|
|
83
113
|
try {
|
|
84
|
-
for (const [sourceName, src] of
|
|
114
|
+
for (const [sourceName, src] of sources) {
|
|
85
115
|
console.log(chalk.gray(`拉取 ${sourceName} @ ${src.tag} ...`))
|
|
86
116
|
const repoDir = path.join(tmpRoot, sourceName)
|
|
87
117
|
await shallowCloneTag(src.repo, src.tag, repoDir)
|
|
88
118
|
|
|
89
119
|
// 搬 LICENSE(MIT 要求保留原版权声明)
|
|
90
120
|
const licenseSrc = path.join(repoDir, src.licenseFile)
|
|
91
|
-
if (await fs.pathExists(licenseSrc)) {
|
|
121
|
+
if (await fs.pathExists(licenseSrc) && (!missingOnly || !await fs.pathExists(path.join(targetDir, src.licenseDest)))) {
|
|
92
122
|
await fs.copy(licenseSrc, path.join(targetDir, src.licenseDest), { overwrite: true })
|
|
93
123
|
}
|
|
94
124
|
|
|
@@ -96,6 +126,7 @@ export async function installSkills() {
|
|
|
96
126
|
for (const [skillName, repoPath] of Object.entries(src.skills)) {
|
|
97
127
|
const from = path.join(repoDir, repoPath)
|
|
98
128
|
const to = path.join(targetDir, skillName)
|
|
129
|
+
if (missingOnly && await validExternalSkill(to)) continue
|
|
99
130
|
if (!await fs.pathExists(from)) {
|
|
100
131
|
throw new Error(`${sourceName} @ ${src.tag} 内未找到 ${repoPath}(仓库结构可能变了)`)
|
|
101
132
|
}
|
|
@@ -109,8 +140,8 @@ export async function installSkills() {
|
|
|
109
140
|
// 写版本清单 + 清单指纹,供查看与后续更新比对。
|
|
110
141
|
// manifest_hash 变化(cli 加了 skill / 改了 tag)→ 下次 ensureSkillsInstalled 触发重装。
|
|
111
142
|
await fs.writeJson(
|
|
112
|
-
|
|
113
|
-
{ manifest_hash: computeManifestHash(), versions },
|
|
143
|
+
versionsFile,
|
|
144
|
+
{ manifest_hash: missingOnly ? previous.manifest_hash : computeManifestHash(), versions },
|
|
114
145
|
{ spaces: 2 },
|
|
115
146
|
)
|
|
116
147
|
console.log(chalk.green(`\n✓ 外部 skill 已安装到 ${targetDir}`))
|
|
@@ -119,6 +150,11 @@ export async function installSkills() {
|
|
|
119
150
|
}
|
|
120
151
|
}
|
|
121
152
|
|
|
153
|
+
/** 兼容显式安装调用:更新固定清单中的全部外部 Skills。 */
|
|
154
|
+
export async function installSkills() {
|
|
155
|
+
return installSkillSources(Object.entries(SKILL_SOURCES), getGlobalSkillsDir(), false)
|
|
156
|
+
}
|
|
157
|
+
|
|
122
158
|
// 全局 skill 是否已就绪(VERSIONS.json 存在)。
|
|
123
159
|
export async function skillsInstalled() {
|
|
124
160
|
return fs.pathExists(path.join(getGlobalSkillsDir(), 'VERSIONS.json'))
|