mdk-skills 2.2.25 → 2.3.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/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/.claude/skills/frontend-design/SKILL.md +42 -0
- package/package.json +1 -1
- package/scripts/web-ui/dist/assets/{index-DanvMbHP.js → index--qLTFieg.js} +26 -26
- package/scripts/web-ui/dist/assets/index-DhB4kj3N.css +1 -0
- package/scripts/web-ui/dist/index.html +2 -2
- package/scripts/web-ui/server.js +180 -0
- package/scripts/web-ui/src/api/skills.js +36 -13
- package/scripts/web-ui/src/views/Dashboard.vue +347 -9
- package/.claude/skills/template/.meta.json +0 -5
- package/.claude/skills/template/SKILL.md +0 -6
- package/scripts/web-ui/dist/assets/index-B3SLlTdd.css +0 -1
|
@@ -56,14 +56,31 @@
|
|
|
56
56
|
</div>
|
|
57
57
|
|
|
58
58
|
<n-spin :show="loading">
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
<div v-for="group in groupedSkills" :key="group.key" class="skill-group">
|
|
60
|
+
<div class="group-header" @click="toggleGroup(group.key)">
|
|
61
|
+
<span class="fold-arrow" :class="{ open: groupOpen[group.key] }">▶</span>
|
|
62
|
+
<span class="group-label">{{ group.type === 'remote' ? group.url : '本地技能' }}</span>
|
|
63
|
+
<span class="group-count">{{ group.skills.length }} 个</span>
|
|
64
|
+
<n-button
|
|
65
|
+
v-if="group.type === 'remote'"
|
|
66
|
+
size="tiny"
|
|
67
|
+
@click.stop="batchUpdateGroup(group)"
|
|
68
|
+
class="group-update-btn"
|
|
69
|
+
>
|
|
70
|
+
全部更新
|
|
71
|
+
</n-button>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="group-body" v-show="groupOpen[group.key]">
|
|
74
|
+
<SkillCard
|
|
75
|
+
v-for="skill in group.skills"
|
|
76
|
+
:key="skill.name"
|
|
77
|
+
:skill="skill"
|
|
78
|
+
:usages="getUsage(skill.name)"
|
|
79
|
+
@refresh="loadSkills"
|
|
80
|
+
@click="showSkillDetail(skill)"
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
67
84
|
</n-spin>
|
|
68
85
|
|
|
69
86
|
<n-empty v-if="!loading && skills.length === 0" description="暂无技能数据">
|
|
@@ -102,6 +119,38 @@
|
|
|
102
119
|
<n-button size="small" type="primary" @click="saveMeta" :loading="savingMeta">保存</n-button>
|
|
103
120
|
<n-button size="small" type="error" @click="handleDelete">删除技能</n-button>
|
|
104
121
|
</div>
|
|
122
|
+
<!-- 来源信息 -->
|
|
123
|
+
<div v-if="skillSource" class="source-info">
|
|
124
|
+
<template v-if="skillSource.type === 'remote'">
|
|
125
|
+
<div class="source-row">
|
|
126
|
+
<span class="source-label">来源</span>
|
|
127
|
+
<span class="source-value">{{ skillSource.url }}</span>
|
|
128
|
+
</div>
|
|
129
|
+
<div class="source-row">
|
|
130
|
+
<span class="source-label">拉取时间</span>
|
|
131
|
+
<span class="source-value">{{ new Date(skillSource.pulledAt).toLocaleString() }}</span>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="source-actions">
|
|
134
|
+
<n-button size="small" @click="handleUpdate" :loading="updatingSkill">检查更新</n-button>
|
|
135
|
+
</div>
|
|
136
|
+
</template>
|
|
137
|
+
<template v-else>
|
|
138
|
+
<div class="source-row">
|
|
139
|
+
<span class="source-label">类型</span>
|
|
140
|
+
<span class="source-value local-tag">本地技能</span>
|
|
141
|
+
</div>
|
|
142
|
+
<div v-if="skillSource.path" class="source-row">
|
|
143
|
+
<span class="source-label">路径</span>
|
|
144
|
+
<span class="source-value path-text">{{ skillSource.path }}</span>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="source-actions">
|
|
147
|
+
<n-button size="small" @click="openDir">打开目录</n-button>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="local-hint">
|
|
150
|
+
本地技能暂不支持远程更新。推送到 Git 仓库后可使用"拉取"功能分享给团队。
|
|
151
|
+
</div>
|
|
152
|
+
</template>
|
|
153
|
+
</div>
|
|
105
154
|
<n-divider style="margin: 12px 0" />
|
|
106
155
|
</div>
|
|
107
156
|
|
|
@@ -160,6 +209,37 @@
|
|
|
160
209
|
</div>
|
|
161
210
|
</div>
|
|
162
211
|
</n-modal>
|
|
212
|
+
|
|
213
|
+
<!-- 同源兄弟更新提示 -->
|
|
214
|
+
<n-modal
|
|
215
|
+
:show="!!updatedSiblings"
|
|
216
|
+
title="同源技能更新"
|
|
217
|
+
preset="card"
|
|
218
|
+
style="width: 480px"
|
|
219
|
+
:mask-closable="true"
|
|
220
|
+
@update:show="(v) => { if (!v) updatedSiblings = null; }"
|
|
221
|
+
>
|
|
222
|
+
<div v-if="updatedSiblings" class="siblings-body">
|
|
223
|
+
<p class="siblings-desc">
|
|
224
|
+
<strong>{{ updatedSiblings.name }}</strong> 已更新。同仓库还有以下技能,是否一起更新?
|
|
225
|
+
</p>
|
|
226
|
+
<div class="siblings-list">
|
|
227
|
+
<n-checkbox
|
|
228
|
+
v-for="name in updatedSiblings.siblings"
|
|
229
|
+
:key="name"
|
|
230
|
+
v-model:checked="siblingCheck[name]"
|
|
231
|
+
>
|
|
232
|
+
{{ name }}
|
|
233
|
+
</n-checkbox>
|
|
234
|
+
</div>
|
|
235
|
+
<div class="siblings-actions">
|
|
236
|
+
<n-button size="small" @click="updatedSiblings = null">跳过</n-button>
|
|
237
|
+
<n-button size="small" type="primary" @click="handleSiblingUpdate" :loading="updatingSiblings">
|
|
238
|
+
更新选中
|
|
239
|
+
</n-button>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
</n-modal>
|
|
163
243
|
</div>
|
|
164
244
|
</template>
|
|
165
245
|
|
|
@@ -170,7 +250,7 @@ import { RefreshOutline } from "@vicons/ionicons5";
|
|
|
170
250
|
import { marked } from "marked";
|
|
171
251
|
import hljs from "highlight.js";
|
|
172
252
|
import SkillCard from "../components/SkillCard.vue";
|
|
173
|
-
import { getSkills, getReadme, getSkillReadme, updateSkillMeta, deleteSkill, pullSkills, installSkills } from "../api/skills";
|
|
253
|
+
import { getSkills, getReadme, getSkillReadme, updateSkillMeta, deleteSkill, pullSkills, installSkills, getSkillSource, updateSkill, batchUpdateSkills, openSkillDir } from "../api/skills";
|
|
174
254
|
import { sortSkills, getUsageMap } from "../utils/usage";
|
|
175
255
|
|
|
176
256
|
// marked 配置:代码高亮 + 外链安全
|
|
@@ -220,6 +300,43 @@ const editDescription = ref("");
|
|
|
220
300
|
const editTags = ref([]);
|
|
221
301
|
const savingMeta = ref(false);
|
|
222
302
|
|
|
303
|
+
// 分组折叠
|
|
304
|
+
const groupOpen = ref({});
|
|
305
|
+
const toggleGroup = (key) => {
|
|
306
|
+
groupOpen.value[key] = !groupOpen.value[key];
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// 技能来源 & 更新
|
|
310
|
+
const skillSource = ref(null);
|
|
311
|
+
const updatingSkill = ref(false);
|
|
312
|
+
|
|
313
|
+
// 同源兄弟提示
|
|
314
|
+
const updatedSiblings = ref(null);
|
|
315
|
+
const siblingCheck = ref({});
|
|
316
|
+
const updatingSiblings = ref(false);
|
|
317
|
+
|
|
318
|
+
async function handleSiblingUpdate() {
|
|
319
|
+
const selected = Object.keys(siblingCheck.value).filter((k) => siblingCheck.value[k]);
|
|
320
|
+
if (selected.length === 0) {
|
|
321
|
+
message.info("未选择任何技能");
|
|
322
|
+
updatedSiblings.value = null;
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
updatingSiblings.value = true;
|
|
326
|
+
try {
|
|
327
|
+
const res = await batchUpdateSkills(selected, updatedSiblings.value.url);
|
|
328
|
+
if (res.ok) {
|
|
329
|
+
message.success(`${res.updated.length} 个技能已更新`);
|
|
330
|
+
await loadSkills();
|
|
331
|
+
}
|
|
332
|
+
} catch {
|
|
333
|
+
message.error("更新失败");
|
|
334
|
+
} finally {
|
|
335
|
+
updatingSiblings.value = false;
|
|
336
|
+
updatedSiblings.value = null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
223
340
|
// 标签筛选 & 排序
|
|
224
341
|
const selectedTags = ref([]);
|
|
225
342
|
const sortBy = ref("name");
|
|
@@ -311,6 +428,33 @@ const displaySkills = computed(() => {
|
|
|
311
428
|
return sortSkills(filtered, sortBy.value);
|
|
312
429
|
});
|
|
313
430
|
|
|
431
|
+
const groupedSkills = computed(() => {
|
|
432
|
+
const groups = {};
|
|
433
|
+
for (const skill of displaySkills.value) {
|
|
434
|
+
const isRemote = skill.source?.type === "remote";
|
|
435
|
+
const key = isRemote ? skill.source.url : "__local__";
|
|
436
|
+
if (!groups[key]) {
|
|
437
|
+
groups[key] = {
|
|
438
|
+
key,
|
|
439
|
+
type: isRemote ? "remote" : "local",
|
|
440
|
+
url: skill.source?.url || "",
|
|
441
|
+
skills: [],
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
groups[key].skills.push(skill);
|
|
445
|
+
}
|
|
446
|
+
// 默认展开所有分组
|
|
447
|
+
for (const key of Object.keys(groups)) {
|
|
448
|
+
if (groupOpen.value[key] === undefined) groupOpen.value[key] = true;
|
|
449
|
+
}
|
|
450
|
+
// 远程分组排前面,本地排后面
|
|
451
|
+
return Object.values(groups).sort((a, b) => {
|
|
452
|
+
if (a.type === "remote" && b.type === "local") return -1;
|
|
453
|
+
if (a.type === "local" && b.type === "remote") return 1;
|
|
454
|
+
return 0;
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
314
458
|
function toggleTag(tag) {
|
|
315
459
|
const idx = selectedTags.value.indexOf(tag);
|
|
316
460
|
if (idx === -1) selectedTags.value.push(tag);
|
|
@@ -368,9 +512,15 @@ async function showSkillDetail(skill) {
|
|
|
368
512
|
detailLoading.value = true;
|
|
369
513
|
detailContent.value = null;
|
|
370
514
|
renderedDetail.value = "";
|
|
515
|
+
skillSource.value = null;
|
|
371
516
|
editVersion.value = skill.version || "1.0.0";
|
|
372
517
|
editDescription.value = skill.description || "";
|
|
373
518
|
editTags.value = [...(skill.tags || [])];
|
|
519
|
+
// 查来源
|
|
520
|
+
try {
|
|
521
|
+
const res = await getSkillSource(skill.name);
|
|
522
|
+
skillSource.value = res;
|
|
523
|
+
} catch { /* ignore */ }
|
|
374
524
|
try {
|
|
375
525
|
const res = await getSkillReadme(skill.name);
|
|
376
526
|
if (res.content) {
|
|
@@ -385,6 +535,61 @@ async function showSkillDetail(skill) {
|
|
|
385
535
|
}
|
|
386
536
|
}
|
|
387
537
|
|
|
538
|
+
async function handleUpdate() {
|
|
539
|
+
if (!detailSkill.value) return;
|
|
540
|
+
updatingSkill.value = true;
|
|
541
|
+
try {
|
|
542
|
+
const res = await updateSkill(detailSkill.value.name);
|
|
543
|
+
if (res.ok) {
|
|
544
|
+
message.success("技能已更新到最新版本");
|
|
545
|
+
if (res.siblings && res.siblings.length > 0) {
|
|
546
|
+
updatedSiblings.value = {
|
|
547
|
+
name: detailSkill.value.name,
|
|
548
|
+
url: skillSource.value?.url || "",
|
|
549
|
+
siblings: res.siblings,
|
|
550
|
+
};
|
|
551
|
+
const sel = {};
|
|
552
|
+
res.siblings.forEach((n) => { sel[n] = true; });
|
|
553
|
+
siblingCheck.value = sel;
|
|
554
|
+
}
|
|
555
|
+
skillSource.value = { type: "remote", url: res.url, pulledAt: res.updatedAt };
|
|
556
|
+
await loadSkills();
|
|
557
|
+
} else {
|
|
558
|
+
message.error(res.error || "更新失败");
|
|
559
|
+
}
|
|
560
|
+
} catch {
|
|
561
|
+
message.error("更新失败");
|
|
562
|
+
} finally {
|
|
563
|
+
updatingSkill.value = false;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async function batchUpdateGroup(group) {
|
|
568
|
+
const ok = window.confirm(`确定更新 "${group.url}" 下的 ${group.skills.length} 个技能吗?`);
|
|
569
|
+
if (!ok) return;
|
|
570
|
+
try {
|
|
571
|
+
const names = group.skills.map((s) => s.name);
|
|
572
|
+
const res = await batchUpdateSkills(names, group.url);
|
|
573
|
+
if (res.ok) {
|
|
574
|
+
message.success(`${res.updated.length} 个技能已更新`);
|
|
575
|
+
if (res.notFound && res.notFound.length > 0) {
|
|
576
|
+
message.warning(`${res.notFound.length} 个技能未找到,已跳过`);
|
|
577
|
+
}
|
|
578
|
+
await loadSkills();
|
|
579
|
+
} else {
|
|
580
|
+
message.error(res.error || "批量更新失败");
|
|
581
|
+
}
|
|
582
|
+
} catch {
|
|
583
|
+
message.error("批量更新失败");
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function openDir() {
|
|
588
|
+
if (!detailSkill.value) return;
|
|
589
|
+
openSkillDir(detailSkill.value.name);
|
|
590
|
+
message.info("已打开技能目录");
|
|
591
|
+
}
|
|
592
|
+
|
|
388
593
|
async function saveMeta() {
|
|
389
594
|
if (!detailSkill.value) return;
|
|
390
595
|
savingMeta.value = true;
|
|
@@ -577,4 +782,137 @@ onUnmounted(() => document.removeEventListener("visibilitychange", onFocus));
|
|
|
577
782
|
justify-content: flex-end;
|
|
578
783
|
gap: 8px;
|
|
579
784
|
}
|
|
785
|
+
|
|
786
|
+
/* 分组折叠 */
|
|
787
|
+
.skill-group {
|
|
788
|
+
margin-bottom: 4px;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.group-header {
|
|
792
|
+
display: flex;
|
|
793
|
+
align-items: center;
|
|
794
|
+
gap: 8px;
|
|
795
|
+
padding: 8px 4px;
|
|
796
|
+
cursor: pointer;
|
|
797
|
+
border-radius: 4px;
|
|
798
|
+
user-select: none;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
.group-header:hover {
|
|
802
|
+
background: rgba(0, 0, 0, 0.03);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
.fold-arrow {
|
|
806
|
+
font-size: 10px;
|
|
807
|
+
transition: transform 0.2s;
|
|
808
|
+
color: #999;
|
|
809
|
+
flex-shrink: 0;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.fold-arrow.open {
|
|
813
|
+
transform: rotate(90deg);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.group-label {
|
|
817
|
+
font-size: 13px;
|
|
818
|
+
font-weight: 600;
|
|
819
|
+
color: inherit;
|
|
820
|
+
overflow: hidden;
|
|
821
|
+
text-overflow: ellipsis;
|
|
822
|
+
white-space: nowrap;
|
|
823
|
+
flex: 1;
|
|
824
|
+
min-width: 0;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.group-count {
|
|
828
|
+
font-size: 12px;
|
|
829
|
+
color: #999;
|
|
830
|
+
flex-shrink: 0;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
.group-update-btn {
|
|
834
|
+
flex-shrink: 0;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
.group-body {
|
|
838
|
+
padding-left: 4px;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/* 来源信息 */
|
|
842
|
+
.source-info {
|
|
843
|
+
margin-bottom: 4px;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.source-row {
|
|
847
|
+
display: flex;
|
|
848
|
+
align-items: center;
|
|
849
|
+
gap: 10px;
|
|
850
|
+
margin-bottom: 6px;
|
|
851
|
+
font-size: 13px;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.source-label {
|
|
855
|
+
width: 70px;
|
|
856
|
+
color: #888;
|
|
857
|
+
flex-shrink: 0;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.source-value {
|
|
861
|
+
word-break: break-all;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
.local-tag {
|
|
865
|
+
display: inline-block;
|
|
866
|
+
padding: 1px 8px;
|
|
867
|
+
border-radius: 3px;
|
|
868
|
+
font-size: 12px;
|
|
869
|
+
background: #e8f5e9;
|
|
870
|
+
color: #2e7d32;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
.path-text {
|
|
874
|
+
font-family: monospace;
|
|
875
|
+
font-size: 12px;
|
|
876
|
+
color: #666;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.source-actions {
|
|
880
|
+
display: flex;
|
|
881
|
+
gap: 8px;
|
|
882
|
+
margin-top: 8px;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
.local-hint {
|
|
886
|
+
margin-top: 8px;
|
|
887
|
+
font-size: 12px;
|
|
888
|
+
color: #999;
|
|
889
|
+
line-height: 1.5;
|
|
890
|
+
padding: 8px;
|
|
891
|
+
background: rgba(0, 0, 0, 0.02);
|
|
892
|
+
border-radius: 4px;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/* 同源兄弟 */
|
|
896
|
+
.siblings-body {
|
|
897
|
+
min-height: 60px;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
.siblings-desc {
|
|
901
|
+
font-size: 13px;
|
|
902
|
+
margin-bottom: 12px;
|
|
903
|
+
line-height: 1.6;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.siblings-list {
|
|
907
|
+
display: flex;
|
|
908
|
+
flex-direction: column;
|
|
909
|
+
gap: 8px;
|
|
910
|
+
margin-bottom: 16px;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
.siblings-actions {
|
|
914
|
+
display: flex;
|
|
915
|
+
justify-content: flex-end;
|
|
916
|
+
gap: 8px;
|
|
917
|
+
}
|
|
580
918
|
</style>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.status-bar[data-v-bc9519f9]{background:#fff;border-bottom:1px solid #e5e7eb;flex-wrap:wrap;align-items:center;gap:12px;padding:10px 24px;display:flex}.status-item[data-v-bc9519f9]{color:#555;align-items:center;gap:6px;font-size:13px;display:flex}.status-item.clickable[data-v-bc9519f9]{cursor:pointer}.status-item.clickable[data-v-bc9519f9]:hover{color:#2080f0}.status-divider[data-v-bc9519f9]{background:#e0e0e0;width:1px;height:20px}[data-theme=dark] .status-bar{background:#1a1a2e;border-bottom-color:#333}[data-theme=dark] .status-item{color:#aaa}[data-theme=dark] .status-divider{background:#333}*{box-sizing:border-box;margin:0;padding:0}html,body,#app{background:#f5f7fa;height:100%}body{overflow-y:auto}*{scrollbar-width:none;-ms-overflow-style:none}::-webkit-scrollbar{display:none}.app-layout{flex-direction:column;min-height:100vh;padding-top:56px;display:flex}.app-header{z-index:100;background:#fff;border-bottom:1px solid #e5e7eb;align-items:center;height:56px;padding:0 24px;display:flex;position:fixed;top:0;left:0;right:0}.header-inner{justify-content:space-between;align-items:center;width:100%;display:flex}.header-left{align-items:center;gap:32px;display:flex}.logo{color:#1a1a1a;white-space:nowrap;font-size:18px;font-weight:700}.app-content{flex:1;width:100%;max-width:1000px;margin:0 auto;padding:24px}.markdown-content{color:#333;word-wrap:break-word;font-size:14px;line-height:1.7}.markdown-content h1{border-bottom:1px solid #eee;margin:20px 0 12px;padding-bottom:8px;font-size:22px;font-weight:700}.markdown-content h2{border-bottom:1px solid #eee;margin:18px 0 10px;padding-bottom:6px;font-size:18px;font-weight:700}.markdown-content h3{margin:16px 0 8px;font-size:16px;font-weight:600}.markdown-content h4{margin:14px 0 6px;font-size:14px;font-weight:600}.markdown-content h5,.markdown-content h6{margin:12px 0 6px;font-size:13px;font-weight:600}.markdown-content p{margin:8px 0}.markdown-content ul,.markdown-content ol{margin:8px 0;padding-left:24px}.markdown-content li{margin:4px 0}.markdown-content blockquote{color:#555;background:#f0f7ff;border-left:4px solid #2080f0;margin:10px 0;padding:8px 16px}.markdown-content blockquote p{margin:4px 0}.markdown-content a{color:#2080f0;text-decoration:none}.markdown-content a:hover{text-decoration:underline}.markdown-content hr{border:none;border-top:1px solid #e0e0e0;margin:16px 0}.markdown-content table{border-collapse:collapse;width:100%;margin:12px 0;font-size:13px}.markdown-content th,.markdown-content td{text-align:left;border:1px solid #e0e0e0;padding:8px 12px}.markdown-content th{background:#f5f7fa;font-weight:600}.markdown-content tr:nth-child(2n) td{background:#fafafa}.markdown-content img{border-radius:4px;max-width:100%;margin:8px 0}.markdown-content code{color:#d63384;background:#f0f0f0;border-radius:3px;padding:2px 6px;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:13px}.markdown-content pre{border:1px solid #e8e8e8;border-radius:6px;margin:12px 0;padding:0;position:relative;overflow:hidden}.markdown-content pre code{color:#333;tab-size:2;background:#f8f9fa;padding:14px 16px;font-size:13px;line-height:1.5;display:block;overflow-x:auto}.markdown-content pre .copy-btn{color:#999;cursor:pointer;opacity:0;background:#fff;border:1px solid #e0e0e0;border-radius:4px;padding:3px 8px;font-size:11px;transition:opacity .2s;position:absolute;top:6px;right:6px}.readme-fold{border:1px solid #e5e7eb;border-radius:6px;margin-bottom:20px;overflow:hidden}.fold-header{cursor:pointer;-webkit-user-select:none;user-select:none;background:#fafafa;align-items:center;gap:6px;padding:10px 16px;font-size:14px;font-weight:600;display:flex}.fold-header:hover{background:#f0f0f0}.fold-arrow{color:#999;font-size:11px;transition:transform .2s}.fold-arrow.open{transform:rotate(90deg)}.fold-body{padding:0 16px 16px}.markdown-content pre:hover .copy-btn{opacity:1}.markdown-content pre .copy-btn:hover{color:#2080f0;border-color:#2080f0}.header-right{align-items:center;gap:8px;display:flex}[data-theme=dark] html,[data-theme=dark] body,[data-theme=dark] #app{background:#1a1a2e}[data-theme=dark] .app-header{background:#1a1a2e;border-bottom-color:#333}[data-theme=dark] .app-content{background:0 0}[data-theme=dark] .readme-fold{border-color:#333}[data-theme=dark] .fold-header{color:#e0e0e0;background:#252538}[data-theme=dark] .fold-header:hover{background:#2a2a40}[data-theme=dark] .fold-body{background:#1e1e32}[data-theme=dark] .fold-arrow{color:#888}[data-theme=dark] .markdown-content{color:#ccc}[data-theme=dark] .markdown-content h1,[data-theme=dark] .markdown-content h2{color:#eee;border-bottom-color:#333}[data-theme=dark] .markdown-content h3,[data-theme=dark] .markdown-content h4{color:#eee}[data-theme=dark] .markdown-content code{color:#e06c9f;background:#2a2a40}[data-theme=dark] .markdown-content pre{border-color:#333}[data-theme=dark] .markdown-content pre code{color:#d4d4d4;background:#1e1e2e}[data-theme=dark] .markdown-content blockquote{color:#bbb;background:#252538;border-left-color:#4a6cf7}[data-theme=dark] .markdown-content a{color:#6a8cff}[data-theme=dark] .markdown-content th{background:#252538}[data-theme=dark] .markdown-content td{border-color:#333}[data-theme=dark] .markdown-content tr:nth-child(2n) td{background:#222238}[data-theme=dark] .markdown-content hr{border-top-color:#333}[data-theme=dark] .markdown-content img{filter:brightness(.85)}[data-theme=dark] .page-header h2{color:#e0e0e0}[data-theme=dark] .scene-desc{color:#aaa}.fade-enter-active,.fade-leave-active{transition:opacity .9s,transform .9s}.fade-enter-from{opacity:0;transform:translateY(6px)}.fade-leave-to{opacity:0}.skill-card[data-v-a11339d4]{cursor:pointer;margin-bottom:12px;transition:box-shadow .2s}.skill-card[data-v-a11339d4]:hover{box-shadow:0 2px 8px #00000014}.skill-meta[data-v-a11339d4]{align-items:center;gap:8px;margin-bottom:8px;display:flex}.skill-version[data-v-a11339d4]{color:#888;font-family:monospace;font-size:12px}.skill-desc[data-v-a11339d4]{color:#666;text-overflow:ellipsis;white-space:nowrap;margin:0;font-size:13px;overflow:hidden}.page-header[data-v-773c62ae]{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;margin-bottom:4px;display:flex}.page-header h2[data-v-773c62ae]{font-size:20px;font-weight:600}.header-actions[data-v-773c62ae]{align-items:center;gap:8px;display:flex}.tag-filter[data-v-773c62ae]{flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:16px;display:flex}.detail-status[data-v-773c62ae]{justify-content:center;padding:40px 0;display:flex}.detail-body[data-v-773c62ae]{max-height:65vh;padding:16px 24px;overflow-y:auto}.edit-panel[data-v-773c62ae]{margin-bottom:4px}.edit-row[data-v-773c62ae]{align-items:flex-start;gap:10px;margin-bottom:10px;display:flex}.edit-label[data-v-773c62ae]{color:#888;flex-shrink:0;width:50px;font-size:13px;line-height:30px}.pull-modal-body[data-v-773c62ae]{min-height:100px}.pull-section[data-v-773c62ae]{margin-top:12px}.pull-input-row[data-v-773c62ae]{gap:8px;margin-bottom:8px;display:flex}.pull-input-row .n-input[data-v-773c62ae]{flex:1}.pull-error[data-v-773c62ae]{margin-bottom:8px}.pull-result[data-v-773c62ae]{margin-top:8px}.pull-result-line[data-v-773c62ae]{flex-wrap:wrap;align-items:center;gap:4px;margin-bottom:8px;font-size:13px;display:flex}.pull-result-label[data-v-773c62ae]{color:#888;flex-shrink:0}.pull-result-hint[data-v-773c62ae]{color:#999;font-size:12px}.install-panel[data-v-773c62ae]{background:#00000005;border-radius:6px;margin-top:12px;padding:12px}.install-panel-label[data-v-773c62ae]{color:#666;margin-bottom:8px;font-size:13px}.install-check-list[data-v-773c62ae]{margin-bottom:10px}.install-btn[data-v-773c62ae]{float:right}.edit-actions[data-v-773c62ae]{justify-content:flex-end;gap:8px;display:flex}.page-header[data-v-1736a530]{justify-content:space-between;align-items:center;margin-bottom:20px;display:flex}.page-header h2[data-v-1736a530]{font-size:20px;font-weight:600}.scene-grid[data-v-1736a530]{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;margin:2px;display:grid}.scene-card[data-v-1736a530]{transition:box-shadow .2s}.scene-card.active[data-v-1736a530]{box-shadow:0 0 0 2px #2080f0}.scene-desc[data-v-1736a530]{color:#666;margin:0;font-size:13px}.scene-footer[data-v-1736a530]{justify-content:space-between;align-items:center;display:flex}.hint-text[data-v-1736a530]{color:#999;font-size:12px}.page-header[data-v-5f659620]{margin-bottom:20px}.page-header h2[data-v-5f659620]{font-size:20px;font-weight:600}.section[data-v-5f659620]{margin-bottom:20px}.init-alert[data-v-5f659620]{margin-bottom:12px}.missing-list[data-v-5f659620]{flex-direction:column;gap:4px;display:flex}.missing-item[data-v-5f659620]{font-size:13px;line-height:1.6}.missing-item code[data-v-5f659620]{background:#0000000f;border-radius:3px;padding:1px 6px;font-size:12px}.skill-tag[data-v-5f659620]{margin:1px 2px;display:inline-block}.missing-hint[data-v-5f659620]{opacity:.7;margin-top:4px;font-size:12px}.source-status[data-v-5f659620]{margin-bottom:16px}.source-actions[data-v-5f659620]{flex-direction:column;gap:12px;display:flex}.action-buttons[data-v-5f659620]{gap:8px;display:flex}.healthy-text[data-v-5f659620]{color:#18a058;font-size:13px}.issue-text[data-v-5f659620]{color:#d03050;font-size:13px}.readme-dialog-desc[data-v-5f659620]{color:#555;margin-bottom:16px;font-size:14px}.readme-checkbox[data-v-5f659620]{align-items:flex-start;margin-bottom:12px;display:flex}.readme-checkbox-content[data-v-5f659620]{flex-direction:column;gap:2px;display:flex}.readme-checkbox-title[data-v-5f659620]{font-size:14px;font-weight:500}.readme-checkbox-desc[data-v-5f659620]{color:#999;font-size:12px}.page-header[data-v-97d38fe1]{justify-content:space-between;align-items:center;margin-bottom:20px;display:flex}.page-header h2[data-v-97d38fe1]{font-size:20px;font-weight:600}.fade-enter-active[data-v-97d38fe1],.fade-leave-active[data-v-97d38fe1]{transition:opacity .2s}.fade-enter-from[data-v-97d38fe1],.fade-leave-to[data-v-97d38fe1]{opacity:0}pre code.hljs{padding:1em;display:block;overflow-x:auto}code.hljs{padding:3px 5px}.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#005cc5}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-comment,.hljs-code,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}
|