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.
Files changed (2) hide show
  1. package/package.json +5 -1
  2. 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.0.0",
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
- const projectRoot = path.resolve(__dirname, "..", "..", "..");
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
- const command = process.argv[2];
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
- * 保存技能选择结果到 settings.json
34
- */
35
- function saveSkillSelection(choices) {
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 { name, enabled } of choices) {
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, installed: 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("\n📋 dk-frontend-skills 技能清单\n");
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
- const name = skill.name.padEnd(20);
73
- const ver = skill.version.padEnd(7);
74
- const desc = skill.description || "(无描述)";
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 进入交互选择模式\n`);
126
+ console.log(`\n ${chalk.cyan("💡")} ${chalk.dim("运行 npx dk-frontend-skills 进入交互选择模式")}\n`);
79
127
  }
80
128
 
81
- // ---------- 命令:enable / disable ----------
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
- const notFound = names.filter((n) => !pkgNames.has(n));
95
- if (notFound.length > 0) {
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
- const notInstalled = names.filter((n) => !userNames.has(n));
102
- if (notInstalled.length > 0) {
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
- process.exit(0);
143
+ return;
127
144
  }
128
145
 
129
- // 读取当前 settings 中的启用状态
146
+ // 读取已安装状态:已装且启用的默认勾选
130
147
  const userSkills = getUserSkills(claudeDest);
131
- const userMap = new Map(userSkills.map((s) => [s.name, s.enabled]));
132
-
133
- // choices 只包含包内有的技能
134
- const skills = pkgSkills.map((s) => ({
135
- ...s,
136
- enabled: userMap.has(s.name) ? userMap.get(s.name) : false,
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
- let cursor = 0;
140
- const toggled = skills.map((s) => s.enabled);
141
- const stdin = process.stdin;
142
- const stdout = process.stdout;
143
-
144
- function render() {
145
- stdout.write("\x1Bc"); // 清屏
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
- stdout.write("\n 选中: ");
162
- const selectedNames = skills.filter((_, i) => toggled[i]).map((s) => s.name);
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
- function exitMenu(saved) {
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
- render();
183
-
184
- readline.emitKeypressEvents(stdin);
185
- if (stdin.isTTY) stdin.setRawMode(true);
186
- stdin.resume();
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
- if (!command) {
215
- // 无参数 启动交互菜单
216
- startInteractiveMenu();
217
- } else if (command === "--help" || command === "-h") {
218
- console.log(`
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 enable <技能名...> 启用指定技能
225
- npx dk-skills disable <技能名...> 停用指定技能
226
- npx dk-skills --help 显示帮助
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 list
230
- npx dk-skills enable vue fe-biz-patterns
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
- cmdList();
235
- } else if (command === "enable") {
236
- cmdToggle(true, args);
237
- } else if (command === "disable") {
238
- cmdToggle(false, args);
239
- } else {
240
- console.log(` 未知命令: ${command}\n 可用: ${COMMANDS.join(", ")}\n`);
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);