zombie-mermaid 5.11.2 → 5.12.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 (3) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +106 -51
  3. package/package.json +7 -6
package/README.md CHANGED
@@ -30,7 +30,7 @@ Ultra-fast, fully themeable, zero DOM dependencies. A maintained fork of [`beaut
30
30
 
31
31
  `beautiful-mermaid` is a genuinely good library — fast, beautiful, and works everywhere from rich UIs to plain terminals. Checkable facts, as of 2026-10-13: the last push to upstream's `main` was 2026-05-06 and its latest release is v1.1.2 (2026-02-26); it has [43 open pull requests](https://github.com/lukilabs/beautiful-mermaid/pulls) (19 opened before 2026-04-13) and [50 open issues](https://github.com/lukilabs/beautiful-mermaid/issues) (live counts at those links; the figures above are a dated snapshot). I maintain `zombie-mermaid` as the fork that won't stay buried: pulling in upstream fixes, giving PRs stuck in the upstream queue a home, and actually shipping releases. Craft and Craft Agents aren't part of this project's process going forward; this is an independently maintained continuation.
32
32
 
33
- This isn't just a claim — see **[what this fork fixes](https://dfadler.github.io/zombie-mermaid/fork-fixes.html)** for an evidence-based before/after showcase (54 fixes): every bug listed there is rendered by the actual pre-fix and post-fix code, not described from memory.
33
+ This isn't just a claim — see **[what this fork fixes](https://dfadler.github.io/zombie-mermaid/fork-fixes.html)** for an evidence-based before/after showcase (55 fixes): every bug listed there is rendered by the actual pre-fix and post-fix code, not described from memory.
34
34
 
35
35
  It's still MIT-licensed, still built on the same foundation, and still credits the original authors — including the ASCII rendering engine's origins — in [Attribution](#attribution) below. Who maintains it, how releases are published, and what is claimed: [provenance and continuity](docs/provenance.md).
36
36
 
@@ -214,6 +214,7 @@ zombie-mermaid render diagram.mmd --ascii -o out.txt # Write the ASCII render
214
214
  zombie-mermaid render diagram.mmd --html # Self-contained pan/zoom HTML viewer
215
215
  zombie-mermaid render diagram.mmd --png # Rasterize to diagram.png
216
216
  cat diagram.mmd | zombie-mermaid render --ascii # Read from stdin
217
+ zombie-mermaid --check README.md docs/*.md # Validate every mermaid block in Markdown; exit 1 with file:line diagnostics
217
218
  zombie-mermaid themes # List built-in theme names
218
219
  zombie-mermaid --help # Show all options
219
220
  ```
package/dist/cli.js CHANGED
@@ -45,7 +45,7 @@ var T = {
45
45
  label: "the PNG rasterization"
46
46
  }
47
47
  }, E = Object.keys(T);
48
- function D(e, t) {
48
+ function ee(e, t) {
49
49
  let i = r(e);
50
50
  return n({
51
51
  dir: i.dir,
@@ -53,12 +53,21 @@ function D(e, t) {
53
53
  ext: t
54
54
  });
55
55
  }
56
- function O(e) {
56
+ function D(e) {
57
57
  let [t, ...n] = e;
58
58
  if (t === void 0 || t === "--help" || t === "-h") return { command: "help" };
59
59
  if (t === "--version" || t === "-v") return { command: "version" };
60
+ if (t === "--check") {
61
+ if (n.length === 0) throw Error("--check requires at least one Markdown file");
62
+ let e = n.find((e) => e.startsWith("-"));
63
+ if (e !== void 0) throw Error(`Unknown flag: ${e}`);
64
+ return {
65
+ command: "check",
66
+ files: n
67
+ };
68
+ }
60
69
  if (t === "themes") return { command: "themes" };
61
- if (t === "render") return M(n);
70
+ if (t === "render") return j(n);
62
71
  if (t === "validate") {
63
72
  let e = n.find((e, t) => t > 0 || e.startsWith("-"));
64
73
  if (e !== void 0) throw Error(`Unexpected argument: ${e}`);
@@ -67,17 +76,17 @@ function O(e) {
67
76
  input: n[0]
68
77
  };
69
78
  }
70
- if (t === "web") return ee(n);
79
+ if (t === "web") return M(n);
71
80
  if (t === "mcp") return { command: "mcp" };
72
81
  throw Error(`Unknown command: ${t}`);
73
82
  }
74
- function k(e, t, n) {
83
+ function O(e, t, n) {
75
84
  let r = e[t + 1];
76
85
  if (t + 1 >= e.length || r === void 0) throw Error(`${n} requires a numeric value`);
77
86
  if (!/^\d+$/.test(r)) throw Error(`${n} requires a non-negative integer, got: "${r}"`);
78
87
  return Number(r);
79
88
  }
80
- function A(e, t, n) {
89
+ function k(e, t, n) {
81
90
  let r = e[t + 1];
82
91
  if (t + 1 >= e.length || r === void 0) throw Error(`${n} requires a value ("auto" or a positive integer)`);
83
92
  if (r === "auto") return "auto";
@@ -85,14 +94,14 @@ function A(e, t, n) {
85
94
  if (!/^\d+$/.test(r) || i === 0 || !Number.isSafeInteger(i)) throw Error(`${n} requires "auto" or a positive integer, got: "${r}"`);
86
95
  return i;
87
96
  }
88
- function j(e, t, n) {
97
+ function A(e, t, n) {
89
98
  let r = e[t + 1];
90
99
  if (t + 1 >= e.length || r === void 0) throw Error(`${n} requires a value (one of TD, TB, BT, LR, RL)`);
91
100
  let i = r.toUpperCase();
92
101
  if (!s(i)) throw Error(`${n} requires one of TD, TB, BT, LR, RL, got: "${r}"`);
93
102
  return i;
94
103
  }
95
- function M(e) {
104
+ function j(e) {
96
105
  let t, n = !1, r = !1, i = !1, a = !1, o = !1, s, c = !1, l, u, d, f, p = !1, m = !1, h, g, _, v, y = 0;
97
106
  for (; y < e.length;) {
98
107
  let b = e[y];
@@ -109,12 +118,12 @@ function M(e) {
109
118
  else if (b === "--theme") {
110
119
  if (y + 1 >= e.length) throw Error("--theme requires a theme name");
111
120
  l = e[y + 1], y += 2;
112
- } else if (b === "-x" || b === "--paddingX") u = k(e, y, b), y += 2;
113
- else if (b === "-y" || b === "--paddingY") d = k(e, y, b), y += 2;
114
- else if (b === "-p" || b === "--borderPadding") f = k(e, y, b), y += 2;
121
+ } else if (b === "-x" || b === "--paddingX") u = O(e, y, b), y += 2;
122
+ else if (b === "-y" || b === "--paddingY") d = O(e, y, b), y += 2;
123
+ else if (b === "-p" || b === "--borderPadding") f = O(e, y, b), y += 2;
115
124
  else if (b === "--coords") p = !0, y++;
116
125
  else if (b === "--hyperlinks") m = !0, y++;
117
- else if (b === "-w" || b === "--max-width") h = A(e, y, b), y += 2;
126
+ else if (b === "-w" || b === "--max-width") h = k(e, y, b), y += 2;
118
127
  else if (b === "--profile") {
119
128
  let t = e[y + 1];
120
129
  if (t !== "markdown") throw Error(`--profile requires one of: markdown, got: "${t ?? ""}"`);
@@ -123,7 +132,7 @@ function M(e) {
123
132
  let t = b.indexOf("="), n = t === -1 ? e[y + 1] : b.slice(t + 1);
124
133
  if (n !== "mermaid" && n !== "mermaid-wide") throw Error(`--layout requires one of: mermaid, mermaid-wide, got: "${n ?? ""}"`);
125
134
  _ = n, y += t === -1 ? 2 : 1;
126
- } else if (b === "--direction") v = j(e, y, b), y += 2;
135
+ } else if (b === "--direction") v = A(e, y, b), y += 2;
127
136
  else if (!b.startsWith("-") || b === "-") {
128
137
  if (b === "-") throw Error("Unexpected argument: - (use -o - to write output to stdout; omit the input file to read stdin)");
129
138
  if (t !== void 0) throw Error(`Unexpected argument: ${b} (input file already set to "${t}")`);
@@ -162,19 +171,19 @@ function M(e) {
162
171
  }
163
172
  throw Error("Specify --ascii, --svg, --html, and/or --png (or -o <path> with a .svg/.txt/.html/.png extension)");
164
173
  }
165
- let O = {
174
+ let D = {
166
175
  svg: r,
167
176
  html: a,
168
177
  png: o
169
- }, M = E.find((e) => O[e]);
170
- if (s === "-" && n && M !== void 0) {
171
- let e = T[M].display;
178
+ }, j = E.find((e) => D[e]);
179
+ if (s === "-" && n && j !== void 0) {
180
+ let e = T[j].display;
172
181
  throw Error(`-o - would send both ASCII and ${e} to stdout; drop --ascii, or write the ${e} to a file path`);
173
182
  }
174
- if (M !== void 0 && s === void 0) {
175
- let { flag: e, ext: n } = T[M];
183
+ if (j !== void 0 && s === void 0) {
184
+ let { flag: e, ext: n } = T[j];
176
185
  if (t === void 0) throw Error(`${e} needs -o <path> (or -o - for stdout) when reading from stdin — there is no input file name to derive an output name from`);
177
- s = D(t, n);
186
+ s = ee(t, n);
178
187
  }
179
188
  if (h !== void 0 && !n) throw Error("-w/--max-width requires --ascii");
180
189
  if (g !== void 0 && !n) throw Error("--profile requires --ascii");
@@ -203,12 +212,12 @@ function M(e) {
203
212
  direction: v
204
213
  };
205
214
  }
206
- function ee(e) {
215
+ function M(e) {
207
216
  let t = S, n = 0;
208
217
  for (; n < e.length;) {
209
218
  let r = e[n];
210
219
  if (r === void 0) throw Error(`parseArgs: index ${n} out of range while parsing arguments`);
211
- if (r === "--port") t = k(e, n, r), n += 2;
220
+ if (r === "--port") t = O(e, n, r), n += 2;
212
221
  else throw Error(`Unknown flag: ${r}`);
213
222
  }
214
223
  return {
@@ -456,8 +465,47 @@ async function Y() {
456
465
  });
457
466
  }
458
467
  //#endregion
468
+ //#region src/cli/check.ts
469
+ var he = /^ {0,3}(`{3,}|~{3,})\s*mermaid\b[^`]*$/i, ge = /^ {0,3}(`+|~+)\s*$/;
470
+ function _e(e, t) {
471
+ let n = ge.exec(e)?.[1];
472
+ return !!n && n[0] === t[0] && n.length >= t.length;
473
+ }
474
+ function ve(e) {
475
+ let t = e.split(/\r?\n/), n = [];
476
+ for (let e = 0; e < t.length; e++) {
477
+ let r = he.exec(t[e]);
478
+ if (!r) continue;
479
+ let i = r[1], a = [], o = e + 1;
480
+ for (; o < t.length && !_e(t[o], i);) a.push(t[o++]);
481
+ n.push({
482
+ startLine: e + 2,
483
+ source: a.join("\n")
484
+ }), e = o;
485
+ }
486
+ return n;
487
+ }
488
+ async function ye(e) {
489
+ let t = 0, n = 0;
490
+ for (let r of e.files) {
491
+ let e = await u(r, "utf-8");
492
+ for (let i of ve(e)) {
493
+ n++;
494
+ try {
495
+ g(i.source);
496
+ } catch (e) {
497
+ t++;
498
+ let n = N(e, i.source), a = i.startLine + (n ? n.line - 1 : 0), o = n ? n.message : e instanceof Error ? e.message : String(e);
499
+ console.error(`${r}:${a}: ${o}`), n && console.error(` ${a} | ${n.sourceLine}`);
500
+ }
501
+ }
502
+ }
503
+ if (t > 0) throw Error(`${t} of ${n} Mermaid block(s) invalid`);
504
+ console.log(`${n} Mermaid block(s) valid`);
505
+ }
506
+ //#endregion
459
507
  //#region src/cli/validate.ts
460
- async function he(e, t) {
508
+ async function be(e, t) {
461
509
  let n;
462
510
  if (e.input !== void 0) n = await u(e.input, "utf-8");
463
511
  else if (t !== void 0) n = t;
@@ -476,12 +524,12 @@ async function he(e, t) {
476
524
  }
477
525
  //#endregion
478
526
  //#region src/cli/web.ts
479
- var ge = 1e6;
480
- function _e(e) {
527
+ var xe = 1e6;
528
+ function Se(e) {
481
529
  return new Promise((t, n) => {
482
530
  let r = [], i = 0;
483
531
  e.on("data", (t) => {
484
- if (i += t.length, i > ge) {
532
+ if (i += t.length, i > xe) {
485
533
  n(/* @__PURE__ */ Error("Request body too large")), e.destroy();
486
534
  return;
487
535
  }
@@ -492,7 +540,7 @@ function _e(e) {
492
540
  function X(e) {
493
541
  return typeof e == "object" && !!e;
494
542
  }
495
- function ve(e) {
543
+ function Z(e) {
496
544
  let t;
497
545
  try {
498
546
  t = JSON.parse(e);
@@ -507,80 +555,80 @@ function ve(e) {
507
555
  format: t.format === "svg" ? "svg" : "ascii"
508
556
  };
509
557
  }
510
- function Z(e, t, n) {
558
+ function Q(e, t, n) {
511
559
  let r = JSON.stringify(n);
512
560
  e.writeHead(t, {
513
561
  "Content-Type": "application/json; charset=utf-8",
514
562
  "Content-Length": Buffer.byteLength(r)
515
563
  }), e.end(r);
516
564
  }
517
- async function ye(e, t) {
565
+ async function Ce(e, t) {
518
566
  try {
519
- let { source: n, format: r } = ve(await _e(e));
520
- Z(t, 200, {
567
+ let { source: n, format: r } = Z(await Se(e));
568
+ Q(t, 200, {
521
569
  output: r === "svg" ? h(n) : p(n),
522
570
  format: r
523
571
  });
524
572
  } catch (e) {
525
- Z(t, 400, { error: e instanceof Error ? e.message : String(e) });
573
+ Q(t, 400, { error: e instanceof Error ? e.message : String(e) });
526
574
  }
527
575
  }
528
- var be = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>zombie-mermaid</title>\n<style>\n body { font-family: system-ui, sans-serif; margin: 2rem; max-width: 900px; }\n h1 { font-size: 1.25rem; }\n textarea { width: 100%; height: 200px; font-family: monospace; font-size: 14px; box-sizing: border-box; }\n pre { white-space: pre; overflow: auto; background: #111; color: #eee; padding: 1rem; border-radius: 4px; }\n .row { display: flex; gap: 1rem; align-items: center; margin: 0.75rem 0; }\n #error { color: #c00; white-space: pre-wrap; }\n #output svg { max-width: 100%; }\n</style>\n</head>\n<body>\n<h1>zombie-mermaid</h1>\n<textarea id=\"source\" placeholder=\"graph LR\n A --> B\">graph LR\n A --> B --> C</textarea>\n<div class=\"row\">\n <label><input type=\"radio\" name=\"format\" value=\"ascii\" checked /> ASCII</label>\n <label><input type=\"radio\" name=\"format\" value=\"svg\" /> SVG</label>\n <button id=\"render\" type=\"button\">Render</button>\n</div>\n<div id=\"error\"></div>\n<div id=\"output\"></div>\n<script>\n const sourceEl = document.getElementById('source')\n const outputEl = document.getElementById('output')\n const errorEl = document.getElementById('error')\n\n async function render() {\n errorEl.textContent = ''\n outputEl.innerHTML = ''\n const formatInput = document.querySelector('input[name=\"format\"]:checked')\n const format = formatInput ? formatInput.value : 'ascii'\n try {\n const res = await fetch('/render', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ source: sourceEl.value, format }),\n })\n const data = await res.json()\n if (!res.ok) {\n errorEl.textContent = data.error || 'Render failed'\n return\n }\n if (format === 'svg') {\n outputEl.innerHTML = data.output\n } else {\n const pre = document.createElement('pre')\n pre.textContent = data.output\n outputEl.appendChild(pre)\n }\n } catch (err) {\n errorEl.textContent = String(err)\n }\n }\n\n document.getElementById('render').addEventListener('click', render)\n<\/script>\n</body>\n</html>\n";
529
- function xe() {
576
+ var we = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>zombie-mermaid</title>\n<style>\n body { font-family: system-ui, sans-serif; margin: 2rem; max-width: 900px; }\n h1 { font-size: 1.25rem; }\n textarea { width: 100%; height: 200px; font-family: monospace; font-size: 14px; box-sizing: border-box; }\n pre { white-space: pre; overflow: auto; background: #111; color: #eee; padding: 1rem; border-radius: 4px; }\n .row { display: flex; gap: 1rem; align-items: center; margin: 0.75rem 0; }\n #error { color: #c00; white-space: pre-wrap; }\n #output svg { max-width: 100%; }\n</style>\n</head>\n<body>\n<h1>zombie-mermaid</h1>\n<textarea id=\"source\" placeholder=\"graph LR\n A --> B\">graph LR\n A --> B --> C</textarea>\n<div class=\"row\">\n <label><input type=\"radio\" name=\"format\" value=\"ascii\" checked /> ASCII</label>\n <label><input type=\"radio\" name=\"format\" value=\"svg\" /> SVG</label>\n <button id=\"render\" type=\"button\">Render</button>\n</div>\n<div id=\"error\"></div>\n<div id=\"output\"></div>\n<script>\n const sourceEl = document.getElementById('source')\n const outputEl = document.getElementById('output')\n const errorEl = document.getElementById('error')\n\n async function render() {\n errorEl.textContent = ''\n outputEl.innerHTML = ''\n const formatInput = document.querySelector('input[name=\"format\"]:checked')\n const format = formatInput ? formatInput.value : 'ascii'\n try {\n const res = await fetch('/render', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ source: sourceEl.value, format }),\n })\n const data = await res.json()\n if (!res.ok) {\n errorEl.textContent = data.error || 'Render failed'\n return\n }\n if (format === 'svg') {\n outputEl.innerHTML = data.output\n } else {\n const pre = document.createElement('pre')\n pre.textContent = data.output\n outputEl.appendChild(pre)\n }\n } catch (err) {\n errorEl.textContent = String(err)\n }\n }\n\n document.getElementById('render').addEventListener('click', render)\n<\/script>\n</body>\n</html>\n";
577
+ function Te() {
530
578
  return v((e, t) => {
531
579
  let n = (e.url ?? "/").split("?")[0];
532
580
  if (e.method === "GET" && (n === "/" || n === "")) {
533
- t.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), t.end(be);
581
+ t.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), t.end(we);
534
582
  return;
535
583
  }
536
584
  if (e.method === "POST" && n === "/render") {
537
- ye(e, t);
585
+ Ce(e, t);
538
586
  return;
539
587
  }
540
588
  t.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }), t.end("Not found");
541
589
  });
542
590
  }
543
- function Se(e) {
591
+ function Ee(e) {
544
592
  return new Promise((t, n) => {
545
- let r = xe();
593
+ let r = Te();
546
594
  r.once("error", n), r.listen(e, "127.0.0.1", () => {
547
595
  r.removeListener("error", n), t(r);
548
596
  });
549
597
  });
550
598
  }
551
- async function Ce(e, t = console.log) {
552
- let n = await Se(e.port), r = n.address();
599
+ async function De(e, t = console.log) {
600
+ let n = await Ee(e.port), r = n.address();
553
601
  return t(`zombie-mermaid web running at http://localhost:${X(r) && typeof r.port == "number" ? r.port : e.port}`), n;
554
602
  }
555
603
  //#endregion
556
604
  //#region src/cli/mcp.ts
557
- async function we(e = (e) => console.error(e)) {
605
+ async function Oe(e = (e) => console.error(e)) {
558
606
  let t = b(), n = new y();
559
607
  return await t.connect(n), e("zombie-mermaid MCP server running on stdio (experimental — feedback welcome: https://github.com/dfadler/zombie-mermaid/issues)"), t;
560
608
  }
561
609
  //#endregion
562
610
  //#region src/package-info.ts
563
- function Q() {
611
+ function ke() {
564
612
  let t = e(import.meta.url)("../package.json");
565
613
  return typeof t == "object" && t && "version" in t && typeof t.version == "string" ? t.version : "unknown";
566
614
  }
567
615
  //#endregion
568
616
  //#region src/cli.ts
569
617
  m(x);
570
- var Te = Q();
618
+ var Ae = ke();
571
619
  async function $() {
572
620
  let e = process.argv.slice(2), t;
573
621
  try {
574
- t = O(e);
622
+ t = D(e);
575
623
  } catch (e) {
576
624
  console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
577
625
  }
578
626
  switch (t.command) {
579
627
  case "help":
580
- Ee();
628
+ je();
581
629
  break;
582
630
  case "version":
583
- console.log(`zombie-mermaid ${Te}`);
631
+ console.log(`zombie-mermaid ${Ae}`);
584
632
  break;
585
633
  case "themes":
586
634
  console.log("Available themes:\n");
@@ -595,27 +643,34 @@ async function $() {
595
643
  break;
596
644
  case "validate":
597
645
  try {
598
- await he(t);
646
+ await be(t);
647
+ } catch (e) {
648
+ console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
649
+ }
650
+ break;
651
+ case "check":
652
+ try {
653
+ await ye(t);
599
654
  } catch (e) {
600
655
  console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
601
656
  }
602
657
  break;
603
658
  case "web":
604
659
  try {
605
- await Ce(t);
660
+ await De(t);
606
661
  } catch (e) {
607
662
  console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
608
663
  }
609
664
  break;
610
665
  case "mcp": try {
611
- await we();
666
+ await Oe();
612
667
  } catch (e) {
613
668
  console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
614
669
  }
615
670
  }
616
671
  }
617
- function Ee() {
618
- console.log("zombie-mermaid — render Mermaid diagrams from the command line\n\nUsage:\n zombie-mermaid render <file> --ascii Render to ASCII in terminal\n zombie-mermaid render <file> --svg Render to <file stem>.svg\n zombie-mermaid render <file> --svg -o <out.svg> Render to a named SVG file\n zombie-mermaid render <file> -o out.svg Format inferred from the extension\n zombie-mermaid render <file> --svg -o - Write SVG to stdout\n zombie-mermaid render <file> --ascii --svg -o <out.svg> Both (ASCII to terminal)\n zombie-mermaid render <file> --html Self-contained pan/zoom HTML viewer\n zombie-mermaid render <file> --png Rasterize to <file stem>.png\n cat file.mmd | zombie-mermaid render --ascii Read from stdin\n zombie-mermaid validate [file] Parse only; exit 1 with line diagnostics if invalid\n zombie-mermaid themes List available themes\n zombie-mermaid web [--port <n>] Start a local web UI (default port: 3000)\n zombie-mermaid mcp Start an MCP server on stdio (render tools)\n zombie-mermaid --help Show this help\n zombie-mermaid --version Show version\n\nOptions:\n --ascii Print ASCII/Unicode diagram to terminal, or to -o <file.txt>\n when it is the only format\n --svg Render SVG to -o <path> (default: <input stem>.svg;\n stdin input must give -o)\n --resolve-colors Replace CSS var()/color-mix() in the SVG with computed\n sRGB values, for rasterizers and other non-browser SVG\n consumers (resvg, librsvg, Inkscape) that don't\n evaluate them. The default output stays a live function\n of its CSS variables. Requires --svg.\n --html Render a self-contained HTML pan/zoom viewer to -o\n <path> (default: <input stem>.html). Embeds the SVG;\n no server, no network, opens from disk. Cannot be\n combined with --svg/--png (different content, same\n slot).\n --png Rasterize to PNG at -o <path> (default: <input\n stem>.png), via the optional @resvg/resvg-js native\n dependency — install it with `pnpm add\n @resvg/resvg-js` if it's missing. Always resolves\n CSS var()/color-mix() first (as --resolve-colors\n does for --svg), since a rasterizer can't evaluate\n them. 1:1 with the SVG's own pixel size, no scaling.\n Cannot be combined with --svg/--html.\n -o, --output <path> Output file for the SVG/HTML/PNG (or for ASCII when\n none of those is given). '-' writes to stdout. A\n .svg, .html, .txt, or .png extension selects the\n format when no --svg/--html/--png/--ascii flag is\n given; one that contradicts the flags is an error.\n Existing files are never overwritten without --force.\n -f, --force Overwrite an existing output file\n --theme <name> Apply a built-in theme (see 'themes' command)\n --direction <dir> Override the diagram's layout direction: TD, TB, BT, LR, or RL\n (flowchart, state, and ER diagrams; nested subgraph\n directions still apply on top of it)\n -x, --paddingX <n> Horizontal spacing between nodes (ASCII, default: 5)\n -y, --paddingY <n> Vertical spacing between nodes (ASCII, default: 5; LR/RL sibling gap is n-1, min 1)\n -p, --borderPadding <n> Padding inside node boxes (ASCII, default: 1)\n --coords Overlay row/column index rulers (ASCII, debug layout)\n --hyperlinks Emit OSC 8 terminal hyperlinks for `click` hrefs\n (ASCII). Off by default: terminal support varies —\n iTerm2, WezTerm, kitty, Windows Terminal, and\n VTE-based terminals render them as clickable links;\n others may show the URL or nothing. Pagers need\n OSC 8 support too (`less -R`, recent versions).\n --profile <name> ASCII preset: 'markdown' = plain ASCII characters, no\n color, for pasting into issues/commits/fenced blocks\n (explicit flags like --coords still win). Requires\n --ascii.\n -w, --max-width <n|auto> Fit ASCII output within this many columns\n ('auto' detects the terminal width): applies compact\n spacing automatically when needed, and warns on\n stderr if it still doesn't fit (no label wrapping or\n direction-flip — see issue #335).\n --layout mermaid Opt in (flowcharts, --ascii): draw mermaid's own ELK layout\n instead of the grid renderer. Subgraphs, some shapes,\n other diagram types, layout failures and drawings that\n cannot fit 120 columns (or -w <n>, used as the cap)\n fall back to the default renderer; stderr says which\n one ran. --layout mermaid-wide drops the cap and always\n draws the (possibly wide) layout-faithful text. Merged\n parallel edges hide edge multiplicity. Depends on\n mermaid 12.1.0 + elkjs 0.9.3: install the optional peer\n with `pnpm add elkjs-0.9.3@npm:elkjs@0.9.3`. -x/-y/-p\n and --coords do not apply to the layout-faithful drawing.\n --port <n> Port for the 'web' command (default: 3000)\n -h, --help Show help\n -v, --version Show version");
672
+ function je() {
673
+ console.log("zombie-mermaid — render Mermaid diagrams from the command line\n\nUsage:\n zombie-mermaid render <file> --ascii Render to ASCII in terminal\n zombie-mermaid render <file> --svg Render to <file stem>.svg\n zombie-mermaid render <file> --svg -o <out.svg> Render to a named SVG file\n zombie-mermaid render <file> -o out.svg Format inferred from the extension\n zombie-mermaid render <file> --svg -o - Write SVG to stdout\n zombie-mermaid render <file> --ascii --svg -o <out.svg> Both (ASCII to terminal)\n zombie-mermaid render <file> --html Self-contained pan/zoom HTML viewer\n zombie-mermaid render <file> --png Rasterize to <file stem>.png\n cat file.mmd | zombie-mermaid render --ascii Read from stdin\n zombie-mermaid validate [file] Parse only; exit 1 with line diagnostics if invalid\n zombie-mermaid --check <file.md>... Validate every mermaid fenced block in Markdown; exit 1 with file:line diagnostics\n zombie-mermaid themes List available themes\n zombie-mermaid web [--port <n>] Start a local web UI (default port: 3000)\n zombie-mermaid mcp Start an MCP server on stdio (render tools)\n zombie-mermaid --help Show this help\n zombie-mermaid --version Show version\n\nOptions:\n --ascii Print ASCII/Unicode diagram to terminal, or to -o <file.txt>\n when it is the only format\n --svg Render SVG to -o <path> (default: <input stem>.svg;\n stdin input must give -o)\n --resolve-colors Replace CSS var()/color-mix() in the SVG with computed\n sRGB values, for rasterizers and other non-browser SVG\n consumers (resvg, librsvg, Inkscape) that don't\n evaluate them. The default output stays a live function\n of its CSS variables. Requires --svg.\n --html Render a self-contained HTML pan/zoom viewer to -o\n <path> (default: <input stem>.html). Embeds the SVG;\n no server, no network, opens from disk. Cannot be\n combined with --svg/--png (different content, same\n slot).\n --png Rasterize to PNG at -o <path> (default: <input\n stem>.png), via the optional @resvg/resvg-js native\n dependency — install it with `pnpm add\n @resvg/resvg-js` if it's missing. Always resolves\n CSS var()/color-mix() first (as --resolve-colors\n does for --svg), since a rasterizer can't evaluate\n them. 1:1 with the SVG's own pixel size, no scaling.\n Cannot be combined with --svg/--html.\n -o, --output <path> Output file for the SVG/HTML/PNG (or for ASCII when\n none of those is given). '-' writes to stdout. A\n .svg, .html, .txt, or .png extension selects the\n format when no --svg/--html/--png/--ascii flag is\n given; one that contradicts the flags is an error.\n Existing files are never overwritten without --force.\n -f, --force Overwrite an existing output file\n --theme <name> Apply a built-in theme (see 'themes' command)\n --direction <dir> Override the diagram's layout direction: TD, TB, BT, LR, or RL\n (flowchart, state, and ER diagrams; nested subgraph\n directions still apply on top of it)\n -x, --paddingX <n> Horizontal spacing between nodes (ASCII, default: 5)\n -y, --paddingY <n> Vertical spacing between nodes (ASCII, default: 5; LR/RL sibling gap is n-1, min 1)\n -p, --borderPadding <n> Padding inside node boxes (ASCII, default: 1)\n --coords Overlay row/column index rulers (ASCII, debug layout)\n --hyperlinks Emit OSC 8 terminal hyperlinks for `click` hrefs\n (ASCII). Off by default: terminal support varies —\n iTerm2, WezTerm, kitty, Windows Terminal, and\n VTE-based terminals render them as clickable links;\n others may show the URL or nothing. Pagers need\n OSC 8 support too (`less -R`, recent versions).\n --profile <name> ASCII preset: 'markdown' = plain ASCII characters, no\n color, for pasting into issues/commits/fenced blocks\n (explicit flags like --coords still win). Requires\n --ascii.\n -w, --max-width <n|auto> Fit ASCII output within this many columns\n ('auto' detects the terminal width): applies compact\n spacing automatically when needed, and warns on\n stderr if it still doesn't fit (no label wrapping or\n direction-flip — see issue #335).\n --layout mermaid Opt in (flowcharts, --ascii): draw mermaid's own ELK layout\n instead of the grid renderer. Subgraphs, some shapes,\n other diagram types, layout failures and drawings that\n cannot fit 120 columns (or -w <n>, used as the cap)\n fall back to the default renderer; stderr says which\n one ran. --layout mermaid-wide drops the cap and always\n draws the (possibly wide) layout-faithful text. Merged\n parallel edges hide edge multiplicity. Depends on\n mermaid 12.1.0 + elkjs 0.9.3: install the optional peer\n with `pnpm add elkjs-0.9.3@npm:elkjs@0.9.3`. -x/-y/-p\n and --coords do not apply to the layout-faithful drawing.\n --port <n> Port for the 'web' command (default: 3000)\n -h, --help Show help\n -v, --version Show version");
619
674
  }
620
675
  $();
621
676
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zombie-mermaid",
3
- "version": "5.11.2",
3
+ "version": "5.12.0",
4
4
  "license": "MIT",
5
5
  "mcpName": "io.github.dfadler/zombie-mermaid",
6
6
  "description": "Render Mermaid diagrams as beautiful SVGs or ASCII art. Ultra-fast, fully themeable, zero DOM dependencies. Maintained fork of beautiful-mermaid.",
@@ -115,11 +115,11 @@
115
115
  "@modelcontextprotocol/sdk": "^1.32.1",
116
116
  "elkjs": "^0.12.0",
117
117
  "entities": "^7.0.1",
118
- "@zombie-mermaid/ascii-renderer": "5.11.2",
119
- "@zombie-mermaid/core": "5.11.2",
120
- "@zombie-mermaid/mermaid-parser": "5.11.2",
121
- "@zombie-mermaid/svg-renderer": "5.11.2",
122
- "@zombie-mermaid/mcp": "5.11.2"
118
+ "@zombie-mermaid/ascii-renderer": "5.12.0",
119
+ "@zombie-mermaid/core": "5.12.0",
120
+ "@zombie-mermaid/mcp": "5.12.0",
121
+ "@zombie-mermaid/mermaid-parser": "5.12.0",
122
+ "@zombie-mermaid/svg-renderer": "5.12.0"
123
123
  },
124
124
  "devDependencies": {
125
125
  "@arethetypeswrong/cli": "0.18.5",
@@ -213,6 +213,7 @@
213
213
  "generate:logo-lockup": "tsx scripts/generate-logo-lockup.ts",
214
214
  "api:report": "tsx scripts/api-report.ts",
215
215
  "mutation": "stryker run",
216
+ "docs:llms": "tsx scripts/generate-llms-full.ts",
216
217
  "docs:scripts": "tsx scripts/generate-development-scripts-doc.ts && oxfmt docs/development-scripts.md",
217
218
  "badge:bundle-size": "tsx scripts/generate-bundle-badge.ts",
218
219
  "visual-diff": "tsx scripts/visual-diff.ts",