mdk-skills 2.4.19 → 2.4.21

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.
@@ -6,21 +6,25 @@ const { buildContext } = require("../context");
6
6
 
7
7
  const ctx = buildContext(utils);
8
8
 
9
+ async function pathExists(p) {
10
+ try { await fs.promises.access(p); return true; } catch { return false; }
11
+ }
12
+
9
13
  function register(router) {
10
14
 
11
15
  // ----------------------------------------------------------------
12
16
  // GET /api/status — 当前状态总览
13
17
  // ----------------------------------------------------------------
14
18
  router.get("/api/status", async (req, res) => {
15
- const settings = ctx.readSettings();
16
- const userSkills = core.getUserSkills(ctx.claudeDest);
17
- const pkgSkills = ctx.skillsSource ? core.getPackageSkills(ctx.skillsSource) : [];
18
- const profiles = ctx.skillsSource ? ctx.loadProfiles() : null;
19
+ const settings = await ctx.readSettings();
20
+ const userSkills = await core.getUserSkills(ctx.claudeDest);
21
+ const pkgSkills = ctx.skillsSource ? await core.getPackageSkills(ctx.skillsSource) : [];
22
+ const profiles = ctx.skillsSource ? await ctx.loadProfiles() : null;
19
23
  const activeProfile = profiles?.find(
20
24
  (p) => p.id === settings._active_profile,
21
25
  );
22
26
  const enabledCount = userSkills.filter((s) => s.enabled).length;
23
- const mdExists = fs.existsSync(path.join(ctx.projectRoot, "CLAUDE.md"));
27
+ const mdExists = await pathExists(path.join(ctx.projectRoot, "CLAUDE.md"));
24
28
  return utils.sendJSON(res, {
25
29
  pkgSkills,
26
30
  enabledCount,
@@ -37,7 +41,7 @@ function register(router) {
37
41
  // GET /api/source — 获取技能目录信息
38
42
  // ----------------------------------------------------------------
39
43
  router.get("/api/source", async (req, res) => {
40
- const settings = ctx.readSettings();
44
+ const settings = await ctx.readSettings();
41
45
  const sourcePath = settings._skill_source || null;
42
46
 
43
47
  let needsInit = false;
@@ -45,17 +49,17 @@ function register(router) {
45
49
  const missingSkillDocs = [];
46
50
  if (sourcePath) {
47
51
  const claudeDir = path.join(sourcePath, ".claude");
48
- if (fs.existsSync(claudeDir)) {
49
- if (!fs.existsSync(path.join(claudeDir, "profiles.json"))) {
52
+ if (await pathExists(claudeDir)) {
53
+ if (!(await pathExists(path.join(claudeDir, "profiles.json")))) {
50
54
  missingConfig.push("profiles.json");
51
55
  }
52
- if (!fs.existsSync(path.join(claudeDir, "settings.json"))) {
56
+ if (!(await pathExists(path.join(claudeDir, "settings.json")))) {
53
57
  missingConfig.push("settings.json");
54
58
  }
55
59
  const skillsDir = path.join(claudeDir, "skills");
56
- if (fs.existsSync(skillsDir)) {
57
- for (const name of core.listSkillDirs(skillsDir)) {
58
- if (!fs.existsSync(path.join(skillsDir, name, "SKILL.md"))) {
60
+ if (await pathExists(skillsDir)) {
61
+ for (const name of await core.listSkillDirs(skillsDir)) {
62
+ if (!(await pathExists(path.join(skillsDir, name, "SKILL.md")))) {
59
63
  missingSkillDocs.push(name);
60
64
  }
61
65
  }
@@ -70,8 +74,8 @@ function register(router) {
70
74
  if (sourcePath) {
71
75
  const skillsDir = path.join(sourcePath, ".claude", "skills");
72
76
  readmeStatus = {
73
- root: fs.existsSync(path.join(sourcePath, "README.md")),
74
- skills: fs.existsSync(path.join(skillsDir, "README.md")),
77
+ root: await pathExists(path.join(sourcePath, "README.md")),
78
+ skills: await pathExists(path.join(skillsDir, "README.md")),
75
79
  };
76
80
  }
77
81
 
@@ -93,25 +97,25 @@ function register(router) {
93
97
  const repoPath = body.path;
94
98
  if (!repoPath) return utils.sendJSON(res, { error: "缺少目录路径" }, 400);
95
99
  const resolved = path.resolve(repoPath);
96
- if (!fs.existsSync(path.join(resolved, ".claude", "skills"))) {
100
+ if (!(await pathExists(path.join(resolved, ".claude", "skills")))) {
97
101
  return utils.sendJSON(
98
102
  res,
99
103
  { error: `路径 "${resolved}" 下没有 .claude/skills/` },
100
104
  400,
101
105
  );
102
106
  }
103
- const settings = ctx.readSettings();
107
+ const settings = await ctx.readSettings();
104
108
  settings._skill_source = resolved;
105
- ctx.writeSettings(settings);
109
+ await ctx.writeSettings(settings);
106
110
  // 复制 CLAUDE.md(不存在才装)
107
111
  const mdSource = path.join(resolved, "CLAUDE.md");
108
112
  const mdDest = path.join(ctx.projectRoot, "CLAUDE.md");
109
- if (fs.existsSync(mdSource) && !fs.existsSync(mdDest)) {
110
- fs.copyFileSync(mdSource, mdDest);
113
+ if (await pathExists(mdSource) && !(await pathExists(mdDest))) {
114
+ await fs.promises.copyFile(mdSource, mdDest);
111
115
  }
112
116
 
113
117
  // 清理幽灵技能引用
114
- const cleanResult = utils.cleanGhostSkills(resolved);
118
+ const cleanResult = await utils.cleanGhostSkills(resolved);
115
119
  if (cleanResult.removed.length > 0) {
116
120
  console.log(`[清理幽灵技能] ${cleanResult.removed.join(", ")}`);
117
121
  }
@@ -124,12 +128,12 @@ function register(router) {
124
128
  // POST /api/source/clear — 清除技能目录
125
129
  // ----------------------------------------------------------------
126
130
  router.post("/api/source/clear", async (req, res) => {
127
- const settings = ctx.readSettings();
131
+ const settings = await ctx.readSettings();
128
132
  if (!settings._skill_source) {
129
133
  return utils.sendJSON(res, { error: "当前未设置技能目录" }, 400);
130
134
  }
131
135
  delete settings._skill_source;
132
- ctx.writeSettings(settings);
136
+ await ctx.writeSettings(settings);
133
137
  ctx.refreshSource();
134
138
  return utils.sendJSON(res, { ok: true });
135
139
  });
@@ -138,34 +142,34 @@ function register(router) {
138
142
  // POST /api/source/sync — 同步到本地源
139
143
  // ----------------------------------------------------------------
140
144
  router.post("/api/source/sync", async (req, res) => {
141
- const settings = ctx.readSettings();
145
+ const settings = await ctx.readSettings();
142
146
  const sourcePath = settings._skill_source;
143
147
  if (!sourcePath) return utils.sendJSON(res, { error: "尚未绑定技能源" }, 400);
144
- if (!fs.existsSync(path.join(sourcePath, ".claude"))) {
148
+ if (!(await pathExists(path.join(sourcePath, ".claude")))) {
145
149
  return utils.sendJSON(res, { error: "绑定的路径已失效" }, 400);
146
150
  }
147
151
 
148
152
  const repoClaude = path.join(sourcePath, ".claude");
149
- core.backupDir(repoClaude);
153
+ await core.backupDir(repoClaude);
150
154
 
151
155
  // 技能反推
152
156
  const repoSkills = path.join(sourcePath, ".claude", "skills");
153
- if (!fs.existsSync(repoSkills))
154
- fs.mkdirSync(repoSkills, { recursive: true });
155
- if (fs.existsSync(ctx.skillsDest)) {
156
- for (const name of core.listSkillDirs(ctx.skillsDest)) {
157
+ if (!(await pathExists(repoSkills)))
158
+ await fs.promises.mkdir(repoSkills, { recursive: true });
159
+ if (await pathExists(ctx.skillsDest)) {
160
+ for (const name of await core.listSkillDirs(ctx.skillsDest)) {
157
161
  const src = path.join(ctx.skillsDest, name);
158
162
  const dest = path.join(repoSkills, name);
159
- if (fs.existsSync(dest))
160
- utils.safeRmSync(dest, name);
161
- utils.copyDirSync(src, dest);
163
+ if (await pathExists(dest))
164
+ await utils.safeRm(dest, name);
165
+ await utils.copyDir(src, dest);
162
166
  }
163
167
  }
164
168
 
165
169
  // profiles.json 反推
166
170
  const projectProfiles = path.join(ctx.claudeDest, "profiles.json");
167
- if (fs.existsSync(projectProfiles)) {
168
- fs.copyFileSync(
171
+ if (await pathExists(projectProfiles)) {
172
+ await fs.promises.copyFile(
169
173
  projectProfiles,
170
174
  path.join(sourcePath, ".claude", "profiles.json"),
171
175
  );
@@ -175,7 +179,7 @@ function register(router) {
175
179
  const cleanSettings = { ...settings };
176
180
  delete cleanSettings._skill_source;
177
181
  delete cleanSettings._active_profile;
178
- fs.writeFileSync(
182
+ await fs.promises.writeFile(
179
183
  path.join(sourcePath, ".claude", "settings.json"),
180
184
  JSON.stringify(cleanSettings, null, 2) + "\n",
181
185
  "utf-8",
@@ -183,8 +187,8 @@ function register(router) {
183
187
 
184
188
  // CLAUDE.md 反推
185
189
  const projectMd = path.join(ctx.projectRoot, "CLAUDE.md");
186
- if (fs.existsSync(projectMd)) {
187
- fs.copyFileSync(projectMd, path.join(sourcePath, "CLAUDE.md"));
190
+ if (await pathExists(projectMd)) {
191
+ await fs.promises.copyFile(projectMd, path.join(sourcePath, "CLAUDE.md"));
188
192
  }
189
193
 
190
194
  return utils.sendJSON(res, { ok: true });
@@ -194,7 +198,7 @@ function register(router) {
194
198
  // POST /api/source/init — 初始化本地源骨架文件
195
199
  // ----------------------------------------------------------------
196
200
  router.post("/api/source/init", async (req, res) => {
197
- const settings = ctx.readSettings();
201
+ const settings = await ctx.readSettings();
198
202
  const sourcePath = settings._skill_source;
199
203
  if (!sourcePath) return utils.sendJSON(res, { error: "未绑定本地源" }, 400);
200
204
 
@@ -206,8 +210,8 @@ function register(router) {
206
210
  const created = { profiles: false, settings: false, metaFiles: 0 };
207
211
 
208
212
  // 1. profiles.json
209
- if (!fs.existsSync(profilesPath)) {
210
- fs.writeFileSync(
213
+ if (!(await pathExists(profilesPath))) {
214
+ await fs.promises.writeFile(
211
215
  profilesPath,
212
216
  JSON.stringify(
213
217
  {
@@ -230,8 +234,8 @@ function register(router) {
230
234
  }
231
235
 
232
236
  // 2. settings.json
233
- if (!fs.existsSync(settingsPath)) {
234
- fs.writeFileSync(
237
+ if (!(await pathExists(settingsPath))) {
238
+ await fs.promises.writeFile(
235
239
  settingsPath,
236
240
  JSON.stringify({ skills: {}, always_apply_skills: [] }, null, 2) + "\n",
237
241
  "utf-8",
@@ -240,20 +244,20 @@ function register(router) {
240
244
  }
241
245
 
242
246
  // 3. 每个 skill 目录的 .meta.json
243
- if (fs.existsSync(skillsDir)) {
244
- for (const name of core.listSkillDirs(skillsDir)) {
247
+ if (await pathExists(skillsDir)) {
248
+ for (const name of await core.listSkillDirs(skillsDir)) {
245
249
  const skillDir = path.join(skillsDir, name);
246
250
  const metaPath = path.join(skillDir, ".meta.json");
247
- if (!fs.existsSync(metaPath)) {
248
- utils.writeSkillMeta(skillDir, false);
251
+ if (!(await pathExists(metaPath))) {
252
+ await utils.writeSkillMeta(skillDir, false);
249
253
  created.metaFiles++;
250
254
  }
251
255
  }
252
256
  }
253
257
 
254
258
  const readmeStatus = {
255
- root: fs.existsSync(path.join(sourcePath, "README.md")),
256
- skills: fs.existsSync(path.join(skillsDir, "README.md")),
259
+ root: await pathExists(path.join(sourcePath, "README.md")),
260
+ skills: await pathExists(path.join(skillsDir, "README.md")),
257
261
  };
258
262
 
259
263
  return utils.sendJSON(res, { ok: true, ...created, readmeStatus });
@@ -4,6 +4,12 @@ const crypto = require("crypto");
4
4
  const os = require("os");
5
5
  const core = require("../../core");
6
6
 
7
+ // ===================== 异步路径检查 =====================
8
+
9
+ async function pathExists(p) {
10
+ try { await fs.promises.access(p); return true; } catch { return false; }
11
+ }
12
+
7
13
  // ===================== HTTP 工具 =====================
8
14
 
9
15
  function sendJSON(res, data, status = 200) {
@@ -25,14 +31,13 @@ function parseBody(req) {
25
31
  });
26
32
  }
27
33
 
28
- // ===================== 文件系统 =====================
34
+ // ===================== 文件系统(异步)======================
29
35
 
30
36
  // 安全删除目录/文件,处理 Windows 文件锁定(EBUSY)
31
- // 返回 { removed: boolean, reason?: 'not_found' | 'locked' | 'error', message?: string }
32
- function safeRmSync(dir, label) {
33
- if (!fs.existsSync(dir)) return { removed: false, reason: "not_found" };
37
+ async function safeRm(dir, label) {
38
+ if (!(await pathExists(dir))) return { removed: false, reason: "not_found" };
34
39
  try {
35
- fs.rmSync(dir, { recursive: true, force: true });
40
+ await fs.promises.rm(dir, { recursive: true, force: true });
36
41
  return { removed: true };
37
42
  } catch (err) {
38
43
  if (err.code === "EBUSY") {
@@ -44,42 +49,30 @@ function safeRmSync(dir, label) {
44
49
  }
45
50
  }
46
51
 
47
- // 手动递归拷贝目录
48
- function copyDirSync(src, dest) {
49
- fs.mkdirSync(dest, { recursive: true });
50
- for (const item of fs.readdirSync(src)) {
51
- const srcPath = path.join(src, item);
52
- const destPath = path.join(dest, item);
53
- if (fs.statSync(srcPath).isDirectory()) {
54
- copyDirSync(srcPath, destPath);
55
- } else {
56
- fs.copyFileSync(srcPath, destPath);
57
- }
58
- }
59
- }
60
-
61
- // npx skills 会在系统临时目录留下 skills-* 工作目录,统一清理
62
- function cleanNpxTemp() {
52
+ // 清理 npx 临时目录
53
+ async function cleanNpxTemp() {
63
54
  const tmp = os.tmpdir();
64
- for (const name of fs.readdirSync(tmp)) {
55
+ let names;
56
+ try { names = await fs.promises.readdir(tmp); } catch { return; }
57
+ for (const name of names) {
65
58
  if (/^skills-[A-Za-z0-9]+$/.test(name)) {
66
59
  const p = path.join(tmp, name);
67
- try { fs.rmSync(p, { recursive: true, force: true }); } catch {}
60
+ try { await fs.promises.rm(p, { recursive: true, force: true }); } catch {}
68
61
  }
69
62
  }
70
63
  }
71
64
 
72
65
  // Windows 下获取可用盘符
73
- function getWindowsDrives() {
66
+ async function getWindowsDrives() {
74
67
  const drives = [];
75
68
  for (let i = 65; i <= 90; i++) {
76
69
  const letter = String.fromCharCode(i);
77
70
  const root = letter + ":\\";
78
71
  try {
79
- if (fs.existsSync(root)) {
72
+ if (await pathExists(root)) {
80
73
  drives.push({ name: root, isDir: true });
81
74
  }
82
- } catch { /* 无权限访问的盘符跳过 */ }
75
+ } catch {}
83
76
  }
84
77
  return drives;
85
78
  }
@@ -131,35 +124,40 @@ function parseSkillsFindOutput(stdout) {
131
124
 
132
125
  // ===================== 指纹 & 元信息 =====================
133
126
 
134
- // 计算技能目录的文件指纹(基于所有文件内容 md5),用于检测是否有真实变更
135
- function calcSkillFingerprint(dir) {
136
- if (!fs.existsSync(dir)) return "";
127
+ // 计算技能目录的文件指纹(MD5,排除 .meta.json)
128
+ async function calcSkillFingerprint(dir) {
129
+ if (!(await pathExists(dir))) return "";
137
130
  const hash = crypto.createHash("md5");
138
- const walk = (d) => {
139
- for (const item of fs.readdirSync(d).sort()) {
131
+ const walk = async (d) => {
132
+ let items;
133
+ try { items = await fs.promises.readdir(d); } catch { return; }
134
+ items.sort();
135
+ for (const item of items) {
140
136
  const p = path.join(d, item);
141
137
  if (item === ".meta.json") continue;
142
- const stat = fs.statSync(p);
138
+ let stat;
139
+ try { stat = await fs.promises.stat(p); } catch { continue; }
143
140
  if (stat.isDirectory()) {
144
- walk(p);
141
+ await walk(p);
145
142
  } else {
146
143
  hash.update(item);
147
- hash.update(fs.readFileSync(p));
144
+ const content = await fs.promises.readFile(p);
145
+ hash.update(content);
148
146
  }
149
147
  }
150
148
  };
151
- walk(dir);
149
+ await walk(dir);
152
150
  return hash.digest("hex");
153
151
  }
154
152
 
155
- // 统一写入技能元信息
156
- function writeSkillMeta(dest, wasUpdated) {
157
- const fm = core.parseFrontmatter(dest);
153
+ // 写入技能元信息(异步版)
154
+ async function writeSkillMeta(dest, wasUpdated) {
155
+ const fm = await core.parseFrontmatter(dest);
158
156
  if (!fm) return;
159
157
  const metaPath = path.join(dest, ".meta.json");
160
158
  let oldMeta = {};
161
- if (fs.existsSync(metaPath)) {
162
- try { oldMeta = JSON.parse(fs.readFileSync(metaPath, "utf-8")); } catch {}
159
+ if (await pathExists(metaPath)) {
160
+ try { oldMeta = JSON.parse(await fs.promises.readFile(metaPath, "utf-8")); } catch {}
163
161
  }
164
162
  const hasUpstream = !!fm.version;
165
163
  const meta = {
@@ -174,24 +172,24 @@ function writeSkillMeta(dest, wasUpdated) {
174
172
  } else {
175
173
  meta._updateCount = oldMeta._updateCount || 0;
176
174
  }
177
- fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2) + "\n", "utf-8");
175
+ await fs.promises.writeFile(metaPath, JSON.stringify(meta, null, 2) + "\n", "utf-8");
178
176
  }
179
177
 
180
178
  // ===================== 幽灵技能清理 =====================
181
179
 
182
- function cleanGhostSkills(sourcePath) {
180
+ async function cleanGhostSkills(sourcePath) {
183
181
  const claudeDir = path.join(sourcePath, ".claude");
184
182
  const skillsDir = path.join(claudeDir, "skills");
185
- if (!fs.existsSync(skillsDir)) return { cleaned: false, reason: "skills 目录不存在" };
183
+ if (!(await pathExists(skillsDir))) return { cleaned: false, reason: "skills 目录不存在" };
186
184
 
187
- const actualSkills = fs.existsSync(skillsDir) ? core.listSkillDirs(skillsDir) : [];
185
+ const actualSkills = await pathExists(skillsDir) ? await core.listSkillDirs(skillsDir) : [];
188
186
  const skillSet = new Set(actualSkills);
189
187
  const cleaned = { profiles: 0, alwaysApply: 0, settings: 0, removed: [] };
190
188
 
191
189
  const profilesPath = path.join(claudeDir, "profiles.json");
192
- if (fs.existsSync(profilesPath)) {
190
+ if (await pathExists(profilesPath)) {
193
191
  try {
194
- const profilesData = JSON.parse(fs.readFileSync(profilesPath, "utf-8"));
192
+ const profilesData = JSON.parse(await fs.promises.readFile(profilesPath, "utf-8"));
195
193
  let changed = false;
196
194
  for (const profile of profilesData.profiles || []) {
197
195
  if (!profile.skills || !Array.isArray(profile.skills)) continue;
@@ -204,15 +202,15 @@ function cleanGhostSkills(sourcePath) {
204
202
  }
205
203
  if (changed) {
206
204
  cleaned.profiles++;
207
- fs.writeFileSync(profilesPath, JSON.stringify(profilesData, null, 2) + "\n", "utf-8");
205
+ await fs.promises.writeFile(profilesPath, JSON.stringify(profilesData, null, 2) + "\n", "utf-8");
208
206
  }
209
207
  } catch {}
210
208
  }
211
209
 
212
210
  const settingsPathInRepo = path.join(claudeDir, "settings.json");
213
- if (fs.existsSync(settingsPathInRepo)) {
211
+ if (await pathExists(settingsPathInRepo)) {
214
212
  try {
215
- const settings = JSON.parse(fs.readFileSync(settingsPathInRepo, "utf-8"));
213
+ const settings = JSON.parse(await fs.promises.readFile(settingsPathInRepo, "utf-8"));
216
214
  let changed = false;
217
215
  if (Array.isArray(settings.always_apply_skills)) {
218
216
  const filtered = settings.always_apply_skills.filter((s) => {
@@ -233,7 +231,7 @@ function cleanGhostSkills(sourcePath) {
233
231
  }
234
232
  if (changed) {
235
233
  cleaned.settings++;
236
- fs.writeFileSync(settingsPathInRepo, JSON.stringify(settings, null, 2) + "\n", "utf-8");
234
+ await fs.promises.writeFile(settingsPathInRepo, JSON.stringify(settings, null, 2) + "\n", "utf-8");
237
235
  }
238
236
  } catch {}
239
237
  }
@@ -242,17 +240,23 @@ function cleanGhostSkills(sourcePath) {
242
240
  }
243
241
 
244
242
  module.exports = {
243
+ // HTTP
245
244
  sendJSON,
246
245
  parseBody,
247
- safeRmSync,
248
- copyDirSync,
246
+ // 文件系统
247
+ pathExists,
248
+ safeRm,
249
+ copyDir: core.copyDir,
249
250
  cleanNpxTemp,
250
251
  getWindowsDrives,
252
+ // 技能解析
251
253
  stripAnsi,
252
254
  escapeShellArg,
253
255
  parseInstallCount,
254
256
  parseSkillsFindOutput,
257
+ // 指纹 & 元信息
255
258
  calcSkillFingerprint,
256
259
  writeSkillMeta,
260
+ // 幽灵清理
257
261
  cleanGhostSkills,
258
262
  };
@@ -8,6 +8,7 @@ const fs = require("fs");
8
8
 
9
9
  const { Router } = require("./server/router");
10
10
  const utils = require("./server/utils");
11
+ const { refreshSource } = require("./server/context");
11
12
 
12
13
  // ===================== 路由注册 =====================
13
14
 
@@ -71,14 +72,30 @@ function startProduction(distDir) {
71
72
  }
72
73
  const urlPath = req.url === "/" ? "/index.html" : req.url.split("?")[0];
73
74
  const filePath = path.join(distDir, urlPath);
74
- if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
75
- const ext = path.extname(filePath);
76
- res.writeHead(200, { "Content-Type": MIME_TYPES[ext] || "application/octet-stream" });
77
- res.end(fs.readFileSync(filePath));
78
- } else {
79
- res.writeHead(200, { "Content-Type": "text/html" });
80
- res.end(fs.readFileSync(path.join(distDir, "index.html")));
81
- }
75
+
76
+ fs.stat(filePath, (err, stat) => {
77
+ if (!err && stat.isFile()) {
78
+ const ext = path.extname(filePath);
79
+ res.writeHead(200, { "Content-Type": MIME_TYPES[ext] || "application/octet-stream" });
80
+ const stream = fs.createReadStream(filePath);
81
+ stream.pipe(res);
82
+ stream.on("error", () => { res.end(); });
83
+ } else {
84
+ // SPA fallback: 返回 index.html
85
+ const indexPath = path.join(distDir, "index.html");
86
+ fs.stat(indexPath, (err2, stat2) => {
87
+ if (err2) {
88
+ res.writeHead(404);
89
+ res.end("Not Found");
90
+ return;
91
+ }
92
+ res.writeHead(200, { "Content-Type": "text/html" });
93
+ const stream = fs.createReadStream(indexPath);
94
+ stream.pipe(res);
95
+ stream.on("error", () => { res.end(); });
96
+ });
97
+ }
98
+ });
82
99
  });
83
100
  server.listen(3344, () => {
84
101
  console.log(`\n 🖥️ mdk-skills Web UI`);
@@ -120,12 +137,19 @@ async function startDev() {
120
137
 
121
138
  // ===================== 启动 =====================
122
139
 
123
- const distDir = path.join(__dirname, "dist");
124
- if (fs.existsSync(distDir)) {
125
- startProduction(distDir);
126
- } else {
127
- startDev().catch((err) => {
128
- console.error("启动失败:", err);
129
- process.exit(1);
130
- });
140
+ async function start() {
141
+ await refreshSource();
142
+ const distDir = path.join(__dirname, "dist");
143
+ let stats;
144
+ try { stats = await fs.promises.stat(distDir); } catch {}
145
+ if (stats) {
146
+ startProduction(distDir);
147
+ } else {
148
+ await startDev();
149
+ }
131
150
  }
151
+
152
+ start().catch((err) => {
153
+ console.error("启动失败:", err);
154
+ process.exit(1);
155
+ });