zombie-mermaid 5.14.4 → 5.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -86,7 +86,7 @@ the SVG renderer at all (see [ASCII Output](#ascii-output)).
86
86
 
87
87
  ## Features
88
88
 
89
- - **9 diagram types** — Flowcharts (including State), Sequence, Class, ER, XY Charts (bar, line, combined), Architecture, C4, Pie, and Quadrant charts
89
+ - **12 diagram types** — Flowcharts (including State), Sequence, Class, ER, XY Charts (bar, line, combined), Architecture, C4, Pie, Quadrant charts, Gantt charts, Mindmaps, and Timelines
90
90
  - **Dual output** — SVG for rich UIs, ASCII/Unicode for terminals
91
91
  - **Synchronous rendering** — No async, no flash. Works with React `useMemo()`
92
92
  - **15 built-in themes** — And dead simple to add your own
@@ -152,7 +152,7 @@ registerElk(ELK)
152
152
  ```
153
153
 
154
154
  Architecture diagrams (`architecture-beta`, see [Supported Diagrams](docs/diagrams.md#architecture-diagrams)) use their own grid layout (as Mermaid does), so they need no `elkjs`.
155
- Sequence, pie, quadrant, xychart and C4 diagrams, and all ASCII output, need no `elkjs`
155
+ Sequence, pie, quadrant, gantt, xychart and C4 diagrams, and all ASCII output, need no `elkjs`
156
156
  and no registration. Rendering a graph diagram with no `elkjs` available throws an
157
157
  `ElkNotRegisteredError` that says how to fix it.
158
158
 
package/dist/cli.js CHANGED
@@ -3,26 +3,26 @@ import { createRequire as e } from "node:module";
3
3
  import { extname as t, format as n, parse as r } from "node:path";
4
4
  import { MermaidParseError as i, THEMES as a, escapeXml as o, isDirection as s, isWideChar as c } from "@zombie-mermaid/core";
5
5
  import { access as l, readFile as u, writeFile as d } from "node:fs/promises";
6
- import { diagramColorsToAsciiTheme as f, renderMermaidASCII as p } from "@zombie-mermaid/ascii-renderer";
6
+ import { diagramColorsToAsciiTheme as f, registerAsciiLayout as p, renderMermaidASCII as m } from "@zombie-mermaid/ascii-renderer";
7
+ import { ElkNotRegisteredError as h, layoutErDiagramSync as g, layoutFlowchartSync as _, registerElk as v, renderMermaidSVG as y, validateMermaid as b } from "@zombie-mermaid/svg-renderer";
7
8
  import "@zombie-mermaid/mermaid-parser";
8
- import { registerElk as m, renderMermaidSVG as h, validateMermaid as g } from "@zombie-mermaid/svg-renderer";
9
- import { renderMermaidASCIIWithLayout as _ } from "./ascii-layout.js";
10
- import { createServer as v } from "node:http";
11
- import { StdioServerTransport as y } from "@modelcontextprotocol/sdk/server/stdio.js";
12
- import { createMcpServer as b } from "@zombie-mermaid/mcp";
13
- import x from "elkjs/lib/elk.bundled.js";
9
+ import { renderMermaidASCIIWithLayout as x } from "./ascii-layout.js";
10
+ import { createServer as S } from "node:http";
11
+ import { StdioServerTransport as C } from "@modelcontextprotocol/sdk/server/stdio.js";
12
+ import { createMcpServer as w } from "@zombie-mermaid/mcp";
13
+ import T from "elkjs/lib/elk.bundled.js";
14
14
  //#region src/cli/parse-args.ts
15
- var S = 3e3, C = {
15
+ var E = 3e3, D = {
16
16
  ".svg": "svg",
17
17
  ".txt": "ascii",
18
18
  ".html": "html",
19
19
  ".htm": "html",
20
20
  ".png": "png"
21
21
  };
22
- function w(e) {
23
- if (e !== void 0 && e !== "-") return C[t(e).toLowerCase()];
22
+ function ee(e) {
23
+ if (e !== void 0 && e !== "-") return D[t(e).toLowerCase()];
24
24
  }
25
- var T = {
25
+ var O = {
26
26
  svg: {
27
27
  flag: "--svg",
28
28
  ext: ".svg",
@@ -44,8 +44,8 @@ var T = {
44
44
  display: "PNG",
45
45
  label: "the PNG rasterization"
46
46
  }
47
- }, E = Object.keys(T);
48
- function ee(e, t) {
47
+ }, k = Object.keys(O);
48
+ function A(e, t) {
49
49
  let i = r(e);
50
50
  return n({
51
51
  dir: i.dir,
@@ -53,7 +53,7 @@ function ee(e, t) {
53
53
  ext: t
54
54
  });
55
55
  }
56
- function D(e) {
56
+ function te(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" };
@@ -67,7 +67,7 @@ function D(e) {
67
67
  };
68
68
  }
69
69
  if (t === "themes") return { command: "themes" };
70
- if (t === "render") return j(n);
70
+ if (t === "render") return ie(n);
71
71
  if (t === "validate") {
72
72
  let e = n.find((e, t) => t > 0 || e.startsWith("-"));
73
73
  if (e !== void 0) throw Error(`Unexpected argument: ${e}`);
@@ -76,17 +76,17 @@ function D(e) {
76
76
  input: n[0]
77
77
  };
78
78
  }
79
- if (t === "web") return M(n);
79
+ if (t === "web") return ae(n);
80
80
  if (t === "mcp") return { command: "mcp" };
81
81
  throw Error(`Unknown command: ${t}`);
82
82
  }
83
- function O(e, t, n) {
83
+ function j(e, t, n) {
84
84
  let r = e[t + 1];
85
85
  if (t + 1 >= e.length || r === void 0) throw Error(`${n} requires a numeric value`);
86
86
  if (!/^\d+$/.test(r)) throw Error(`${n} requires a non-negative integer, got: "${r}"`);
87
87
  return Number(r);
88
88
  }
89
- function k(e, t, n) {
89
+ function ne(e, t, n) {
90
90
  let r = e[t + 1];
91
91
  if (t + 1 >= e.length || r === void 0) throw Error(`${n} requires a value ("auto" or a positive integer)`);
92
92
  if (r === "auto") return "auto";
@@ -94,14 +94,14 @@ function k(e, t, n) {
94
94
  if (!/^\d+$/.test(r) || i === 0 || !Number.isSafeInteger(i)) throw Error(`${n} requires "auto" or a positive integer, got: "${r}"`);
95
95
  return i;
96
96
  }
97
- function A(e, t, n) {
97
+ function re(e, t, n) {
98
98
  let r = e[t + 1];
99
99
  if (t + 1 >= e.length || r === void 0) throw Error(`${n} requires a value (one of TD, TB, BT, LR, RL)`);
100
100
  let i = r.toUpperCase();
101
101
  if (!s(i)) throw Error(`${n} requires one of TD, TB, BT, LR, RL, got: "${r}"`);
102
102
  return i;
103
103
  }
104
- function j(e) {
104
+ function ie(e) {
105
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;
106
106
  for (; y < e.length;) {
107
107
  let b = e[y];
@@ -118,12 +118,12 @@ function j(e) {
118
118
  else if (b === "--theme") {
119
119
  if (y + 1 >= e.length) throw Error("--theme requires a theme name");
120
120
  l = e[y + 1], 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;
121
+ } else if (b === "-x" || b === "--paddingX") u = j(e, y, b), y += 2;
122
+ else if (b === "-y" || b === "--paddingY") d = j(e, y, b), y += 2;
123
+ else if (b === "-p" || b === "--borderPadding") f = j(e, y, b), y += 2;
124
124
  else if (b === "--coords") p = !0, y++;
125
125
  else if (b === "--hyperlinks") m = !0, y++;
126
- else if (b === "-w" || b === "--max-width") h = k(e, y, b), y += 2;
126
+ else if (b === "-w" || b === "--max-width") h = ne(e, y, b), y += 2;
127
127
  else if (b === "--profile") {
128
128
  let t = e[y + 1];
129
129
  if (t !== "markdown") throw Error(`--profile requires one of: markdown, got: "${t ?? ""}"`);
@@ -132,7 +132,7 @@ function j(e) {
132
132
  let t = b.indexOf("="), n = t === -1 ? e[y + 1] : b.slice(t + 1);
133
133
  if (n !== "mermaid" && n !== "mermaid-wide") throw Error(`--layout requires one of: mermaid, mermaid-wide, got: "${n ?? ""}"`);
134
134
  _ = n, y += t === -1 ? 2 : 1;
135
- } else if (b === "--direction") v = A(e, y, b), y += 2;
135
+ } else if (b === "--direction") v = re(e, y, b), y += 2;
136
136
  else if (!b.startsWith("-") || b === "-") {
137
137
  if (b === "-") throw Error("Unexpected argument: - (use -o - to write output to stdout; omit the input file to read stdin)");
138
138
  if (t !== void 0) throw Error(`Unexpected argument: ${b} (input file already set to "${t}")`);
@@ -143,47 +143,47 @@ function j(e) {
143
143
  svg: r,
144
144
  html: a,
145
145
  png: o
146
- }, x = E.filter((e) => b[e]);
146
+ }, x = k.filter((e) => b[e]);
147
147
  if (x.length > 1) {
148
148
  let [e, t] = x;
149
- throw Error(`${T[e].flag} and ${T[t].flag} cannot both be set — they would write different content to the same output path. Run the command twice with different -o paths for each.`);
149
+ throw Error(`${O[e].flag} and ${O[t].flag} cannot both be set — they would write different content to the same output path. Run the command twice with different -o paths for each.`);
150
150
  }
151
- let S = w(s);
151
+ let S = ee(s);
152
152
  if (S !== void 0 && S !== "ascii") {
153
- let e = E.find((e) => e !== S && b[e]);
153
+ let e = k.find((e) => e !== S && b[e]);
154
154
  if (e !== void 0) {
155
- let t = T[S], n = T[e];
155
+ let t = O[S], n = O[e];
156
156
  throw Error(`-o ${s} has ${t.article} ${t.ext} extension, but ${n.flag} output would be written to it. Use a ${n.ext} path (or -o - for stdout) for ${n.label}.`);
157
157
  }
158
158
  S === "svg" ? r = !0 : S === "html" ? a = !0 : o = !0;
159
159
  } else if (S === "ascii") {
160
- let e = E.find((e) => b[e]);
160
+ let e = k.find((e) => b[e]);
161
161
  if (e !== void 0) {
162
- let t = T[e];
162
+ let t = O[e];
163
163
  throw Error(`-o ${s} has a .txt extension, but ${t.flag} output would be written to it. Use a ${t.ext} path (or -o - for stdout) instead.`);
164
164
  }
165
165
  n = !0;
166
166
  }
167
167
  if (!n && !r && !a && !o) {
168
168
  if (s !== void 0 && s !== "-") {
169
- let e = Object.keys(C).join(", ");
169
+ let e = Object.keys(D).join(", ");
170
170
  throw Error(`Cannot infer an output format from "${s}" — pass --ascii, --svg, --html, or --png, or use a recognised extension (${e})`);
171
171
  }
172
172
  throw Error("Specify --ascii, --svg, --html, and/or --png (or -o <path> with a .svg/.txt/.html/.png extension)");
173
173
  }
174
- let D = {
174
+ let C = {
175
175
  svg: r,
176
176
  html: a,
177
177
  png: o
178
- }, j = E.find((e) => D[e]);
179
- if (s === "-" && n && j !== void 0) {
180
- let e = T[j].display;
178
+ }, w = k.find((e) => C[e]);
179
+ if (s === "-" && n && w !== void 0) {
180
+ let e = O[w].display;
181
181
  throw Error(`-o - would send both ASCII and ${e} to stdout; drop --ascii, or write the ${e} to a file path`);
182
182
  }
183
- if (j !== void 0 && s === void 0) {
184
- let { flag: e, ext: n } = T[j];
183
+ if (w !== void 0 && s === void 0) {
184
+ let { flag: e, ext: n } = O[w];
185
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`);
186
- s = ee(t, n);
186
+ s = A(t, n);
187
187
  }
188
188
  if (h !== void 0 && !n) throw Error("-w/--max-width requires --ascii");
189
189
  if (g !== void 0 && !n) throw Error("--profile requires --ascii");
@@ -212,12 +212,12 @@ function j(e) {
212
212
  direction: v
213
213
  };
214
214
  }
215
- function M(e) {
216
- let t = S, n = 0;
215
+ function ae(e) {
216
+ let t = E, n = 0;
217
217
  for (; n < e.length;) {
218
218
  let r = e[n];
219
219
  if (r === void 0) throw Error(`parseArgs: index ${n} out of range while parsing arguments`);
220
- if (r === "--port") t = O(e, n, r), n += 2;
220
+ if (r === "--port") t = j(e, n, r), n += 2;
221
221
  else throw Error(`Unknown flag: ${r}`);
222
222
  }
223
223
  return {
@@ -227,40 +227,155 @@ function M(e) {
227
227
  }
228
228
  //#endregion
229
229
  //#region packages/ascii-renderer/src/display-width.ts
230
- var te = /\p{Mn}|\p{Me}/u;
231
- function ne(e) {
232
- return te.test(e);
230
+ var oe = /\p{Mn}|\p{Me}/u;
231
+ function se(e) {
232
+ return oe.test(e);
233
233
  }
234
- var re = new Intl.Segmenter(void 0, { granularity: "grapheme" }), ie = "️";
235
- function ae(e) {
234
+ var ce = new Intl.Segmenter(void 0, { granularity: "grapheme" }), le = "️";
235
+ function ue(e) {
236
236
  if (/^[\x20-\x7e]*$/.test(e)) return e.split("");
237
237
  let t = [];
238
- for (let { segment: n } of re.segment(e)) t.push(n);
238
+ for (let { segment: n } of ce.segment(e)) t.push(n);
239
239
  return t;
240
240
  }
241
- function oe(e) {
242
- if (e.includes(ie)) return 2;
241
+ function de(e) {
242
+ if (e.includes(le)) return 2;
243
243
  let t = !1, n = !1;
244
- for (let r of e) c(r) && (t = !0), ne(r) || (n = !0);
244
+ for (let r of e) c(r) && (t = !0), se(r) || (n = !0);
245
245
  return t ? 2 : +!!n;
246
246
  }
247
- function se(e) {
247
+ function fe(e) {
248
248
  let t = 0;
249
- for (let n of ae(e)) t += oe(n);
249
+ for (let n of ue(e)) t += de(n);
250
250
  return t;
251
251
  }
252
252
  //#endregion
253
253
  //#region packages/ascii-renderer/src/hyperlinks.ts
254
- var ce = /\x1b\]8;[^\x07\x1b]*(?:\x1b\\|\x07)/g;
255
- function le(e) {
256
- return e.replace(ce, "");
254
+ var pe = /\x1b\]8;[^\x07\x1b]*(?:\x1b\\|\x07)/g;
255
+ function me(e) {
256
+ return e.replace(pe, "");
257
+ }
258
+ var M = .5, N = (e, t, n) => {
259
+ if (e.diamond) {
260
+ let [r, i, a] = n ? [
261
+ t.x - e.cx,
262
+ e.w / 2,
263
+ t.y - e.cy
264
+ ] : [
265
+ t.y - e.cy,
266
+ e.h / 2,
267
+ t.x - e.cx
268
+ ];
269
+ return Math.abs(r) / i >= .49 ? n ? r < 0 ? "L" : "R" : r < 0 ? "T" : "B" : n ? a < 0 ? "T" : "B" : a < 0 ? "L" : "R";
270
+ }
271
+ let r = {
272
+ T: Math.abs(t.y - (e.cy - e.h / 2)),
273
+ B: Math.abs(t.y - (e.cy + e.h / 2)),
274
+ L: Math.abs(t.x - (e.cx - e.w / 2)),
275
+ R: Math.abs(t.x - (e.cx + e.w / 2))
276
+ };
277
+ return Object.keys(r).reduce((e, t) => r[t] < r[e] ? t : e);
278
+ };
279
+ function P(e, t, n) {
280
+ let r = n === "LR" || n === "RL", i = (e) => r ? e.cx : e.cy, a = (e) => r ? e.cy : e.cx, o = (e) => (r ? e.w : e.h) / 2, s = [...e].sort((e, t) => i(e) - i(t)), c = /* @__PURE__ */ new Map(), l = -1, u, d = (e, t) => Math.abs(e - t) <= 20;
281
+ for (let e of s) (!u || !(d(i(e), i(u)) || d(i(e) - o(e), i(u) - o(u)) || d(i(e) + o(e), i(u) + o(u)))) && (l++, u = e), c.set(e.id, l);
282
+ let f = l, p = n === "RL" || n === "BT", m = /* @__PURE__ */ new Map(), h = /* @__PURE__ */ new Map();
283
+ for (let t of [...e].sort((e, t) => a(e) - a(t))) {
284
+ let e = c.get(t.id), n = h.get(e) ?? 0;
285
+ h.set(e, n + 1), m.set(t.id, {
286
+ layer: p ? f - e : e,
287
+ order: n,
288
+ cross: a(t),
289
+ size: r ? t.h : t.w
290
+ });
291
+ }
292
+ let g = new Map(e.map((e) => [e.id, e])), _ = /* @__PURE__ */ new Map(), v = t.map((e, t) => {
293
+ let n = g.get(e.from), i = g.get(e.to);
294
+ if (!n || !i || e.points.length < 2) return;
295
+ let a = e.points[0], o = e.points[e.points.length - 1], s = (e, n, i) => {
296
+ let a = N(e, n, r), o = {
297
+ edge: t,
298
+ start: i,
299
+ face: a,
300
+ at: a === "T" || a === "B" ? n.x : n.y
301
+ }, s = `${e.id}|${a}`;
302
+ return _.set(s, [..._.get(s) ?? [], o]), o;
303
+ };
304
+ return {
305
+ p: e,
306
+ s: s(n, a, !0),
307
+ e: s(i, o, !1)
308
+ };
309
+ }), y = (e, t) => {
310
+ let n = [..._.get(`${e}|${t.face}`)].map((e) => e.at);
311
+ return new Set(n.filter((e) => e < t.at - M)).size;
312
+ }, b = [];
313
+ return v.forEach((e, t) => {
314
+ e && b.push({
315
+ index: t,
316
+ from: e.p.from,
317
+ to: e.p.to,
318
+ start: {
319
+ face: e.s.face,
320
+ order: y(e.p.from, e.s)
321
+ },
322
+ end: {
323
+ face: e.e.face,
324
+ order: y(e.p.to, e.e)
325
+ }
326
+ });
327
+ }), {
328
+ nodes: m,
329
+ edges: b
330
+ };
331
+ }
332
+ var F = (e) => {
333
+ try {
334
+ return e();
335
+ } catch (e) {
336
+ if (e instanceof h) return;
337
+ throw e;
338
+ }
339
+ }, I = {
340
+ graph: (e) => F(() => {
341
+ let t = _(e);
342
+ return P(t.nodes.map((e) => ({
343
+ id: e.id,
344
+ cx: e.x + e.width / 2,
345
+ cy: e.y + e.height / 2,
346
+ w: e.width,
347
+ h: e.height,
348
+ diamond: e.shape === "diamond"
349
+ })), t.edges.map((e) => ({
350
+ from: e.source,
351
+ to: e.target,
352
+ points: e.points
353
+ })), e.direction);
354
+ }),
355
+ er: (e) => F(() => {
356
+ let t = g(e);
357
+ return P(t.entities.map((e) => ({
358
+ id: e.id,
359
+ cx: e.x + e.width / 2,
360
+ cy: e.y + e.height / 2,
361
+ w: e.width,
362
+ h: e.height
363
+ })), t.relationships.map((e) => ({
364
+ from: e.entity1,
365
+ to: e.entity2,
366
+ points: e.points
367
+ })), e.direction ?? "TD");
368
+ })
369
+ };
370
+ function L() {
371
+ p(I);
257
372
  }
258
373
  //#endregion
259
374
  //#region packages/mcp/src/tools/parse-diagnostics.ts
260
- var ue = /^Line (\d+): ([\s\S]*)$/;
261
- function N(e, t) {
375
+ var R = /^Line (\d+): ([\s\S]*)$/;
376
+ function z(e, t) {
262
377
  if (!(e instanceof i) || e.line === void 0) return;
263
- let { line: n, message: r } = e, a = ue.exec(r), o = t.split(/\r?\n/)[n - 1];
378
+ let { line: n, message: r } = e, a = R.exec(r), o = t.split(/\r?\n/)[n - 1];
264
379
  if (!(n < 1 || o === void 0)) return {
265
380
  line: n,
266
381
  sourceLine: o,
@@ -269,15 +384,15 @@ function N(e, t) {
269
384
  }
270
385
  //#endregion
271
386
  //#region src/cli/diagnostics.ts
272
- function P(e, t) {
387
+ function B(e, t) {
273
388
  if (!(e instanceof Error)) return e;
274
- let n = N(e, t);
389
+ let n = z(e, t);
275
390
  return n === void 0 ? e : /* @__PURE__ */ Error(`${e.message}\n ${n.line} | ${n.sourceLine}`);
276
391
  }
277
392
  //#endregion
278
393
  //#region src/cli/html-viewer-client-source.ts
279
- var F = "// @ts-check\n/// <reference lib=\"dom\" />\n// ============================================================================\n// Client script for the CLI's self-contained HTML viewer (`render --html`).\n//\n// Inlined verbatim into the viewer page by src/cli/html-viewer.ts (imported\n// with Vite's `?raw`), so this file must stay plain browser JavaScript: no\n// imports, no TypeScript syntax, no build step. It is checked by tsc via\n// `// @ts-check` + JSDoc and linted like any other source file.\n//\n// This script ships only inside the HTML *viewer file* the CLI writes. It is\n// not — and must never become — part of the library's SVG output, which\n// stays script-free by decision (docs/decisions/no-script-interactivity.md).\n// ============================================================================\n\n;(() => {\n const stage = document.getElementById('stage')\n const diagram = document.getElementById('diagram')\n const svg = diagram ? diagram.querySelector('svg') : null\n const zoomLabel = document.getElementById('zoom')\n const themeButton = document.getElementById('theme')\n if (!stage || !diagram || !svg || !zoomLabel || !themeButton) return\n\n const MIN_SCALE = 0.05\n const MAX_SCALE = 40\n const KEY_PAN_STEP = 40\n const KEY_PAN_STEP_FAST = 160\n\n let scale = 1\n let tx = 0\n let ty = 0\n\n // -- Geometry ---------------------------------------------------------\n\n // Assigned as `const ... = () => {}` rather than `function` declarations\n // from here through stageCenter (and release/setMode further below):\n // these all read stage/diagram/svg/zoomLabel/themeButton, which the\n // guard above narrowed from `T | null` to `T` — a narrowing that only\n // survives for a function *expression* defined after the guard, not a\n // hoisted `function` declaration (tsc can't rule out the declaration\n // being invoked, in theory, before the guard runs).\n\n /** Natural (unscaled) size of the diagram, from the root's width/height or viewBox. */\n const naturalSize = () => {\n const w = Number(svg.getAttribute('width'))\n const h = Number(svg.getAttribute('height'))\n if (w > 0 && h > 0) return { w, h }\n const vb = svg.viewBox.baseVal\n if (vb && vb.width > 0 && vb.height > 0)\n return { w: vb.width, h: vb.height }\n const r = diagram.getBoundingClientRect()\n return { w: r.width / scale || 1, h: r.height / scale || 1 }\n }\n\n /** @param {number} s */\n function clampScale(s) {\n return Math.min(MAX_SCALE, Math.max(MIN_SCALE, s))\n }\n\n const apply = () => {\n diagram.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`\n zoomLabel.textContent = `${Math.round(scale * 100)}%`\n }\n\n /**\n * Zoom by `factor` keeping the stage point (cx, cy) fixed under the cursor.\n * @param {number} factor\n * @param {number} cx\n * @param {number} cy\n */\n function zoomAt(factor, cx, cy) {\n const next = clampScale(scale * factor)\n const k = next / scale\n tx = cx - (cx - tx) * k\n ty = cy - (cy - ty) * k\n scale = next\n apply()\n }\n\n const fit = () => {\n const r = stage.getBoundingClientRect()\n const { w, h } = naturalSize()\n scale = clampScale(Math.min(r.width / w, r.height / h) * 0.95)\n tx = (r.width - w * scale) / 2\n ty = (r.height - h * scale) / 2\n apply()\n }\n\n const oneToOne = () => {\n const r = stage.getBoundingClientRect()\n const { w, h } = naturalSize()\n scale = 1\n tx = Math.max(0, (r.width - w) / 2)\n ty = Math.max(0, (r.height - h) / 2)\n apply()\n }\n\n const stageCenter = () => {\n const r = stage.getBoundingClientRect()\n return { x: r.width / 2, y: r.height / 2 }\n }\n\n // -- Wheel: plain scroll pans; ctrl/cmd + wheel (which is also what a\n // trackpad pinch produces) zooms around the cursor. -------------------\n\n stage.addEventListener(\n 'wheel',\n (e) => {\n e.preventDefault()\n const r = stage.getBoundingClientRect()\n if (e.ctrlKey || e.metaKey) {\n zoomAt(\n Math.exp(-e.deltaY * 0.01),\n e.clientX - r.left,\n e.clientY - r.top,\n )\n } else {\n tx -= e.deltaX\n ty -= e.deltaY\n apply()\n }\n },\n { passive: false },\n )\n\n // -- Pointers: one pointer drags, two pinch-zoom around their midpoint. --\n\n /** @type {Map<number, {x: number, y: number}>} */\n const pointers = new Map()\n /** @type {{dist: number, mid: {x: number, y: number}} | null} */\n let lastPinch = null\n\n stage.addEventListener('pointerdown', (e) => {\n if (e.pointerType === 'mouse' && e.button !== 0) return\n stage.setPointerCapture(e.pointerId)\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY })\n stage.classList.add('dragging')\n })\n\n stage.addEventListener('pointermove', (e) => {\n const prev = pointers.get(e.pointerId)\n if (!prev) return\n const current = { x: e.clientX, y: e.clientY }\n pointers.set(e.pointerId, current)\n if (pointers.size === 1) {\n tx += current.x - prev.x\n ty += current.y - prev.y\n apply()\n return\n }\n if (pointers.size === 2) {\n const [a, b] = [...pointers.values()]\n if (!a || !b) return\n const dist = Math.hypot(a.x - b.x, a.y - b.y)\n const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }\n if (lastPinch && lastPinch.dist > 0) {\n const r = stage.getBoundingClientRect()\n zoomAt(dist / lastPinch.dist, mid.x - r.left, mid.y - r.top)\n tx += mid.x - lastPinch.mid.x\n ty += mid.y - lastPinch.mid.y\n apply()\n }\n lastPinch = { dist, mid }\n }\n })\n\n /** @param {PointerEvent} e */\n const release = (e) => {\n pointers.delete(e.pointerId)\n if (pointers.size < 2) lastPinch = null\n if (pointers.size === 0) stage.classList.remove('dragging')\n }\n stage.addEventListener('pointerup', release)\n stage.addEventListener('pointercancel', release)\n\n stage.addEventListener('dblclick', (e) => {\n const r = stage.getBoundingClientRect()\n zoomAt(scale < 1 ? 1 / scale : 2, e.clientX - r.left, e.clientY - r.top)\n })\n\n // -- Theme: follows prefers-color-scheme until the user toggles, unless\n // the page was generated with an explicit --theme (data-theme preset). --\n\n const root = document.documentElement\n const darkMedia = matchMedia('(prefers-color-scheme: dark)')\n let userChose = root.dataset.themeSource === 'theme'\n\n function currentMode() {\n const preset = root.dataset.theme\n if (preset === 'dark' || preset === 'light') return preset\n return darkMedia.matches ? 'dark' : 'light'\n }\n\n /** @param {'light' | 'dark'} mode */\n const setMode = (mode) => {\n root.dataset.theme = mode\n themeButton.textContent = mode === 'dark' ? 'Light' : 'Dark'\n themeButton.setAttribute('aria-pressed', String(mode === 'dark'))\n themeButton.setAttribute(\n 'aria-label',\n `Switch to ${mode === 'dark' ? 'light' : 'dark'} mode (T)`,\n )\n }\n\n function toggleMode() {\n userChose = true\n setMode(currentMode() === 'dark' ? 'light' : 'dark')\n }\n\n darkMedia.addEventListener('change', () => {\n if (!userChose) {\n delete root.dataset.theme\n setMode(currentMode())\n delete root.dataset.theme\n }\n })\n // Reflect the initial mode on the button without pinning it: leave\n // data-theme unset for system mode so a later OS change still applies.\n themeButton.textContent = currentMode() === 'dark' ? 'Light' : 'Dark'\n themeButton.setAttribute('aria-pressed', String(currentMode() === 'dark'))\n\n // -- Buttons -------------------------------------------------------------\n\n /**\n * @param {string} id\n * @param {() => void} handler\n */\n function onClick(id, handler) {\n const el = document.getElementById(id)\n if (el) el.addEventListener('click', handler)\n }\n onClick('zoom-in', () => {\n const c = stageCenter()\n zoomAt(1.25, c.x, c.y)\n })\n onClick('zoom-out', () => {\n const c = stageCenter()\n zoomAt(0.8, c.x, c.y)\n })\n onClick('fit', fit)\n onClick('one', oneToOne)\n onClick('theme', toggleMode)\n\n // -- Keyboard (stage must be focused; it has tabindex=0) -----------------\n\n stage.addEventListener('keydown', (e) => {\n const c = stageCenter()\n const step = e.shiftKey ? KEY_PAN_STEP_FAST : KEY_PAN_STEP\n switch (e.key) {\n case '+':\n case '=':\n zoomAt(1.25, c.x, c.y)\n break\n case '-':\n case '_':\n zoomAt(0.8, c.x, c.y)\n break\n case '0':\n fit()\n break\n case '1':\n oneToOne()\n break\n case 'ArrowLeft':\n tx += step\n apply()\n break\n case 'ArrowRight':\n tx -= step\n apply()\n break\n case 'ArrowUp':\n ty += step\n apply()\n break\n case 'ArrowDown':\n ty -= step\n apply()\n break\n case 't':\n case 'T':\n toggleMode()\n break\n default:\n return\n }\n e.preventDefault()\n })\n\n fit()\n stage.focus({ preventScroll: true })\n})()\n", I = ":root {\n color-scheme: light dark;\n --bg: #f4f4f5;\n --fg: #18181b;\n --chrome-bg: #ffffff;\n --chrome-border: #e4e4e7;\n --chrome-fg: #3f3f46;\n --chrome-fg-hover: #18181b;\n --focus-ring: #2563eb;\n }\n :root[data-theme='dark'] {\n --bg: #09090b;\n --fg: #fafafa;\n --chrome-bg: #18181b;\n --chrome-border: #3f3f46;\n --chrome-fg: #d4d4d8;\n --chrome-fg-hover: #fafafa;\n --focus-ring: #60a5fa;\n }\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme='light']) {\n --bg: #09090b;\n --fg: #fafafa;\n --chrome-bg: #18181b;\n --chrome-border: #3f3f46;\n --chrome-fg: #d4d4d8;\n --chrome-fg-hover: #fafafa;\n --focus-ring: #60a5fa;\n }\n }\n * { box-sizing: border-box; }\n html, body {\n margin: 0;\n height: 100%;\n background: var(--bg);\n color: var(--fg);\n font: 14px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n }\n #stage {\n position: fixed;\n inset: 0;\n overflow: hidden;\n cursor: grab;\n touch-action: none;\n outline: none;\n }\n #stage.dragging { cursor: grabbing; }\n #diagram {\n position: absolute;\n top: 0;\n left: 0;\n transform-origin: 0 0;\n will-change: transform;\n }\n #diagram svg { display: block; }\n #toolbar {\n position: fixed;\n bottom: 16px;\n left: 50%;\n transform: translateX(-50%);\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 6px;\n border-radius: 10px;\n background: var(--chrome-bg);\n border: 1px solid var(--chrome-border);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);\n }\n #toolbar button {\n appearance: none;\n border: none;\n background: transparent;\n color: var(--chrome-fg);\n font: inherit;\n font-size: 13px;\n padding: 6px 10px;\n border-radius: 6px;\n cursor: pointer;\n min-width: 32px;\n }\n #toolbar button:hover { color: var(--chrome-fg-hover); background: var(--chrome-border); }\n #toolbar button:focus-visible {\n outline: 2px solid var(--focus-ring);\n outline-offset: 1px;\n }\n #zoom {\n min-width: 3.5em;\n text-align: center;\n color: var(--chrome-fg);\n font-variant-numeric: tabular-nums;\n }\n #stage:focus-visible {\n box-shadow: inset 0 0 0 2px var(--focus-ring);\n }\n @media print {\n #toolbar { display: none; }\n #stage { position: static; overflow: visible; }\n /* Genuinely needs !important (zombie-mermaid#1080 audit): the client\n * script's apply() sets #diagram's transform inline on every pan/zoom,\n * so whatever pan/zoom state the viewer was left in is a live inline\n * style this print rule must beat -- there's no static default to\n * hoist it to instead. */\n #diagram { position: static; transform: none !important; }\n }";
280
- function L(e) {
394
+ var he = "// @ts-check\n/// <reference lib=\"dom\" />\n// ============================================================================\n// Client script for the CLI's self-contained HTML viewer (`render --html`).\n//\n// Inlined verbatim into the viewer page by src/cli/html-viewer.ts (imported\n// with Vite's `?raw`), so this file must stay plain browser JavaScript: no\n// imports, no TypeScript syntax, no build step. It is checked by tsc via\n// `// @ts-check` + JSDoc and linted like any other source file.\n//\n// This script ships only inside the HTML *viewer file* the CLI writes. It is\n// not — and must never become — part of the library's SVG output, which\n// stays script-free by decision (docs/decisions/no-script-interactivity.md).\n// ============================================================================\n\n;(() => {\n const stage = document.getElementById('stage')\n const diagram = document.getElementById('diagram')\n const svg = diagram ? diagram.querySelector('svg') : null\n const zoomLabel = document.getElementById('zoom')\n const themeButton = document.getElementById('theme')\n if (!stage || !diagram || !svg || !zoomLabel || !themeButton) return\n\n const MIN_SCALE = 0.05\n const MAX_SCALE = 40\n const KEY_PAN_STEP = 40\n const KEY_PAN_STEP_FAST = 160\n\n let scale = 1\n let tx = 0\n let ty = 0\n\n // -- Geometry ---------------------------------------------------------\n\n // Assigned as `const ... = () => {}` rather than `function` declarations\n // from here through stageCenter (and release/setMode further below):\n // these all read stage/diagram/svg/zoomLabel/themeButton, which the\n // guard above narrowed from `T | null` to `T` — a narrowing that only\n // survives for a function *expression* defined after the guard, not a\n // hoisted `function` declaration (tsc can't rule out the declaration\n // being invoked, in theory, before the guard runs).\n\n /** Natural (unscaled) size of the diagram, from the root's width/height or viewBox. */\n const naturalSize = () => {\n const w = Number(svg.getAttribute('width'))\n const h = Number(svg.getAttribute('height'))\n if (w > 0 && h > 0) return { w, h }\n const vb = svg.viewBox.baseVal\n if (vb && vb.width > 0 && vb.height > 0)\n return { w: vb.width, h: vb.height }\n const r = diagram.getBoundingClientRect()\n return { w: r.width / scale || 1, h: r.height / scale || 1 }\n }\n\n /** @param {number} s */\n function clampScale(s) {\n return Math.min(MAX_SCALE, Math.max(MIN_SCALE, s))\n }\n\n const apply = () => {\n diagram.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`\n zoomLabel.textContent = `${Math.round(scale * 100)}%`\n }\n\n /**\n * Zoom by `factor` keeping the stage point (cx, cy) fixed under the cursor.\n * @param {number} factor\n * @param {number} cx\n * @param {number} cy\n */\n function zoomAt(factor, cx, cy) {\n const next = clampScale(scale * factor)\n const k = next / scale\n tx = cx - (cx - tx) * k\n ty = cy - (cy - ty) * k\n scale = next\n apply()\n }\n\n const fit = () => {\n const r = stage.getBoundingClientRect()\n const { w, h } = naturalSize()\n scale = clampScale(Math.min(r.width / w, r.height / h) * 0.95)\n tx = (r.width - w * scale) / 2\n ty = (r.height - h * scale) / 2\n apply()\n }\n\n const oneToOne = () => {\n const r = stage.getBoundingClientRect()\n const { w, h } = naturalSize()\n scale = 1\n tx = Math.max(0, (r.width - w) / 2)\n ty = Math.max(0, (r.height - h) / 2)\n apply()\n }\n\n const stageCenter = () => {\n const r = stage.getBoundingClientRect()\n return { x: r.width / 2, y: r.height / 2 }\n }\n\n // -- Wheel: plain scroll pans; ctrl/cmd + wheel (which is also what a\n // trackpad pinch produces) zooms around the cursor. -------------------\n\n stage.addEventListener(\n 'wheel',\n (e) => {\n e.preventDefault()\n const r = stage.getBoundingClientRect()\n if (e.ctrlKey || e.metaKey) {\n zoomAt(\n Math.exp(-e.deltaY * 0.01),\n e.clientX - r.left,\n e.clientY - r.top,\n )\n } else {\n tx -= e.deltaX\n ty -= e.deltaY\n apply()\n }\n },\n { passive: false },\n )\n\n // -- Pointers: one pointer drags, two pinch-zoom around their midpoint. --\n\n /** @type {Map<number, {x: number, y: number}>} */\n const pointers = new Map()\n /** @type {{dist: number, mid: {x: number, y: number}} | null} */\n let lastPinch = null\n\n stage.addEventListener('pointerdown', (e) => {\n if (e.pointerType === 'mouse' && e.button !== 0) return\n stage.setPointerCapture(e.pointerId)\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY })\n stage.classList.add('dragging')\n })\n\n stage.addEventListener('pointermove', (e) => {\n const prev = pointers.get(e.pointerId)\n if (!prev) return\n const current = { x: e.clientX, y: e.clientY }\n pointers.set(e.pointerId, current)\n if (pointers.size === 1) {\n tx += current.x - prev.x\n ty += current.y - prev.y\n apply()\n return\n }\n if (pointers.size === 2) {\n const [a, b] = [...pointers.values()]\n if (!a || !b) return\n const dist = Math.hypot(a.x - b.x, a.y - b.y)\n const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }\n if (lastPinch && lastPinch.dist > 0) {\n const r = stage.getBoundingClientRect()\n zoomAt(dist / lastPinch.dist, mid.x - r.left, mid.y - r.top)\n tx += mid.x - lastPinch.mid.x\n ty += mid.y - lastPinch.mid.y\n apply()\n }\n lastPinch = { dist, mid }\n }\n })\n\n /** @param {PointerEvent} e */\n const release = (e) => {\n pointers.delete(e.pointerId)\n if (pointers.size < 2) lastPinch = null\n if (pointers.size === 0) stage.classList.remove('dragging')\n }\n stage.addEventListener('pointerup', release)\n stage.addEventListener('pointercancel', release)\n\n stage.addEventListener('dblclick', (e) => {\n const r = stage.getBoundingClientRect()\n zoomAt(scale < 1 ? 1 / scale : 2, e.clientX - r.left, e.clientY - r.top)\n })\n\n // -- Theme: follows prefers-color-scheme until the user toggles, unless\n // the page was generated with an explicit --theme (data-theme preset). --\n\n const root = document.documentElement\n const darkMedia = matchMedia('(prefers-color-scheme: dark)')\n let userChose = root.dataset.themeSource === 'theme'\n\n function currentMode() {\n const preset = root.dataset.theme\n if (preset === 'dark' || preset === 'light') return preset\n return darkMedia.matches ? 'dark' : 'light'\n }\n\n /** @param {'light' | 'dark'} mode */\n const setMode = (mode) => {\n root.dataset.theme = mode\n themeButton.textContent = mode === 'dark' ? 'Light' : 'Dark'\n themeButton.setAttribute('aria-pressed', String(mode === 'dark'))\n themeButton.setAttribute(\n 'aria-label',\n `Switch to ${mode === 'dark' ? 'light' : 'dark'} mode (T)`,\n )\n }\n\n function toggleMode() {\n userChose = true\n setMode(currentMode() === 'dark' ? 'light' : 'dark')\n }\n\n darkMedia.addEventListener('change', () => {\n if (!userChose) {\n delete root.dataset.theme\n setMode(currentMode())\n delete root.dataset.theme\n }\n })\n // Reflect the initial mode on the button without pinning it: leave\n // data-theme unset for system mode so a later OS change still applies.\n themeButton.textContent = currentMode() === 'dark' ? 'Light' : 'Dark'\n themeButton.setAttribute('aria-pressed', String(currentMode() === 'dark'))\n\n // -- Buttons -------------------------------------------------------------\n\n /**\n * @param {string} id\n * @param {() => void} handler\n */\n function onClick(id, handler) {\n const el = document.getElementById(id)\n if (el) el.addEventListener('click', handler)\n }\n onClick('zoom-in', () => {\n const c = stageCenter()\n zoomAt(1.25, c.x, c.y)\n })\n onClick('zoom-out', () => {\n const c = stageCenter()\n zoomAt(0.8, c.x, c.y)\n })\n onClick('fit', fit)\n onClick('one', oneToOne)\n onClick('theme', toggleMode)\n\n // -- Keyboard (stage must be focused; it has tabindex=0) -----------------\n\n stage.addEventListener('keydown', (e) => {\n const c = stageCenter()\n const step = e.shiftKey ? KEY_PAN_STEP_FAST : KEY_PAN_STEP\n switch (e.key) {\n case '+':\n case '=':\n zoomAt(1.25, c.x, c.y)\n break\n case '-':\n case '_':\n zoomAt(0.8, c.x, c.y)\n break\n case '0':\n fit()\n break\n case '1':\n oneToOne()\n break\n case 'ArrowLeft':\n tx += step\n apply()\n break\n case 'ArrowRight':\n tx -= step\n apply()\n break\n case 'ArrowUp':\n ty += step\n apply()\n break\n case 'ArrowDown':\n ty -= step\n apply()\n break\n case 't':\n case 'T':\n toggleMode()\n break\n default:\n return\n }\n e.preventDefault()\n })\n\n fit()\n stage.focus({ preventScroll: true })\n})()\n", ge = ":root {\n color-scheme: light dark;\n --bg: #f4f4f5;\n --fg: #18181b;\n --chrome-bg: #ffffff;\n --chrome-border: #e4e4e7;\n --chrome-fg: #3f3f46;\n --chrome-fg-hover: #18181b;\n --focus-ring: #2563eb;\n }\n :root[data-theme='dark'] {\n --bg: #09090b;\n --fg: #fafafa;\n --chrome-bg: #18181b;\n --chrome-border: #3f3f46;\n --chrome-fg: #d4d4d8;\n --chrome-fg-hover: #fafafa;\n --focus-ring: #60a5fa;\n }\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme='light']) {\n --bg: #09090b;\n --fg: #fafafa;\n --chrome-bg: #18181b;\n --chrome-border: #3f3f46;\n --chrome-fg: #d4d4d8;\n --chrome-fg-hover: #fafafa;\n --focus-ring: #60a5fa;\n }\n }\n * { box-sizing: border-box; }\n html, body {\n margin: 0;\n height: 100%;\n background: var(--bg);\n color: var(--fg);\n font: 14px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n }\n #stage {\n position: fixed;\n inset: 0;\n overflow: hidden;\n cursor: grab;\n touch-action: none;\n outline: none;\n }\n #stage.dragging { cursor: grabbing; }\n #diagram {\n position: absolute;\n top: 0;\n left: 0;\n transform-origin: 0 0;\n will-change: transform;\n }\n #diagram svg { display: block; }\n #toolbar {\n position: fixed;\n bottom: 16px;\n left: 50%;\n transform: translateX(-50%);\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 6px;\n border-radius: 10px;\n background: var(--chrome-bg);\n border: 1px solid var(--chrome-border);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);\n }\n #toolbar button {\n appearance: none;\n border: none;\n background: transparent;\n color: var(--chrome-fg);\n font: inherit;\n font-size: 13px;\n padding: 6px 10px;\n border-radius: 6px;\n cursor: pointer;\n min-width: 32px;\n }\n #toolbar button:hover { color: var(--chrome-fg-hover); background: var(--chrome-border); }\n #toolbar button:focus-visible {\n outline: 2px solid var(--focus-ring);\n outline-offset: 1px;\n }\n #zoom {\n min-width: 3.5em;\n text-align: center;\n color: var(--chrome-fg);\n font-variant-numeric: tabular-nums;\n }\n #stage:focus-visible {\n box-shadow: inset 0 0 0 2px var(--focus-ring);\n }\n @media print {\n #toolbar { display: none; }\n #stage { position: static; overflow: visible; }\n /* Genuinely needs !important (zombie-mermaid#1080 audit): the client\n * script's apply() sets #diagram's transform inline on every pan/zoom,\n * so whatever pan/zoom state the viewer was left in is a live inline\n * style this print rule must beat -- there's no static default to\n * hoist it to instead. */\n #diagram { position: static; transform: none !important; }\n }";
395
+ function _e(e) {
281
396
  let t = e.title?.trim() || "Mermaid diagram";
282
397
  return `<!doctype html>
283
398
  <html lang="en">
@@ -286,7 +401,7 @@ function L(e) {
286
401
  <meta name="viewport" content="width=device-width, initial-scale=1" />
287
402
  <title>${o(t)}</title>
288
403
  <style>
289
- ${I}
404
+ ${ge}
290
405
  </style>
291
406
  </head>
292
407
  <body>
@@ -304,7 +419,7 @@ ${e.svg}
304
419
  <button id="theme" type="button" aria-pressed="false" aria-label="Switch to dark mode (T)">Dark</button>
305
420
  </div>
306
421
  <script>
307
- ${F}
422
+ ${he}
308
423
  <\/script>
309
424
  </body>
310
425
  </html>
@@ -312,7 +427,7 @@ ${F}
312
427
  }
313
428
  //#endregion
314
429
  //#region src/cli/png.ts
315
- async function R(e) {
430
+ async function ve(e) {
316
431
  let t;
317
432
  try {
318
433
  t = await import("@resvg/resvg-js");
@@ -324,14 +439,14 @@ async function R(e) {
324
439
  }
325
440
  //#endregion
326
441
  //#region src/cli/render.ts
327
- var z = 80, B = 1, V = 1, H = 0, U = /\x1b\[[0-9;]*m/g;
442
+ var ye = 80, V = 1, H = 1, U = 0, be = /\x1b\[[0-9;]*m/g;
328
443
  function W(e) {
329
- return e.split("\n").reduce((e, t) => Math.max(e, se(le(t.replace(U, "")))), 0);
444
+ return e.split("\n").reduce((e, t) => Math.max(e, fe(me(t.replace(be, "")))), 0);
330
445
  }
331
446
  function G(e) {
332
- return e === "auto" ? process.stdout.columns ?? z : e;
447
+ return e === "auto" ? process.stdout.columns ?? ye : e;
333
448
  }
334
- async function de(e, t, n, r) {
449
+ async function xe(e, t, n, r) {
335
450
  let i = t ?? process.stdout, a = r ?? process.stderr, o;
336
451
  if (e.input !== void 0) o = await u(e.input, "utf-8");
337
452
  else if (n !== void 0) o = n;
@@ -341,24 +456,24 @@ async function de(e, t, n, r) {
341
456
  }
342
457
  if (o.trim().length === 0) throw Error("Empty input — provide a Mermaid diagram via file or stdin");
343
458
  try {
344
- await fe(e, o, i, a);
459
+ await Se(e, o, i, a);
345
460
  } catch (e) {
346
- throw P(e, o);
461
+ throw B(e, o);
347
462
  }
348
463
  }
349
- async function fe(e, t, n, i) {
464
+ async function Se(e, t, n, i) {
350
465
  let o;
351
466
  if (e.theme !== void 0 && (o = a[e.theme], o === void 0)) {
352
467
  let t = Object.keys(a).join(", ");
353
468
  throw Error(`Unknown theme: "${e.theme}". Available themes: ${t}`);
354
469
  }
355
- let s = e.svg && e.output === "-", c = e.html && e.output === "-", l = e.png && e.output === "-", u = e.ascii && !e.svg && !e.html && !e.png && e.output !== void 0 && e.output !== "-" ? e.output : void 0, d = e.svg && !s ? e.output : void 0, m = e.html && !c ? e.output : void 0, g = e.png && !l ? e.output : void 0;
470
+ let s = e.svg && e.output === "-", c = e.html && e.output === "-", l = e.png && e.output === "-", u = e.ascii && !e.svg && !e.html && !e.png && e.output !== void 0 && e.output !== "-" ? e.output : void 0, d = e.svg && !s ? e.output : void 0, p = e.html && !c ? e.output : void 0, h = e.png && !l ? e.output : void 0;
356
471
  for (let t of [
357
472
  u,
358
473
  d,
359
- m,
360
- g
361
- ]) t !== void 0 && await pe(t, e.force);
474
+ p,
475
+ h
476
+ ]) t !== void 0 && await Ce(t, e.force);
362
477
  if (e.ascii) {
363
478
  let r = o && u === void 0 ? {
364
479
  colorMode: "auto",
@@ -367,19 +482,19 @@ async function fe(e, t, n, i) {
367
482
  e.paddingX !== void 0 && (r.paddingX = e.paddingX), e.paddingY !== void 0 && (r.paddingY = e.paddingY), e.borderPadding !== void 0 && (r.boxBorderPadding = e.borderPadding), e.coords && (r.showCoords = !0), e.profile !== void 0 && (r.profile = e.profile), e.direction !== void 0 && (r.direction = e.direction), e.hyperlinks && u === void 0 && (r.hyperlinks = !0);
368
483
  let a, s = !1;
369
484
  if (e.layout !== void 0) {
370
- let n = e.layout === "mermaid-wide" ? Infinity : e.maxWidth === void 0 ? void 0 : G(e.maxWidth), o = await _(t, {
485
+ let n = e.layout === "mermaid-wide" ? Infinity : e.maxWidth === void 0 ? void 0 : G(e.maxWidth), o = await x(t, {
371
486
  ...r,
372
487
  layout: "mermaid",
373
488
  maxCols: n
374
489
  });
375
490
  a = o.text, s = o.path === "layout-faithful", i.write(s ? `layout-faithful: used mermaid's ELK layout (mermaid 12.1.0 + elkjs 0.9.3). ${o.note ?? ""}`.trimEnd() + "\n" : `layout-faithful: ${o.fallbackReason ?? "unavailable"}; used standard renderer\n`);
376
- } else a = p(t, r);
491
+ } else a = m(t, r);
377
492
  if (e.maxWidth !== void 0) {
378
493
  let n = G(e.maxWidth), o = W(a), c = !1;
379
494
  if (o > n && !s) {
380
- let e = r.paddingX ?? 5, n = r.paddingY ?? 5, i = r.boxBorderPadding ?? 1, s = Math.min(e, B), l = Math.min(n, V), u = Math.min(i, H);
495
+ let e = r.paddingX ?? 5, n = r.paddingY ?? 5, i = r.boxBorderPadding ?? 1, s = Math.min(e, V), l = Math.min(n, H), u = Math.min(i, U);
381
496
  if (s < e || l < n || u < i) {
382
- let e = p(t, {
497
+ let e = m(t, {
383
498
  ...r,
384
499
  paddingX: s,
385
500
  paddingY: l,
@@ -389,9 +504,9 @@ async function fe(e, t, n, i) {
389
504
  }
390
505
  }
391
506
  if (o > n) {
392
- let t = e.maxWidth === "auto" ? "detected terminal width" : "--max-width", r = c ? `Compact spacing (-x ${B} -y ${V} -p ${H}) was already applied automatically. ` : "", a = c ? "Try a narrower direction (LR vs TD), shorter labels, or widen your terminal." : "Try -x/-y/-p for tighter spacing, a narrower direction (LR vs TD), or widen your terminal.";
507
+ let t = e.maxWidth === "auto" ? "detected terminal width" : "--max-width", r = c ? `Compact spacing (-x ${V} -y ${H} -p ${U}) was already applied automatically. ` : "", a = c ? "Try a narrower direction (LR vs TD), shorter labels, or widen your terminal." : "Try -x/-y/-p for tighter spacing, a narrower direction (LR vs TD), or widen your terminal.";
393
508
  i.write(`Warning: ASCII output is ${o} columns wide, exceeding ${t} of ${n}. ${r}zombie-mermaid does not reflow diagram structure to fit (no label wrapping or direction-flip cascade — see https://github.com/dfadler/zombie-mermaid/issues/335). ${a}\n`);
394
- } else c && i.write(`Note: ASCII output exceeded ${n} columns at the requested spacing; applied compact spacing automatically (-x ${B} -y ${V} -p ${H}) to fit.\n`);
509
+ } else c && i.write(`Note: ASCII output exceeded ${n} columns at the requested spacing; applied compact spacing automatically (-x ${V} -y ${H} -p ${U}) to fit.\n`);
395
510
  }
396
511
  await q(K(u === void 0, u), a + "\n", n, e.force);
397
512
  }
@@ -401,15 +516,15 @@ async function fe(e, t, n, i) {
401
516
  resolveColors: e.resolveColors
402
517
  };
403
518
  e.direction !== void 0 && (r.direction = e.direction);
404
- let i = h(t, r);
519
+ let i = y(t, r);
405
520
  await q(K(s, d), i, n, e.force);
406
521
  }
407
522
  if (e.html) {
408
- let i = L({
409
- svg: h(t, o ?? {}),
523
+ let i = _e({
524
+ svg: y(t, o ?? {}),
410
525
  title: e.input ? r(e.input).name : void 0
411
526
  });
412
- await q(K(c, m), i, n, e.force);
527
+ await q(K(c, p), i, n, e.force);
413
528
  }
414
529
  if (e.png) {
415
530
  let r = {
@@ -417,8 +532,8 @@ async function fe(e, t, n, i) {
417
532
  resolveColors: !0
418
533
  };
419
534
  e.direction !== void 0 && (r.direction = e.direction);
420
- let i = await R(h(t, r));
421
- await q(K(l, g), i, n, e.force);
535
+ let i = await ve(y(t, r));
536
+ await q(K(l, h), i, n, e.force);
422
537
  }
423
538
  }
424
539
  function K(e, t) {
@@ -432,13 +547,13 @@ async function q(e, t, n, r) {
432
547
  case "stdout":
433
548
  n.write(t);
434
549
  break;
435
- case "file": await me(e.path, t, r);
550
+ case "file": await we(e.path, t, r);
436
551
  }
437
552
  }
438
553
  function J(e) {
439
554
  return /* @__PURE__ */ Error(`Refusing to overwrite existing file "${e}" — pass --force to replace it`);
440
555
  }
441
- async function pe(e, t) {
556
+ async function Ce(e, t) {
442
557
  if (t) return;
443
558
  let n = !0;
444
559
  try {
@@ -448,7 +563,7 @@ async function pe(e, t) {
448
563
  }
449
564
  if (n) throw J(e);
450
565
  }
451
- async function me(e, t, n) {
566
+ async function we(e, t, n) {
452
567
  try {
453
568
  await d(e, t, {
454
569
  encoding: "utf-8",
@@ -466,18 +581,18 @@ async function Y() {
466
581
  }
467
582
  //#endregion
468
583
  //#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];
584
+ var Te = /^ {0,3}(`{3,}|~{3,})\s*mermaid\b[^`]*$/i, Ee = /^ {0,3}(`+|~+)\s*$/;
585
+ function De(e, t) {
586
+ let n = Ee.exec(e)?.[1];
472
587
  return !!n && n[0] === t[0] && n.length >= t.length;
473
588
  }
474
- function ve(e) {
589
+ function Oe(e) {
475
590
  let t = e.split(/\r?\n/), n = [];
476
591
  for (let e = 0; e < t.length; e++) {
477
- let r = he.exec(t[e]);
592
+ let r = Te.exec(t[e]);
478
593
  if (!r) continue;
479
594
  let i = r[1], a = [], o = e + 1;
480
- for (; o < t.length && !_e(t[o], i);) a.push(t[o++]);
595
+ for (; o < t.length && !De(t[o], i);) a.push(t[o++]);
481
596
  n.push({
482
597
  startLine: e + 2,
483
598
  source: a.join("\n")
@@ -485,17 +600,17 @@ function ve(e) {
485
600
  }
486
601
  return n;
487
602
  }
488
- async function ye(e) {
603
+ async function ke(e) {
489
604
  let t = 0, n = 0;
490
605
  for (let r of e.files) {
491
606
  let e = await u(r, "utf-8");
492
- for (let i of ve(e)) {
607
+ for (let i of Oe(e)) {
493
608
  n++;
494
609
  try {
495
- g(i.source);
610
+ b(i.source);
496
611
  } catch (e) {
497
612
  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);
613
+ let n = z(e, i.source), a = i.startLine + (n ? n.line - 1 : 0), o = n ? n.message : e instanceof Error ? e.message : String(e);
499
614
  console.error(`${r}:${a}: ${o}`), n && console.error(` ${a} | ${n.sourceLine}`);
500
615
  }
501
616
  }
@@ -505,7 +620,7 @@ async function ye(e) {
505
620
  }
506
621
  //#endregion
507
622
  //#region src/cli/validate.ts
508
- async function be(e, t) {
623
+ async function Ae(e, t) {
509
624
  let n;
510
625
  if (e.input !== void 0) n = await u(e.input, "utf-8");
511
626
  else if (t !== void 0) n = t;
@@ -516,20 +631,20 @@ async function be(e, t) {
516
631
  let r = n;
517
632
  if (r.trim().length === 0) throw Error("Empty input — provide a Mermaid diagram via file or stdin");
518
633
  try {
519
- g(r);
634
+ b(r);
520
635
  } catch (e) {
521
- throw P(e, r);
636
+ throw B(e, r);
522
637
  }
523
638
  console.log("valid");
524
639
  }
525
640
  //#endregion
526
641
  //#region src/cli/web.ts
527
- var xe = 1e6;
528
- function Se(e) {
642
+ var je = 1e6;
643
+ function Me(e) {
529
644
  return new Promise((t, n) => {
530
645
  let r = [], i = 0;
531
646
  e.on("data", (t) => {
532
- if (i += t.length, i > xe) {
647
+ if (i += t.length, i > je) {
533
648
  n(/* @__PURE__ */ Error("Request body too large")), e.destroy();
534
649
  return;
535
650
  }
@@ -540,7 +655,7 @@ function Se(e) {
540
655
  function X(e) {
541
656
  return typeof e == "object" && !!e;
542
657
  }
543
- function Z(e) {
658
+ function Ne(e) {
544
659
  let t;
545
660
  try {
546
661
  t = JSON.parse(e);
@@ -555,80 +670,78 @@ function Z(e) {
555
670
  format: t.format === "svg" ? "svg" : "ascii"
556
671
  };
557
672
  }
558
- function Q(e, t, n) {
673
+ function Z(e, t, n) {
559
674
  let r = JSON.stringify(n);
560
675
  e.writeHead(t, {
561
676
  "Content-Type": "application/json; charset=utf-8",
562
677
  "Content-Length": Buffer.byteLength(r)
563
678
  }), e.end(r);
564
679
  }
565
- async function Ce(e, t) {
680
+ async function Pe(e, t) {
566
681
  try {
567
- let { source: n, format: r } = Z(await Se(e));
568
- Q(t, 200, {
569
- output: r === "svg" ? h(n) : p(n),
682
+ let { source: n, format: r } = Ne(await Me(e));
683
+ Z(t, 200, {
684
+ output: r === "svg" ? y(n) : m(n),
570
685
  format: r
571
686
  });
572
687
  } catch (e) {
573
- Q(t, 400, { error: e instanceof Error ? e.message : String(e) });
688
+ Z(t, 400, { error: e instanceof Error ? e.message : String(e) });
574
689
  }
575
690
  }
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() {
578
- return v((e, t) => {
691
+ var Fe = "<!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";
692
+ function Q() {
693
+ return S((e, t) => {
579
694
  let n = (e.url ?? "/").split("?")[0];
580
695
  if (e.method === "GET" && (n === "/" || n === "")) {
581
- t.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), t.end(we);
696
+ t.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), t.end(Fe);
582
697
  return;
583
698
  }
584
699
  if (e.method === "POST" && n === "/render") {
585
- Ce(e, t);
700
+ Pe(e, t);
586
701
  return;
587
702
  }
588
703
  t.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }), t.end("Not found");
589
704
  });
590
705
  }
591
- function Ee(e) {
706
+ function Ie(e) {
592
707
  return new Promise((t, n) => {
593
- let r = Te();
708
+ let r = Q();
594
709
  r.once("error", n), r.listen(e, "127.0.0.1", () => {
595
710
  r.removeListener("error", n), t(r);
596
711
  });
597
712
  });
598
713
  }
599
- async function De(e, t = console.log) {
600
- let n = await Ee(e.port), r = n.address();
714
+ async function Le(e, t = console.log) {
715
+ let n = await Ie(e.port), r = n.address();
601
716
  return t(`zombie-mermaid web running at http://localhost:${X(r) && typeof r.port == "number" ? r.port : e.port}`), n;
602
717
  }
603
718
  //#endregion
604
719
  //#region src/cli/mcp.ts
605
- async function Oe(e = (e) => console.error(e)) {
606
- let t = b(), n = new y();
720
+ async function Re(e = (e) => console.error(e)) {
721
+ let t = w(), n = new C();
607
722
  return await t.connect(n), e("zombie-mermaid MCP server running on stdio (experimental — feedback welcome: https://github.com/dfadler/zombie-mermaid/issues)"), t;
608
723
  }
609
724
  //#endregion
610
725
  //#region src/package-info.ts
611
- function ke() {
726
+ function ze() {
612
727
  let t = e(import.meta.url)("../package.json");
613
728
  return typeof t == "object" && t && "version" in t && typeof t.version == "string" ? t.version : "unknown";
614
729
  }
615
- //#endregion
616
- //#region src/cli.ts
617
- m(x);
618
- var Ae = ke();
730
+ v(T), L();
731
+ var Be = ze();
619
732
  async function $() {
620
733
  let e = process.argv.slice(2), t;
621
734
  try {
622
- t = D(e);
735
+ t = te(e);
623
736
  } catch (e) {
624
737
  console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
625
738
  }
626
739
  switch (t.command) {
627
740
  case "help":
628
- je();
741
+ Ve();
629
742
  break;
630
743
  case "version":
631
- console.log(`zombie-mermaid ${Ae}`);
744
+ console.log(`zombie-mermaid ${Be}`);
632
745
  break;
633
746
  case "themes":
634
747
  console.log("Available themes:\n");
@@ -636,40 +749,40 @@ async function $() {
636
749
  break;
637
750
  case "render":
638
751
  try {
639
- await de(t);
752
+ await xe(t);
640
753
  } catch (e) {
641
754
  console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
642
755
  }
643
756
  break;
644
757
  case "validate":
645
758
  try {
646
- await be(t);
759
+ await Ae(t);
647
760
  } catch (e) {
648
761
  console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
649
762
  }
650
763
  break;
651
764
  case "check":
652
765
  try {
653
- await ye(t);
766
+ await ke(t);
654
767
  } catch (e) {
655
768
  console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
656
769
  }
657
770
  break;
658
771
  case "web":
659
772
  try {
660
- await De(t);
773
+ await Le(t);
661
774
  } catch (e) {
662
775
  console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
663
776
  }
664
777
  break;
665
778
  case "mcp": try {
666
- await Oe();
779
+ await Re();
667
780
  } catch (e) {
668
781
  console.error(`Error: ${e instanceof Error ? e.message : String(e)}`), process.exit(1);
669
782
  }
670
783
  }
671
784
  }
672
- function je() {
785
+ function Ve() {
673
786
  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");
674
787
  }
675
788
  $();
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@zombie-mermaid/core"),t=require("@zombie-mermaid/mermaid-parser"),n=require("@zombie-mermaid/ascii-renderer"),r=require("@zombie-mermaid/svg-renderer");Object.defineProperty(exports,"DEFAULTS",{enumerable:!0,get:function(){return e.DEFAULTS}}),Object.defineProperty(exports,"DEFAULT_RENDER_LIMITS",{enumerable:!0,get:function(){return e.DEFAULT_RENDER_LIMITS}}),Object.defineProperty(exports,"ElkNotRegisteredError",{enumerable:!0,get:function(){return r.ElkNotRegisteredError}}),Object.defineProperty(exports,"LimitExceededError",{enumerable:!0,get:function(){return e.LimitExceededError}}),Object.defineProperty(exports,"THEMES",{enumerable:!0,get:function(){return e.THEMES}}),Object.defineProperty(exports,"createLayoutCache",{enumerable:!0,get:function(){return r.createLayoutCache}}),Object.defineProperty(exports,"fromShikiTheme",{enumerable:!0,get:function(){return e.fromShikiTheme}}),Object.defineProperty(exports,"loadEmbeddedFont",{enumerable:!0,get:function(){return r.loadEmbeddedFont}}),Object.defineProperty(exports,"parseMermaid",{enumerable:!0,get:function(){return t.parseMermaid}}),Object.defineProperty(exports,"registerElk",{enumerable:!0,get:function(){return r.registerElk}}),Object.defineProperty(exports,"renderMermaid",{enumerable:!0,get:function(){return r.renderMermaid}}),Object.defineProperty(exports,"renderMermaidASCII",{enumerable:!0,get:function(){return n.renderMermaidASCII}}),Object.defineProperty(exports,"renderMermaidAscii",{enumerable:!0,get:function(){return n.renderMermaidAscii}}),Object.defineProperty(exports,"renderMermaidSVG",{enumerable:!0,get:function(){return r.renderMermaidSVG}}),Object.defineProperty(exports,"renderMermaidSVGAsync",{enumerable:!0,get:function(){return r.renderMermaidSVGAsync}}),Object.defineProperty(exports,"renderMermaidSync",{enumerable:!0,get:function(){return r.renderMermaidSync}}),Object.defineProperty(exports,"themeCssVariables",{enumerable:!0,get:function(){return r.themeCssVariables}}),Object.defineProperty(exports,"validateMermaid",{enumerable:!0,get:function(){return r.validateMermaid}});
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@zombie-mermaid/ascii-renderer"),t=require("@zombie-mermaid/svg-renderer"),n=require("@zombie-mermaid/core"),r=require("@zombie-mermaid/mermaid-parser");var i=.5,a=(e,t,n)=>{if(e.diamond){let[r,i,a]=n?[t.x-e.cx,e.w/2,t.y-e.cy]:[t.y-e.cy,e.h/2,t.x-e.cx];return Math.abs(r)/i>=.49?n?r<0?`L`:`R`:r<0?`T`:`B`:n?a<0?`T`:`B`:a<0?`L`:`R`}let r={T:Math.abs(t.y-(e.cy-e.h/2)),B:Math.abs(t.y-(e.cy+e.h/2)),L:Math.abs(t.x-(e.cx-e.w/2)),R:Math.abs(t.x-(e.cx+e.w/2))};return Object.keys(r).reduce((e,t)=>r[t]<r[e]?t:e)};function o(e,t,n){let r=n===`LR`||n===`RL`,o=e=>r?e.cx:e.cy,s=e=>r?e.cy:e.cx,c=e=>(r?e.w:e.h)/2,l=[...e].sort((e,t)=>o(e)-o(t)),u=new Map,d=-1,f,p=(e,t)=>Math.abs(e-t)<=20;for(let e of l)(!f||!(p(o(e),o(f))||p(o(e)-c(e),o(f)-c(f))||p(o(e)+c(e),o(f)+c(f))))&&(d++,f=e),u.set(e.id,d);let m=d,h=n===`RL`||n===`BT`,g=new Map,_=new Map;for(let t of[...e].sort((e,t)=>s(e)-s(t))){let e=u.get(t.id),n=_.get(e)??0;_.set(e,n+1),g.set(t.id,{layer:h?m-e:e,order:n,cross:s(t),size:r?t.h:t.w})}let v=new Map(e.map(e=>[e.id,e])),y=new Map,b=t.map((e,t)=>{let n=v.get(e.from),i=v.get(e.to);if(!n||!i||e.points.length<2)return;let o=e.points[0],s=e.points[e.points.length-1],c=(e,n,i)=>{let o=a(e,n,r),s={edge:t,start:i,face:o,at:o===`T`||o===`B`?n.x:n.y},c=`${e.id}|${o}`;return y.set(c,[...y.get(c)??[],s]),s};return{p:e,s:c(n,o,!0),e:c(i,s,!1)}}),x=(e,t)=>{let n=[...y.get(`${e}|${t.face}`)].map(e=>e.at);return new Set(n.filter(e=>e<t.at-i)).size},S=[];return b.forEach((e,t)=>{e&&S.push({index:t,from:e.p.from,to:e.p.to,start:{face:e.s.face,order:x(e.p.from,e.s)},end:{face:e.e.face,order:x(e.p.to,e.e)}})}),{nodes:g,edges:S}}var s=e=>{try{return e()}catch(e){if(e instanceof t.ElkNotRegisteredError)return;throw e}},c={graph:e=>s(()=>{let n=(0,t.layoutFlowchartSync)(e);return o(n.nodes.map(e=>({id:e.id,cx:e.x+e.width/2,cy:e.y+e.height/2,w:e.width,h:e.height,diamond:e.shape===`diamond`})),n.edges.map(e=>({from:e.source,to:e.target,points:e.points})),e.direction)}),er:e=>s(()=>{let n=(0,t.layoutErDiagramSync)(e);return o(n.entities.map(e=>({id:e.id,cx:e.x+e.width/2,cy:e.y+e.height/2,w:e.width,h:e.height})),n.relationships.map(e=>({from:e.entity1,to:e.entity2,points:e.points})),e.direction??`TD`)})};function l(){(0,e.registerAsciiLayout)(c)}l(),Object.defineProperty(exports,"DEFAULTS",{enumerable:!0,get:function(){return n.DEFAULTS}}),Object.defineProperty(exports,"DEFAULT_RENDER_LIMITS",{enumerable:!0,get:function(){return n.DEFAULT_RENDER_LIMITS}}),Object.defineProperty(exports,"ElkNotRegisteredError",{enumerable:!0,get:function(){return t.ElkNotRegisteredError}}),Object.defineProperty(exports,"LimitExceededError",{enumerable:!0,get:function(){return n.LimitExceededError}}),Object.defineProperty(exports,"THEMES",{enumerable:!0,get:function(){return n.THEMES}}),Object.defineProperty(exports,"createLayoutCache",{enumerable:!0,get:function(){return t.createLayoutCache}}),Object.defineProperty(exports,"fromShikiTheme",{enumerable:!0,get:function(){return n.fromShikiTheme}}),Object.defineProperty(exports,"loadEmbeddedFont",{enumerable:!0,get:function(){return t.loadEmbeddedFont}}),Object.defineProperty(exports,"parseMermaid",{enumerable:!0,get:function(){return r.parseMermaid}}),Object.defineProperty(exports,"registerElk",{enumerable:!0,get:function(){return t.registerElk}}),Object.defineProperty(exports,"renderMermaid",{enumerable:!0,get:function(){return t.renderMermaid}}),Object.defineProperty(exports,"renderMermaidASCII",{enumerable:!0,get:function(){return e.renderMermaidASCII}}),Object.defineProperty(exports,"renderMermaidAscii",{enumerable:!0,get:function(){return e.renderMermaidAscii}}),Object.defineProperty(exports,"renderMermaidSVG",{enumerable:!0,get:function(){return t.renderMermaidSVG}}),Object.defineProperty(exports,"renderMermaidSVGAsync",{enumerable:!0,get:function(){return t.renderMermaidSVGAsync}}),Object.defineProperty(exports,"renderMermaidSync",{enumerable:!0,get:function(){return t.renderMermaidSync}}),Object.defineProperty(exports,"themeCssVariables",{enumerable:!0,get:function(){return t.themeCssVariables}}),Object.defineProperty(exports,"validateMermaid",{enumerable:!0,get:function(){return t.validateMermaid}});
package/dist/index.js CHANGED
@@ -1,5 +1,124 @@
1
- import { DEFAULTS as e, DEFAULT_RENDER_LIMITS as t, LimitExceededError as n, THEMES as r, fromShikiTheme as i } from "@zombie-mermaid/core";
2
- import { parseMermaid as a } from "@zombie-mermaid/mermaid-parser";
3
- import { renderMermaidASCII as o, renderMermaidAscii as s } from "@zombie-mermaid/ascii-renderer";
4
- import { ElkNotRegisteredError as c, createLayoutCache as l, loadEmbeddedFont as u, registerElk as d, renderMermaid as f, renderMermaidSVG as p, renderMermaidSVGAsync as m, renderMermaidSync as h, themeCssVariables as g, validateMermaid as _ } from "@zombie-mermaid/svg-renderer";
5
- export { e as DEFAULTS, t as DEFAULT_RENDER_LIMITS, c as ElkNotRegisteredError, n as LimitExceededError, r as THEMES, l as createLayoutCache, i as fromShikiTheme, u as loadEmbeddedFont, a as parseMermaid, d as registerElk, f as renderMermaid, o as renderMermaidASCII, s as renderMermaidAscii, p as renderMermaidSVG, m as renderMermaidSVGAsync, h as renderMermaidSync, g as themeCssVariables, _ as validateMermaid };
1
+ import { registerAsciiLayout as e, renderMermaidASCII as t, renderMermaidAscii as n } from "@zombie-mermaid/ascii-renderer";
2
+ import { ElkNotRegisteredError as r, ElkNotRegisteredError as i, createLayoutCache as a, layoutErDiagramSync as o, layoutFlowchartSync as s, loadEmbeddedFont as c, registerElk as l, renderMermaid as u, renderMermaidSVG as d, renderMermaidSVGAsync as f, renderMermaidSync as p, themeCssVariables as m, validateMermaid as h } from "@zombie-mermaid/svg-renderer";
3
+ import { DEFAULTS as g, DEFAULT_RENDER_LIMITS as _, LimitExceededError as v, THEMES as y, fromShikiTheme as b } from "@zombie-mermaid/core";
4
+ import { parseMermaid as x } from "@zombie-mermaid/mermaid-parser";
5
+ var S = .5, C = (e, t, n) => {
6
+ if (e.diamond) {
7
+ let [r, i, a] = n ? [
8
+ t.x - e.cx,
9
+ e.w / 2,
10
+ t.y - e.cy
11
+ ] : [
12
+ t.y - e.cy,
13
+ e.h / 2,
14
+ t.x - e.cx
15
+ ];
16
+ return Math.abs(r) / i >= .49 ? n ? r < 0 ? "L" : "R" : r < 0 ? "T" : "B" : n ? a < 0 ? "T" : "B" : a < 0 ? "L" : "R";
17
+ }
18
+ let r = {
19
+ T: Math.abs(t.y - (e.cy - e.h / 2)),
20
+ B: Math.abs(t.y - (e.cy + e.h / 2)),
21
+ L: Math.abs(t.x - (e.cx - e.w / 2)),
22
+ R: Math.abs(t.x - (e.cx + e.w / 2))
23
+ };
24
+ return Object.keys(r).reduce((e, t) => r[t] < r[e] ? t : e);
25
+ };
26
+ function w(e, t, n) {
27
+ let r = n === "LR" || n === "RL", i = (e) => r ? e.cx : e.cy, a = (e) => r ? e.cy : e.cx, o = (e) => (r ? e.w : e.h) / 2, s = [...e].sort((e, t) => i(e) - i(t)), c = /* @__PURE__ */ new Map(), l = -1, u, d = (e, t) => Math.abs(e - t) <= 20;
28
+ for (let e of s) (!u || !(d(i(e), i(u)) || d(i(e) - o(e), i(u) - o(u)) || d(i(e) + o(e), i(u) + o(u)))) && (l++, u = e), c.set(e.id, l);
29
+ let f = l, p = n === "RL" || n === "BT", m = /* @__PURE__ */ new Map(), h = /* @__PURE__ */ new Map();
30
+ for (let t of [...e].sort((e, t) => a(e) - a(t))) {
31
+ let e = c.get(t.id), n = h.get(e) ?? 0;
32
+ h.set(e, n + 1), m.set(t.id, {
33
+ layer: p ? f - e : e,
34
+ order: n,
35
+ cross: a(t),
36
+ size: r ? t.h : t.w
37
+ });
38
+ }
39
+ let g = new Map(e.map((e) => [e.id, e])), _ = /* @__PURE__ */ new Map(), v = t.map((e, t) => {
40
+ let n = g.get(e.from), i = g.get(e.to);
41
+ if (!n || !i || e.points.length < 2) return;
42
+ let a = e.points[0], o = e.points[e.points.length - 1], s = (e, n, i) => {
43
+ let a = C(e, n, r), o = {
44
+ edge: t,
45
+ start: i,
46
+ face: a,
47
+ at: a === "T" || a === "B" ? n.x : n.y
48
+ }, s = `${e.id}|${a}`;
49
+ return _.set(s, [..._.get(s) ?? [], o]), o;
50
+ };
51
+ return {
52
+ p: e,
53
+ s: s(n, a, !0),
54
+ e: s(i, o, !1)
55
+ };
56
+ }), y = (e, t) => {
57
+ let n = [..._.get(`${e}|${t.face}`)].map((e) => e.at);
58
+ return new Set(n.filter((e) => e < t.at - S)).size;
59
+ }, b = [];
60
+ return v.forEach((e, t) => {
61
+ e && b.push({
62
+ index: t,
63
+ from: e.p.from,
64
+ to: e.p.to,
65
+ start: {
66
+ face: e.s.face,
67
+ order: y(e.p.from, e.s)
68
+ },
69
+ end: {
70
+ face: e.e.face,
71
+ order: y(e.p.to, e.e)
72
+ }
73
+ });
74
+ }), {
75
+ nodes: m,
76
+ edges: b
77
+ };
78
+ }
79
+ var T = (e) => {
80
+ try {
81
+ return e();
82
+ } catch (e) {
83
+ if (e instanceof i) return;
84
+ throw e;
85
+ }
86
+ }, E = {
87
+ graph: (e) => T(() => {
88
+ let t = s(e);
89
+ return w(t.nodes.map((e) => ({
90
+ id: e.id,
91
+ cx: e.x + e.width / 2,
92
+ cy: e.y + e.height / 2,
93
+ w: e.width,
94
+ h: e.height,
95
+ diamond: e.shape === "diamond"
96
+ })), t.edges.map((e) => ({
97
+ from: e.source,
98
+ to: e.target,
99
+ points: e.points
100
+ })), e.direction);
101
+ }),
102
+ er: (e) => T(() => {
103
+ let t = o(e);
104
+ return w(t.entities.map((e) => ({
105
+ id: e.id,
106
+ cx: e.x + e.width / 2,
107
+ cy: e.y + e.height / 2,
108
+ w: e.width,
109
+ h: e.height
110
+ })), t.relationships.map((e) => ({
111
+ from: e.entity1,
112
+ to: e.entity2,
113
+ points: e.points
114
+ })), e.direction ?? "TD");
115
+ })
116
+ };
117
+ function D() {
118
+ e(E);
119
+ }
120
+ //#endregion
121
+ //#region src/index.ts
122
+ D();
123
+ //#endregion
124
+ export { g as DEFAULTS, _ as DEFAULT_RENDER_LIMITS, r as ElkNotRegisteredError, v as LimitExceededError, y as THEMES, a as createLayoutCache, b as fromShikiTheme, c as loadEmbeddedFont, x as parseMermaid, l as registerElk, u as renderMermaid, t as renderMermaidASCII, n as renderMermaidAscii, d as renderMermaidSVG, f as renderMermaidSVGAsync, p as renderMermaidSync, m as themeCssVariables, h as validateMermaid };
package/dist/mcp.cjs CHANGED
@@ -1 +1 @@
1
- var e=require("@zombie-mermaid/mcp");Object.keys(e).forEach(function(t){t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:function(){return e[t]}})});
1
+ let e=require("@zombie-mermaid/ascii-renderer"),t=require("@zombie-mermaid/svg-renderer");var n=.5,r=(e,t,n)=>{if(e.diamond){let[r,i,a]=n?[t.x-e.cx,e.w/2,t.y-e.cy]:[t.y-e.cy,e.h/2,t.x-e.cx];return Math.abs(r)/i>=.49?n?r<0?`L`:`R`:r<0?`T`:`B`:n?a<0?`T`:`B`:a<0?`L`:`R`}let r={T:Math.abs(t.y-(e.cy-e.h/2)),B:Math.abs(t.y-(e.cy+e.h/2)),L:Math.abs(t.x-(e.cx-e.w/2)),R:Math.abs(t.x-(e.cx+e.w/2))};return Object.keys(r).reduce((e,t)=>r[t]<r[e]?t:e)};function i(e,t,i){let a=i===`LR`||i===`RL`,o=e=>a?e.cx:e.cy,s=e=>a?e.cy:e.cx,c=e=>(a?e.w:e.h)/2,l=[...e].sort((e,t)=>o(e)-o(t)),u=new Map,d=-1,f,p=(e,t)=>Math.abs(e-t)<=20;for(let e of l)(!f||!(p(o(e),o(f))||p(o(e)-c(e),o(f)-c(f))||p(o(e)+c(e),o(f)+c(f))))&&(d++,f=e),u.set(e.id,d);let m=d,h=i===`RL`||i===`BT`,g=new Map,_=new Map;for(let t of[...e].sort((e,t)=>s(e)-s(t))){let e=u.get(t.id),n=_.get(e)??0;_.set(e,n+1),g.set(t.id,{layer:h?m-e:e,order:n,cross:s(t),size:a?t.h:t.w})}let v=new Map(e.map(e=>[e.id,e])),y=new Map,b=t.map((e,t)=>{let n=v.get(e.from),i=v.get(e.to);if(!n||!i||e.points.length<2)return;let o=e.points[0],s=e.points[e.points.length-1],c=(e,n,i)=>{let o=r(e,n,a),s={edge:t,start:i,face:o,at:o===`T`||o===`B`?n.x:n.y},c=`${e.id}|${o}`;return y.set(c,[...y.get(c)??[],s]),s};return{p:e,s:c(n,o,!0),e:c(i,s,!1)}}),x=(e,t)=>{let r=[...y.get(`${e}|${t.face}`)].map(e=>e.at);return new Set(r.filter(e=>e<t.at-n)).size},S=[];return b.forEach((e,t)=>{e&&S.push({index:t,from:e.p.from,to:e.p.to,start:{face:e.s.face,order:x(e.p.from,e.s)},end:{face:e.e.face,order:x(e.p.to,e.e)}})}),{nodes:g,edges:S}}var a=e=>{try{return e()}catch(e){if(e instanceof t.ElkNotRegisteredError)return;throw e}},o={graph:e=>a(()=>{let n=(0,t.layoutFlowchartSync)(e);return i(n.nodes.map(e=>({id:e.id,cx:e.x+e.width/2,cy:e.y+e.height/2,w:e.width,h:e.height,diamond:e.shape===`diamond`})),n.edges.map(e=>({from:e.source,to:e.target,points:e.points})),e.direction)}),er:e=>a(()=>{let n=(0,t.layoutErDiagramSync)(e);return i(n.entities.map(e=>({id:e.id,cx:e.x+e.width/2,cy:e.y+e.height/2,w:e.width,h:e.height})),n.relationships.map(e=>({from:e.entity1,to:e.entity2,points:e.points})),e.direction??`TD`)})};function s(){(0,e.registerAsciiLayout)(o)}s();var c=require("@zombie-mermaid/mcp");Object.keys(c).forEach(function(e){e!=="default"&&!Object.prototype.hasOwnProperty.call(exports,e)&&Object.defineProperty(exports,e,{enumerable:!0,get:function(){return c[e]}})});
package/dist/mcp.js CHANGED
@@ -1 +1,122 @@
1
+ import { registerAsciiLayout as e } from "@zombie-mermaid/ascii-renderer";
2
+ import { ElkNotRegisteredError as t, layoutErDiagramSync as n, layoutFlowchartSync as r } from "@zombie-mermaid/svg-renderer";
1
3
  export * from "@zombie-mermaid/mcp";
4
+ var i = .5, a = (e, t, n) => {
5
+ if (e.diamond) {
6
+ let [r, i, a] = n ? [
7
+ t.x - e.cx,
8
+ e.w / 2,
9
+ t.y - e.cy
10
+ ] : [
11
+ t.y - e.cy,
12
+ e.h / 2,
13
+ t.x - e.cx
14
+ ];
15
+ return Math.abs(r) / i >= .49 ? n ? r < 0 ? "L" : "R" : r < 0 ? "T" : "B" : n ? a < 0 ? "T" : "B" : a < 0 ? "L" : "R";
16
+ }
17
+ let r = {
18
+ T: Math.abs(t.y - (e.cy - e.h / 2)),
19
+ B: Math.abs(t.y - (e.cy + e.h / 2)),
20
+ L: Math.abs(t.x - (e.cx - e.w / 2)),
21
+ R: Math.abs(t.x - (e.cx + e.w / 2))
22
+ };
23
+ return Object.keys(r).reduce((e, t) => r[t] < r[e] ? t : e);
24
+ };
25
+ function o(e, t, n) {
26
+ let r = n === "LR" || n === "RL", o = (e) => r ? e.cx : e.cy, s = (e) => r ? e.cy : e.cx, c = (e) => (r ? e.w : e.h) / 2, l = [...e].sort((e, t) => o(e) - o(t)), u = /* @__PURE__ */ new Map(), d = -1, f, p = (e, t) => Math.abs(e - t) <= 20;
27
+ for (let e of l) (!f || !(p(o(e), o(f)) || p(o(e) - c(e), o(f) - c(f)) || p(o(e) + c(e), o(f) + c(f)))) && (d++, f = e), u.set(e.id, d);
28
+ let m = d, h = n === "RL" || n === "BT", g = /* @__PURE__ */ new Map(), _ = /* @__PURE__ */ new Map();
29
+ for (let t of [...e].sort((e, t) => s(e) - s(t))) {
30
+ let e = u.get(t.id), n = _.get(e) ?? 0;
31
+ _.set(e, n + 1), g.set(t.id, {
32
+ layer: h ? m - e : e,
33
+ order: n,
34
+ cross: s(t),
35
+ size: r ? t.h : t.w
36
+ });
37
+ }
38
+ let v = new Map(e.map((e) => [e.id, e])), y = /* @__PURE__ */ new Map(), b = t.map((e, t) => {
39
+ let n = v.get(e.from), i = v.get(e.to);
40
+ if (!n || !i || e.points.length < 2) return;
41
+ let o = e.points[0], s = e.points[e.points.length - 1], c = (e, n, i) => {
42
+ let o = a(e, n, r), s = {
43
+ edge: t,
44
+ start: i,
45
+ face: o,
46
+ at: o === "T" || o === "B" ? n.x : n.y
47
+ }, c = `${e.id}|${o}`;
48
+ return y.set(c, [...y.get(c) ?? [], s]), s;
49
+ };
50
+ return {
51
+ p: e,
52
+ s: c(n, o, !0),
53
+ e: c(i, s, !1)
54
+ };
55
+ }), x = (e, t) => {
56
+ let n = [...y.get(`${e}|${t.face}`)].map((e) => e.at);
57
+ return new Set(n.filter((e) => e < t.at - i)).size;
58
+ }, S = [];
59
+ return b.forEach((e, t) => {
60
+ e && S.push({
61
+ index: t,
62
+ from: e.p.from,
63
+ to: e.p.to,
64
+ start: {
65
+ face: e.s.face,
66
+ order: x(e.p.from, e.s)
67
+ },
68
+ end: {
69
+ face: e.e.face,
70
+ order: x(e.p.to, e.e)
71
+ }
72
+ });
73
+ }), {
74
+ nodes: g,
75
+ edges: S
76
+ };
77
+ }
78
+ var s = (e) => {
79
+ try {
80
+ return e();
81
+ } catch (e) {
82
+ if (e instanceof t) return;
83
+ throw e;
84
+ }
85
+ }, c = {
86
+ graph: (e) => s(() => {
87
+ let t = r(e);
88
+ return o(t.nodes.map((e) => ({
89
+ id: e.id,
90
+ cx: e.x + e.width / 2,
91
+ cy: e.y + e.height / 2,
92
+ w: e.width,
93
+ h: e.height,
94
+ diamond: e.shape === "diamond"
95
+ })), t.edges.map((e) => ({
96
+ from: e.source,
97
+ to: e.target,
98
+ points: e.points
99
+ })), e.direction);
100
+ }),
101
+ er: (e) => s(() => {
102
+ let t = n(e);
103
+ return o(t.entities.map((e) => ({
104
+ id: e.id,
105
+ cx: e.x + e.width / 2,
106
+ cy: e.y + e.height / 2,
107
+ w: e.width,
108
+ h: e.height
109
+ })), t.relationships.map((e) => ({
110
+ from: e.entity1,
111
+ to: e.entity2,
112
+ points: e.points
113
+ })), e.direction ?? "TD");
114
+ })
115
+ };
116
+ function l() {
117
+ e(c);
118
+ }
119
+ //#endregion
120
+ //#region src/mcp-entry.ts
121
+ l();
122
+ //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zombie-mermaid",
3
- "version": "5.14.4",
3
+ "version": "5.15.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.14.4",
119
- "@zombie-mermaid/core": "5.14.4",
120
- "@zombie-mermaid/mcp": "5.14.4",
121
- "@zombie-mermaid/mermaid-parser": "5.14.4",
122
- "@zombie-mermaid/svg-renderer": "5.14.4"
118
+ "@zombie-mermaid/ascii-renderer": "5.15.0",
119
+ "@zombie-mermaid/core": "5.15.0",
120
+ "@zombie-mermaid/svg-renderer": "5.15.0",
121
+ "@zombie-mermaid/mcp": "5.15.0",
122
+ "@zombie-mermaid/mermaid-parser": "5.15.0"
123
123
  },
124
124
  "devDependencies": {
125
125
  "@arethetypeswrong/cli": "0.18.5",
@@ -204,6 +204,7 @@
204
204
  "check:changeset-coverage": "tsx scripts/check-changeset-coverage.ts",
205
205
  "check:docs": "tsx scripts/check-docs.ts",
206
206
  "check:plugin-skill": "tsx scripts/sync-plugin-skill.ts --check",
207
+ "check:llms-full": "tsx scripts/check-llms-full.ts",
207
208
  "check:plugin-lock": "tsx scripts/check-plugin-lock.ts",
208
209
  "sync:plugin-skill": "tsx scripts/sync-plugin-skill.ts",
209
210
  "mcp:server-json": "tsx scripts/generate-server-json.ts",
@@ -246,6 +247,9 @@
246
247
  "label:oracle": "tsx scripts/label-position-oracle.ts",
247
248
  "sequence:oracle": "tsx scripts/sequence-oracle.ts",
248
249
  "quadrant:oracle": "tsx scripts/quadrant-oracle.ts",
250
+ "mindmap:oracle": "tsx scripts/mindmap-oracle.ts",
251
+ "gantt:oracle": "tsx scripts/gantt-oracle.ts",
252
+ "timeline:oracle": "tsx scripts/timeline-oracle.ts",
249
253
  "pie:oracle": "tsx scripts/pie-oracle.ts",
250
254
  "c4:oracle": "tsx scripts/c4-oracle.ts",
251
255
  "xychart:oracle": "tsx scripts/xychart-oracle.ts",