dk-frontend-skills 2.1.2 → 3.0.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/package.json +11 -5
- package/scripts/build-skills.js +89 -0
- package/scripts/cli.js +126 -37
- package/scripts/copy-skills.js +20 -21
- package/scripts/core.js +71 -60
- package/skills-index.json +99 -0
- package/.claude/skills/agentation/.meta.json +0 -6
- package/.claude/skills/agentation/SKILL.md +0 -107
- package/.claude/skills/fe-biz-patterns/.meta.json +0 -6
- package/.claude/skills/fe-biz-patterns/SKILL.md +0 -26
- package/.claude/skills/fe-biz-patterns/references/infinite-scroll.md +0 -292
- package/.claude/skills/fe-biz-patterns/references/pinia-store.md +0 -174
- package/.claude/skills/fe-biz-patterns/references/service-layer.md +0 -198
- package/.claude/skills/fe-biz-patterns/references/tab-anchor.md +0 -1125
- package/.claude/skills/fe-biz-patterns/references/use-loading.md +0 -114
- package/.claude/skills/frontend-code-review/.meta.json +0 -6
- package/.claude/skills/frontend-code-review/SKILL.md +0 -167
- package/.claude/skills/frontend-code-review/references/checklist.md +0 -298
- package/.claude/skills/frontend-design/.meta.json +0 -6
- package/.claude/skills/frontend-design/LICENSE.txt +0 -177
- package/.claude/skills/frontend-design/SKILL.md +0 -42
- package/.claude/skills/moai-framework-electron/.meta.json +0 -6
- package/.claude/skills/moai-framework-electron/SKILL.md +0 -328
- package/.claude/skills/skill-creator/.meta.json +0 -6
- package/.claude/skills/skill-creator/SKILL.md +0 -356
- package/.claude/skills/skill-creator/references/output-patterns.md +0 -82
- package/.claude/skills/skill-creator/references/workflows.md +0 -28
- package/.claude/skills/skill-creator/scripts/init_skill.py +0 -303
- package/.claude/skills/skill-creator/scripts/package_skill.py +0 -110
- package/.claude/skills/skill-creator/scripts/quick_validate.py +0 -95
- package/.claude/skills/ui-ux-pro-max/.meta.json +0 -6
- package/.claude/skills/ui-ux-pro-max/SKILL.md +0 -228
- package/.claude/skills/ui-ux-pro-max/data/charts.csv +0 -26
- package/.claude/skills/ui-ux-pro-max/data/colors.csv +0 -97
- package/.claude/skills/ui-ux-pro-max/data/landing.csv +0 -31
- package/.claude/skills/ui-ux-pro-max/data/products.csv +0 -97
- package/.claude/skills/ui-ux-pro-max/data/prompts.csv +0 -24
- package/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
- package/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
- package/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -51
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
- package/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
- package/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +0 -54
- package/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
- package/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
- package/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +0 -50
- package/.claude/skills/ui-ux-pro-max/data/styles.csv +0 -59
- package/.claude/skills/ui-ux-pro-max/data/typography.csv +0 -58
- package/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
- package/.claude/skills/ui-ux-pro-max/scripts/core.py +0 -238
- package/.claude/skills/ui-ux-pro-max/scripts/search.py +0 -61
- package/.claude/skills/vue/.meta.json +0 -6
- package/.claude/skills/vue/SKILL.md +0 -103
- package/.claude/skills/vue/references/components.md +0 -323
- package/.claude/skills/vue/references/composables.md +0 -358
- package/.claude/skills/vue/references/directives.md +0 -225
- package/.claude/skills/vue/references/gotchas.md +0 -438
- package/.claude/skills/vue/references/provide-inject.md +0 -174
- package/.claude/skills/vue/references/reactivity.md +0 -289
- package/.claude/skills/vue/references/router.md +0 -181
- package/.claude/skills/vue/references/testing.md +0 -294
- package/.claude/skills/vue/references/typescript.md +0 -172
- package/.claude/skills/vue/references/utils-client.md +0 -156
package/package.json
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dk-frontend-skills",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "dk-engineer - 幽默沉稳靠谱的前端开发助手 Claude Skills 配置包,一键注入 .claude/ 技能目录和 CLAUDE.md 人设配置",
|
|
5
5
|
"author": "XiaoMa",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
8
8
|
"files": [
|
|
9
|
-
".claude/",
|
|
10
9
|
"CLAUDE.md",
|
|
11
|
-
"scripts/"
|
|
10
|
+
"scripts/",
|
|
11
|
+
"skills-index.json",
|
|
12
|
+
".claude/settings.json",
|
|
13
|
+
".claude/settings.local.json"
|
|
12
14
|
],
|
|
13
15
|
"bin": {
|
|
14
|
-
"dk-skills": "./scripts/cli.js"
|
|
16
|
+
"dk-skills": "./scripts/cli.js",
|
|
17
|
+
"dk-frontend-skills": "./scripts/cli.js"
|
|
15
18
|
},
|
|
16
19
|
"scripts": {
|
|
20
|
+
"build-skills": "node scripts/build-skills.js",
|
|
17
21
|
"postinstall": "node scripts/copy-skills.js"
|
|
18
22
|
},
|
|
19
23
|
"dependencies": {
|
|
20
24
|
"@inquirer/prompts": "^8.4.2",
|
|
21
|
-
"chalk": "^5.6.2"
|
|
25
|
+
"chalk": "^5.6.2",
|
|
26
|
+
"cli-progress": "^3.12.0",
|
|
27
|
+
"tar": "^7.5.15"
|
|
22
28
|
}
|
|
23
29
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 技能打包脚本
|
|
5
|
+
* 将 skills/<name>/ 目录打包成 dist/skills/<name>-<version>.tar.gz
|
|
6
|
+
* 并更新 skills-index.json 中的版本和文件大小
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
const tar = require("tar");
|
|
12
|
+
const { execSync } = require("child_process");
|
|
13
|
+
|
|
14
|
+
const repoRoot = path.join(__dirname, "..");
|
|
15
|
+
const skillsDir = path.join(repoRoot, "skills");
|
|
16
|
+
const distDir = path.join(repoRoot, "dist", "skills");
|
|
17
|
+
const indexPath = path.join(repoRoot, "skills-index.json");
|
|
18
|
+
|
|
19
|
+
async function build() {
|
|
20
|
+
// 读取已有的索引
|
|
21
|
+
const index = JSON.parse(fs.readFileSync(indexPath, "utf-8"));
|
|
22
|
+
const indexSkills = index.skills;
|
|
23
|
+
|
|
24
|
+
// 确保输出目录存在
|
|
25
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
const skillNames = fs.readdirSync(skillsDir);
|
|
28
|
+
|
|
29
|
+
for (const name of skillNames) {
|
|
30
|
+
const skillPath = path.join(skillsDir, name);
|
|
31
|
+
if (!fs.statSync(skillPath).isDirectory()) continue;
|
|
32
|
+
|
|
33
|
+
// 读取 .meta.json 获取版本
|
|
34
|
+
const metaPath = path.join(skillPath, ".meta.json");
|
|
35
|
+
if (!fs.existsSync(metaPath)) {
|
|
36
|
+
console.log(` ⚠️ ${name}: 缺少 .meta.json,跳过`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
|
|
41
|
+
const version = meta.version || "0.0.0";
|
|
42
|
+
const fileName = `${name}-${version}.tar.gz`;
|
|
43
|
+
const outputPath = path.join(distDir, fileName);
|
|
44
|
+
|
|
45
|
+
console.log(` 📦 ${name} (${version}) → ${fileName}`);
|
|
46
|
+
|
|
47
|
+
// 打包:进入 skills/ 目录,只打包当前技能子目录
|
|
48
|
+
await tar.c(
|
|
49
|
+
{
|
|
50
|
+
gzip: true,
|
|
51
|
+
file: outputPath,
|
|
52
|
+
cwd: skillsDir,
|
|
53
|
+
portable: true,
|
|
54
|
+
},
|
|
55
|
+
[name],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// 获取文件大小
|
|
59
|
+
const stat = fs.statSync(outputPath);
|
|
60
|
+
|
|
61
|
+
// 更新索引
|
|
62
|
+
if (indexSkills[name]) {
|
|
63
|
+
indexSkills[name].version = version;
|
|
64
|
+
indexSkills[name].fileName = fileName;
|
|
65
|
+
indexSkills[name].size = stat.size;
|
|
66
|
+
} else {
|
|
67
|
+
indexSkills[name] = {
|
|
68
|
+
version,
|
|
69
|
+
description: meta.description || "",
|
|
70
|
+
fileName,
|
|
71
|
+
tags: meta.tags || [],
|
|
72
|
+
size: stat.size,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 更新索引的更新时间
|
|
78
|
+
index.updatedAt = new Date().toISOString().slice(0, 10);
|
|
79
|
+
|
|
80
|
+
// 写回索引
|
|
81
|
+
fs.writeFileSync(indexPath, JSON.stringify(index, null, 2) + "\n", "utf-8");
|
|
82
|
+
console.log(`\n ✅ skills-index.json 已更新`);
|
|
83
|
+
console.log(` 📂 压缩包输出目录: dist/skills/\n`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
build().catch((err) => {
|
|
87
|
+
console.error("打包失败:", err);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
package/scripts/cli.js
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const
|
|
5
|
+
const tar = require("tar");
|
|
6
|
+
const {
|
|
7
|
+
getPackageSkills,
|
|
8
|
+
getUserSkills,
|
|
9
|
+
readSkillsIndex,
|
|
10
|
+
downloadFile,
|
|
11
|
+
} = require("./core");
|
|
6
12
|
|
|
7
13
|
// npx 运行时:process.cwd() 是用户项目目录
|
|
8
14
|
// __dirname 是包内 scripts/ 目录
|
|
@@ -18,12 +24,12 @@ const packageDir = path.join(__dirname, "..");
|
|
|
18
24
|
const claudeDest = path.join(projectRoot, ".claude");
|
|
19
25
|
const skillsDest = path.join(claudeDest, "skills");
|
|
20
26
|
const settingsPath = path.join(claudeDest, "settings.json");
|
|
21
|
-
const pkgSkillsSource = path.join(packageDir, ".claude", "skills");
|
|
22
27
|
|
|
23
28
|
// ---------- 文件写入 ----------
|
|
24
29
|
|
|
25
30
|
function readSettings() {
|
|
26
|
-
if (!fs.existsSync(settingsPath))
|
|
31
|
+
if (!fs.existsSync(settingsPath))
|
|
32
|
+
return { skills: {}, always_apply_skills: [] };
|
|
27
33
|
try {
|
|
28
34
|
return JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
29
35
|
} catch {
|
|
@@ -35,36 +41,117 @@ function writeSettings(settings) {
|
|
|
35
41
|
if (!fs.existsSync(claudeDest)) {
|
|
36
42
|
fs.mkdirSync(claudeDest, { recursive: true });
|
|
37
43
|
}
|
|
38
|
-
fs.writeFileSync(
|
|
44
|
+
fs.writeFileSync(
|
|
45
|
+
settingsPath,
|
|
46
|
+
JSON.stringify(settings, null, 2) + "\n",
|
|
47
|
+
"utf-8",
|
|
48
|
+
);
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
// ----------
|
|
51
|
+
// ---------- 远程下载与安装 ----------
|
|
52
|
+
|
|
53
|
+
async function downloadAndInstallSkill(name, index) {
|
|
54
|
+
const skillInfo = index.skills[name];
|
|
55
|
+
if (!skillInfo) {
|
|
56
|
+
console.error(` ❌ 技能 "${name}" 不在索引中`);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const destDir = path.join(skillsDest, name);
|
|
61
|
+
|
|
62
|
+
// 检查本地是否已安装且版本一致
|
|
63
|
+
const localMetaPath = path.join(destDir, ".meta.json");
|
|
64
|
+
if (fs.existsSync(localMetaPath)) {
|
|
65
|
+
try {
|
|
66
|
+
const localMeta = JSON.parse(fs.readFileSync(localMetaPath, "utf-8"));
|
|
67
|
+
if (localMeta.version === skillInfo.version) {
|
|
68
|
+
return true; // 已是最新,跳过
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
// 元信息损坏,重新下载
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 构造下载地址
|
|
76
|
+
const url = `${index.baseUrl}/${skillInfo.fileName}`;
|
|
77
|
+
const tempDir = path.join(claudeDest, ".temp");
|
|
78
|
+
if (!fs.existsSync(tempDir)) {
|
|
79
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
80
|
+
}
|
|
81
|
+
const tempFile = path.join(tempDir, skillInfo.fileName);
|
|
82
|
+
|
|
83
|
+
// 下载带进度条
|
|
84
|
+
const { SingleBar, Presets } = await import("cli-progress");
|
|
85
|
+
const bar = new SingleBar(
|
|
86
|
+
{
|
|
87
|
+
format: ` 📥 ${name} |{bar}| {percentage}% | {value}/{total} bytes`,
|
|
88
|
+
barCompleteChar: "\u2588",
|
|
89
|
+
barIncompleteChar: "\u2591",
|
|
90
|
+
hideCursor: true,
|
|
91
|
+
},
|
|
92
|
+
Presets.shades_classic,
|
|
93
|
+
);
|
|
42
94
|
|
|
43
|
-
|
|
44
|
-
|
|
95
|
+
try {
|
|
96
|
+
console.log(` ⏬ 正在下载 ${name} (${skillInfo.version})...`);
|
|
97
|
+
bar.start(skillInfo.size || 100, 0);
|
|
98
|
+
await downloadFile(url, tempFile, (downloaded, total) => {
|
|
99
|
+
bar.setTotal(total || skillInfo.size || 100);
|
|
100
|
+
bar.update(downloaded);
|
|
101
|
+
});
|
|
102
|
+
bar.stop();
|
|
103
|
+
|
|
104
|
+
// 创建目标目录,解压
|
|
105
|
+
if (fs.existsSync(destDir)) {
|
|
106
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
109
|
+
|
|
110
|
+
await tar.x({
|
|
111
|
+
file: tempFile,
|
|
112
|
+
C: destDir,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// 清理临时文件
|
|
116
|
+
fs.rmSync(tempFile, { force: true });
|
|
117
|
+
|
|
118
|
+
console.log(` ✅ ${name} (${skillInfo.version}) 安装成功`);
|
|
119
|
+
return true;
|
|
120
|
+
} catch (err) {
|
|
121
|
+
bar.stop();
|
|
122
|
+
// 清理残留
|
|
123
|
+
if (fs.existsSync(tempFile)) fs.rmSync(tempFile, { force: true });
|
|
124
|
+
if (fs.existsSync(destDir))
|
|
125
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
126
|
+
console.error(` ❌ 下载 "${name}" 失败: ${err.message}`);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function installSelectedSkills(selectedNames) {
|
|
132
|
+
// 创建目录
|
|
45
133
|
if (!fs.existsSync(skillsDest)) {
|
|
46
134
|
fs.mkdirSync(skillsDest, { recursive: true });
|
|
47
135
|
}
|
|
48
136
|
|
|
49
|
-
const
|
|
137
|
+
const index = readSkillsIndex(packageDir);
|
|
138
|
+
if (!index) {
|
|
139
|
+
console.error(" ❌ skills-index.json 读取失败");
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const indexSkills = index.skills;
|
|
50
144
|
let installedCount = 0;
|
|
51
145
|
|
|
52
|
-
for (const name of
|
|
53
|
-
const src = path.join(pkgSkillsSource, name);
|
|
146
|
+
for (const name of Object.keys(indexSkills)) {
|
|
54
147
|
const dest = path.join(skillsDest, name);
|
|
55
148
|
|
|
56
149
|
if (selectedNames.includes(name)) {
|
|
57
|
-
// 勾选了的 →
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
fs.cpSync(src, dest, { recursive: true });
|
|
61
|
-
installedCount++;
|
|
62
|
-
} catch (err) {
|
|
63
|
-
console.error(` ❌ 安装技能 "${name}" 失败:`, err.message);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
150
|
+
// 勾选了的 → 下载/更新
|
|
151
|
+
const ok = await downloadAndInstallSkill(name, index);
|
|
152
|
+
if (ok) installedCount++;
|
|
66
153
|
} else {
|
|
67
|
-
// 没勾选的 →
|
|
154
|
+
// 没勾选的 → 移除
|
|
68
155
|
if (fs.existsSync(dest)) {
|
|
69
156
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
70
157
|
}
|
|
@@ -75,7 +162,7 @@ function installSelectedSkills(selectedNames) {
|
|
|
75
162
|
const settings = readSettings();
|
|
76
163
|
if (!settings.skills) settings.skills = {};
|
|
77
164
|
|
|
78
|
-
for (const name of
|
|
165
|
+
for (const name of Object.keys(indexSkills)) {
|
|
79
166
|
const enabled = selectedNames.includes(name);
|
|
80
167
|
if (!settings.skills[name]) {
|
|
81
168
|
settings.skills[name] = {};
|
|
@@ -90,7 +177,7 @@ function installSelectedSkills(selectedNames) {
|
|
|
90
177
|
const mdDest = path.join(projectRoot, "CLAUDE.md");
|
|
91
178
|
if (fs.existsSync(mdSource) && !fs.existsSync(mdDest)) {
|
|
92
179
|
fs.copyFileSync(mdSource, mdDest);
|
|
93
|
-
console.log(" 📝 CLAUDE.md
|
|
180
|
+
console.log(" 📝 CLAUDE.md 已创建");
|
|
94
181
|
}
|
|
95
182
|
|
|
96
183
|
return installedCount;
|
|
@@ -119,13 +206,15 @@ async function cmdList() {
|
|
|
119
206
|
const allSkills = [...skillMap.values()];
|
|
120
207
|
|
|
121
208
|
console.log(`\n${chalk.bold("📋 dk-frontend-skills 技能清单")}\n`);
|
|
122
|
-
console.log(
|
|
123
|
-
|
|
209
|
+
console.log(
|
|
210
|
+
` ${chalk.dim("状态 技能名 版本 描述")}`,
|
|
211
|
+
);
|
|
212
|
+
console.log(
|
|
213
|
+
` ${chalk.dim("─────────────────────────────────────────────────────")}`,
|
|
214
|
+
);
|
|
124
215
|
|
|
125
216
|
for (const skill of allSkills) {
|
|
126
|
-
const status = skill.enabled
|
|
127
|
-
? chalk.green("● 启用")
|
|
128
|
-
: chalk.gray("○ 停用");
|
|
217
|
+
const status = skill.enabled ? chalk.green("● 启用") : chalk.gray("○ 停用");
|
|
129
218
|
const name = chalk.white(skill.name.padEnd(20));
|
|
130
219
|
const ver = chalk.dim(skill.version.padEnd(7));
|
|
131
220
|
const desc = skill.description
|
|
@@ -134,19 +223,15 @@ async function cmdList() {
|
|
|
134
223
|
console.log(` ${status} ${name} ${ver} ${desc}`);
|
|
135
224
|
}
|
|
136
225
|
|
|
137
|
-
console.log(
|
|
226
|
+
console.log(
|
|
227
|
+
`\n ${chalk.cyan("💡")} ${chalk.dim("运行 npx dk-frontend-skills 进入交互选择模式")}\n`,
|
|
228
|
+
);
|
|
138
229
|
}
|
|
139
230
|
|
|
140
231
|
// ---------- 交互式菜单(选完即装) ----------
|
|
141
232
|
|
|
142
233
|
async function startInteractiveMenu() {
|
|
143
234
|
const { checkbox } = await import("@inquirer/prompts");
|
|
144
|
-
|
|
145
|
-
if (!fs.existsSync(pkgSkillsSource)) {
|
|
146
|
-
console.log(" ⚠️ 包内没有找到技能文件\n");
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
235
|
const pkgSkills = getPackageSkills(packageDir);
|
|
151
236
|
|
|
152
237
|
if (pkgSkills.length === 0) {
|
|
@@ -183,9 +268,11 @@ async function startInteractiveMenu() {
|
|
|
183
268
|
});
|
|
184
269
|
|
|
185
270
|
const newCount = selected.filter((name) => !installedSet.has(name)).length;
|
|
186
|
-
const removedCount = [...installedSet].filter(
|
|
271
|
+
const removedCount = [...installedSet].filter(
|
|
272
|
+
(name) => !selected.includes(name),
|
|
273
|
+
).length;
|
|
187
274
|
|
|
188
|
-
const installed = installSelectedSkills(selected);
|
|
275
|
+
const installed = await installSelectedSkills(selected);
|
|
189
276
|
|
|
190
277
|
const parts = [];
|
|
191
278
|
if (newCount > 0) parts.push(`新装 ${newCount} 个`);
|
|
@@ -223,7 +310,9 @@ async function main() {
|
|
|
223
310
|
} else if (command === "list") {
|
|
224
311
|
await cmdList();
|
|
225
312
|
} else if (command === "enable" || command === "disable") {
|
|
226
|
-
console.log(
|
|
313
|
+
console.log(
|
|
314
|
+
` ⚠️ 该命令已废弃,请直接运行 npx dk-frontend-skills 使用交互菜单\n`,
|
|
315
|
+
);
|
|
227
316
|
} else {
|
|
228
317
|
console.log(` 未知命令: ${command}\n 可用: ${COMMANDS.join(", ")}\n`);
|
|
229
318
|
process.exit(1);
|
package/scripts/copy-skills.js
CHANGED
|
@@ -3,14 +3,13 @@ const path = require("path");
|
|
|
3
3
|
const {
|
|
4
4
|
backupTimestamp,
|
|
5
5
|
appendLog,
|
|
6
|
-
backupDir,
|
|
7
|
-
installSkills,
|
|
8
6
|
installSettings,
|
|
9
7
|
} = require("./core");
|
|
10
8
|
|
|
11
9
|
// 获取用户项目根目录
|
|
12
10
|
// npm lifecycle 脚本会设置 INIT_CWD 为执行命令时的目录,比从 __dirname 爬三级更可靠
|
|
13
|
-
const projectRoot =
|
|
11
|
+
const projectRoot =
|
|
12
|
+
process.env.INIT_CWD || path.resolve(__dirname, "..", "..", "..");
|
|
14
13
|
const packageDir = path.join(__dirname, "..");
|
|
15
14
|
|
|
16
15
|
// 源路径
|
|
@@ -28,25 +27,21 @@ const logFile = path.join(claudeDest, ".install.log");
|
|
|
28
27
|
|
|
29
28
|
console.log("\n📦 dk-frontend-skills 技能包安装开始\n");
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const skillsSource = path.join(claudeSource, "skills");
|
|
38
|
-
const skillsDest = path.join(claudeDest, "skills");
|
|
30
|
+
// 确保 .claude/ 目录存在
|
|
31
|
+
if (!fs.existsSync(claudeDest)) {
|
|
32
|
+
fs.mkdirSync(claudeDest, { recursive: true });
|
|
33
|
+
appendLog(logFile, "INFO", `.claude/ directory created`);
|
|
34
|
+
}
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
appendLog(logFile, "INFO", "Install started (fresh)");
|
|
45
|
-
fs.cpSync(claudeSource, claudeDest, { recursive: true });
|
|
46
|
-
appendLog(logFile, "INFO", `.claude/ directory created`);
|
|
47
|
-
}
|
|
36
|
+
// 确保 .claude/skills/ 目录存在(不覆盖已有技能)
|
|
37
|
+
const skillsDest = path.join(claudeDest, "skills");
|
|
38
|
+
if (!fs.existsSync(skillsDest)) {
|
|
39
|
+
fs.mkdirSync(skillsDest, { recursive: true });
|
|
48
40
|
}
|
|
49
41
|
|
|
42
|
+
// 安装 settings.json(深度合并)
|
|
43
|
+
installSettings(claudeSource, claudeDest, logFile);
|
|
44
|
+
|
|
50
45
|
// 安装 CLAUDE.md
|
|
51
46
|
if (fs.existsSync(mdSource)) {
|
|
52
47
|
if (!fs.existsSync(mdDest)) {
|
|
@@ -60,7 +55,11 @@ if (fs.existsSync(mdSource)) {
|
|
|
60
55
|
const bakName = `CLAUDE.md.${backupTimestamp()}`;
|
|
61
56
|
const bakPath = path.join(backupsDir, bakName);
|
|
62
57
|
fs.copyFileSync(mdDest, bakPath);
|
|
63
|
-
appendLog(
|
|
58
|
+
appendLog(
|
|
59
|
+
logFile,
|
|
60
|
+
"BACKUP",
|
|
61
|
+
`backups/${bakName} (CLAUDE.md user version preserved)`,
|
|
62
|
+
);
|
|
64
63
|
}
|
|
65
64
|
}
|
|
66
65
|
|
|
@@ -77,4 +76,4 @@ if (fs.existsSync(logFile)) {
|
|
|
77
76
|
console.log("");
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
console.log("💡 提示:运行 npx dk-skills
|
|
79
|
+
console.log("💡 提示:运行 npx dk-skills 可交互选择安装/启用/禁用技能\n");
|
package/scripts/core.js
CHANGED
|
@@ -46,7 +46,7 @@ function backupDir(dir) {
|
|
|
46
46
|
|
|
47
47
|
for (const item of fs.readdirSync(dir)) {
|
|
48
48
|
if (item === "backups") continue;
|
|
49
|
-
|
|
49
|
+
copyDirSync(path.join(dir, item), path.join(bakPath, item));
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// 清理旧备份,只保留最近 3 份
|
|
@@ -70,6 +70,22 @@ function isPlainObject(val) {
|
|
|
70
70
|
return Object.prototype.toString.call(val) === "[object Object]";
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* 手动递归拷贝目录,绕开 Windows 中文路径下 fs.cpSync({ recursive: true }) 死锁的 bug
|
|
75
|
+
*/
|
|
76
|
+
function copyDirSync(src, dest) {
|
|
77
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
78
|
+
for (const item of fs.readdirSync(src)) {
|
|
79
|
+
const srcPath = path.join(src, item);
|
|
80
|
+
const destPath = path.join(dest, item);
|
|
81
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
82
|
+
copyDirSync(srcPath, destPath);
|
|
83
|
+
} else {
|
|
84
|
+
fs.copyFileSync(srcPath, destPath);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
/**
|
|
74
90
|
* 递归深合并,用户值优先,目标新增字段补充
|
|
75
91
|
*/
|
|
@@ -102,51 +118,6 @@ function readMeta(skillDir) {
|
|
|
102
118
|
return null;
|
|
103
119
|
}
|
|
104
120
|
|
|
105
|
-
/**
|
|
106
|
-
* 安装技能:逐个复制,补缺不覆盖
|
|
107
|
-
*/
|
|
108
|
-
function installSkills(skillsSource, skillsDest, logFile) {
|
|
109
|
-
if (!fs.existsSync(skillsSource)) return [];
|
|
110
|
-
|
|
111
|
-
if (!fs.existsSync(skillsDest)) {
|
|
112
|
-
fs.mkdirSync(skillsDest, { recursive: true });
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const skillDirs = fs.readdirSync(skillsSource);
|
|
116
|
-
const results = [];
|
|
117
|
-
|
|
118
|
-
for (const dir of skillDirs) {
|
|
119
|
-
const srcSkill = path.join(skillsSource, dir);
|
|
120
|
-
const destSkill = path.join(skillsDest, dir);
|
|
121
|
-
const meta = readMeta(srcSkill);
|
|
122
|
-
const version = meta ? meta.version : "?";
|
|
123
|
-
|
|
124
|
-
if (!fs.existsSync(destSkill)) {
|
|
125
|
-
fs.cpSync(srcSkill, destSkill, { recursive: true });
|
|
126
|
-
appendLog(logFile, "SKILL", `${dir} (${version}) → installed`);
|
|
127
|
-
results.push({ name: dir, version, action: "installed" });
|
|
128
|
-
} else {
|
|
129
|
-
const destMeta = readMeta(destSkill);
|
|
130
|
-
const needUpdate = meta && destMeta && meta.version !== destMeta.version;
|
|
131
|
-
|
|
132
|
-
if (needUpdate) {
|
|
133
|
-
const bakName = `${dir}.bak.${backupTimestamp()}`;
|
|
134
|
-
const bakPath = path.join(skillsDest, bakName);
|
|
135
|
-
fs.cpSync(destSkill, bakPath, { recursive: true });
|
|
136
|
-
fs.rmSync(destSkill, { recursive: true, force: true });
|
|
137
|
-
fs.cpSync(srcSkill, destSkill, { recursive: true });
|
|
138
|
-
appendLog(logFile, "SKILL", `${dir} (${destMeta.version} → ${meta.version}) → updated`);
|
|
139
|
-
results.push({ name: dir, version: meta.version, action: "updated" });
|
|
140
|
-
} else {
|
|
141
|
-
appendLog(logFile, "SKILL", `${dir} (${version}) → skipped (exists)`);
|
|
142
|
-
results.push({ name: dir, version, action: "skipped" });
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return results;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
121
|
/**
|
|
151
122
|
* 安装 settings.json,深度合并
|
|
152
123
|
*/
|
|
@@ -176,21 +147,31 @@ function installSettings(sourceDir, destDir, logFile) {
|
|
|
176
147
|
}
|
|
177
148
|
|
|
178
149
|
/**
|
|
179
|
-
*
|
|
150
|
+
* 读取 skills-index.json 获取包内技能列表
|
|
180
151
|
*/
|
|
181
|
-
function
|
|
182
|
-
const
|
|
183
|
-
if (!fs.existsSync(
|
|
152
|
+
function readSkillsIndex(packageDir) {
|
|
153
|
+
const indexPath = path.join(packageDir, "skills-index.json");
|
|
154
|
+
if (!fs.existsSync(indexPath)) return null;
|
|
155
|
+
try {
|
|
156
|
+
return JSON.parse(fs.readFileSync(indexPath, "utf-8"));
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
184
161
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
162
|
+
/**
|
|
163
|
+
* 获取包的技能列表(从 skills-index.json 读取)
|
|
164
|
+
*/
|
|
165
|
+
function getPackageSkills(packageDir) {
|
|
166
|
+
const index = readSkillsIndex(packageDir);
|
|
167
|
+
if (!index) return [];
|
|
168
|
+
|
|
169
|
+
return Object.entries(index.skills).map(([name, info]) => ({
|
|
170
|
+
name,
|
|
171
|
+
version: info.version,
|
|
172
|
+
description: info.description || "",
|
|
173
|
+
tags: info.tags || [],
|
|
174
|
+
}));
|
|
194
175
|
}
|
|
195
176
|
|
|
196
177
|
/**
|
|
@@ -225,15 +206,45 @@ function getUserSkills(claudeDest) {
|
|
|
225
206
|
});
|
|
226
207
|
}
|
|
227
208
|
|
|
209
|
+
/**
|
|
210
|
+
* 从远程下载文件到本地临时路径
|
|
211
|
+
*/
|
|
212
|
+
async function downloadFile(url, destPath, onProgress) {
|
|
213
|
+
const response = await fetch(url);
|
|
214
|
+
if (!response.ok) {
|
|
215
|
+
throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const total = parseInt(response.headers.get("content-length") || "0", 10);
|
|
219
|
+
let downloaded = 0;
|
|
220
|
+
|
|
221
|
+
const reader = response.body.getReader();
|
|
222
|
+
const writer = fs.createWriteStream(destPath);
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
while (true) {
|
|
226
|
+
const { done, value } = await reader.read();
|
|
227
|
+
if (done) break;
|
|
228
|
+
downloaded += value.length;
|
|
229
|
+
if (onProgress) onProgress(downloaded, total);
|
|
230
|
+
writer.write(value);
|
|
231
|
+
}
|
|
232
|
+
} finally {
|
|
233
|
+
writer.close();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
228
237
|
module.exports = {
|
|
229
238
|
timestamp,
|
|
230
239
|
backupTimestamp,
|
|
231
240
|
appendLog,
|
|
232
241
|
backupDir,
|
|
242
|
+
copyDirSync,
|
|
233
243
|
deepMerge,
|
|
234
244
|
readMeta,
|
|
235
|
-
installSkills,
|
|
236
245
|
installSettings,
|
|
246
|
+
readSkillsIndex,
|
|
237
247
|
getPackageSkills,
|
|
238
248
|
getUserSkills,
|
|
249
|
+
downloadFile,
|
|
239
250
|
};
|