mdk-skills 2.2.11 → 2.2.13

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.13",
4
4
  "description": "mdk-engineer - 沉稳靠谱的前端开发助手 Claude Skills 配置包,一键注入 .claude/ 技能目录和 CLAUDE.md 人设配置",
5
5
  "author": "XiaoMa",
6
6
  "license": "MIT",
@@ -497,10 +497,21 @@ async function handleApi(req, res) {
497
497
  }
498
498
  }
499
499
 
500
+ // README.md 缺失状态(独立于骨架文件)
501
+ let readmeStatus = null;
502
+ if (sourcePath) {
503
+ const skillsDir = path.join(sourcePath, ".claude", "skills");
504
+ readmeStatus = {
505
+ root: fs.existsSync(path.join(sourcePath, "README.md")),
506
+ skills: fs.existsSync(path.join(skillsDir, "README.md")),
507
+ };
508
+ }
509
+
500
510
  return sendJSON(res, {
501
511
  connected: !!sourcePath,
502
512
  path: sourcePath,
503
513
  needsInit,
514
+ readmeStatus,
504
515
  });
505
516
  }
506
517
 
@@ -668,7 +679,109 @@ async function handleApi(req, res) {
668
679
  }
669
680
  }
670
681
 
671
- return sendJSON(res, { ok: true, ...created });
682
+ // README.md 缺失状态
683
+ const readmeStatus = {
684
+ root: fs.existsSync(path.join(sourcePath, "README.md")),
685
+ skills: fs.existsSync(path.join(skillsDir, "README.md")),
686
+ };
687
+
688
+ return sendJSON(res, { ok: true, ...created, readmeStatus });
689
+ }
690
+
691
+ // POST /api/readme/create — 创建 README.md 文档
692
+ if (method === "POST" && pathname === "/api/readme/create") {
693
+ const body = await parseBody(req);
694
+ const settings = readSettings();
695
+ const sourcePath = settings._skill_source;
696
+ if (!sourcePath) return sendJSON(res, { error: "未绑定本地源" }, 400);
697
+
698
+ const { root, skills, projectName, teamName } = body;
699
+ const name = projectName || "我的技能仓库";
700
+ const team = teamName || "未设定";
701
+ const created = [];
702
+ const skipped = [];
703
+
704
+ // 获取技能列表
705
+ const skillsDir = path.join(sourcePath, ".claude", "skills");
706
+ const skillNames = fs.existsSync(skillsDir)
707
+ ? core.listSkillDirs(skillsDir)
708
+ : [];
709
+ const skillsList = skillNames.length
710
+ ? skillNames.map((s) => `- \`${s}\``).join("\n")
711
+ : "<!-- 暂无技能 -->";
712
+
713
+ // 根目录 README.md
714
+ if (root) {
715
+ const rootPath = path.join(sourcePath, "README.md");
716
+ if (!fs.existsSync(rootPath)) {
717
+ const tmpl = `# ${name}
718
+
719
+ > 维护团队:${team}
720
+
721
+ ## 简介
722
+
723
+ <!--
724
+ 在这里写这个技能仓库的简介。
725
+
726
+ 示例:
727
+ 本仓库包含面向 mdk-engineer 的前端开发技能集,涵盖 Vue3 组件开发、Chrome DevTools 使用、性能优化等方向。
728
+ -->
729
+
730
+ ## 技能列表
731
+
732
+ ${skillsList}
733
+
734
+ ## 使用方式
735
+
736
+ 本仓库通过 mdk-skills 管理技能,连接本地源后即可在 Web UI 中查看和安装技能。
737
+
738
+ ## License
739
+
740
+ <!-- 许可证信息 -->
741
+ `;
742
+ fs.writeFileSync(rootPath, tmpl, "utf-8");
743
+ created.push("README.md");
744
+ } else {
745
+ skipped.push("README.md");
746
+ }
747
+ }
748
+
749
+ // skills/README.md
750
+ if (skills) {
751
+ const skillsReadmePath = path.join(skillsDir, "README.md");
752
+ if (!fs.existsSync(skillsReadmePath)) {
753
+ const tmpl = `# 技能说明
754
+
755
+ ## 快速开始
756
+
757
+ <!--
758
+ 安装 mdk-skills 后连接本仓库,即可在 Web UI 中查看所有技能。
759
+ -->
760
+
761
+ ## 场景建议
762
+
763
+ <!--
764
+ 这里可以写哪些技能适合一起使用,不同开发场景下的推荐组合。
765
+
766
+ 示例:
767
+ - 日常开发:启用代码格式化、ESLint 校验、Git 辅助
768
+ - 性能优化:启用 Lighthouse、Bundle Analysis
769
+ -->
770
+
771
+ ## 注意事项
772
+
773
+ <!--
774
+ 技能使用过程中的注意事项。
775
+ -->
776
+ `;
777
+ fs.writeFileSync(skillsReadmePath, tmpl, "utf-8");
778
+ created.push("skills/README.md");
779
+ } else {
780
+ skipped.push("skills/README.md");
781
+ }
782
+ }
783
+
784
+ return sendJSON(res, { ok: true, created, skipped });
672
785
  }
673
786
 
674
787
  // 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>
@@ -31,6 +31,18 @@
31
31
  >
32
32
  <template #prefix>📁</template>
33
33
  </n-input>
34
+
35
+ <!-- 独立于 init:仅 README.md 缺失时的创建入口 -->
36
+ <n-alert
37
+ v-if="sourceInfo.connected && sourceInfo.needsInit === false && hasReadmeMissing"
38
+ type="info"
39
+ :bordered="false"
40
+ class="init-alert"
41
+ >
42
+ <template #header>缺少 README.md 文档</template>
43
+ 创建后将生成模板文件,可在对应目录中自行编辑
44
+ </n-alert>
45
+
34
46
  <div class="action-buttons">
35
47
  <n-button
36
48
  v-if="!sourceInfo.connected"
@@ -56,6 +68,13 @@
56
68
  >
57
69
  同步到仓库
58
70
  </n-button>
71
+ <n-button
72
+ v-if="sourceInfo.connected && sourceInfo.needsInit === false && hasReadmeMissing"
73
+ ghost
74
+ @click="onShowCreateReadme"
75
+ >
76
+ 创建文档
77
+ </n-button>
59
78
  <n-button
60
79
  v-if="sourceInfo.connected"
61
80
  type="error"
@@ -69,6 +88,51 @@
69
88
  </div>
70
89
  </n-card>
71
90
 
91
+ <!-- 创建 README.md 对话框 -->
92
+ <n-modal
93
+ v-model:show="showReadmeDialog"
94
+ title="创建文档"
95
+ preset="card"
96
+ style="width: 520px"
97
+ :mask-closable="false"
98
+ >
99
+ <div class="readme-dialog-desc">检测到缺少以下文档,是否创建?</div>
100
+
101
+ <n-checkbox v-model:checked="readmeForm.root" class="readme-checkbox">
102
+ <div class="readme-checkbox-content">
103
+ <span class="readme-checkbox-title">根目录 README.md</span>
104
+ <span class="readme-checkbox-desc">仪表盘 → "仓库说明"折叠面板,适合写项目简介、维护团队、许可证</span>
105
+ </div>
106
+ </n-checkbox>
107
+
108
+ <n-checkbox v-model:checked="readmeForm.skills" class="readme-checkbox">
109
+ <div class="readme-checkbox-content">
110
+ <span class="readme-checkbox-title">.claude/skills/README.md</span>
111
+ <span class="readme-checkbox-desc">场景切换 → "技能说明"折叠面板,适合写使用指南、场景搭配建议</span>
112
+ </div>
113
+ </n-checkbox>
114
+
115
+ <n-divider />
116
+
117
+ <n-form label-placement="top">
118
+ <n-form-item label="项目名称(选填)">
119
+ <n-input v-model:value="readmeForm.projectName" placeholder="我的技能仓库" />
120
+ </n-form-item>
121
+ <n-form-item label="维护团队(选填)">
122
+ <n-input v-model:value="readmeForm.teamName" placeholder="未设定" />
123
+ </n-form-item>
124
+ </n-form>
125
+
126
+ <template #footer>
127
+ <n-space justify="end">
128
+ <n-button @click="showReadmeDialog = false">跳过</n-button>
129
+ <n-button type="primary" :loading="creatingReadme" @click="onCreateReadme">
130
+ 创建
131
+ </n-button>
132
+ </n-space>
133
+ </template>
134
+ </n-modal>
135
+
72
136
  <!-- 健康检查 -->
73
137
  <n-card title="健康检查" size="small" class="section">
74
138
  <template #header-extra>
@@ -100,7 +164,7 @@
100
164
  </template>
101
165
 
102
166
  <script setup>
103
- import { ref, onMounted } from "vue";
167
+ import { ref, computed, onMounted } from "vue";
104
168
  import { useMessage } from "naive-ui";
105
169
  import {
106
170
  CheckmarkCircleOutline,
@@ -113,6 +177,7 @@ import {
113
177
  syncSource,
114
178
  initSource,
115
179
  diagnose,
180
+ createReadme,
116
181
  } from "../api/skills";
117
182
 
118
183
  const emit = defineEmits(["refresh"]);
@@ -127,6 +192,21 @@ const disconnecting = ref(false);
127
192
  const diagnosing = ref(false);
128
193
  const diagnosis = ref([]);
129
194
 
195
+ // 创建 README.md 对话框
196
+ const showReadmeDialog = ref(false);
197
+ const creatingReadme = ref(false);
198
+ const readmeForm = ref({
199
+ root: true,
200
+ skills: true,
201
+ projectName: "",
202
+ teamName: "",
203
+ });
204
+
205
+ const hasReadmeMissing = computed(() => {
206
+ if (!sourceInfo.value.readmeStatus) return false;
207
+ return !sourceInfo.value.readmeStatus.root || !sourceInfo.value.readmeStatus.skills;
208
+ });
209
+
130
210
  async function loadSource() {
131
211
  sourceInfo.value = await getSource();
132
212
  }
@@ -175,6 +255,17 @@ async function onInit() {
175
255
  if (res.settings) parts.push("settings.json");
176
256
  if (res.metaFiles > 0) parts.push(`${res.metaFiles} 个 .meta.json`);
177
257
  message.success(`初始化完成:${parts.join("、") || "无缺失文件"}`);
258
+
259
+ // 检测 README.md 缺失情况,弹创建对话框
260
+ if (res.readmeStatus) {
261
+ const anyMissing = !res.readmeStatus.root || !res.readmeStatus.skills;
262
+ if (anyMissing) {
263
+ readmeForm.value.root = !res.readmeStatus.root;
264
+ readmeForm.value.skills = !res.readmeStatus.skills;
265
+ showReadmeDialog.value = true;
266
+ }
267
+ }
268
+
178
269
  await loadSource();
179
270
  emit("refresh");
180
271
  } else {
@@ -187,6 +278,35 @@ async function onInit() {
187
278
  }
188
279
  }
189
280
 
281
+ async function onCreateReadme() {
282
+ creatingReadme.value = true;
283
+ try {
284
+ const res = await createReadme({
285
+ root: readmeForm.value.root,
286
+ skills: readmeForm.value.skills,
287
+ projectName: readmeForm.value.projectName.trim(),
288
+ teamName: readmeForm.value.teamName.trim(),
289
+ });
290
+ if (res.ok) {
291
+ const parts = res.created || [];
292
+ message.success(`已创建:${parts.join("、") || "无"}`);
293
+ showReadmeDialog.value = false;
294
+ } else {
295
+ message.error(res.error || "创建失败");
296
+ }
297
+ } catch {
298
+ message.error("创建失败");
299
+ } finally {
300
+ creatingReadme.value = false;
301
+ }
302
+ }
303
+
304
+ function onShowCreateReadme() {
305
+ readmeForm.value.root = !sourceInfo.value.readmeStatus?.root;
306
+ readmeForm.value.skills = !sourceInfo.value.readmeStatus?.skills;
307
+ showReadmeDialog.value = true;
308
+ }
309
+
190
310
  async function onDisconnect() {
191
311
  disconnecting.value = true;
192
312
  try {
@@ -259,4 +379,32 @@ onMounted(loadSource);
259
379
  color: #d03050;
260
380
  font-size: 13px;
261
381
  }
382
+
383
+ .readme-dialog-desc {
384
+ margin-bottom: 16px;
385
+ font-size: 14px;
386
+ color: #555;
387
+ }
388
+
389
+ .readme-checkbox {
390
+ display: flex;
391
+ align-items: flex-start;
392
+ margin-bottom: 12px;
393
+ }
394
+
395
+ .readme-checkbox-content {
396
+ display: flex;
397
+ flex-direction: column;
398
+ gap: 2px;
399
+ }
400
+
401
+ .readme-checkbox-title {
402
+ font-size: 14px;
403
+ font-weight: 500;
404
+ }
405
+
406
+ .readme-checkbox-desc {
407
+ font-size: 12px;
408
+ color: #999;
409
+ }
262
410
  </style>