zdashboard 2.2.0 → 2.3.1

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.
Files changed (34) hide show
  1. package/dist/cli.js +312 -212
  2. package/dist/cli.js.map +1 -1
  3. package/dist/web/assets/{web-BnZ_68eg.js → FileIcon-CA7j6xAr.js} +36 -11
  4. package/dist/web/assets/FilterPills-CT9_aiMo.js +1 -0
  5. package/dist/web/assets/{katex.min-D_D1u30F.js → MdViewer-C-qU08GL.js} +24 -24
  6. package/dist/web/assets/ProgressBar-CyYoGfrt.js +1 -0
  7. package/dist/web/assets/Workspace-CqK1Sdtv.js +16 -0
  8. package/dist/web/assets/badge-CsNeQaUt.js +1 -0
  9. package/dist/web/assets/book-open-DgUv7g0V.js +6 -0
  10. package/dist/web/assets/file-text-AM0snHQm.js +6 -0
  11. package/dist/web/assets/{index-6FGn10zY.js → index-BF1XYOKG.js} +1 -1
  12. package/dist/web/assets/index-C9nEYrn8.css +1 -0
  13. package/dist/web/assets/index-Dcz2Myaf.js +111 -0
  14. package/dist/web/assets/web-4IVEiPQ7.js +36 -0
  15. package/dist/web/assets/web-4pvyVYqE.js +31 -0
  16. package/dist/web/assets/web-B8iKfUHX.js +2 -0
  17. package/dist/web/assets/web-BOZwX-p2.js +56 -0
  18. package/dist/web/assets/web-CPCrz28U.js +25 -0
  19. package/dist/web/assets/web-DJnenbT2.js +7 -0
  20. package/dist/web/assets/web-WH-i-efe.js +16 -0
  21. package/dist/web/index.html +2 -2
  22. package/package.json +1 -1
  23. package/dist/web/assets/MdViewer-qvXbO8IA.js +0 -1
  24. package/dist/web/assets/Workspace-D96dCoOA.js +0 -1
  25. package/dist/web/assets/git-branch-DJLd5kDE.js +0 -11
  26. package/dist/web/assets/index-15cLfCw4.js +0 -90
  27. package/dist/web/assets/index-CYqm3unP.css +0 -1
  28. package/dist/web/assets/web-BUNysAkV.js +0 -7
  29. package/dist/web/assets/web-BeuJXCg2.js +0 -26
  30. package/dist/web/assets/web-C-O8qmKu.js +0 -31
  31. package/dist/web/assets/web-CJq1r_vk.js +0 -11
  32. package/dist/web/assets/web-DP_CBpB2.js +0 -2
  33. package/dist/web/assets/web-Xpqif6fn.js +0 -32
  34. /package/dist/web/assets/{katex-BTcE7LSd.css → MdViewer-BTcE7LSd.css} +0 -0
package/dist/cli.js CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import path10 from "path";
4
+ import path11 from "path";
5
5
  import fs11 from "fs";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
7
  import { access, readdir } from "fs/promises";
8
+ import { parseArgs as utilParseArgs } from "util";
8
9
 
9
10
  // src/server/detect.ts
10
11
  import fs2 from "fs";
@@ -14,6 +15,7 @@ import { execFile } from "child_process";
14
15
  // src/server/bugs.ts
15
16
  import fs from "fs";
16
17
  import path from "path";
18
+ import YAML from "yaml";
17
19
 
18
20
  // src/server/api/fetch.ts
19
21
  import ky from "ky";
@@ -61,20 +63,23 @@ function loadConfig(root) {
61
63
  for (const rel of BUGS_CONFIG_CANDIDATES) {
62
64
  const file = path.join(root, rel);
63
65
  if (!fs.existsSync(file)) continue;
64
- const kv = {};
65
- for (const line of fs.readFileSync(file, "utf8").split("\n")) {
66
- const m = line.match(/^\s*([A-Za-z_]\w*)\s*:\s*(.+?)\s*$/);
67
- if (m && !m[2].startsWith("#")) kv[m[1]] = m[2].replace(/^["']|["']$/g, "");
66
+ try {
67
+ const kv = YAML.parse(fs.readFileSync(file, "utf8"));
68
+ if (!kv || typeof kv !== "object") return null;
69
+ const record = kv;
70
+ const product = Number(record.product);
71
+ if (!record.url || !product) return null;
72
+ const str = (v) => typeof v === "string" ? v : v == null ? void 0 : String(v);
73
+ return {
74
+ url: String(record.url).replace(/\/+$/, ""),
75
+ account: str(record.account) ?? "",
76
+ password: str(record.password),
77
+ token: str(record.token),
78
+ product
79
+ };
80
+ } catch {
81
+ return null;
68
82
  }
69
- const product = Number(kv.product);
70
- if (!kv.url || !product) return null;
71
- return {
72
- url: kv.url.replace(/\/+$/, ""),
73
- account: kv.account ?? "",
74
- password: kv.password,
75
- token: kv.token,
76
- product
77
- };
78
83
  }
79
84
  return null;
80
85
  }
@@ -162,7 +167,7 @@ import { fileURLToPath } from "url";
162
167
  // package.json
163
168
  var package_default = {
164
169
  name: "zdashboard",
165
- version: "2.2.0",
170
+ version: "2.3.1",
166
171
  description: "ZCode skill dashboard platform \u2014 pluggable viewers for zdesign/zview/zreview/zgoal",
167
172
  type: "module",
168
173
  bin: {
@@ -344,13 +349,18 @@ async function stopInstance(record) {
344
349
  }
345
350
 
346
351
  // src/core/open-url.ts
347
- import { exec } from "child_process";
352
+ import { exec, execFile as execFile2 } from "child_process";
348
353
  function openUrl(url) {
349
354
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
350
- exec(`${cmd} ${url}`);
355
+ if (process.platform === "win32") {
356
+ exec(`start "" "${url}"`);
357
+ } else {
358
+ execFile2(cmd, [url]);
359
+ }
351
360
  }
352
361
 
353
362
  // src/core/server.ts
363
+ import { execFile as execFile3 } from "child_process";
354
364
  var VERSION = package_default.version;
355
365
  var __dirname = path4.dirname(fileURLToPath(import.meta.url));
356
366
  var MIME = {
@@ -367,6 +377,7 @@ var MIME = {
367
377
  ".jpeg": "image/jpeg",
368
378
  ".gif": "image/gif",
369
379
  ".webp": "image/webp",
380
+ ".avif": "image/avif",
370
381
  ".md": "text/markdown; charset=utf-8",
371
382
  ".txt": "text/plain; charset=utf-8",
372
383
  ".sql": "text/plain; charset=utf-8",
@@ -396,6 +407,11 @@ var MIME = {
396
407
  ".env": "text/plain; charset=utf-8",
397
408
  ".gitignore": "text/plain; charset=utf-8",
398
409
  ".dockerfile": "text/plain; charset=utf-8",
410
+ ".mp4": "video/mp4",
411
+ ".webm": "video/webm",
412
+ ".mov": "video/quicktime",
413
+ ".m4a": "audio/mp4",
414
+ ".aac": "audio/aac",
399
415
  ".woff": "font/woff",
400
416
  ".woff2": "font/woff2",
401
417
  ".ttf": "font/woff",
@@ -427,9 +443,10 @@ var ServerService = class extends Service {
427
443
  this.dataDir = config.dataDir;
428
444
  this.onListen = config.onListen;
429
445
  ctx.effect(() => () => this.dispose());
446
+ this.refreshGitInfo();
430
447
  this.route("/__config", (_req, res) => {
431
448
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" });
432
- res.end(JSON.stringify({ stopToken: this.stopToken, version: VERSION, root: this.root }));
449
+ res.end(JSON.stringify({ stopToken: this.stopToken, version: VERSION, root: this.root, ...this.gitInfo }));
433
450
  });
434
451
  this.route("/__stop", async (req, res) => {
435
452
  if (req.headers["x-stop-token"] !== this.stopToken) {
@@ -443,36 +460,74 @@ var ServerService = class extends Service {
443
460
  });
444
461
  this.start(config.port);
445
462
  }
446
- route(path11, handler) {
447
- this.routes.set(path11, handler);
448
- this.ctx.effect(() => () => this.routes.delete(path11));
463
+ route(path12, handler) {
464
+ this.routes.set(path12, handler);
465
+ this.ctx.effect(() => () => this.routes.delete(path12));
449
466
  }
450
- sse(path11, onConnect) {
451
- this.sses.set(path11, onConnect);
452
- this.ctx.effect(() => () => this.sses.delete(path11));
467
+ sse(path12, onConnect) {
468
+ this.sses.set(path12, onConnect);
469
+ this.ctx.effect(() => () => this.sses.delete(path12));
453
470
  }
454
471
  static(prefix, dir) {
455
472
  this.prefixStatic.set(prefix, dir);
456
473
  this.ctx.effect(() => () => this.prefixStatic.delete(prefix));
457
474
  }
458
- serveFile(filePath, res, injectHtml) {
459
- fs4.readFile(filePath, (err, data) => {
475
+ serveFile(filePath, req, res, injectHtml) {
476
+ fs4.stat(filePath, (err, stat) => {
460
477
  if (err) {
461
478
  res.writeHead(404);
462
479
  return res.end("Not found");
463
480
  }
464
481
  const ext = path4.extname(filePath).toLowerCase();
465
482
  const ct = MIME[ext] ?? "application/octet-stream";
466
- let body = data;
467
- if (injectHtml && ext === ".html") {
468
- const s = data.toString("utf8");
469
- body = Buffer.from(s.indexOf("</body>") >= 0 ? s.replace("</body>", INJECT + "</body>") : s + INJECT);
483
+ const rangeHeader = req.headers["range"];
484
+ let range = null;
485
+ if (rangeHeader) {
486
+ const m = rangeHeader.match(/^bytes=(\d+)-(\d*)$/);
487
+ if (m) {
488
+ const start = Number(m[1]);
489
+ const end = m[2] !== "" ? Number(m[2]) : stat.size - 1;
490
+ if (!Number.isNaN(start) && !Number.isNaN(end) && start <= end && start < stat.size) {
491
+ range = { start, end: Math.min(end, stat.size - 1) };
492
+ }
493
+ }
470
494
  }
471
- res.writeHead(200, { "Content-Type": ct, "Cache-Control": "no-cache" });
472
- res.end(body);
495
+ if (range) {
496
+ const { start, end } = range;
497
+ const chunkSize = end - start + 1;
498
+ res.writeHead(206, {
499
+ "Content-Type": ct,
500
+ "Content-Length": String(chunkSize),
501
+ "Content-Range": `bytes ${start}-${end}/${stat.size}`,
502
+ "Accept-Ranges": "bytes",
503
+ "Cache-Control": "no-cache"
504
+ });
505
+ const stream = fs4.createReadStream(filePath, { start, end });
506
+ stream.on("error", () => {
507
+ try {
508
+ res.destroy();
509
+ } catch {
510
+ }
511
+ });
512
+ stream.pipe(res);
513
+ return;
514
+ }
515
+ fs4.readFile(filePath, (err2, data) => {
516
+ if (err2) {
517
+ res.writeHead(404);
518
+ return res.end("Not found");
519
+ }
520
+ let body = data;
521
+ if (injectHtml && ext === ".html") {
522
+ const s = data.toString("utf8");
523
+ body = Buffer.from(s.indexOf("</body>") >= 0 ? s.replace("</body>", INJECT + "</body>") : s + INJECT);
524
+ }
525
+ res.writeHead(200, { "Content-Type": ct, "Accept-Ranges": "bytes", "Cache-Control": "no-cache" });
526
+ res.end(body);
527
+ });
473
528
  });
474
529
  }
475
- servePrefix(url, res) {
530
+ servePrefix(url, req, res) {
476
531
  for (const [prefix, dir] of this.prefixStatic) {
477
532
  if (url.indexOf(prefix) === 0) {
478
533
  let rel = this.safeDecode(url.slice(prefix.length));
@@ -480,9 +535,10 @@ var ServerService = class extends Service {
480
535
  const fp = path4.join(dir, rel);
481
536
  if (fp.indexOf(dir + path4.sep) !== 0) {
482
537
  res.writeHead(403);
483
- return res.end("Forbidden");
538
+ res.end("Forbidden");
539
+ return false;
484
540
  }
485
- this.serveFile(fp, res, true);
541
+ this.serveFile(fp, req, res, true);
486
542
  return true;
487
543
  }
488
544
  }
@@ -514,7 +570,7 @@ var ServerService = class extends Service {
514
570
  });
515
571
  return;
516
572
  }
517
- if (this.servePrefix(url, res)) return;
573
+ if (this.servePrefix(url, req, res)) return;
518
574
  if (url === "/" || url.indexOf("/__app/") === 0 || url.indexOf("/assets/") === 0) {
519
575
  let fp2 = this.appDir;
520
576
  if (url !== "/") fp2 = path4.join(this.appDir, this.safeDecode(url));
@@ -524,14 +580,14 @@ var ServerService = class extends Service {
524
580
  return res.end("Forbidden");
525
581
  }
526
582
  if (url === "/") fp2 = path4.join(this.appDir, "index.html");
527
- return this.serveFile(fp2, res, false);
583
+ return this.serveFile(fp2, req, res, false);
528
584
  }
529
585
  const fp = path4.join(this.root, this.safeDecode(url));
530
586
  if (fp !== this.root && fp.indexOf(this.root + path4.sep) !== 0) {
531
587
  res.writeHead(403);
532
588
  return res.end("Forbidden");
533
589
  }
534
- return this.serveFile(fp, res, true);
590
+ return this.serveFile(fp, req, res, true);
535
591
  } catch (e) {
536
592
  try {
537
593
  res.writeHead(400);
@@ -562,6 +618,21 @@ var ServerService = class extends Service {
562
618
  this.onListen?.(port);
563
619
  });
564
620
  }
621
+ /** 项目 git 信息(分支/脏文件数),启动探测一次,文件变更时刷新 */
622
+ gitInfo = {};
623
+ refreshGitInfo() {
624
+ const run = (args) => new Promise((resolve) => {
625
+ execFile3("git", args, { cwd: this.root, timeout: 3e3 }, (err, stdout) => resolve(err ? "" : stdout));
626
+ });
627
+ void (async () => {
628
+ const [branch, status] = await Promise.all([
629
+ run(["rev-parse", "--abbrev-ref", "HEAD"]),
630
+ run(["status", "--porcelain"])
631
+ ]);
632
+ const b = branch.trim();
633
+ this.gitInfo = b ? { branch: b, dirty: status ? status.split("\n").filter(Boolean).length : 0 } : {};
634
+ })();
635
+ }
565
636
  stop() {
566
637
  if (this.server) {
567
638
  try {
@@ -615,6 +686,7 @@ var ReloadService = class extends Service2 {
615
686
  this.watcher = fs5.watch(config.root, { recursive: true }, () => {
616
687
  if (this.timer) clearTimeout(this.timer);
617
688
  this.timer = setTimeout(() => {
689
+ this.ctx.server.refreshGitInfo();
618
690
  this.broadcast("reload");
619
691
  this.broadcast("files");
620
692
  }, WATCH_DEBOUNCE_MS);
@@ -655,73 +727,128 @@ data: ${JSON.stringify(data == null ? "" : data)}
655
727
  };
656
728
 
657
729
  // src/server/spec-scan.ts
730
+ import fs7 from "fs";
731
+ import path6 from "path";
732
+
733
+ // src/server/walk.ts
658
734
  import fs6 from "fs";
659
735
  import path5 from "path";
660
- function walkFiles(absDir, relDir, depth = 0) {
661
- if (depth > 4) return [];
662
- let ents;
663
- try {
664
- ents = fs6.readdirSync(absDir, { withFileTypes: true });
665
- } catch {
666
- return [];
667
- }
668
- const nodes = [];
669
- for (const ent of ents) {
670
- if (ent.name.startsWith(".") || ent.name === "node_modules") continue;
671
- const rel = relDir ? `${relDir}/${ent.name}` : ent.name;
672
- if (ent.isDirectory()) {
673
- nodes.push({ name: ent.name, kind: "dir", children: walkFiles(path5.join(absDir, ent.name), rel, depth + 1) });
674
- } else {
675
- nodes.push({ name: ent.name, kind: "file", path: rel });
736
+ function walkDir(root, opts = {}) {
737
+ const { skip = /* @__PURE__ */ new Set(), maxDepth, onFile, onDir } = opts;
738
+ function walk(dir, rel, depth) {
739
+ if (typeof maxDepth === "number" && depth > maxDepth) return;
740
+ let ents;
741
+ try {
742
+ ents = fs6.readdirSync(dir, { withFileTypes: true });
743
+ } catch {
744
+ return;
745
+ }
746
+ for (const ent of ents) {
747
+ if (ent.name.startsWith(".") || skip.has(ent.name)) continue;
748
+ const abs = path5.join(dir, ent.name);
749
+ const r = rel ? `${rel}/${ent.name}` : ent.name;
750
+ if (ent.isDirectory()) {
751
+ onDir?.(abs, r);
752
+ walk(abs, r, depth + 1);
753
+ } else if (onFile) {
754
+ onFile(abs, r);
755
+ }
756
+ }
757
+ }
758
+ walk(root, "", 0);
759
+ }
760
+
761
+ // src/server/spec-scan.ts
762
+ function buildTree(paths) {
763
+ const root = [];
764
+ const map = /* @__PURE__ */ new Map();
765
+ for (const p of paths) {
766
+ const parts = p.split("/");
767
+ let current = "";
768
+ let parent = root;
769
+ for (let i = 0; i < parts.length; i++) {
770
+ const part = parts[i];
771
+ current = current ? `${current}/${part}` : part;
772
+ const isLast = i === parts.length - 1;
773
+ if (!map.has(current)) {
774
+ const node = {
775
+ name: part,
776
+ kind: isLast ? "file" : "dir",
777
+ ...isLast ? { path: p } : { children: [] }
778
+ };
779
+ map.set(current, node);
780
+ parent.push(node);
781
+ }
782
+ if (!isLast) {
783
+ parent = map.get(current).children;
784
+ }
676
785
  }
677
786
  }
678
- nodes.sort((a, b) => a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind === "dir" ? -1 : 1);
679
- return nodes;
787
+ const sortNodes = (nodes) => {
788
+ nodes.sort((a, b) => a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind === "dir" ? -1 : 1);
789
+ for (const n of nodes) {
790
+ if (n.children) sortNodes(n.children);
791
+ }
792
+ };
793
+ sortNodes(root);
794
+ return root;
680
795
  }
681
796
  function scanTree(root, hasOpenspec, hasDocs) {
682
797
  const tree = [];
683
- if (hasOpenspec && fs6.existsSync(path5.join(root, "openspec", "changes"))) {
684
- const changesDir = path5.join(root, "openspec", "changes");
798
+ if (hasOpenspec && fs7.existsSync(path6.join(root, "openspec", "changes"))) {
799
+ const changesDir = path6.join(root, "openspec", "changes");
685
800
  const active = [];
686
801
  const archived = [];
687
- for (const ent of fs6.readdirSync(changesDir, { withFileTypes: true })) {
802
+ for (const ent of fs7.readdirSync(changesDir, { withFileTypes: true })) {
688
803
  if (!ent.isDirectory() || ent.name.startsWith(".") || ent.name === "archive") continue;
689
- active.push({ name: ent.name, kind: "dir", children: walkFiles(path5.join(changesDir, ent.name), `openspec/changes/${ent.name}`) });
804
+ const relBase = `openspec/changes/${ent.name}`;
805
+ const paths = [];
806
+ walkDir(path6.join(changesDir, ent.name), { maxDepth: 4, onFile: (_, rel) => paths.push(rel ? `${relBase}/${rel}` : relBase) });
807
+ const children = buildTree(paths).map((n) => ({ ...n, path: n.path ? `${relBase}/${n.path}` : void 0 }));
808
+ active.push({ name: ent.name, kind: "dir", children });
690
809
  }
691
810
  active.sort((a, b) => a.name.localeCompare(b.name));
692
- const archiveDir = path5.join(changesDir, "archive");
693
- if (fs6.existsSync(archiveDir)) {
694
- for (const ent of fs6.readdirSync(archiveDir, { withFileTypes: true })) {
811
+ const archiveDir = path6.join(changesDir, "archive");
812
+ if (fs7.existsSync(archiveDir)) {
813
+ for (const ent of fs7.readdirSync(archiveDir, { withFileTypes: true })) {
695
814
  if (!ent.isDirectory() || ent.name.startsWith(".")) continue;
696
- archived.push({ name: ent.name, kind: "dir", children: walkFiles(path5.join(archiveDir, ent.name), `openspec/changes/archive/${ent.name}`) });
815
+ const relBase = `openspec/changes/archive/${ent.name}`;
816
+ const paths = [];
817
+ walkDir(path6.join(archiveDir, ent.name), { maxDepth: 4, onFile: (_, rel) => paths.push(rel ? `${relBase}/${rel}` : relBase) });
818
+ const children = buildTree(paths).map((n) => ({ ...n, path: n.path ? `${relBase}/${n.path}` : void 0 }));
819
+ archived.push({ name: ent.name, kind: "dir", children });
697
820
  }
698
821
  archived.sort((a, b) => b.name.localeCompare(a.name));
699
822
  }
700
- if (active.length) tree.push({ name: `\u8FDB\u884C\u4E2D (${active.length})`, kind: "dir", children: active });
701
- if (archived.length) tree.push({ name: "archive", kind: "dir", defaultCollapsed: true, children: archived });
702
- const specsDir = path5.join(root, "openspec", "specs");
703
- if (fs6.existsSync(specsDir)) {
704
- const specs = walkFiles(specsDir, "openspec/specs");
823
+ if (active.length) tree.push({ name: `changes (${active.length})`, kind: "dir", children: active });
824
+ if (archived.length) tree.push({ name: `archive (${archived.length})`, kind: "dir", defaultCollapsed: true, children: archived });
825
+ const specsDir = path6.join(root, "openspec", "specs");
826
+ if (fs7.existsSync(specsDir)) {
827
+ const paths = [];
828
+ walkDir(specsDir, { maxDepth: 4, onFile: (_, rel) => paths.push(`openspec/specs/${rel}`) });
829
+ const specs = buildTree(paths).map((n) => ({ ...n, path: n.path ? `openspec/specs/${n.path}` : void 0 }));
705
830
  if (specs.length) tree.push({ name: "specs", kind: "dir", children: specs });
706
831
  }
707
832
  }
708
- if (hasDocs && fs6.existsSync(path5.join(root, "docs"))) {
709
- const docs = walkFiles(path5.join(root, "docs"), "docs");
833
+ if (hasDocs && fs7.existsSync(path6.join(root, "docs"))) {
834
+ const paths = [];
835
+ walkDir(path6.join(root, "docs"), { maxDepth: 4, onFile: (_, rel) => paths.push(`docs/${rel}`) });
836
+ const docs = buildTree(paths).map((n) => ({ ...n, path: n.path ? `docs/${n.path}` : void 0 }));
710
837
  if (docs.length) tree.push({ name: "docs", kind: "dir", children: docs });
711
838
  }
712
839
  const skip = /* @__PURE__ */ new Set(["openspec", "docs", "node_modules", ".git", "dist", "test-server"]);
713
840
  const etc = [];
714
841
  try {
715
- for (const ent of fs6.readdirSync(root, { withFileTypes: true })) {
842
+ for (const ent of fs7.readdirSync(root, { withFileTypes: true })) {
716
843
  if (ent.name.startsWith(".") || skip.has(ent.name)) continue;
717
- const ext = path5.extname(ent.name).toLowerCase();
844
+ const ext = path6.extname(ent.name).toLowerCase();
718
845
  if (ent.isFile() && (ext === ".md" || ext === ".markdown")) etc.push({ name: ent.name, kind: "file", path: ent.name });
719
846
  }
720
847
  } catch (e) {
721
848
  console.error("[zdashboard] scan root etc failed:", e);
722
849
  }
723
850
  etc.sort((a, b) => a.name.localeCompare(b.name));
724
- if (etc.length) tree.push({ name: `\u5176\u4ED6 (${etc.length})`, kind: "dir", children: etc });
851
+ if (etc.length) tree.push({ name: `other (${etc.length})`, kind: "dir", children: etc });
725
852
  return tree;
726
853
  }
727
854
 
@@ -755,12 +882,12 @@ var apply = {
755
882
  };
756
883
 
757
884
  // src/core/worktrees.ts
758
- import { execFile as execFile2 } from "child_process";
885
+ import { execFile as execFile4 } from "child_process";
759
886
  var GIT_TIMEOUT_MS = 5e3;
760
887
  var ZWORKTREE_SEGMENT = ".zworktree/";
761
888
  function runGit(args, cwd) {
762
889
  return new Promise((resolve) => {
763
- execFile2("git", args, { cwd, timeout: GIT_TIMEOUT_MS }, (err, stdout) => {
890
+ execFile4("git", args, { cwd, timeout: GIT_TIMEOUT_MS }, (err, stdout) => {
764
891
  if (err) return resolve("");
765
892
  resolve(stdout);
766
893
  });
@@ -858,7 +985,7 @@ var DashboardService = class extends Service3 {
858
985
  };
859
986
 
860
987
  // src/server/just-runner.ts
861
- import { spawn, execFile as execFile3 } from "child_process";
988
+ import { spawn, execFile as execFile5 } from "child_process";
862
989
  var MAX_BUFFER = 1e3;
863
990
  var JustRunner = class {
864
991
  cwd;
@@ -871,7 +998,7 @@ var JustRunner = class {
871
998
  recipes() {
872
999
  if (this.recipesCache) return Promise.resolve(this.recipesCache);
873
1000
  return new Promise((resolve) => {
874
- execFile3("just", ["--list", "--unsorted"], { cwd: this.cwd, maxBuffer: 1 << 20, timeout: 8e3 }, (err, stdout) => {
1001
+ execFile5("just", ["--list", "--unsorted"], { cwd: this.cwd, maxBuffer: 1 << 20, timeout: 8e3 }, (err, stdout) => {
875
1002
  if (err) {
876
1003
  resolve([]);
877
1004
  return;
@@ -996,6 +1123,18 @@ var JustRunner = class {
996
1123
  }
997
1124
  };
998
1125
 
1126
+ // src/core/read-body.ts
1127
+ async function readBody(req) {
1128
+ return new Promise((resolve) => {
1129
+ let data = "";
1130
+ req.on("data", (chunk) => {
1131
+ data += chunk;
1132
+ });
1133
+ req.on("end", () => resolve(data));
1134
+ req.on("error", () => resolve(""));
1135
+ });
1136
+ }
1137
+
999
1138
  // src/plugins/just/index.ts
1000
1139
  var apply3 = {
1001
1140
  inject: ["server", "dashboard"],
@@ -1077,16 +1216,6 @@ var apply3 = {
1077
1216
  });
1078
1217
  }
1079
1218
  };
1080
- async function readBody(req) {
1081
- return new Promise((resolve) => {
1082
- let data = "";
1083
- req.on("data", (c) => {
1084
- data += c;
1085
- });
1086
- req.on("end", () => resolve(data));
1087
- req.on("error", () => resolve(""));
1088
- });
1089
- }
1090
1219
 
1091
1220
  // src/plugins/bugs/index.ts
1092
1221
  var apply4 = {
@@ -1110,9 +1239,9 @@ var apply4 = {
1110
1239
  };
1111
1240
 
1112
1241
  // src/server/review-store.ts
1113
- import fs7 from "fs";
1114
- import path6 from "path";
1115
- import YAML from "yaml";
1242
+ import fs8 from "fs";
1243
+ import path7 from "path";
1244
+ import YAML2 from "yaml";
1116
1245
  var REVIEW_CANDIDATES = [".zdev/review.yaml", "review.yaml"];
1117
1246
  var ReviewStore = class {
1118
1247
  root;
@@ -1121,15 +1250,15 @@ var ReviewStore = class {
1121
1250
  constructor(root, onChange) {
1122
1251
  this.root = root;
1123
1252
  this.onChange = onChange;
1124
- const existing = REVIEW_CANDIDATES.find((rel) => fs7.existsSync(path6.join(root, rel)));
1125
- this.file = path6.join(root, existing ?? REVIEW_CANDIDATES[0]);
1253
+ const existing = REVIEW_CANDIDATES.find((rel) => fs8.existsSync(path7.join(root, rel)));
1254
+ this.file = path7.join(root, existing ?? REVIEW_CANDIDATES[0]);
1126
1255
  }
1127
1256
  exists() {
1128
- return fs7.existsSync(this.file);
1257
+ return fs8.existsSync(this.file);
1129
1258
  }
1130
1259
  read() {
1131
1260
  try {
1132
- const parsed = YAML.parse(fs7.readFileSync(this.file, "utf8"));
1261
+ const parsed = YAML2.parse(fs8.readFileSync(this.file, "utf8"));
1133
1262
  if (!parsed || !Array.isArray(parsed.items)) return { status: "draft", items: [] };
1134
1263
  return parsed;
1135
1264
  } catch {
@@ -1137,8 +1266,8 @@ var ReviewStore = class {
1137
1266
  }
1138
1267
  }
1139
1268
  write(data) {
1140
- fs7.mkdirSync(path6.dirname(this.file), { recursive: true });
1141
- fs7.writeFileSync(this.file, YAML.stringify(data), "utf8");
1269
+ fs8.mkdirSync(path7.dirname(this.file), { recursive: true });
1270
+ fs8.writeFileSync(this.file, YAML2.stringify(data), "utf8");
1142
1271
  this.onChange?.();
1143
1272
  }
1144
1273
  updateItem(id, patch) {
@@ -1162,9 +1291,9 @@ var ReviewStore = class {
1162
1291
  }
1163
1292
  docs() {
1164
1293
  try {
1165
- const zdevDir = path6.join(this.root, ".zdev");
1166
- if (!fs7.existsSync(zdevDir) || !fs7.statSync(zdevDir).isDirectory()) return [];
1167
- return fs7.readdirSync(zdevDir).filter((f) => /\.(md|markdown)$/i.test(f) && fs7.statSync(path6.join(zdevDir, f)).isFile()).sort();
1294
+ const zdevDir = path7.join(this.root, ".zdev");
1295
+ if (!fs8.existsSync(zdevDir) || !fs8.statSync(zdevDir).isDirectory()) return [];
1296
+ return fs8.readdirSync(zdevDir).filter((f) => /\.(md|markdown)$/i.test(f) && fs8.statSync(path7.join(zdevDir, f)).isFile()).sort();
1168
1297
  } catch {
1169
1298
  return [];
1170
1299
  }
@@ -1193,7 +1322,7 @@ var apply5 = {
1193
1322
  return;
1194
1323
  }
1195
1324
  try {
1196
- const body = JSON.parse(await readBody2(req) || "{}");
1325
+ const body = JSON.parse(await readBody(req) || "{}");
1197
1326
  const data = reviewStore.updateItem(body.id, { answer: body.answer, state: body.state });
1198
1327
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" });
1199
1328
  res.end(JSON.stringify(data));
@@ -1209,7 +1338,7 @@ var apply5 = {
1209
1338
  return;
1210
1339
  }
1211
1340
  try {
1212
- const body = JSON.parse(await readBody2(req) || "{}");
1341
+ const body = JSON.parse(await readBody(req) || "{}");
1213
1342
  const data = reviewStore.setStatus(body.status);
1214
1343
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" });
1215
1344
  res.end(JSON.stringify(data));
@@ -1225,36 +1354,37 @@ var apply5 = {
1225
1354
  });
1226
1355
  }
1227
1356
  };
1228
- async function readBody2(req) {
1229
- return new Promise((resolve) => {
1230
- let data = "";
1231
- req.on("data", (c) => {
1232
- data += c;
1233
- });
1234
- req.on("end", () => resolve(data));
1235
- });
1236
- }
1237
1357
 
1238
1358
  // src/plugins/apply/scan.ts
1239
- import fs8 from "fs";
1240
- import path7 from "path";
1359
+ import fs9 from "fs";
1360
+ import path8 from "path";
1361
+
1362
+ // src/plugins/apply/parse-tasks.ts
1363
+ var TASK_RE = /^\s*-\s*\[([ xX])\]\s*(.*)$/;
1364
+ function parseTasks(md) {
1365
+ return md.split("\n").filter((l) => TASK_RE.test(l)).map((l) => {
1366
+ const m = l.match(TASK_RE);
1367
+ return { text: (m?.[2] ?? l).trim(), checked: m?.[1] !== " " };
1368
+ });
1369
+ }
1241
1370
  function countTasks(md) {
1242
- const all = (md.match(/^\s*-\s*\[[ xX]\]\s*/gm) || []).length;
1243
- const done = (md.match(/^\s*-\s*\[[xX]\]\s*/gm) || []).length;
1244
- return { total: all, done };
1371
+ const tasks = parseTasks(md);
1372
+ return { total: tasks.length, done: tasks.filter((t) => t.checked).length };
1245
1373
  }
1374
+
1375
+ // src/plugins/apply/scan.ts
1246
1376
  function readText(p) {
1247
1377
  try {
1248
- return fs8.readFileSync(p, "utf8");
1378
+ return fs9.readFileSync(p, "utf8");
1249
1379
  } catch {
1250
1380
  return "";
1251
1381
  }
1252
1382
  }
1253
1383
  function worktreeDir(root, name) {
1254
- return path7.join(root, ".zworktree", name);
1384
+ return path8.join(root, ".zworktree", name);
1255
1385
  }
1256
1386
  function changeDir(root, name) {
1257
- return path7.join(root, "openspec", "changes", name);
1387
+ return path8.join(root, "openspec", "changes", name);
1258
1388
  }
1259
1389
  function parseDependsOn(proposal) {
1260
1390
  const deps = [];
@@ -1268,42 +1398,42 @@ function parseDependsOn(proposal) {
1268
1398
  return deps;
1269
1399
  }
1270
1400
  function scanApplyChanges(root) {
1271
- const changesDir = path7.join(root, "openspec", "changes");
1272
- if (!fs8.existsSync(changesDir)) return [];
1401
+ const changesDir = path8.join(root, "openspec", "changes");
1402
+ if (!fs9.existsSync(changesDir)) return [];
1273
1403
  const out = [];
1274
- for (const ent of fs8.readdirSync(changesDir, { withFileTypes: true })) {
1404
+ for (const ent of fs9.readdirSync(changesDir, { withFileTypes: true })) {
1275
1405
  if (!ent.isDirectory() || ent.name.startsWith(".") || ent.name === "archive") continue;
1276
1406
  const wt = worktreeDir(root, ent.name);
1277
- const tasks = readText(path7.join(wt, "openspec", "changes", ent.name, "tasks.md"));
1407
+ const tasks = readText(path8.join(wt, "openspec", "changes", ent.name, "tasks.md"));
1278
1408
  if (!tasks) {
1279
- const fallback = readText(path7.join(changesDir, ent.name, "tasks.md"));
1409
+ const fallback = readText(path8.join(changesDir, ent.name, "tasks.md"));
1280
1410
  if (fallback) {
1281
1411
  const { total, done } = countTasks(fallback);
1282
- const wtChange = path7.join(wt, "openspec", "changes", ent.name);
1283
- const mainChange = path7.join(changesDir, ent.name);
1412
+ const wtChange = path8.join(wt, "openspec", "changes", ent.name);
1413
+ const mainChange = path8.join(changesDir, ent.name);
1284
1414
  out.push({
1285
1415
  name: ent.name,
1286
1416
  path: `openspec/changes/${ent.name}`,
1287
1417
  total,
1288
1418
  done,
1289
- hasProposal: fs8.existsSync(path7.join(wtChange, "proposal.md")) || fs8.existsSync(path7.join(mainChange, "proposal.md")),
1290
- hasDesign: fs8.existsSync(path7.join(wtChange, "design.md")) || fs8.existsSync(path7.join(mainChange, "design.md")),
1419
+ hasProposal: fs9.existsSync(path8.join(wtChange, "proposal.md")) || fs9.existsSync(path8.join(mainChange, "proposal.md")),
1420
+ hasDesign: fs9.existsSync(path8.join(wtChange, "design.md")) || fs9.existsSync(path8.join(mainChange, "design.md")),
1291
1421
  // worktree 目录存在即算执行中(tasks.md 可能尚未落盘)
1292
- inWorktree: fs8.existsSync(wtChange)
1422
+ inWorktree: fs9.existsSync(wtChange)
1293
1423
  });
1294
1424
  }
1295
1425
  } else {
1296
1426
  const { total, done } = countTasks(tasks);
1297
- const wtChange = path7.join(wt, "openspec", "changes", ent.name);
1298
- const mainChange = path7.join(changesDir, ent.name);
1427
+ const wtChange = path8.join(wt, "openspec", "changes", ent.name);
1428
+ const mainChange = path8.join(changesDir, ent.name);
1299
1429
  out.push({
1300
1430
  name: ent.name,
1301
1431
  path: `openspec/changes/${ent.name}`,
1302
1432
  total,
1303
1433
  done,
1304
1434
  // 任一侧存在即算(详情读取是 worktree 优先、主目录兜底,列表标记保持一致)
1305
- hasProposal: fs8.existsSync(path7.join(wtChange, "proposal.md")) || fs8.existsSync(path7.join(mainChange, "proposal.md")),
1306
- hasDesign: fs8.existsSync(path7.join(wtChange, "design.md")) || fs8.existsSync(path7.join(mainChange, "design.md")),
1435
+ hasProposal: fs9.existsSync(path8.join(wtChange, "proposal.md")) || fs9.existsSync(path8.join(mainChange, "proposal.md")),
1436
+ hasDesign: fs9.existsSync(path8.join(wtChange, "design.md")) || fs9.existsSync(path8.join(mainChange, "design.md")),
1307
1437
  inWorktree: true
1308
1438
  });
1309
1439
  }
@@ -1314,14 +1444,14 @@ function scanApplyChanges(root) {
1314
1444
  function readApplyChange(root, name) {
1315
1445
  const wt = worktreeDir(root, name);
1316
1446
  const mainChangeDir = changeDir(root, name);
1317
- const proposalFrom = fs8.existsSync(path7.join(wt, "openspec", "changes", name, "proposal.md")) ? path7.join(wt, "openspec", "changes", name, "proposal.md") : path7.join(mainChangeDir, "proposal.md");
1318
- const designFrom = fs8.existsSync(path7.join(wt, "openspec", "changes", name, "design.md")) ? path7.join(wt, "openspec", "changes", name, "design.md") : path7.join(mainChangeDir, "design.md");
1319
- const tasksFrom = fs8.existsSync(path7.join(wt, "openspec", "changes", name, "tasks.md")) ? path7.join(wt, "openspec", "changes", name, "tasks.md") : path7.join(mainChangeDir, "tasks.md");
1447
+ const proposalFrom = fs9.existsSync(path8.join(wt, "openspec", "changes", name, "proposal.md")) ? path8.join(wt, "openspec", "changes", name, "proposal.md") : path8.join(mainChangeDir, "proposal.md");
1448
+ const designFrom = fs9.existsSync(path8.join(wt, "openspec", "changes", name, "design.md")) ? path8.join(wt, "openspec", "changes", name, "design.md") : path8.join(mainChangeDir, "design.md");
1449
+ const tasksFrom = fs9.existsSync(path8.join(wt, "openspec", "changes", name, "tasks.md")) ? path8.join(wt, "openspec", "changes", name, "tasks.md") : path8.join(mainChangeDir, "tasks.md");
1320
1450
  const proposal = readText(proposalFrom);
1321
1451
  const design = readText(designFrom);
1322
1452
  const tasks = readText(tasksFrom);
1323
1453
  const { total, done } = countTasks(tasks);
1324
- const inWorktree = fs8.existsSync(path7.join(wt, "openspec", "changes", name));
1454
+ const inWorktree = fs9.existsSync(path8.join(wt, "openspec", "changes", name));
1325
1455
  const dependsOn = parseDependsOn(proposal);
1326
1456
  const hasTestStrategy = /^##\s+测试策略\s*$/m.test(design);
1327
1457
  return {
@@ -1379,13 +1509,12 @@ var apply6 = {
1379
1509
  };
1380
1510
 
1381
1511
  // src/server/design-assets.ts
1382
- import fs9 from "fs";
1383
- import path8 from "path";
1512
+ import path9 from "path";
1384
1513
  var PAGE_EXTS = [".html", ".htm"];
1385
1514
  var ICON_EXTS = [".svg", ".png", ".ico", ".jpg", ".jpeg", ".gif", ".webp"];
1386
1515
  var VIDEO_EXTS = [".mp4", ".webm", ".mov", ".ogg", ".ogv"];
1387
1516
  var AUDIO_EXTS = [".mp3", ".wav", ".flac", ".aac", ".m4a"];
1388
- var CODE_EXTS = [".js", ".mjs", ".ts", ".tsx", ".jsx", ".css", ".json", ".txt", ".xml", ".yml", ".yaml", ".sh", ".md"];
1517
+ var CODE_EXTS = [".js", ".mjs", ".cjs", ".mts", ".cts", ".ts", ".tsx", ".jsx", ".css", ".json", ".txt", ".xml", ".yml", ".yaml", ".sh", ".md"];
1389
1518
  var FONT_EXTS = [".woff", ".woff2", ".ttf", ".otf"];
1390
1519
  var TOKEN_RE = /token|theme|design|color|palette|typograph/i;
1391
1520
  function categorize(rel, ext) {
@@ -1408,26 +1537,11 @@ function scanAssets(root) {
1408
1537
  const keys = ["page", "component", "icon", "token", "md", "video", "audio", "pdf", "font", "other"];
1409
1538
  for (const k of keys) out[k] = [];
1410
1539
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".cache"]);
1411
- function walk(dir, rel) {
1412
- let ents;
1413
- try {
1414
- ents = fs9.readdirSync(dir, { withFileTypes: true });
1415
- } catch {
1416
- return;
1417
- }
1418
- for (const ent of ents) {
1419
- if (ent.name.startsWith(".") || SKIP.has(ent.name)) continue;
1420
- const r = rel ? `${rel}/${ent.name}` : ent.name;
1421
- if (ent.isDirectory()) {
1422
- walk(path8.join(dir, ent.name), r);
1423
- continue;
1424
- }
1425
- const ext = path8.extname(ent.name).toLowerCase();
1426
- const t = categorize(r, ext);
1427
- if (t) out[t].push({ path: r, name: ent.name, ext, type: t });
1428
- }
1429
- }
1430
- walk(root, "");
1540
+ walkDir(root, { skip: SKIP, onFile: (abs, rel) => {
1541
+ const ext = path9.extname(abs).toLowerCase();
1542
+ const t = categorize(rel, ext);
1543
+ if (t) out[t].push({ path: rel, name: path9.basename(abs), ext, type: t });
1544
+ } });
1431
1545
  return out;
1432
1546
  }
1433
1547
 
@@ -1457,53 +1571,42 @@ var apply8 = {
1457
1571
 
1458
1572
  // src/plugins/stats/index.ts
1459
1573
  import fs10 from "fs";
1460
- import path9 from "path";
1574
+ import path10 from "path";
1461
1575
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".cache"]);
1462
1576
  function scan(root) {
1463
1577
  const stats = {
1464
- root: path9.basename(root),
1578
+ root: path10.basename(root),
1465
1579
  files: 0,
1466
1580
  dirs: 0,
1467
1581
  totalSize: 0,
1468
1582
  byExt: [],
1469
1583
  markdown: 0,
1470
1584
  openspec: { active: 0, archived: 0 },
1471
- hasJust: fs10.existsSync(path9.join(root, "justfile"))
1585
+ hasJust: fs10.existsSync(path10.join(root, "justfile"))
1472
1586
  };
1473
1587
  const extMap = /* @__PURE__ */ new Map();
1474
1588
  const walk = (dir) => {
1475
- let ents;
1476
- try {
1477
- ents = fs10.readdirSync(dir, { withFileTypes: true });
1478
- } catch {
1479
- return;
1480
- }
1481
- for (const ent of ents) {
1482
- if (ent.name.startsWith(".") || SKIP_DIRS.has(ent.name)) continue;
1483
- const abs = path9.join(dir, ent.name);
1484
- if (ent.isDirectory()) {
1485
- stats.dirs++;
1486
- walk(abs);
1487
- continue;
1488
- }
1589
+ walkDir(dir, { skip: SKIP_DIRS, onDir: () => {
1590
+ stats.dirs++;
1591
+ }, onFile: (abs) => {
1489
1592
  stats.files++;
1490
1593
  try {
1491
1594
  stats.totalSize += fs10.statSync(abs).size;
1492
1595
  } catch {
1493
1596
  }
1494
- const ext = path9.extname(ent.name).toLowerCase() || "(\u65E0\u6269\u5C55\u540D)";
1597
+ const ext = path10.extname(abs).toLowerCase() || "(\u65E0\u6269\u5C55\u540D)";
1495
1598
  extMap.set(ext, (extMap.get(ext) ?? 0) + 1);
1496
1599
  if (ext === ".md" || ext === ".markdown") stats.markdown++;
1497
- }
1600
+ } });
1498
1601
  };
1499
1602
  walk(root);
1500
- const changesDir = path9.join(root, "openspec", "changes");
1603
+ const changesDir = path10.join(root, "openspec", "changes");
1501
1604
  if (fs10.existsSync(changesDir)) {
1502
1605
  for (const ent of fs10.readdirSync(changesDir, { withFileTypes: true })) {
1503
1606
  if (!ent.isDirectory() || ent.name.startsWith(".")) continue;
1504
1607
  if (ent.name === "archive") {
1505
1608
  try {
1506
- stats.openspec.archived = fs10.readdirSync(path9.join(changesDir, "archive"), { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).length;
1609
+ stats.openspec.archived = fs10.readdirSync(path10.join(changesDir, "archive"), { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).length;
1507
1610
  } catch {
1508
1611
  }
1509
1612
  } else stats.openspec.active++;
@@ -1534,34 +1637,31 @@ var apply9 = {
1534
1637
  };
1535
1638
 
1536
1639
  // src/cli.ts
1537
- var __dirname2 = path10.dirname(fileURLToPath2(import.meta.url));
1640
+ var __dirname2 = path11.dirname(fileURLToPath2(import.meta.url));
1538
1641
  var DEFAULT_PORT = 4190;
1539
1642
  function parseArgs() {
1540
1643
  const args = process.argv.slice(2);
1541
- const opts = {};
1542
- let portExplicit = false;
1543
- for (let i = 0; i < args.length; i++) {
1544
- const a = args[i];
1545
- if (a.startsWith("--")) {
1546
- const key = a.slice(2);
1547
- const next = args[i + 1];
1548
- if (next && !next.startsWith("--")) {
1549
- opts[key] = next;
1550
- i++;
1551
- } else {
1552
- opts[key] = true;
1553
- }
1554
- if (key === "port") portExplicit = true;
1555
- }
1556
- }
1644
+ const { values } = utilParseArgs({
1645
+ options: {
1646
+ dir: { type: "string" },
1647
+ port: { type: "string" },
1648
+ open: { type: "boolean", default: false },
1649
+ page: { type: "string" },
1650
+ restart: { type: "boolean", default: false },
1651
+ plugins: { type: "string" }
1652
+ },
1653
+ args,
1654
+ strict: false,
1655
+ allowPositionals: false
1656
+ });
1557
1657
  return {
1558
- dir: typeof opts.dir === "string" ? opts.dir : process.cwd(),
1559
- port: typeof opts.port === "string" ? Number(opts.port) : DEFAULT_PORT,
1560
- portExplicit,
1561
- open: !!opts.open,
1562
- page: typeof opts.page === "string" ? opts.page : null,
1563
- restart: !!opts.restart,
1564
- plugins: typeof opts.plugins === "string" ? opts.plugins : null
1658
+ dir: typeof values.dir === "string" ? values.dir : process.cwd(),
1659
+ port: typeof values.port === "string" ? Number(values.port) : DEFAULT_PORT,
1660
+ portExplicit: args.some((a) => a === "--port" || a.startsWith("--port=")),
1661
+ open: !!values.open,
1662
+ page: typeof values.page === "string" ? values.page : null,
1663
+ restart: !!values.restart,
1664
+ plugins: typeof values.plugins === "string" ? values.plugins : null
1565
1665
  };
1566
1666
  }
1567
1667
  async function pathExists(p) {
@@ -1587,7 +1687,7 @@ async function loadExternal(ctx, dir, root) {
1587
1687
  if (!ent.isDirectory()) continue;
1588
1688
  const candidates = tsxLoaded ? ["index.ts", "index.js", "index.mjs"] : ["index.js", "index.mjs"];
1589
1689
  for (const name of candidates) {
1590
- const p = path10.join(dir, ent.name, name);
1690
+ const p = path11.join(dir, ent.name, name);
1591
1691
  if (await pathExists(p)) {
1592
1692
  try {
1593
1693
  const mod = await import(p);
@@ -1602,8 +1702,8 @@ async function loadExternal(ctx, dir, root) {
1602
1702
  const m = ctx.dashboard.get(ent.name);
1603
1703
  if (m && m.mode === ent.name) {
1604
1704
  const patch = { external: true };
1605
- const webDir = path10.join(dir, ent.name, "web");
1606
- if (await pathExists(path10.join(webDir, "index.html"))) {
1705
+ const webDir = path11.join(dir, ent.name, "web");
1706
+ if (await pathExists(path11.join(webDir, "index.html"))) {
1607
1707
  ctx.server.static(`${PLUGIN_STATIC_PREFIX}${ent.name}/`, webDir);
1608
1708
  if (!m.viewerUrl) patch.viewerUrl = `${PLUGIN_STATIC_PREFIX}${ent.name}/`;
1609
1709
  }
@@ -1622,7 +1722,7 @@ async function loadExternal(ctx, dir, root) {
1622
1722
  }
1623
1723
  async function main() {
1624
1724
  const args = parseArgs();
1625
- const root = path10.resolve(args.dir);
1725
+ const root = path11.resolve(args.dir);
1626
1726
  const existing = await findReusable(root);
1627
1727
  if (existing && !args.restart) {
1628
1728
  const u = `http://localhost:${existing.port}` + (args.page ? `#${args.page}` : "");
@@ -1636,9 +1736,9 @@ async function main() {
1636
1736
  console.log(`[zdashboard] --restart\uFF1A\u505C\u6B62\u65E7\u5B9E\u4F8B pid=${oldRecord.pid}`);
1637
1737
  await stopInstance(oldRecord);
1638
1738
  }
1639
- const appDir = path10.resolve(__dirname2, "web");
1739
+ const appDir = path11.resolve(__dirname2, "web");
1640
1740
  const startPort = oldRecord && !args.portExplicit ? oldRecord.port : args.port;
1641
- const zdevDir = path10.join(root, ".zdev");
1741
+ const zdevDir = path11.join(root, ".zdev");
1642
1742
  const dataDir = fs11.existsSync(zdevDir) ? ".zdev/" : "";
1643
1743
  const det = await detect(root);
1644
1744
  const ctx = new Context();
@@ -1673,7 +1773,7 @@ async function main() {
1673
1773
  }
1674
1774
  }
1675
1775
  if (args.plugins) {
1676
- await loadExternal(ctx, path10.resolve(args.plugins), root);
1776
+ await loadExternal(ctx, path11.resolve(args.plugins), root);
1677
1777
  }
1678
1778
  const shutdown = () => {
1679
1779
  try {