mdk-skills 2.2.11 → 2.2.12

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdk-skills",
3
- "version": "2.2.11",
3
+ "version": "2.2.12",
4
4
  "description": "mdk-engineer - 沉稳靠谱的前端开发助手 Claude Skills 配置包,一键注入 .claude/ 技能目录和 CLAUDE.md 人设配置",
5
5
  "author": "XiaoMa",
6
6
  "license": "MIT",
@@ -668,7 +668,109 @@ async function handleApi(req, res) {
668
668
  }
669
669
  }
670
670
 
671
- return sendJSON(res, { ok: true, ...created });
671
+ // README.md 缺失状态
672
+ const readmeStatus = {
673
+ root: fs.existsSync(path.join(sourcePath, "README.md")),
674
+ skills: fs.existsSync(path.join(skillsDir, "README.md")),
675
+ };
676
+
677
+ return sendJSON(res, { ok: true, ...created, readmeStatus });
678
+ }
679
+
680
+ // POST /api/readme/create — 创建 README.md 文档
681
+ if (method === "POST" && pathname === "/api/readme/create") {
682
+ const body = await parseBody(req);
683
+ const settings = readSettings();
684
+ const sourcePath = settings._skill_source;
685
+ if (!sourcePath) return sendJSON(res, { error: "未绑定本地源" }, 400);
686
+
687
+ const { root, skills, projectName, teamName } = body;
688
+ const name = projectName || "我的技能仓库";
689
+ const team = teamName || "未设定";
690
+ const created = [];
691
+ const skipped = [];
692
+
693
+ // 获取技能列表
694
+ const skillsDir = path.join(sourcePath, ".claude", "skills");
695
+ const skillNames = fs.existsSync(skillsDir)
696
+ ? core.listSkillDirs(skillsDir)
697
+ : [];
698
+ const skillsList = skillNames.length
699
+ ? skillNames.map((s) => `- \`${s}\``).join("\n")
700
+ : "<!-- 暂无技能 -->";
701
+
702
+ // 根目录 README.md
703
+ if (root) {
704
+ const rootPath = path.join(sourcePath, "README.md");
705
+ if (!fs.existsSync(rootPath)) {
706
+ const tmpl = `# ${name}
707
+
708
+ > 维护团队:${team}
709
+
710
+ ## 简介
711
+
712
+ <!--
713
+ 在这里写这个技能仓库的简介。
714
+
715
+ 示例:
716
+ 本仓库包含面向 mdk-engineer 的前端开发技能集,涵盖 Vue3 组件开发、Chrome DevTools 使用、性能优化等方向。
717
+ -->
718
+
719
+ ## 技能列表
720
+
721
+ ${skillsList}
722
+
723
+ ## 使用方式
724
+
725
+ 本仓库通过 mdk-skills 管理技能,连接本地源后即可在 Web UI 中查看和安装技能。
726
+
727
+ ## License
728
+
729
+ <!-- 许可证信息 -->
730
+ `;
731
+ fs.writeFileSync(rootPath, tmpl, "utf-8");
732
+ created.push("README.md");
733
+ } else {
734
+ skipped.push("README.md");
735
+ }
736
+ }
737
+
738
+ // skills/README.md
739
+ if (skills) {
740
+ const skillsReadmePath = path.join(skillsDir, "README.md");
741
+ if (!fs.existsSync(skillsReadmePath)) {
742
+ const tmpl = `# 技能说明
743
+
744
+ ## 快速开始
745
+
746
+ <!--
747
+ 安装 mdk-skills 后连接本仓库,即可在 Web UI 中查看所有技能。
748
+ -->
749
+
750
+ ## 场景建议
751
+
752
+ <!--
753
+ 这里可以写哪些技能适合一起使用,不同开发场景下的推荐组合。
754
+
755
+ 示例:
756
+ - 日常开发:启用代码格式化、ESLint 校验、Git 辅助
757
+ - 性能优化:启用 Lighthouse、Bundle Analysis
758
+ -->
759
+
760
+ ## 注意事项
761
+
762
+ <!--
763
+ 技能使用过程中的注意事项。
764
+ -->
765
+ `;
766
+ fs.writeFileSync(skillsReadmePath, tmpl, "utf-8");
767
+ created.push("skills/README.md");
768
+ } else {
769
+ skipped.push("skills/README.md");
770
+ }
771
+ }
772
+
773
+ return sendJSON(res, { ok: true, created, skipped });
672
774
  }
673
775
 
674
776
  // GET /api/diagnose — 健康检查
@@ -26,7 +26,9 @@
26
26
 
27
27
  <!-- 内容区 -->
28
28
  <n-layout-content class="app-content">
29
- <router-view @refresh="loadStatus" />
29
+ <transition name="fade" mode="out-in">
30
+ <router-view @refresh="loadStatus" />
31
+ </transition>
30
32
  </n-layout-content>
31
33
  </n-layout>
32
34
  </n-config-provider>
@@ -100,4 +102,14 @@ onMounted(loadStatus);
100
102
 
101
103
  <style>
102
104
  @import "./styles/main.css";
105
+
106
+ .fade-enter-active,
107
+ .fade-leave-active {
108
+ transition: opacity 0.2s ease;
109
+ }
110
+
111
+ .fade-enter-from,
112
+ .fade-leave-to {
113
+ opacity: 0;
114
+ }
103
115
  </style>
@@ -101,3 +101,10 @@ export function getClaudeMd() {
101
101
  export function getSkillReadme(name) {
102
102
  return request("/skills/" + encodeURIComponent(name) + "/readme");
103
103
  }
104
+
105
+ export function createReadme(data) {
106
+ return request("/readme/create", {
107
+ method: "POST",
108
+ body: JSON.stringify(data),
109
+ });
110
+ }
@@ -9,8 +9,10 @@
9
9
  </div>
10
10
 
11
11
  <n-spin :show="loading">
12
- <div v-if="content" class="markdown-content" v-html="rendered" />
13
- <n-empty v-else description="暂无 CLAUDE.md 文档" />
12
+ <transition name="fade">
13
+ <div v-if="content" class="markdown-content" v-html="rendered" />
14
+ <n-empty v-else-if="loaded" description="暂无 CLAUDE.md 文档" />
15
+ </transition>
14
16
  </n-spin>
15
17
  </div>
16
18
  </template>
@@ -45,6 +47,7 @@ marked.use({
45
47
  });
46
48
 
47
49
  const loading = ref(false);
50
+ const loaded = ref(false);
48
51
  const content = ref(null);
49
52
  const rendered = ref("");
50
53
 
@@ -66,6 +69,7 @@ function renderMd(text) {
66
69
 
67
70
  async function loadClaudeMd() {
68
71
  loading.value = true;
72
+ loaded.value = false;
69
73
  try {
70
74
  const res = await getClaudeMd();
71
75
  if (res.content) {
@@ -79,6 +83,7 @@ async function loadClaudeMd() {
79
83
  content.value = null;
80
84
  } finally {
81
85
  loading.value = false;
86
+ loaded.value = true;
82
87
  }
83
88
  }
84
89
 
@@ -97,4 +102,14 @@ onMounted(loadClaudeMd);
97
102
  font-size: 20px;
98
103
  font-weight: 600;
99
104
  }
105
+
106
+ .fade-enter-active,
107
+ .fade-leave-active {
108
+ transition: opacity 0.2s ease;
109
+ }
110
+
111
+ .fade-enter-from,
112
+ .fade-leave-to {
113
+ opacity: 0;
114
+ }
100
115
  </style>
@@ -69,6 +69,51 @@
69
69
  </div>
70
70
  </n-card>
71
71
 
72
+ <!-- 创建 README.md 对话框 -->
73
+ <n-modal
74
+ v-model:show="showReadmeDialog"
75
+ title="创建文档"
76
+ preset="card"
77
+ style="width: 520px"
78
+ :mask-closable="false"
79
+ >
80
+ <div class="readme-dialog-desc">检测到缺少以下文档,是否创建?</div>
81
+
82
+ <n-checkbox v-model:checked="readmeForm.root" class="readme-checkbox">
83
+ <div class="readme-checkbox-content">
84
+ <span class="readme-checkbox-title">根目录 README.md</span>
85
+ <span class="readme-checkbox-desc">仪表盘 → "仓库说明"折叠面板,适合写项目简介、维护团队、许可证</span>
86
+ </div>
87
+ </n-checkbox>
88
+
89
+ <n-checkbox v-model:checked="readmeForm.skills" class="readme-checkbox">
90
+ <div class="readme-checkbox-content">
91
+ <span class="readme-checkbox-title">.claude/skills/README.md</span>
92
+ <span class="readme-checkbox-desc">场景切换 → "技能说明"折叠面板,适合写使用指南、场景搭配建议</span>
93
+ </div>
94
+ </n-checkbox>
95
+
96
+ <n-divider />
97
+
98
+ <n-form label-placement="top">
99
+ <n-form-item label="项目名称(选填)">
100
+ <n-input v-model:value="readmeForm.projectName" placeholder="我的技能仓库" />
101
+ </n-form-item>
102
+ <n-form-item label="维护团队(选填)">
103
+ <n-input v-model:value="readmeForm.teamName" placeholder="未设定" />
104
+ </n-form-item>
105
+ </n-form>
106
+
107
+ <template #footer>
108
+ <n-space justify="end">
109
+ <n-button @click="showReadmeDialog = false">跳过</n-button>
110
+ <n-button type="primary" :loading="creatingReadme" @click="onCreateReadme">
111
+ 创建
112
+ </n-button>
113
+ </n-space>
114
+ </template>
115
+ </n-modal>
116
+
72
117
  <!-- 健康检查 -->
73
118
  <n-card title="健康检查" size="small" class="section">
74
119
  <template #header-extra>
@@ -113,6 +158,7 @@ import {
113
158
  syncSource,
114
159
  initSource,
115
160
  diagnose,
161
+ createReadme,
116
162
  } from "../api/skills";
117
163
 
118
164
  const emit = defineEmits(["refresh"]);
@@ -127,6 +173,16 @@ const disconnecting = ref(false);
127
173
  const diagnosing = ref(false);
128
174
  const diagnosis = ref([]);
129
175
 
176
+ // 创建 README.md 对话框
177
+ const showReadmeDialog = ref(false);
178
+ const creatingReadme = ref(false);
179
+ const readmeForm = ref({
180
+ root: true,
181
+ skills: true,
182
+ projectName: "",
183
+ teamName: "",
184
+ });
185
+
130
186
  async function loadSource() {
131
187
  sourceInfo.value = await getSource();
132
188
  }
@@ -175,6 +231,17 @@ async function onInit() {
175
231
  if (res.settings) parts.push("settings.json");
176
232
  if (res.metaFiles > 0) parts.push(`${res.metaFiles} 个 .meta.json`);
177
233
  message.success(`初始化完成:${parts.join("、") || "无缺失文件"}`);
234
+
235
+ // 检测 README.md 缺失情况,弹创建对话框
236
+ if (res.readmeStatus) {
237
+ const anyMissing = !res.readmeStatus.root || !res.readmeStatus.skills;
238
+ if (anyMissing) {
239
+ readmeForm.value.root = !res.readmeStatus.root;
240
+ readmeForm.value.skills = !res.readmeStatus.skills;
241
+ showReadmeDialog.value = true;
242
+ }
243
+ }
244
+
178
245
  await loadSource();
179
246
  emit("refresh");
180
247
  } else {
@@ -187,6 +254,29 @@ async function onInit() {
187
254
  }
188
255
  }
189
256
 
257
+ async function onCreateReadme() {
258
+ creatingReadme.value = true;
259
+ try {
260
+ const res = await createReadme({
261
+ root: readmeForm.value.root,
262
+ skills: readmeForm.value.skills,
263
+ projectName: readmeForm.value.projectName.trim(),
264
+ teamName: readmeForm.value.teamName.trim(),
265
+ });
266
+ if (res.ok) {
267
+ const parts = res.created || [];
268
+ message.success(`已创建:${parts.join("、") || "无"}`);
269
+ showReadmeDialog.value = false;
270
+ } else {
271
+ message.error(res.error || "创建失败");
272
+ }
273
+ } catch {
274
+ message.error("创建失败");
275
+ } finally {
276
+ creatingReadme.value = false;
277
+ }
278
+ }
279
+
190
280
  async function onDisconnect() {
191
281
  disconnecting.value = true;
192
282
  try {
@@ -259,4 +349,32 @@ onMounted(loadSource);
259
349
  color: #d03050;
260
350
  font-size: 13px;
261
351
  }
352
+
353
+ .readme-dialog-desc {
354
+ margin-bottom: 16px;
355
+ font-size: 14px;
356
+ color: #555;
357
+ }
358
+
359
+ .readme-checkbox {
360
+ display: flex;
361
+ align-items: flex-start;
362
+ margin-bottom: 12px;
363
+ }
364
+
365
+ .readme-checkbox-content {
366
+ display: flex;
367
+ flex-direction: column;
368
+ gap: 2px;
369
+ }
370
+
371
+ .readme-checkbox-title {
372
+ font-size: 14px;
373
+ font-weight: 500;
374
+ }
375
+
376
+ .readme-checkbox-desc {
377
+ font-size: 12px;
378
+ color: #999;
379
+ }
262
380
  </style>