dk-frontend-skills 2.0.0 → 2.1.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 +5 -1
- package/scripts/cli.js +135 -155
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dk-frontend-skills",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "dk-engineer - 幽默沉稳靠谱的前端开发助手 Claude Skills 配置包,一键注入 .claude/ 技能目录和 CLAUDE.md 人设配置",
|
|
5
5
|
"author": "XiaoMa",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,5 +15,9 @@
|
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"postinstall": "node scripts/copy-skills.js"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@inquirer/prompts": "^8.4.2",
|
|
21
|
+
"chalk": "^5.6.2"
|
|
18
22
|
}
|
|
19
23
|
}
|
package/scripts/cli.js
CHANGED
|
@@ -2,19 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const readline = require("readline");
|
|
6
5
|
const { getPackageSkills, getUserSkills } = require("./core");
|
|
7
6
|
|
|
8
|
-
//
|
|
9
|
-
|
|
7
|
+
// npx 运行时:process.cwd() 是用户项目目录
|
|
8
|
+
// __dirname 是包内 scripts/ 目录
|
|
9
|
+
const projectRoot = process.cwd();
|
|
10
10
|
const packageDir = path.join(__dirname, "..");
|
|
11
11
|
const claudeDest = path.join(projectRoot, ".claude");
|
|
12
|
+
const skillsDest = path.join(claudeDest, "skills");
|
|
12
13
|
const settingsPath = path.join(claudeDest, "settings.json");
|
|
14
|
+
const pkgSkillsSource = path.join(packageDir, ".claude", "skills");
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
const args = process.argv.slice(3);
|
|
16
|
-
|
|
17
|
-
// ---------- 辅助函数 ----------
|
|
16
|
+
// ---------- 文件写入 ----------
|
|
18
17
|
|
|
19
18
|
function readSettings() {
|
|
20
19
|
if (!fs.existsSync(settingsPath)) return { skills: {}, always_apply_skills: [] };
|
|
@@ -26,17 +25,47 @@ function readSettings() {
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
function writeSettings(settings) {
|
|
28
|
+
if (!fs.existsSync(claudeDest)) {
|
|
29
|
+
fs.mkdirSync(claudeDest, { recursive: true });
|
|
30
|
+
}
|
|
29
31
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// ---------- 安装勾选的技能 ----------
|
|
35
|
+
|
|
36
|
+
function installSelectedSkills(selectedNames) {
|
|
37
|
+
// 创建 .claude/ 和 skills/ 目录
|
|
38
|
+
if (!fs.existsSync(skillsDest)) {
|
|
39
|
+
fs.mkdirSync(skillsDest, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const allPkgSkills = fs.readdirSync(pkgSkillsSource);
|
|
43
|
+
let installedCount = 0;
|
|
44
|
+
|
|
45
|
+
for (const name of allPkgSkills) {
|
|
46
|
+
const src = path.join(pkgSkillsSource, name);
|
|
47
|
+
const dest = path.join(skillsDest, name);
|
|
48
|
+
|
|
49
|
+
if (selectedNames.includes(name)) {
|
|
50
|
+
// 勾选了的 → 安装(不存在才装)
|
|
51
|
+
if (!fs.existsSync(dest)) {
|
|
52
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
53
|
+
installedCount++;
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
// 没勾选的 → 如果之前装了,删掉
|
|
57
|
+
if (fs.existsSync(dest)) {
|
|
58
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 生成 settings.json
|
|
36
64
|
const settings = readSettings();
|
|
37
65
|
if (!settings.skills) settings.skills = {};
|
|
38
66
|
|
|
39
|
-
for (const
|
|
67
|
+
for (const name of allPkgSkills) {
|
|
68
|
+
const enabled = selectedNames.includes(name);
|
|
40
69
|
if (!settings.skills[name]) {
|
|
41
70
|
settings.skills[name] = {};
|
|
42
71
|
}
|
|
@@ -44,199 +73,150 @@ function saveSkillSelection(choices) {
|
|
|
44
73
|
}
|
|
45
74
|
|
|
46
75
|
writeSettings(settings);
|
|
76
|
+
|
|
77
|
+
// 安装 CLAUDE.md(没有才装)
|
|
78
|
+
const mdSource = path.join(packageDir, "CLAUDE.md");
|
|
79
|
+
const mdDest = path.join(projectRoot, "CLAUDE.md");
|
|
80
|
+
if (fs.existsSync(mdSource) && !fs.existsSync(mdDest)) {
|
|
81
|
+
fs.copyFileSync(mdSource, mdDest);
|
|
82
|
+
console.log(" 📝 CLAUDE.md 已创建\n");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return installedCount;
|
|
47
86
|
}
|
|
48
87
|
|
|
49
88
|
// ---------- 命令:list ----------
|
|
50
89
|
|
|
51
|
-
function cmdList() {
|
|
90
|
+
async function cmdList() {
|
|
91
|
+
if (!fs.existsSync(claudeDest)) {
|
|
92
|
+
console.log("\n ⚠️ 尚未安装技能,请先运行 npx dk-frontend-skills\n");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const chalk = (await import("chalk")).default;
|
|
52
97
|
const pkgSkills = getPackageSkills(packageDir);
|
|
53
98
|
const userSkills = getUserSkills(claudeDest);
|
|
54
99
|
|
|
55
|
-
// 合并包内和用户已安装的技能状态
|
|
56
100
|
const skillMap = new Map();
|
|
57
101
|
for (const s of userSkills) skillMap.set(s.name, s);
|
|
58
102
|
for (const s of pkgSkills) {
|
|
59
103
|
if (!skillMap.has(s.name)) {
|
|
60
|
-
skillMap.set(s.name, { ...s, enabled: false
|
|
104
|
+
skillMap.set(s.name, { ...s, enabled: false });
|
|
61
105
|
}
|
|
62
106
|
}
|
|
63
107
|
|
|
64
108
|
const allSkills = [...skillMap.values()];
|
|
65
109
|
|
|
66
|
-
console.log("
|
|
67
|
-
console.log("
|
|
68
|
-
console.log("
|
|
110
|
+
console.log(`\n${chalk.bold("📋 dk-frontend-skills 技能清单")}\n`);
|
|
111
|
+
console.log(` ${chalk.dim("状态 技能名 版本 描述")}`);
|
|
112
|
+
console.log(` ${chalk.dim("─────────────────────────────────────────────────────")}`);
|
|
69
113
|
|
|
70
114
|
for (const skill of allSkills) {
|
|
71
|
-
const status = skill.enabled
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
115
|
+
const status = skill.enabled
|
|
116
|
+
? chalk.green("● 启用")
|
|
117
|
+
: chalk.gray("○ 停用");
|
|
118
|
+
const name = chalk.white(skill.name.padEnd(20));
|
|
119
|
+
const ver = chalk.dim(skill.version.padEnd(7));
|
|
120
|
+
const desc = skill.description
|
|
121
|
+
? chalk.gray(skill.description)
|
|
122
|
+
: chalk.dim("(无描述)");
|
|
75
123
|
console.log(` ${status} ${name} ${ver} ${desc}`);
|
|
76
124
|
}
|
|
77
125
|
|
|
78
|
-
console.log(`\n 💡 运行 npx dk-skills
|
|
126
|
+
console.log(`\n ${chalk.cyan("💡")} ${chalk.dim("运行 npx dk-frontend-skills 进入交互选择模式")}\n`);
|
|
79
127
|
}
|
|
80
128
|
|
|
81
|
-
// ----------
|
|
82
|
-
|
|
83
|
-
function cmdToggle(enable, names) {
|
|
84
|
-
if (names.length === 0) {
|
|
85
|
-
console.log(` 用法: npx dk-skills ${enable ? "enable" : "disable"} <技能名...>`);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const pkgSkills = getPackageSkills(packageDir);
|
|
90
|
-
const pkgNames = new Set(pkgSkills.map((s) => s.name));
|
|
91
|
-
const userSkills = getUserSkills(claudeDest);
|
|
92
|
-
const userNames = new Set(userSkills.map((s) => s.name));
|
|
129
|
+
// ---------- 交互式菜单(选完即装) ----------
|
|
93
130
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
console.log(` ⚠️ 未知技能: ${notFound.join(", ")}`);
|
|
97
|
-
console.log(` 可用: ${[...pkgNames].join(", ")}\n`);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
131
|
+
async function startInteractiveMenu() {
|
|
132
|
+
const { checkbox } = await import("@inquirer/prompts");
|
|
100
133
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
console.log(` ⚠️ 以下技能尚未安装: ${notInstalled.join(", ")}`);
|
|
104
|
-
console.log(` 请先执行 npm i dk-frontend-skills 安装完整技能包\n`);
|
|
134
|
+
if (!fs.existsSync(pkgSkillsSource)) {
|
|
135
|
+
console.log(" ⚠️ 包内没有找到技能文件\n");
|
|
105
136
|
return;
|
|
106
137
|
}
|
|
107
138
|
|
|
108
|
-
const choices = userSkills.map((s) => ({
|
|
109
|
-
name: s.name,
|
|
110
|
-
enabled: names.includes(s.name) ? enable : s.enabled,
|
|
111
|
-
}));
|
|
112
|
-
|
|
113
|
-
saveSkillSelection(choices);
|
|
114
|
-
|
|
115
|
-
const action = enable ? "启用" : "停用";
|
|
116
|
-
console.log(` ✅ 已${action}: ${names.join(", ")}\n`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ---------- 交互式 TUI 菜单 ----------
|
|
120
|
-
|
|
121
|
-
function startInteractiveMenu() {
|
|
122
139
|
const pkgSkills = getPackageSkills(packageDir);
|
|
123
140
|
|
|
124
141
|
if (pkgSkills.length === 0) {
|
|
125
142
|
console.log(" ⚠️ 包内没有可用技能\n");
|
|
126
|
-
|
|
143
|
+
return;
|
|
127
144
|
}
|
|
128
145
|
|
|
129
|
-
//
|
|
146
|
+
// 读取已安装状态:已装且启用的默认勾选
|
|
130
147
|
const userSkills = getUserSkills(claudeDest);
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
148
|
+
const enabledSet = new Set(
|
|
149
|
+
userSkills.filter((s) => s.enabled).map((s) => s.name),
|
|
150
|
+
);
|
|
151
|
+
const installedSet = new Set(userSkills.map((s) => s.name));
|
|
152
|
+
|
|
153
|
+
// 首次运行全部默认勾选(省事),再次运行沿用已有状态
|
|
154
|
+
const hasExistingInstall = fs.existsSync(claudeDest);
|
|
155
|
+
const defaultChecked = hasExistingInstall
|
|
156
|
+
? enabledSet
|
|
157
|
+
: new Set(pkgSkills.map((s) => s.name));
|
|
158
|
+
|
|
159
|
+
const choices = pkgSkills.map((s) => ({
|
|
160
|
+
name: s.name,
|
|
161
|
+
value: s.name,
|
|
162
|
+
description: s.description || undefined,
|
|
163
|
+
checked: defaultChecked.has(s.name),
|
|
137
164
|
}));
|
|
138
165
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
stdout.write("╔══════════════════════════════════════════════╗\n");
|
|
147
|
-
stdout.write("║ dk-frontend-skills 技能选择 ║\n");
|
|
148
|
-
stdout.write("╠══════════════════════════════════════════════╣\n");
|
|
149
|
-
stdout.write("║ ↑↓ 导航 ␣ 开关 Enter 确认 q 退出 ║\n");
|
|
150
|
-
stdout.write("╚══════════════════════════════════════════════╝\n\n");
|
|
151
|
-
|
|
152
|
-
for (let i = 0; i < skills.length; i++) {
|
|
153
|
-
const s = skills[i];
|
|
154
|
-
const arrow = i === cursor ? "❯" : " ";
|
|
155
|
-
const check = toggled[i] ? "✓" : " ";
|
|
156
|
-
const name = s.name.padEnd(20);
|
|
157
|
-
const desc = s.description || "";
|
|
158
|
-
stdout.write(` ${arrow} [${check}] ${name} ${desc}\n`);
|
|
159
|
-
}
|
|
166
|
+
const selected = await checkbox({
|
|
167
|
+
message: "选择要安装的技能(未勾选的将从 .claude/ 中移除)",
|
|
168
|
+
instructions: "(↑↓ 导航, 空格 开关, Enter 确认)",
|
|
169
|
+
choices,
|
|
170
|
+
pageSize: 15,
|
|
171
|
+
loop: false,
|
|
172
|
+
});
|
|
160
173
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (selectedNames.length === 0) {
|
|
164
|
-
stdout.write("(无)");
|
|
165
|
-
} else if (selectedNames.length <= 3) {
|
|
166
|
-
stdout.write(selectedNames.join(", "));
|
|
167
|
-
} else {
|
|
168
|
-
stdout.write(`${selectedNames.length} 个技能`);
|
|
169
|
-
}
|
|
170
|
-
stdout.write("\n");
|
|
171
|
-
}
|
|
174
|
+
const newCount = selected.filter((name) => !installedSet.has(name)).length;
|
|
175
|
+
const removedCount = [...installedSet].filter((name) => !selected.includes(name)).length;
|
|
172
176
|
|
|
173
|
-
|
|
174
|
-
stdin.setRawMode(false);
|
|
175
|
-
stdin.pause();
|
|
176
|
-
if (saved) {
|
|
177
|
-
console.log("\n ✅ 技能选择已保存\n");
|
|
178
|
-
}
|
|
179
|
-
process.exit(0);
|
|
180
|
-
}
|
|
177
|
+
const installed = installSelectedSkills(selected);
|
|
181
178
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
stdin.on("keypress", (str, key) => {
|
|
189
|
-
if (!key) return;
|
|
190
|
-
|
|
191
|
-
if (key.name === "up") {
|
|
192
|
-
cursor = Math.max(0, cursor - 1);
|
|
193
|
-
render();
|
|
194
|
-
} else if (key.name === "down") {
|
|
195
|
-
cursor = Math.min(skills.length - 1, cursor + 1);
|
|
196
|
-
render();
|
|
197
|
-
} else if (key.name === "space") {
|
|
198
|
-
toggled[cursor] = !toggled[cursor];
|
|
199
|
-
render();
|
|
200
|
-
} else if (key.name === "return") {
|
|
201
|
-
const choices = skills.map((s, i) => ({ name: s.name, enabled: toggled[i] }));
|
|
202
|
-
saveSkillSelection(choices);
|
|
203
|
-
exitMenu(true);
|
|
204
|
-
} else if (key.name === "q" || (key.ctrl && key.name === "c")) {
|
|
205
|
-
exitMenu(false);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
179
|
+
const parts = [];
|
|
180
|
+
if (newCount > 0) parts.push(`新装 ${newCount} 个`);
|
|
181
|
+
if (removedCount > 0) parts.push(`移除 ${removedCount} 个`);
|
|
182
|
+
const summary = parts.length > 0 ? `(${parts.join(",")})` : "";
|
|
183
|
+
|
|
184
|
+
console.log(`\n ✅ 技能安装完成 ${summary}\n`);
|
|
208
185
|
}
|
|
209
186
|
|
|
210
187
|
// ---------- 主入口 ----------
|
|
211
188
|
|
|
212
189
|
const COMMANDS = ["list", "enable", "disable"];
|
|
213
190
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
191
|
+
async function main() {
|
|
192
|
+
const command = process.argv[2];
|
|
193
|
+
const args = process.argv.slice(3);
|
|
194
|
+
|
|
195
|
+
if (!command) {
|
|
196
|
+
await startInteractiveMenu();
|
|
197
|
+
} else if (command === "--help" || command === "-h") {
|
|
198
|
+
console.log(`
|
|
219
199
|
dk-frontend-skills CLI
|
|
220
200
|
|
|
221
201
|
用法:
|
|
222
|
-
npx dk-skills
|
|
223
|
-
npx dk-skills list
|
|
224
|
-
npx dk-skills
|
|
225
|
-
|
|
226
|
-
|
|
202
|
+
npx dk-frontend-skills 交互式选择并安装技能
|
|
203
|
+
npx dk-frontend-skills list 查看已安装的技能
|
|
204
|
+
npx dk-frontend-skills --help 显示帮助
|
|
205
|
+
|
|
206
|
+
首次运行会自动创建 .claude/ 目录,勾选要安装的技能即可
|
|
227
207
|
|
|
228
208
|
示例:
|
|
229
|
-
npx dk-skills
|
|
230
|
-
npx dk-skills
|
|
231
|
-
npx dk-skills disable moai-framework-electron
|
|
209
|
+
npx dk-frontend-skills
|
|
210
|
+
npx dk-frontend-skills list
|
|
232
211
|
`);
|
|
233
|
-
} else if (command === "list") {
|
|
234
|
-
|
|
235
|
-
} else if (command === "enable") {
|
|
236
|
-
|
|
237
|
-
} else
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
process.exit(1);
|
|
212
|
+
} else if (command === "list") {
|
|
213
|
+
await cmdList();
|
|
214
|
+
} else if (command === "enable" || command === "disable") {
|
|
215
|
+
console.log(` ⚠️ 该命令已废弃,请直接运行 npx dk-frontend-skills 使用交互菜单\n`);
|
|
216
|
+
} else {
|
|
217
|
+
console.log(` 未知命令: ${command}\n 可用: ${COMMANDS.join(", ")}\n`);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
242
220
|
}
|
|
221
|
+
|
|
222
|
+
main().catch(console.error);
|