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