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
package/scripts/web-ui/server.js
CHANGED
|
@@ -668,7 +668,109 @@ async function handleApi(req, res) {
|
|
|
668
668
|
}
|
|
669
669
|
}
|
|
670
670
|
|
|
671
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
13
|
-
|
|
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>
|